From daa268f24374e8b8bc39c96e52fba5a1927c33b0 Mon Sep 17 00:00:00 2001 From: qiqi49 Date: Fri, 7 Aug 2026 19:03:35 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(ukui-search-qml):=20=E5=BF=AB=E9=97=AE?= =?UTF-8?q?=E5=BF=AB=E7=AD=94=E5=8D=A1=E7=89=87=E5=AE=9E=E7=8E=B0=E6=B8=90?= =?UTF-8?q?=E5=8F=98=E8=89=B2=E8=83=8C=E6=99=AF=E5=92=8C=E8=BE=B9=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据设计稿实现渐变色动画背景,静态渐变色边框 Task: #661791 From: kylin Severity: Moderate Change-Id: I77b6bd30296386e3b85ced050d3eac9def0c9afc --- .../org.ukui.search/ui/InstantResultCard.qml | 36 +++- .../ui/QuickSearchCardBackground.frag | 163 ++++++++++++++++++ .../ui/QuickSearchCardBorder.frag | 44 +++++ ukui-search-qml/src/qml.qrc | 3 + 4 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBackground.frag create mode 100644 ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBorder.frag diff --git a/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml b/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml index 9b7a2f94..8f21bb78 100644 --- a/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml +++ b/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml @@ -28,6 +28,7 @@ import org.ukui.quick.platform 1.0 MouseArea { id: cardMouseArea + hoverEnabled: true readonly property Search.InstantResultState instantResult: searchView.instantResultState readonly property bool isCalculation: instantResult.valid @@ -43,8 +44,41 @@ MouseArea { DtThemeBackground { anchors.fill: parent useStyleTransparency: false + alpha: 0.5 radius: GlobalTheme.kRadiusMenu - backgroundColor: GlobalTheme.BaseActive + backgroundColor: GlobalTheme.kContainGeneralNormal + } + + ShaderEffect { + id: animatedBackground + anchors.fill: parent + + property real time: 0 + property vector2d resolution: Qt.vector2d(width, height) + property real cornerRadius: GlobalTheme.kRadiusMenu + + fragmentShader: "qrc:/ukui-search/org.ukui.search/ui/QuickSearchCardBackground.frag" + + // 窗口隐藏后暂停动画,动画更新限制约为30FPS + Timer { + interval: 33 + repeat: true + running: cardMouseArea.visible && searchView.visible + onTriggered: animatedBackground.time = (animatedBackground.time + interval) % 1000000 + } + } + + ShaderEffect { + id: focusBorder + + anchors.fill: parent + visible: cardMouseArea.containsMouse || cardMouseArea.containsPress || cardMouseArea.activeFocus + + property vector2d resolution: Qt.vector2d(width, height) + property real cornerRadius: GlobalTheme.kRadiusMenu + property real borderWidth: 1.0 + + fragmentShader: "qrc:/ukui-search/org.ukui.search/ui/QuickSearchCardBorder.frag" } Loader { diff --git a/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBackground.frag b/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBackground.frag new file mode 100644 index 00000000..fe22c4be --- /dev/null +++ b/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBackground.frag @@ -0,0 +1,163 @@ +uniform lowp float qt_Opacity; +uniform highp float time; +uniform highp vec2 resolution; +uniform highp float cornerRadius; + +varying highp vec2 qt_TexCoord0; + +//设计稿相关CSS代码: +//第一层:渐变遮罩 background: linear-gradient(180deg, #ffffffb3 0%, #ffffff8f 52%, #ffffff00 100%); +//第二层:GLSL动画 透明度为100% +//第三层:遮罩层 box-shadow: inset 0px -8px 32px 0px rgba(255, 255, 255, 0.25) + +// 动画流动速度,数值越大流动越快。 +const float SPEED = 0.2; +// 有机形变强度,控制颜色区域的流体扭曲程度。 +const float DISTORTION = 1.0; +// 旋涡扭曲强度。 +const float SWIRL = 0.5; +// 参与混合的动态颜色数量。 +const int COLOR_COUNT = 3; +// 颜色团聚合度,数值越大边缘越集中。 +const float DIST_POWER = 4.8; +// 颜色边缘的颗粒扰动强度。 +const float GRAIN_MIXER = 0.4; +// 动态颜色内阴影的透明度。 +const float ANIMATION_OPACITY = 1.0; +// 对应 box-shadow 的 rgba(255, 255, 255, 0.25)。 +const float EDGE_HIGHLIGHT_OPACITY = 0.25; +// 对应 box-shadow 的 32px blur,使用距离场平滑模拟。 +const float EDGE_HIGHLIGHT_BLUR_RADIUS = 32.0; + +// 动态渐变使用的三种颜色,第四个分量为各颜色自身透明度。 +const vec4 COLOR_0 = vec4(0.1412, 0.5843, 1.0, 0.9); +const vec4 COLOR_1 = vec4(0.5560, 0.3805, 1.0, 0.7); +const vec4 COLOR_2 = vec4(0.8319, 0.8991, 1.0, 1.0); + +float hash21(vec2 position) +{ + position = fract(position * vec2(0.3183099, 0.3678794)) + 0.1; + position += dot(position, position + 19.19); + return fract(position.x * position.y); +} + +float valueNoise(vec2 position) +{ + vec2 integerPart = floor(position); + vec2 fractionalPart = fract(position); + float bottomLeft = hash21(integerPart); + float bottomRight = hash21(integerPart + vec2(1.0, 0.0)); + float topLeft = hash21(integerPart + vec2(0.0, 1.0)); + float topRight = hash21(integerPart + vec2(1.0, 1.0)); + vec2 interpolation = fractionalPart * fractionalPart * (3.0 - 2.0 * fractionalPart); + + return mix(mix(bottomLeft, bottomRight, interpolation.x), + mix(topLeft, topRight, interpolation.x), interpolation.y); +} + +vec2 rotate(vec2 position, float angle) +{ + return mat2(cos(angle), sin(angle), -sin(angle), cos(angle)) * position; +} + +vec2 getPosition(int index, float currentTime) +{ + float floatIndex = float(index); + float phase = floatIndex * 0.37; + float horizontalSpeed = 0.6 + fract(floatIndex / 3.0) * 0.9; + float verticalSpeed = 0.8 + fract((floatIndex + 1.0) / 4.0); + + return 0.5 + 0.5 * vec2(sin(currentTime * horizontalSpeed + phase), + cos(currentTime * verticalSpeed + phase * 1.5)); +} + +float roundedRectangleDistance(vec2 uv) +{ + vec2 halfSize = resolution * 0.5; + vec2 position = (uv - 0.5) * resolution; + vec2 cornerDistance = abs(position) - halfSize + vec2(cornerRadius); + + return length(max(cornerDistance, 0.0)) + + min(max(cornerDistance.x, cornerDistance.y), 0.0) + - cornerRadius; +} + +float roundedRectangleMask(vec2 uv) +{ + return 1.0 - smoothstep(-1.0, 1.0, roundedRectangleDistance(uv)); +} + +vec4 compositeOver(vec4 background, vec4 foreground) +{ + return foreground + background * (1.0 - foreground.a); +} + +void main() +{ + vec2 uv = qt_TexCoord0; + float currentTime = (time * 0.001 + 41.5) * SPEED; + vec2 grainUv = uv * 1000.0; + float mixerGrain = 0.4 * GRAIN_MIXER * (valueNoise(grainUv) - 0.5); + + float radius = smoothstep(0.0, 1.0, length(uv - 0.5)); + float center = 1.0 - radius; + + for (float index = 1.0; index <= 2.0; index++) { + uv.x += DISTORTION * center / index + * sin(currentTime + index * 0.4 * smoothstep(0.0, 1.0, uv.y)) + * cos(0.2 * currentTime + index * 2.4 * smoothstep(0.0, 1.0, uv.y)); + uv.y += DISTORTION * center / index + * cos(currentTime + index * 2.0 * smoothstep(0.0, 1.0, uv.x)); + } + + vec2 rotatedUv = rotate(uv - 0.5, -3.0 * SWIRL * radius) + 0.5; + vec3 color = vec3(0.0); + float alpha = 0.0; + float totalWeight = 0.0; + + for (int index = 0; index < COLOR_COUNT; index++) { + vec4 currentColor = COLOR_0; + if (index == 1) + currentColor = COLOR_1; + else if (index == 2) + currentColor = COLOR_2; + + vec2 colorPosition = getPosition(index, currentTime) + mixerGrain; + float distance = pow(length(rotatedUv - colorPosition), DIST_POWER); + float weight = 1.0 / (distance + 1e-3); + + color += currentColor.rgb * currentColor.a * weight; + alpha += currentColor.a * weight; + totalWeight += weight; + } + + color /= max(1e-4, totalWeight); + alpha = clamp(alpha / max(1e-4, totalWeight), 0.0, 1.0); + + // 第三层:基于 inset 0px -8px 32px 0px rgba(255, 255, 255, 0.25) + // 的强度和模糊范围,扩展为四周向内渐隐的白色内阴影。 + // 距离场在圆角矩形内为负值,取反后得到到最近边缘的内部距离。 + float distanceFromEdge = max(0.0, -roundedRectangleDistance(qt_TexCoord0)); + float edgeHighlightMask = 1.0 - smoothstep(0.0, + EDGE_HIGHLIGHT_BLUR_RADIUS, distanceFromEdge); + float edgeHighlightOpacity = EDGE_HIGHLIGHT_OPACITY * edgeHighlightMask; + vec4 result = vec4(vec3(edgeHighlightOpacity), edgeHighlightOpacity); + + // 第二层:与第三层使用相同范围的动态颜色内阴影。 + vec3 dynamicColor = color / max(1e-4, alpha); + float dynamicEdgeOpacity = alpha * edgeHighlightOpacity * ANIMATION_OPACITY; + result = compositeOver(result, vec4(dynamicColor * dynamicEdgeOpacity, dynamicEdgeOpacity)); + + // 第一层:对应 linear-gradient(180deg, #ffffffb3 0%, #ffffff8f 52%, #ffffff00 100%)。 + float gradientOpacity; + if (qt_TexCoord0.y <= 0.52) { + gradientOpacity = mix(0.702, 0.561, + qt_TexCoord0.y / 0.52); + } else { + gradientOpacity = mix(0.561, 0.0, + (qt_TexCoord0.y - 0.52) / 0.48); + } + result = compositeOver(result, vec4(vec3(gradientOpacity), gradientOpacity)); + + gl_FragColor = result * roundedRectangleMask(qt_TexCoord0) * qt_Opacity; +} diff --git a/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBorder.frag b/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBorder.frag new file mode 100644 index 00000000..25be326f --- /dev/null +++ b/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBorder.frag @@ -0,0 +1,44 @@ +uniform lowp float qt_Opacity; +uniform highp vec2 resolution; +uniform highp float cornerRadius; +uniform highp float borderWidth; + +varying highp vec2 qt_TexCoord0; +// 边框渐变参数:border: 1px solid; +// border-image: linear-gradient(270deg, #0036f5 0%, #4e75ff 34%, #8a41ff 71%, #e888f8 100%) 1; +float roundedRectangleDistance(vec2 uv) +{ + vec2 halfSize = resolution * 0.5; + vec2 position = (uv - 0.5) * resolution; + vec2 cornerDistance = abs(position) - halfSize + vec2(cornerRadius); + + return length(max(cornerDistance, 0.0)) + + min(max(cornerDistance.x, cornerDistance.y), 0.0) + - cornerRadius; +} + +vec3 borderColor(float position) +{ + const vec3 firstColor = vec3(0.0, 0.2118, 0.9608); + const vec3 secondColor = vec3(0.3059, 0.4588, 1.0); + const vec3 thirdColor = vec3(0.5412, 0.2549, 1.0); + const vec3 fourthColor = vec3(0.9098, 0.5333, 0.9725); + + if (position <= 0.34) + return mix(firstColor, secondColor, position / 0.34); + if (position <= 0.71) + return mix(secondColor, thirdColor, (position - 0.34) / 0.37); + return mix(thirdColor, fourthColor, (position - 0.71) / 0.29); +} + +void main() +{ + float distance = roundedRectangleDistance(qt_TexCoord0); + float outerCoverage = 1.0 - smoothstep(-0.5, 0.5, distance); + float innerCoverage = smoothstep(-borderWidth - 0.5, + -borderWidth + 0.5, distance); + float alpha = outerCoverage * innerCoverage; + vec3 color = borderColor(1.0 - qt_TexCoord0.x); + + gl_FragColor = vec4(color * alpha, alpha) * qt_Opacity; +} diff --git a/ukui-search-qml/src/qml.qrc b/ukui-search-qml/src/qml.qrc index 64c17d21..b2421405 100644 --- a/ukui-search-qml/src/qml.qrc +++ b/ukui-search-qml/src/qml.qrc @@ -17,5 +17,8 @@ org.ukui.search/ui/ResultItem.qml org.ukui.search/ui/ResultSortFilterButton.qml org.ukui.search/ui/InstantResultCard.qml + + org.ukui.search/ui/QuickSearchCardBackground.frag + org.ukui.search/ui/QuickSearchCardBorder.frag -- Gitee From aa980d54a637fe4076ea50a142a038d976a8d89d Mon Sep 17 00:00:00 2001 From: qiqi49 Date: Thu, 13 Aug 2026 10:41:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(ukui-search-qml):=20=E5=BF=AB=E9=97=AE?= =?UTF-8?q?=E5=BF=AB=E7=AD=94=E6=B8=90=E5=8F=98=E8=83=8C=E6=99=AFshader?= =?UTF-8?q?=E5=85=BC=E5=AE=B9qt6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为快问快答卡片的动态背景和渐变边框新增 Qt6 着色器,生成 QSB 资源。 Task: #661791 From: kylin Severity: Moderate Change-Id: Ia49f56d9e6f5fb74bfa49ae2f56e533be2ea655d --- ukui-search-qml/CMakeLists.txt | 8 + .../org.ukui.search/ui/InstantResultCard.qml | 8 +- ukui-search-qml/src/qml.qrc | 3 - .../QuickSearchCardBackground.frag | 0 .../ui => shaders}/QuickSearchCardBorder.frag | 0 ukui-search-qml/src/shaders/shaders5.qrc | 6 + ukui-search-qml/src/shaders6/CMakeLists.txt | 13 ++ .../shaders6/QuickSearchCardBackground.frag | 169 ++++++++++++++++++ .../shaders6/QuickSearchCardBackground.vert | 19 ++ .../src/shaders6/QuickSearchCardBorder.frag | 50 ++++++ .../src/shaders6/QuickSearchCardBorder.vert | 19 ++ ukui-search-qml/src/ukui-search-view.cpp | 1 + 12 files changed, 291 insertions(+), 5 deletions(-) rename ukui-search-qml/src/{org.ukui.search/ui => shaders}/QuickSearchCardBackground.frag (100%) rename ukui-search-qml/src/{org.ukui.search/ui => shaders}/QuickSearchCardBorder.frag (100%) create mode 100644 ukui-search-qml/src/shaders/shaders5.qrc create mode 100644 ukui-search-qml/src/shaders6/CMakeLists.txt create mode 100644 ukui-search-qml/src/shaders6/QuickSearchCardBackground.frag create mode 100644 ukui-search-qml/src/shaders6/QuickSearchCardBackground.vert create mode 100644 ukui-search-qml/src/shaders6/QuickSearchCardBorder.frag create mode 100644 ukui-search-qml/src/shaders6/QuickSearchCardBorder.vert diff --git a/ukui-search-qml/CMakeLists.txt b/ukui-search-qml/CMakeLists.txt index 11f87eaa..b436be3b 100644 --- a/ukui-search-qml/CMakeLists.txt +++ b/ukui-search-qml/CMakeLists.txt @@ -31,6 +31,9 @@ find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Quick Widgets LinguistTools DBus REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Quick Widgets LinguistTools DBus REQUIRED) +if (QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 REQUIRED COMPONENTS ShaderTools) +endif() find_package(PkgConfig REQUIRED) set(GSETTINGS_MODULE gsettings-qt) @@ -121,12 +124,17 @@ else() endif() set(QRC_FILES src/qml.qrc) +if (QT_VERSION_MAJOR EQUAL 5) + list(APPEND QRC_FILES src/shaders/shaders5.qrc) +endif() add_executable( ${PROJECT_NAME} ${SOURCE_FILES} ${QRC_FILES} ) +add_subdirectory(src/shaders6) + set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ukui-search diff --git a/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml b/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml index 8f21bb78..fc11e6a2 100644 --- a/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml +++ b/ukui-search-qml/src/org.ukui.search/ui/InstantResultCard.qml @@ -35,6 +35,8 @@ MouseArea { && instantResult.contentType === Search.InstantResultState.Calculation readonly property Search.CalculationCardContent calculation: isCalculation ? instantResult.calculationContent : null + readonly property bool useQsb: qtVersionMajor >= 6 + readonly property string shaderResource: "qrc:/ukui-search/" visible: isCalculation // Do not use childrenRect here: the background fills this item and would @@ -57,7 +59,8 @@ MouseArea { property vector2d resolution: Qt.vector2d(width, height) property real cornerRadius: GlobalTheme.kRadiusMenu - fragmentShader: "qrc:/ukui-search/org.ukui.search/ui/QuickSearchCardBackground.frag" + vertexShader: cardMouseArea.useQsb ? cardMouseArea.shaderResource + "QuickSearchCardBackground.vert.qsb" : "" + fragmentShader: cardMouseArea.shaderResource + "QuickSearchCardBackground.frag" + (cardMouseArea.useQsb ? ".qsb" : "") // 窗口隐藏后暂停动画,动画更新限制约为30FPS Timer { @@ -78,7 +81,8 @@ MouseArea { property real cornerRadius: GlobalTheme.kRadiusMenu property real borderWidth: 1.0 - fragmentShader: "qrc:/ukui-search/org.ukui.search/ui/QuickSearchCardBorder.frag" + vertexShader: cardMouseArea.useQsb ? cardMouseArea.shaderResource + "QuickSearchCardBorder.vert.qsb" : "" + fragmentShader: cardMouseArea.shaderResource + "QuickSearchCardBorder.frag" + (cardMouseArea.useQsb ? ".qsb" : "") } Loader { diff --git a/ukui-search-qml/src/qml.qrc b/ukui-search-qml/src/qml.qrc index b2421405..64c17d21 100644 --- a/ukui-search-qml/src/qml.qrc +++ b/ukui-search-qml/src/qml.qrc @@ -17,8 +17,5 @@ org.ukui.search/ui/ResultItem.qml org.ukui.search/ui/ResultSortFilterButton.qml org.ukui.search/ui/InstantResultCard.qml - - org.ukui.search/ui/QuickSearchCardBackground.frag - org.ukui.search/ui/QuickSearchCardBorder.frag diff --git a/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBackground.frag b/ukui-search-qml/src/shaders/QuickSearchCardBackground.frag similarity index 100% rename from ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBackground.frag rename to ukui-search-qml/src/shaders/QuickSearchCardBackground.frag diff --git a/ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBorder.frag b/ukui-search-qml/src/shaders/QuickSearchCardBorder.frag similarity index 100% rename from ukui-search-qml/src/org.ukui.search/ui/QuickSearchCardBorder.frag rename to ukui-search-qml/src/shaders/QuickSearchCardBorder.frag diff --git a/ukui-search-qml/src/shaders/shaders5.qrc b/ukui-search-qml/src/shaders/shaders5.qrc new file mode 100644 index 00000000..945505d2 --- /dev/null +++ b/ukui-search-qml/src/shaders/shaders5.qrc @@ -0,0 +1,6 @@ + + + QuickSearchCardBackground.frag + QuickSearchCardBorder.frag + + diff --git a/ukui-search-qml/src/shaders6/CMakeLists.txt b/ukui-search-qml/src/shaders6/CMakeLists.txt new file mode 100644 index 00000000..0f49afac --- /dev/null +++ b/ukui-search-qml/src/shaders6/CMakeLists.txt @@ -0,0 +1,13 @@ +if (QT_VERSION_MAJOR EQUAL 6) + qt6_add_shaders(${PROJECT_NAME} search_shaders + BATCHABLE + PRECOMPILE + OPTIMIZED + PREFIX "/ukui-search/" + FILES + QuickSearchCardBackground.vert + QuickSearchCardBackground.frag + QuickSearchCardBorder.vert + QuickSearchCardBorder.frag + ) +endif() diff --git a/ukui-search-qml/src/shaders6/QuickSearchCardBackground.frag b/ukui-search-qml/src/shaders6/QuickSearchCardBackground.frag new file mode 100644 index 00000000..f83558bb --- /dev/null +++ b/ukui-search-qml/src/shaders6/QuickSearchCardBackground.frag @@ -0,0 +1,169 @@ +#version 440 + +layout(location = 0) in highp vec2 qt_TexCoord0; +layout(location = 0) out lowp vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + highp mat4 qt_Matrix; + lowp float qt_Opacity; + highp float time; + highp vec2 resolution; + highp float cornerRadius; +}; + +//设计稿相关CSS代码: +//第一层:渐变遮罩 background: linear-gradient(180deg, #ffffffb3 0%, #ffffff8f 52%, #ffffff00 100%); +//第二层:GLSL动画 透明度为100% +//第三层:遮罩层 box-shadow: inset 0px -8px 32px 0px rgba(255, 255, 255, 0.25) + +// 动画流动速度,数值越大流动越快。 +const float SPEED = 0.2; +// 有机形变强度,控制颜色区域的流体扭曲程度。 +const float DISTORTION = 1.0; +// 旋涡扭曲强度。 +const float SWIRL = 0.5; +// 参与混合的动态颜色数量。 +const int COLOR_COUNT = 3; +// 颜色团聚合度,数值越大边缘越集中。 +const float DIST_POWER = 4.8; +// 颜色边缘的颗粒扰动强度。 +const float GRAIN_MIXER = 0.4; +// 动态颜色内阴影的透明度。 +const float ANIMATION_OPACITY = 1.0; +// 对应 box-shadow 的 rgba(255, 255, 255, 0.25)。 +const float EDGE_HIGHLIGHT_OPACITY = 0.25; +// 对应 box-shadow 的 32px blur,使用距离场平滑模拟。 +const float EDGE_HIGHLIGHT_BLUR_RADIUS = 32.0; + +// 动态渐变使用的三种颜色,第四个分量为各颜色自身透明度。 +const vec4 COLOR_0 = vec4(0.1412, 0.5843, 1.0, 0.9); +const vec4 COLOR_1 = vec4(0.5560, 0.3805, 1.0, 0.7); +const vec4 COLOR_2 = vec4(0.8319, 0.8991, 1.0, 1.0); + +float hash21(vec2 position) +{ + position = fract(position * vec2(0.3183099, 0.3678794)) + 0.1; + position += dot(position, position + 19.19); + return fract(position.x * position.y); +} + +float valueNoise(vec2 position) +{ + vec2 integerPart = floor(position); + vec2 fractionalPart = fract(position); + float bottomLeft = hash21(integerPart); + float bottomRight = hash21(integerPart + vec2(1.0, 0.0)); + float topLeft = hash21(integerPart + vec2(0.0, 1.0)); + float topRight = hash21(integerPart + vec2(1.0, 1.0)); + vec2 interpolation = fractionalPart * fractionalPart * (3.0 - 2.0 * fractionalPart); + + return mix(mix(bottomLeft, bottomRight, interpolation.x), + mix(topLeft, topRight, interpolation.x), interpolation.y); +} + +vec2 rotate(vec2 position, float angle) +{ + return mat2(cos(angle), sin(angle), -sin(angle), cos(angle)) * position; +} + +vec2 getPosition(int index, float currentTime) +{ + float floatIndex = float(index); + float phase = floatIndex * 0.37; + float horizontalSpeed = 0.6 + fract(floatIndex / 3.0) * 0.9; + float verticalSpeed = 0.8 + fract((floatIndex + 1.0) / 4.0); + + return 0.5 + 0.5 * vec2(sin(currentTime * horizontalSpeed + phase), + cos(currentTime * verticalSpeed + phase * 1.5)); +} + +float roundedRectangleDistance(vec2 uv) +{ + vec2 halfSize = resolution * 0.5; + vec2 position = (uv - 0.5) * resolution; + vec2 cornerDistance = abs(position) - halfSize + vec2(cornerRadius); + + return length(max(cornerDistance, 0.0)) + + min(max(cornerDistance.x, cornerDistance.y), 0.0) + - cornerRadius; +} + +float roundedRectangleMask(vec2 uv) +{ + return 1.0 - smoothstep(-1.0, 1.0, roundedRectangleDistance(uv)); +} + +vec4 compositeOver(vec4 background, vec4 foreground) +{ + return foreground + background * (1.0 - foreground.a); +} + +void main() +{ + vec2 uv = qt_TexCoord0; + float currentTime = (time * 0.001 + 41.5) * SPEED; + vec2 grainUv = uv * 1000.0; + float mixerGrain = 0.4 * GRAIN_MIXER * (valueNoise(grainUv) - 0.5); + + float radius = smoothstep(0.0, 1.0, length(uv - 0.5)); + float center = 1.0 - radius; + + for (float index = 1.0; index <= 2.0; index++) { + uv.x += DISTORTION * center / index + * sin(currentTime + index * 0.4 * smoothstep(0.0, 1.0, uv.y)) + * cos(0.2 * currentTime + index * 2.4 * smoothstep(0.0, 1.0, uv.y)); + uv.y += DISTORTION * center / index + * cos(currentTime + index * 2.0 * smoothstep(0.0, 1.0, uv.x)); + } + + vec2 rotatedUv = rotate(uv - 0.5, -3.0 * SWIRL * radius) + 0.5; + vec3 color = vec3(0.0); + float alpha = 0.0; + float totalWeight = 0.0; + + for (int index = 0; index < COLOR_COUNT; index++) { + vec4 currentColor = COLOR_0; + if (index == 1) + currentColor = COLOR_1; + else if (index == 2) + currentColor = COLOR_2; + + vec2 colorPosition = getPosition(index, currentTime) + mixerGrain; + float distance = pow(length(rotatedUv - colorPosition), DIST_POWER); + float weight = 1.0 / (distance + 1e-3); + + color += currentColor.rgb * currentColor.a * weight; + alpha += currentColor.a * weight; + totalWeight += weight; + } + + color /= max(1e-4, totalWeight); + alpha = clamp(alpha / max(1e-4, totalWeight), 0.0, 1.0); + + // 第三层:基于 inset 0px -8px 32px 0px rgba(255, 255, 255, 0.25) + // 的强度和模糊范围,扩展为四周向内渐隐的白色内阴影。 + // 距离场在圆角矩形内为负值,取反后得到到最近边缘的内部距离。 + float distanceFromEdge = max(0.0, -roundedRectangleDistance(qt_TexCoord0)); + float edgeHighlightMask = 1.0 - smoothstep(0.0, + EDGE_HIGHLIGHT_BLUR_RADIUS, distanceFromEdge); + float edgeHighlightOpacity = EDGE_HIGHLIGHT_OPACITY * edgeHighlightMask; + vec4 result = vec4(vec3(edgeHighlightOpacity), edgeHighlightOpacity); + + // 第二层:与第三层使用相同范围的动态颜色内阴影。 + vec3 dynamicColor = color / max(1e-4, alpha); + float dynamicEdgeOpacity = alpha * edgeHighlightOpacity * ANIMATION_OPACITY; + result = compositeOver(result, vec4(dynamicColor * dynamicEdgeOpacity, dynamicEdgeOpacity)); + + // 第一层:对应 linear-gradient(180deg, #ffffffb3 0%, #ffffff8f 52%, #ffffff00 100%)。 + float gradientOpacity; + if (qt_TexCoord0.y <= 0.52) { + gradientOpacity = mix(0.702, 0.561, + qt_TexCoord0.y / 0.52); + } else { + gradientOpacity = mix(0.561, 0.0, + (qt_TexCoord0.y - 0.52) / 0.48); + } + result = compositeOver(result, vec4(vec3(gradientOpacity), gradientOpacity)); + + fragColor = result * roundedRectangleMask(qt_TexCoord0) * qt_Opacity; +} diff --git a/ukui-search-qml/src/shaders6/QuickSearchCardBackground.vert b/ukui-search-qml/src/shaders6/QuickSearchCardBackground.vert new file mode 100644 index 00000000..536b7a20 --- /dev/null +++ b/ukui-search-qml/src/shaders6/QuickSearchCardBackground.vert @@ -0,0 +1,19 @@ +#version 440 + +layout(location = 0) in highp vec4 qt_Vertex; +layout(location = 1) in highp vec2 qt_MultiTexCoord0; +layout(location = 0) out highp vec2 qt_TexCoord0; + +layout(std140, binding = 0) uniform buf { + highp mat4 qt_Matrix; + lowp float qt_Opacity; + highp float time; + highp vec2 resolution; + highp float cornerRadius; +}; + +void main() +{ + qt_TexCoord0 = qt_MultiTexCoord0; + gl_Position = qt_Matrix * qt_Vertex; +} diff --git a/ukui-search-qml/src/shaders6/QuickSearchCardBorder.frag b/ukui-search-qml/src/shaders6/QuickSearchCardBorder.frag new file mode 100644 index 00000000..809664ae --- /dev/null +++ b/ukui-search-qml/src/shaders6/QuickSearchCardBorder.frag @@ -0,0 +1,50 @@ +#version 440 + +layout(location = 0) in highp vec2 qt_TexCoord0; +layout(location = 0) out lowp vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + highp mat4 qt_Matrix; + lowp float qt_Opacity; + highp vec2 resolution; + highp float cornerRadius; + highp float borderWidth; +}; +// 边框渐变参数:border: 1px solid; +// border-image: linear-gradient(270deg, #0036f5 0%, #4e75ff 34%, #8a41ff 71%, #e888f8 100%) 1; +float roundedRectangleDistance(vec2 uv) +{ + vec2 halfSize = resolution * 0.5; + vec2 position = (uv - 0.5) * resolution; + vec2 cornerDistance = abs(position) - halfSize + vec2(cornerRadius); + + return length(max(cornerDistance, 0.0)) + + min(max(cornerDistance.x, cornerDistance.y), 0.0) + - cornerRadius; +} + +vec3 borderColor(float position) +{ + const vec3 firstColor = vec3(0.0, 0.2118, 0.9608); + const vec3 secondColor = vec3(0.3059, 0.4588, 1.0); + const vec3 thirdColor = vec3(0.5412, 0.2549, 1.0); + const vec3 fourthColor = vec3(0.9098, 0.5333, 0.9725); + + if (position <= 0.34) + return mix(firstColor, secondColor, position / 0.34); + if (position <= 0.71) + return mix(secondColor, thirdColor, (position - 0.34) / 0.37); + return mix(thirdColor, fourthColor, (position - 0.71) / 0.29); +} + +void main() +{ + float distance = roundedRectangleDistance(qt_TexCoord0); + float outerCoverage = 1.0 - smoothstep(-0.5, 0.5, distance); + float innerCoverage = smoothstep(-borderWidth - 0.5, + -borderWidth + 0.5, distance); + float alpha = outerCoverage * innerCoverage; + vec3 color = borderColor(1.0 - qt_TexCoord0.x); + + fragColor = vec4(color * alpha, alpha) * qt_Opacity; +} diff --git a/ukui-search-qml/src/shaders6/QuickSearchCardBorder.vert b/ukui-search-qml/src/shaders6/QuickSearchCardBorder.vert new file mode 100644 index 00000000..dd17fc91 --- /dev/null +++ b/ukui-search-qml/src/shaders6/QuickSearchCardBorder.vert @@ -0,0 +1,19 @@ +#version 440 + +layout(location = 0) in highp vec4 qt_Vertex; +layout(location = 1) in highp vec2 qt_MultiTexCoord0; +layout(location = 0) out highp vec2 qt_TexCoord0; + +layout(std140, binding = 0) uniform buf { + highp mat4 qt_Matrix; + lowp float qt_Opacity; + highp vec2 resolution; + highp float cornerRadius; + highp float borderWidth; +}; + +void main() +{ + qt_TexCoord0 = qt_MultiTexCoord0; + gl_Position = qt_Matrix * qt_Vertex; +} diff --git a/ukui-search-qml/src/ukui-search-view.cpp b/ukui-search-qml/src/ukui-search-view.cpp index d79c4d5f..3a6a05d0 100644 --- a/ukui-search-qml/src/ukui-search-view.cpp +++ b/ukui-search-qml/src/ukui-search-view.cpp @@ -118,6 +118,7 @@ UkuiSearchView::UkuiSearchView(QObject *parent) initIndexAskTimers(); rootContext()->setContextProperty("searchView", this); + rootContext()->setContextProperty("qtVersionMajor", QT_VERSION_MAJOR); const QString defaultViewId = QStringLiteral("org.ukui.search"); // 添加search包所在的qrc路径 UkuiQuick::WidgetContainer::widgetLoader().addWidgetSearchPath(QStringLiteral(":/ukui-search")); -- Gitee