Modifier.kt 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. @file:Suppress("MemberVisibilityCanBePrivate")
  2. package eu.mjdev.desktop.extensions
  3. import androidx.compose.animation.core.animateFloatAsState
  4. import androidx.compose.foundation.border
  5. import androidx.compose.foundation.interaction.InteractionSource
  6. import androidx.compose.foundation.interaction.collectIsPressedAsState
  7. import androidx.compose.foundation.shape.CircleShape
  8. import androidx.compose.foundation.shape.RoundedCornerShape
  9. import androidx.compose.runtime.Composable
  10. import androidx.compose.runtime.Stable
  11. import androidx.compose.runtime.getValue
  12. import androidx.compose.ui.Modifier
  13. import androidx.compose.ui.Modifier.Node
  14. import androidx.compose.ui.composed
  15. import androidx.compose.ui.draw.clip
  16. import androidx.compose.ui.draw.drawBehind
  17. import androidx.compose.ui.draw.shadow
  18. import androidx.compose.ui.geometry.Offset
  19. import androidx.compose.ui.geometry.Size
  20. import androidx.compose.ui.graphics.*
  21. import androidx.compose.ui.graphics.drawscope.ContentDrawScope
  22. import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
  23. import androidx.compose.ui.node.DrawModifierNode
  24. import androidx.compose.ui.node.ModifierNodeElement
  25. import androidx.compose.ui.unit.Dp
  26. import androidx.compose.ui.unit.dp
  27. import eu.mjdev.desktop.extensions.Custom.setMaskFilter
  28. @Suppress("unused")
  29. object Modifier {
  30. @Stable
  31. @Composable
  32. fun Modifier.conditional(
  33. condition: Boolean,
  34. onFalse: (Modifier.() -> Modifier)? = null,
  35. onTrue: Modifier.() -> Modifier,
  36. ): Modifier = if (condition) {
  37. then(onTrue(Modifier))
  38. } else if (onFalse != null) {
  39. then(onFalse(Modifier))
  40. } else {
  41. this
  42. }
  43. fun Modifier.scaleOnPress(
  44. interactionSource: InteractionSource,
  45. pressScale: Float = 0.95f,
  46. leaveScale: Float = 1f
  47. ) = composed {
  48. val isPressed by interactionSource.collectIsPressedAsState()
  49. val scale by animateFloatAsState(if (isPressed) pressScale else leaveScale)
  50. this.graphicsLayer {
  51. scaleX = scale
  52. scaleY = scale
  53. }
  54. }
  55. @Stable
  56. @Composable
  57. fun Modifier.shadow(
  58. elevation: Dp,
  59. shape: Shape = RectangleShape,
  60. color: Color = DefaultShadowColor,
  61. clip: Boolean = false,
  62. ): Modifier = shadow(elevation, shape, clip, color, color)
  63. @Stable
  64. @Composable
  65. fun Modifier.rectShadow(
  66. size: Dp = 8.dp,
  67. color: Color = Color.Black,
  68. shape: Shape = RectangleShape
  69. ) = shadow(size, shape, color)
  70. @Stable
  71. @Composable
  72. fun Modifier.circleShadow(
  73. size: Dp = 8.dp,
  74. color: Color = Color.Black,
  75. shape: Shape = CircleShape
  76. ) = shadow(size, shape, color)
  77. @Stable
  78. @Composable
  79. fun Modifier.clipCircle() = clip(CircleShape)
  80. @Stable
  81. @Composable
  82. fun Modifier.clipRect() = clip(RectangleShape)
  83. @Stable
  84. @Composable
  85. fun Modifier.clipRoundRect(round: Dp) = clip(RoundedCornerShape(round))
  86. @Stable
  87. @Composable
  88. fun Modifier.circleBorder(
  89. width: Dp = 2.dp,
  90. color: Color = Color.White
  91. ) = clipCircle().border(width, color, CircleShape)
  92. @Stable
  93. @Composable
  94. fun Modifier.coloredCircleShadow(
  95. color: Color,
  96. blurRadius: Float,
  97. offsetY: Dp,
  98. offsetX: Dp,
  99. ) = then(
  100. drawBehind {
  101. drawIntoCanvas { canvas ->
  102. val paint = Paint()
  103. val frameworkPaint = paint.asFrameworkPaint()
  104. if (blurRadius != 0f) {
  105. frameworkPaint.setMaskFilter(blurRadius)
  106. }
  107. frameworkPaint.color = color.toArgb()
  108. val centerX = size.width / 2 + offsetX.toPx()
  109. val centerY = size.height / 2 + offsetY.toPx()
  110. val radius = size.width.coerceAtLeast(size.height) / 2
  111. canvas.drawCircle(Offset(centerX, centerY), radius, paint)
  112. }
  113. }
  114. )
  115. fun Modifier.rightShadow(
  116. color: Color,
  117. offsetX: Dp,
  118. blur: Dp
  119. ) = dropShadow(
  120. shape = RectangleShape,
  121. color = color,
  122. offsetX = -offsetX,
  123. offsetY = 0.dp,
  124. blur = blur,
  125. spread = offsetX,
  126. )
  127. fun Modifier.leftShadow(
  128. color: Color,
  129. offsetX: Dp,
  130. blur: Dp
  131. ) = dropShadow(
  132. shape = RectangleShape,
  133. color = color,
  134. offsetX = offsetX,
  135. offsetY = 0.dp,
  136. blur = blur,
  137. spread = offsetX,
  138. )
  139. fun Modifier.topShadow(
  140. color: Color,
  141. offsetY: Dp,
  142. blur: Dp
  143. ) = dropShadow(
  144. shape = RectangleShape,
  145. color = color,
  146. offsetX = 0.dp,
  147. offsetY = -offsetY,
  148. blur = blur,
  149. spread = offsetY,
  150. )
  151. fun Modifier.bottomShadow(
  152. color: Color,
  153. offsetY: Dp,
  154. blur: Dp
  155. ) = dropShadow(
  156. shape = RectangleShape,
  157. color = color,
  158. offsetX = 0.dp,
  159. offsetY = offsetY,
  160. blur = blur,
  161. spread = offsetY,
  162. )
  163. fun Modifier.dropShadow(
  164. shape: Shape,
  165. color: Color = Color.Black.copy(0.25f),
  166. offsetX: Dp = 0.dp,
  167. offsetY: Dp = 4.dp,
  168. blur: Dp = 4.dp,
  169. spread: Dp = 0.dp
  170. ): Modifier {
  171. return this then DropShadowNodeElement(shape, color, offsetX, offsetY, blur, spread)
  172. }
  173. private data class DropShadowNodeElement(
  174. val shape: Shape,
  175. val color: Color,
  176. val offsetX: Dp,
  177. val offsetY: Dp,
  178. val blur: Dp,
  179. val spread: Dp,
  180. ) : ModifierNodeElement<DropShadowNode>() {
  181. override fun create() = DropShadowNode(shape, color, offsetX, offsetY, blur, spread)
  182. override fun update(node: DropShadowNode) {
  183. node.shape = shape
  184. node.color = color
  185. node.offsetX = offsetX
  186. node.offsetY = offsetY
  187. node.blur = blur
  188. node.spread = spread
  189. }
  190. }
  191. private class DropShadowNode(
  192. var shape: Shape,
  193. var color: Color,
  194. var offsetX: Dp,
  195. var offsetY: Dp,
  196. var blur: Dp,
  197. var spread: Dp,
  198. ) : DrawModifierNode, Node() {
  199. override fun ContentDrawScope.draw() {
  200. val shadowSize = Size(size.width + spread.toPx(), size.height + spread.toPx())
  201. val shadowOutline = shape.createOutline(shadowSize, layoutDirection, this)
  202. val rOffsetX = offsetX.toPx() - spread.toPx() / 2
  203. val rOffsetY = offsetY.toPx() - spread.toPx() / 2
  204. val paint = Paint()
  205. paint.color = color
  206. if (blur.value > 0) {
  207. paint.asFrameworkPaint().apply {
  208. setMaskFilter(blur.toPx())
  209. }
  210. }
  211. drawIntoCanvas { canvas ->
  212. // Save the canvas state
  213. canvas.save()
  214. // Translate to specified offsets
  215. canvas.translate(rOffsetX, rOffsetY)
  216. // Draw the shadow
  217. canvas.drawOutline(shadowOutline, paint)
  218. // Restore the canvas state
  219. canvas.restore()
  220. }
  221. drawContent()
  222. }
  223. }
  224. }