Skip to content

Commit 53fb4ef

Browse files
committed
支持毛玻璃效果,新增 frostedGlass 修饰符和 FrostedGlassContainer 组件,优化交互视觉,包括卡片圆角调整和动态背景透明度变化,同时更新文档以反映相关修改。
1 parent 7b4bb01 commit 53fb4ef

File tree

3 files changed

+338
-3
lines changed

3 files changed

+338
-3
lines changed

.junie/guidelines.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Project-specific development guidelines for simbot-codegen
33
Last verified: 2025-08-08 (local), Gradle 8.10.2, Kotlin 2.2.20-Beta1
44

55
Overview
6-
- This repo is a Kotlin Multiplatform (KMP) workspace primarily targeting Web (JS + Wasm/JS) with Compose Multiplatform for the UI.
6+
- This repo is a Kotlin Multiplatform (KMP) workspace primarily targeting Web (Wasm/JS) with Compose Multiplatform for the UI.
77
- Modules:
88
- composeApp: Web UI and code generation front-end (Wasm/JS executable).
99
- common: Shared code and small utilities (JS + Wasm/JS libraries).
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
package love.forte.simbot.codegen.components
2+
3+
import androidx.compose.animation.core.animateFloatAsState
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.draw.alpha
12+
import androidx.compose.ui.draw.drawBehind
13+
import androidx.compose.ui.geometry.Offset
14+
import androidx.compose.ui.graphics.*
15+
import androidx.compose.ui.graphics.drawscope.DrawScope
16+
import androidx.compose.ui.graphics.drawscope.clipPath
17+
import androidx.compose.ui.draw.clip
18+
import androidx.compose.ui.geometry.Size
19+
import androidx.compose.ui.geometry.toRect
20+
import kotlin.math.sin
21+
import kotlin.math.cos
22+
import kotlin.math.PI
23+
import kotlin.math.exp
24+
import kotlin.math.sqrt
25+
26+
/**
27+
* 毛玻璃效果修饰符
28+
* 通过多层半透明背景和纹理模拟毛玻璃效果,无需依赖CSS或DOM
29+
* 现在支持形状裁剪,确保效果不会溢出圆角等边界
30+
*
31+
* @param isActive 是否启用毛玻璃效果
32+
* @param intensity 效果强度,范围0.0-1.0
33+
* @param backgroundColor 基础背景色,如果为null则使用主题色
34+
* @param shape 裁剪形状,如果为null则不进行形状裁剪
35+
*/
36+
@Composable
37+
fun Modifier.frostedGlass(
38+
isActive: Boolean,
39+
intensity: Float = 1.0f,
40+
backgroundColor: Color? = null,
41+
shape: Shape? = null
42+
): Modifier {
43+
val colorScheme = MaterialTheme.colorScheme
44+
val baseColor = backgroundColor ?: colorScheme.surface
45+
46+
// 动画过渡效果强度
47+
val animatedIntensity by animateFloatAsState(
48+
targetValue = if (isActive) intensity else 0f,
49+
label = "frostedGlassIntensity"
50+
)
51+
52+
return this.then(
53+
if (animatedIntensity > 0f) {
54+
Modifier.drawBehind {
55+
drawFrostedGlassEffect(
56+
baseColor = baseColor,
57+
primaryColor = colorScheme.primary,
58+
surfaceVariant = colorScheme.surfaceVariant,
59+
intensity = animatedIntensity,
60+
shape = shape
61+
)
62+
}
63+
} else {
64+
Modifier
65+
}
66+
)
67+
}
68+
69+
/**
70+
* 绘制毛玻璃效果的核心函数
71+
* 通过多层渐变、噪声纹理和透明度变化模拟真实的毛玻璃效果
72+
* 现在支持形状裁剪,确保效果在指定形状内绘制
73+
*/
74+
private fun DrawScope.drawFrostedGlassEffect(
75+
baseColor: Color,
76+
primaryColor: Color,
77+
surfaceVariant: Color,
78+
intensity: Float,
79+
shape: Shape? = null
80+
) {
81+
val width = size.width
82+
val height = size.height
83+
84+
// 定义绘制操作的lambda函数
85+
val drawOperations = {
86+
// 第一层:基础半透明背景
87+
drawRect(
88+
color = baseColor.copy(alpha = 0.85f * intensity),
89+
size = size
90+
)
91+
92+
// 第二层:增强的高斯模糊效果,多层渐变模拟真实模糊
93+
drawEnhancedBlurEffect(width, height, intensity, surfaceVariant, primaryColor)
94+
95+
// 第三层:添加纹理噪声,模拟毛玻璃的细微纹理
96+
drawGlassTexture(width, height, intensity, surfaceVariant)
97+
98+
// 第四层:边缘高光效果,增强玻璃质感
99+
drawGlassHighlights(width, height, intensity, primaryColor)
100+
}
101+
102+
// 如果提供了形状,使用裁剪路径绘制;否则直接绘制
103+
if (shape != null) {
104+
val path = Path().apply {
105+
addOutline(shape.createOutline(size, layoutDirection, this@drawFrostedGlassEffect))
106+
}
107+
clipPath(path) {
108+
drawOperations()
109+
}
110+
} else {
111+
drawOperations()
112+
}
113+
}
114+
115+
/**
116+
* 绘制玻璃纹理噪声
117+
* 通过计算生成的伪随机点创建类似磨砂玻璃的纹理效果
118+
* 现在使用增强版本,支持多种大小和透明度的纹理点
119+
*/
120+
private fun DrawScope.drawGlassTexture(
121+
width: Float,
122+
height: Float,
123+
intensity: Float,
124+
color: Color
125+
) {
126+
// 使用增强的纹理点生成算法
127+
val enhancedTexturePoints = generateEnhancedTexturePoints(width, height)
128+
129+
enhancedTexturePoints.forEach { (point, size, alpha) ->
130+
drawCircle(
131+
color = color.copy(alpha = alpha * intensity),
132+
radius = size,
133+
center = point
134+
)
135+
}
136+
}
137+
138+
/**
139+
* 绘制玻璃高光效果
140+
* 在边缘和特定区域添加微妙的高光,增强玻璃的立体感
141+
*/
142+
private fun DrawScope.drawGlassHighlights(
143+
width: Float,
144+
height: Float,
145+
intensity: Float,
146+
highlightColor: Color
147+
) {
148+
val highlightAlpha = 0.12f * intensity
149+
150+
// 顶部高光
151+
val topHighlight = Brush.verticalGradient(
152+
colors = listOf(
153+
highlightColor.copy(alpha = highlightAlpha),
154+
Color.Transparent
155+
),
156+
startY = 0f,
157+
endY = height * 0.3f
158+
)
159+
160+
drawRect(
161+
brush = topHighlight,
162+
size = size
163+
)
164+
165+
// 左侧高光
166+
val leftHighlight = Brush.horizontalGradient(
167+
colors = listOf(
168+
highlightColor.copy(alpha = highlightAlpha * 0.7f),
169+
Color.Transparent
170+
),
171+
startX = 0f,
172+
endX = width * 0.2f
173+
)
174+
175+
drawRect(
176+
brush = leftHighlight,
177+
size = size
178+
)
179+
}
180+
181+
/**
182+
* 生成纹理点位置
183+
* 使用确定性算法生成看起来随机但可重现的纹理点
184+
*/
185+
private fun generateTexturePoints(width: Float, height: Float): List<Offset> {
186+
val points = mutableListOf<Offset>()
187+
val density = 0.3f // 控制纹理密度
188+
val stepX = width / (width * density / 10)
189+
val stepY = height / (height * density / 10)
190+
191+
var x = 0f
192+
while (x < width) {
193+
var y = 0f
194+
while (y < height) {
195+
// 使用三角函数创建伪随机偏移
196+
val offsetX = sin(x * 0.01f + y * 0.007f) * 3f
197+
val offsetY = cos(y * 0.013f + x * 0.009f) * 3f
198+
199+
// 只有满足特定条件的点才会被绘制,创建稀疏的纹理效果
200+
if (sin(x * 0.02f) * cos(y * 0.015f) > 0.1f) {
201+
points.add(Offset(x + offsetX, y + offsetY))
202+
}
203+
204+
y += stepY
205+
}
206+
x += stepX
207+
}
208+
209+
return points
210+
}
211+
212+
/**
213+
* 生成增强的纹理点位置
214+
* 返回包含位置、大小和透明度的纹理点,创建更自然的效果
215+
*/
216+
private fun generateEnhancedTexturePoints(width: Float, height: Float): List<Triple<Offset, Float, Float>> {
217+
val points = mutableListOf<Triple<Offset, Float, Float>>()
218+
val density = 0.4f // 增加密度获得更丰富的纹理
219+
val stepX = width / (width * density / 8)
220+
val stepY = height / (height * density / 8)
221+
222+
var x = 0f
223+
while (x < width) {
224+
var y = 0f
225+
while (y < height) {
226+
// 使用更复杂的函数创建更自然的分布
227+
val noiseX = sin(x * 0.008f + y * 0.012f) * 4f
228+
val noiseY = cos(y * 0.011f + x * 0.007f) * 4f
229+
230+
// 使用多个条件创建不同类型的纹理点
231+
val threshold = sin(x * 0.015f) * cos(y * 0.018f)
232+
if (threshold > -0.2f) {
233+
// 计算点的大小 - 基于位置的变化
234+
val sizeVariation = (sin(x * 0.02f + y * 0.025f) + 1f) * 0.5f
235+
val pointSize = 0.3f + sizeVariation * 1.2f
236+
237+
// 计算透明度 - 创建不均匀分布
238+
val alphaVariation = (cos(x * 0.013f + y * 0.019f) + 1f) * 0.5f
239+
val pointAlpha = 0.04f + alphaVariation * 0.08f
240+
241+
points.add(
242+
Triple(
243+
Offset(x + noiseX, y + noiseY),
244+
pointSize,
245+
pointAlpha
246+
)
247+
)
248+
}
249+
250+
y += stepY
251+
}
252+
x += stepX
253+
}
254+
255+
return points
256+
}
257+
258+
/**
259+
* 增强的高斯模糊效果
260+
* 通过多层不同半径的渐变叠加,更真实地模拟高斯模糊
261+
*/
262+
private fun DrawScope.drawEnhancedBlurEffect(
263+
width: Float,
264+
height: Float,
265+
intensity: Float,
266+
surfaceVariant: Color,
267+
primaryColor: Color
268+
) {
269+
// 模拟多层高斯模糊 - 使用多个不同半径的径向渐变
270+
val blurLayers = listOf(
271+
// 大半径,低权重 - 模拟远距离模糊
272+
Triple(width * 1.2f, 0.08f, Offset(width * 0.5f, height * 0.5f)),
273+
// 中等半径,中等权重 - 主要模糊效果
274+
Triple(width * 0.8f, 0.15f, Offset(width * 0.3f, height * 0.2f)),
275+
Triple(width * 0.6f, 0.12f, Offset(width * 0.7f, height * 0.6f)),
276+
// 小半径,高权重 - 细节模糊
277+
Triple(width * 0.4f, 0.2f, Offset(width * 0.2f, height * 0.8f)),
278+
Triple(width * 0.3f, 0.18f, Offset(width * 0.8f, height * 0.3f))
279+
)
280+
281+
blurLayers.forEach { (radius, weight, center) ->
282+
// 为每层使用轻微不同的颜色,增加真实感
283+
val layerColor = when {
284+
radius > width * 0.8f -> surfaceVariant.copy(alpha = weight * intensity)
285+
radius > width * 0.5f -> primaryColor.copy(alpha = weight * 0.8f * intensity)
286+
else -> surfaceVariant.copy(alpha = weight * 1.2f * intensity)
287+
}
288+
289+
val blurGradient = Brush.radialGradient(
290+
colors = listOf(
291+
layerColor,
292+
layerColor.copy(alpha = layerColor.alpha * 0.7f),
293+
layerColor.copy(alpha = layerColor.alpha * 0.3f),
294+
Color.Transparent
295+
),
296+
center = center,
297+
radius = radius
298+
)
299+
300+
drawRect(
301+
brush = blurGradient,
302+
size = size
303+
)
304+
}
305+
}
306+
307+
/**
308+
* 毛玻璃容器组件
309+
* 为内容提供毛玻璃背景效果的便捷容器
310+
*
311+
* @param isActive 是否启用毛玻璃效果
312+
* @param intensity 效果强度
313+
* @param modifier 修饰符
314+
* @param content 内容
315+
*/
316+
@Composable
317+
fun FrostedGlassContainer(
318+
isActive: Boolean,
319+
modifier: Modifier = Modifier,
320+
intensity: Float = 1.0f,
321+
content: @Composable () -> Unit
322+
) {
323+
Box(
324+
modifier = modifier.frostedGlass(
325+
isActive = isActive,
326+
intensity = intensity
327+
)
328+
) {
329+
content()
330+
}
331+
}

composeApp/src/wasmJsMain/kotlin/love/forte/simbot/codegen/components/GroupCard.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ fun GroupCard(
4949

5050
val cardContainerColor by animateColorAsState(
5151
targetValue = if (isHovered) {
52-
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.15f)
52+
// 当启用毛玻璃效果时,使用透明背景让效果显现
53+
MaterialTheme.colorScheme.surface.copy(alpha = 0.3f)
5354
} else {
5455
MaterialTheme.colorScheme.surface
5556
},
@@ -60,11 +61,14 @@ fun GroupCard(
6061
targetValue = if (isHovered) 2.dp else 0.dp,
6162
label = "cardElevation"
6263
)
64+
val cardShape = RoundedCornerShape(16.dp) // 稍微增大圆角以获得更现代的外观
65+
6366
OutlinedCard(
6467
modifier = modifier
6568
.fillMaxWidth()
69+
.frostedGlass(isActive = isHovered, intensity = 0.8f, shape = cardShape)
6670
.hoverable(interactionSource),
67-
shape = RoundedCornerShape(16.dp), // 稍微增大圆角以获得更现代的外观
71+
shape = cardShape,
6872
border = BorderStroke(
6973
width = 1.dp,
7074
color = cardBorderColor

0 commit comments

Comments
 (0)