DesktopPanel.kt 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. package eu.mjdev.desktop.components.desktoppanel
  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.ExperimentalFoundationApi
  6. import androidx.compose.foundation.TooltipArea
  7. import androidx.compose.foundation.TooltipPlacement
  8. import androidx.compose.foundation.background
  9. import androidx.compose.foundation.gestures.Orientation.Vertical
  10. import androidx.compose.foundation.layout.*
  11. import androidx.compose.material.Divider
  12. import androidx.compose.runtime.*
  13. import androidx.compose.ui.Alignment
  14. import androidx.compose.ui.Modifier
  15. import androidx.compose.ui.graphics.Brush
  16. import androidx.compose.ui.graphics.Color
  17. import androidx.compose.ui.layout.onPlaced
  18. import androidx.compose.ui.unit.*
  19. import androidx.compose.ui.window.WindowPosition
  20. import eu.mjdev.desktop.components.desktoppanel.applets.DesktopMenuIcon
  21. import eu.mjdev.desktop.components.desktoppanel.applets.DesktopPanelDateTime
  22. import eu.mjdev.desktop.components.desktoppanel.applets.DesktopPanelFavoriteApps
  23. import eu.mjdev.desktop.components.desktoppanel.applets.DesktopPanelLanguage
  24. import eu.mjdev.desktop.components.sliding.SlidingMenu
  25. import eu.mjdev.desktop.components.sliding.base.VisibilityState
  26. import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
  27. import eu.mjdev.desktop.components.tooltip.Tooltip
  28. import eu.mjdev.desktop.data.App
  29. import eu.mjdev.desktop.data.TooltipData
  30. import eu.mjdev.desktop.extensions.ColorUtils.alpha
  31. import eu.mjdev.desktop.extensions.ColorUtils.lighter
  32. import eu.mjdev.desktop.extensions.Compose.height
  33. import eu.mjdev.desktop.extensions.Compose.rememberState
  34. import eu.mjdev.desktop.extensions.Modifier.topShadow
  35. import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
  36. import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelExitAnimation
  37. import eu.mjdev.desktop.provider.DesktopScope
  38. import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
  39. import eu.mjdev.desktop.windows.ChromeWindow
  40. import eu.mjdev.desktop.windows.ChromeWindowState
  41. import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
  42. @OptIn(ExperimentalFoundationApi::class)
  43. @Suppress("FunctionName")
  44. @Composable
  45. fun DesktopPanel(
  46. iconSize: DpSize = DpSize(56.dp, 56.dp),
  47. iconPadding: PaddingValues = PaddingValues(4.dp),
  48. iconOuterPadding: PaddingValues = PaddingValues(2.dp),
  49. showMenuIcon: Boolean = true,
  50. panelState: VisibilityState = rememberVisibilityState(),
  51. menuState: VisibilityState = rememberVisibilityState(),
  52. enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
  53. exitAnimation: ExitTransition = DesktopPanelExitAnimation,
  54. tooltipHeight: Dp = 64.dp,
  55. tooltipConverter: (Any?) -> TooltipData = { item ->
  56. when (item) {
  57. is App -> TooltipData(title = item.name, description = item.comment)
  58. else -> TooltipData(title = item.toString())
  59. }
  60. },
  61. position: WindowPosition.Aligned = WindowPosition.Aligned(Alignment.BottomCenter),
  62. onMenuIconClicked: () -> Unit = {},
  63. onMenuIconContextMenuClicked: () -> Unit = {},
  64. onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
  65. onAppClick: DesktopScope.(App) -> Unit = { app -> startApp(app) },
  66. onAppContextMenuClick: (App) -> Unit = {},
  67. onLanguageClick: () -> Unit = {},
  68. onClockClick: () -> Unit = {}
  69. ) = withDesktopScope {
  70. val tooltipState = rememberState<Any?>(null)
  71. val panelDividerWidth by rememberState(theme.panelDividerWidth)
  72. val panelHeight: (visible: Boolean) -> Dp = { visible ->
  73. if (visible) iconSize.height + iconOuterPadding.height + tooltipHeight + theme.panelContentPadding.times(2)
  74. else panelDividerWidth
  75. }
  76. val panelSize = remember(panelState.isVisible, panelState.enabled) {
  77. DpSize(containerSize.width, panelHeight(panelState.isVisible))
  78. }
  79. val windowState: ChromeWindowState = rememberChromeWindowState(
  80. position = position,
  81. size = panelSize
  82. )
  83. panelState.updateSize(panelSize - DpSize(0.dp, tooltipHeight))
  84. ChromeWindow(
  85. windowState = windowState,
  86. visible = true,
  87. alwaysOnTop = panelState.enabled,
  88. enterAnimation = enterAnimation,
  89. exitAnimation = exitAnimation,
  90. onFocusChange = { focused ->
  91. panelState.onFocusChange(focused)
  92. onFocusChange(focused)
  93. }) {
  94. SlidingMenu(
  95. modifier = Modifier.fillMaxWidth(),
  96. orientation = Vertical,
  97. state = panelState,
  98. onPointerEnter = {
  99. panelState.show()
  100. if (!menuState.isVisible) {
  101. windowState.requestFocus()
  102. }
  103. }, onPointerLeave = {
  104. if (!menuState.isVisible) {
  105. panelState.hide()
  106. }
  107. }) { isVisible ->
  108. Divider(
  109. modifier = Modifier.fillMaxWidth().height(panelDividerWidth),
  110. color = Color.Transparent,
  111. thickness = panelDividerWidth
  112. )
  113. if (isVisible) {
  114. TooltipArea(
  115. modifier = Modifier.fillMaxWidth().wrapContentHeight(), tooltip = {
  116. Tooltip(
  117. textColor = textColor,
  118. borderColor = borderColor,
  119. state = tooltipState,
  120. converter = tooltipConverter,
  121. backgroundColor = backgroundColor
  122. )
  123. }, tooltipPlacement = TooltipPlacement.CursorPoint(
  124. alignment = Alignment.TopEnd, offset = DpOffset(32.dp, 32.dp)
  125. )
  126. ) {
  127. Column {
  128. Box(
  129. modifier = Modifier.fillMaxWidth().height(tooltipHeight)
  130. )
  131. Column(
  132. modifier = Modifier.fillMaxWidth().wrapContentHeight().background(
  133. brush = Brush.verticalGradient(
  134. colors = listOf(
  135. backgroundColor.alpha(0.3f),
  136. backgroundColor.alpha(0.7f),
  137. backgroundColor.alpha(0.8f),
  138. )
  139. )
  140. ).topShadow(
  141. color = borderColor.alpha(0.3f), blur = 4.dp
  142. ).onPlaced(panelState::onPlaced),
  143. ) {
  144. Divider(
  145. modifier = Modifier.fillMaxWidth().height(2.dp), color = borderColor, thickness = 2.dp
  146. )
  147. Box(
  148. modifier = Modifier.fillMaxWidth().padding(top = 4.dp)
  149. .padding(theme.panelContentPadding)
  150. ) {
  151. if (showMenuIcon) {
  152. DesktopMenuIcon(
  153. modifier = Modifier.align(Alignment.CenterStart),
  154. iconColor = borderColor,
  155. iconBackgroundColor = iconsTintColor,
  156. iconSize = iconSize,
  157. iconPadding = iconPadding,
  158. iconOuterPadding = iconOuterPadding,
  159. onTooltip = { item -> tooltipState.value = item },
  160. onClick = { onMenuIconClicked() },
  161. onContextMenuClick = onMenuIconContextMenuClicked
  162. )
  163. }
  164. DesktopPanelFavoriteApps(
  165. modifier = Modifier.align(Alignment.Center),
  166. iconColor = borderColor,
  167. iconBackgroundColor = iconsTintColor,
  168. iconColorRunning = iconsTintColor.lighter(0.3f),
  169. iconSize = iconSize,
  170. iconPadding = iconPadding,
  171. iconOuterPadding = iconOuterPadding,
  172. onTooltip = { item -> tooltipState.value = item },
  173. onAppClick = { app -> onAppClick(app) },
  174. onContextMenuClick = onAppContextMenuClick
  175. )
  176. DesktopPanelTray(
  177. modifier = Modifier.align(Alignment.CenterEnd).padding(end = 16.dp),
  178. ) {
  179. DesktopPanelLanguage(
  180. onTooltip = { item -> tooltipState.value = item },
  181. onClick = onLanguageClick
  182. )
  183. DesktopPanelDateTime(
  184. onTooltip = { item -> tooltipState.value = item },
  185. onClick = onClockClick
  186. )
  187. }
  188. }
  189. }
  190. }
  191. }
  192. }
  193. }
  194. }
  195. }
  196. @Preview
  197. @Composable
  198. fun DesktopPanelPreview() = DesktopPanel()