소스 검색

windowed elements

milan jurkulák 2 년 전
부모
커밋
09390d70bb
27개의 변경된 파일과 864개의 추가작업 그리고 653개의 파일을 삭제
  1. 41 2
      src/main/kotlin/eu/mjdev/desktop/Main.kt
  2. 105 71
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  3. 4 1
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt
  4. 4 1
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt
  5. 180 0
      src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlCenter.kt
  6. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlCenterPage.kt
  7. 0 146
      src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlPanel.kt
  8. 2 2
      src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt
  9. 135 97
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  10. 13 17
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt
  11. 3 10
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelText.kt
  12. 8 3
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIcon.kt
  13. 6 5
      src/main/kotlin/eu/mjdev/desktop/components/icon/ShapedIcon.kt
  14. 2 9
      src/main/kotlin/eu/mjdev/desktop/components/slidemenu/SlidingMenu.kt
  15. 10 8
      src/main/kotlin/eu/mjdev/desktop/components/slidemenu/VisibilityState.kt
  16. 73 0
      src/main/kotlin/eu/mjdev/desktop/components/text/AutoResizeText.kt
  17. 19 0
      src/main/kotlin/eu/mjdev/desktop/components/text/FontSizeRange.kt
  18. 42 0
      src/main/kotlin/eu/mjdev/desktop/components/window/FullScreenWindow.kt
  19. 37 0
      src/main/kotlin/eu/mjdev/desktop/components/window/TopWindow.kt
  20. 10 87
      src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt
  21. 9 0
      src/main/kotlin/eu/mjdev/desktop/extensions/Modifier.kt
  22. 6 8
      src/main/kotlin/eu/mjdev/desktop/helpers/WindowFocusState.kt
  23. 66 40
      src/main/kotlin/eu/mjdev/desktop/provider/AppsProvider.kt
  24. 18 35
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  25. 6 0
      src/main/kotlin/eu/mjdev/desktop/provider/data/Theme.kt
  26. 0 110
      src/main/kotlin/eu/mjdev/desktop/screens/MainWindow.kt
  27. 64 0
      src/main/kotlin/eu/mjdev/desktop/windows/MainWindow.kt

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

@@ -1,10 +1,49 @@
 package eu.mjdev.desktop
 
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.application
-import eu.mjdev.desktop.screens.MainWindow
+import eu.mjdev.desktop.components.appsmenu.AppsMenu
+import eu.mjdev.desktop.components.controlpanel.ControlCenter
+import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
+import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.windows.MainWindow
 
+@OptIn(ExperimentalFoundationApi::class)
 fun main() = application(
     exitProcessOnExit = true
 ) {
-    MainWindow()
+    CompositionLocalProvider(
+        LocalDesktop provides DesktopProvider()
+    ) {
+        MaterialTheme {
+            val api: DesktopProvider = LocalDesktop.current
+            val controlCenterState = rememberVisibilityState()
+            val panelState = rememberVisibilityState(true, api.currentUser.config.autoHidePanel)
+            val menuState = rememberVisibilityState()
+            MainWindow(
+                panelState = panelState,
+                controlCenterState = controlCenterState,
+                menuState = menuState
+            )
+            DesktopPanel(
+                panelState = panelState,
+                onMenuIconClicked = {
+                    menuState.toggle()
+                }
+            )
+            AppsMenu(
+                panelState = panelState,
+                bottomY = 64.dp,
+                menuState = menuState,
+            )
+            ControlCenter(
+                controlCenterState = controlCenterState
+            )
+        }
+    }
 }

+ 105 - 71
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -18,11 +18,17 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.WindowPlacement
+import androidx.compose.ui.window.WindowPosition
+import androidx.compose.ui.window.WindowState
+import androidx.compose.ui.window.rememberWindowState
 import eu.mjdev.desktop.components.custom.UserAvatar
 import eu.mjdev.desktop.components.slidemenu.VisibilityState
 import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
-import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
+import eu.mjdev.desktop.components.window.TopWindow
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.setWindowBounds
+import eu.mjdev.desktop.helpers.WindowFocusState.Companion.windowFocusHandler
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.data.App
@@ -31,93 +37,121 @@ import eu.mjdev.desktop.provider.data.Category
 @Composable
 fun AppsMenu(
     modifier: Modifier = Modifier,
-    appMenuExpandedWidth: Dp = 480.dp,
-    appMenuMinHeight: Dp = 640.dp,
-    bottomY: Dp = 0.dp,
-    backgroundColor: Color = Color.SuperDarkGray,
-    menuPadding: PaddingValues = PaddingValues(2.dp),
-    state: VisibilityState = rememberVisibilityState(),
     api: DesktopProvider = LocalDesktop.current,
+    appMenuExpandedWidth: Dp = 480.dp, // todo
+    appMenuMinHeight: Dp = 640.dp, // todo
+    bottomY: Dp = 64.dp, // todo panel height
+    backgroundColor: Color = api.currentUser.theme.backgroundColor,
+    menuPadding: PaddingValues = PaddingValues(2.dp), // todo
+    menuState: VisibilityState = rememberVisibilityState(),
+    panelState: VisibilityState = rememberVisibilityState(),
     items: MutableState<List<Any>> = remember { mutableStateOf(api.appsProvider.appCategories) },
     onVisibilityChange: (visible: Boolean) -> Unit = {},
-) = AnimatedVisibility(
-    state.isVisible,
-    modifier = modifier,
-    enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
-    exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
 ) {
-    Box(
-        modifier = modifier
-            .padding(bottom = bottomY)
-            .width(appMenuExpandedWidth)
-            .heightIn(
-                min = appMenuMinHeight,
-                max = appMenuMinHeight
-            )
+    val windowState: WindowState = rememberWindowState(
+        placement = WindowPlacement.Floating,
+        position = WindowPosition.Aligned(api.currentUser.theme.panelLocation.alignment),
+        size = api.containerSize.copy(
+            width = if (panelState.isVisible) appMenuMinHeight else 0.dp
+        ),
+        isMinimized = false
+    )
+    TopWindow(
+        windowState = windowState,
     ) {
-        Box(
-            modifier = Modifier
-                .fillMaxSize()
-                .padding(menuPadding)
-                .background(backgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
-                .clip(RoundedCornerShape(24.dp))
-                .border(2.dp, backgroundColor, RoundedCornerShape(24.dp))
+        windowFocusHandler { hasFocus -> if (!hasFocus) menuState.hide() }
+        AnimatedVisibility(
+            menuState.isVisible,
+            modifier = modifier,
+            enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
+            exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
         ) {
-            Column(
-                modifier = Modifier.fillMaxSize()
+            Box(
+                modifier = modifier
+                    .padding(bottom = bottomY)
+                    .width(appMenuExpandedWidth)
+                    .heightIn(
+                        min = appMenuMinHeight,
+                        max = appMenuMinHeight
+                    )
             ) {
-                UserAvatar(
-                    avatarSize = 64.dp,
-                    backgroundColor = backgroundColor,
-                    orientation = Orientation.Horizontal
-                )
-                LazyColumn(
+                Box(
                     modifier = Modifier
-                        .padding(bottom = 48.dp)
                         .fillMaxSize()
-                        .padding(24.dp)
+                        .padding(menuPadding)
+                        .background(backgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
+                        .clip(RoundedCornerShape(24.dp))
+                        .border(2.dp, backgroundColor, RoundedCornerShape(24.dp))
                 ) {
-                    when (items.value.firstOrNull()) {
-                        is Category -> {
-                            items(items.value) { item ->
-                                AppsMenuCategory(
-                                    category = item as Category,
-                                    backgroundColor = backgroundColor,
-                                    iconTint = Color.White
-                                ) { category ->
-                                    items.value = api.appsProvider.categoriesAndApps[category.name] ?: emptyList()
+                    Column(
+                        modifier = Modifier.fillMaxSize()
+                    ) {
+                        UserAvatar(
+                            avatarSize = 64.dp,
+                            backgroundColor = backgroundColor,
+                            orientation = Orientation.Horizontal
+                        )
+                        LazyColumn(
+                            modifier = Modifier
+                                .padding(bottom = 48.dp)
+                                .fillMaxSize()
+                                .padding(24.dp)
+                        ) {
+                            when (items.value.firstOrNull()) {
+                                is Category -> {
+                                    items(items.value) { item ->
+                                        AppsMenuCategory(
+                                            category = item as Category,
+                                            backgroundColor = backgroundColor,
+                                            iconTint = Color.White
+                                        ) { category ->
+                                            items.value =
+                                                api.appsProvider.categoriesAndApps[category.name] ?: emptyList()
+                                        }
+                                    }
                                 }
-                            }
-                        }
 
-                        is App -> {
-                            items(items.value) { item ->
-                                AppsMenuApp(
-                                    app = item as App,
-                                    backgroundColor = backgroundColor,
-                                    iconTint = Color.White
-                                ) { app ->
-                                    app?.start()
-                                    state.hide(0)
+                                is App -> {
+                                    items(items.value) { item ->
+                                        AppsMenuApp(
+                                            app = item as App,
+                                            backgroundColor = backgroundColor,
+                                            iconTint = Color.White
+                                        ) { app ->
+                                            app?.start()
+                                            menuState.hide()
+                                        }
+                                    }
                                 }
                             }
                         }
                     }
+                    AppsBottomBar(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .wrapContentHeight()
+                            .align(Alignment.BottomStart)
+                            .background(backgroundColor)
+                    )
                 }
             }
-            AppsBottomBar(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .wrapContentHeight()
-                    .align(Alignment.BottomStart)
-                    .background(backgroundColor)
-            )
-        }
-    }
-    launchedEffect(state.isVisible) {
-        if (!state.isVisible) {
-            items.value = api.appsProvider.appCategories
+            launchedEffect(menuState.isVisible) { isVisible ->
+                val x = 0.dp
+                val y = when (isVisible) {
+                    true -> (api.containerSize.height - appMenuMinHeight)
+                    else -> api.containerSize.height
+                }
+                val width = api.containerSize.width
+                val height = when (isVisible) {
+                    true -> appMenuMinHeight
+                    else -> 0.dp
+                }
+                window.setWindowBounds(x, y, width, height)
+                if (!isVisible) {
+                    items.value = api.appsProvider.appCategories
+                }
+                onVisibilityChange(isVisible)
+            }
         }
-        onVisibilityChange(state.isVisible)
     }
-}
+}

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

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.components.appsmenu
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -39,7 +40,9 @@ fun AppsMenuApp(
         iconId = materialIcon,
         iconSize = iconSize,
         iconColor = iconTint,
-        iconBackgroundColor = backgroundColor
+        iconBackgroundColor = backgroundColor,
+        outerPadding = PaddingValues(2.dp),
+        innerPadding = PaddingValues(0.dp)
     )
     TextAny(
         modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),

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

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.components.appsmenu
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -38,7 +39,9 @@ fun AppsMenuCategory(
         iconId = materialIcon,
         iconSize = iconSize,
         iconColor = iconTint,
-        iconBackgroundColor = backgroundColor
+        iconBackgroundColor = backgroundColor,
+        outerPadding = PaddingValues(2.dp),
+        innerPadding = PaddingValues(0.dp)
     )
     TextAny(
         modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),

+ 180 - 0
src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlCenter.kt

@@ -0,0 +1,180 @@
+package eu.mjdev.desktop.components.controlpanel
+
+import androidx.compose.animation.*
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+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 androidx.compose.ui.window.WindowPlacement
+import androidx.compose.ui.window.WindowPosition
+import androidx.compose.ui.window.WindowState
+import androidx.compose.ui.window.rememberWindowState
+import eu.mjdev.desktop.components.shadow.LeftShadow
+import eu.mjdev.desktop.components.shadow.RightShadow
+import eu.mjdev.desktop.components.slidemenu.SlidingMenu
+import eu.mjdev.desktop.components.slidemenu.VisibilityState
+import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.window.TopWindow
+import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.setWindowBounds
+import eu.mjdev.desktop.helpers.WindowFocusState.Companion.windowFocusHandler
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@Suppress("FunctionName")
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun ControlCenter(
+    api: DesktopProvider = LocalDesktop.current,
+    backgroundColor: Color = api.currentUser.theme.backgroundColor,
+    backgroundAlpha: Float = api.currentUser.theme.controlCenterBackgroundAlpha,
+    shadowColor: Color = backgroundColor, // todo theme
+    controlCenterDividerColor: Color = backgroundColor,// todo theme
+    controlCenterExpandedWidth: Dp = api.currentUser.theme.controlCenterExpandedWidth,
+    controlCenterDividerWidth: Dp = api.currentUser.theme.controlCenterDividerWidth,
+    controlCenterIconColor: Color = api.currentUser.theme.controlCenterIconColor,
+    controlCenterIconSize: DpSize = api.currentUser.theme.controlCenterIconSize,
+    pages: List<ControlCenterPage> = api.controlCenterPages,
+    pagerState: PagerState = rememberPagerState(pageCount = { pages.size }, initialPage = 0),
+    controlCenterState: VisibilityState = rememberVisibilityState(),
+    scope: CoroutineScope = rememberCoroutineScope(),
+    onVisibilityChange: (visible: Boolean) -> Unit = {},
+) {
+    val windowState: WindowState = rememberWindowState(
+        placement = WindowPlacement.Floating,
+        position = WindowPosition.Aligned(api.currentUser.theme.controlCenterLocation.alignment),
+        size = api.containerSize.copy(width = controlCenterDividerWidth),
+        isMinimized = false
+    )
+    TopWindow(
+        windowState = windowState,
+    ) {
+        windowFocusHandler { hasFocus -> if (!hasFocus) controlCenterState.hide() }
+        SlidingMenu(
+            orientation = Orientation.Horizontal,
+            state = controlCenterState,
+            onVisibilityChange = onVisibilityChange
+        ) { isVisible ->
+            if (!isVisible) {
+                Divider(
+                    modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
+                    color = Color.Transparent,
+                    thickness = controlCenterDividerWidth
+                )
+            }
+            AnimatedVisibility(
+                isVisible,
+                enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }),
+                exit = slideOutHorizontally(targetOffsetX = { it }) + fadeOut(),
+            ) {
+                LeftShadow(
+                    modifier = Modifier.fillMaxHeight().wrapContentSize(),
+                    color = shadowColor,
+                    contentBackgroundColor = backgroundColor.copy(alpha = backgroundAlpha)
+                ) {
+                    Row(
+                        modifier = Modifier.fillMaxHeight().wrapContentSize(),
+                    ) {
+                        Divider(
+                            modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
+                            color = controlCenterDividerColor,
+                            thickness = controlCenterDividerWidth
+                        )
+                        Box(
+                            contentAlignment = Alignment.TopEnd
+                        ) {
+                            LazyColumn(
+                                modifier = Modifier
+                                    .fillMaxHeight()
+                                    .background(backgroundColor)
+                                    .padding(8.dp),
+                                verticalArrangement = Arrangement.Top,
+                                horizontalAlignment = Alignment.Start
+                            ) {
+                                itemsIndexed(pages) { idx, page ->
+                                    val isSelected = (pagerState.currentPage == idx)
+                                    Icon(
+                                        modifier = Modifier
+                                            .padding(vertical = 8.dp)
+                                            .size(controlCenterIconSize)
+                                            .background(
+                                                color = if (isSelected) Color.White.copy(alpha = 0.4f) else Color.Transparent,
+                                                shape = RoundedCornerShape(8.dp)
+                                            )
+                                            .clickable {
+                                                scope.launch {
+                                                    pagerState.scrollToPage(idx)
+                                                }
+                                            },
+                                        imageVector = page.icon,
+                                        contentDescription = "",
+                                        tint = if (isSelected) backgroundColor else controlCenterIconColor
+                                    )
+                                }
+                            }
+                            RightShadow(
+                                modifier = Modifier.padding(
+                                    end = controlCenterIconSize.width + 8.dp
+                                ).fillMaxHeight().wrapContentSize(),
+                                color = Color.Black,
+                                alpha = 0.3f,
+                                contentBackgroundColor = Color.Transparent
+                            ) {
+                                Row {
+                                    HorizontalPager(
+                                        modifier = Modifier.width(controlCenterExpandedWidth),
+                                        state = pagerState
+                                    ) { page ->
+                                        pages[page].content(backgroundColor)
+                                    }
+                                    Divider(
+                                        modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
+                                        color = controlCenterDividerColor,
+                                        thickness = controlCenterDividerWidth
+                                    )
+                                }
+                            }
+                            Divider(
+                                modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
+                                color = controlCenterDividerColor,
+                                thickness = controlCenterDividerWidth
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        launchedEffect(controlCenterState.isVisible) { isVisible ->
+            val x = when (isVisible) {
+                true -> (api.containerSize.width - controlCenterExpandedWidth)
+                else -> (api.containerSize.width - controlCenterDividerWidth)
+            }
+            val y = 0.dp
+            val width = when (isVisible) {
+                true -> controlCenterExpandedWidth
+                else -> controlCenterDividerWidth
+            }
+            val height = api.containerSize.height
+            window.setWindowBounds(x, y, width, height)
+        }
+    }
+}

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlCenterPage.kt

@@ -7,7 +7,7 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 
 class ControlCenterPage(
-    var icon: ImageVector = Icons.Filled.Settings,
+    val icon: ImageVector = Icons.Filled.Settings,
     val name: String = "",
     val content: @Composable (backgroundColor: Color) -> Unit = {}
 )

+ 0 - 146
src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlPanel.kt

@@ -1,146 +0,0 @@
-package eu.mjdev.desktop.components.controlpanel
-
-import androidx.compose.animation.*
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.pager.HorizontalPager
-import androidx.compose.foundation.pager.PagerState
-import androidx.compose.foundation.pager.rememberPagerState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Divider
-import androidx.compose.material.Icon
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-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.shadow.LeftShadow
-import eu.mjdev.desktop.components.shadow.RightShadow
-import eu.mjdev.desktop.components.slidemenu.SlidingMenu
-import eu.mjdev.desktop.components.slidemenu.VisibilityState
-import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
-import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-@Suppress("FunctionName")
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-fun ControlPanel(
-    modifier: Modifier = Modifier,
-    backgroundColor: Color = Color.SuperDarkGray,
-    backgroundAlpha: Float = 0.8f,
-    shadowColor: Color = backgroundColor,
-    controlCenterDividerColor: Color = backgroundColor,
-    controlCenterExpandedWidth: Dp,
-    controlCenterDividerWidth: Dp,
-    controlCenterIconColor: Color,
-    controlCenterIconSize: DpSize,
-    pages: List<ControlCenterPage>,
-    pagerState: PagerState = rememberPagerState(pageCount = { pages.size }),
-    state: VisibilityState = rememberVisibilityState(),
-    scope: CoroutineScope = rememberCoroutineScope(),
-    onVisibilityChange: (visible: Boolean) -> Unit = {},
-) = SlidingMenu(
-    modifier = modifier,
-    orientation = Orientation.Horizontal,
-    state = state,
-    onVisibilityChange = onVisibilityChange
-) { isVisible ->
-    if (!isVisible) {
-        Divider(
-            modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-            color = Color.Transparent,
-            thickness = controlCenterDividerWidth
-        )
-    }
-    AnimatedVisibility(
-        isVisible,
-        enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }),
-        exit = slideOutHorizontally(targetOffsetX = { it }) + fadeOut(),
-    ) {
-        LeftShadow(
-            modifier = modifier.fillMaxHeight().wrapContentSize(),
-            color = shadowColor,
-            contentBackgroundColor = backgroundColor.copy(alpha = backgroundAlpha)
-        ) {
-            Row(
-                modifier = Modifier.fillMaxHeight().wrapContentSize(),
-            ) {
-                Divider(
-                    modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                    color = controlCenterDividerColor,
-                    thickness = controlCenterDividerWidth
-                )
-                Box(
-                    contentAlignment = Alignment.TopEnd
-                ) {
-                    LazyColumn(
-                        modifier = Modifier
-                            .fillMaxHeight()
-                            .background(backgroundColor)
-                            .padding(8.dp),
-                        verticalArrangement = Arrangement.Top,
-                        horizontalAlignment = Alignment.Start
-                    ) {
-                        itemsIndexed(pages) { idx, page ->
-                            val isSelected = (pagerState.currentPage == idx)
-                            Icon(
-                                modifier = Modifier
-                                    .padding(vertical = 8.dp)
-                                    .size(controlCenterIconSize)
-                                    .background(
-                                        color = if (isSelected) Color.White.copy(alpha = 0.4f) else Color.Transparent,
-                                        shape = RoundedCornerShape(8.dp)
-                                    )
-                                    .clickable {
-                                        scope.launch {
-                                            pagerState.scrollToPage(idx)
-                                        }
-                                    },
-                                imageVector = page.icon,
-                                contentDescription = "",
-                                tint = if (isSelected) backgroundColor else controlCenterIconColor
-                            )
-                        }
-                    }
-                    RightShadow(
-                        modifier = modifier.padding(
-                            end = controlCenterIconSize.width + 8.dp
-                        ).fillMaxHeight().wrapContentSize(),
-                        color = Color.Black,
-                        alpha = 0.3f,
-                        contentBackgroundColor = Color.Transparent
-                    ) {
-                        Row {
-                            HorizontalPager(
-                                modifier = Modifier.width(controlCenterExpandedWidth),
-                                state = pagerState
-                            ) { page ->
-                                pages[page].content(backgroundColor)
-                            }
-                            Divider(
-                                modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                                color = controlCenterDividerColor,
-                                thickness = controlCenterDividerWidth
-                            )
-                        }
-                    }
-                    Divider(
-                        modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                        color = controlCenterDividerColor,
-                        thickness = controlCenterDividerWidth
-                    )
-                }
-            }
-        }
-    }
-}

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

@@ -63,7 +63,7 @@ fun UserAvatar(
             }
             Text(
                 modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
-                text = api.currentUser.name ?: api.appsProvider.homeDir.name,
+                text = api.currentUser.name ?: api.appsProvider.homeDir?.name ?: "-",
                 color = Color.White,
                 textAlign = TextAlign.Center
             )
@@ -95,7 +95,7 @@ fun UserAvatar(
             }
             Text(
                 modifier = Modifier.padding(start = 16.dp).fillMaxWidth().padding(top = 16.dp),
-                text = api.currentUser.name ?: api.appsProvider.homeDir.name,
+                text = api.currentUser.name ?: api.appsProvider.homeDir?.name ?: "-",
                 color = Color.White,
                 textAlign = TextAlign.Start
             )

+ 135 - 97
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -22,11 +22,19 @@ 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.WindowPlacement
+import androidx.compose.ui.window.WindowPosition
+import androidx.compose.ui.window.WindowState
+import androidx.compose.ui.window.rememberWindowState
 import eu.mjdev.desktop.components.shadow.TopShadow
 import eu.mjdev.desktop.components.slidemenu.SlidingMenu
 import eu.mjdev.desktop.components.slidemenu.VisibilityState
 import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
-import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
+import eu.mjdev.desktop.components.window.TopWindow
+import eu.mjdev.desktop.extensions.Compose.height
+import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.setWindowBounds
+import eu.mjdev.desktop.helpers.WindowFocusState.Companion.windowFocusHandler
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.data.App
@@ -35,118 +43,148 @@ import eu.mjdev.desktop.provider.data.App
 @Suppress("FunctionName")
 @Composable
 fun DesktopPanel(
-    modifier: Modifier = Modifier,
-    backgroundColor: Color = Color.SuperDarkGray,
+    api: DesktopProvider = LocalDesktop.current,
+    backgroundColor: Color = api.currentUser.theme.backgroundColor,
     shadowColor: Color = backgroundColor,
     iconColor: Color = Color.Black,
     iconBackgroundColor: Color = Color.White,
     dividerWidth: Dp = 4.dp,
-    iconSize: DpSize = DpSize(48.dp, 48.dp),
+    iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
+    iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     panelAlpha: Float = 0.7f,
     showMenuIcon: Boolean = true,
-    state: VisibilityState = rememberVisibilityState(),
+    panelState: VisibilityState = rememberVisibilityState(),
     onVisibilityChange: (visible: Boolean) -> Unit = {},
     onMenuIconClicked: () -> Unit = {},
-    api: DesktopProvider = LocalDesktop.current
-) = SlidingMenu(
-    modifier = modifier,
-    orientation = Orientation.Vertical,
-    state = state,
-    onVisibilityChange = onVisibilityChange
-) { isVisible ->
-    val favoriteApps = api.appsProvider.favoriteApps.collectAsState(emptyList())
-    val tooltipData: MutableState<Any?> = mutableStateOf(null)
-    val onToolTip: (item: Any?) -> Unit = { item -> tooltipData.value = item }
-    if (!isVisible) {
-        Divider(
-            modifier = Modifier
-                .fillMaxWidth()
-                .height(dividerWidth),
-            color = Color.Transparent,
-            thickness = dividerWidth
-        )
-    }
-    AnimatedVisibility(
-        isVisible,
-        enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
-        exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+) {
+    val panelSize = api.containerSize.copy(height = iconSize.height + iconPadding.height + iconOuterPadding.height)
+    val windowState: WindowState = rememberWindowState(
+        placement = WindowPlacement.Floating,
+        position = WindowPosition.Aligned(api.currentUser.theme.panelLocation.alignment),
+        size = api.containerSize.copy(
+            width = if (panelState.isVisible) panelSize.height else dividerWidth
+        ),
+        isMinimized = false
+    )
+    TopWindow(
+        windowState = windowState,
     ) {
-        TooltipArea(
-            tooltip = {
-                // todo more types
-                when (tooltipData.value) {
-                    is App -> DesktopPanelAppTooltip(tooltipData.value as App)
-                    else -> {}
-                }
-            },
-            modifier = Modifier.fillMaxWidth(),
-            tooltipPlacement = TooltipPlacement.CursorPoint(
-                alignment = Alignment.TopEnd,
-                offset = DpOffset(16.dp, 32.dp)
-            )
-        ) {
-            TopShadow(
-                modifier = Modifier.fillMaxWidth(),
-                color = shadowColor,
-                contentBackgroundColor = backgroundColor.copy(alpha = panelAlpha)
+        windowFocusHandler { hasFocus -> if (!hasFocus) panelState.hide() }
+        SlidingMenu(
+            orientation = Orientation.Vertical,
+            state = panelState,
+            onVisibilityChange = onVisibilityChange
+        ) { isVisible ->
+            val favoriteApps = api.appsProvider.favoriteApps.collectAsState(emptyList())
+            val tooltipData: MutableState<Any?> = mutableStateOf(null)
+            val onToolTip: (item: Any?) -> Unit = { item -> tooltipData.value = item }
+            if (!isVisible) {
+                Divider(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(dividerWidth),
+                    color = Color.Transparent,
+                    thickness = dividerWidth
+                )
+            }
+            AnimatedVisibility(
+                isVisible,
+                enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
+                exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
             ) {
-                LazyRow(
-                    modifier = Modifier.fillMaxWidth().wrapContentHeight().background(
-                        brush = Brush.verticalGradient(
-                            colors = listOf(
-                                backgroundColor.copy(alpha = 0.3f),
-                                backgroundColor.copy(alpha = 0.7f),
-                                backgroundColor.copy(alpha = 0.3f),
-                            )
-                        )
-                    ),
-                    horizontalArrangement = Arrangement.Start,
-                    verticalAlignment = Alignment.CenterVertically
+                TooltipArea(
+                    tooltip = {
+                        // todo more types
+                        when (tooltipData.value) {
+                            is App -> DesktopPanelAppTooltip(tooltipData.value as App)
+                            else -> {}
+                        }
+                    },
+                    modifier = Modifier.fillMaxSize(),
+                    tooltipPlacement = TooltipPlacement.CursorPoint(
+                        alignment = Alignment.TopEnd,
+                        offset = DpOffset(16.dp, 32.dp)
+                    )
                 ) {
-                    if (showMenuIcon) {
-                        item {
-                            DesktopPanelIcon(
-                                icon = "menu",
-                                iconColor = iconColor,
-                                iconBackgroundColor = iconBackgroundColor,
-                                iconBackgroundHover = Color.White.copy(alpha = 0.3f),
-                                iconSize = iconSize,
-                                iconPadding = iconPadding,
-                                onToolTip = onToolTip,
-                                onClick = {
-                                    onMenuIconClicked()
+                    TopShadow(
+                        modifier = Modifier.fillMaxWidth(),
+                        color = shadowColor,
+                        contentBackgroundColor = backgroundColor.copy(alpha = panelAlpha)
+                    ) {
+                        LazyRow(
+                            modifier = Modifier.fillMaxSize().background(
+                                brush = Brush.verticalGradient(
+                                    colors = listOf(
+                                        backgroundColor.copy(alpha = 0.3f),
+                                        backgroundColor.copy(alpha = 0.7f),
+                                        backgroundColor.copy(alpha = 0.3f),
+                                    )
+                                )
+                            ),
+                            horizontalArrangement = Arrangement.Start,
+                            verticalAlignment = Alignment.CenterVertically
+                        ) {
+                            if (showMenuIcon) {
+                                item {
+                                    DesktopPanelIcon(
+                                        icon = "menu",
+                                        iconColor = iconColor,
+                                        iconBackgroundColor = iconBackgroundColor,
+                                        iconBackgroundHover = Color.White.copy(alpha = 0.3f),
+                                        iconSize = iconSize,
+                                        iconPadding = iconPadding,
+                                        iconOuterPadding = iconOuterPadding,
+                                        onToolTip = onToolTip,
+                                        onClick = {
+                                            onMenuIconClicked()
+                                        }
+                                    )
+                                    Divider(
+                                        modifier = Modifier
+                                            .padding(2.dp)
+                                            .height(iconSize.height - 8.dp)
+                                            .width(2.dp)
+                                            .background(Color.White.copy(alpha = 0.4f))
+                                    )
                                 }
-                            )
-                            Divider(
-                                modifier = Modifier
-                                    .padding(2.dp)
-                                    .height(iconSize.height - 8.dp)
-                                    .width(2.dp)
-                                    .background(Color.White.copy(alpha = 0.4f))
-                            )
+                            }
+                            items(favoriteApps.value) { app ->
+                                DesktopPanelIcon(
+                                    app = app,
+                                    iconColor = iconColor,
+                                    iconBackgroundColor = iconBackgroundColor,
+                                    iconBackgroundHover = Color.White.copy(alpha = 0.3f),
+                                    iconSize = iconSize,
+                                    iconPadding = iconPadding,
+                                    iconOuterPadding = iconOuterPadding,
+                                    onToolTip = onToolTip
+                                )
+                            }
+                            item {
+                                DesktopPanelText(
+                                    text = api.appsProvider.currentLocale.country,
+                                    backgroundHover = Color.White.copy(alpha = 0.3f),
+                                    onToolTip = onToolTip
+                                )
+                            }
                         }
                     }
-                    items(favoriteApps.value) { app ->
-                        DesktopPanelIcon(
-                            app = app,
-                            iconColor = iconColor,
-                            iconBackgroundColor = iconBackgroundColor,
-                            iconBackgroundHover = Color.White.copy(alpha = 0.3f),
-                            iconSize = iconSize,
-                            iconPadding = iconPadding,
-                            onToolTip = onToolTip
-                        )
-                    }
-                    item {
-                        DesktopPanelText(
-                            text = api.appsProvider.currentLocale.country,
-                            backgroundHover = Color.White.copy(alpha = 0.3f),
-                            onToolTip = onToolTip
-                        )
-                    }
                 }
             }
         }
+        launchedEffect(panelState.isVisible) { isVisible ->
+            val x = 0.dp
+            val y = when (isVisible) {
+                true -> (api.containerSize.height - panelSize.height)
+                else -> (api.containerSize.height - dividerWidth)
+            }
+            val width = api.containerSize.width
+            val height = when (isVisible) {
+                true -> panelSize.height
+                else -> dividerWidth
+            }
+            window.setWindowBounds(x, y, width, height)
+        }
     }
-}
+}

+ 13 - 17
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt

@@ -1,11 +1,10 @@
 package eu.mjdev.desktop.components.desktoppanel
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.runtime.Composable
@@ -22,12 +21,12 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.fonticon.MaterialIcon
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
-import eu.mjdev.desktop.extensions.Compose.size
+import eu.mjdev.desktop.extensions.Modifier.clipRect
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.data.App
 
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalComposeUiApi::class)
 @Preview
 @Composable
 fun DesktopPanelIcon(
@@ -39,6 +38,7 @@ fun DesktopPanelIcon(
     iconBackgroundHover: Color = Color.Red,
     iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
+    iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     iconState: MutableState<Boolean> = remember { mutableStateOf(false) },
     onToolTip: (item: Any?) -> Unit = {},
     onClick: () -> Unit = { app?.start() },
@@ -47,26 +47,21 @@ fun DesktopPanelIcon(
     val background = remember(iconState.value) { if (iconState.value) iconBackgroundHover else Color.Transparent }
     Box(
         modifier = Modifier
-            .wrapContentSize()
+            .size(iconSize)
+            .clipRect()
             .onPointerEvent(PointerEventType.Enter) {
-                if (api.windowFocusState.isFocused) {
-                    iconState.value = true
-                    onToolTip(app)
-                }
+                iconState.value = true
+                onToolTip(app)
             }
             .onPointerEvent(PointerEventType.Exit) {
-                if (api.windowFocusState.isFocused) {
-                    iconState.value = false
-                }
+                iconState.value = false
             }
             .onPointerEvent(PointerEventType.Press) {
-                if (api.windowFocusState.isFocused) {
-                    iconState.value = false
-                }
+                iconState.value = false
             }
     ) {
         Button(
-            modifier = Modifier.size(iconSize + iconPadding.size),
+            modifier = Modifier.fillMaxSize(),
             contentPadding = PaddingValues(0.dp),
             onClick = onClick,
             colors = ButtonDefaults.color(background),
@@ -77,7 +72,8 @@ fun DesktopPanelIcon(
                 iconColor = iconColor,
                 iconBackgroundColor = iconBackgroundColor,
                 iconSize = iconSize,
-                innerPadding = iconPadding
+                innerPadding = iconPadding,
+                outerPadding = iconOuterPadding,
             )
         }
     }

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

@@ -21,8 +21,6 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Preview
@@ -33,22 +31,17 @@ fun DesktopPanelText(
     backgroundHover: Color = Color.Red,
     textPadding: PaddingValues = PaddingValues(4.dp),
     buttonState: MutableState<Boolean> = remember { mutableStateOf(false) },
-    api: DesktopProvider = LocalDesktop.current,
     onToolTip: (item: Any?) -> Unit = {},
     onClick: () -> Unit = {},
 ) = Box(
     modifier = Modifier
         .wrapContentSize()
         .onPointerEvent(PointerEventType.Enter) {
-            if (api.windowFocusState.isFocused) {
-                buttonState.value = true
-                onToolTip(text)
-            }
+            buttonState.value = true
+            onToolTip(text)
         }
         .onPointerEvent(PointerEventType.Exit) {
-            if (api.windowFocusState.isFocused) {
-                buttonState.value = false
-            }
+            buttonState.value = false
         }
 ) {
     Button(

+ 8 - 3
src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIcon.kt

@@ -4,7 +4,6 @@ package eu.mjdev.desktop.components.fonticon
 
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -13,7 +12,10 @@ import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.text.style.TextAlign
 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.components.text.FontSizeRange
 import eu.mjdev.desktop.extensions.Compose.sp
 import eu.mjdev.desktop.extensions.Compose.width
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
@@ -38,11 +40,14 @@ fun MaterialIcon(
     innerPadding = innerPadding,
     outerPadding = outerPadding,
     customContent = {
-        Text(
+        AutoResizeText(
             text = iconId.toChar().toString(),
             modifier = Modifier.align(Alignment.Center),
             color = iconColor,
-            fontSize = (iconSize.height - (innerPadding.width + outerPadding.width)).sp,
+            fontSizeRange = FontSizeRange(
+                min = 16.sp,
+                max = (iconSize.height - (innerPadding.width + outerPadding.width)).sp,
+            ),
             fontFamily = iconicFont.fontFile.fontFamily,
             textAlign = TextAlign.Center
         )

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

@@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.extensions.Compose.size
 
 @Composable
 fun ShapedIcon(
@@ -29,7 +30,7 @@ fun ShapedIcon(
 ) = Box(
     modifier = Modifier
         .padding(outerPadding)
-        .size(iconSize)
+        .size(iconSize + innerPadding.size + outerPadding.size)
         .background(iconBackgroundColor, iconShape)
         .clip(iconShape)
         .padding(innerPadding)
@@ -38,12 +39,12 @@ fun ShapedIcon(
         customContent()
     } else {
         Icon(
-            modifier = Modifier
-                .fillMaxSize()
-                .clip(iconShape),
+            modifier = Modifier.fillMaxSize(),
             imageVector = imageVector,
             contentDescription = contentDescription,
             tint = iconColor
         )
     }
-}
+}
+
+

+ 2 - 9
src/main/kotlin/eu/mjdev/desktop/components/slidemenu/SlidingMenu.kt

@@ -12,8 +12,6 @@ import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.onPointerEvent
 import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Suppress("FunctionName")
@@ -24,7 +22,6 @@ fun SlidingMenu(
     state: VisibilityState = rememberVisibilityState(),
     orientation: Orientation = Horizontal,
     onVisibilityChange: (visible: Boolean) -> Unit = {},
-    api: DesktopProvider = LocalDesktop.current,
     content: @Composable (isVisible: Boolean) -> Unit
 ) {
     when (orientation) {
@@ -33,9 +30,7 @@ fun SlidingMenu(
                 .fillMaxHeight()
                 .wrapContentWidth()
                 .onPointerEvent(PointerEventType.Enter) {
-                    if (api.windowFocusState.isFocused) {
-                        state.show()
-                    }
+                    state.show()
                 }
         ) {
             content(state.isVisible)
@@ -46,9 +41,7 @@ fun SlidingMenu(
                 .fillMaxWidth()
                 .wrapContentHeight()
                 .onPointerEvent(PointerEventType.Enter) {
-                    if (api.windowFocusState.isFocused) {
-                        state.show()
-                    }
+                    state.show()
                 }
         ) {
             content(state.isVisible)

+ 10 - 8
src/main/kotlin/eu/mjdev/desktop/components/slidemenu/VisibilityState.kt

@@ -2,10 +2,9 @@ package eu.mjdev.desktop.components.slidemenu
 
 import androidx.compose.runtime.*
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
-@Suppress("CanBeParameter")
+@Suppress("CanBeParameter", "unused")
 class VisibilityState(
     private val scope: CoroutineScope,
     private val startState: Boolean = false,
@@ -20,15 +19,18 @@ class VisibilityState(
     private val visible: MutableState<Boolean> = mutableStateOf(startState)
 
     fun show() {
-        if (enabled) scope.launch {
-            visible.value = true
+        if (!isVisible) {
+            if (enabled) scope.launch {
+                visible.value = true
+            }
         }
     }
 
-    fun hide(delay: Long = 0L) {
-        if (enabled) scope.launch {
-            delay(delay)
-            visible.value = false
+    fun hide() {
+        if (isVisible) {
+            if (enabled) scope.launch {
+                visible.value = false
+            }
         }
     }
 

+ 73 - 0
src/main/kotlin/eu/mjdev/desktop/components/text/AutoResizeText.kt

@@ -0,0 +1,73 @@
+package eu.mjdev.desktop.components.text
+
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun AutoResizeText(
+    text: String,
+    fontSizeRange: FontSizeRange,
+    modifier: Modifier = Modifier,
+    color: Color = Color.Unspecified,
+    fontStyle: FontStyle? = null,
+    fontWeight: FontWeight? = null,
+    fontFamily: FontFamily? = null,
+    letterSpacing: TextUnit = TextUnit.Unspecified,
+    textDecoration: TextDecoration? = null,
+    textAlign: TextAlign? = null,
+    lineHeight: TextUnit = TextUnit.Unspecified,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    style: TextStyle = LocalTextStyle.current,
+) {
+    var fontSizeValue by remember { mutableStateOf(fontSizeRange.max.value) }
+    var readyToDraw by remember { mutableStateOf(false) }
+    Text(
+        text = text,
+        color = color,
+        maxLines = maxLines,
+        fontStyle = fontStyle,
+        fontWeight = fontWeight,
+        fontFamily = fontFamily,
+        letterSpacing = letterSpacing,
+        textDecoration = textDecoration,
+        textAlign = textAlign,
+        lineHeight = lineHeight,
+        overflow = overflow,
+        softWrap = softWrap,
+        style = style,
+        fontSize = fontSizeValue.sp,
+        onTextLayout = {
+            if (it.didOverflowHeight && !readyToDraw) {
+                val nextFontSizeValue = fontSizeValue - fontSizeRange.step.value
+                if (nextFontSizeValue <= fontSizeRange.min.value) {
+                    fontSizeValue = fontSizeRange.min.value
+                    readyToDraw = true
+                } else {
+                    fontSizeValue = nextFontSizeValue
+                }
+            } else {
+                readyToDraw = true
+            }
+        },
+        modifier = modifier.drawWithContent {
+            if (readyToDraw) {
+                drawContent()
+            }
+        }
+    )
+}

+ 19 - 0
src/main/kotlin/eu/mjdev/desktop/components/text/FontSizeRange.kt

@@ -0,0 +1,19 @@
+package eu.mjdev.desktop.components.text
+
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.sp
+
+class FontSizeRange(
+    val min: TextUnit,
+    val max: TextUnit,
+    val step: TextUnit = DEFAULT_TEXT_STEP,
+) {
+    init {
+        require(min < max) { "min should be less than max, $this" }
+        require(step.value > 0) { "step should be greater than 0, $this" }
+    }
+
+    companion object {
+        private val DEFAULT_TEXT_STEP = 1.sp
+    }
+}

+ 42 - 0
src/main/kotlin/eu/mjdev/desktop/components/window/FullScreenWindow.kt

@@ -0,0 +1,42 @@
+package eu.mjdev.desktop.components.window
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.window.FrameWindowScope
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.WindowPlacement
+import androidx.compose.ui.window.rememberWindowState
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+
+@Composable
+fun FullScreenWindow(
+    visible: Boolean = true,
+    title: String = "",
+    icon: Painter? = null,
+    focusable: Boolean = true,
+    alwaysOnTop: Boolean = false,
+    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
+    onKeyEvent: (KeyEvent) -> Boolean = { false },
+    api: DesktopProvider = LocalDesktop.current,
+    onCloseRequest: () -> Unit = {},
+    content: @Composable FrameWindowScope.() -> Unit
+) = Window(
+    state = rememberWindowState(
+        placement = WindowPlacement.Floating,
+        size = api.containerSize
+    ),
+    resizable = false,
+    undecorated = true,
+    transparent = true,
+    focusable = focusable,
+    alwaysOnTop = alwaysOnTop,
+    visible = visible,
+    title = title,
+    icon = icon,
+    onPreviewKeyEvent = onPreviewKeyEvent,
+    onKeyEvent = onKeyEvent,
+    onCloseRequest = onCloseRequest,
+    content = content
+)

+ 37 - 0
src/main/kotlin/eu/mjdev/desktop/components/window/TopWindow.kt

@@ -0,0 +1,37 @@
+package eu.mjdev.desktop.components.window
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.window.FrameWindowScope
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.WindowState
+
+@Composable
+fun TopWindow(
+    resizable: Boolean = false,
+    undecorated: Boolean = true,
+    visible: Boolean = true,
+    title: String = "",
+    icon: Painter? = null,
+    focusable: Boolean = true,
+    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
+    onKeyEvent: (KeyEvent) -> Boolean = { false },
+    windowState: WindowState,
+    onCloseRequest: () -> Unit = {},
+    content: @Composable FrameWindowScope.() -> Unit
+) = Window(
+    state = windowState,
+    resizable = resizable,
+    undecorated = undecorated,
+    onCloseRequest = onCloseRequest,
+    visible = visible,
+    transparent = true,
+    title = title,
+    icon = icon,
+    focusable = focusable,
+    alwaysOnTop = true,
+    onPreviewKeyEvent = onPreviewKeyEvent,
+    onKeyEvent = onKeyEvent,
+    content = content
+)

+ 10 - 87
src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt

@@ -4,17 +4,11 @@ package eu.mjdev.desktop.extensions
 
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.unit.*
-import androidx.compose.ui.window.*
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.desktopLocalProvider
 import kotlinx.coroutines.CoroutineScope
 
 object Compose {
@@ -49,10 +43,10 @@ object Compose {
     ) = LaunchedEffect(Unit, block)
 
     @Composable
-    fun launchedEffect(
-        key: Any?,
-        block: suspend CoroutineScope.() -> Unit
-    ) = LaunchedEffect(key, block)
+    fun <T> launchedEffect(
+        key: T,
+        block: suspend CoroutineScope.(key: T) -> Unit
+    ) = LaunchedEffect(key) { block(key) }
 
     @Composable
     fun <T> textFrom(text: T?): String = when (text) {
@@ -84,83 +78,12 @@ object Compose {
         disabledBackgroundColor = Color.Transparent
     )
 
-    @Composable
-    fun FullScreenWindow(
-        visible: Boolean = true,
-        title: String = "",
-        icon: Painter? = null,
-        focusable: Boolean = true,
-        alwaysOnTop: Boolean = false,
-        onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
-        onKeyEvent: (KeyEvent) -> Boolean = { false },
-        onCloseRequest: () -> Unit = {},
-        content: @Composable FrameWindowScope.() -> Unit
-    ) = Window(
-        state = rememberWindowState(
-            placement = WindowPlacement.Fullscreen
-        ),
-        resizable = false,
-        undecorated = true,
-        transparent = true,
-        focusable = focusable,
-        alwaysOnTop = alwaysOnTop,
-        visible = visible,
-        title = title,
-        icon = icon,
-        onPreviewKeyEvent = onPreviewKeyEvent,
-        onKeyEvent = onKeyEvent,
-        onCloseRequest = onCloseRequest,
-        content = {
-            MaterialTheme {
-                desktopLocalProvider {
-                    content()
-                }
-            }
-        }
-    )
+    fun java.awt.Window.setWindowBounds(x: Dp, y: Dp, w: Dp, h: Dp) {
+        setBounds(x.value.toInt(), y.value.toInt(), w.value.toInt(), h.value.toInt())
+    }
 
-    @Composable
-    fun TopWindow(
-        // todo
-        @Suppress("UNUSED_PARAMETER") movable: Boolean = false,
-        position: WindowPosition = WindowPosition.Aligned(Alignment.BottomStart),
-        size: DpSize = DpSize(800.dp, 600.dp),
-        resizable: Boolean = false,
-        undecorated: Boolean = true,
-        visible: Boolean = true,
-        minimized: Boolean = false,
-        title: String = "",
-        icon: Painter? = null,
-        focusable: Boolean = true,
-        onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
-        onKeyEvent: (KeyEvent) -> Boolean = { false },
-        onCloseRequest: () -> Unit = {},
-        content: @Composable FrameWindowScope.() -> Unit
-    ) = Window(
-        state = rememberWindowState(
-            placement = WindowPlacement.Floating,
-            position = position,
-            size = size,
-            isMinimized = minimized
-        ),
-        resizable = resizable,
-        undecorated = undecorated,
-        onCloseRequest = onCloseRequest,
-        visible = visible,
-        transparent = true,
-        title = title,
-        icon = icon,
-        focusable = focusable,
-        alwaysOnTop = true,
-        onPreviewKeyEvent = onPreviewKeyEvent,
-        onKeyEvent = onKeyEvent,
-        content = {
-            MaterialTheme {
-                desktopLocalProvider {
-                    content()
-                }
-            }
-        }
-    )
+    // todo
+//    fun java.awt.Window.resizeTo(w: Dp, h: Dp) {
+//    }
 
 }

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

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.extensions
 
 import androidx.compose.foundation.border
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
@@ -61,6 +62,14 @@ object Modifier {
     @Composable
     fun Modifier.clipCircle() = clip(CircleShape)
 
+    @Stable
+    @Composable
+    fun Modifier.clipRect() = clip(RectangleShape)
+
+    @Stable
+    @Composable
+    fun Modifier.clipRoundRect(round: Dp) = clip(RoundedCornerShape(round))
+
     @Stable
     @Composable
     fun Modifier.circleBorder(

+ 6 - 8
src/main/kotlin/eu/mjdev/desktop/helpers/WindowFocusState.kt

@@ -2,9 +2,11 @@ package eu.mjdev.desktop.helpers
 
 import androidx.compose.runtime.*
 import androidx.compose.ui.awt.ComposeWindow
+import androidx.compose.ui.window.FrameWindowScope
 import java.awt.event.WindowEvent
 import java.awt.event.WindowFocusListener
 
+@Suppress("MemberVisibilityCanBePrivate")
 class WindowFocusState(
     private val window: ComposeWindow? = null,
     private val onFocusChange: (focused: Boolean) -> Unit = {}
@@ -16,12 +18,12 @@ class WindowFocusState(
 
     override fun windowGainedFocus(p0: WindowEvent?) {
         focusState.value = true
-        onFocusChange(true)
+        onFocusChange(isFocused)
     }
 
     override fun windowLostFocus(p0: WindowEvent?) {
         focusState.value = false
-        onFocusChange(false)
+        onFocusChange(isFocused)
     }
 
     fun init() {
@@ -33,13 +35,10 @@ class WindowFocusState(
     }
 
     companion object {
-        val EMPTY: WindowFocusState = WindowFocusState()
-
         @Composable
-        fun rememberWindowFocusState(
-            window: ComposeWindow,
+        fun FrameWindowScope.windowFocusHandler(
             onFocusChange: (focused: Boolean) -> Unit = {}
-        ): WindowFocusState {
+        ) {
             val state = remember(window) { WindowFocusState(window, onFocusChange) }
             DisposableEffect(Unit) {
                 state.init()
@@ -47,7 +46,6 @@ class WindowFocusState(
                     state.destroy()
                 }
             }
-            return state
         }
     }
 }

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

@@ -13,66 +13,92 @@ import java.util.*
 class AppsProvider(
     val desktopProvider: DesktopProvider
 ) {
-    val homeDir by lazy { File(System.getProperty("user.home")) }
-
-    private val configDir by lazy { homeDir.resolve(".config") }
-    private val iconsDir by lazy { homeDir.resolve(".icons") }
-    private val themesDir by lazy { homeDir.resolve(".themes") }
-    private val localDir by lazy { 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") }
-    private val backgroundFilesDir by lazy { localShareDir.resolve("backgrounds") }
-
-    private val menusDir by lazy { configDir.resolve("menus") }
-
-    private val gnomeAppsFile by lazy { menusDir.resolve("gnome-applications.menu") } // xml
-    private val backgroundFile by lazy { configDir.resolve("background") } // jpg
-
-    private val mimeApps by lazy { configDir.resolve("mimeapps.list").readLines() } // <mime>=<desktop.file>
-    private val userDirs by lazy { configDir.resolve("user-dirs.dirs").readText() } // <type>="<path>"
-    private val userDirsLocale by lazy { configDir.resolve("user-dirs.locale").readText().toLocale() } //SK_sk
-
-    private val menusItems by lazy { menusDir.resolve("gnome-applications.menu").readText() } // xml
-
-    private val iconThemes by lazy { iconsDir.listFiles() } // folders
-    private val systemThemes by lazy { themesDir.listFiles() } // folders
+    val homeDir by lazy { runCatching { File(System.getProperty("user.home")) }.getOrNull() }
+
+    private val configDir by lazy { homeDir?.resolve(".config") }
+    private val iconsDir by lazy { homeDir?.resolve(".icons") }
+    private val themesDir by lazy { homeDir?.resolve(".themes") }
+    private val localDir by lazy { 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") }
+    private val backgroundFilesDir by lazy { localShareDir?.resolve("backgrounds") }
+
+    private val menusDir by lazy { configDir?.resolve("menus") }
+
+    private val gnomeAppsFile by lazy { menusDir?.resolve("gnome-applications.menu") } // xml
+    private val backgroundFile by lazy { configDir?.resolve("background") } // jpg
+
+    private val mimeApps by lazy {
+        runCatching {
+            configDir?.resolve("mimeapps.list")?.readLines()
+        }.getOrNull()
+    } // <mime>=<desktop.file>
+    private val userDirs by lazy {
+        runCatching {
+            configDir?.resolve("user-dirs.dirs")?.readText()
+        }.getOrNull()
+    } // <type>="<path>"
+    private val userDirsLocale by lazy {
+        runCatching {
+            configDir?.resolve("user-dirs.locale")?.readText()?.toLocale()
+        }.getOrNull()
+    } //SK_sk
+
+    private val menusItems by lazy {
+        runCatching {
+            menusDir?.resolve("gnome-applications.menu")?.readText()
+        }.getOrNull()
+    } // xml
+
+    private val iconThemes by lazy { runCatching { iconsDir?.listFiles() }.getOrNull() } // folders
+    private val systemThemes by lazy { runCatching { themesDir?.listFiles() }.getOrNull() } // folders
 
     private val autoStartDesktopFiles
-        get() = autostartDesktopFilesDir.listFiles()?.map { DesktopFile(it) } ?: emptyList()
+        get() = runCatching {
+            autostartDesktopFilesDir?.listFiles()?.map { DesktopFile(it) }
+        }.getOrNull() ?: emptyList()
 
     // todo : probably not all here, need usr and local also
     private val allAppsDesktopFilesLocal
-        get() = allAppsDesktopFilesDir.listFiles()?.filter {
-            it.extension == "desktop"
-        }?.map { DesktopFile(it) } ?: emptyList()
+        get() = runCatching {
+            allAppsDesktopFilesDir?.listFiles()?.filter {
+                it.extension == "desktop"
+            }?.map { DesktopFile(it) }
+        }.getOrNull() ?: emptyList()
 
     private val allAppsDesktopFilesShared
-        get() = File("/usr/share/applications").listFiles()?.filter {
-            it.extension == "desktop"
-        }?.map { DesktopFile(it) } ?: emptyList()
+        get() = runCatching {
+            File("/usr/share/applications").listFiles()?.filter {
+                it.extension == "desktop"
+            }?.map { DesktopFile(it) }
+        }.getOrNull() ?: emptyList()
 
     private val allAppsDesktopFilesFlatPack
-        get() = File("/var/lib/flatpak/exports/share/applications/").listFiles()?.filter {
-            it.extension == "desktop"
-        }?.map { DesktopFile(it) } ?: emptyList()
+        get() = runCatching {
+            File("/var/lib/flatpak/exports/share/applications/").listFiles()?.filter {
+                it.extension == "desktop"
+            }?.map { DesktopFile(it) }
+        }.getOrNull() ?: emptyList()
 
     private val allAppsDesktopFilesSnap
-        get() = File("/var/lib/snapd/desktop/applications/").listFiles()?.filter {
-            it.extension == "desktop"
-        }?.map { DesktopFile(it) } ?: emptyList()
+        get() = runCatching {
+            File("/var/lib/snapd/desktop/applications/").listFiles()?.filter {
+                it.extension == "desktop"
+            }?.map { DesktopFile(it) }
+        }.getOrNull() ?: emptyList()
 
     private val allAppsDesktopFiles
         get() = allAppsDesktopFilesLocal + allAppsDesktopFilesShared + allAppsDesktopFilesFlatPack + allAppsDesktopFilesSnap
 
     val currentLocale: Locale
-        get() = userDirsLocale
+        get() = userDirsLocale ?: Locale.ENGLISH
     val autoStartApps
         get() = autoStartDesktopFiles.map { file -> App(desktopFile = file, file = file.file) }
     val allApps
         get() = allAppsDesktopFiles.map { file -> App(desktopFile = file, file = file.file) }
     val backgrounds
-        get() = backgroundFilesDir.listFiles()?.toList() ?: emptyList<File>()
+        get() = runCatching { backgroundFilesDir?.listFiles()?.toList() }.getOrNull() ?: emptyList<File>()
     val appCategories
         get() = mutableListOf<String>().apply {
             allApps.forEach { app ->
@@ -102,7 +128,7 @@ class AppsProvider(
                 "org.gnome.shell",
                 "favorite-apps"
             ).let { json ->
-                Gson().fromJson(json, List::class.java)
+                Gson().fromJson(json ?: "[]", List::class.java)
             }.mapNotNull { appName ->
                 val deskFile = desktopFiles.firstOrNull { deskFile ->
                     deskFile?.file?.name?.contentEquals(appName.toString()) == true

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

@@ -1,16 +1,14 @@
 package eu.mjdev.desktop.provider
 
-import androidx.compose.runtime.*
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.unit.IntSize
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.FrameWindowScope
 import eu.mjdev.desktop.components.controlpanel.ControlCenterPage
 import eu.mjdev.desktop.components.controlpanel.pages.*
-import eu.mjdev.desktop.helpers.WindowFocusState
-import eu.mjdev.desktop.helpers.WindowFocusState.Companion.rememberWindowFocusState
 import eu.mjdev.desktop.provider.data.User
+import java.awt.Toolkit
 import java.io.BufferedReader
 import java.io.File
 import java.io.FileReader
@@ -19,12 +17,9 @@ import java.util.*
 import javax.script.ScriptEngine
 import javax.script.ScriptEngineManager
 
-
 @Suppress("unused")
 class DesktopProvider(
-    val containerSize: IntSize? = null,
-    val currentUser: User = User.Empty,
-    val windowFocusState: WindowFocusState = WindowFocusState.EMPTY
+    val currentUser: User = User.Empty, // todo
 ) {
     private val _currentUser: MutableState<User> = mutableStateOf(currentUser)
 
@@ -46,13 +41,19 @@ class DesktopProvider(
         }
     }
 
+    val containerSize: DpSize by lazy {
+        Toolkit.getDefaultToolkit().screenSize.let {
+            DpSize(
+                it.width.dp,
+                it.height.dp
+            )
+        }
+    }
     val controlCenterPages get() = _controlCenterPages.value
     val appsProvider by lazy { AppsProvider(this) }
 
     init {
-        if (containerSize != null) {
-            currentUser.theme.controlCenterExpandedWidth = containerSize.width.div(4).dp
-        }
+        currentUser.theme.controlCenterExpandedWidth = containerSize.width.div(4)
     }
 
     fun runScript(script: String): Any =
@@ -74,14 +75,13 @@ class DesktopProvider(
         }.start()
     }
 
-    fun runCommandForOutput(vararg cmd: String): String {
+    fun runCommandForOutput(vararg cmd: String): String? = runCatching {
         val pb = ProcessBuilder(*cmd)
         val p: Process
         var result = ""
         try {
             p = pb.start()
             val reader = BufferedReader(InputStreamReader(p.inputStream))
-
             val sj = StringJoiner(System.lineSeparator())
             reader.lines().iterator().forEachRemaining { newElement: String? ->
                 sj.add(newElement)
@@ -92,29 +92,12 @@ class DesktopProvider(
         } catch (e: Exception) {
             e.printStackTrace()
         }
-        return result
-    }
+        result
+    }.getOrNull()
 
     companion object {
         val LocalDesktop = compositionLocalOf {
             DesktopProvider()
         }
-
-        @OptIn(ExperimentalComposeUiApi::class)
-        @Composable
-        fun FrameWindowScope.desktopLocalProvider(content: @Composable () -> Unit) {
-            val containerSize = LocalWindowInfo.current.containerSize
-            val currentUser = User.Empty // todo get user
-            val windowFocusState = rememberWindowFocusState(window)
-            CompositionLocalProvider(
-                LocalDesktop provides DesktopProvider(
-                    containerSize = containerSize,
-                    currentUser = currentUser,
-                    windowFocusState = windowFocusState
-                )
-            ) {
-                content()
-            }
-        }
     }
 }

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

@@ -25,9 +25,11 @@ class Theme(
     controlCenterDividerWidth: Dp,
     controlCenterIconColor: Color,
     controlCenterIconSize: DpSize,
+    controlCenterBackgroundAlpha: Float,
 
     iconSet: MaterialIconFont,
 ) {
+
     val backgroundColorState = mutableStateOf(backgroundColor)
     val panelLocationState = mutableStateOf(panelLocation)
     val panelHideDelayState = mutableStateOf(panelHideDelay)
@@ -40,6 +42,7 @@ class Theme(
     val controlCenterIconSizeState = mutableStateOf(controlCenterIconSize)
     val iconSetState = mutableStateOf(iconSet)
     val backgroundRotationDelayState = mutableStateOf(backgroundRotationDelay)
+    val controlCenterBackgroundAlphaState = mutableStateOf(controlCenterBackgroundAlpha)
 
     var backgroundColor
         get() = backgroundColorState.value
@@ -59,6 +62,7 @@ class Theme(
     val controlCenterDividerWidth get() = controlCenterDividerWidthState.value
     val controlCenterIconColor get() = controlCenterIconColorState.value
     val controlCenterIconSize get() = controlCenterIconSizeState.value
+    val controlCenterBackgroundAlpha get() = controlCenterBackgroundAlphaState.value
     val iconSet get() = iconSetState.value
     val backgroundRotationDelay get() = backgroundRotationDelayState.value
 
@@ -78,6 +82,8 @@ class Theme(
             controlCenterDividerWidth = 2.dp,
             controlCenterIconColor = Color.White,
             controlCenterIconSize = DpSize(32.dp, 32.dp),
+            controlCenterBackgroundAlpha = 0.8f,
+
             backgroundRotationDelay = 10000
         )
     }

+ 0 - 110
src/main/kotlin/eu/mjdev/desktop/screens/MainWindow.kt

@@ -1,110 +0,0 @@
-package eu.mjdev.desktop.screens
-
-import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-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 androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.components.appsmenu.AppsMenu
-import eu.mjdev.desktop.components.background.BackgroundImage
-import eu.mjdev.desktop.components.controlpanel.ControlPanel
-import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
-import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
-import eu.mjdev.desktop.components.widgets.WidgetsPanel
-import eu.mjdev.desktop.extensions.Compose.FullScreenWindow
-import eu.mjdev.desktop.helpers.Palette.Companion.rememberPalette
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
-
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
-@Suppress("FunctionName")
-@Preview
-@Composable
-fun MainWindow() = FullScreenWindow {
-    val api: DesktopProvider = LocalDesktop.current
-    val palette = rememberPalette(api.currentUser.theme.backgroundColor)
-    val panelState = rememberVisibilityState(true, api.currentUser.config.autoHidePanel)
-    val controlCenterState = rememberVisibilityState()
-    val menuState = rememberVisibilityState()
-    Box(
-        modifier = Modifier.fillMaxSize().background(api.currentUser.theme.backgroundColor)
-    ) {
-        BackgroundImage(
-            modifier = Modifier
-                .fillMaxSize()
-                .onPointerEvent(PointerEventType.Enter) {
-                    controlCenterState.hide(0)
-                    panelState.hide(0)
-                }
-                .onPointerEvent(PointerEventType.Press) {
-                    menuState.hide(0)
-                },
-            backgroundColor = api.currentUser.theme.backgroundColor,
-            backgrounds = api.appsProvider.backgrounds + api.currentUser.config.desktopBackgroundUrls,
-            switchDelay = api.currentUser.theme.backgroundRotationDelay,
-            onChange = { src ->
-                palette.update(src)
-                api.currentUser.theme.backgroundColor = palette.backgroundColor
-            }
-        )
-        WidgetsPanel(
-            modifier = Modifier
-                .fillMaxSize()
-                .onPointerEvent(PointerEventType.Enter) {
-                    controlCenterState.hide(0)
-                    panelState.hide(0)
-                }
-                .onPointerEvent(PointerEventType.Press) {
-                    menuState.hide(0)
-                },
-        )
-        AppsMenu(
-            modifier = Modifier.align(Alignment.BottomStart),
-            bottomY = 64.dp,
-            state = menuState,
-            backgroundColor = api.currentUser.theme.backgroundColor,
-        )
-        DesktopPanel(
-            modifier = Modifier.align(api.currentUser.theme.panelLocation.alignment),
-            state = panelState,
-            backgroundColor = api.currentUser.theme.backgroundColor,
-            onMenuIconClicked = {
-                menuState.toggle()
-            },
-            onVisibilityChange = { visible ->
-                if (visible) {
-                    controlCenterState.hide(0)
-                }
-            }
-        )
-        ControlPanel(
-            modifier = Modifier.align(api.currentUser.theme.controlCenterLocation.alignment),
-            state = controlCenterState,
-            backgroundColor = api.currentUser.theme.backgroundColor,
-            controlCenterExpandedWidth = api.currentUser.theme.controlCenterExpandedWidth,
-            controlCenterDividerWidth = api.currentUser.theme.controlCenterDividerWidth,
-            controlCenterIconColor = api.currentUser.theme.controlCenterIconColor,
-            controlCenterIconSize = api.currentUser.theme.controlCenterIconSize,
-            pages = api.controlCenterPages,
-            onVisibilityChange = { visible ->
-                if (visible) {
-                    panelState.hide(0)
-                }
-            }
-        )
-    }
-//    launchedEffect {
-//        runCatching {
-//            Notification("Hello!", "I just wanted to say hello :)").apply {
-//                setUrgency(Notification.UrgencyCritical)
-//            }.send()
-//        }
-//    }
-}

+ 64 - 0
src/main/kotlin/eu/mjdev/desktop/windows/MainWindow.kt

@@ -0,0 +1,64 @@
+package eu.mjdev.desktop.windows
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+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.background.BackgroundImage
+import eu.mjdev.desktop.components.slidemenu.VisibilityState
+import eu.mjdev.desktop.components.window.FullScreenWindow
+import eu.mjdev.desktop.helpers.Palette
+import eu.mjdev.desktop.helpers.Palette.Companion.rememberPalette
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Suppress("FunctionName")
+@Preview
+@Composable
+fun MainWindow(
+    controlCenterState: VisibilityState,
+    panelState: VisibilityState,
+    menuState: VisibilityState,
+    api: DesktopProvider = LocalDesktop.current,
+    palette: Palette = rememberPalette(api.currentUser.theme.backgroundColor)
+) = FullScreenWindow {
+    Box(
+        modifier = Modifier
+            .fillMaxSize()
+            .background(api.currentUser.theme.backgroundColor)
+    ) {
+        BackgroundImage(
+            modifier = Modifier
+                .fillMaxSize()
+                .onPointerEvent(PointerEventType.Press) {
+                    panelState.hide()
+                    menuState.hide()
+                    controlCenterState.hide()
+                },
+            backgroundColor = api.currentUser.theme.backgroundColor,
+            backgrounds = api.appsProvider.backgrounds + api.currentUser.config.desktopBackgroundUrls,
+            switchDelay = api.currentUser.theme.backgroundRotationDelay,
+            onChange = { src ->
+                palette.update(src)
+                api.currentUser.theme.backgroundColor = palette.backgroundColor
+            }
+        )
+//        WidgetsPanel(
+//            modifier = Modifier
+//                .fillMaxSize()
+//        )
+    }
+//    launchedEffect {
+//        runCatching {
+//            Notification("Hello!", "I just wanted to say hello :)").apply {
+//                setUrgency(Notification.UrgencyCritical)
+//            }.send()
+//        }
+//    }
+}