DesktopPanel.kt 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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: (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 size = remember(panelState.isVisible) {
  77. DpSize(containerSize.width, panelHeight(panelState.isVisible))
  78. }
  79. val windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size)
  80. ChromeWindow(
  81. windowState = windowState,
  82. visible = true,
  83. enterAnimation = enterAnimation,
  84. exitAnimation = exitAnimation,
  85. onFocusChange = { focused ->
  86. panelState.onFocusChange(focused)
  87. onFocusChange(focused)
  88. }) {
  89. SlidingMenu(modifier = Modifier.fillMaxWidth(), orientation = Vertical, state = panelState, onPointerEnter = {
  90. panelState.show()
  91. if (!menuState.isVisible) {
  92. windowState.requestFocus()
  93. }
  94. }, onPointerLeave = {
  95. if (!menuState.isVisible) {
  96. panelState.hide()
  97. }
  98. }) { isVisible ->
  99. Divider(
  100. modifier = Modifier.fillMaxWidth().height(panelDividerWidth),
  101. color = Color.Transparent,
  102. thickness = panelDividerWidth
  103. )
  104. if (isVisible) {
  105. TooltipArea(
  106. modifier = Modifier.fillMaxWidth().wrapContentHeight(), tooltip = {
  107. Tooltip(
  108. textColor = textColor,
  109. borderColor = borderColor,
  110. state = tooltipState,
  111. converter = tooltipConverter,
  112. backgroundColor = backgroundColor
  113. )
  114. }, tooltipPlacement = TooltipPlacement.CursorPoint(
  115. alignment = Alignment.TopEnd, offset = DpOffset(32.dp, 32.dp)
  116. )
  117. ) {
  118. Column {
  119. Box(
  120. modifier = Modifier.fillMaxWidth().height(tooltipHeight)
  121. )
  122. Column(
  123. modifier = Modifier.fillMaxWidth().wrapContentHeight().background(
  124. brush = Brush.verticalGradient(
  125. colors = listOf(
  126. backgroundColor.alpha(0.3f),
  127. backgroundColor.alpha(0.7f),
  128. backgroundColor.alpha(0.8f),
  129. )
  130. )
  131. ).topShadow(
  132. color = borderColor.alpha(0.3f), blur = 4.dp
  133. ).onPlaced(panelState::onPlaced),
  134. ) {
  135. Divider(
  136. modifier = Modifier.fillMaxWidth().height(2.dp), color = borderColor, thickness = 2.dp
  137. )
  138. Box(
  139. modifier = Modifier.fillMaxWidth().padding(top = 4.dp)
  140. .padding(theme.panelContentPadding)
  141. ) {
  142. if (showMenuIcon) {
  143. DesktopMenuIcon(
  144. modifier = Modifier.align(Alignment.CenterStart),
  145. iconColor = borderColor,
  146. iconBackgroundColor = iconsTintColor,
  147. iconSize = iconSize,
  148. iconPadding = iconPadding,
  149. iconOuterPadding = iconOuterPadding,
  150. onTooltip = { item -> tooltipState.value = item },
  151. onClick = { onMenuIconClicked() },
  152. onContextMenuClick = onMenuIconContextMenuClicked
  153. )
  154. }
  155. DesktopPanelFavoriteApps(
  156. modifier = Modifier.align(Alignment.Center),
  157. iconColor = borderColor,
  158. iconBackgroundColor = iconsTintColor,
  159. iconColorRunning = iconsTintColor.lighter(0.3f),
  160. iconSize = iconSize,
  161. iconPadding = iconPadding,
  162. iconOuterPadding = iconOuterPadding,
  163. onTooltip = { item -> tooltipState.value = item },
  164. onAppClick = { app -> onAppClick(app) },
  165. onContextMenuClick = onAppContextMenuClick
  166. )
  167. DesktopPanelTray(
  168. modifier = Modifier.align(Alignment.CenterEnd).padding(end = 16.dp),
  169. ) {
  170. DesktopPanelLanguage(
  171. onTooltip = { item -> tooltipState.value = item },
  172. onClick = onLanguageClick
  173. )
  174. DesktopPanelDateTime(
  175. onTooltip = { item -> tooltipState.value = item },
  176. onClick = onClockClick
  177. )
  178. }
  179. }
  180. }
  181. }
  182. }
  183. }
  184. }
  185. }
  186. }
  187. @Preview
  188. @Composable
  189. fun DesktopPanelPreview() = DesktopPanel()