2
0

ControlCenter.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. package eu.mjdev.desktop.components.controlcenter
  2. import androidx.compose.animation.EnterTransition
  3. import androidx.compose.animation.ExitTransition
  4. import androidx.compose.desktop.ui.tooling.preview.Preview
  5. import androidx.compose.foundation.background
  6. import androidx.compose.foundation.gestures.Orientation.Horizontal
  7. import androidx.compose.foundation.gestures.detectTapGestures
  8. import androidx.compose.foundation.layout.*
  9. import androidx.compose.foundation.lazy.LazyColumn
  10. import androidx.compose.foundation.lazy.itemsIndexed
  11. import androidx.compose.foundation.shape.RoundedCornerShape
  12. import androidx.compose.material.Divider
  13. import androidx.compose.material.Icon
  14. import androidx.compose.runtime.Composable
  15. import androidx.compose.runtime.LaunchedEffect
  16. import androidx.compose.runtime.MutableState
  17. import androidx.compose.runtime.getValue
  18. import androidx.compose.ui.Alignment
  19. import androidx.compose.ui.Modifier
  20. import androidx.compose.ui.graphics.Color
  21. import androidx.compose.ui.input.pointer.pointerInput
  22. import androidx.compose.ui.unit.DpSize
  23. import androidx.compose.ui.unit.dp
  24. import androidx.compose.ui.window.WindowPosition
  25. import eu.mjdev.desktop.components.blur.BlurPanel
  26. import eu.mjdev.desktop.components.sliding.SlidingPanel
  27. import eu.mjdev.desktop.extensions.Compose.onLeftClick
  28. import eu.mjdev.desktop.extensions.Compose.onMousePress
  29. import eu.mjdev.desktop.extensions.Compose.onRightClick
  30. import eu.mjdev.desktop.extensions.Compose.preview
  31. import eu.mjdev.desktop.extensions.Compose.rememberCalculated
  32. import eu.mjdev.desktop.extensions.Compose.rememberComputed
  33. import eu.mjdev.desktop.extensions.Compose.rememberState
  34. import eu.mjdev.desktop.extensions.Compose.runAsync
  35. import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterEnterAnimation
  36. import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
  37. import eu.mjdev.desktop.helpers.keyevents.GlobalKeyListener.Companion.globalKeyEventHandler
  38. import eu.mjdev.desktop.helpers.mouseevents.GlobalMouseListener.Companion.globalMouseEventHandler
  39. import eu.mjdev.desktop.helpers.mouseevents.MouseRange
  40. import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
  41. import eu.mjdev.desktop.windows.ChromeWindow
  42. import eu.mjdev.desktop.windows.ChromeWindowState
  43. import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
  44. @Suppress("FunctionName")
  45. @Composable
  46. fun ControlCenter(
  47. pagerState: MutableState<Int> = rememberState(0),
  48. enterAnimation: EnterTransition = ControlCenterEnterAnimation,
  49. exitAnimation: ExitTransition = ControlCenterExitAnimation,
  50. onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
  51. onContextMenuClick: () -> Unit = {},
  52. controlCenterState: ChromeWindowState = rememberChromeWindowState(),
  53. ) = withDesktopScope {
  54. val controlCenterExpandedWidth by rememberComputed(
  55. theme.controlCenterExpandedWidth,
  56. containerSize.width
  57. ) {
  58. (containerSize.width / 100f) * theme.controlCenterExpandedWidth
  59. }
  60. val pagesFiltered by rememberCalculated(controlCenterPages) {
  61. controlCenterPages.filter { p -> p.condition.invoke(api) }
  62. }
  63. val size by rememberCalculated(controlCenterState.isVisible) {
  64. if (controlCenterState.isVisible) {
  65. DpSize(controlCenterExpandedWidth, containerSize.height)
  66. } else {
  67. DpSize(controlCenterDividerWidth, containerSize.height)
  68. }
  69. }
  70. val position by rememberComputed(size) {
  71. WindowPosition.Absolute(
  72. containerSize.width - size.width,
  73. 0.dp
  74. )
  75. }
  76. val mouseRange by rememberCalculated(
  77. containerSize,
  78. size
  79. ) {
  80. MouseRange(
  81. x = containerSize.width - controlCenterDividerWidth,
  82. y = 0.dp,
  83. width = size.width,
  84. height = containerSize.height
  85. )
  86. }
  87. globalKeyEventHandler(
  88. isEnabled = { controlCenterState.isVisible && controlCenterState.enabled }
  89. ) {
  90. onEscape {
  91. controlCenterState.hide()
  92. true
  93. }
  94. }
  95. globalMouseEventHandler {
  96. onPointerEnter(mouseRange) {
  97. runAsync {
  98. println("Pointer enter control center.")
  99. controlCenterState.showOrFocus()
  100. }
  101. }
  102. }
  103. ChromeWindow(
  104. name = "ControlCenter",
  105. visible = true,
  106. windowState = controlCenterState,
  107. enterAnimation = enterAnimation,
  108. exitAnimation = exitAnimation,
  109. onFocusChange = onFocusChange,
  110. position = position,
  111. size = size,
  112. onCreated = {
  113. controlCenterState.size = size
  114. controlCenterState.position = position
  115. }
  116. ) {
  117. SlidingPanel(
  118. modifier = Modifier.fillMaxHeight(),
  119. orientation = Horizontal,
  120. state = controlCenterState,
  121. onPointerEnter = {
  122. runAsync {
  123. controlCenterState.show()
  124. controlCenterState.requestFocus()
  125. }
  126. }
  127. ) { isVisible ->
  128. if (!isVisible) {
  129. Divider(
  130. modifier = Modifier.fillMaxHeight()
  131. .width(controlCenterDividerWidth),
  132. color = if (isDebug) Color.Red else Color.Transparent,
  133. thickness = controlCenterDividerWidth
  134. )
  135. } else {
  136. BlurPanel(
  137. modifier = Modifier.fillMaxHeight()
  138. .matchParentSize()
  139. )
  140. Box(
  141. modifier = Modifier.fillMaxHeight()
  142. .wrapContentSize()
  143. .background(
  144. backgroundColor.copy(alpha = controlCenterBackgroundAlpha)
  145. ),
  146. ) {
  147. Row(
  148. modifier = Modifier.fillMaxHeight(),
  149. ) {
  150. Box(
  151. contentAlignment = Alignment.TopEnd
  152. ) {
  153. LazyColumn(
  154. modifier = Modifier
  155. .fillMaxHeight()
  156. .background(backgroundColor)
  157. .padding(8.dp),
  158. verticalArrangement = Arrangement.Top,
  159. horizontalAlignment = Alignment.Start
  160. ) {
  161. itemsIndexed(pagesFiltered) { idx, page ->
  162. Icon(
  163. modifier = Modifier
  164. .padding(vertical = 8.dp)
  165. .size(controlCenterIconSize)
  166. .background(
  167. color = if (pagerState.value == idx) iconsTintColor else borderColor,
  168. shape = RoundedCornerShape(8.dp)
  169. )
  170. .pointerInput(Unit) {
  171. detectTapGestures(
  172. onTap = {
  173. pagerState.value = idx
  174. }
  175. )
  176. }
  177. .onMousePress {
  178. onLeftClick {
  179. runAsync {
  180. pagerState.value = idx
  181. }
  182. }
  183. onRightClick {
  184. runAsync {
  185. pagerState.value = idx
  186. }
  187. onContextMenuClick()
  188. }
  189. },
  190. imageVector = page.icon,
  191. contentDescription = "",
  192. tint = if (pagerState.value == idx) backgroundColor else textColor
  193. )
  194. }
  195. }
  196. Box(
  197. modifier = Modifier
  198. .padding(
  199. end = controlCenterIconSize.width + 16.dp
  200. )
  201. .fillMaxHeight(),
  202. ) {
  203. Row(
  204. Modifier.fillMaxHeight()
  205. ) {
  206. Divider(
  207. modifier = Modifier.fillMaxHeight()
  208. .width(2.dp),
  209. color = borderColor,
  210. thickness = 2.dp
  211. )
  212. Column(
  213. modifier = Modifier.width(controlCenterExpandedWidth),
  214. ) {
  215. val page = pagesFiltered[pagerState.value]
  216. Box(modifier = Modifier.weight(1f)) {
  217. page.render()
  218. }
  219. }
  220. Divider(
  221. modifier = Modifier.fillMaxHeight()
  222. .width(2.dp),
  223. color = borderColor,
  224. thickness = 2.dp
  225. )
  226. }
  227. }
  228. Divider(
  229. modifier = Modifier.fillMaxHeight().width(2.dp),
  230. color = Color.White.copy(0.1f),
  231. thickness = 2.dp
  232. )
  233. }
  234. }
  235. }
  236. }
  237. }
  238. }
  239. LaunchedEffect(position, size) {
  240. controlCenterState.size = size
  241. }
  242. }
  243. // todo
  244. @Preview
  245. @Composable
  246. fun ControlCenterPreview() = preview {
  247. ControlCenter()
  248. }