Browse Source

design improvements

milan jurkulák 2 years ago
parent
commit
9647ffaeae
33 changed files with 658 additions and 438 deletions
  1. 4 27
      src/main/kotlin/eu/mjdev/desktop/Main.kt
  2. 45 0
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt
  3. 64 78
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  4. 6 5
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt
  5. 6 5
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt
  6. 11 5
      src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt
  7. 4 4
      src/main/kotlin/eu/mjdev/desktop/components/controlpanel/ControlPanel.kt
  8. 4 4
      src/main/kotlin/eu/mjdev/desktop/components/controlpanel/pages/MainSettingsPage.kt
  9. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/custom/Clock.kt
  10. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/custom/DateTime.kt
  11. 6 6
      src/main/kotlin/eu/mjdev/desktop/components/custom/PowerBlock.kt
  12. 4 4
      src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt
  13. 7 6
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  14. 24 31
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt
  15. 5 2
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelText.kt
  16. 12 0
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/CodePointsFile.kt
  17. 25 0
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/FontFile.kt
  18. 35 72
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIcon.kt
  19. 28 0
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIconFont.kt
  20. 49 0
      src/main/kotlin/eu/mjdev/desktop/components/icon/ShapedIcon.kt
  21. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/image/ImageAny.kt
  22. 6 54
      src/main/kotlin/eu/mjdev/desktop/components/slidemenu/SlidingMenu.kt
  23. 51 0
      src/main/kotlin/eu/mjdev/desktop/components/slidemenu/VisibilityState.kt
  24. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/text/CircleText.kt
  25. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/text/TextWithShadow.kt
  26. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/web/ComposeWebView.kt
  27. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/widgets/WidgetsPanel.kt
  28. 90 95
      src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt
  29. 8 0
      src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt
  30. 95 0
      src/main/kotlin/eu/mjdev/desktop/extensions/Modifier.kt
  31. 28 7
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  32. 7 2
      src/main/kotlin/eu/mjdev/desktop/provider/data/Theme.kt
  33. 27 24
      src/main/kotlin/eu/mjdev/desktop/screens/MainWindow.kt

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

@@ -1,33 +1,10 @@
 package eu.mjdev.desktop
 
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.platform.LocalWindowInfo
-import androidx.compose.ui.window.*
-import eu.mjdev.desktop.extensions.Compose.fullScreenWindow
-import eu.mjdev.desktop.helpers.WindowFocusState.Companion.rememberWindowFocusState
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
-import eu.mjdev.desktop.provider.data.User
-import eu.mjdev.desktop.screens.MainScreen
+import androidx.compose.ui.window.application
+import eu.mjdev.desktop.screens.MainWindow
 
-@OptIn(ExperimentalComposeUiApi::class)
 fun main() = application(
-    exitProcessOnExit = false
+    exitProcessOnExit = true
 ) {
-    fullScreenWindow {
-        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
-            )
-        ) {
-            MainScreen()
-        }
-    }
+    MainWindow()
 }

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

@@ -0,0 +1,45 @@
+package eu.mjdev.desktop.components.appsmenu
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.Logout
+import androidx.compose.material.icons.filled.PowerOff
+import androidx.compose.material.icons.filled.RestartAlt
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.components.icon.ShapedIcon
+
+@Composable
+fun AppsBottomBar(
+    modifier: Modifier = Modifier
+) = Box(
+    modifier = modifier.padding(8.dp)
+) {
+    Row(
+        modifier = Modifier.align(Alignment.BottomStart)
+    ) {
+
+    }
+
+    Row(
+        modifier = Modifier.align(Alignment.BottomEnd)
+    ) {
+        ShapedIcon(
+            imageVector = Icons.Filled.RestartAlt,
+            iconBackgroundColor = Color.White.copy(alpha = 0.7f),
+        )
+        ShapedIcon(
+            imageVector = Icons.AutoMirrored.Filled.Logout,
+            iconBackgroundColor = Color.White.copy(alpha = 0.7f),
+        )
+        ShapedIcon(
+            imageVector = Icons.Filled.PowerOff,
+            iconBackgroundColor = Color.White.copy(alpha = 0.7f),
+        )
+    }
+}

+ 64 - 78
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -18,14 +18,13 @@ 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 eu.mjdev.desktop.components.SlideMenuState
-import eu.mjdev.desktop.components.SlideMenuState.Companion.rememberSlideMenuState
-import eu.mjdev.desktop.components.SlidingMenu
-import eu.mjdev.desktop.components.UserAvatar
+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.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.data.App
 import eu.mjdev.desktop.provider.data.Category
 
@@ -37,101 +36,88 @@ fun AppsMenu(
     bottomY: Dp = 0.dp,
     backgroundColor: Color = Color.SuperDarkGray,
     menuPadding: PaddingValues = PaddingValues(2.dp),
-    state: SlideMenuState = rememberSlideMenuState(),
+    state: VisibilityState = rememberVisibilityState(),
     api: DesktopProvider = LocalDesktop.current,
     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(),
 ) {
-    SlidingMenu(
-        modifier = modifier,
-        orientation = Orientation.Vertical,
-        state = state,
-        onVisibilityChange = onVisibilityChange
-    ) { isVisible ->
-        AnimatedVisibility(
-            isVisible,
-            enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
-            exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+    Box(
+        modifier = modifier
+            .padding(bottom = bottomY)
+            .width(appMenuExpandedWidth)
+            .heightIn(
+                min = appMenuMinHeight,
+                max = appMenuMinHeight
+            )
+    ) {
+        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))
         ) {
-            Box(
-                modifier = modifier
-                    .padding(bottom = bottomY)
-                    .width(appMenuExpandedWidth)
-                    .heightIn(
-                        min = appMenuMinHeight,
-                        max = appMenuMinHeight
-                    )
+            Column(
+                modifier = Modifier.fillMaxSize()
             ) {
-                Box(
+                UserAvatar(
+                    avatarSize = 64.dp,
+                    backgroundColor = backgroundColor,
+                    orientation = Orientation.Horizontal
+                )
+                LazyColumn(
                     modifier = Modifier
+                        .padding(bottom = 48.dp)
                         .fillMaxSize()
-                        .padding(menuPadding)
-                        .background(backgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
-                        .clip(RoundedCornerShape(24.dp))
-                        .border(2.dp, backgroundColor, RoundedCornerShape(24.dp))
+                        .padding(24.dp)
                 ) {
-                    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
-                                        ) { category ->
-                                            items.value =
-                                                api.appsProvider.categoriesAndApps[category.name] ?: emptyList()
-                                        }
-                                    }
+                    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
-                                        ) { 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()
+                                    state.hide(0)
                                 }
                             }
                         }
                     }
-                    AppsBottomBar(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .height(48.dp)
-                            .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
         }
+        onVisibilityChange(state.isVisible)
     }
 }
-
-@Composable
-fun AppsBottomBar(
-    modifier: Modifier = Modifier
-) = Box(
-    modifier = modifier
-) {
-}

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

@@ -5,7 +5,6 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 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
@@ -16,7 +15,7 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.fonticon.MaterialIcon
 import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.data.App
 
 @Preview
@@ -26,8 +25,9 @@ fun AppsMenuApp(
     app: App? = null,
     icon: String? = null,
     iconSize: DpSize = DpSize(32.dp, 32.dp),
+    iconTint: Color = Color.Black,
     textColor: Color = Color.White,
-    iconTint: Color = Color.White,
+    backgroundColor: Color = Color.White,
     api: DesktopProvider = LocalDesktop.current,
     onClick: (app: App?) -> Unit = { app?.start() }
 ) = Row(
@@ -36,9 +36,10 @@ fun AppsMenuApp(
 ) {
     val materialIcon = api.appsProvider.iconForApp(app?.name ?: icon) ?: "?".toInt()
     MaterialIcon(
-        modifier = modifier.size(iconSize),
         iconId = materialIcon,
-        tint = iconTint
+        iconSize = iconSize,
+        iconColor = iconTint,
+        iconBackgroundColor = backgroundColor
     )
     TextAny(
         modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),

+ 6 - 5
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt

@@ -5,7 +5,6 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 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
@@ -16,7 +15,7 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.fonticon.MaterialIcon
 import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.data.Category
 
 @Preview
@@ -25,8 +24,9 @@ fun AppsMenuCategory(
     modifier: Modifier = Modifier,
     category: Category = Category.Empty,
     iconSize: DpSize = DpSize(32.dp, 32.dp),
-    iconTint: Color = Color.White,
+    iconTint: Color = Color.Black,
     textColor: Color = Color.White,
+    backgroundColor: Color = Color.White,
     api: DesktopProvider = LocalDesktop.current,
     onClick: (category: Category) -> Unit = {}
 ) = Row(
@@ -35,9 +35,10 @@ fun AppsMenuCategory(
 ) {
     val materialIcon = api.appsProvider.iconForApp(category.name) ?: "?".toInt()
     MaterialIcon(
-        modifier = modifier.size(iconSize),
         iconId = materialIcon,
-        tint = iconTint
+        iconSize = iconSize,
+        iconColor = iconTint,
+        iconBackgroundColor = backgroundColor
     )
     TextAny(
         modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),

+ 11 - 5
src/main/kotlin/eu/mjdev/desktop/components/BackgroundImage.kt → src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt

@@ -1,7 +1,10 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.background
 
-import androidx.compose.animation.*
+import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -11,6 +14,7 @@ import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.ContentScale
+import eu.mjdev.desktop.components.image.ImageAny
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.helpers.Queue.Companion.mutableQueue
 import kotlinx.coroutines.delay
@@ -44,8 +48,10 @@ fun BackgroundImage(
         )
     }
     launchedEffect(currentBackground.value) {
-        delay(switchDelay)
-        currentBackground.value = backgroundQueue.value
-        onChange(currentBackground.value)
+        if (switchDelay > 0) {
+            delay(switchDelay)
+            currentBackground.value = backgroundQueue.value
+            onChange(currentBackground.value)
+        }
     }
 }

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

@@ -22,11 +22,11 @@ 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.SlideMenuState
-import eu.mjdev.desktop.components.SlideMenuState.Companion.rememberSlideMenuState
-import eu.mjdev.desktop.components.SlidingMenu
 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
@@ -46,7 +46,7 @@ fun ControlPanel(
     controlCenterIconSize: DpSize,
     pages: List<ControlCenterPage>,
     pagerState: PagerState = rememberPagerState(pageCount = { pages.size }),
-    state: SlideMenuState = rememberSlideMenuState(),
+    state: VisibilityState = rememberVisibilityState(),
     scope: CoroutineScope = rememberCoroutineScope(),
     onVisibilityChange: (visible: Boolean) -> Unit = {},
 ) = SlidingMenu(

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

@@ -9,11 +9,11 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.components.DateTime
-import eu.mjdev.desktop.components.PowerBlock
-import eu.mjdev.desktop.components.UserAvatar
 import eu.mjdev.desktop.components.controlpanel.ControlCenterPage
-import eu.mjdev.desktop.extensions.Compose.rectShadow
+import eu.mjdev.desktop.components.custom.DateTime
+import eu.mjdev.desktop.components.custom.PowerBlock
+import eu.mjdev.desktop.components.custom.UserAvatar
+import eu.mjdev.desktop.extensions.Modifier.rectShadow
 
 @Suppress("FunctionName")
 fun MainSettingsPage() = ControlCenterPage(

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/Clock.kt → src/main/kotlin/eu/mjdev/desktop/components/custom/Clock.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.custom
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box

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

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.custom
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background

+ 6 - 6
src/main/kotlin/eu/mjdev/desktop/components/PowerBlock.kt → src/main/kotlin/eu/mjdev/desktop/components/custom/PowerBlock.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.custom
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.Image
@@ -8,7 +8,7 @@ import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.*
+import androidx.compose.material.icons.filled.SettingsPower
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -16,12 +16,12 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.extensions.Compose.circleBorder
-import eu.mjdev.desktop.extensions.Compose.circleShadow
-import eu.mjdev.desktop.extensions.Compose.clipCircle
 import eu.mjdev.desktop.extensions.Compose.noElevation
-import eu.mjdev.desktop.extensions.Compose.rectShadow
 import eu.mjdev.desktop.extensions.Compose.transparent
+import eu.mjdev.desktop.extensions.Modifier.circleBorder
+import eu.mjdev.desktop.extensions.Modifier.circleShadow
+import eu.mjdev.desktop.extensions.Modifier.clipCircle
+import eu.mjdev.desktop.extensions.Modifier.rectShadow
 
 @Preview
 @Composable

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

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.custom
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.Image
@@ -16,12 +16,12 @@ import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.extensions.Compose.circleBorder
-import eu.mjdev.desktop.extensions.Compose.clipCircle
 import eu.mjdev.desktop.extensions.Compose.noElevation
 import eu.mjdev.desktop.extensions.Compose.transparent
+import eu.mjdev.desktop.extensions.Modifier.circleBorder
+import eu.mjdev.desktop.extensions.Modifier.clipCircle
 import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Preview
 @Composable

+ 7 - 6
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -16,13 +16,13 @@ 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.SlideMenuState
-import eu.mjdev.desktop.components.SlideMenuState.Companion.rememberSlideMenuState
-import eu.mjdev.desktop.components.SlidingMenu
 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.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Suppress("FunctionName")
 @Composable
@@ -33,11 +33,11 @@ fun DesktopPanel(
     iconColor: Color = Color.Black,
     iconBackgroundColor: Color = Color.White,
     dividerWidth: Dp = 4.dp,
-    iconSize: DpSize = DpSize(56.dp, 56.dp),
+    iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     panelAlpha: Float = 0.7f,
     showMenuIcon: Boolean = true,
-    state: SlideMenuState = rememberSlideMenuState(),
+    state: VisibilityState = rememberVisibilityState(),
     onVisibilityChange: (visible: Boolean) -> Unit = {},
     onMenuIconClicked: () -> Unit = {},
     api: DesktopProvider = LocalDesktop.current
@@ -115,6 +115,7 @@ fun DesktopPanel(
                 item {
                     DesktopPanelText(
                         text = api.appsProvider.currentLocale.country,
+                        backgroundHover = Color.White.copy(alpha = 0.3f),
                     )
                 }
             }

+ 24 - 31
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.background
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.onClick
-import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+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
@@ -20,14 +19,14 @@ import androidx.compose.ui.input.pointer.onPointerEvent
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.fonticon.MaterialIcon
-import eu.mjdev.desktop.extensions.Compose.clipCircle
 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.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
+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(
@@ -37,15 +36,16 @@ fun DesktopPanelIcon(
     iconColor: Color = Color.Black,
     iconBackgroundColor: Color = Color.White,
     iconBackgroundHover: Color = Color.Red,
-    iconSize: DpSize = DpSize(56.dp, 56.dp),
+    iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconState: MutableState<Boolean> = remember { mutableStateOf(false) },
     onClick: () -> Unit = { app?.start() },
 ) {
     val materialIcon = api.appsProvider.iconForApp(app?.name ?: icon) ?: "?".toInt()
+    val background = remember(iconState.value) { if (iconState.value) iconBackgroundHover else Color.Transparent }
     Box(
         modifier = Modifier
-            .size(iconSize)
+            .wrapContentSize()
             .onPointerEvent(PointerEventType.Enter) {
                 if (api.windowFocusState.isFocused) {
                     iconState.value = true
@@ -56,33 +56,26 @@ fun DesktopPanelIcon(
                     iconState.value = false
                 }
             }
+            .onPointerEvent(PointerEventType.Press) {
+                if (api.windowFocusState.isFocused) {
+                    iconState.value = false
+                }
+            }
     ) {
         Button(
-            modifier = Modifier
-                .size(iconSize)
-                .padding(iconPadding),
+            modifier = Modifier.size(iconSize + iconPadding.size),
             contentPadding = PaddingValues(0.dp),
             onClick = onClick,
-            colors = ButtonDefaults.color(if (iconState.value) iconBackgroundHover else Color.Transparent),
+            colors = ButtonDefaults.color(background),
             elevation = ButtonDefaults.noElevation()
         ) {
-            Box(
-                modifier = Modifier
-                    .padding(2.dp)
-                    .size(iconSize)
-                    .background(iconBackgroundColor, CircleShape)
-                    .clipCircle()
-            ) {
-                MaterialIcon(
-                    iconId = materialIcon,
-                    modifier = Modifier
-                        .size(iconSize)
-                        .padding(2.dp)
-                        // todo app manager
-                        .onClick { onClick() },
-                    tint = iconColor
-                )
-            }
+            MaterialIcon(
+                iconId = materialIcon,
+                iconColor = iconColor,
+                iconBackgroundColor = iconBackgroundColor,
+                iconSize = iconSize,
+                innerPadding = iconPadding
+            )
         }
     }
 }

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

@@ -1,7 +1,10 @@
 package eu.mjdev.desktop.components.desktoppanel
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Text
@@ -19,7 +22,7 @@ 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.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Preview

+ 12 - 0
src/main/kotlin/eu/mjdev/desktop/components/fonticon/CodePointsFile.kt

@@ -0,0 +1,12 @@
+package eu.mjdev.desktop.components.fonticon
+
+import eu.mjdev.desktop.helpers.ResourceStream
+
+class CodePointsFile(
+    private val codepointsFileName: String,
+    private val codepointsResourceResource: ResourceStream = ResourceStream(codepointsFileName),
+    val icons: Map<String, Int> = codepointsResourceResource.string.split("\n")
+        .map { it.split(" ") }
+        .mapNotNull { if (it.size == 2) Pair(it[0], it[1].toInt(radix = 16)) else null }
+        .toMap()
+)

+ 25 - 0
src/main/kotlin/eu/mjdev/desktop/components/fonticon/FontFile.kt

@@ -0,0 +1,25 @@
+package eu.mjdev.desktop.components.fonticon
+
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.platform.Typeface
+import eu.mjdev.desktop.helpers.ResourceStream
+import org.jetbrains.skia.Data
+import org.jetbrains.skia.FontVariation
+import org.jetbrains.skia.Typeface
+
+class FontFile(
+    private val ttfFileName: String,
+    private val ttfResource: ResourceStream = ResourceStream(ttfFileName),
+    private val fontWeight: FontWeight = FontWeight.Normal,
+    val fontFamily: FontFamily = Typeface.makeFromData(Data.makeFromBytes(ttfResource.bytes))
+        .makeClone(fontWeight).let {
+            FontFamily(typeface = Typeface(it, "alias${fontWeight.weight}"))
+        }
+) {
+    companion object {
+        fun Typeface.makeClone(weight: FontWeight): Typeface {
+            return makeClone(FontVariation("wght", weight.weight.toFloat()))
+        }
+    }
+}

+ 35 - 72
src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIcon.kt

@@ -2,86 +2,49 @@
 
 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
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import eu.mjdev.desktop.helpers.ResourceStream
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.platform.Typeface
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.text.style.TextAlign
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.LocalDesktop
-import org.jetbrains.skia.Data
-import org.jetbrains.skia.FontVariation
-import org.jetbrains.skia.Typeface
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.components.icon.ShapedIcon
+import eu.mjdev.desktop.extensions.Compose.sp
+import eu.mjdev.desktop.extensions.Compose.width
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Composable
 fun MaterialIcon(
-    api: DesktopProvider = LocalDesktop.current,
-    iconicFont: MaterialIconFont = api.currentUser.theme.iconSet,
+    iconicFont: MaterialIconFont = LocalDesktop.current.currentUser.theme.iconSet,
     iconId: Int,
-    modifier: Modifier = Modifier,
-    size: Dp = 32.dp,
-    tint: Color = Color.Unspecified
-) {
-    Text(
-        text = iconId.toChar().toString(),
-        modifier = modifier,
-        color = tint,
-        fontSize = size.value.sp,
-        fontFamily = iconicFont.fontFile.fontFamily,
-        textAlign = TextAlign.Center
-    )
-}
-
-val MaterialSymbolsOutlined = MaterialIconFont(
-    "icons/MaterialSymbolsOutlined.ttf",
-    "icons/MaterialSymbolsOutlined.codepoints"
-)
-
-val MaterialSymbolsRounded = MaterialIconFont(
-    "icons/MaterialSymbolsRounded.ttf",
-    "icons/MaterialSymbolsRounded.codepoints"
-)
-
-val MaterialSymbolsSharp = MaterialIconFont(
-    "icons/MaterialSymbolsSharp.ttf",
-    "icons/MaterialSymbolsSharp.codepoints"
-)
-
-class MaterialIconFont(
-    private val ttfFileName: String,
-    private val codepointsFileName: String,
-    val fontFile: FontFile = FontFile(ttfFileName),
-    val codePointsFile: CodePointsFile = CodePointsFile(codepointsFileName)
-)
-
-class FontFile(
-    private val ttfFileName: String,
-    private val ttfResource: ResourceStream = ResourceStream(ttfFileName),
-    private val fontWeight: FontWeight = FontWeight.Normal,
-    val fontFamily: FontFamily = Typeface.makeFromData(Data.makeFromBytes(ttfResource.bytes))
-        .makeClone(fontWeight).let {
-            FontFamily(typeface = Typeface(it, "alias${fontWeight.weight}"))
-        }
-) {
-    companion object {
-        fun Typeface.makeClone(weight: FontWeight): Typeface {
-            return makeClone(FontVariation("wght", weight.weight.toFloat()))
-        }
+    iconSize: DpSize = DpSize(32.dp, 32.dp),
+    iconColor: Color = Color.Black,
+    iconBackgroundColor: Color = Color.White,
+    innerPadding: PaddingValues = PaddingValues(4.dp),
+    contentDescription: String = "",
+    outerPadding: PaddingValues = PaddingValues(2.dp),
+    iconShape: Shape = CircleShape,
+) = ShapedIcon(
+    iconSize = iconSize,
+    iconColor = iconColor,
+    iconBackgroundColor = iconBackgroundColor,
+    contentDescription = contentDescription,
+    iconShape = iconShape,
+    innerPadding = innerPadding,
+    outerPadding = outerPadding,
+    customContent = {
+        Text(
+            text = iconId.toChar().toString(),
+            modifier = Modifier.align(Alignment.Center),
+            color = iconColor,
+            fontSize = (iconSize.height - (innerPadding.width + outerPadding.width)).sp,
+            fontFamily = iconicFont.fontFile.fontFamily,
+            textAlign = TextAlign.Center
+        )
     }
-}
-
-class CodePointsFile(
-    private val codepointsFileName: String,
-    private val codepointsResourceResource: ResourceStream = ResourceStream(codepointsFileName),
-    val icons: Map<String, Int> = codepointsResourceResource.string.split("\n")
-        .map { it.split(" ") }
-        .mapNotNull { if (it.size == 2) Pair(it[0], it[1].toInt(radix = 16)) else null }
-        .toMap()
 )

+ 28 - 0
src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIconFont.kt

@@ -0,0 +1,28 @@
+@file:Suppress("unused")
+
+package eu.mjdev.desktop.components.fonticon
+
+class MaterialIconFont(
+    private val ttfFileName: String,
+    private val codepointsFileName: String,
+    val fontFile: FontFile = FontFile(ttfFileName),
+    val codePointsFile: CodePointsFile = CodePointsFile(codepointsFileName)
+)
+
+val MaterialSymbolsOutlined
+    get() = MaterialIconFont(
+        "icons/MaterialSymbolsOutlined.ttf",
+        "icons/MaterialSymbolsOutlined.codepoints"
+    )
+
+val MaterialSymbolsRounded
+    get() = MaterialIconFont(
+        "icons/MaterialSymbolsRounded.ttf",
+        "icons/MaterialSymbolsRounded.codepoints"
+    )
+
+val MaterialSymbolsSharp
+    get() = MaterialIconFont(
+        "icons/MaterialSymbolsSharp.ttf",
+        "icons/MaterialSymbolsSharp.codepoints"
+    )

+ 49 - 0
src/main/kotlin/eu/mjdev/desktop/components/icon/ShapedIcon.kt

@@ -0,0 +1,49 @@
+package eu.mjdev.desktop.components.icon
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Icon
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Apps
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+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
+
+@Composable
+fun ShapedIcon(
+    imageVector: ImageVector = Icons.Filled.Apps,
+    iconBackgroundColor: Color = Color.White,
+    iconColor: Color = Color.Black,
+    iconSize: DpSize = DpSize(32.dp, 32.dp),
+    contentDescription: String = "",
+    customContent: (@Composable BoxScope.() -> Unit)? = null,
+    iconShape: Shape = CircleShape,
+    innerPadding: PaddingValues = PaddingValues(4.dp),
+    outerPadding: PaddingValues = PaddingValues(2.dp),
+) = Box(
+    modifier = Modifier
+        .padding(outerPadding)
+        .size(iconSize)
+        .background(iconBackgroundColor, iconShape)
+        .clip(iconShape)
+        .padding(innerPadding)
+) {
+    if (customContent != null) {
+        customContent()
+    } else {
+        Icon(
+            modifier = Modifier
+                .fillMaxSize()
+                .clip(iconShape),
+            imageVector = imageVector,
+            contentDescription = contentDescription,
+            tint = iconColor
+        )
+    }
+}

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/ImageAny.kt → src/main/kotlin/eu/mjdev/desktop/components/image/ImageAny.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.image
 
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column

+ 6 - 54
src/main/kotlin/eu/mjdev/desktop/components/SlidingMenu.kt → src/main/kotlin/eu/mjdev/desktop/components/slidemenu/SlidingMenu.kt

@@ -1,22 +1,19 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.slidemenu
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.Orientation.Vertical
 import androidx.compose.foundation.gestures.Orientation.Horizontal
+import androidx.compose.foundation.gestures.Orientation.Vertical
 import androidx.compose.foundation.layout.*
-import androidx.compose.runtime.*
+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.SlideMenuState.Companion.rememberSlideMenuState
+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.LocalDesktop
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Suppress("FunctionName")
@@ -24,7 +21,7 @@ import kotlinx.coroutines.launch
 @Composable
 fun SlidingMenu(
     modifier: Modifier = Modifier,
-    state: SlideMenuState = rememberSlideMenuState(),
+    state: VisibilityState = rememberVisibilityState(),
     orientation: Orientation = Horizontal,
     onVisibilityChange: (visible: Boolean) -> Unit = {},
     api: DesktopProvider = LocalDesktop.current,
@@ -66,48 +63,3 @@ fun SlidingMenu(
 //        }
 //    }
 }
-
-@Suppress("CanBeParameter")
-class SlideMenuState(
-    private val scope: CoroutineScope,
-    private val startState: Boolean = false,
-    private val enabled: Boolean = true
-) {
-    var isVisible: Boolean
-        get() = visible.value
-        set(value) {
-            visible.value = value
-        }
-
-    private val visible: MutableState<Boolean> = mutableStateOf(startState)
-
-    fun show() {
-        if (enabled) scope.launch {
-            visible.value = true
-        }
-    }
-
-    fun hide(delay: Long = 0L) {
-        if (enabled) scope.launch {
-            delay(delay)
-            visible.value = false
-        }
-    }
-
-    fun toggle() {
-        visible.value = !visible.value
-    }
-
-    companion object {
-        @Composable
-        fun rememberSlideMenuState(
-            startState: Boolean = false,
-            enabled: Boolean = true
-        ): SlideMenuState {
-            val scope = rememberCoroutineScope()
-            return remember(startState, enabled) {
-                SlideMenuState(scope, startState, enabled)
-            }
-        }
-    }
-}

+ 51 - 0
src/main/kotlin/eu/mjdev/desktop/components/slidemenu/VisibilityState.kt

@@ -0,0 +1,51 @@
+package eu.mjdev.desktop.components.slidemenu
+
+import androidx.compose.runtime.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+@Suppress("CanBeParameter")
+class VisibilityState(
+    private val scope: CoroutineScope,
+    private val startState: Boolean = false,
+    private val enabled: Boolean = true
+) {
+    var isVisible: Boolean
+        get() = visible.value
+        set(value) {
+            visible.value = value
+        }
+
+    private val visible: MutableState<Boolean> = mutableStateOf(startState)
+
+    fun show() {
+        if (enabled) scope.launch {
+            visible.value = true
+        }
+    }
+
+    fun hide(delay: Long = 0L) {
+        if (enabled) scope.launch {
+            delay(delay)
+            visible.value = false
+        }
+    }
+
+    fun toggle() {
+        visible.value = !visible.value
+    }
+
+    companion object {
+        @Composable
+        fun rememberVisibilityState(
+            startState: Boolean = false,
+            enabled: Boolean = true
+        ): VisibilityState {
+            val scope = rememberCoroutineScope()
+            return remember(startState, enabled) {
+                VisibilityState(scope, startState, enabled)
+            }
+        }
+    }
+}

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

@@ -19,7 +19,7 @@ import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import eu.mjdev.desktop.extensions.Compose.clipCircle
+import eu.mjdev.desktop.extensions.Modifier.clipCircle
 
 @Preview
 @Composable

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

@@ -18,7 +18,7 @@ import androidx.compose.ui.unit.sp
 @Composable
 fun TextWithShadow(
     modifier: Modifier = Modifier,
-    shadowSize: Dp = 8.dp,
+    shadowSize: Dp = 4.dp,
     shadowColor: Color = Color.Black,
     offsetX: Dp = 0.dp,
     offsetY: Dp = 0.dp,

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/ComposeWebView.kt → src/main/kotlin/eu/mjdev/desktop/components/web/ComposeWebView.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.web
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.runtime.Composable

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/WidgetsPanel.kt → src/main/kotlin/eu/mjdev/desktop/components/widgets/WidgetsPanel.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components
+package eu.mjdev.desktop.components.widgets
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box

+ 90 - 95
src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt

@@ -2,26 +2,22 @@
 
 package eu.mjdev.desktop.extensions
 
-import androidx.compose.foundation.border
-import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material.ButtonDefaults
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.draw.shadow
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.*
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+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.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.Companion.desktopLocalProvider
 import kotlinx.coroutines.CoroutineScope
-import org.jetbrains.skia.FilterBlurMode
-import org.jetbrains.skia.MaskFilter
 
 object Compose {
 
@@ -37,17 +33,28 @@ object Compose {
     val Color.Companion.LiteDarkGray: Color
         get() = Color(0xffc0c0c0)
 
-    fun Modifier.conditional(
-        condition: Boolean,
-        onFalse: (Modifier.() -> Modifier)? = null,
-        onTrue: Modifier.() -> Modifier,
-    ): Modifier = if (condition) {
-        then(onTrue(Modifier))
-    } else if (onFalse != null) {
-        then(onFalse(Modifier))
-    } else {
-        this
-    }
+    val PaddingValues.width: Dp
+        get() = calculateLeftPadding(LayoutDirection.Ltr) + calculateRightPadding(LayoutDirection.Ltr)
+
+    val Dp.sp: TextUnit
+        get() = value.sp
+
+    val PaddingValues.height: Dp
+        get() = calculateTopPadding() + calculateBottomPadding()
+
+    val PaddingValues.size: DpSize
+        get() = DpSize(width, height)
+
+    @Composable
+    fun launchedEffect(
+        block: suspend CoroutineScope.() -> Unit
+    ) = LaunchedEffect(Unit, block)
+
+    @Composable
+    fun launchedEffect(
+        key: Any?,
+        block: suspend CoroutineScope.() -> Unit
+    ) = LaunchedEffect(key, block)
 
     @Composable
     fun <T> textFrom(text: T?): String = when (text) {
@@ -63,17 +70,6 @@ object Compose {
     fun ButtonDefaults.noElevation() =
         elevation(0.dp, 0.dp, 0.dp, 0.dp, 0.dp)
 
-    @Composable
-    fun launchedEffect(
-        block: suspend CoroutineScope.() -> Unit
-    ) = LaunchedEffect(Unit, block)
-
-    @Composable
-    fun launchedEffect(
-        key: Any?,
-        block: suspend CoroutineScope.() -> Unit
-    ) = LaunchedEffect(key, block)
-
     @Composable
     fun ButtonDefaults.color(color: Color) = buttonColors(
         backgroundColor = color,
@@ -90,78 +86,77 @@ object Compose {
         disabledBackgroundColor = Color.Transparent
     )
 
-    @Stable
-    fun Modifier.shadow(
-        elevation: Dp,
-        shape: Shape = RectangleShape,
-        color: Color = DefaultShadowColor,
-        clip: Boolean = false,
-    ): Modifier = shadow(elevation, shape, clip, color, color)
-
-    @Composable
-    fun Modifier.rectShadow(
-        size: Dp = 8.dp,
-        color: Color = Color.Black,
-        shape: Shape = RectangleShape
-    ) = shadow(size, shape, color)
-
-    @Composable
-    fun Modifier.circleShadow(
-        size: Dp = 8.dp,
-        color: Color = Color.Black,
-        shape: Shape = CircleShape
-    ) = shadow(size, shape, color)
-
-    @Composable
-    fun Modifier.clipCircle() = clip(CircleShape)
-
     @Composable
-    fun Modifier.circleBorder(
-        width: Dp = 2.dp,
-        color: Color = Color.White
-    ) = clipCircle().border(width, color, CircleShape)
-
-    @Composable
-    fun Modifier.coloredCircleShadow(
-        color: Color,
-        blurRadius: Float,
-        offsetY: Dp,
-        offsetX: Dp,
-    ) = then(
-        drawBehind {
-            drawIntoCanvas { canvas ->
-                val paint = Paint()
-                val frameworkPaint = paint.asFrameworkPaint()
-                if (blurRadius != 0f) {
-                    frameworkPaint.setMaskFilter(blurRadius)
+    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()
                 }
-                frameworkPaint.color = color.toArgb()
-                val centerX = size.width / 2 + offsetX.toPx()
-                val centerY = size.height / 2 + offsetY.toPx()
-                val radius = size.width.coerceAtLeast(size.height) / 2
-                canvas.drawCircle(Offset(centerX, centerY), radius, paint)
             }
         }
     )
 
-    fun NativePaint.setMaskFilter(
-        blurRadius: Float
-    ) {
-        this.maskFilter = MaskFilter.makeBlur(FilterBlurMode.NORMAL, blurRadius / 2, true)
-    }
-
     @Composable
-    fun fullScreenWindow(
+    fun TopWindow(
+        // todo
+        @Suppress("UNUSED_PARAMETER") movable: Boolean = false,
+        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 },
         onCloseRequest: () -> Unit = {},
         content: @Composable FrameWindowScope.() -> Unit
     ) = Window(
         state = rememberWindowState(
-            placement = WindowPlacement.Fullscreen
+            placement = WindowPlacement.Floating
         ),
-        resizable = false,
-        undecorated = true,
+        resizable = resizable,
+        undecorated = undecorated,
         onCloseRequest = onCloseRequest,
-        content = content
+        visible = visible,
+        transparent = true,
+        title = title,
+        icon = icon,
+        focusable = focusable,
+        alwaysOnTop = true,
+        onPreviewKeyEvent = onPreviewKeyEvent,
+        onKeyEvent = onKeyEvent,
+        content = {
+            MaterialTheme {
+                desktopLocalProvider {
+                    content()
+                }
+            }
+        }
     )
 
 }

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

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.extensions
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.graphics.NativePaint
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.channelFlow
 import kotlinx.coroutines.launch
@@ -10,6 +11,13 @@ import java.util.*
 
 object Custom {
 
+    fun NativePaint.setMaskFilter(
+        blurRadius: Float
+    ) {
+        this.maskFilter =
+            org.jetbrains.skia.MaskFilter.makeBlur(org.jetbrains.skia.FilterBlurMode.NORMAL, blurRadius / 2, true)
+    }
+
     val dateFlow
         @Composable
         get() = channelFlow {

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

@@ -0,0 +1,95 @@
+package eu.mjdev.desktop.extensions
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.extensions.Custom.setMaskFilter
+
+@Suppress("unused")
+object Modifier {
+
+    @Stable
+    @Composable
+    fun Modifier.conditional(
+        condition: Boolean,
+        onFalse: (Modifier.() -> Modifier)? = null,
+        onTrue: Modifier.() -> Modifier,
+    ): Modifier = if (condition) {
+        then(onTrue(Modifier))
+    } else if (onFalse != null) {
+        then(onFalse(Modifier))
+    } else {
+        this
+    }
+
+    @Stable
+    @Composable
+    fun Modifier.shadow(
+        elevation: Dp,
+        shape: Shape = RectangleShape,
+        color: Color = DefaultShadowColor,
+        clip: Boolean = false,
+    ): Modifier = shadow(elevation, shape, clip, color, color)
+
+    @Stable
+    @Composable
+    fun Modifier.rectShadow(
+        size: Dp = 8.dp,
+        color: Color = Color.Black,
+        shape: Shape = RectangleShape
+    ) = shadow(size, shape, color)
+
+    @Stable
+    @Composable
+    fun Modifier.circleShadow(
+        size: Dp = 8.dp,
+        color: Color = Color.Black,
+        shape: Shape = CircleShape
+    ) = shadow(size, shape, color)
+
+    @Stable
+    @Composable
+    fun Modifier.clipCircle() = clip(CircleShape)
+
+    @Stable
+    @Composable
+    fun Modifier.circleBorder(
+        width: Dp = 2.dp,
+        color: Color = Color.White
+    ) = clipCircle().border(width, color, CircleShape)
+
+    @Stable
+    @Composable
+    fun Modifier.coloredCircleShadow(
+        color: Color,
+        blurRadius: Float,
+        offsetY: Dp,
+        offsetX: Dp,
+    ) = then(
+        drawBehind {
+            drawIntoCanvas { canvas ->
+                val paint = Paint()
+                val frameworkPaint = paint.asFrameworkPaint()
+                if (blurRadius != 0f) {
+                    frameworkPaint.setMaskFilter(blurRadius)
+                }
+                frameworkPaint.color = color.toArgb()
+                val centerX = size.width / 2 + offsetX.toPx()
+                val centerY = size.height / 2 + offsetY.toPx()
+                val radius = size.width.coerceAtLeast(size.height) / 2
+                canvas.drawCircle(Offset(centerX, centerY), radius, paint)
+            }
+        }
+    )
+
+}

+ 28 - 7
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -1,13 +1,15 @@
 package eu.mjdev.desktop.provider
 
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.*
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.LocalWindowInfo
 import androidx.compose.ui.unit.IntSize
 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.io.BufferedReader
 import java.io.File
@@ -72,7 +74,7 @@ class DesktopProvider(
         }.start()
     }
 
-    fun runCommandForOutput(vararg cmd:String): String {
+    fun runCommandForOutput(vararg cmd: String): String {
         val pb = ProcessBuilder(*cmd)
         val p: Process
         var result = ""
@@ -92,8 +94,27 @@ class DesktopProvider(
         }
         return result
     }
-}
 
-val LocalDesktop = compositionLocalOf {
-    DesktopProvider()
+    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()
+            }
+        }
+    }
 }

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

@@ -9,9 +9,11 @@ import eu.mjdev.desktop.components.fonticon.MaterialIconFont
 import eu.mjdev.desktop.components.fonticon.MaterialSymbolsSharp
 import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 
+// todo all customizable
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 class Theme(
     backgroundColor: Color,
+    backgroundRotationDelay: Long,
 
     panelLocation: PanelLocation,
     panelHideDelay: Long,
@@ -24,7 +26,7 @@ class Theme(
     controlCenterIconColor: Color,
     controlCenterIconSize: DpSize,
 
-    iconSet: MaterialIconFont
+    iconSet: MaterialIconFont,
 ) {
     val backgroundColorState = mutableStateOf(backgroundColor)
     val panelLocationState = mutableStateOf(panelLocation)
@@ -37,6 +39,7 @@ class Theme(
     val controlCenterIconColorState = mutableStateOf(controlCenterIconColor)
     val controlCenterIconSizeState = mutableStateOf(controlCenterIconSize)
     val iconSetState = mutableStateOf(iconSet)
+    val backgroundRotationDelayState = mutableStateOf(backgroundRotationDelay)
 
     var backgroundColor
         get() = backgroundColorState.value
@@ -57,6 +60,7 @@ class Theme(
     val controlCenterIconColor get() = controlCenterIconColorState.value
     val controlCenterIconSize get() = controlCenterIconSizeState.value
     val iconSet get() = iconSetState.value
+    val backgroundRotationDelay get() = backgroundRotationDelayState.value
 
     companion object {
         val Default = Theme(
@@ -73,7 +77,8 @@ class Theme(
             controlCenterDividerColor = Color.SuperDarkGray,
             controlCenterDividerWidth = 2.dp,
             controlCenterIconColor = Color.White,
-            controlCenterIconSize = DpSize(32.dp, 32.dp)
+            controlCenterIconSize = DpSize(32.dp, 32.dp),
+            backgroundRotationDelay = 10000
         )
     }
 }

+ 27 - 24
src/main/kotlin/eu/mjdev/desktop/screens/MainScreen.kt → src/main/kotlin/eu/mjdev/desktop/screens/MainWindow.kt

@@ -5,33 +5,34 @@ 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.material.MaterialTheme
-import androidx.compose.runtime.*
+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.*
-import eu.mjdev.desktop.components.SlideMenuState.Companion.rememberSlideMenuState
 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.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
 @Suppress("FunctionName")
 @Preview
 @Composable
-fun MainScreen() = MaterialTheme {
+fun MainWindow() = FullScreenWindow {
     val api: DesktopProvider = LocalDesktop.current
     val palette = rememberPalette(api.currentUser.theme.backgroundColor)
-    val panelState = rememberSlideMenuState(true, api.currentUser.config.autoHidePanel)
-    val controlCenterState = rememberSlideMenuState()
-    val menuState = rememberSlideMenuState()
+    val panelState = rememberVisibilityState(true, api.currentUser.config.autoHidePanel)
+    val controlCenterState = rememberVisibilityState()
+    val menuState = rememberVisibilityState()
     Box(
         modifier = Modifier.fillMaxSize().background(api.currentUser.theme.backgroundColor)
     ) {
@@ -39,29 +40,31 @@ fun MainScreen() = MaterialTheme {
             modifier = Modifier
                 .fillMaxSize()
                 .onPointerEvent(PointerEventType.Enter) {
-                    if (api.windowFocusState.isFocused) {
-                        controlCenterState.hide(0)
-                        panelState.hide(0)
-                        menuState.hide(0)
-                    }
+                    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) {
-//                    if (api.windowFocusState.isFocused) {
-//                        controlCenterState.hide(0)
-//                        panelState.hide(0)
-//                    }
-//                },
-//        )
+        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,