Bladeren bron

code improvements

code improvements

code improvements

code improvements
Milan Jurkulak 2 jaren geleden
bovenliggende
commit
9ad1a35c96

+ 49 - 9
build.gradle.kts

@@ -5,6 +5,7 @@ plugins {
     alias(libs.plugins.compose.desktop)
     alias(libs.plugins.kotlin.serialization)
 //    id("dev.datlag.sekret") version "2.0.0-alpha-07"
+//    id("com.bintray.gradle.plugins.bintray")
 }
 
 group = libs.versions.packageName.get()
@@ -53,8 +54,6 @@ allprojects {
         implementation(libs.coil.network.okhttp)
         // ffmpeg
         implementation(libs.ffmpeg.platform)
-        // music
-//        implementation(libs.korau)
         // palette
         implementation(libs.material.kolor)
         // json
@@ -67,10 +66,13 @@ allprojects {
         implementation(libs.charts)
         // gemini ai
         implementation(libs.google.generativeai)
-        // compose debugger
-        implementation("io.github.theapache64:rebugger:1.0.0-rc03")
+        // jna
+        implementation(libs.jna.platform)
         // qr code
-        implementation("io.github.alexzhirkevich:qrose:1.0.1")
+        implementation(libs.qrose)
+        // tts
+        implementation(libs.tts)
+        // testing yet
         // connectivity
         implementation("dev.tmapps:konnection:1.4.1")
         // web
@@ -79,8 +81,13 @@ allprojects {
         implementation("com.github.skydoves:flexible-bottomsheet-material3:0.1.5")
         // fs watcher
         implementation("io.github.irgaly.kfswatch:kfswatch:1.3.0")
-        // tts
-        implementation("nl.marc-apps:tts:2.5.0")
+        // compose debugger
+//        implementation("io.github.theapache64:rebugger:1.0.0-rc03")
+        // music
+//        implementation(libs.korau)
+        // dbus
+//        implementation("org.freedesktop.dbus:dbus-java:1.2.0") // Replace with the desired version
+//        implementation("org.freedesktop.dbus:dbus-java-annotations:1.2.0")// Replace with the desired version
         // bar code
 //        implementation("io.github.alexzhirkevich:qrose-oned:1.0.1")
         // flow extensions
@@ -95,8 +102,7 @@ allprojects {
 //        implementation("com.github.moaxcp.x11:x11-client:0.18.2")
         // jwm
 //        implementation("io.github.humbleui:jwm:0.4.8")
-        // jna
-        implementation("net.java.dev.jna:jna-platform:5.6.0")
+
         // hid
 //        implementation('org.hid4java:hid4java')
         // dbus
@@ -122,6 +128,40 @@ fun Project.createTask(
     configureAction.invoke(this)
 }
 
+//tasks.withType(Copy::class) {
+//    from {
+//        fileTree("native") {
+//            include "*.so", "*.dylib", "*.dll"
+//        }
+//    }
+//    into {
+//        configurations.runtimeClasspath.files.collect { it.absolutePath }
+//    }
+//}
+
+//bintray {
+//    user = 'your_bintray_username'
+//    key = 'your_bintray_api_key'
+//
+//    publish = true
+//    pkg {
+//        repo = 'your_repository_name'
+//        name = project.name
+//        desc = 'Your desktop Compose application'
+//        version = project.version
+//        licenses = ['Apache-2.0']
+//        vcsUrl = 'https://github.com/your_username/your_project'
+//
+//        // Configure deb package dependencies
+//        deb {
+//            packageDescription = 'Your desktop Compose application'
+//            packageVersion = project.version
+//            packageArchitecture = 'amd64'  // Adjust based on your target architecture
+//            packageDepends = ['dbus-java', 'libgtk-3-dev']  // Replace with your actual dependencies
+//        }
+//    }
+//}
+
 compose {
     desktop {
         application {

+ 6 - 0
gradle/libs.versions.toml

@@ -25,6 +25,9 @@ mozilla-rhino = "1.7.13"
 ini4j = "0.5.4"
 charts = "Beta-0.0.5"
 generativeai = "0.5.0-1.0.0-wasm"
+jna-platform = "5.6.0"
+qrose = "1.0.1"
+tts = "2.5.0"
 #korau = "4.0.10"
 #lanscapist = "2.3.8"
 #compose-webview = "1.9.20"
@@ -49,6 +52,9 @@ fuzzywuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzywuzzy" }
 ffmpeg-platform = { module = "org.bytedeco:ffmpeg-platform", version.ref = "ffmpeg-platform" }
 charts = { module = "io.github.thechance101:chart", version.ref = "charts" }
 google-generativeai = { module = "dev.shreyaspatil.generativeai:generativeai-google-jvm", version.ref = "generativeai" }
+jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna-platform" }
+qrose = { module = "io.github.alexzhirkevich:qrose", version.ref = "qrose" }
+tts = { module = "nl.marc-apps:tts", version.ref = "tts" }
 
 #coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil3" }
 #korau = { module = "com.soywiz.korlibs.korau:korau", version.ref = "korau" }

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

@@ -1,6 +1,5 @@
 package eu.mjdev.desktop
 
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.CompositionLocalProvider
@@ -10,7 +9,6 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.application
-import com.theapache64.rebugger.RebuggerConfig
 import eu.mjdev.desktop.components.appsmenu.AppsMenu
 import eu.mjdev.desktop.components.charts.MemoryChart
 import eu.mjdev.desktop.components.controlcenter.ControlCenter
@@ -25,6 +23,10 @@ fun main() = application(
     exitProcessOnExit = true
 ) {
     val scope = rememberCoroutineScope()
+    val api = DesktopProvider(
+        scope = scope,
+        imageLoader = asyncImageLoader()
+    )
     val controlCenterState = rememberVisibilityState()
     val panelState = rememberVisibilityState()
     val menuState = rememberVisibilityState()
@@ -48,21 +50,17 @@ fun main() = application(
         }
     }
 
-    RebuggerConfig.init(
-        tag = "Composition",
-        logger = { tag, message ->
-            println("$tag >  $message")
-        }
-    )
+//    RebuggerConfig.init(
+//        tag = "Composition",
+//        logger = { tag, message ->
+//            println("$tag >  $message")
+//        }
+//    )
 
     MaterialTheme {
         CompositionLocalProvider(
-            LocalDesktop providesDefault DesktopProvider(
-                scope = scope,
-                imageLoader = asyncImageLoader()
-            ),
+            LocalDesktop provides api
         ) {
-            val api: DesktopProvider = LocalDesktop.current
             MainWindow(
                 panelState = panelState,
                 controlCenterState = controlCenterState,
@@ -72,13 +70,12 @@ fun main() = application(
 //                    modifier = Modifier.size(640.dp, 480.dp).align(Alignment.Center)
 //                )
                 MemoryChart(
-                    modifier = Modifier.padding(bottom = 64.dp).size(350.dp, 300.dp).align(Alignment.BottomEnd)
+                    modifier = Modifier.size(350.dp, 300.dp)
+                        .align(Alignment.BottomEnd)
                 )
                 DesktopPanel(
                     panelState = panelState,
-                    onMenuIconClicked = {
-                        menuState.toggle()
-                    },
+                    onMenuIconClicked = { menuState.toggle() },
                     onFocusChange = { focus -> handlePanelFocus(focus) }
                 )
                 AppsMenu(

+ 122 - 121
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.components.appsmenu
 
-import androidx.compose.animation.*
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.Orientation
@@ -9,16 +10,12 @@ import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Divider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
 import eu.mjdev.desktop.components.custom.UserAvatar
@@ -27,6 +24,8 @@ import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVis
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.data.Category
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.helpers.Animations.AppsMenuEnterAnimation
+import eu.mjdev.desktop.helpers.Animations.AppsMenuExitAnimation
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
@@ -36,20 +35,12 @@ fun AppsMenu(
     api: DesktopProvider = LocalDesktop.current,
     panelState: VisibilityState = rememberVisibilityState(),
     menuState: VisibilityState = rememberVisibilityState(),
-    appMenuMinWidth: Dp = api.currentUser.theme.appMenuMinWidth,
-    appMenuMinHeight: Dp = api.currentUser.theme.appMenuMinHeight,
-    appMenuBackgroundColor: Color = api.currentUser.theme.backgroundColor,
-    menuPadding: PaddingValues = PaddingValues(api.currentUser.theme.appMenuOuterPadding),// todo
-    enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
-    exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
-    items: MutableState<List<Any>> = remember(api.appsProvider) { mutableStateOf(api.appsProvider.appCategories) },
+    enterAnimation: EnterTransition = AppsMenuEnterAnimation,
+    exitAnimation: ExitTransition = AppsMenuExitAnimation,
     onFocusChange: (Boolean) -> Unit = {},
     onAppClick: (App) -> Unit = { app ->
         api.appsProvider.startApp(app)
     },
-    onCategoryClick: (Category) -> Unit = { category ->
-        items.value = api.appsProvider.categoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
-    },
     onAppContextMenuClick: (App) -> Unit = {
         // todo
     },
@@ -59,129 +50,139 @@ fun AppsMenu(
     onUserAvatarClick: () -> Unit = {
         // todo
     }
-) = ChromeWindow(
-    visible = menuState.isVisible,
-    enterAnimation = enterAnimation,
-    exitAnimation = exitAnimation,
-    position = WindowPosition.Absolute(
-        panelState.bounds.x,
-        api.containerSize.height - (panelState.bounds.height + appMenuMinHeight)
-    ),
-    onFocusChange = { focused ->
-        menuState.onFocusChange(focused)
-        onFocusChange(focused)
-    }
 ) {
-    Box(
-        modifier = Modifier
-            .width(appMenuMinWidth)
-            .heightIn(
-                min = appMenuMinHeight,
-                max = appMenuMinHeight
-            )
-            .onPlaced(menuState::onPlaced)
+    val appMenuMinWidth by remember { api.currentUser.theme.appMenuMinWidthState }
+    val appMenuMinHeight by remember { api.currentUser.theme.appMenuMinHeightState }
+    val appMenuBackgroundColor by remember { api.currentUser.theme.backgroundColorState }
+    val menuPadding by remember { api.currentUser.theme.appMenuOuterPaddingState } // todo
+    var items: List<Any> by remember(api.appsProvider) { mutableStateOf(api.appsProvider.appCategories) }
+    val onCategoryClick: (Category) -> Unit = { category ->
+        items = api.appsProvider.categoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
+    }
+    ChromeWindow(
+        visible = menuState.isVisible,
+        enterAnimation = enterAnimation,
+        exitAnimation = exitAnimation,
+        position = WindowPosition.Absolute(
+            panelState.bounds.x,
+            api.containerSize.height - (panelState.bounds.height + appMenuMinHeight)
+        ),
+        onFocusChange = { focused ->
+            menuState.onFocusChange(focused)
+            onFocusChange(focused)
+        }
     ) {
         Box(
             modifier = Modifier
-                .fillMaxSize()
-                .padding(menuPadding)
-                .background(appMenuBackgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
-                .clip(RoundedCornerShape(24.dp))
-                .border(2.dp, Color.White.copy(0.1f), RoundedCornerShape(24.dp))
+                .width(appMenuMinWidth)
+                .heightIn(
+                    min = appMenuMinHeight,
+                    max = appMenuMinHeight
+                )
+                .onPlaced(menuState::onPlaced)
         ) {
-            Column(
-                modifier = Modifier.fillMaxSize()
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(menuPadding)
+                    .background(appMenuBackgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
+                    .clip(RoundedCornerShape(24.dp))
+                    .border(2.dp, Color.White.copy(0.1f), RoundedCornerShape(24.dp))
             ) {
-                UserAvatar(
-                    avatarSize = 64.dp,
-                    backgroundColor = appMenuBackgroundColor,
-                    orientation = Orientation.Horizontal,
-                    onUserAvatarClick = onUserAvatarClick
-                )
-                Divider(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .height(2.dp)
-                        .padding(
-                            start = 2.dp,
-                            end = 2.dp
-                        ),
-                    color = Color.White.copy(0.1f),
-                    thickness = 2.dp
-                )
-                LazyColumn(
-                    modifier = Modifier
-                        .padding(bottom = 62.dp)
-                        .fillMaxSize()
-                        .padding(24.dp)
+                Column(
+                    modifier = Modifier.fillMaxSize()
                 ) {
-                    when (items.value.firstOrNull()) {
-                        is Category -> {
-                            items(items.value) { item ->
-                                AppsMenuCategory(
-                                    category = item as Category,
-                                    backgroundColor = appMenuBackgroundColor,
-                                    iconTint = Color.White,
-                                    onClick = { onCategoryClick(item) },
-                                    onContextMenuClick = { onCategoryContextMenuClick(item) }
-                                )
+                    UserAvatar(
+                        avatarSize = 64.dp,
+                        backgroundColor = appMenuBackgroundColor,
+                        orientation = Orientation.Horizontal,
+                        onUserAvatarClick = onUserAvatarClick
+                    )
+                    Divider(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .height(2.dp)
+                            .padding(
+                                start = 2.dp,
+                                end = 2.dp
+                            ),
+                        color = Color.White.copy(0.1f),
+                        thickness = 2.dp
+                    )
+                    LazyColumn(
+                        modifier = Modifier
+                            .padding(bottom = 62.dp)
+                            .fillMaxSize()
+                            .padding(24.dp)
+                    ) {
+                        when (items.firstOrNull()) {
+                            is Category -> {
+                                items(items) { item ->
+                                    AppsMenuCategory(
+                                        category = item as Category,
+                                        backgroundColor = appMenuBackgroundColor,
+                                        iconTint = Color.White,
+                                        onClick = { onCategoryClick(item) },
+                                        onContextMenuClick = { onCategoryContextMenuClick(item) }
+                                    )
+                                }
                             }
-                        }
 
-                        is App -> {
-                            items(items.value) { item ->
-                                AppsMenuApp(
-                                    app = item as App,
-                                    backgroundColor = appMenuBackgroundColor,
-                                    iconTint = Color.White,
-                                    onClick = {
-                                        onAppClick(item)
-                                    },
-                                    onContextMenuClick = {
-                                        onAppContextMenuClick(item)
-                                    }
-                                )
+                            is App -> {
+                                items(items) { item ->
+                                    AppsMenuApp(
+                                        app = item as App,
+                                        backgroundColor = appMenuBackgroundColor,
+                                        iconTint = Color.White,
+                                        onClick = {
+                                            onAppClick(item)
+                                        },
+                                        onContextMenuClick = {
+                                            onAppContextMenuClick(item)
+                                        }
+                                    )
+                                }
                             }
                         }
                     }
                 }
-            }
-            Column(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .wrapContentHeight()
-                    .align(Alignment.BottomStart),
-            ) {
-                Divider(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .height(2.dp)
-                        .padding(
-                            start = 2.dp,
-                            end = 2.dp
-                        ),
-                    color = Color.White.copy(0.1f),
-                    thickness = 2.dp
-                )
-                AppsBottomBar(
+                Column(
                     modifier = Modifier
                         .fillMaxWidth()
                         .wrapContentHeight()
-                        .background(appMenuBackgroundColor),
-                    backButtonVisible = items.value.first() is App,// todo
-                    onBackClick = {
-                        items.value = api.appsProvider.appCategories
-                    },
-                    onContextMenuClick = {
-                        // todo : context menu
-                    }
-                )
+                        .align(Alignment.BottomStart),
+                ) {
+                    Divider(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .height(2.dp)
+                            .padding(
+                                start = 2.dp,
+                                end = 2.dp
+                            ),
+                        color = Color.White.copy(0.1f),
+                        thickness = 2.dp
+                    )
+                    AppsBottomBar(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .wrapContentHeight()
+                            .background(appMenuBackgroundColor),
+                        backButtonVisible = items.first() is App,
+                        onBackClick = {
+                            items = api.appsProvider.appCategories
+                        },
+                        onContextMenuClick = {
+                            // todo : context menu
+                        }
+                    )
+                }
             }
         }
-    }
-    launchedEffect(menuState.isVisible) {
-        if (!menuState.isVisible) {
-            items.value = api.appsProvider.appCategories
+        launchedEffect(menuState.isVisible) {
+            if (!menuState.isVisible) {
+                items = api.appsProvider.appCategories
+            }
         }
     }
 }

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

@@ -8,53 +8,59 @@ import androidx.compose.animation.togetherWith
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.*
 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.SuperDarkGray
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.helpers.internal.Queue.Companion.mutableQueue
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import kotlinx.coroutines.delay
 
 @Preview
 @Composable
 fun BackgroundImage(
     modifier: Modifier = Modifier,
-    switchDelay: Long = 5000L,
+    api: DesktopProvider = LocalDesktop.current,
     fadeInDuration: Long = 3000L,
     fadeOutDuration: Long = 5000L,
-    backgroundColor: Color = Color.SuperDarkGray,
-    backgrounds: List<Any> = emptyList(),
     onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
     onChange: (bck: Any?) -> Unit = {}
-) = Box(
-    modifier = modifier.background(backgroundColor)
 ) {
-    val backgroundQueue = remember { mutableQueue(backgrounds) }
-    val currentBackground = remember { mutableStateOf(backgroundQueue.value) }
-    AnimatedContent(
-        backgroundQueue.value,
-        transitionSpec = {
-            fadeIn(animationSpec = tween(durationMillis = fadeInDuration.toInt())) togetherWith
-                    fadeOut(animationSpec = tween(durationMillis = fadeOutDuration.toInt()))
+    val switchDelay by remember { api.currentUser.theme.backgroundRotationDelayState }
+    val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
+    val userBackgrounds by remember { api.currentUser.config.desktopBackgroundsState }
+    val backgrounds by remember {
+        derivedStateOf {
+            api.appsProvider.backgrounds + userBackgrounds
         }
-    ) { value ->
-        ImageAny(
-            modifier = modifier,
-            contentScale = ContentScale.Crop,
-            src = value,
-            onFail = onError
-        )
     }
-    launchedEffect(currentBackground.value) {
-        if (switchDelay > 0) {
-            delay(switchDelay)
-            currentBackground.value = backgroundQueue.value
-            onChange(currentBackground.value)
+    val backgroundQueue = remember { mutableQueue(backgrounds) }
+    var currentBackground: Any? by remember { mutableStateOf(backgroundQueue.value) }
+    Box(
+        modifier = modifier.background(backgroundColor)
+    ) {
+        AnimatedContent(
+            currentBackground,
+            transitionSpec = {
+                fadeIn(animationSpec = tween(durationMillis = fadeInDuration.toInt())) togetherWith
+                        fadeOut(animationSpec = tween(durationMillis = fadeOutDuration.toInt()))
+            }
+        ) { value ->
+            ImageAny(
+                modifier = modifier,
+                contentScale = ContentScale.Crop,
+                src = value,
+                onFail = onError
+            )
+        }
+        launchedEffect(currentBackground) {
+            if (switchDelay > 0) {
+                delay(switchDelay)
+                currentBackground = backgroundQueue.value
+                onChange(currentBackground)
+            }
         }
     }
 }

+ 115 - 109
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.components.controlcenter
 
-import androidx.compose.animation.*
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation.Horizontal
@@ -16,7 +17,6 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.isSecondaryPressed
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
@@ -25,6 +25,8 @@ import eu.mjdev.desktop.components.sliding.SlidingMenu
 import eu.mjdev.desktop.components.sliding.VisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.extensions.Modifier.rightShadow
+import eu.mjdev.desktop.helpers.Animations.ControlCenterEnterAnimation
+import eu.mjdev.desktop.helpers.Animations.ControlCenterExitAnimation
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
@@ -36,135 +38,139 @@ import kotlinx.coroutines.launch
 @Composable
 fun ControlCenter(
     api: DesktopProvider = LocalDesktop.current,
-    backgroundColor: Color = api.currentUser.theme.backgroundColor,
-    backgroundAlpha: Float = api.currentUser.theme.controlCenterBackgroundAlpha,
     shadowColor: Color = Color.Black.copy(alpha = 0.3f), // 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: MutableState<Int> = remember { mutableStateOf(0) },
     controlCenterState: VisibilityState = rememberVisibilityState(),
     scope: CoroutineScope = rememberCoroutineScope(),
-    enterAnimation: EnterTransition = fadeIn() + slideInHorizontally(initialOffsetX = { it }),
-    exitAnimation: ExitTransition = slideOutHorizontally(targetOffsetX = { it }) + fadeOut(),
+    enterAnimation: EnterTransition = ControlCenterEnterAnimation,
+    exitAnimation: ExitTransition = ControlCenterExitAnimation,
     onFocusChange: (Boolean) -> Unit = {},
     onContextMenuClick: () -> Unit = {}
-) = ChromeWindow(
-    visible = true,
-    enterAnimation = enterAnimation,
-    exitAnimation = exitAnimation,
-    position = WindowPosition.Aligned(Alignment.TopEnd),
-    size = if (controlCenterState.isVisible) {
-        DpSize(controlCenterExpandedWidth, api.containerSize.height)
-    } else {
-        DpSize(controlCenterDividerWidth, api.containerSize.height)
-    },
-    onFocusChange = { focused ->
-        controlCenterState.onFocusChange(focused)
-        onFocusChange(focused)
-    }
 ) {
-    val cscope = rememberControlCenterScope(backgroundColor, api)
+    val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
+    val backgroundAlpha by remember { api.currentUser.theme.controlCenterBackgroundAlphaState }
+    val backgroundInvoker = remember { { backgroundColor } }
+    val cscope = rememberControlCenterScope(backgroundInvoker, api)
+    val controlCenterExpandedWidth by remember { api.currentUser.theme.controlCenterExpandedWidthState }
+    val controlCenterDividerWidth by remember { api.currentUser.theme.controlCenterDividerWidthState }
+    val controlCenterIconColor by remember { api.currentUser.theme.controlCenterIconColorState }
+    val controlCenterIconSize by remember { api.currentUser.theme.controlCenterIconSizeState }
+    val pages by remember { api.controlCenterPagesState }
     val pagesFiltered = remember(pages) {
         derivedStateOf {
             pages.filter { p -> p.condition.invoke(cscope) }
         }
     }
-    SlidingMenu(
-        modifier = Modifier.fillMaxHeight(),
-        orientation = Horizontal,
-        state = controlCenterState,
-    ) { isVisible ->
-        if (!isVisible) {
-            Divider(
-                modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                color = Color.Transparent,
-                thickness = controlCenterDividerWidth
-            )
+    ChromeWindow(
+        visible = true,
+        enterAnimation = enterAnimation,
+        exitAnimation = exitAnimation,
+        position = WindowPosition.Aligned(Alignment.TopEnd),
+        size = if (controlCenterState.isVisible) {
+            DpSize(controlCenterExpandedWidth, api.containerSize.height)
         } else {
-            Box(
-                modifier = Modifier
-                    .fillMaxHeight()
-                    .wrapContentSize()
-                    .background(backgroundColor.copy(alpha = backgroundAlpha)),
-            ) {
-                Row(
-                    modifier = Modifier.fillMaxHeight().wrapContentSize(),
+            DpSize(controlCenterDividerWidth, api.containerSize.height)
+        },
+        onFocusChange = { focused ->
+            controlCenterState.onFocusChange(focused)
+            onFocusChange(focused)
+        }
+    ) {
+
+        SlidingMenu(
+            modifier = Modifier.fillMaxHeight(),
+            orientation = Horizontal,
+            state = controlCenterState,
+        ) { isVisible ->
+            if (!isVisible) {
+                Divider(
+                    modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
+                    color = Color.Transparent,
+                    thickness = controlCenterDividerWidth
+                )
+            } else {
+                Box(
+                    modifier = Modifier
+                        .fillMaxHeight()
+                        .wrapContentSize()
+                        .background(backgroundColor.copy(alpha = backgroundAlpha)),
                 ) {
-                    Box(
-                        contentAlignment = Alignment.TopEnd
+                    Row(
+                        modifier = Modifier.fillMaxHeight().wrapContentSize(),
                     ) {
-                        LazyColumn(
-                            modifier = Modifier
-                                .fillMaxHeight()
-                                .background(backgroundColor)
-                                .padding(8.dp),
-                            verticalArrangement = Arrangement.Top,
-                            horizontalAlignment = Alignment.Start
+                        Box(
+                            contentAlignment = Alignment.TopEnd
                         ) {
-                            itemsIndexed(pagesFiltered.value) { idx, page ->
-                                val isSelected = (pagerState.value == idx)
-                                Icon(
-                                    modifier = Modifier
-                                        .padding(vertical = 8.dp)
-                                        .size(controlCenterIconSize)
-                                        .background(
-                                            color = if (isSelected) Color.White else Color.Transparent,
-                                            shape = RoundedCornerShape(8.dp)
-                                        )
-                                        .mouseClickable {
-                                            if (buttons.isSecondaryPressed) {
-                                                onContextMenuClick()
-                                            } else {
-                                                scope.launch {
-                                                    pagerState.value = idx
+                            LazyColumn(
+                                modifier = Modifier
+                                    .fillMaxHeight()
+                                    .background(backgroundColor)
+                                    .padding(8.dp),
+                                verticalArrangement = Arrangement.Top,
+                                horizontalAlignment = Alignment.Start
+                            ) {
+                                itemsIndexed(pagesFiltered.value) { idx, page ->
+                                    val isSelected = (pagerState.value == idx)
+                                    Icon(
+                                        modifier = Modifier
+                                            .padding(vertical = 8.dp)
+                                            .size(controlCenterIconSize)
+                                            .background(
+                                                color = if (isSelected) Color.White else Color.Transparent,
+                                                shape = RoundedCornerShape(8.dp)
+                                            )
+                                            .mouseClickable {
+                                                if (buttons.isSecondaryPressed) {
+                                                    onContextMenuClick()
+                                                } else {
+                                                    scope.launch {
+                                                        pagerState.value = idx
+                                                    }
                                                 }
-                                            }
-                                        },
-                                    imageVector = page.icon,
-                                    contentDescription = "",
-                                    tint = if (isSelected) backgroundColor else controlCenterIconColor
-                                )
+                                            },
+                                        imageVector = page.icon,
+                                        contentDescription = "",
+                                        tint = if (isSelected) backgroundColor else controlCenterIconColor
+                                    )
+                                }
                             }
-                        }
-                        Box(
-                            modifier = Modifier
-                                .padding(
-                                    end = controlCenterIconSize.width + 16.dp
-                                )
-                                .fillMaxHeight()
-                                .wrapContentSize(),
-                        ) {
-                            Row {
-                                Divider(
-                                    modifier = Modifier
-                                        .fillMaxHeight()
-                                        .width(2.dp),
-                                    color = Color.White.copy(0.1f),
-                                    thickness = 2.dp
-                                )
-                                Box(
-                                    modifier = Modifier
-                                        .width(controlCenterExpandedWidth)
-                                        .rightShadow(
-                                            color = shadowColor,
-                                            offsetX = 4.dp,
-                                            blur = 10.dp,
-                                        ),
-                                ) {
-                                    with(pagesFiltered.value[pagerState.value]) {
-                                        content(cscope)
+                            Box(
+                                modifier = Modifier
+                                    .padding(
+                                        end = controlCenterIconSize.width + 16.dp
+                                    )
+                                    .fillMaxHeight()
+                                    .wrapContentSize(),
+                            ) {
+                                Row {
+                                    Divider(
+                                        modifier = Modifier
+                                            .fillMaxHeight()
+                                            .width(2.dp),
+                                        color = Color.White.copy(0.1f),
+                                        thickness = 2.dp
+                                    )
+                                    Box(
+                                        modifier = Modifier
+                                            .width(controlCenterExpandedWidth)
+                                            .rightShadow(
+                                                color = shadowColor,
+                                                offsetX = 4.dp,
+                                                blur = 10.dp,
+                                            ),
+                                    ) {
+                                        with(pagesFiltered.value[pagerState.value]) {
+                                            content(cscope)
+                                        }
                                     }
                                 }
                             }
+                            Divider(
+                                modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
+                                color = Color.White.copy(0.1f),
+                                thickness = controlCenterDividerWidth
+                            )
                         }
-                        Divider(
-                            modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                            color = Color.White.copy(0.1f),
-                            thickness = controlCenterDividerWidth
-                        )
                     }
                 }
             }

+ 45 - 2
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenterPage.kt

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

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

@@ -12,7 +12,6 @@ import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Campaign
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
@@ -94,7 +93,8 @@ fun AIPage() = ControlCenterPage(
                     minLines = 4,
                     shape = RoundedCornerShape(8.dp),
                     colors = TextFieldDefaults.outlinedTextFieldColors(
-                        backgroundColor = Color.White.copy(alpha = 0.1f)
+                        backgroundColor = Color.White.copy(alpha = 0.1f),
+                        textColor = Color.White.copy(alpha = 0.9f)
                     )
                 )
             }

+ 108 - 109
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.components.desktoppanel
 
-import androidx.compose.animation.*
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.TooltipArea
 import androidx.compose.foundation.TooltipPlacement
@@ -8,9 +9,7 @@ import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation.Vertical
 import androidx.compose.foundation.layout.*
 import androidx.compose.material.Divider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Brush
@@ -30,6 +29,8 @@ import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVis
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.extensions.Compose.height
 import eu.mjdev.desktop.extensions.Modifier.topShadow
+import eu.mjdev.desktop.helpers.Animations.DesktopPanelEnterAnimation
+import eu.mjdev.desktop.helpers.Animations.DesktopPanelExitAnimation
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
@@ -39,7 +40,6 @@ import eu.mjdev.desktop.windows.ChromeWindow
 @Composable
 fun DesktopPanel(
     api: DesktopProvider = LocalDesktop.current,
-    backgroundColor: Color = api.currentUser.theme.backgroundColor,
     iconColor: Color = Color.Black,
     iconColorRunning: Color = Color.White.copy(0.8f),
     iconBackgroundColor: Color = Color.White.copy(0.6f),
@@ -48,16 +48,12 @@ fun DesktopPanel(
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     showMenuIcon: Boolean = true,
-    panelContentPadding: PaddingValues = PaddingValues(
-        start = 4.dp,
-        top = 4.dp,
-        end = 4.dp
-    ),
+    panelContentPadding: PaddingValues = PaddingValues(4.dp),
     panelState: VisibilityState = rememberVisibilityState(),
-    enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
-    exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+    enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
+    exitAnimation: ExitTransition = DesktopPanelExitAnimation,
     tooltipHeight: Dp = 64.dp,
-    tooltipData: MutableState<Any?> = mutableStateOf(null),
+    tooltipData: MutableState<Any?> = remember { mutableStateOf(null) },
     onTooltip: (item: Any?) -> Unit = { item -> tooltipData.value = item },
     panelHeight: (visible: Boolean) -> Dp = { visible ->
         if (visible)
@@ -84,119 +80,122 @@ fun DesktopPanel(
     },
     onLanguageClick: () -> Unit = {
         // todo
-    }
-) = ChromeWindow(
-    visible = true,
-    enterAnimation = enterAnimation,
-    exitAnimation = exitAnimation,
-    position = WindowPosition.Aligned(Alignment.BottomCenter),
-    size = DpSize(
-        api.containerSize.width,
-        panelHeight(panelState.isVisible)
-    ),
-    onFocusChange = { focused ->
-        panelState.onFocusChange(focused)
-        onFocusChange(focused)
-    }
+    },
 ) {
-    SlidingMenu(
-        modifier = Modifier.fillMaxWidth(),
-        orientation = Vertical,
-        state = panelState,
-    ) { isVisible ->
-        Divider(
-            modifier = Modifier
-                .fillMaxWidth()
-                .height(dividerWidth),
-            color = Color.Transparent,
-            thickness = dividerWidth
-        )
-        if (isVisible) {
-            TooltipArea(
-                modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-                tooltip = {
-                    DesktopPanelTooltip(
-                        tooltipData.value,
-                        converter = tooltipConverter
-                    )
-                },
-                tooltipPlacement = TooltipPlacement.CursorPoint(
-                    alignment = Alignment.TopEnd,
-                    offset = DpOffset(32.dp, 32.dp)
-                )
-            ) {
-                Column {
-                    Box(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .height(tooltipHeight)
+    val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
+    ChromeWindow(
+        visible = true,
+        enterAnimation = enterAnimation,
+        exitAnimation = exitAnimation,
+        position = WindowPosition.Aligned(Alignment.BottomCenter),
+        size = DpSize(
+            api.containerSize.width,
+            panelHeight(panelState.isVisible)
+        ),
+        onFocusChange = { focused ->
+            panelState.onFocusChange(focused)
+            onFocusChange(focused)
+        }
+    ) {
+        SlidingMenu(
+            modifier = Modifier.fillMaxWidth(),
+            orientation = Vertical,
+            state = panelState,
+        ) { isVisible ->
+            Divider(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .height(dividerWidth),
+                color = Color.Transparent,
+                thickness = dividerWidth
+            )
+            if (isVisible) {
+                TooltipArea(
+                    modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+                    tooltip = {
+                        DesktopPanelTooltip(
+                            tooltipData.value,
+                            converter = tooltipConverter
+                        )
+                    },
+                    tooltipPlacement = TooltipPlacement.CursorPoint(
+                        alignment = Alignment.TopEnd,
+                        offset = DpOffset(32.dp, 32.dp)
                     )
-                    Column(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .wrapContentHeight()
-                            .background(
-                                brush = Brush.verticalGradient(
-                                    colors = listOf(
-                                        backgroundColor.copy(alpha = 0.3f),
-                                        backgroundColor.copy(alpha = 0.7f),
-                                        backgroundColor.copy(alpha = 0.8f),
-                                    )
-                                )
-                            )
-                            .topShadow(
-                                color = backgroundColor.copy(alpha = 0.3f),
-                                offsetY = 4.dp,
-                                blur = 10.dp
-                            )
-                            .onPlaced(panelState::onPlaced),
-                    ) {
-                        Divider(
+                ) {
+                    Column {
+                        Box(
                             modifier = Modifier
                                 .fillMaxWidth()
-                                .height(2.dp),
-                            color = Color.White.copy(0.1f),
-                            thickness = 2.dp
+                                .height(tooltipHeight)
                         )
-                        Box(
-                            modifier = Modifier.fillMaxWidth()
-                                .padding(top = 4.dp)
-                                .padding(panelContentPadding)
+                        Column(
+                            modifier = Modifier
+                                .fillMaxWidth()
+                                .wrapContentHeight()
+                                .background(
+                                    brush = Brush.verticalGradient(
+                                        colors = listOf(
+                                            backgroundColor.copy(alpha = 0.3f),
+                                            backgroundColor.copy(alpha = 0.7f),
+                                            backgroundColor.copy(alpha = 0.8f),
+                                        )
+                                    )
+                                )
+                                .topShadow(
+                                    color = backgroundColor.copy(alpha = 0.3f),
+                                    offsetY = 4.dp,
+                                    blur = 10.dp
+                                )
+                                .onPlaced(panelState::onPlaced),
                         ) {
-                            if (showMenuIcon) {
-                                DesktopMenuIcon(
-                                    modifier = Modifier.align(Alignment.CenterStart),
+                            Divider(
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .height(2.dp),
+                                color = Color.White.copy(0.1f),
+                                thickness = 2.dp
+                            )
+                            Box(
+                                modifier = Modifier.fillMaxWidth()
+                                    .padding(top = 4.dp)
+                                    .padding(panelContentPadding)
+                            ) {
+                                if (showMenuIcon) {
+                                    DesktopMenuIcon(
+                                        modifier = Modifier.align(Alignment.CenterStart),
+                                        iconColor = iconColor,
+                                        iconBackgroundColor = iconBackgroundColor,
+                                        iconSize = iconSize,
+                                        iconPadding = iconPadding,
+                                        iconOuterPadding = iconOuterPadding,
+                                        onTooltip = onTooltip,
+                                        onClick = { onMenuIconClicked() },
+                                        onContextMenuClick = onMenuIconContextMenuClicked
+                                    )
+                                }
+                                DesktopPanelFavoriteApps(
+                                    modifier = Modifier.align(Alignment.Center),
                                     iconColor = iconColor,
                                     iconBackgroundColor = iconBackgroundColor,
+                                    iconColorRunning = iconColorRunning,
                                     iconSize = iconSize,
                                     iconPadding = iconPadding,
                                     iconOuterPadding = iconOuterPadding,
                                     onTooltip = onTooltip,
-                                    onClick = { onMenuIconClicked() },
-                                    onContextMenuClick = onMenuIconContextMenuClicked
+                                    onAppClick = onAppClick,
+                                    onContextMenuClick = onAppContextMenuClick
+                                )
+                                DesktopPanelLanguage(
+                                    modifier = Modifier.align(Alignment.CenterEnd),
+                                    onTooltip = onTooltip,
+                                    onClick = onLanguageClick
                                 )
                             }
-                            DesktopPanelFavoriteApps(
-                                modifier = Modifier.align(Alignment.Center),
-                                iconColor = iconColor,
-                                iconBackgroundColor = iconBackgroundColor,
-                                iconColorRunning = iconColorRunning,
-                                iconSize = iconSize,
-                                iconPadding = iconPadding,
-                                iconOuterPadding = iconOuterPadding,
-                                onTooltip = onTooltip,
-                                onAppClick = onAppClick,
-                                onContextMenuClick = onAppContextMenuClick
-                            )
-                            DesktopPanelLanguage(
-                                modifier = Modifier.align(Alignment.CenterEnd),
-                                onTooltip = onTooltip,
-                                onClick = onLanguageClick
-                            )
                         }
                     }
                 }
             }
         }
     }
-}
+}

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

@@ -1,5 +1,6 @@
 package eu.mjdev.desktop.components.image
 
+import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -35,6 +36,8 @@ fun ImageAny(
         it.endsWith(".gif") || it.endsWith(".mp4") || it.endsWith(".mpg")
     }
     when {
+        src == null -> Box(modifier = modifier)
+
         isVideo -> VideoPlayerView(
             modifier = modifier,
             src = src,

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

@@ -5,10 +5,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.CursorDropdownMenu
 import androidx.compose.material.DropdownMenuItem
 import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
@@ -60,7 +57,7 @@ fun ContextMenu(
 class ContextMenuState(
     val menuExpanded: MutableState<Boolean> = mutableStateOf(false),
     val menuRenderCount: MutableState<Int> = mutableStateOf(0),
-    val items: List<String> = listOf<String>()
+    val items: List<String> = listOf()
 ) {
     fun show() {
         menuExpanded.value = true
@@ -72,9 +69,10 @@ class ContextMenuState(
 
     companion object {
         @Composable
-        fun rememberContextMenuState(vararg items: String) =
+        fun rememberContextMenuState(vararg items: String) = remember {
             ContextMenuState(
                 items = items.asList()
             )
+        }
     }
 }

+ 1 - 0
src/main/kotlin/eu/mjdev/desktop/data/App.kt

@@ -135,6 +135,7 @@ class App(
             println("$windows")
             windows.forEach { w -> w.toFront() }
         } else {
+            println("No window for process PID: [${pids.joinToString { p -> "$p," }}]")
             closeWindow(api)
         }
     }

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

@@ -6,8 +6,8 @@ import androidx.compose.runtime.mutableStateOf
 class DesktopConfig(
     desktopBackgroundUrls: List<Any> = emptyList()
 ) {
-    val desktopBackgroundUrlsState = mutableStateOf(desktopBackgroundUrls.toMutableList())
-    val desktopBackgrounds get() = desktopBackgroundUrlsState.value
+    val desktopBackgroundsState = mutableStateOf(desktopBackgroundUrls.toMutableList())
+    val desktopBackgrounds get() = desktopBackgroundsState.value
 
     fun addBackground(path: String) {
         desktopBackgrounds.add(path)

+ 14 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/Animations.kt

@@ -0,0 +1,14 @@
+package eu.mjdev.desktop.helpers
+
+import androidx.compose.animation.*
+
+object Animations {
+    val ControlCenterEnterAnimation = fadeIn() + slideInHorizontally(initialOffsetX = { it })
+    val ControlCenterExitAnimation = slideOutHorizontally(targetOffsetX = { it }) + fadeOut()
+
+    val DesktopPanelEnterAnimation = fadeIn() + slideInVertically(initialOffsetY = { it })
+    val DesktopPanelExitAnimation = slideOutVertically(targetOffsetY = { it }) + fadeOut()
+
+    val AppsMenuEnterAnimation = fadeIn() + slideInVertically(initialOffsetY = { it })
+    val AppsMenuExitAnimation = slideOutVertically(targetOffsetY = { it }) + fadeOut()
+}

+ 11 - 5
src/main/kotlin/eu/mjdev/desktop/helpers/internal/Palette.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.helpers.internal
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.graphics.Color
@@ -10,12 +11,17 @@ import eu.mjdev.desktop.extensions.Image.loadPicture
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-@Suppress("unused")
+@Suppress("unused", "MemberVisibilityCanBePrivate")
 class Palette(
     private val scope: CoroutineScope,
-    private val baseColor: Color = Color.Transparent
+    private val baseColor: Color = Color.SuperDarkGray
 ) {
-    var backgroundColor: Color = Color.SuperDarkGray
+    val backgroundColorState = mutableStateOf(baseColor)
+    var backgroundColor
+        get() = backgroundColorState.value
+        set(value) {
+            backgroundColorState.value = value
+        }
 
     fun update(src: Any?) = scope.launch {
         loadPicture(src)
@@ -28,9 +34,9 @@ class Palette(
     companion object {
         @Composable
         fun rememberPalette(
-            backgroundColor: Color = Color.SuperDarkGray,
+            baseColor: Color = Color.SuperDarkGray,
             scope: CoroutineScope = rememberCoroutineScope()
-        ) = remember(scope, backgroundColor) { Palette(scope, backgroundColor) }
+        ) = remember { Palette(scope, baseColor) }
     }
 }
 

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

@@ -71,7 +71,7 @@ class WindowsManager {
 
     fun getWindowsByPids(
         pids: List<Long>
-    ): List<IWindow> = allSystemWindows.filter { pids.contains(it.pid) }.distinctBy { it.id }
+    ): List<IWindow> = allSystemWindows.filter { pids.contains(it.pid) }
 
     fun getWindowPid(
         window: Window

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

@@ -38,7 +38,7 @@ class DesktopProvider(
     val currentUser
         get() = _currentUser.value
 
-    private val _controlCenterPages: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
+    val controlCenterPagesState: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
 
 //    private val engine: ScriptEngine by lazy {
 //        scriptManager.getEngineByName("JavaScript").apply {
@@ -61,7 +61,7 @@ class DesktopProvider(
             )
         }
     }
-    val controlCenterPages get() = _controlCenterPages.value
+    val controlCenterPages get() = controlCenterPagesState.value
     val appsProvider = AppsProvider(this)
 
     val graphicsEnvironment: GraphicsEnvironment

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

@@ -57,9 +57,6 @@ fun MainWindow(
                         controlCenterState.hide()
                     }
                 },
-            backgroundColor = api.currentUser.theme.backgroundColor,
-            backgrounds = api.appsProvider.backgrounds + api.currentUser.config.desktopBackgrounds,
-            switchDelay = api.currentUser.theme.backgroundRotationDelay,
             onChange = { src ->
                 palette.apply {
                     update(src)