SurfaceGlow.kt 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /*
  2. * Copyright (c) Milan Jurkulák 2024.
  3. * Contact:
  4. * e: mimoccc@gmail.com
  5. * e: mj@mjdev.org
  6. * w: https://mjdev.org
  7. */
  8. package eu.mjdev.desktop.components.surface.base
  9. import androidx.compose.runtime.Composable
  10. import androidx.compose.ui.Modifier
  11. import androidx.compose.ui.graphics.Color
  12. import androidx.compose.ui.graphics.Outline
  13. import androidx.compose.ui.graphics.Paint
  14. import androidx.compose.ui.graphics.Shape
  15. import androidx.compose.ui.graphics.drawscope.ContentDrawScope
  16. import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
  17. import androidx.compose.ui.node.DrawModifierNode
  18. import androidx.compose.ui.node.ModifierNodeElement
  19. import androidx.compose.ui.platform.InspectorInfo
  20. import androidx.compose.ui.platform.LocalDensity
  21. import androidx.compose.ui.platform.debugInspectorInfo
  22. import eu.mjdev.desktop.components.surface.surfaceColorAtElevation
  23. import eu.mjdev.desktop.extensions.ColorUtils.alpha
  24. @Composable
  25. internal fun Modifier.surfaceGlow(
  26. shape: Shape,
  27. glow: Glow,
  28. ): Modifier {
  29. val color = surfaceColorAtElevation(
  30. color = glow.elevationColor,
  31. // elevation = glow.elevation
  32. )
  33. val glowBlurRadiusPx = with(LocalDensity.current) {
  34. glow.elevation.toPx()
  35. }
  36. return then(
  37. SurfaceGlowElement(
  38. shape = shape,
  39. glowBlurRadiusPx = glowBlurRadiusPx,
  40. color = color,
  41. inspectorInfo =
  42. debugInspectorInfo {
  43. name = "tvSurfaceGlow"
  44. properties["shape"] = shape
  45. properties["glow"] = glow
  46. }
  47. )
  48. )
  49. }
  50. private class SurfaceGlowElement(
  51. private val shape: Shape,
  52. private val glowBlurRadiusPx: Float,
  53. private val color: Color,
  54. private val inspectorInfo: InspectorInfo.() -> Unit
  55. ) : ModifierNodeElement<SurfaceGlowNode>() {
  56. override fun create(): SurfaceGlowNode {
  57. return SurfaceGlowNode(
  58. shape = shape,
  59. glowBlurRadiusPx = glowBlurRadiusPx,
  60. color = color,
  61. )
  62. }
  63. override fun update(node: SurfaceGlowNode) {
  64. node.reactToUpdates(
  65. newShape = shape,
  66. newGlowBlurRadiusPx = glowBlurRadiusPx,
  67. newColor = color
  68. )
  69. }
  70. override fun InspectorInfo.inspectableProperties() {
  71. inspectorInfo()
  72. }
  73. override fun hashCode(): Int {
  74. var result = shape.hashCode()
  75. result = 31 * result + glowBlurRadiusPx.hashCode()
  76. result = 31 * result + color.hashCode()
  77. return result
  78. }
  79. override fun equals(other: Any?): Boolean {
  80. val otherTyped = other as? SurfaceGlowElement ?: return false
  81. return shape == otherTyped.shape &&
  82. glowBlurRadiusPx == otherTyped.glowBlurRadiusPx &&
  83. color == otherTyped.color
  84. }
  85. }
  86. private class SurfaceGlowNode(
  87. private var shape: Shape,
  88. private var glowBlurRadiusPx: Float,
  89. private var color: Color,
  90. ) : DrawModifierNode, Modifier.Node() {
  91. private var paint: Paint? = null
  92. private var frameworkPaint: Paint? = null
  93. private var shapeOutlineCache: SurfaceShapeOutlineCache? = null
  94. fun reactToUpdates(
  95. newShape: Shape,
  96. newGlowBlurRadiusPx: Float,
  97. newColor: Color,
  98. ) {
  99. shape = newShape
  100. glowBlurRadiusPx = newGlowBlurRadiusPx
  101. color = newColor
  102. if (paint == null) {
  103. initializePaint()
  104. }
  105. setShadowLayer()
  106. }
  107. override fun ContentDrawScope.draw() {
  108. drawIntoCanvas { canvas ->
  109. if (paint == null) {
  110. initializePaint()
  111. setShadowLayer()
  112. }
  113. if (shapeOutlineCache == null) {
  114. shapeOutlineCache =
  115. SurfaceShapeOutlineCache(
  116. shape = shape,
  117. size = size,
  118. layoutDirection = layoutDirection,
  119. density = this
  120. )
  121. }
  122. when (
  123. val shapeOutline =
  124. shapeOutlineCache!!.updatedOutline(
  125. shape = shape,
  126. size = size,
  127. layoutDirection = layoutDirection,
  128. density = this
  129. )
  130. ) {
  131. is Outline.Rectangle -> canvas.drawRect(shapeOutline.rect, paint!!)
  132. is Outline.Rounded -> {
  133. val shapeCornerRadiusX = shapeOutline.roundRect.topLeftCornerRadius.x
  134. val shapeCornerRadiusY = shapeOutline.roundRect.topLeftCornerRadius.y
  135. canvas.drawRoundRect(
  136. left = 0f,
  137. top = 0f,
  138. right = size.width,
  139. bottom = size.height,
  140. radiusX = shapeCornerRadiusX,
  141. radiusY = shapeCornerRadiusY,
  142. paint = paint!!
  143. )
  144. }
  145. is Outline.Generic -> canvas.drawPath(shapeOutline.path, paint!!)
  146. }
  147. }
  148. drawContent()
  149. }
  150. private fun initializePaint() {
  151. paint = Paint()
  152. frameworkPaint = paint
  153. }
  154. private fun setShadowLayer() {
  155. val transparentColor = color.alpha(0f)
  156. val shadowColor = color
  157. frameworkPaint?.color = transparentColor
  158. frameworkPaint?.setShadowLayer(
  159. /* radius= */ glowBlurRadiusPx,
  160. /* dx= */ 0f,
  161. /* dy= */ 0f,
  162. /* shadowColor= */ shadowColor
  163. )
  164. }
  165. fun Paint.setShadowLayer(radius: Float, dx: Float, dy: Float, shadowColor: Color) {
  166. setShadowLayer(radius, dx, dy, shadowColor)
  167. }
  168. }