ControlCenter.kt 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package eu.mjdev.desktop.components.controlpanel
  2. import androidx.compose.animation.*
  3. import androidx.compose.foundation.ExperimentalFoundationApi
  4. import androidx.compose.foundation.background
  5. import androidx.compose.foundation.clickable
  6. import androidx.compose.foundation.gestures.Orientation
  7. import androidx.compose.foundation.layout.*
  8. import androidx.compose.foundation.lazy.LazyColumn
  9. import androidx.compose.foundation.lazy.itemsIndexed
  10. import androidx.compose.foundation.pager.HorizontalPager
  11. import androidx.compose.foundation.pager.PagerState
  12. import androidx.compose.foundation.pager.rememberPagerState
  13. import androidx.compose.foundation.shape.RoundedCornerShape
  14. import androidx.compose.material.Divider
  15. import androidx.compose.material.Icon
  16. import androidx.compose.runtime.Composable
  17. import androidx.compose.runtime.rememberCoroutineScope
  18. import androidx.compose.ui.Alignment
  19. import androidx.compose.ui.Modifier
  20. import androidx.compose.ui.graphics.Color
  21. import androidx.compose.ui.unit.Dp
  22. import androidx.compose.ui.unit.DpSize
  23. import androidx.compose.ui.unit.dp
  24. import androidx.compose.ui.window.WindowPlacement
  25. import androidx.compose.ui.window.WindowPosition
  26. import androidx.compose.ui.window.WindowState
  27. import androidx.compose.ui.window.rememberWindowState
  28. import eu.mjdev.desktop.components.shadow.LeftShadow
  29. import eu.mjdev.desktop.components.shadow.RightShadow
  30. import eu.mjdev.desktop.components.slidemenu.SlidingMenu
  31. import eu.mjdev.desktop.components.slidemenu.VisibilityState
  32. import eu.mjdev.desktop.components.slidemenu.VisibilityState.Companion.rememberVisibilityState
  33. import eu.mjdev.desktop.components.window.TopWindow
  34. import eu.mjdev.desktop.extensions.Compose.launchedEffect
  35. import eu.mjdev.desktop.extensions.Compose.setWindowBounds
  36. import eu.mjdev.desktop.helpers.WindowFocusState.Companion.windowFocusHandler
  37. import eu.mjdev.desktop.provider.DesktopProvider
  38. import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
  39. import kotlinx.coroutines.CoroutineScope
  40. import kotlinx.coroutines.launch
  41. @Suppress("FunctionName")
  42. @OptIn(ExperimentalFoundationApi::class)
  43. @Composable
  44. fun ControlCenter(
  45. api: DesktopProvider = LocalDesktop.current,
  46. backgroundColor: Color = api.currentUser.theme.backgroundColor,
  47. backgroundAlpha: Float = api.currentUser.theme.controlCenterBackgroundAlpha,
  48. shadowColor: Color = backgroundColor, // todo theme
  49. controlCenterDividerColor: Color = backgroundColor,// todo theme
  50. controlCenterExpandedWidth: Dp = api.currentUser.theme.controlCenterExpandedWidth,
  51. controlCenterDividerWidth: Dp = api.currentUser.theme.controlCenterDividerWidth,
  52. controlCenterIconColor: Color = api.currentUser.theme.controlCenterIconColor,
  53. controlCenterIconSize: DpSize = api.currentUser.theme.controlCenterIconSize,
  54. pages: List<ControlCenterPage> = api.controlCenterPages,
  55. pagerState: PagerState = rememberPagerState(pageCount = { pages.size }, initialPage = 0),
  56. controlCenterState: VisibilityState = rememberVisibilityState(),
  57. scope: CoroutineScope = rememberCoroutineScope(),
  58. onVisibilityChange: (visible: Boolean) -> Unit = {},
  59. ) {
  60. val windowState: WindowState = rememberWindowState(
  61. placement = WindowPlacement.Floating,
  62. position = WindowPosition.Aligned(api.currentUser.theme.controlCenterLocation.alignment),
  63. size = api.containerSize.copy(width = controlCenterDividerWidth),
  64. isMinimized = false
  65. )
  66. TopWindow(
  67. windowState = windowState,
  68. ) {
  69. windowFocusHandler { hasFocus -> if (!hasFocus) controlCenterState.hide() }
  70. SlidingMenu(
  71. orientation = Orientation.Horizontal,
  72. state = controlCenterState,
  73. onVisibilityChange = onVisibilityChange
  74. ) { isVisible ->
  75. if (!isVisible) {
  76. Divider(
  77. modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
  78. color = Color.Transparent,
  79. thickness = controlCenterDividerWidth
  80. )
  81. }
  82. AnimatedVisibility(
  83. isVisible,
  84. enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }),
  85. exit = slideOutHorizontally(targetOffsetX = { it }) + fadeOut(),
  86. ) {
  87. LeftShadow(
  88. modifier = Modifier.fillMaxHeight().wrapContentSize(),
  89. color = shadowColor,
  90. contentBackgroundColor = backgroundColor.copy(alpha = backgroundAlpha)
  91. ) {
  92. Row(
  93. modifier = Modifier.fillMaxHeight().wrapContentSize(),
  94. ) {
  95. Divider(
  96. modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
  97. color = controlCenterDividerColor,
  98. thickness = controlCenterDividerWidth
  99. )
  100. Box(
  101. contentAlignment = Alignment.TopEnd
  102. ) {
  103. LazyColumn(
  104. modifier = Modifier
  105. .fillMaxHeight()
  106. .background(backgroundColor)
  107. .padding(8.dp),
  108. verticalArrangement = Arrangement.Top,
  109. horizontalAlignment = Alignment.Start
  110. ) {
  111. itemsIndexed(pages) { idx, page ->
  112. val isSelected = (pagerState.currentPage == idx)
  113. Icon(
  114. modifier = Modifier
  115. .padding(vertical = 8.dp)
  116. .size(controlCenterIconSize)
  117. .background(
  118. color = if (isSelected) Color.White.copy(alpha = 0.4f) else Color.Transparent,
  119. shape = RoundedCornerShape(8.dp)
  120. )
  121. .clickable {
  122. scope.launch {
  123. pagerState.scrollToPage(idx)
  124. }
  125. },
  126. imageVector = page.icon,
  127. contentDescription = "",
  128. tint = if (isSelected) backgroundColor else controlCenterIconColor
  129. )
  130. }
  131. }
  132. RightShadow(
  133. modifier = Modifier.padding(
  134. end = controlCenterIconSize.width + 8.dp
  135. ).fillMaxHeight().wrapContentSize(),
  136. color = Color.Black,
  137. alpha = 0.3f,
  138. contentBackgroundColor = Color.Transparent
  139. ) {
  140. Row {
  141. HorizontalPager(
  142. modifier = Modifier.width(controlCenterExpandedWidth),
  143. state = pagerState
  144. ) { page ->
  145. pages[page].content(backgroundColor)
  146. }
  147. Divider(
  148. modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
  149. color = controlCenterDividerColor,
  150. thickness = controlCenterDividerWidth
  151. )
  152. }
  153. }
  154. Divider(
  155. modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
  156. color = controlCenterDividerColor,
  157. thickness = controlCenterDividerWidth
  158. )
  159. }
  160. }
  161. }
  162. }
  163. }
  164. launchedEffect(controlCenterState.isVisible) { isVisible ->
  165. val x = when (isVisible) {
  166. true -> (api.containerSize.width - controlCenterExpandedWidth)
  167. else -> (api.containerSize.width - controlCenterDividerWidth)
  168. }
  169. val y = 0.dp
  170. val width = when (isVisible) {
  171. true -> controlCenterExpandedWidth
  172. else -> controlCenterDividerWidth
  173. }
  174. val height = api.containerSize.height
  175. window.setWindowBounds(x, y, width, height)
  176. }
  177. }
  178. }