diff --git a/lib/src/shiny_widget.dart b/lib/src/shiny_widget.dart index d6e6670..7f9e9cb 100644 --- a/lib/src/shiny_widget.dart +++ b/lib/src/shiny_widget.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'shiny_controller.dart'; @@ -244,30 +243,19 @@ class Shiny extends StatefulWidget { State createState() => _ShinyState(); } -class _ShinyState extends State with TickerProviderStateMixin { +class _ShinyState extends State { static Future? _programFuture; ui.FragmentShader? _shader; StreamSubscription? _tiltSub; - late final Ticker _ticker; - - double _time = 0.0; Offset _tilt = Offset.zero; @override void initState() { super.initState(); - _ticker = createTicker((Duration elapsed) { - if (!mounted || _shader == null) return; - setState(() { - _time = elapsed.inMicroseconds / 1000000.0; - }); - }); - _attachController(); if (widget.enableShader) { _loadShader(); - _ticker.start(); } } @@ -282,9 +270,6 @@ class _ShinyState extends State with TickerProviderStateMixin { if (oldWidget.enableShader != widget.enableShader) { if (widget.enableShader) { if (_shader == null) _loadShader(); - _ticker.start(); - } else { - _ticker.stop(); } } } @@ -329,7 +314,6 @@ class _ShinyState extends State with TickerProviderStateMixin { @override void dispose() { - _ticker.dispose(); _tiltSub?.cancel(); _shader?.dispose(); super.dispose(); @@ -343,17 +327,16 @@ class _ShinyState extends State with TickerProviderStateMixin { ..setFloat(1, bounds.height) ..setFloat(2, effectiveTilt.dx) ..setFloat(3, effectiveTilt.dy) - ..setFloat(4, _time) - ..setFloat(5, widget.prismatic) - ..setFloat(6, widget.sparkle) - ..setFloat(7, widget.specular) - ..setFloat(8, widget.diffraction) - ..setFloat(9, widget.style.index.toDouble()) - ..setFloat(10, widget.sparkleShape.kind.index.toDouble()) - ..setFloat(11, widget.sparkleShape.primary) - ..setFloat(12, widget.sparkleShape.secondary) - ..setFloat(13, widget.sparkleShape.tertiary) - ..setFloat(14, widget.opacity); + ..setFloat(4, widget.prismatic) + ..setFloat(5, widget.sparkle) + ..setFloat(6, widget.specular) + ..setFloat(7, widget.diffraction) + ..setFloat(8, widget.style.index.toDouble()) + ..setFloat(9, widget.sparkleShape.kind.index.toDouble()) + ..setFloat(10, widget.sparkleShape.primary) + ..setFloat(11, widget.sparkleShape.secondary) + ..setFloat(12, widget.sparkleShape.tertiary) + ..setFloat(13, widget.opacity); return shader; } diff --git a/shaders/shiny_card.frag b/shaders/shiny_card.frag index 0ac5e45..1467852 100644 --- a/shaders/shiny_card.frag +++ b/shaders/shiny_card.frag @@ -5,7 +5,6 @@ out vec4 fragColor; uniform vec2 uSize; uniform vec2 uTilt; -uniform float uTime; uniform float uPrismatic; uniform float uSparkle; uniform float uSpecular; @@ -120,14 +119,15 @@ float sparkleShapeMask(vec2 p, vec2 id) { return sparkleRectangleMask(p, uSparklePrimary, uSparkleSecondary, randomRotation * uSparkleTertiary); } -vec3 styleHolographicSilver(vec2 uv, vec2 tilt, float time) { +vec3 styleHolographicSilver(vec2 uv, vec2 tilt) { vec2 p = uv * 2.0 - 1.0; - float flowA = noise(uv * 3.5 + vec2(time * 0.03, -time * 0.02)); - float flowB = noise(uv * 7.0 + vec2(-time * 0.02, time * 0.03)); + vec2 tiltWarp = tilt * 0.18; + float flowA = noise(uv * 3.5 + tiltWarp + vec2(dot(uv, vec2(0.25, -0.22)))); + float flowB = noise(uv * 7.0 - tiltWarp + vec2(dot(uv, vec2(-0.18, 0.21)))); vec2 warp = p + 0.25 * vec2(flowA - 0.5, flowB - 0.5); float ribbon = sin(dot(warp, vec2(6.5, 2.8)) + flowA * 5.0 + dot(tilt, vec2(2.2, -1.7))); - float plume = sin(length(warp + vec2(flowB - 0.5, flowA - 0.5)) * 9.0 - tilt.x * 3.0 + time * 0.2); - float phase = flowA * 1.8 + ribbon * 0.28 + plume * 0.22 + time * 0.03; + float plume = sin(length(warp + vec2(flowB - 0.5, flowA - 0.5)) * 9.0 - tilt.x * 3.0 + dot(uv, vec2(1.5, -1.2))); + float phase = flowA * 1.8 + ribbon * 0.28 + plume * 0.22; vec3 holo = rainbow(phase, 0.72, 1.0); float blend = smoothstep(-0.65, 0.95, ribbon + plume * 0.55); @@ -136,7 +136,7 @@ vec3 styleHolographicSilver(vec2 uv, vec2 tilt, float time) { return mix(silver, holo, 0.65 * blend); } -vec3 styleCrackedIce(vec2 uv, vec2 tilt, float time) { +vec3 styleCrackedIce(vec2 uv, vec2 tilt) { float maxZ = -999.0; vec2 bestSlope = vec2(0.0); float bestHash = 0.0; @@ -187,11 +187,11 @@ vec3 styleCrackedIce(vec2 uv, vec2 tilt, float time) { // --- VIBRANT COLOR OPTICS --- float alignment = dot(tilt, normalize(bestSlope + vec2(0.001))); - float phase = dot(uv, vec2(0.6, -0.4)) + time * 0.05 + bestHash * 0.6 + alignment * 0.5; + float phase = dot(uv, vec2(0.6, -0.4)) + bestHash * 0.6 + alignment * 0.5; float saturation = 0.7 + 0.3 * hash(vec2(bestHash, 6.1)); vec3 baseColor = rainbow(phase, saturation, 1.0); - float lightCycle = sin(alignment * 4.0 + bestHash * TWO_PI + time * 0.15); + float lightCycle = sin(alignment * 4.0 + bestHash * TWO_PI + dot(uv, vec2(2.1, -1.7))); float ambient = 0.15 + 0.25 * max(0.0, lightCycle); vec3 finalColor = baseColor * ambient; float flash = pow(max(0.0, lightCycle), 5.0); @@ -201,7 +201,7 @@ vec3 styleCrackedIce(vec2 uv, vec2 tilt, float time) { return finalColor; } -vec3 styleSilverMosaic(vec2 uv, vec2 tilt, float time) { +vec3 styleSilverMosaic(vec2 uv, vec2 tilt) { // Mosaic tile grid. vec2 g = uv * 6.0; vec2 id = floor(g); @@ -228,7 +228,7 @@ vec3 styleSilverMosaic(vec2 uv, vec2 tilt, float time) { // 2D cross product sign decides which side of the highlight receives hue shift. float side = sign(radialDir.x * lightDir.y - radialDir.y * lightDir.x); - float basePhase = length(tilt) * 2.5 + time * 0.1; + float basePhase = length(tilt) * 2.5 + dot(uv, vec2(0.9, -0.7)); float phase = basePhase + side * angleDiff * 1.8; vec3 holoColor = rainbow(phase, 0.85, 1.0); @@ -252,7 +252,7 @@ vec3 styleSilverMosaic(vec2 uv, vec2 tilt, float time) { return clamp(finalColor * border, 0.0, 1.0); } -vec3 styleSuperGoldVinyl(vec2 uv, vec2 tilt, float time) { +vec3 styleSuperGoldVinyl(vec2 uv, vec2 tilt) { vec2 g = uv * 60.0; vec2 staggered = vec2(g.x + 0.5 * mod(floor(g.y), 2.0), g.y); vec2 f = fract(staggered) - 0.5; @@ -262,7 +262,7 @@ vec3 styleSuperGoldVinyl(vec2 uv, vec2 tilt, float time) { float dotMask = smoothstep(0.1764, 0.01, d2); float emboss = smoothstep(0.2025, 0.0324, d2); - float sheen = 0.5 + 0.5 * sin(dot(uv, vec2(18.0, -6.0)) + noise(uv * 6.0) * 2.5 + dot(tilt, vec2(2.4, 1.8)) * 2.0 + time * 0.2); + float sheen = 0.5 + 0.5 * sin(dot(uv, vec2(18.0, -6.0)) + noise(uv * 6.0) * 2.5 + dot(tilt, vec2(2.4, 1.8)) * 2.0); vec3 gold = vec3(0.84, 0.70, 0.24); vec3 warm = vec3(1.0, 0.88, 0.42); vec3 shadow = vec3(0.18, 0.14, 0.05); @@ -286,15 +286,18 @@ void main() { // Highlight uses stretched UVs so glare spans the full widget area. float highlightDistance = length(uv - (center + (uTilt * 0.35))); - float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), 3.2); bool isHolographicSilver = uStyle < 0.5; bool isCrackedIce = uStyle >= 0.5 && uStyle < 1.5; bool isSilverMosaic = uStyle >= 1.5 && uStyle < 2.5; - float prismaticGain = isHolographicSilver ? 1.10 : (isCrackedIce ? 1.00 : (isSilverMosaic ? 1.30 : 1.55)); - float sparkleGain = isHolographicSilver ? 1.00 : (isCrackedIce ? 0.55 : (isSilverMosaic ? 0.80 : 0.70)); - float specularGain = isHolographicSilver ? 1.00 : (isCrackedIce ? 1.35 : (isSilverMosaic ? 1.15 : 1.50)); + float specularSharpness = isHolographicSilver ? 3.0 : (isCrackedIce ? 4.0 : (isSilverMosaic ? 3.4 : 3.6)); + float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), specularSharpness); + + float prismaticGain = isHolographicSilver ? 1.10 : (isCrackedIce ? 1.00 : (isSilverMosaic ? 1.30 : 1.18)); + float sparkleGain = isHolographicSilver ? 1.00 : (isCrackedIce ? 0.70 : (isSilverMosaic ? 0.80 : 0.70)); + float specularGain = isHolographicSilver ? 1.00 : (isCrackedIce ? 1.65 : (isSilverMosaic ? 1.15 : 1.50)); + float prismaticTintGain = isHolographicSilver ? 1.00 : (isCrackedIce ? 0.90 : (isSilverMosaic ? 1.15 : 0.65)); float sparkleGridScale = isCrackedIce ? 7.0 : (isSilverMosaic ? 14.0 : 18.0); @@ -313,7 +316,7 @@ void main() { float alignment = max(0.0, dot(tiltDir, sparkleNormal)); if (alignment > 0.0) { - float twinkle = 0.5 + 0.5 * sin(uTime * 5.5 + sparkleHash * TWO_PI); + float twinkle = 0.45 + 0.55 * sin(dot(cell, vec2(21.0, -17.0)) + dot(uTilt, vec2(5.3, -4.1)) + sparkleHash * TWO_PI); float shapeMask = 0.0; if (isCrackedIce) { @@ -336,13 +339,13 @@ void main() { vec3 styleBase; // Style bases sample aspect-corrected UVs for stable pattern geometry. if (uStyle < 0.5) { - styleBase = styleHolographicSilver(aspectUV, uTilt, uTime); + styleBase = styleHolographicSilver(aspectUV, uTilt); } else if (uStyle < 1.5) { - styleBase = styleCrackedIce(aspectUV, uTilt, uTime); + styleBase = styleCrackedIce(aspectUV, uTilt); } else if (uStyle < 2.5) { - styleBase = styleSilverMosaic(aspectUV, uTilt, uTime); + styleBase = styleSilverMosaic(aspectUV, uTilt); } else { - styleBase = styleSuperGoldVinyl(aspectUV, uTilt, uTime); + styleBase = styleSuperGoldVinyl(aspectUV, uTilt); } float styleLuma = dot(styleBase, vec3(0.299, 0.587, 0.114)); @@ -357,8 +360,8 @@ void main() { float edgeSpec = pow(1.0 - directional, 2.2); vec3 specular = vec3(specularMask) * (0.3 + 0.7 * edgeSpec) * uSpecular * (0.35 + 0.65 * tiltMag) * specularGain; - float prismaticPhase = dot(aspectUV, vec2(3.6, -2.1)) + dot(uTilt, vec2(0.9, -0.7)) + uTime * 0.04; - vec3 prismaticTint = rainbow(prismaticPhase, 0.72, 1.0) * uPrismatic * prismaticGain * (0.08 + 0.14 * edgeSpec); + float prismaticPhase = dot(aspectUV, vec2(3.6, -2.1)) + dot(uTilt, vec2(0.9, -0.7)); + vec3 prismaticTint = rainbow(prismaticPhase, 0.72, 1.0) * uPrismatic * prismaticGain * prismaticTintGain * (0.08 + 0.14 * edgeSpec); float microStrength = isCrackedIce ? 0.08 : 0.18;