diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3374e0..dd7ae6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,3 +6,4 @@
- Added `Shiny` widget with shader-driven holographic effects.
- Added optional `ShinyController` and `SensorTiltController` motion APIs.
- Added package example app and baseline tests.
+- Replaced the monolithic shader with specialized profile shaders and a profile-based API.
diff --git a/README.md b/README.md
index 870dcb3..b47f855 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,7 @@ It exposes two widget layers:
- Position-driven rendering: visuals respond to tilt/input position with no internal time animation
- Generic wrapper API for any widget via `Shiny(child: ...)`
- Card API with `background`, `foreground`, `shape`, and drag tilt via `ShinyCard`
-- Built-in sparkle presets including `none` (default), 8-point star, 5-point star, rectangle, diamond, hexagon, random polygon, and confetti
-- Custom sparkle shapes via parameterized `SparkleShapeSpec` factories
+- Four specialized material profiles backed by dedicated shader assets
- Global shader opacity control via `opacity`
- Cross-platform Flutter support (mobile, web, desktop)
@@ -62,7 +61,7 @@ ShinyCard(
controller: controller,
background: Container(color: const Color(0xFF1B2D4B)),
foreground: const Center(child: Text('HOLO')),
- sparkleShape: SparkleShapeSpec.none,
+ profile: ShinyProfile.crackedIce,
opacity: 1.0,
)
```
@@ -78,37 +77,17 @@ Shiny(
)
```
-## Sparkle Shapes
+## Profiles
-Use built-in sparkle presets:
+Choose a precompiled material profile:
```dart
ShinyCard(
- sparkleShape: SparkleShapeSpec.none,
+ profile: ShinyProfile.holographicSilver,
)
ShinyCard(
- sparkleShape: SparkleShapeSpec.hexagon,
-)
-```
-
-Or create your own parameterized shapes:
-
-```dart
-ShinyCard(
- sparkleShape: SparkleShapeSpec.customStar(
- points: 7,
- innerRatio: 0.36,
- ),
-)
-
-Shiny(
- sparkleShape: SparkleShapeSpec.customPolygon(
- sides: 8,
- aspectRatio: 1.2,
- rotation: 0.2,
- ),
- child: const SizedBox(width: 240, height: 120),
+ profile: ShinyProfile.superGoldVinyl,
)
```
@@ -123,13 +102,13 @@ final ShinyController controller = ShinyController(tiltStream: input.stream);
- `Shiny`: generic effect wrapper
- `ShinyCard`: shape + rotation + composition convenience widget
-- `SparkleShapeSpec`: built-in and custom sparkle silhouette configuration
+- `ShinyProfile`: specialized material presets mapped to dedicated shader assets
- `ShinyController`: optional source selection for tilt
- `SensorTiltController`: low-level sensor fusion stream utility
Important defaults:
-- `sparkleShape` defaults to `SparkleShapeSpec.none` (no sparkles)
+- `profile` defaults to `ShinyProfile.crackedIce`
- `opacity` defaults to `1.0`
## Platform Notes
@@ -147,8 +126,7 @@ See the package example app in `example/` for:
- Sensor-driven motion
- External stream override
- Custom card shape/background/foreground composition
-- Built-in sparkle shape toggles in the demo UI
-- User-defined sparkle shape presets using `SparkleShapeSpec`
+- Profile switching in the demo UI
## Publishing Notes
diff --git a/android/.gitignore b/android/.gitignore
deleted file mode 100644
index be3943c..0000000
--- a/android/.gitignore
+++ /dev/null
@@ -1,14 +0,0 @@
-gradle-wrapper.jar
-/.gradle
-/captures/
-/gradlew
-/gradlew.bat
-/local.properties
-GeneratedPluginRegistrant.java
-.cxx/
-
-# Remember to never publicly share your keystore.
-# See https://flutter.dev/to/reference-keystore
-key.properties
-**/*.keystore
-**/*.jks
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
deleted file mode 100644
index 733b056..0000000
--- a/android/app/build.gradle.kts
+++ /dev/null
@@ -1,44 +0,0 @@
-plugins {
- id("com.android.application")
- id("kotlin-android")
- // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
- id("dev.flutter.flutter-gradle-plugin")
-}
-
-android {
- namespace = "com.example.holo_shiny"
- compileSdk = flutter.compileSdkVersion
- ndkVersion = flutter.ndkVersion
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
- }
-
- defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId = "com.example.holo_shiny"
- // You can update the following values to match your application needs.
- // For more information, see: https://flutter.dev/to/review-gradle-config.
- minSdk = flutter.minSdkVersion
- targetSdk = flutter.targetSdkVersion
- versionCode = flutter.versionCode
- versionName = flutter.versionName
- }
-
- buildTypes {
- release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig = signingConfigs.getByName("debug")
- }
- }
-}
-
-flutter {
- source = "../.."
-}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index 399f698..0000000
--- a/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 9b6acd9..0000000
--- a/android/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/kotlin/com/example/holo_shiny/MainActivity.kt b/android/app/src/main/kotlin/com/example/holo_shiny/MainActivity.kt
deleted file mode 100644
index e58140d..0000000
--- a/android/app/src/main/kotlin/com/example/holo_shiny/MainActivity.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.example.holo_shiny
-
-import io.flutter.embedding.android.FlutterActivity
-
-class MainActivity : FlutterActivity()
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
deleted file mode 100644
index f74085f..0000000
--- a/android/app/src/main/res/drawable-v21/launch_background.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
deleted file mode 100644
index 304732f..0000000
--- a/android/app/src/main/res/drawable/launch_background.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index db77bb4..0000000
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 17987b7..0000000
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 09d4391..0000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index d5f1c8d..0000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 4d6372e..0000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
deleted file mode 100644
index 06952be..0000000
--- a/android/app/src/main/res/values-night/styles.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
deleted file mode 100644
index cb1ef88..0000000
--- a/android/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
deleted file mode 100644
index 399f698..0000000
--- a/android/app/src/profile/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
deleted file mode 100644
index dbee657..0000000
--- a/android/build.gradle.kts
+++ /dev/null
@@ -1,24 +0,0 @@
-allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-val newBuildDir: Directory =
- rootProject.layout.buildDirectory
- .dir("../../build")
- .get()
-rootProject.layout.buildDirectory.value(newBuildDir)
-
-subprojects {
- val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
- project.layout.buildDirectory.value(newSubprojectBuildDir)
-}
-subprojects {
- project.evaluationDependsOn(":app")
-}
-
-tasks.register("clean") {
- delete(rootProject.layout.buildDirectory)
-}
diff --git a/android/gradle.properties b/android/gradle.properties
deleted file mode 100644
index fbee1d8..0000000
--- a/android/gradle.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
-android.useAndroidX=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index e4ef43f..0000000
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
deleted file mode 100644
index ca7fe06..0000000
--- a/android/settings.gradle.kts
+++ /dev/null
@@ -1,26 +0,0 @@
-pluginManagement {
- val flutterSdkPath =
- run {
- val properties = java.util.Properties()
- file("local.properties").inputStream().use { properties.load(it) }
- val flutterSdkPath = properties.getProperty("flutter.sdk")
- require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
- flutterSdkPath
- }
-
- includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
-
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-plugins {
- id("dev.flutter.flutter-plugin-loader") version "1.0.0"
- id("com.android.application") version "8.11.1" apply false
- id("org.jetbrains.kotlin.android") version "2.2.20" apply false
-}
-
-include(":app")
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 2b5eaf5..68efab8 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -30,13 +30,8 @@ class ExampleHome extends StatefulWidget {
}
class _ExampleHomeState extends State {
- /// Sensor-driven controller used by the first two demo surfaces.
late final ShinyController _sensorController;
-
- /// Manual stream used by tilt preset buttons.
late final StreamController _externalTiltController;
-
- /// Controller backed by [_externalTiltController].
late final ShinyController _overrideController;
double _prismatic = 0.8;
@@ -44,20 +39,7 @@ class _ExampleHomeState extends State {
double _specular = 0.8;
double _diffraction = 0.8;
double _opacity = 1.0;
- HolographStyle _style = HolographStyle.crackedIce;
- SparkleShapeSpec _sparkleShape = SparkleShapeSpec.none;
-
- static const Map _sparkleChoices =
- {
- 'None': SparkleShapeSpec.none,
- '8-Point Star': SparkleShapeSpec.eightPointStar,
- '5-Point Star': SparkleShapeSpec.fivePointStar,
- 'Rectangle': SparkleShapeSpec.rectangle,
- 'Diamond': SparkleShapeSpec.diamond,
- 'Hexagon': SparkleShapeSpec.hexagon,
- 'Random Polygon': SparkleShapeSpec.randomPolygon,
- 'Confetti': SparkleShapeSpec.confetti,
- };
+ ShinyProfile _profile = ShinyProfile.crackedIce;
@override
void initState() {
@@ -95,13 +77,12 @@ class _ExampleHomeState extends State {
height: 120,
child: Shiny(
controller: _sensorController,
- style: _style,
+ profile: _profile,
prismatic: _prismatic,
sparkle: _sparkle,
specular: _specular,
diffraction: _diffraction,
opacity: _opacity,
- sparkleShape: _sparkleShape,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
@@ -130,8 +111,7 @@ class _ExampleHomeState extends State {
Center(
child: ShinyCard(
controller: _sensorController,
- style: _style,
- sparkleShape: _sparkleShape,
+ profile: _profile,
background: const _DemoCardBackground(),
foreground: const _RareBadge(),
prismatic: _prismatic,
@@ -143,20 +123,10 @@ class _ExampleHomeState extends State {
),
const SizedBox(height: 16),
_StylePicker(
- selectedStyle: _style,
- onChanged: (HolographStyle style) {
+ selectedProfile: _profile,
+ onChanged: (ShinyProfile profile) {
setState(() {
- _style = style;
- });
- },
- ),
- const SizedBox(height: 8),
- _SparkleShapePicker(
- selectedShape: _sparkleShape,
- choices: _sparkleChoices,
- onChanged: (SparkleShapeSpec shape) {
- setState(() {
- _sparkleShape = shape;
+ _profile = profile;
});
},
),
@@ -198,8 +168,7 @@ class _ExampleHomeState extends State {
Center(
child: ShinyCard(
controller: _overrideController,
- style: _style,
- sparkleShape: _sparkleShape,
+ profile: _profile,
prismatic: _prismatic,
sparkle: _sparkle,
specular: _specular,
@@ -258,31 +227,30 @@ class _LabeledSlider extends StatelessWidget {
}
class _StylePicker extends StatelessWidget {
- /// Creates the style selection control.
const _StylePicker({
- required this.selectedStyle,
+ required this.selectedProfile,
required this.onChanged,
});
- final HolographStyle selectedStyle;
- final ValueChanged onChanged;
+ final ShinyProfile selectedProfile;
+ final ValueChanged onChanged;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- const Text('Style'),
+ const Text('Profile'),
const SizedBox(height: 6),
- SegmentedButton(
- segments: HolographStyle.values
- .map((HolographStyle style) => ButtonSegment(
- value: style,
- label: Text(_styleLabel(style)),
+ SegmentedButton(
+ segments: ShinyProfile.values
+ .map((ShinyProfile profile) => ButtonSegment(
+ value: profile,
+ label: Text(_profileLabel(profile)),
))
.toList(),
- selected: {selectedStyle},
- onSelectionChanged: (Set value) {
+ selected: {selectedProfile},
+ onSelectionChanged: (Set value) {
if (value.isNotEmpty) {
onChanged(value.first);
}
@@ -294,61 +262,20 @@ class _StylePicker extends StatelessWidget {
);
}
- String _styleLabel(HolographStyle style) {
- switch (style) {
- case HolographStyle.holographicSilver:
+ String _profileLabel(ShinyProfile profile) {
+ switch (profile) {
+ case ShinyProfile.holographicSilver:
return 'Holographic Silver';
- case HolographStyle.crackedIce:
+ case ShinyProfile.crackedIce:
return 'Cracked Ice';
- case HolographStyle.silverMosaic:
+ case ShinyProfile.silverMosaic:
return 'Silver Mosaic';
- case HolographStyle.superGoldVinyl:
+ case ShinyProfile.superGoldVinyl:
return 'Super Gold Vinyl';
}
}
}
-class _SparkleShapePicker extends StatelessWidget {
- /// Creates sparkle shape chips from the provided shape map.
- const _SparkleShapePicker({
- required this.selectedShape,
- required this.choices,
- required this.onChanged,
- });
-
- final SparkleShapeSpec selectedShape;
- final Map choices;
- final ValueChanged onChanged;
-
- @override
- Widget build(BuildContext context) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Text('Sparkle Shape'),
- const SizedBox(height: 6),
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children:
- choices.entries.map((MapEntry entry) {
- return ChoiceChip(
- label: Text(entry.key),
- selected: selectedShape == entry.value,
- onSelected: (bool selected) {
- if (!selected) {
- return;
- }
- onChanged(entry.value);
- },
- );
- }).toList(),
- ),
- ],
- );
- }
-}
-
/// Circular D-pad control for feeding the external tilt stream.
class _TiltPresetPicker extends StatelessWidget {
const _TiltPresetPicker({required this.onChanged});
diff --git a/example/linux/.gitignore b/example/linux/.gitignore
deleted file mode 100644
index d3896c9..0000000
--- a/example/linux/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-flutter/ephemeral
diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt
deleted file mode 100644
index 1761446..0000000
--- a/example/linux/CMakeLists.txt
+++ /dev/null
@@ -1,128 +0,0 @@
-# Project-level configuration.
-cmake_minimum_required(VERSION 3.13)
-project(runner LANGUAGES CXX)
-
-# The name of the executable created for the application. Change this to change
-# the on-disk name of your application.
-set(BINARY_NAME "holo_shiny_example")
-# The unique GTK application identifier for this application. See:
-# https://wiki.gnome.org/HowDoI/ChooseApplicationID
-set(APPLICATION_ID "com.example.holo_shiny_example")
-
-# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
-# versions of CMake.
-cmake_policy(SET CMP0063 NEW)
-
-# Load bundled libraries from the lib/ directory relative to the binary.
-set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
-
-# Root filesystem for cross-building.
-if(FLUTTER_TARGET_PLATFORM_SYSROOT)
- set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
- set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
- set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
- set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
- set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
- set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
-endif()
-
-# Define build configuration options.
-if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
- set(CMAKE_BUILD_TYPE "Debug" CACHE
- STRING "Flutter build mode" FORCE)
- set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
- "Debug" "Profile" "Release")
-endif()
-
-# Compilation settings that should be applied to most targets.
-#
-# Be cautious about adding new options here, as plugins use this function by
-# default. In most cases, you should add new options to specific targets instead
-# of modifying this function.
-function(APPLY_STANDARD_SETTINGS TARGET)
- target_compile_features(${TARGET} PUBLIC cxx_std_14)
- target_compile_options(${TARGET} PRIVATE -Wall -Werror)
- target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
- target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
-endfunction()
-
-# Flutter library and tool build rules.
-set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
-add_subdirectory(${FLUTTER_MANAGED_DIR})
-
-# System-level dependencies.
-find_package(PkgConfig REQUIRED)
-pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
-
-# Application build; see runner/CMakeLists.txt.
-add_subdirectory("runner")
-
-# Run the Flutter tool portions of the build. This must not be removed.
-add_dependencies(${BINARY_NAME} flutter_assemble)
-
-# Only the install-generated bundle's copy of the executable will launch
-# correctly, since the resources must in the right relative locations. To avoid
-# people trying to run the unbundled copy, put it in a subdirectory instead of
-# the default top-level location.
-set_target_properties(${BINARY_NAME}
- PROPERTIES
- RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
-)
-
-
-# Generated plugin build rules, which manage building the plugins and adding
-# them to the application.
-include(flutter/generated_plugins.cmake)
-
-
-# === Installation ===
-# By default, "installing" just makes a relocatable bundle in the build
-# directory.
-set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
-if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
- set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
-endif()
-
-# Start with a clean build bundle directory every time.
-install(CODE "
- file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
- " COMPONENT Runtime)
-
-set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
-set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
-
-install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
- COMPONENT Runtime)
-
-install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
- COMPONENT Runtime)
-
-install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
- COMPONENT Runtime)
-
-foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
- install(FILES "${bundled_library}"
- DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
- COMPONENT Runtime)
-endforeach(bundled_library)
-
-# Copy the native assets provided by the build.dart from all packages.
-set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
-install(DIRECTORY "${NATIVE_ASSETS_DIR}"
- DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
- COMPONENT Runtime)
-
-# Fully re-copy the assets directory on each build to avoid having stale files
-# from a previous install.
-set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
-install(CODE "
- file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
- " COMPONENT Runtime)
-install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
- DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
-
-# Install the AOT library on non-Debug builds only.
-if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
- install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
- COMPONENT Runtime)
-endif()
diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt
deleted file mode 100644
index d5bd016..0000000
--- a/example/linux/flutter/CMakeLists.txt
+++ /dev/null
@@ -1,88 +0,0 @@
-# This file controls Flutter-level build steps. It should not be edited.
-cmake_minimum_required(VERSION 3.10)
-
-set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
-
-# Configuration provided via flutter tool.
-include(${EPHEMERAL_DIR}/generated_config.cmake)
-
-# TODO: Move the rest of this into files in ephemeral. See
-# https://github.com/flutter/flutter/issues/57146.
-
-# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
-# which isn't available in 3.10.
-function(list_prepend LIST_NAME PREFIX)
- set(NEW_LIST "")
- foreach(element ${${LIST_NAME}})
- list(APPEND NEW_LIST "${PREFIX}${element}")
- endforeach(element)
- set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
-endfunction()
-
-# === Flutter Library ===
-# System-level dependencies.
-find_package(PkgConfig REQUIRED)
-pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
-pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
-pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
-
-set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
-
-# Published to parent scope for install step.
-set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
-set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
-set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
-set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
-
-list(APPEND FLUTTER_LIBRARY_HEADERS
- "fl_basic_message_channel.h"
- "fl_binary_codec.h"
- "fl_binary_messenger.h"
- "fl_dart_project.h"
- "fl_engine.h"
- "fl_json_message_codec.h"
- "fl_json_method_codec.h"
- "fl_message_codec.h"
- "fl_method_call.h"
- "fl_method_channel.h"
- "fl_method_codec.h"
- "fl_method_response.h"
- "fl_plugin_registrar.h"
- "fl_plugin_registry.h"
- "fl_standard_message_codec.h"
- "fl_standard_method_codec.h"
- "fl_string_codec.h"
- "fl_value.h"
- "fl_view.h"
- "flutter_linux.h"
-)
-list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
-add_library(flutter INTERFACE)
-target_include_directories(flutter INTERFACE
- "${EPHEMERAL_DIR}"
-)
-target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
-target_link_libraries(flutter INTERFACE
- PkgConfig::GTK
- PkgConfig::GLIB
- PkgConfig::GIO
-)
-add_dependencies(flutter flutter_assemble)
-
-# === Flutter tool backend ===
-# _phony_ is a non-existent file to force this command to run every time,
-# since currently there's no way to get a full input/output list from the
-# flutter tool.
-add_custom_command(
- OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
- ${CMAKE_CURRENT_BINARY_DIR}/_phony_
- COMMAND ${CMAKE_COMMAND} -E env
- ${FLUTTER_TOOL_ENVIRONMENT}
- "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
- ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
- VERBATIM
-)
-add_custom_target(flutter_assemble DEPENDS
- "${FLUTTER_LIBRARY}"
- ${FLUTTER_LIBRARY_HEADERS}
-)
diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc
deleted file mode 100644
index e71a16d..0000000
--- a/example/linux/flutter/generated_plugin_registrant.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-//
-// Generated file. Do not edit.
-//
-
-// clang-format off
-
-#include "generated_plugin_registrant.h"
-
-
-void fl_register_plugins(FlPluginRegistry* registry) {
-}
diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h
deleted file mode 100644
index e0f0a47..0000000
--- a/example/linux/flutter/generated_plugin_registrant.h
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// Generated file. Do not edit.
-//
-
-// clang-format off
-
-#ifndef GENERATED_PLUGIN_REGISTRANT_
-#define GENERATED_PLUGIN_REGISTRANT_
-
-#include
-
-// Registers Flutter plugins.
-void fl_register_plugins(FlPluginRegistry* registry);
-
-#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake
deleted file mode 100644
index 2e1de87..0000000
--- a/example/linux/flutter/generated_plugins.cmake
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Generated file, do not edit.
-#
-
-list(APPEND FLUTTER_PLUGIN_LIST
-)
-
-list(APPEND FLUTTER_FFI_PLUGIN_LIST
-)
-
-set(PLUGIN_BUNDLED_LIBRARIES)
-
-foreach(plugin ${FLUTTER_PLUGIN_LIST})
- add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
- target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
- list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
- list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
-endforeach(plugin)
-
-foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
- add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
- list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
-endforeach(ffi_plugin)
diff --git a/example/linux/runner/CMakeLists.txt b/example/linux/runner/CMakeLists.txt
deleted file mode 100644
index e97dabc..0000000
--- a/example/linux/runner/CMakeLists.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-cmake_minimum_required(VERSION 3.13)
-project(runner LANGUAGES CXX)
-
-# Define the application target. To change its name, change BINARY_NAME in the
-# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
-# work.
-#
-# Any new source files that you add to the application should be added here.
-add_executable(${BINARY_NAME}
- "main.cc"
- "my_application.cc"
- "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
-)
-
-# Apply the standard set of build settings. This can be removed for applications
-# that need different build settings.
-apply_standard_settings(${BINARY_NAME})
-
-# Add preprocessor definitions for the application ID.
-add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
-
-# Add dependency libraries. Add any application-specific dependencies here.
-target_link_libraries(${BINARY_NAME} PRIVATE flutter)
-target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
-
-target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
diff --git a/example/linux/runner/main.cc b/example/linux/runner/main.cc
deleted file mode 100644
index e7c5c54..0000000
--- a/example/linux/runner/main.cc
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "my_application.h"
-
-int main(int argc, char** argv) {
- g_autoptr(MyApplication) app = my_application_new();
- return g_application_run(G_APPLICATION(app), argc, argv);
-}
diff --git a/example/linux/runner/my_application.cc b/example/linux/runner/my_application.cc
deleted file mode 100644
index 448e8c8..0000000
--- a/example/linux/runner/my_application.cc
+++ /dev/null
@@ -1,148 +0,0 @@
-#include "my_application.h"
-
-#include
-#ifdef GDK_WINDOWING_X11
-#include
-#endif
-
-#include "flutter/generated_plugin_registrant.h"
-
-struct _MyApplication {
- GtkApplication parent_instance;
- char** dart_entrypoint_arguments;
-};
-
-G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
-
-// Called when first Flutter frame received.
-static void first_frame_cb(MyApplication* self, FlView* view) {
- gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
-}
-
-// Implements GApplication::activate.
-static void my_application_activate(GApplication* application) {
- MyApplication* self = MY_APPLICATION(application);
- GtkWindow* window =
- GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
-
- // Use a header bar when running in GNOME as this is the common style used
- // by applications and is the setup most users will be using (e.g. Ubuntu
- // desktop).
- // If running on X and not using GNOME then just use a traditional title bar
- // in case the window manager does more exotic layout, e.g. tiling.
- // If running on Wayland assume the header bar will work (may need changing
- // if future cases occur).
- gboolean use_header_bar = TRUE;
-#ifdef GDK_WINDOWING_X11
- GdkScreen* screen = gtk_window_get_screen(window);
- if (GDK_IS_X11_SCREEN(screen)) {
- const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
- if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
- use_header_bar = FALSE;
- }
- }
-#endif
- if (use_header_bar) {
- GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
- gtk_widget_show(GTK_WIDGET(header_bar));
- gtk_header_bar_set_title(header_bar, "holo_shiny_example");
- gtk_header_bar_set_show_close_button(header_bar, TRUE);
- gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
- } else {
- gtk_window_set_title(window, "holo_shiny_example");
- }
-
- gtk_window_set_default_size(window, 1280, 720);
-
- g_autoptr(FlDartProject) project = fl_dart_project_new();
- fl_dart_project_set_dart_entrypoint_arguments(
- project, self->dart_entrypoint_arguments);
-
- FlView* view = fl_view_new(project);
- GdkRGBA background_color;
- // Background defaults to black, override it here if necessary, e.g. #00000000
- // for transparent.
- gdk_rgba_parse(&background_color, "#000000");
- fl_view_set_background_color(view, &background_color);
- gtk_widget_show(GTK_WIDGET(view));
- gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
-
- // Show the window when Flutter renders.
- // Requires the view to be realized so we can start rendering.
- g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
- self);
- gtk_widget_realize(GTK_WIDGET(view));
-
- fl_register_plugins(FL_PLUGIN_REGISTRY(view));
-
- gtk_widget_grab_focus(GTK_WIDGET(view));
-}
-
-// Implements GApplication::local_command_line.
-static gboolean my_application_local_command_line(GApplication* application,
- gchar*** arguments,
- int* exit_status) {
- MyApplication* self = MY_APPLICATION(application);
- // Strip out the first argument as it is the binary name.
- self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
-
- g_autoptr(GError) error = nullptr;
- if (!g_application_register(application, nullptr, &error)) {
- g_warning("Failed to register: %s", error->message);
- *exit_status = 1;
- return TRUE;
- }
-
- g_application_activate(application);
- *exit_status = 0;
-
- return TRUE;
-}
-
-// Implements GApplication::startup.
-static void my_application_startup(GApplication* application) {
- // MyApplication* self = MY_APPLICATION(object);
-
- // Perform any actions required at application startup.
-
- G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
-}
-
-// Implements GApplication::shutdown.
-static void my_application_shutdown(GApplication* application) {
- // MyApplication* self = MY_APPLICATION(object);
-
- // Perform any actions required at application shutdown.
-
- G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
-}
-
-// Implements GObject::dispose.
-static void my_application_dispose(GObject* object) {
- MyApplication* self = MY_APPLICATION(object);
- g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
- G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
-}
-
-static void my_application_class_init(MyApplicationClass* klass) {
- G_APPLICATION_CLASS(klass)->activate = my_application_activate;
- G_APPLICATION_CLASS(klass)->local_command_line =
- my_application_local_command_line;
- G_APPLICATION_CLASS(klass)->startup = my_application_startup;
- G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
- G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
-}
-
-static void my_application_init(MyApplication* self) {}
-
-MyApplication* my_application_new() {
- // Set the program name to the application ID, which helps various systems
- // like GTK and desktop environments map this running application to its
- // corresponding .desktop file. This ensures better integration by allowing
- // the application to be recognized beyond its binary name.
- g_set_prgname(APPLICATION_ID);
-
- return MY_APPLICATION(g_object_new(my_application_get_type(),
- "application-id", APPLICATION_ID, "flags",
- G_APPLICATION_NON_UNIQUE, nullptr));
-}
diff --git a/example/linux/runner/my_application.h b/example/linux/runner/my_application.h
deleted file mode 100644
index db16367..0000000
--- a/example/linux/runner/my_application.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef FLUTTER_MY_APPLICATION_H_
-#define FLUTTER_MY_APPLICATION_H_
-
-#include
-
-G_DECLARE_FINAL_TYPE(MyApplication,
- my_application,
- MY,
- APPLICATION,
- GtkApplication)
-
-/**
- * my_application_new:
- *
- * Creates a new Flutter-based application.
- *
- * Returns: a new #MyApplication.
- */
-MyApplication* my_application_new();
-
-#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/lib/src/shiny_widget.dart b/lib/src/shiny_widget.dart
index 7f9e9cb..f6a26dc 100644
--- a/lib/src/shiny_widget.dart
+++ b/lib/src/shiny_widget.dart
@@ -5,8 +5,8 @@ import 'package:flutter/material.dart';
import 'shiny_controller.dart';
-/// Available holographic material profiles rendered by the fragment shader.
-enum HolographStyle {
+/// Available holographic material profiles rendered by dedicated shader assets.
+enum ShinyProfile {
/// Smooth silver foil with soft rainbow flow.
holographicSilver,
@@ -20,168 +20,20 @@ enum HolographStyle {
superGoldVinyl,
}
-/// Sparkle silhouette families used by the shader when drawing glints.
-enum SparkleShapeKind {
- /// Disables sparkle rendering entirely.
- none,
-
- /// Star-shaped sparkle with configurable point count and inner radius.
- star,
-
- /// Rectangular sparkle streak.
- rectangle,
-
- /// Fixed-side polygon sparkle.
- polygon,
-
- /// Randomized polygon sparkle per sparkle cell.
- randomPolygon,
-
- /// Confetti-like elongated rectangle with random rotation.
- confetti,
-}
-
-/// Declarative sparkle shape configuration for [Shiny] and [ShinyCard].
-class SparkleShapeSpec {
- /// Internal constructor that maps directly to shader uniform values.
- const SparkleShapeSpec._({
- required this.kind,
- required this.primary,
- required this.secondary,
- required this.tertiary,
- });
-
- /// No sparkle output.
- static const SparkleShapeSpec none = SparkleShapeSpec._(
- kind: SparkleShapeKind.none,
- primary: 0.0,
- secondary: 0.0,
- tertiary: 0.0,
- );
-
- /// Eight-point star sparkle preset.
- static const SparkleShapeSpec eightPointStar = SparkleShapeSpec._(
- kind: SparkleShapeKind.star,
- primary: 8.0,
- secondary: 0.42,
- tertiary: 0.0,
- );
-
- /// Five-point star sparkle preset.
- static const SparkleShapeSpec fivePointStar = SparkleShapeSpec._(
- kind: SparkleShapeKind.star,
- primary: 5.0,
- secondary: 0.42,
- tertiary: 0.0,
- );
-
- /// Rectangular sparkle preset.
- static const SparkleShapeSpec rectangle = SparkleShapeSpec._(
- kind: SparkleShapeKind.rectangle,
- primary: 0.24,
- secondary: 0.055,
- tertiary: 1.0,
- );
-
- /// Diamond sparkle preset.
- static const SparkleShapeSpec diamond = SparkleShapeSpec._(
- kind: SparkleShapeKind.polygon,
- primary: 4.0,
- secondary: 1.0,
- tertiary: 0.78539816339,
- );
-
- /// Hexagon sparkle preset.
- static const SparkleShapeSpec hexagon = SparkleShapeSpec._(
- kind: SparkleShapeKind.polygon,
- primary: 6.0,
- secondary: 1.0,
- tertiary: 0.0,
- );
-
- /// Randomized polygon sparkle preset.
- static const SparkleShapeSpec randomPolygon = SparkleShapeSpec._(
- kind: SparkleShapeKind.randomPolygon,
- primary: 0.0,
- secondary: 0.0,
- tertiary: 0.0,
- );
-
- /// Confetti sparkle preset.
- static const SparkleShapeSpec confetti = SparkleShapeSpec._(
- kind: SparkleShapeKind.confetti,
- primary: 0.34,
- secondary: 0.045,
- tertiary: 1.0,
- );
-
- /// Creates a custom star sparkle.
- ///
- /// [points] is clamped to `[4, 12]` and [innerRatio] to `[0.15, 0.85]`.
- factory SparkleShapeSpec.customStar(
- {int points = 8, double innerRatio = 0.42}) {
- return SparkleShapeSpec._(
- kind: SparkleShapeKind.star,
- primary: points.toDouble().clamp(4.0, 12.0),
- secondary: innerRatio.clamp(0.15, 0.85),
- tertiary: 0.0,
- );
+extension ShinyProfileShaderAsset on ShinyProfile {
+ /// Local shader asset path for the profile.
+ String get shaderAsset {
+ switch (this) {
+ case ShinyProfile.holographicSilver:
+ return 'shaders/shiny_holographic_silver.frag';
+ case ShinyProfile.crackedIce:
+ return 'shaders/shiny_cracked_ice.frag';
+ case ShinyProfile.silverMosaic:
+ return 'shaders/shiny_silver_mosaic.frag';
+ case ShinyProfile.superGoldVinyl:
+ return 'shaders/shiny_super_gold_vinyl.frag';
+ }
}
-
- /// Creates a custom polygon sparkle.
- ///
- /// [sides] is clamped to `[3, 10]` and [aspectRatio] to `[0.4, 2.0]`.
- factory SparkleShapeSpec.customPolygon(
- {int sides = 6, double aspectRatio = 1.0, double rotation = 0.0}) {
- return SparkleShapeSpec._(
- kind: SparkleShapeKind.polygon,
- primary: sides.toDouble().clamp(3.0, 10.0),
- secondary: aspectRatio.clamp(0.4, 2.0),
- tertiary: rotation,
- );
- }
-
- /// Creates a custom rectangular sparkle.
- ///
- /// Values are clamped to keep shapes visible and numerically stable.
- factory SparkleShapeSpec.customRectangle(
- {double halfWidth = 0.24,
- double halfHeight = 0.055,
- double rotationJitter = 1.0}) {
- return SparkleShapeSpec._(
- kind: SparkleShapeKind.rectangle,
- primary: halfWidth.clamp(0.05, 0.45),
- secondary: halfHeight.clamp(0.02, 0.30),
- tertiary: rotationJitter.clamp(0.0, 1.0),
- );
- }
-
- /// Creates a custom confetti sparkle.
- ///
- /// Values are clamped to keep geometry inside the sparkle cell.
- factory SparkleShapeSpec.customConfetti(
- {double length = 0.34,
- double thickness = 0.045,
- double rotationJitter = 1.0}) {
- return SparkleShapeSpec._(
- kind: SparkleShapeKind.confetti,
- primary: length.clamp(0.08, 0.48),
- secondary: thickness.clamp(0.01, 0.14),
- tertiary: rotationJitter.clamp(0.0, 1.0),
- );
- }
-
- /// Sparkle family used by the shader.
- final SparkleShapeKind kind;
-
- /// Shape parameter 1. Meaning depends on [kind].
- final double primary;
-
- /// Shape parameter 2. Meaning depends on [kind].
- final double secondary;
-
- /// Shape parameter 3. Meaning depends on [kind].
- final double tertiary;
}
/// Applies holographic shader lighting to any child widget.
@@ -197,8 +49,7 @@ class Shiny extends StatefulWidget {
this.specular = 0.8,
this.diffraction = 0.8,
this.opacity = 1.0,
- this.sparkleShape = SparkleShapeSpec.none,
- this.style = HolographStyle.crackedIce,
+ this.profile = ShinyProfile.crackedIce,
this.blendMode = BlendMode.screen,
this.enableShader = true,
});
@@ -227,11 +78,8 @@ class Shiny extends StatefulWidget {
/// Global shader opacity multiplier.
final double opacity;
- /// Sparkle silhouette specification.
- final SparkleShapeSpec sparkleShape;
-
/// Foil style preset.
- final HolographStyle style;
+ final ShinyProfile profile;
/// Blend mode applied by [ShaderMask].
final BlendMode blendMode;
@@ -244,7 +92,8 @@ class Shiny extends StatefulWidget {
}
class _ShinyState extends State {
- static Future? _programFuture;
+ static final Map> _programFutures =
+ >{};
ui.FragmentShader? _shader;
StreamSubscription? _tiltSub;
@@ -269,30 +118,43 @@ class _ShinyState extends State {
}
if (oldWidget.enableShader != widget.enableShader) {
if (widget.enableShader) {
- if (_shader == null) _loadShader();
+ _loadShader();
+ } else {
+ _shader?.dispose();
+ _shader = null;
}
}
+ if (oldWidget.profile != widget.profile && widget.enableShader) {
+ _loadShader();
+ }
}
Future _loadShader() async {
+ final ShinyProfile profile = widget.profile;
try {
- _programFuture ??= _loadProgram();
- final ui.FragmentProgram program = await _programFuture!;
- if (!mounted) return;
+ final Future future =
+ _programFutures.putIfAbsent(profile, () => _loadProgram(profile));
+ final ui.FragmentProgram program = await future;
+ if (!mounted || !widget.enableShader || widget.profile != profile) {
+ return;
+ }
+ final ui.FragmentShader shader = program.fragmentShader();
setState(() {
- _shader = program.fragmentShader();
+ _shader?.dispose();
+ _shader = shader;
});
} catch (_) {
// Keep rendering without shader when runtime effects are unavailable.
}
}
- Future _loadProgram() async {
+ Future _loadProgram(ShinyProfile profile) async {
+ final String localAsset = profile.shaderAsset;
try {
- return await ui.FragmentProgram.fromAsset('shaders/shiny_card.frag');
+ return await ui.FragmentProgram.fromAsset(localAsset);
} catch (_) {
return await ui.FragmentProgram.fromAsset(
- 'packages/holo_shiny/shaders/shiny_card.frag');
+ 'packages/holo_shiny/$localAsset');
}
}
@@ -331,12 +193,7 @@ class _ShinyState extends State {
..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);
+ ..setFloat(8, widget.opacity);
return shader;
}
@@ -372,8 +229,7 @@ class ShinyCard extends StatefulWidget {
this.specular = 0.8,
this.diffraction = 0.8,
this.opacity = 1.0,
- this.sparkleShape = SparkleShapeSpec.none,
- this.style = HolographStyle.crackedIce,
+ this.profile = ShinyProfile.crackedIce,
this.enableShader = true,
});
@@ -410,11 +266,8 @@ class ShinyCard extends StatefulWidget {
/// Global shader opacity multiplier.
final double opacity;
- /// Sparkle silhouette specification.
- final SparkleShapeSpec sparkleShape;
-
/// Foil style preset.
- final HolographStyle style;
+ final ShinyProfile profile;
/// Toggles shader execution.
final bool enableShader;
@@ -550,8 +403,7 @@ class _ShinyCardState extends State
specular: widget.specular,
diffraction: widget.diffraction,
opacity: widget.opacity,
- sparkleShape: widget.sparkleShape,
- style: widget.style,
+ profile: widget.profile,
child: base,
),
if (widget.foreground != null) widget.foreground!,
diff --git a/pubspec.yaml b/pubspec.yaml
index 4457941..4c05e25 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -23,4 +23,7 @@ dev_dependencies:
flutter:
shaders:
- - shaders/shiny_card.frag
+ - shaders/shiny_holographic_silver.frag
+ - shaders/shiny_cracked_ice.frag
+ - shaders/shiny_silver_mosaic.frag
+ - shaders/shiny_super_gold_vinyl.frag
diff --git a/shaders/shiny_card.frag b/shaders/shiny_card.frag
deleted file mode 100644
index 1467852..0000000
--- a/shaders/shiny_card.frag
+++ /dev/null
@@ -1,382 +0,0 @@
-#version 460 core
-#include
-
-out vec4 fragColor;
-
-uniform vec2 uSize;
-uniform vec2 uTilt;
-uniform float uPrismatic;
-uniform float uSparkle;
-uniform float uSpecular;
-uniform float uDiffraction;
-uniform float uStyle;
-uniform float uSparkleShapeKind;
-uniform float uSparklePrimary;
-uniform float uSparkleSecondary;
-uniform float uSparkleTertiary;
-uniform float uOpacity;
-
-#define TWO_PI 6.28318530718
-
-// Precomputed rotation matrices reduce repeated sin/cos work in tiled layers.
-#define ROT_0_78 mat2( 0.71091, 0.70328, -0.70328, 0.71091)
-#define ROT_M0_5 mat2( 0.87758, -0.47943, 0.47943, 0.87758)
-#define ROT_1_2 mat2( 0.36236, 0.93204, -0.93204, 0.36236)
-
-float hash(vec2 p) {
- vec3 p3 = fract(vec3(p.xyx) * 0.1031);
- p3 += dot(p3, p3.yzx + 33.33);
- return fract((p3.x + p3.y) * p3.z);
-}
-
-float noise(vec2 p) {
- vec2 i = floor(p);
- vec2 f = fract(p);
- vec2 u = f * f * (3.0 - 2.0 * f);
-
- float a = hash(i);
- float b = hash(i + vec2(1.0, 0.0));
- float c = hash(i + vec2(0.0, 1.0));
- float d = hash(i + vec2(1.0, 1.0));
-
- return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
-}
-
-vec3 hsv2rgb(float h, float s, float v) {
- vec3 k = vec3(1.0, 2.0 / 3.0, 1.0 / 3.0);
- vec3 p = abs(fract(vec3(h) + k) * 6.0 - 3.0);
- return v * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), s);
-}
-
-vec3 rainbow(float phase, float saturation, float value) {
- return hsv2rgb(fract(phase), saturation, value);
-}
-
-float sdBox(vec2 p, vec2 b) {
- vec2 d = abs(p) - b;
- return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
-}
-
-float sdRegularPolygon(vec2 p, float sides, float radius) {
- float angle = atan(p.y, p.x);
- float sector = TWO_PI / sides;
- return cos(floor(0.5 + angle / sector) * sector - angle) * length(p) - radius;
-}
-
-float sparkleStarMask(vec2 p, float points, float innerRatio) {
- float angle = atan(p.y, p.x);
- float radius = dot(p, p);
- float spikes = 0.5 + 0.5 * cos(angle * points);
- float starRadius = mix(0.34 * innerRatio, 0.34, pow(spikes, 1.4));
- return smoothstep(0.0009, -0.0009, radius - (starRadius * starRadius));
-}
-
-vec2 rotatePoint(vec2 p, float rotation) {
- float c = cos(rotation);
- float s = sin(rotation);
- return vec2(c * p.x - s * p.y, s * p.x + c * p.y);
-}
-
-float sparkleRectangleMask(vec2 p, float halfWidth, float halfHeight, float rotation) {
- vec2 q = rotatePoint(p, rotation);
- float body = sdBox(q, vec2(halfWidth, halfHeight));
- float bevel = sdBox(q, vec2(halfWidth * 0.78, halfHeight * 0.78));
- float bodyMask = smoothstep(0.03, -0.03, body);
-
- float centerLine = 1.0 - smoothstep(0.0, halfHeight * 1.25, abs(q.y));
- float endFade = 1.0 - smoothstep(halfWidth * 0.55, halfWidth * 1.05, abs(q.x));
- float streak = centerLine * endFade;
- float bevelRing = smoothstep(0.025, -0.025, body) - smoothstep(0.025, -0.025, bevel);
- return clamp(bodyMask * (0.58 + 0.42 * streak) + bevelRing * 0.25, 0.0, 1.0);
-}
-
-float sparklePolygonMask(vec2 p, float sides, float aspectRatio, float rotation) {
- vec2 q = rotatePoint(p, rotation);
- q.x /= aspectRatio;
- float poly = sdRegularPolygon(q, sides, 0.26);
- return smoothstep(0.03, -0.03, poly);
-}
-
-float sparkleShapeMask(vec2 p, vec2 id) {
- float randomRotation = hash(id + 5.1) * TWO_PI;
- if (uSparkleShapeKind < 0.5) {
- return 0.0;
- }
- if (uSparkleShapeKind < 1.5) {
- return sparkleStarMask(p, uSparklePrimary, uSparkleSecondary);
- }
- if (uSparkleShapeKind < 2.5) {
- return sparkleRectangleMask(p, uSparklePrimary, uSparkleSecondary, randomRotation * uSparkleTertiary);
- }
- if (uSparkleShapeKind < 3.5) {
- return sparklePolygonMask(p, uSparklePrimary, uSparkleSecondary, uSparkleTertiary);
- }
- if (uSparkleShapeKind < 4.5) {
- float sides = 5.0 + floor(hash(id + 9.4) * 4.0);
- float aspect = 0.7 + hash(id + 13.1) * 0.7;
- return sparklePolygonMask(p, sides, aspect, randomRotation);
- }
- return sparkleRectangleMask(p, uSparklePrimary, uSparkleSecondary, randomRotation * uSparkleTertiary);
-}
-
-vec3 styleHolographicSilver(vec2 uv, vec2 tilt) {
- vec2 p = uv * 2.0 - 1.0;
- 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 + 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);
- vec3 silver = vec3(0.62, 0.65, 0.70) * (0.85 + 0.15 * flowB);
-
- return mix(silver, holo, 0.65 * blend);
-}
-
-vec3 styleCrackedIce(vec2 uv, vec2 tilt) {
- float maxZ = -999.0;
- vec2 bestSlope = vec2(0.0);
- float bestHash = 0.0;
- float bestEdge = 1.0;
-
- vec2 warp = vec2(sin(uv.y * 10.0), cos(uv.x * 10.0)) * 0.05;
- vec2 baseUV = uv + warp;
-
- // --- LAYER 1 ---
- vec2 p1 = baseUV * 12.0;
- vec2 id1 = floor(p1);
- vec2 f1 = fract(p1) - 0.5;
- vec2 slope1 = vec2(hash(id1 + 1.2), hash(id1 + 1.3)) * 2.0 - 1.0;
- float z1 = dot(f1, slope1) * 3.5;
- if (z1 > maxZ) {
- maxZ = z1; bestSlope = slope1; bestHash = hash(id1 + 1.1); bestEdge = min(0.5 - abs(f1.x), 0.5 - abs(f1.y));
- }
-
- // --- LAYER 2 (Optimized Rotation) ---
- vec2 p2 = (ROT_0_78 * baseUV) * 15.0;
- vec2 id2 = floor(p2);
- vec2 f2 = fract(p2) - 0.5;
- vec2 slope2 = vec2(hash(id2 + 2.2), hash(id2 + 2.3)) * 2.0 - 1.0;
- float z2 = 0.8 + dot(f2, slope2) * 3.5;
- if (z2 > maxZ) {
- maxZ = z2; bestSlope = slope2; bestHash = hash(id2 + 2.1); bestEdge = min(0.5 - abs(f2.x), 0.5 - abs(f2.y));
- }
-
- // --- LAYER 3 (Optimized Rotation) ---
- vec2 p3 = (ROT_M0_5 * baseUV) * 18.0;
- vec2 id3 = floor(p3);
- vec2 f3 = fract(p3) - 0.5;
- vec2 slope3 = vec2(hash(id3 + 3.2), hash(id3 + 3.3)) * 2.0 - 1.0;
- float z3 = 1.6 + dot(f3, slope3) * 3.5;
- if (z3 > maxZ) {
- maxZ = z3; bestSlope = slope3; bestHash = hash(id3 + 3.1); bestEdge = min(0.5 - abs(f3.x), 0.5 - abs(f3.y));
- }
-
- // --- LAYER 4 (Optimized Rotation) ---
- vec2 p4 = (ROT_1_2 * baseUV) * 21.0;
- vec2 id4 = floor(p4);
- vec2 f4 = fract(p4) - 0.5;
- vec2 slope4 = vec2(hash(id4 + 4.2), hash(id4 + 4.3)) * 2.0 - 1.0;
- float z4 = 2.4 + dot(f4, slope4) * 3.5;
- if (z4 > maxZ) {
- maxZ = z4; bestSlope = slope4; bestHash = hash(id4 + 4.1); bestEdge = min(0.5 - abs(f4.x), 0.5 - abs(f4.y));
- }
-
- // --- VIBRANT COLOR OPTICS ---
- float alignment = dot(tilt, normalize(bestSlope + vec2(0.001)));
- 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 + 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);
- finalColor += mix(baseColor, vec3(1.0, 0.95, 0.85), 0.6) * flash * 0.55;
- finalColor += baseColor * smoothstep(0.05, 0.0, bestEdge) * flash * 0.4;
-
- return finalColor;
-}
-
-vec3 styleSilverMosaic(vec2 uv, vec2 tilt) {
- // Mosaic tile grid.
- vec2 g = uv * 6.0;
- vec2 id = floor(g);
-
- // Center local tile coordinates for radial optics.
- vec2 f = fract(g) - 0.5;
-
- // Real foil sheets often alternate the grain of the squares to catch light from all angles.
- if (mod(id.x + id.y, 2.0) > 0.5) {
- f = vec2(-f.y, f.x);
- }
-
- vec2 radialDir = normalize(f + vec2(0.0001));
-
- vec2 lightDir = normalize(tilt + vec2(0.001));
-
- // Radial highlight where tile direction aligns with light direction.
- float alignment = abs(dot(radialDir, lightDir));
-
- float highlight = pow(alignment, 12.0);
-
- float angleDiff = acos(alignment);
-
- // 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 + dot(uv, vec2(0.9, -0.7));
-
- float phase = basePhase + side * angleDiff * 1.8;
- vec3 holoColor = rainbow(phase, 0.85, 1.0);
-
- // White specular core appears when alignment is nearly perfect.
- vec3 core = vec3(1.0) * smoothstep(0.98, 1.0, alignment);
-
- vec3 foilBase = vec3(0.25, 0.27, 0.30);
-
- vec3 finalColor = foilBase + (holoColor * highlight * 1.5) + (core * 0.8);
-
- // Tile borders and edge darkening add perceived depth.
- float edgeX = 0.5 - abs(f.x);
- float edgeY = 0.5 - abs(f.y);
-
- float border = smoothstep(0.0, 0.015, min(edgeX, edgeY));
-
- float dist = length(f);
- finalColor *= mix(0.7, 1.0, 1.0 - smoothstep(0.0, 0.5, dist) * 0.3);
-
- return clamp(finalColor * border, 0.0, 1.0);
-}
-
-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;
-
- // Distance-squared masks avoid square roots and preserve circular dots.
- float d2 = dot(f, f);
- 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);
- 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);
-
- return gold * (0.75 + 0.25 * sheen) + warm * dotMask * 0.28 + shadow * emboss * 0.08;
-}
-
-void main() {
- // UVs in [0, 1] for global lighting and center-based effects.
- vec2 uv = FlutterFragCoord().xy / uSize;
- vec2 center = vec2(0.5, 0.5);
- vec2 fromCenter = uv - center;
-
- // Aspect-corrected UVs keep grids and sparkles physically proportional.
- float maxDim = max(uSize.x, uSize.y);
- vec2 aspectUV = FlutterFragCoord().xy / maxDim;
-
- float tiltMag = length(uTilt);
- float safeTiltMag = max(tiltMag, 0.001);
- vec2 tiltDir = uTilt / safeTiltMag;
-
- // Highlight uses stretched UVs so glare spans the full widget area.
- float highlightDistance = length(uv - (center + (uTilt * 0.35)));
-
- bool isHolographicSilver = uStyle < 0.5;
- bool isCrackedIce = uStyle >= 0.5 && uStyle < 1.5;
- bool isSilverMosaic = uStyle >= 1.5 && uStyle < 2.5;
-
- 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);
-
- // Sparkle grid uses aspect-corrected coordinates to avoid stretching.
- vec2 sparkleGrid = aspectUV * sparkleGridScale;
- vec2 grid = floor(sparkleGrid);
-
- float rarityThreshold = isCrackedIce ? 0.965 : (isSilverMosaic ? 0.88 : 0.83);
- float rarity = step(rarityThreshold, hash(grid + 0.31));
-
- vec3 sparkle = vec3(0.0);
- if (rarity > 0.0) {
- vec2 cell = fract(sparkleGrid) - 0.5;
- float sparkleHash = hash(grid);
- vec2 sparkleNormal = normalize(vec2(sparkleHash * 2.0 - 1.0, hash(grid + 19.7) * 2.0 - 1.0));
- float alignment = max(0.0, dot(tiltDir, sparkleNormal));
-
- if (alignment > 0.0) {
- 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) {
- float sides = 5.0 + floor(hash(grid + 9.4) * 4.0);
- float aspect = 0.75 + hash(grid + 13.1) * 0.55;
- shapeMask = sparklePolygonMask(cell, sides, aspect, hash(grid + 5.1) * TWO_PI);
- } else {
- shapeMask = sparkleShapeMask(cell, grid);
- }
-
- float sparklePower = isCrackedIce ? 4.8 : (isSilverMosaic ? 3.8 : 3.2);
- float sparkleIntensity = isCrackedIce ? 0.25 : (isSilverMosaic ? 0.55 : 1.0);
- float sparkleChroma = isCrackedIce ? 0.20 : 0.35;
- float sparkleMask = pow(alignment, sparklePower) * twinkle * rarity * shapeMask;
- vec3 sparkleColor = mix(vec3(1.0), rainbow(sparkleHash + dot(uTilt, vec2(0.2, -0.15)), 0.55, 1.0), sparkleChroma);
- sparkle = sparkleColor * sparkleMask * uSparkle * sparkleIntensity * sparkleGain;
- }
- }
-
- vec3 styleBase;
- // Style bases sample aspect-corrected UVs for stable pattern geometry.
- if (uStyle < 0.5) {
- styleBase = styleHolographicSilver(aspectUV, uTilt);
- } else if (uStyle < 1.5) {
- styleBase = styleCrackedIce(aspectUV, uTilt);
- } else if (uStyle < 2.5) {
- styleBase = styleSilverMosaic(aspectUV, uTilt);
- } else {
- styleBase = styleSuperGoldVinyl(aspectUV, uTilt);
- }
-
- float styleLuma = dot(styleBase, vec3(0.299, 0.587, 0.114));
- vec3 chromaAdjusted = mix(vec3(styleLuma), styleBase, 0.2 + 0.8 * uPrismatic);
-
- // Directional lighting uses center-relative UVs for broad foil gradients.
- vec2 normal2d = normalize(fromCenter + vec2(0.001));
- float directional = 0.5 + 0.5 * dot(normal2d, tiltDir);
- float tiltLighting = mix(0.86, 1.20, pow(directional, 1.2));
- chromaAdjusted *= tiltLighting;
-
- 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));
- vec3 prismaticTint = rainbow(prismaticPhase, 0.72, 1.0) * uPrismatic * prismaticGain * prismaticTintGain * (0.08 + 0.14 * edgeSpec);
-
- float microStrength = isCrackedIce ? 0.08 : 0.18;
-
- // Micro diffraction uses aspect-corrected UVs to keep grain isotropic.
- float microMask = 0.5 + 0.5 * sin(dot(aspectUV, vec2(137.0, 57.0)) + noise(aspectUV * 14.0) * 4.0 + dot(uTilt, vec2(9.0, 4.0)) * 2.0);
- vec3 microShimmer = rainbow(noise(aspectUV * 10.0) + dot(uTilt, vec2(0.4, -0.3)) * 0.3, 0.65, 1.0) * microMask * uDiffraction * microStrength;
-
- vec3 styleColor = chromaAdjusted + microShimmer + prismaticTint + (specular * 0.22) + sparkle;
- vec3 mappedColor = styleColor / (1.0 + styleColor * 0.3);
- float luma = dot(mappedColor, vec3(0.299, 0.587, 0.114));
- vec3 vibrantColor = mix(vec3(luma), mappedColor, 1.4);
-
- float foilBrightness = dot(vibrantColor, vec3(0.333));
- float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35) * clamp(uOpacity, 0.0, 1.0);
- vec3 finalColor = clamp(vibrantColor, 0.0, 1.0);
-
- fragColor = vec4(finalColor * alpha, alpha);
-}
\ No newline at end of file
diff --git a/shaders/shiny_cracked_ice.frag b/shaders/shiny_cracked_ice.frag
new file mode 100644
index 0000000..6fcb8f2
--- /dev/null
+++ b/shaders/shiny_cracked_ice.frag
@@ -0,0 +1,192 @@
+#version 460 core
+#include
+
+out vec4 fragColor;
+
+uniform vec2 uSize;
+uniform vec2 uTilt;
+uniform float uPrismatic;
+uniform float uSparkle;
+uniform float uSpecular;
+uniform float uDiffraction;
+uniform float uOpacity;
+
+#define TWO_PI 6.28318530718
+#define ROT_0_78 mat2(0.71091, 0.70328, -0.70328, 0.71091)
+#define ROT_M0_5 mat2(0.87758, -0.47943, 0.47943, 0.87758)
+#define ROT_1_2 mat2(0.36236, 0.93204, -0.93204, 0.36236)
+
+float hash(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+ p3 += dot(p3, p3.yzx + 33.33);
+ return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ float a = hash(i);
+ float b = hash(i + vec2(1.0, 0.0));
+ float c = hash(i + vec2(0.0, 1.0));
+ float d = hash(i + vec2(1.0, 1.0));
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
+}
+
+vec3 hsv2rgb(float h, float s, float v) {
+ vec3 k = vec3(1.0, 2.0 / 3.0, 1.0 / 3.0);
+ vec3 p = abs(fract(vec3(h) + k) * 6.0 - 3.0);
+ return v * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), s);
+}
+
+vec3 rainbow(float phase, float saturation, float value) {
+ return hsv2rgb(fract(phase), saturation, value);
+}
+
+vec2 safeNormalize(vec2 p) {
+ return p * inversesqrt(max(dot(p, p), 0.000001));
+}
+
+float sdRegularPolygon(vec2 p, float sides, float radius) {
+ float angle = atan(p.y, p.x);
+ float sector = TWO_PI / sides;
+ return cos(floor(0.5 + angle / sector) * sector - angle) * length(p) - radius;
+}
+
+float sparklePolygonMask(vec2 p, float sides, float aspectRatio, float rotation) {
+ float c = cos(rotation);
+ float s = sin(rotation);
+ vec2 q = vec2(c * p.x - s * p.y, s * p.x + c * p.y);
+ q.x /= aspectRatio;
+ float poly = sdRegularPolygon(q, sides, 0.26);
+ return smoothstep(0.03, -0.03, poly);
+}
+
+vec3 styleBase(vec2 uv, vec2 tilt) {
+ float maxZ = -999.0;
+ vec2 bestSlope = vec2(0.0);
+ float bestHash = 0.0;
+ float bestEdge = 1.0;
+
+ vec2 warp = vec2(sin(uv.y * 10.0), cos(uv.x * 10.0)) * 0.05;
+ vec2 baseUV = uv + warp;
+
+ vec2 p1 = baseUV * 12.0;
+ vec2 id1 = floor(p1);
+ vec2 f1 = fract(p1) - 0.5;
+ vec2 slope1 = vec2(hash(id1 + 1.2), hash(id1 + 1.3)) * 2.0 - 1.0;
+ float z1 = dot(f1, slope1) * 3.5;
+ float edge1 = min(0.5 - abs(f1.x), 0.5 - abs(f1.y));
+ float hash1 = hash(id1 + 1.1);
+ float use1 = step(maxZ, z1);
+ maxZ = mix(maxZ, z1, use1);
+ bestSlope = mix(bestSlope, slope1, use1);
+ bestHash = mix(bestHash, hash1, use1);
+ bestEdge = mix(bestEdge, edge1, use1);
+
+ vec2 p2 = (ROT_0_78 * baseUV) * 15.0;
+ vec2 id2 = floor(p2);
+ vec2 f2 = fract(p2) - 0.5;
+ vec2 slope2 = vec2(hash(id2 + 2.2), hash(id2 + 2.3)) * 2.0 - 1.0;
+ float z2 = 0.8 + dot(f2, slope2) * 3.5;
+ float edge2 = min(0.5 - abs(f2.x), 0.5 - abs(f2.y));
+ float hash2 = hash(id2 + 2.1);
+ float use2 = step(maxZ, z2);
+ maxZ = mix(maxZ, z2, use2);
+ bestSlope = mix(bestSlope, slope2, use2);
+ bestHash = mix(bestHash, hash2, use2);
+ bestEdge = mix(bestEdge, edge2, use2);
+
+ vec2 p3 = (ROT_M0_5 * baseUV) * 18.0;
+ vec2 id3 = floor(p3);
+ vec2 f3 = fract(p3) - 0.5;
+ vec2 slope3 = vec2(hash(id3 + 3.2), hash(id3 + 3.3)) * 2.0 - 1.0;
+ float z3 = 1.6 + dot(f3, slope3) * 3.5;
+ float edge3 = min(0.5 - abs(f3.x), 0.5 - abs(f3.y));
+ float hash3 = hash(id3 + 3.1);
+ float use3 = step(maxZ, z3);
+ maxZ = mix(maxZ, z3, use3);
+ bestSlope = mix(bestSlope, slope3, use3);
+ bestHash = mix(bestHash, hash3, use3);
+ bestEdge = mix(bestEdge, edge3, use3);
+
+ vec2 p4 = (ROT_1_2 * baseUV) * 21.0;
+ vec2 id4 = floor(p4);
+ vec2 f4 = fract(p4) - 0.5;
+ vec2 slope4 = vec2(hash(id4 + 4.2), hash(id4 + 4.3)) * 2.0 - 1.0;
+ float z4 = 2.4 + dot(f4, slope4) * 3.5;
+ float edge4 = min(0.5 - abs(f4.x), 0.5 - abs(f4.y));
+ float hash4 = hash(id4 + 4.1);
+ float use4 = step(maxZ, z4);
+ maxZ = mix(maxZ, z4, use4);
+ bestSlope = mix(bestSlope, slope4, use4);
+ bestHash = mix(bestHash, hash4, use4);
+ bestEdge = mix(bestEdge, edge4, use4);
+
+ float alignment = dot(tilt, safeNormalize(bestSlope + vec2(0.001)));
+ 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 + dot(uv, vec2(2.1, -1.7)));
+ float ambient = 0.15 + 0.25 * max(0.0, lightCycle);
+ float flash = pow(max(0.0, lightCycle), 5.0);
+ vec3 finalColor = baseColor * ambient;
+ finalColor += mix(baseColor, vec3(1.0, 0.95, 0.85), 0.6) * flash * 0.55;
+ finalColor += baseColor * smoothstep(0.05, 0.0, bestEdge) * flash * 0.4;
+ return finalColor;
+}
+
+vec3 sparkleLayer(vec2 aspectUV, vec2 tilt, vec2 tiltDir) {
+ vec2 sparkleGrid = aspectUV * 7.0;
+ vec2 grid = floor(sparkleGrid);
+ vec2 cell = fract(sparkleGrid) - 0.5;
+ float sparkleHash = hash(grid);
+ vec2 sparkleNormal = safeNormalize(vec2(sparkleHash * 2.0 - 1.0, hash(grid + 19.7) * 2.0 - 1.0));
+ float rarity = step(0.965, hash(grid + 0.31));
+ float alignment = max(0.0, dot(tiltDir, sparkleNormal));
+ float twinkle = 0.45 + 0.55 * sin(dot(cell, vec2(21.0, -17.0)) + dot(tilt, vec2(5.3, -4.1)) + sparkleHash * TWO_PI);
+ float sides = 5.0 + floor(hash(grid + 9.4) * 4.0);
+ float aspect = 0.75 + hash(grid + 13.1) * 0.55;
+ float shapeMask = sparklePolygonMask(cell, sides, aspect, hash(grid + 5.1) * TWO_PI);
+ float sparkleMask = pow(alignment, 4.8) * twinkle * rarity * shapeMask;
+ vec3 sparkleColor = mix(vec3(1.0), rainbow(sparkleHash + dot(tilt, vec2(0.2, -0.15)), 0.55, 1.0), 0.20);
+ return sparkleColor * sparkleMask * uSparkle * 0.25 * 0.70;
+}
+
+void main() {
+ vec2 frag = FlutterFragCoord().xy;
+ vec2 uv = frag / uSize;
+ vec2 fromCenter = uv - vec2(0.5);
+ float maxDim = max(uSize.x, uSize.y);
+ vec2 aspectUV = frag / maxDim;
+ float tiltMag = length(uTilt);
+ vec2 tiltDir = safeNormalize(uTilt + vec2(0.001));
+ float highlightDistance = length(uv - (vec2(0.5) + (uTilt * 0.35)));
+ float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), 4.0);
+
+ vec3 base = styleBase(aspectUV, uTilt);
+ vec3 sparkle = sparkleLayer(aspectUV, uTilt, tiltDir);
+ float baseLuma = dot(base, vec3(0.299, 0.587, 0.114));
+ vec3 chromaAdjusted = mix(vec3(baseLuma), base, 0.2 + 0.8 * uPrismatic);
+
+ vec2 normal2d = safeNormalize(fromCenter + vec2(0.001));
+ float directional = 0.5 + 0.5 * dot(normal2d, tiltDir);
+ float tiltLighting = mix(0.86, 1.20, pow(directional, 1.2));
+ chromaAdjusted *= tiltLighting;
+
+ float edgeSpec = pow(1.0 - directional, 2.2);
+ vec3 specular = vec3(specularMask) * (0.3 + 0.7 * edgeSpec) * uSpecular * (0.35 + 0.65 * tiltMag) * 1.65;
+ 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 * 0.90 * (0.08 + 0.14 * edgeSpec);
+ float microMask = 0.5 + 0.5 * sin(dot(aspectUV, vec2(137.0, 57.0)) + noise(aspectUV * 14.0) * 4.0 + dot(uTilt, vec2(9.0, 4.0)) * 2.0);
+ vec3 microShimmer = rainbow(noise(aspectUV * 10.0) + dot(uTilt, vec2(0.4, -0.3)) * 0.3, 0.65, 1.0) * microMask * uDiffraction * 0.08;
+
+ vec3 styleColor = chromaAdjusted + microShimmer + prismaticTint + (specular * 0.22) + sparkle;
+ vec3 mappedColor = styleColor / (1.0 + styleColor * 0.3);
+ float luma = dot(mappedColor, vec3(0.299, 0.587, 0.114));
+ vec3 vibrantColor = mix(vec3(luma), mappedColor, 1.4);
+ float foilBrightness = dot(vibrantColor, vec3(0.333));
+ float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35) * clamp(uOpacity, 0.0, 1.0);
+ vec3 finalColor = clamp(vibrantColor, 0.0, 1.0);
+ fragColor = vec4(finalColor * alpha, alpha);
+}
\ No newline at end of file
diff --git a/shaders/shiny_holographic_silver.frag b/shaders/shiny_holographic_silver.frag
new file mode 100644
index 0000000..0278d88
--- /dev/null
+++ b/shaders/shiny_holographic_silver.frag
@@ -0,0 +1,121 @@
+#version 460 core
+#include
+
+out vec4 fragColor;
+
+uniform vec2 uSize;
+uniform vec2 uTilt;
+uniform float uPrismatic;
+uniform float uSparkle;
+uniform float uSpecular;
+uniform float uDiffraction;
+uniform float uOpacity;
+
+#define TWO_PI 6.28318530718
+
+float hash(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+ p3 += dot(p3, p3.yzx + 33.33);
+ return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ float a = hash(i);
+ float b = hash(i + vec2(1.0, 0.0));
+ float c = hash(i + vec2(0.0, 1.0));
+ float d = hash(i + vec2(1.0, 1.0));
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
+}
+
+vec3 hsv2rgb(float h, float s, float v) {
+ vec3 k = vec3(1.0, 2.0 / 3.0, 1.0 / 3.0);
+ vec3 p = abs(fract(vec3(h) + k) * 6.0 - 3.0);
+ return v * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), s);
+}
+
+vec3 rainbow(float phase, float saturation, float value) {
+ return hsv2rgb(fract(phase), saturation, value);
+}
+
+vec2 safeNormalize(vec2 p) {
+ return p * inversesqrt(max(dot(p, p), 0.000001));
+}
+
+float sparkleStarMask(vec2 p, float points, float innerRatio) {
+ float angle = atan(p.y, p.x);
+ float radius = dot(p, p);
+ float spikes = 0.5 + 0.5 * cos(angle * points);
+ float starRadius = mix(0.34 * innerRatio, 0.34, pow(spikes, 1.4));
+ return smoothstep(0.0009, -0.0009, radius - (starRadius * starRadius));
+}
+
+vec3 styleBase(vec2 uv, vec2 tilt) {
+ vec2 p = uv * 2.0 - 1.0;
+ 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 + 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);
+ vec3 silver = vec3(0.62, 0.65, 0.70) * (0.85 + 0.15 * flowB);
+ return mix(silver, holo, 0.65 * blend);
+}
+
+vec3 sparkleLayer(vec2 aspectUV, vec2 tilt, vec2 tiltDir) {
+ vec2 sparkleGrid = aspectUV * 18.0;
+ vec2 grid = floor(sparkleGrid);
+ vec2 cell = fract(sparkleGrid) - 0.5;
+ float sparkleHash = hash(grid);
+ vec2 sparkleNormal = safeNormalize(vec2(sparkleHash * 2.0 - 1.0, hash(grid + 19.7) * 2.0 - 1.0));
+ float rarity = step(0.83, hash(grid + 0.31));
+ float alignment = max(0.0, dot(tiltDir, sparkleNormal));
+ float twinkle = 0.45 + 0.55 * sin(dot(cell, vec2(21.0, -17.0)) + dot(tilt, vec2(5.3, -4.1)) + sparkleHash * TWO_PI);
+ float shapeMask = sparkleStarMask(cell, 8.0, 0.42);
+ float sparkleMask = pow(alignment, 3.2) * twinkle * rarity * shapeMask;
+ vec3 sparkleColor = mix(vec3(1.0), rainbow(sparkleHash + dot(tilt, vec2(0.2, -0.15)), 0.55, 1.0), 0.35);
+ return sparkleColor * sparkleMask * uSparkle;
+}
+
+void main() {
+ vec2 frag = FlutterFragCoord().xy;
+ vec2 uv = frag / uSize;
+ vec2 fromCenter = uv - vec2(0.5);
+ float maxDim = max(uSize.x, uSize.y);
+ vec2 aspectUV = frag / maxDim;
+ float tiltMag = length(uTilt);
+ vec2 tiltDir = safeNormalize(uTilt + vec2(0.001));
+ float highlightDistance = length(uv - (vec2(0.5) + (uTilt * 0.35)));
+ float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), 3.0);
+
+ vec3 base = styleBase(aspectUV, uTilt);
+ vec3 sparkle = sparkleLayer(aspectUV, uTilt, tiltDir);
+ float baseLuma = dot(base, vec3(0.299, 0.587, 0.114));
+ vec3 chromaAdjusted = mix(vec3(baseLuma), base, 0.2 + 0.8 * uPrismatic);
+
+ vec2 normal2d = safeNormalize(fromCenter + vec2(0.001));
+ float directional = 0.5 + 0.5 * dot(normal2d, tiltDir);
+ float tiltLighting = mix(0.86, 1.20, pow(directional, 1.2));
+ chromaAdjusted *= tiltLighting;
+
+ float edgeSpec = pow(1.0 - directional, 2.2);
+ vec3 specular = vec3(specularMask) * (0.3 + 0.7 * edgeSpec) * uSpecular * (0.35 + 0.65 * tiltMag);
+ 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 * 1.10 * (0.08 + 0.14 * edgeSpec);
+ float microMask = 0.5 + 0.5 * sin(dot(aspectUV, vec2(137.0, 57.0)) + noise(aspectUV * 14.0) * 4.0 + dot(uTilt, vec2(9.0, 4.0)) * 2.0);
+ vec3 microShimmer = rainbow(noise(aspectUV * 10.0) + dot(uTilt, vec2(0.4, -0.3)) * 0.3, 0.65, 1.0) * microMask * uDiffraction * 0.18;
+
+ vec3 styleColor = chromaAdjusted + microShimmer + prismaticTint + (specular * 0.22) + sparkle;
+ vec3 mappedColor = styleColor / (1.0 + styleColor * 0.3);
+ float luma = dot(mappedColor, vec3(0.299, 0.587, 0.114));
+ vec3 vibrantColor = mix(vec3(luma), mappedColor, 1.4);
+ float foilBrightness = dot(vibrantColor, vec3(0.333));
+ float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35) * clamp(uOpacity, 0.0, 1.0);
+ vec3 finalColor = clamp(vibrantColor, 0.0, 1.0);
+ fragColor = vec4(finalColor * alpha, alpha);
+}
\ No newline at end of file
diff --git a/shaders/shiny_silver_mosaic.frag b/shaders/shiny_silver_mosaic.frag
new file mode 100644
index 0000000..f6902ca
--- /dev/null
+++ b/shaders/shiny_silver_mosaic.frag
@@ -0,0 +1,139 @@
+#version 460 core
+#include
+
+out vec4 fragColor;
+
+uniform vec2 uSize;
+uniform vec2 uTilt;
+uniform float uPrismatic;
+uniform float uSparkle;
+uniform float uSpecular;
+uniform float uDiffraction;
+uniform float uOpacity;
+
+#define TWO_PI 6.28318530718
+
+float hash(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+ p3 += dot(p3, p3.yzx + 33.33);
+ return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ float a = hash(i);
+ float b = hash(i + vec2(1.0, 0.0));
+ float c = hash(i + vec2(0.0, 1.0));
+ float d = hash(i + vec2(1.0, 1.0));
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
+}
+
+vec3 hsv2rgb(float h, float s, float v) {
+ vec3 k = vec3(1.0, 2.0 / 3.0, 1.0 / 3.0);
+ vec3 p = abs(fract(vec3(h) + k) * 6.0 - 3.0);
+ return v * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), s);
+}
+
+vec3 rainbow(float phase, float saturation, float value) {
+ return hsv2rgb(fract(phase), saturation, value);
+}
+
+vec2 safeNormalize(vec2 p) {
+ return p * inversesqrt(max(dot(p, p), 0.000001));
+}
+
+float sdRegularPolygon(vec2 p, float sides, float radius) {
+ float angle = atan(p.y, p.x);
+ float sector = TWO_PI / sides;
+ return cos(floor(0.5 + angle / sector) * sector - angle) * length(p) - radius;
+}
+
+float sparklePolygonMask(vec2 p, float sides, float aspectRatio, float rotation) {
+ float c = cos(rotation);
+ float s = sin(rotation);
+ vec2 q = vec2(c * p.x - s * p.y, s * p.x + c * p.y);
+ q.x /= aspectRatio;
+ float poly = sdRegularPolygon(q, sides, 0.26);
+ return smoothstep(0.03, -0.03, poly);
+}
+
+vec3 styleBase(vec2 uv, vec2 tilt) {
+ vec2 g = uv * 6.0;
+ vec2 id = floor(g);
+ vec2 f = fract(g) - 0.5;
+ vec2 rotatedF = vec2(-f.y, f.x);
+ f = mix(f, rotatedF, step(0.5, mod(id.x + id.y, 2.0)));
+ vec2 radialDir = safeNormalize(f + vec2(0.0001));
+ vec2 lightDir = safeNormalize(tilt + vec2(0.001));
+ float alignment = abs(dot(radialDir, lightDir));
+ float highlight = pow(alignment, 12.0);
+ float angleDiff = acos(clamp(alignment, 0.0, 1.0));
+ float side = sign(radialDir.x * lightDir.y - radialDir.y * lightDir.x);
+ 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);
+ vec3 core = vec3(1.0) * smoothstep(0.98, 1.0, alignment);
+ vec3 foilBase = vec3(0.25, 0.27, 0.30);
+ vec3 finalColor = foilBase + (holoColor * highlight * 1.5) + (core * 0.8);
+ float edgeX = 0.5 - abs(f.x);
+ float edgeY = 0.5 - abs(f.y);
+ float border = smoothstep(0.0, 0.015, min(edgeX, edgeY));
+ float dist = length(f);
+ finalColor *= mix(0.7, 1.0, 1.0 - smoothstep(0.0, 0.5, dist) * 0.3);
+ return clamp(finalColor * border, 0.0, 1.0);
+}
+
+vec3 sparkleLayer(vec2 aspectUV, vec2 tilt, vec2 tiltDir) {
+ vec2 sparkleGrid = aspectUV * 14.0;
+ vec2 grid = floor(sparkleGrid);
+ vec2 cell = fract(sparkleGrid) - 0.5;
+ float sparkleHash = hash(grid);
+ vec2 sparkleNormal = safeNormalize(vec2(sparkleHash * 2.0 - 1.0, hash(grid + 19.7) * 2.0 - 1.0));
+ float rarity = step(0.88, hash(grid + 0.31));
+ float alignment = max(0.0, dot(tiltDir, sparkleNormal));
+ float twinkle = 0.45 + 0.55 * sin(dot(cell, vec2(21.0, -17.0)) + dot(tilt, vec2(5.3, -4.1)) + sparkleHash * TWO_PI);
+ float shapeMask = sparklePolygonMask(cell, 6.0, 1.0, 0.0);
+ float sparkleMask = pow(alignment, 3.8) * twinkle * rarity * shapeMask;
+ vec3 sparkleColor = mix(vec3(1.0), rainbow(sparkleHash + dot(tilt, vec2(0.2, -0.15)), 0.55, 1.0), 0.35);
+ return sparkleColor * sparkleMask * uSparkle * 0.55 * 0.80;
+}
+
+void main() {
+ vec2 frag = FlutterFragCoord().xy;
+ vec2 uv = frag / uSize;
+ vec2 fromCenter = uv - vec2(0.5);
+ float maxDim = max(uSize.x, uSize.y);
+ vec2 aspectUV = frag / maxDim;
+ float tiltMag = length(uTilt);
+ vec2 tiltDir = safeNormalize(uTilt + vec2(0.001));
+ float highlightDistance = length(uv - (vec2(0.5) + (uTilt * 0.35)));
+ float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), 3.4);
+
+ vec3 base = styleBase(aspectUV, uTilt);
+ vec3 sparkle = sparkleLayer(aspectUV, uTilt, tiltDir);
+ float baseLuma = dot(base, vec3(0.299, 0.587, 0.114));
+ vec3 chromaAdjusted = mix(vec3(baseLuma), base, 0.2 + 0.8 * uPrismatic);
+
+ vec2 normal2d = safeNormalize(fromCenter + vec2(0.001));
+ float directional = 0.5 + 0.5 * dot(normal2d, tiltDir);
+ float tiltLighting = mix(0.86, 1.20, pow(directional, 1.2));
+ chromaAdjusted *= tiltLighting;
+
+ float edgeSpec = pow(1.0 - directional, 2.2);
+ vec3 specular = vec3(specularMask) * (0.3 + 0.7 * edgeSpec) * uSpecular * (0.35 + 0.65 * tiltMag) * 1.15;
+ 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 * 1.30 * 1.15 * (0.08 + 0.14 * edgeSpec);
+ float microMask = 0.5 + 0.5 * sin(dot(aspectUV, vec2(137.0, 57.0)) + noise(aspectUV * 14.0) * 4.0 + dot(uTilt, vec2(9.0, 4.0)) * 2.0);
+ vec3 microShimmer = rainbow(noise(aspectUV * 10.0) + dot(uTilt, vec2(0.4, -0.3)) * 0.3, 0.65, 1.0) * microMask * uDiffraction * 0.18;
+
+ vec3 styleColor = chromaAdjusted + microShimmer + prismaticTint + (specular * 0.22) + sparkle;
+ vec3 mappedColor = styleColor / (1.0 + styleColor * 0.3);
+ float luma = dot(mappedColor, vec3(0.299, 0.587, 0.114));
+ vec3 vibrantColor = mix(vec3(luma), mappedColor, 1.4);
+ float foilBrightness = dot(vibrantColor, vec3(0.333));
+ float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35) * clamp(uOpacity, 0.0, 1.0);
+ vec3 finalColor = clamp(vibrantColor, 0.0, 1.0);
+ fragColor = vec4(finalColor * alpha, alpha);
+}
\ No newline at end of file
diff --git a/shaders/shiny_super_gold_vinyl.frag b/shaders/shiny_super_gold_vinyl.frag
new file mode 100644
index 0000000..3dc6cad
--- /dev/null
+++ b/shaders/shiny_super_gold_vinyl.frag
@@ -0,0 +1,131 @@
+#version 460 core
+#include
+
+out vec4 fragColor;
+
+uniform vec2 uSize;
+uniform vec2 uTilt;
+uniform float uPrismatic;
+uniform float uSparkle;
+uniform float uSpecular;
+uniform float uDiffraction;
+uniform float uOpacity;
+
+#define TWO_PI 6.28318530718
+
+float hash(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+ p3 += dot(p3, p3.yzx + 33.33);
+ return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ float a = hash(i);
+ float b = hash(i + vec2(1.0, 0.0));
+ float c = hash(i + vec2(0.0, 1.0));
+ float d = hash(i + vec2(1.0, 1.0));
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
+}
+
+vec3 hsv2rgb(float h, float s, float v) {
+ vec3 k = vec3(1.0, 2.0 / 3.0, 1.0 / 3.0);
+ vec3 p = abs(fract(vec3(h) + k) * 6.0 - 3.0);
+ return v * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), s);
+}
+
+vec3 rainbow(float phase, float saturation, float value) {
+ return hsv2rgb(fract(phase), saturation, value);
+}
+
+vec2 safeNormalize(vec2 p) {
+ return p * inversesqrt(max(dot(p, p), 0.000001));
+}
+
+float sdBox(vec2 p, vec2 b) {
+ vec2 d = abs(p) - b;
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+float sparkleRectangleMask(vec2 p, float halfWidth, float halfHeight, float rotation) {
+ float c = cos(rotation);
+ float s = sin(rotation);
+ vec2 q = vec2(c * p.x - s * p.y, s * p.x + c * p.y);
+ float body = sdBox(q, vec2(halfWidth, halfHeight));
+ float bevel = sdBox(q, vec2(halfWidth * 0.78, halfHeight * 0.78));
+ float bodyMask = smoothstep(0.03, -0.03, body);
+ float centerLine = 1.0 - smoothstep(0.0, halfHeight * 1.25, abs(q.y));
+ float endFade = 1.0 - smoothstep(halfWidth * 0.55, halfWidth * 1.05, abs(q.x));
+ float streak = centerLine * endFade;
+ float bevelRing = smoothstep(0.025, -0.025, body) - smoothstep(0.025, -0.025, bevel);
+ return clamp(bodyMask * (0.58 + 0.42 * streak) + bevelRing * 0.25, 0.0, 1.0);
+}
+
+vec3 styleBase(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;
+ float d2 = dot(f, f);
+ 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);
+ 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);
+ return gold * (0.75 + 0.25 * sheen) + warm * dotMask * 0.28 + shadow * emboss * 0.08;
+}
+
+vec3 sparkleLayer(vec2 aspectUV, vec2 tilt, vec2 tiltDir) {
+ vec2 sparkleGrid = aspectUV * 18.0;
+ vec2 grid = floor(sparkleGrid);
+ vec2 cell = fract(sparkleGrid) - 0.5;
+ float sparkleHash = hash(grid);
+ vec2 sparkleNormal = safeNormalize(vec2(sparkleHash * 2.0 - 1.0, hash(grid + 19.7) * 2.0 - 1.0));
+ float rarity = step(0.83, hash(grid + 0.31));
+ float alignment = max(0.0, dot(tiltDir, sparkleNormal));
+ float twinkle = 0.45 + 0.55 * sin(dot(cell, vec2(21.0, -17.0)) + dot(tilt, vec2(5.3, -4.1)) + sparkleHash * TWO_PI);
+ float shapeMask = sparkleRectangleMask(cell, 0.34, 0.045, hash(grid + 5.1) * TWO_PI);
+ float sparkleMask = pow(alignment, 3.2) * twinkle * rarity * shapeMask;
+ vec3 sparkleColor = mix(vec3(1.0), rainbow(sparkleHash + dot(tilt, vec2(0.2, -0.15)), 0.55, 1.0), 0.35);
+ return sparkleColor * sparkleMask * uSparkle * 0.70;
+}
+
+void main() {
+ vec2 frag = FlutterFragCoord().xy;
+ vec2 uv = frag / uSize;
+ vec2 fromCenter = uv - vec2(0.5);
+ float maxDim = max(uSize.x, uSize.y);
+ vec2 aspectUV = frag / maxDim;
+ float tiltMag = length(uTilt);
+ vec2 tiltDir = safeNormalize(uTilt + vec2(0.001));
+ float highlightDistance = length(uv - (vec2(0.5) + (uTilt * 0.35)));
+ float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), 3.6);
+
+ vec3 base = styleBase(aspectUV, uTilt);
+ vec3 sparkle = sparkleLayer(aspectUV, uTilt, tiltDir);
+ float baseLuma = dot(base, vec3(0.299, 0.587, 0.114));
+ vec3 chromaAdjusted = mix(vec3(baseLuma), base, 0.2 + 0.8 * uPrismatic);
+
+ vec2 normal2d = safeNormalize(fromCenter + vec2(0.001));
+ float directional = 0.5 + 0.5 * dot(normal2d, tiltDir);
+ float tiltLighting = mix(0.86, 1.20, pow(directional, 1.2));
+ chromaAdjusted *= tiltLighting;
+
+ float edgeSpec = pow(1.0 - directional, 2.2);
+ vec3 specular = vec3(specularMask) * (0.3 + 0.7 * edgeSpec) * uSpecular * (0.35 + 0.65 * tiltMag) * 1.50;
+ 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 * 1.18 * 0.65 * (0.08 + 0.14 * edgeSpec);
+ float microMask = 0.5 + 0.5 * sin(dot(aspectUV, vec2(137.0, 57.0)) + noise(aspectUV * 14.0) * 4.0 + dot(uTilt, vec2(9.0, 4.0)) * 2.0);
+ vec3 microShimmer = rainbow(noise(aspectUV * 10.0) + dot(uTilt, vec2(0.4, -0.3)) * 0.3, 0.65, 1.0) * microMask * uDiffraction * 0.18;
+
+ vec3 styleColor = chromaAdjusted + microShimmer + prismaticTint + (specular * 0.22) + sparkle;
+ vec3 mappedColor = styleColor / (1.0 + styleColor * 0.3);
+ float luma = dot(mappedColor, vec3(0.299, 0.587, 0.114));
+ vec3 vibrantColor = mix(vec3(luma), mappedColor, 1.4);
+ float foilBrightness = dot(vibrantColor, vec3(0.333));
+ float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35) * clamp(uOpacity, 0.0, 1.0);
+ vec3 finalColor = clamp(vibrantColor, 0.0, 1.0);
+ fragColor = vec4(finalColor * alpha, alpha);
+}
\ No newline at end of file
diff --git a/test/shiny_widget_test.dart b/test/shiny_widget_test.dart
index 732b613..4263148 100644
--- a/test/shiny_widget_test.dart
+++ b/test/shiny_widget_test.dart
@@ -8,22 +8,12 @@ const ValueKey _cardTransformKey =
ValueKey('holo_shiny.card.transform');
void main() {
- test('default holograph style is crackedIce', () {
+ test('default profile is crackedIce', () {
const Shiny shiny = Shiny(child: SizedBox.shrink());
const ShinyCard shinyCard = ShinyCard();
- expect(shiny.style, HolographStyle.crackedIce);
- expect(shinyCard.style, HolographStyle.crackedIce);
- });
-
- test('default sparkle shape is none', () {
- const Shiny shiny = Shiny(child: SizedBox.shrink());
- const ShinyCard shinyCard = ShinyCard();
-
- expect(shiny.sparkleShape.kind, SparkleShapeKind.none);
- expect(shiny.sparkleShape.primary, 0.0);
- expect(shinyCard.sparkleShape.kind, SparkleShapeKind.none);
- expect(shinyCard.sparkleShape.primary, 0.0);
+ expect(shiny.profile, ShinyProfile.crackedIce);
+ expect(shinyCard.profile, ShinyProfile.crackedIce);
});
test('default opacity is 1.0', () {
@@ -34,27 +24,17 @@ void main() {
expect(shinyCard.opacity, 1.0);
});
- test('custom sparkle factories produce expected specs', () {
- final SparkleShapeSpec star =
- SparkleShapeSpec.customStar(points: 7, innerRatio: 0.33);
- final SparkleShapeSpec polygon = SparkleShapeSpec.customPolygon(
- sides: 8, aspectRatio: 1.2, rotation: 0.4);
- final SparkleShapeSpec rectangle = SparkleShapeSpec.customRectangle(
- halfWidth: 0.20, halfHeight: 0.05, rotationJitter: 0.7);
+ test('custom profile can be set on both widgets', () {
+ const Shiny shiny = Shiny(
+ profile: ShinyProfile.superGoldVinyl,
+ child: SizedBox.shrink(),
+ );
+ const ShinyCard shinyCard = ShinyCard(
+ profile: ShinyProfile.silverMosaic,
+ );
- expect(star.kind, SparkleShapeKind.star);
- expect(star.primary, 7.0);
- expect(star.secondary, 0.33);
-
- expect(polygon.kind, SparkleShapeKind.polygon);
- expect(polygon.primary, 8.0);
- expect(polygon.secondary, 1.2);
- expect(polygon.tertiary, 0.4);
-
- expect(rectangle.kind, SparkleShapeKind.rectangle);
- expect(rectangle.primary, 0.20);
- expect(rectangle.secondary, 0.05);
- expect(rectangle.tertiary, 0.7);
+ expect(shiny.profile, ShinyProfile.superGoldVinyl);
+ expect(shinyCard.profile, ShinyProfile.silverMosaic);
});
testWidgets('Shiny wraps any child without card transform',