Optimize shaders
CI / quality (push) Failing after 5m56s

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-04-21 17:40:21 +02:00
parent 37e0814483
commit 9564096a5c
40 changed files with 678 additions and 1442 deletions
+1
View File
@@ -6,3 +6,4 @@
- Added `Shiny` widget with shader-driven holographic effects. - Added `Shiny` widget with shader-driven holographic effects.
- Added optional `ShinyController` and `SensorTiltController` motion APIs. - Added optional `ShinyController` and `SensorTiltController` motion APIs.
- Added package example app and baseline tests. - Added package example app and baseline tests.
- Replaced the monolithic shader with specialized profile shaders and a profile-based API.
+9 -31
View File
@@ -14,8 +14,7 @@ It exposes two widget layers:
- Position-driven rendering: visuals respond to tilt/input position with no internal time animation - Position-driven rendering: visuals respond to tilt/input position with no internal time animation
- Generic wrapper API for any widget via `Shiny(child: ...)` - Generic wrapper API for any widget via `Shiny(child: ...)`
- Card API with `background`, `foreground`, `shape`, and drag tilt via `ShinyCard` - 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 - Four specialized material profiles backed by dedicated shader assets
- Custom sparkle shapes via parameterized `SparkleShapeSpec` factories
- Global shader opacity control via `opacity` - Global shader opacity control via `opacity`
- Cross-platform Flutter support (mobile, web, desktop) - Cross-platform Flutter support (mobile, web, desktop)
@@ -62,7 +61,7 @@ ShinyCard(
controller: controller, controller: controller,
background: Container(color: const Color(0xFF1B2D4B)), background: Container(color: const Color(0xFF1B2D4B)),
foreground: const Center(child: Text('HOLO')), foreground: const Center(child: Text('HOLO')),
sparkleShape: SparkleShapeSpec.none, profile: ShinyProfile.crackedIce,
opacity: 1.0, opacity: 1.0,
) )
``` ```
@@ -78,37 +77,17 @@ Shiny(
) )
``` ```
## Sparkle Shapes ## Profiles
Use built-in sparkle presets: Choose a precompiled material profile:
```dart ```dart
ShinyCard( ShinyCard(
sparkleShape: SparkleShapeSpec.none, profile: ShinyProfile.holographicSilver,
) )
ShinyCard( ShinyCard(
sparkleShape: SparkleShapeSpec.hexagon, profile: ShinyProfile.superGoldVinyl,
)
```
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),
) )
``` ```
@@ -123,13 +102,13 @@ final ShinyController controller = ShinyController(tiltStream: input.stream);
- `Shiny`: generic effect wrapper - `Shiny`: generic effect wrapper
- `ShinyCard`: shape + rotation + composition convenience widget - `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 - `ShinyController`: optional source selection for tilt
- `SensorTiltController`: low-level sensor fusion stream utility - `SensorTiltController`: low-level sensor fusion stream utility
Important defaults: Important defaults:
- `sparkleShape` defaults to `SparkleShapeSpec.none` (no sparkles) - `profile` defaults to `ShinyProfile.crackedIce`
- `opacity` defaults to `1.0` - `opacity` defaults to `1.0`
## Platform Notes ## Platform Notes
@@ -147,8 +126,7 @@ See the package example app in `example/` for:
- Sensor-driven motion - Sensor-driven motion
- External stream override - External stream override
- Custom card shape/background/foreground composition - Custom card shape/background/foreground composition
- Built-in sparkle shape toggles in the demo UI - Profile switching in the demo UI
- User-defined sparkle shape presets using `SparkleShapeSpec`
## Publishing Notes ## Publishing Notes
-14
View File
@@ -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
-44
View File
@@ -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 = "../.."
}
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
-45
View File
@@ -1,45 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="holo_shiny"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -1,5 +0,0 @@
package com.example.holo_shiny
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
-24
View File
@@ -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<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
-2
View File
@@ -1,2 +0,0 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
-5
View File
@@ -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
-26
View File
@@ -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")
+24 -97
View File
@@ -30,13 +30,8 @@ class ExampleHome extends StatefulWidget {
} }
class _ExampleHomeState extends State<ExampleHome> { class _ExampleHomeState extends State<ExampleHome> {
/// Sensor-driven controller used by the first two demo surfaces.
late final ShinyController _sensorController; late final ShinyController _sensorController;
/// Manual stream used by tilt preset buttons.
late final StreamController<Offset> _externalTiltController; late final StreamController<Offset> _externalTiltController;
/// Controller backed by [_externalTiltController].
late final ShinyController _overrideController; late final ShinyController _overrideController;
double _prismatic = 0.8; double _prismatic = 0.8;
@@ -44,20 +39,7 @@ class _ExampleHomeState extends State<ExampleHome> {
double _specular = 0.8; double _specular = 0.8;
double _diffraction = 0.8; double _diffraction = 0.8;
double _opacity = 1.0; double _opacity = 1.0;
HolographStyle _style = HolographStyle.crackedIce; ShinyProfile _profile = ShinyProfile.crackedIce;
SparkleShapeSpec _sparkleShape = SparkleShapeSpec.none;
static const Map<String, SparkleShapeSpec> _sparkleChoices =
<String, SparkleShapeSpec>{
'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,
};
@override @override
void initState() { void initState() {
@@ -95,13 +77,12 @@ class _ExampleHomeState extends State<ExampleHome> {
height: 120, height: 120,
child: Shiny( child: Shiny(
controller: _sensorController, controller: _sensorController,
style: _style, profile: _profile,
prismatic: _prismatic, prismatic: _prismatic,
sparkle: _sparkle, sparkle: _sparkle,
specular: _specular, specular: _specular,
diffraction: _diffraction, diffraction: _diffraction,
opacity: _opacity, opacity: _opacity,
sparkleShape: _sparkleShape,
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -130,8 +111,7 @@ class _ExampleHomeState extends State<ExampleHome> {
Center( Center(
child: ShinyCard( child: ShinyCard(
controller: _sensorController, controller: _sensorController,
style: _style, profile: _profile,
sparkleShape: _sparkleShape,
background: const _DemoCardBackground(), background: const _DemoCardBackground(),
foreground: const _RareBadge(), foreground: const _RareBadge(),
prismatic: _prismatic, prismatic: _prismatic,
@@ -143,20 +123,10 @@ class _ExampleHomeState extends State<ExampleHome> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_StylePicker( _StylePicker(
selectedStyle: _style, selectedProfile: _profile,
onChanged: (HolographStyle style) { onChanged: (ShinyProfile profile) {
setState(() { setState(() {
_style = style; _profile = profile;
});
},
),
const SizedBox(height: 8),
_SparkleShapePicker(
selectedShape: _sparkleShape,
choices: _sparkleChoices,
onChanged: (SparkleShapeSpec shape) {
setState(() {
_sparkleShape = shape;
}); });
}, },
), ),
@@ -198,8 +168,7 @@ class _ExampleHomeState extends State<ExampleHome> {
Center( Center(
child: ShinyCard( child: ShinyCard(
controller: _overrideController, controller: _overrideController,
style: _style, profile: _profile,
sparkleShape: _sparkleShape,
prismatic: _prismatic, prismatic: _prismatic,
sparkle: _sparkle, sparkle: _sparkle,
specular: _specular, specular: _specular,
@@ -258,31 +227,30 @@ class _LabeledSlider extends StatelessWidget {
} }
class _StylePicker extends StatelessWidget { class _StylePicker extends StatelessWidget {
/// Creates the style selection control.
const _StylePicker({ const _StylePicker({
required this.selectedStyle, required this.selectedProfile,
required this.onChanged, required this.onChanged,
}); });
final HolographStyle selectedStyle; final ShinyProfile selectedProfile;
final ValueChanged<HolographStyle> onChanged; final ValueChanged<ShinyProfile> onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const Text('Style'), const Text('Profile'),
const SizedBox(height: 6), const SizedBox(height: 6),
SegmentedButton<HolographStyle>( SegmentedButton<ShinyProfile>(
segments: HolographStyle.values segments: ShinyProfile.values
.map((HolographStyle style) => ButtonSegment<HolographStyle>( .map((ShinyProfile profile) => ButtonSegment<ShinyProfile>(
value: style, value: profile,
label: Text(_styleLabel(style)), label: Text(_profileLabel(profile)),
)) ))
.toList(), .toList(),
selected: <HolographStyle>{selectedStyle}, selected: <ShinyProfile>{selectedProfile},
onSelectionChanged: (Set<HolographStyle> value) { onSelectionChanged: (Set<ShinyProfile> value) {
if (value.isNotEmpty) { if (value.isNotEmpty) {
onChanged(value.first); onChanged(value.first);
} }
@@ -294,61 +262,20 @@ class _StylePicker extends StatelessWidget {
); );
} }
String _styleLabel(HolographStyle style) { String _profileLabel(ShinyProfile profile) {
switch (style) { switch (profile) {
case HolographStyle.holographicSilver: case ShinyProfile.holographicSilver:
return 'Holographic Silver'; return 'Holographic Silver';
case HolographStyle.crackedIce: case ShinyProfile.crackedIce:
return 'Cracked Ice'; return 'Cracked Ice';
case HolographStyle.silverMosaic: case ShinyProfile.silverMosaic:
return 'Silver Mosaic'; return 'Silver Mosaic';
case HolographStyle.superGoldVinyl: case ShinyProfile.superGoldVinyl:
return 'Super Gold Vinyl'; 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<String, SparkleShapeSpec> choices;
final ValueChanged<SparkleShapeSpec> onChanged;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Sparkle Shape'),
const SizedBox(height: 6),
Wrap(
spacing: 8,
runSpacing: 8,
children:
choices.entries.map((MapEntry<String, SparkleShapeSpec> 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. /// Circular D-pad control for feeding the external tilt stream.
class _TiltPresetPicker extends StatelessWidget { class _TiltPresetPicker extends StatelessWidget {
const _TiltPresetPicker({required this.onChanged}); const _TiltPresetPicker({required this.onChanged});
-1
View File
@@ -1 +0,0 @@
flutter/ephemeral
-128
View File
@@ -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 "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>: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()
-88
View File
@@ -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}
)
@@ -1,11 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}
@@ -1,15 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
@@ -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 $<TARGET_FILE:${plugin}_plugin>)
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)
-26
View File
@@ -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}")
-6
View File
@@ -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);
}
-148
View File
@@ -1,148 +0,0 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#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));
}
-21
View File
@@ -1,21 +0,0 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
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_
+44 -192
View File
@@ -5,8 +5,8 @@ import 'package:flutter/material.dart';
import 'shiny_controller.dart'; import 'shiny_controller.dart';
/// Available holographic material profiles rendered by the fragment shader. /// Available holographic material profiles rendered by dedicated shader assets.
enum HolographStyle { enum ShinyProfile {
/// Smooth silver foil with soft rainbow flow. /// Smooth silver foil with soft rainbow flow.
holographicSilver, holographicSilver,
@@ -20,168 +20,20 @@ enum HolographStyle {
superGoldVinyl, superGoldVinyl,
} }
/// Sparkle silhouette families used by the shader when drawing glints. extension ShinyProfileShaderAsset on ShinyProfile {
enum SparkleShapeKind { /// Local shader asset path for the profile.
/// Disables sparkle rendering entirely. String get shaderAsset {
none, switch (this) {
case ShinyProfile.holographicSilver:
/// Star-shaped sparkle with configurable point count and inner radius. return 'shaders/shiny_holographic_silver.frag';
star, case ShinyProfile.crackedIce:
return 'shaders/shiny_cracked_ice.frag';
/// Rectangular sparkle streak. case ShinyProfile.silverMosaic:
rectangle, return 'shaders/shiny_silver_mosaic.frag';
case ShinyProfile.superGoldVinyl:
/// Fixed-side polygon sparkle. return 'shaders/shiny_super_gold_vinyl.frag';
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,
);
} }
/// 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. /// Applies holographic shader lighting to any child widget.
@@ -197,8 +49,7 @@ class Shiny extends StatefulWidget {
this.specular = 0.8, this.specular = 0.8,
this.diffraction = 0.8, this.diffraction = 0.8,
this.opacity = 1.0, this.opacity = 1.0,
this.sparkleShape = SparkleShapeSpec.none, this.profile = ShinyProfile.crackedIce,
this.style = HolographStyle.crackedIce,
this.blendMode = BlendMode.screen, this.blendMode = BlendMode.screen,
this.enableShader = true, this.enableShader = true,
}); });
@@ -227,11 +78,8 @@ class Shiny extends StatefulWidget {
/// Global shader opacity multiplier. /// Global shader opacity multiplier.
final double opacity; final double opacity;
/// Sparkle silhouette specification.
final SparkleShapeSpec sparkleShape;
/// Foil style preset. /// Foil style preset.
final HolographStyle style; final ShinyProfile profile;
/// Blend mode applied by [ShaderMask]. /// Blend mode applied by [ShaderMask].
final BlendMode blendMode; final BlendMode blendMode;
@@ -244,7 +92,8 @@ class Shiny extends StatefulWidget {
} }
class _ShinyState extends State<Shiny> { class _ShinyState extends State<Shiny> {
static Future<ui.FragmentProgram>? _programFuture; static final Map<ShinyProfile, Future<ui.FragmentProgram>> _programFutures =
<ShinyProfile, Future<ui.FragmentProgram>>{};
ui.FragmentShader? _shader; ui.FragmentShader? _shader;
StreamSubscription<Offset>? _tiltSub; StreamSubscription<Offset>? _tiltSub;
@@ -269,30 +118,43 @@ class _ShinyState extends State<Shiny> {
} }
if (oldWidget.enableShader != widget.enableShader) { if (oldWidget.enableShader != widget.enableShader) {
if (widget.enableShader) { if (widget.enableShader) {
if (_shader == null) _loadShader(); _loadShader();
} else {
_shader?.dispose();
_shader = null;
} }
} }
if (oldWidget.profile != widget.profile && widget.enableShader) {
_loadShader();
}
} }
Future<void> _loadShader() async { Future<void> _loadShader() async {
final ShinyProfile profile = widget.profile;
try { try {
_programFuture ??= _loadProgram(); final Future<ui.FragmentProgram> future =
final ui.FragmentProgram program = await _programFuture!; _programFutures.putIfAbsent(profile, () => _loadProgram(profile));
if (!mounted) return; final ui.FragmentProgram program = await future;
if (!mounted || !widget.enableShader || widget.profile != profile) {
return;
}
final ui.FragmentShader shader = program.fragmentShader();
setState(() { setState(() {
_shader = program.fragmentShader(); _shader?.dispose();
_shader = shader;
}); });
} catch (_) { } catch (_) {
// Keep rendering without shader when runtime effects are unavailable. // Keep rendering without shader when runtime effects are unavailable.
} }
} }
Future<ui.FragmentProgram> _loadProgram() async { Future<ui.FragmentProgram> _loadProgram(ShinyProfile profile) async {
final String localAsset = profile.shaderAsset;
try { try {
return await ui.FragmentProgram.fromAsset('shaders/shiny_card.frag'); return await ui.FragmentProgram.fromAsset(localAsset);
} catch (_) { } catch (_) {
return await ui.FragmentProgram.fromAsset( return await ui.FragmentProgram.fromAsset(
'packages/holo_shiny/shaders/shiny_card.frag'); 'packages/holo_shiny/$localAsset');
} }
} }
@@ -331,12 +193,7 @@ class _ShinyState extends State<Shiny> {
..setFloat(5, widget.sparkle) ..setFloat(5, widget.sparkle)
..setFloat(6, widget.specular) ..setFloat(6, widget.specular)
..setFloat(7, widget.diffraction) ..setFloat(7, widget.diffraction)
..setFloat(8, widget.style.index.toDouble()) ..setFloat(8, widget.opacity);
..setFloat(9, widget.sparkleShape.kind.index.toDouble())
..setFloat(10, widget.sparkleShape.primary)
..setFloat(11, widget.sparkleShape.secondary)
..setFloat(12, widget.sparkleShape.tertiary)
..setFloat(13, widget.opacity);
return shader; return shader;
} }
@@ -372,8 +229,7 @@ class ShinyCard extends StatefulWidget {
this.specular = 0.8, this.specular = 0.8,
this.diffraction = 0.8, this.diffraction = 0.8,
this.opacity = 1.0, this.opacity = 1.0,
this.sparkleShape = SparkleShapeSpec.none, this.profile = ShinyProfile.crackedIce,
this.style = HolographStyle.crackedIce,
this.enableShader = true, this.enableShader = true,
}); });
@@ -410,11 +266,8 @@ class ShinyCard extends StatefulWidget {
/// Global shader opacity multiplier. /// Global shader opacity multiplier.
final double opacity; final double opacity;
/// Sparkle silhouette specification.
final SparkleShapeSpec sparkleShape;
/// Foil style preset. /// Foil style preset.
final HolographStyle style; final ShinyProfile profile;
/// Toggles shader execution. /// Toggles shader execution.
final bool enableShader; final bool enableShader;
@@ -550,8 +403,7 @@ class _ShinyCardState extends State<ShinyCard>
specular: widget.specular, specular: widget.specular,
diffraction: widget.diffraction, diffraction: widget.diffraction,
opacity: widget.opacity, opacity: widget.opacity,
sparkleShape: widget.sparkleShape, profile: widget.profile,
style: widget.style,
child: base, child: base,
), ),
if (widget.foreground != null) widget.foreground!, if (widget.foreground != null) widget.foreground!,
+4 -1
View File
@@ -23,4 +23,7 @@ dev_dependencies:
flutter: flutter:
shaders: 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
-382
View File
@@ -1,382 +0,0 @@
#version 460 core
#include <flutter/runtime_effect.glsl>
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);
}
+192
View File
@@ -0,0 +1,192 @@
#version 460 core
#include <flutter/runtime_effect.glsl>
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);
}
+121
View File
@@ -0,0 +1,121 @@
#version 460 core
#include <flutter/runtime_effect.glsl>
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);
}
+139
View File
@@ -0,0 +1,139 @@
#version 460 core
#include <flutter/runtime_effect.glsl>
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);
}
+131
View File
@@ -0,0 +1,131 @@
#version 460 core
#include <flutter/runtime_effect.glsl>
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);
}
+13 -33
View File
@@ -8,22 +8,12 @@ const ValueKey<String> _cardTransformKey =
ValueKey<String>('holo_shiny.card.transform'); ValueKey<String>('holo_shiny.card.transform');
void main() { void main() {
test('default holograph style is crackedIce', () { test('default profile is crackedIce', () {
const Shiny shiny = Shiny(child: SizedBox.shrink()); const Shiny shiny = Shiny(child: SizedBox.shrink());
const ShinyCard shinyCard = ShinyCard(); const ShinyCard shinyCard = ShinyCard();
expect(shiny.style, HolographStyle.crackedIce); expect(shiny.profile, ShinyProfile.crackedIce);
expect(shinyCard.style, HolographStyle.crackedIce); expect(shinyCard.profile, ShinyProfile.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);
}); });
test('default opacity is 1.0', () { test('default opacity is 1.0', () {
@@ -34,27 +24,17 @@ void main() {
expect(shinyCard.opacity, 1.0); expect(shinyCard.opacity, 1.0);
}); });
test('custom sparkle factories produce expected specs', () { test('custom profile can be set on both widgets', () {
final SparkleShapeSpec star = const Shiny shiny = Shiny(
SparkleShapeSpec.customStar(points: 7, innerRatio: 0.33); profile: ShinyProfile.superGoldVinyl,
final SparkleShapeSpec polygon = SparkleShapeSpec.customPolygon( child: SizedBox.shrink(),
sides: 8, aspectRatio: 1.2, rotation: 0.4); );
final SparkleShapeSpec rectangle = SparkleShapeSpec.customRectangle( const ShinyCard shinyCard = ShinyCard(
halfWidth: 0.20, halfHeight: 0.05, rotationJitter: 0.7); profile: ShinyProfile.silverMosaic,
);
expect(star.kind, SparkleShapeKind.star); expect(shiny.profile, ShinyProfile.superGoldVinyl);
expect(star.primary, 7.0); expect(shinyCard.profile, ShinyProfile.silverMosaic);
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);
}); });
testWidgets('Shiny wraps any child without card transform', testWidgets('Shiny wraps any child without card transform',