TopWindow.kt 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package eu.mjdev.desktop.windows
  2. import androidx.compose.animation.*
  3. import androidx.compose.runtime.Composable
  4. import androidx.compose.ui.Alignment
  5. import androidx.compose.ui.graphics.painter.Painter
  6. import androidx.compose.ui.input.key.KeyEvent
  7. import androidx.compose.ui.unit.Dp
  8. import androidx.compose.ui.unit.DpSize
  9. import androidx.compose.ui.unit.dp
  10. import androidx.compose.ui.window.*
  11. import eu.mjdev.desktop.extensions.Compose.setWindowBounds
  12. import eu.mjdev.desktop.helpers.WindowFocusState.Companion.windowFocusHandler
  13. import eu.mjdev.desktop.provider.DesktopProvider
  14. import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
  15. import java.awt.Window
  16. @Composable
  17. fun TopWindow(
  18. windowState: TopWindowState,
  19. resizable: Boolean = false,
  20. title: String = "",
  21. icon: Painter? = null,
  22. focusable: Boolean = true,
  23. onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
  24. onKeyEvent: (KeyEvent) -> Boolean = { false },
  25. onCloseRequest: () -> Unit = {},
  26. onFocusChange: (hasFocus: Boolean) -> Unit = {},
  27. content: @Composable FrameWindowScope.() -> Unit
  28. ) {
  29. Window(
  30. state = windowState,
  31. resizable = resizable,
  32. undecorated = true,
  33. onCloseRequest = onCloseRequest,
  34. visible = true,
  35. transparent = true,
  36. title = title,
  37. icon = icon,
  38. focusable = focusable,
  39. alwaysOnTop = true,
  40. onPreviewKeyEvent = onPreviewKeyEvent,
  41. onKeyEvent = onKeyEvent,
  42. content = {
  43. windowFocusHandler { hasFocus -> onFocusChange(hasFocus) }
  44. windowState.window = window
  45. AnimatedVisibility(
  46. visible = windowState.isVisible,
  47. enter = windowState.enter,
  48. exit = windowState.exit,
  49. ) {
  50. content()
  51. }
  52. }
  53. )
  54. }
  55. data class WindowBounds(
  56. val x: Dp = 0.dp,
  57. val y: Dp = 0.dp,
  58. val width: Dp = 0.dp,
  59. val height: Dp = 0.dp
  60. )
  61. @Suppress("MemberVisibilityCanBePrivate", "unused")
  62. class TopWindowState(
  63. val containerSize: DpSize,
  64. override var placement: WindowPlacement,
  65. override var position: WindowPosition,
  66. override var size: DpSize = containerSize,
  67. override var isMinimized: Boolean = false,
  68. val minSize: DpSize = DpSize(0.dp, 0.dp),
  69. val visible: Boolean = true,
  70. val enter: EnterTransition = fadeIn() + expandIn(),
  71. val exit: ExitTransition = shrinkOut() + fadeOut(),
  72. val computeBounds: (visible: Boolean) -> WindowBounds = { isVisible ->
  73. when {
  74. isVisible -> WindowBounds(0.dp, 0.dp, size.width, size.height)
  75. else -> WindowBounds(0.dp, 0.dp, minSize.width, minSize.height)
  76. }
  77. },
  78. ) : WindowState {
  79. var isVisible: Boolean = !isMinimized && visible
  80. set(value) {
  81. field = value
  82. if (window != null) {
  83. computeBounds(value).let { bounds ->
  84. window?.setWindowBounds(bounds.x, bounds.y, bounds.width, bounds.height)
  85. }
  86. }
  87. }
  88. var window: Window? = null
  89. set(value) {
  90. field = value
  91. if (window != null) {
  92. computeBounds(isVisible).let { bounds ->
  93. value?.setWindowBounds(bounds.x, bounds.y, bounds.width, bounds.height)
  94. }
  95. }
  96. }
  97. fun hide() {
  98. isVisible = false
  99. }
  100. fun show() {
  101. isVisible = true
  102. }
  103. companion object {
  104. @Composable
  105. fun rememberTopWindowState(
  106. size: DpSize = DpSize(640.dp, 480.dp),
  107. minSize: DpSize = DpSize(0.dp, 0.dp),
  108. position: WindowPosition,
  109. visible: Boolean = true,
  110. placement: WindowPlacement = WindowPlacement.Floating,
  111. api: DesktopProvider = LocalDesktop.current,
  112. containerSize: DpSize = api.containerSize,
  113. enter: EnterTransition = fadeIn() + expandIn(),
  114. exit: ExitTransition = shrinkOut() + fadeOut(),
  115. computeBounds: (visible: Boolean) -> WindowBounds = { isVisible ->
  116. when {
  117. isVisible -> WindowBounds(0.dp, 0.dp, size.width, size.height)
  118. else -> WindowBounds(0.dp, 0.dp, 0.dp, 0.dp)
  119. }
  120. }
  121. ) = TopWindowState(
  122. containerSize = containerSize,
  123. placement = placement,
  124. position = position,
  125. size = size,
  126. minSize = minSize,
  127. computeBounds = computeBounds,
  128. enter = enter,
  129. exit = exit,
  130. visible = visible
  131. )
  132. @Composable
  133. fun rememberTopWindowState(
  134. size: DpSize = DpSize(640.dp, 480.dp),
  135. minSize: DpSize = DpSize(0.dp, 0.dp),
  136. position: Alignment = Alignment.BottomCenter,
  137. visible: Boolean = true,
  138. placement: WindowPlacement = WindowPlacement.Floating,
  139. api: DesktopProvider = LocalDesktop.current,
  140. containerSize: DpSize = api.containerSize,
  141. enter: EnterTransition = fadeIn() + expandIn(),
  142. exit: ExitTransition = shrinkOut() + fadeOut(),
  143. computeBounds: (visible: Boolean) -> WindowBounds = { isVisible ->
  144. when {
  145. isVisible -> WindowBounds(0.dp, 0.dp, size.width, size.height)
  146. else -> WindowBounds(0.dp, 0.dp, 0.dp, 0.dp)
  147. }
  148. }
  149. ) = rememberTopWindowState(
  150. size = size,
  151. minSize = minSize,
  152. position = WindowPosition.Aligned(position),
  153. placement = placement,
  154. api = api,
  155. containerSize = containerSize,
  156. computeBounds = computeBounds,
  157. enter = enter,
  158. exit = exit,
  159. visible = visible
  160. )
  161. }
  162. }