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',