From 575729fc62c7448564779dad771504679ad6163c Mon Sep 17 00:00:00 2001 From: Raphael TEYSSANDIER Date: Mon, 17 Aug 2026 09:57:29 +0200 Subject: [PATCH 1/2] feat: Window v2 --- FloconDesktop/gradle/libs.versions.toml | 2 +- .../navigation/scene/WindowScene.kt | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/FloconDesktop/gradle/libs.versions.toml b/FloconDesktop/gradle/libs.versions.toml index 834efd907..fd3b10698 100644 --- a/FloconDesktop/gradle/libs.versions.toml +++ b/FloconDesktop/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] about-libraries = "15.0.2" agp = "9.2.0" -androidx-lifecycle = "2.11.0-rc01" +androidx-lifecycle = "2.11.0" buildconfig = "6.0.10" coil = "3.5.0" compose-multiplatform = "1.12.0" diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt index d4f8e5935..2aa2f6d36 100644 --- a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt @@ -1,13 +1,17 @@ -@file:OptIn(ExperimentalUuidApi::class) +@file:OptIn(ExperimentalUuidApi::class, ExperimentalComposeUiApi::class) package io.github.openflocon.navigation.scene import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.rememberWindowState +import androidx.compose.ui.window.WindowPlacement +import androidx.compose.ui.window.v2.Window +import androidx.compose.ui.window.v2.WindowBoundsProvider +import androidx.compose.ui.window.v2.WindowPositionProvider +import androidx.compose.ui.window.v2.rememberWindowState import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.NavMetadataKey import androidx.navigation3.runtime.get @@ -35,8 +39,15 @@ data class WindowScene( val windowProperties = entry.metadata[WindowPropertiesKey] val state = rememberWindowState( - size = windowProperties?.size ?: DpSize(800.dp, 600.dp), + initialPlacement = WindowPlacement.Floating, + initialBoundsProvider = WindowBoundsProvider( + sizeProvider = { + windowProperties?.size ?: DpSize(800.dp, 600.dp) + }, + positionProvider = WindowPositionProvider.CenteredInParentWindow + ) ) + Window( onCloseRequest = onBack, state = state, @@ -77,4 +88,4 @@ data class WindowProperties( val isWindow: Boolean = true } -private object WindowPropertiesKey: NavMetadataKey +private object WindowPropertiesKey : NavMetadataKey From fa9cb920e8a032802a6dec6dcab91593d9e533d6 Mon Sep 17 00:00:00 2001 From: Raphael TEYSSANDIER Date: Tue, 1 Sep 2026 18:32:44 +0200 Subject: [PATCH 2/2] feat: Use window v2 --- FloconAndroid/gradle/libs.versions.toml | 2 +- .../flocon/myapplication/multi/Main.kt | 5 +- .../flocon/myapplication/multi/Main.kt | 5 +- FloconDesktop/composeApp/build.gradle.kts | 1 + .../common/ui/window/FloconWindow.desktop.kt | 64 +----------------- .../github/openflocon/flocondesktop/Main.kt | 35 +++++----- .../flocondesktop/about/AboutScreen.kt | 16 +++-- .../common/ui/window/FloconWindow.desktop.kt | 65 +++++++++++++++++++ .../flocondesktop/window/WindowExt.kt | 13 +++- .../navigation/scene/WindowScene.kt | 20 +++--- 10 files changed, 124 insertions(+), 102 deletions(-) create mode 100644 FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt diff --git a/FloconAndroid/gradle/libs.versions.toml b/FloconAndroid/gradle/libs.versions.toml index 93ff42501..8136244ee 100644 --- a/FloconAndroid/gradle/libs.versions.toml +++ b/FloconAndroid/gradle/libs.versions.toml @@ -2,7 +2,7 @@ agp = "8.11.0-rc02" apollo = "4.0.0" coilCompose = "3.2.0" -compose = "1.11.0" +compose = "1.12.0" datastorePreferences = "1.1.7" kotlin = "2.4.0" mavenPublish = "0.34.0" diff --git a/FloconAndroid/sample-multiplatform/src/desktopMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt b/FloconAndroid/sample-multiplatform/src/desktopMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt index 6b0af5a63..efd025f31 100644 --- a/FloconAndroid/sample-multiplatform/src/desktopMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt +++ b/FloconAndroid/sample-multiplatform/src/desktopMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt @@ -1,7 +1,10 @@ +@file:OptIn(ExperimentalComposeUiApi::class) + package io.github.openflocon.flocon.myapplication.multi -import androidx.compose.ui.window.Window +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.application +import androidx.compose.ui.window.v2.Window import androidx.room.Room import androidx.room.RoomDatabase import androidx.sqlite.driver.bundled.BundledSQLiteDriver diff --git a/FloconAndroid/sample-multiplatform/src/jvmMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt b/FloconAndroid/sample-multiplatform/src/jvmMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt index b442d3c5a..2cbcb885d 100644 --- a/FloconAndroid/sample-multiplatform/src/jvmMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt +++ b/FloconAndroid/sample-multiplatform/src/jvmMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Main.kt @@ -1,7 +1,10 @@ +@file:OptIn(ExperimentalComposeUiApi::class) + package io.github.openflocon.flocon.myapplication.multi -import androidx.compose.ui.window.Window +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.application +import androidx.compose.ui.window.v2.Window import io.github.openflocon.flocon.ktor.FloconKtorPlugin import io.github.openflocon.flocon.myapplication.multi.ui.App import io.ktor.client.HttpClient diff --git a/FloconDesktop/composeApp/build.gradle.kts b/FloconDesktop/composeApp/build.gradle.kts index 683cf370e..f884dcf64 100644 --- a/FloconDesktop/composeApp/build.gradle.kts +++ b/FloconDesktop/composeApp/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { // Pour Kotlin 1.9+ freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") + freeCompilerArgs.add("-opt-in=androidx.compose.ui.ExperimentalComposeUiApi") freeCompilerArgs.add("-Xcontext-parameters") freeCompilerArgs.add("-Xcontext-sensitive-resolution") } diff --git a/FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt b/FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt index f4fe216a5..e7c4b44d7 100644 --- a/FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt +++ b/FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt @@ -1,63 +1 @@ -package io.github.openflocon.flocondesktop.common.ui.window - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.KeyEventType -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.type -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.WindowPlacement -import androidx.compose.ui.window.WindowPosition -import androidx.compose.ui.window.WindowState -import flocondesktop.composeapp.generated.resources.Res -import flocondesktop.composeapp.generated.resources.app_icon -import io.github.openflocon.library.designsystem.components.escape.LocalEscapeHandlerStack -import org.jetbrains.compose.resources.painterResource - -data class FloconWindowStateDesktop( - val windowState: WindowState, -) : FloconWindowState - -actual fun createFloconWindowState(size: DpSize?): FloconWindowState = FloconWindowStateDesktop( - WindowState( - placement = WindowPlacement.Floating, - position = WindowPosition(Alignment.Center), - size = size ?: defaultWindowSize - ) -) - -// TODO Use this component for main window too -@Composable -actual fun FloconWindow( - title: String, - state: FloconWindowState, - onCloseRequest: () -> Unit, - alwaysOnTop: Boolean, - content: @Composable () -> Unit, -) { - val handlers = remember { mutableStateListOf<() -> Boolean>() } - - Window( - title = title, - icon = painterResource(Res.drawable.app_icon), - state = (state as FloconWindowStateDesktop).windowState, - alwaysOnTop = alwaysOnTop, - onPreviewKeyEvent = { - when (it.key) { - Key.Escape if (it.type == KeyEventType.KeyDown) -> handlers.lastOrNull()?.invoke() ?: false - - else -> false - } - }, - onCloseRequest = onCloseRequest, - ) { - CompositionLocalProvider(LocalEscapeHandlerStack provides handlers) { - content() - } - } -} +// File moved to src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt diff --git a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt index e4c0a2da2..f6dac09d8 100644 --- a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt +++ b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalComposeUiApi::class) + package io.github.openflocon.flocondesktop import androidx.compose.runtime.Composable @@ -8,17 +10,19 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type +import androidx.compose.ui.unit.size import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.Notification import androidx.compose.ui.window.Tray -import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberTrayState -import androidx.compose.ui.window.rememberWindowState +import androidx.compose.ui.window.v2.Window +import androidx.compose.ui.window.v2.rememberWindowState import coil3.ImageLoader import coil3.compose.setSingletonImageLoaderFactory import coil3.network.ktor3.KtorNetworkFetcherFactory @@ -31,8 +35,7 @@ import io.github.openflocon.flocondesktop.window.MIN_WINDOW_HEIGHT import io.github.openflocon.flocondesktop.window.MIN_WINDOW_WIDTH import io.github.openflocon.flocondesktop.window.WindowStateData import io.github.openflocon.flocondesktop.window.WindowStateSaver -import io.github.openflocon.flocondesktop.window.size -import io.github.openflocon.flocondesktop.window.windowPosition +import io.github.openflocon.flocondesktop.window.windowBoundsProvider import io.github.openflocon.library.designsystem.components.escape.LocalEscapeHandlerStack import org.jetbrains.compose.resources.painterResource import org.koin.compose.koinInject @@ -54,8 +57,7 @@ fun main() { var openAbout by remember { mutableStateOf(false) } val savedState = remember { WindowStateSaver.load() } val windowState = rememberWindowState( - size = savedState.size(), - position = savedState.windowPosition(), + initialBoundsProvider = savedState.windowBoundsProvider(), ) with(Desktop.getDesktop()) { @@ -77,16 +79,17 @@ fun main() { Window( state = windowState, onCloseRequest = { - val currentSize = windowState.size - val currentPosition = windowState.position - WindowStateSaver.save( - WindowStateData( - width = currentSize.width.value.toInt(), - height = currentSize.height.value.toInt(), - x = currentPosition.x.value.toInt(), - y = currentPosition.y.value.toInt(), - ), - ) + if (windowState.isInitialized) { + val currentBounds = windowState.bounds + WindowStateSaver.save( + WindowStateData( + width = currentBounds.size.width.value.toInt(), + height = currentBounds.size.height.value.toInt(), + x = currentBounds.left.value.toInt(), + y = currentBounds.top.value.toInt(), + ), + ) + } exitApplication() }, diff --git a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/about/AboutScreen.kt b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/about/AboutScreen.kt index 0733c2893..3f10ffba6 100644 --- a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/about/AboutScreen.kt +++ b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/about/AboutScreen.kt @@ -18,10 +18,11 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPlacement -import androidx.compose.ui.window.WindowPosition -import androidx.compose.ui.window.rememberWindowState +import androidx.compose.ui.window.v2.Window +import androidx.compose.ui.window.v2.WindowBoundsProvider +import androidx.compose.ui.window.v2.WindowPositionProvider +import androidx.compose.ui.window.v2.rememberWindowState import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.app_icon import io.github.openflocon.flocondesktop.BuildConfig @@ -40,10 +41,11 @@ internal fun AboutScreen( title = "About", onCloseRequest = onCloseRequest, state = rememberWindowState( - placement = WindowPlacement.Floating, - position = WindowPosition(Alignment.Center), - size = DpSize(Dp.Unspecified, Dp.Unspecified) - ) + initialPlacement = WindowPlacement.Floating, + initialBoundsProvider = WindowBoundsProvider( + positionProvider = WindowPositionProvider.CenteredOnScreen, + ), + ), ) { FloconSurface { Column( diff --git a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt new file mode 100644 index 000000000..0031cd72b --- /dev/null +++ b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt @@ -0,0 +1,65 @@ +package io.github.openflocon.flocondesktop.common.ui.window + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.type +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.window.WindowPlacement +import androidx.compose.ui.window.v2.Window +import androidx.compose.ui.window.v2.WindowBoundsProvider +import androidx.compose.ui.window.v2.WindowPositionProvider +import androidx.compose.ui.window.v2.WindowState +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.app_icon +import io.github.openflocon.library.designsystem.components.escape.LocalEscapeHandlerStack +import org.jetbrains.compose.resources.painterResource + +data class FloconWindowStateDesktop( + val windowState: WindowState, +) : FloconWindowState + +actual fun createFloconWindowState(size: DpSize?): FloconWindowState = FloconWindowStateDesktop( + WindowState( + initialPlacement = WindowPlacement.Floating, + initialBoundsProvider = WindowBoundsProvider( + sizeProvider = { size ?: defaultWindowSize }, + positionProvider = WindowPositionProvider.CenteredOnScreen, + ), + ), +) + +// TODO Use this component for main window too +@Composable +actual fun FloconWindow( + title: String, + state: FloconWindowState, + onCloseRequest: () -> Unit, + alwaysOnTop: Boolean, + content: @Composable () -> Unit, +) { + val handlers = remember { mutableStateListOf<() -> Boolean>() } + + Window( + title = title, + icon = painterResource(Res.drawable.app_icon), + state = (state as FloconWindowStateDesktop).windowState, + alwaysOnTop = alwaysOnTop, + onPreviewKeyEvent = { + when (it.key) { + Key.Escape if (it.type == KeyEventType.KeyDown) -> handlers.lastOrNull()?.invoke() ?: false + + else -> false + } + }, + onCloseRequest = onCloseRequest, + ) { + CompositionLocalProvider(LocalEscapeHandlerStack provides handlers) { + content() + } + } +} diff --git a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/window/WindowExt.kt b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/window/WindowExt.kt index c6053f5a7..9ae142abe 100644 --- a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/window/WindowExt.kt +++ b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/window/WindowExt.kt @@ -2,11 +2,14 @@ package io.github.openflocon.flocondesktop.window import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.v2.WindowBoundsProvider +import androidx.compose.ui.window.v2.WindowPositionProvider import java.awt.Toolkit import kotlin.math.min -fun WindowStateData?.windowPosition() = this?.let { WindowPosition(x.dp, y.dp) } ?: WindowPosition.PlatformDefault +fun WindowStateData?.windowPositionProvider(): WindowPositionProvider = + this?.let { WindowPositionProvider.Absolute(x.dp, y.dp) } ?: WindowPositionProvider.Default + fun WindowStateData?.size(): DpSize { val screenSize = Toolkit.getDefaultToolkit().screenSize @@ -23,3 +26,9 @@ fun WindowStateData?.size(): DpSize { width = width, height = height, ) } + +fun WindowStateData?.windowBoundsProvider(): WindowBoundsProvider = + WindowBoundsProvider( + positionProvider = windowPositionProvider(), + sizeProvider = { size() }, + ) diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt index 2aa2f6d36..35b5ab140 100644 --- a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt @@ -7,11 +7,10 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.WindowPlacement -import androidx.compose.ui.window.v2.Window +import androidx.compose.ui.window.v2.DialogWindow import androidx.compose.ui.window.v2.WindowBoundsProvider import androidx.compose.ui.window.v2.WindowPositionProvider -import androidx.compose.ui.window.v2.rememberWindowState +import androidx.compose.ui.window.v2.rememberDialogState import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.NavMetadataKey import androidx.navigation3.runtime.get @@ -27,7 +26,7 @@ import kotlin.uuid.ExperimentalUuidApi data class WindowScene( private val entry: NavEntry, override val previousEntries: List>, - private val onBack: () -> Unit + private val onBack: () -> Unit, ) : OverlayScene { override val key: Any = entry.contentKey @@ -38,17 +37,16 @@ data class WindowScene( override val content: @Composable (() -> Unit) = { val windowProperties = entry.metadata[WindowPropertiesKey] - val state = rememberWindowState( - initialPlacement = WindowPlacement.Floating, + val state = rememberDialogState( initialBoundsProvider = WindowBoundsProvider( sizeProvider = { windowProperties?.size ?: DpSize(800.dp, 600.dp) }, - positionProvider = WindowPositionProvider.CenteredInParentWindow - ) + positionProvider = WindowPositionProvider.CenteredInParentWindow, + ), ) - Window( + DialogWindow( onCloseRequest = onBack, state = state, title = windowProperties?.title ?: "", @@ -67,7 +65,7 @@ class WindowSceneStrategy : SceneStrategy { return WindowScene( entry = entry, previousEntries = entries.dropLast(1), - onBack = onBack + onBack = onBack, ) } @@ -83,7 +81,7 @@ class WindowSceneStrategy : SceneStrategy { data class WindowProperties( val size: DpSize? = null, - val title: String? = null + val title: String? = null, ) { val isWindow: Boolean = true }