Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
@@ -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
|
||||
@@ -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
@@ -30,13 +30,8 @@ class ExampleHome extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExampleHomeState extends State<ExampleHome> {
|
||||
/// Sensor-driven controller used by the first two demo surfaces.
|
||||
late final ShinyController _sensorController;
|
||||
|
||||
/// Manual stream used by tilt preset buttons.
|
||||
late final StreamController<Offset> _externalTiltController;
|
||||
|
||||
/// Controller backed by [_externalTiltController].
|
||||
late final ShinyController _overrideController;
|
||||
|
||||
double _prismatic = 0.8;
|
||||
@@ -44,20 +39,7 @@ class _ExampleHomeState extends State<ExampleHome> {
|
||||
double _specular = 0.8;
|
||||
double _diffraction = 0.8;
|
||||
double _opacity = 1.0;
|
||||
HolographStyle _style = HolographStyle.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,
|
||||
};
|
||||
ShinyProfile _profile = ShinyProfile.crackedIce;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -95,13 +77,12 @@ class _ExampleHomeState extends State<ExampleHome> {
|
||||
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<ExampleHome> {
|
||||
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<ExampleHome> {
|
||||
),
|
||||
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<ExampleHome> {
|
||||
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<HolographStyle> onChanged;
|
||||
final ShinyProfile selectedProfile;
|
||||
final ValueChanged<ShinyProfile> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text('Style'),
|
||||
const Text('Profile'),
|
||||
const SizedBox(height: 6),
|
||||
SegmentedButton<HolographStyle>(
|
||||
segments: HolographStyle.values
|
||||
.map((HolographStyle style) => ButtonSegment<HolographStyle>(
|
||||
value: style,
|
||||
label: Text(_styleLabel(style)),
|
||||
SegmentedButton<ShinyProfile>(
|
||||
segments: ShinyProfile.values
|
||||
.map((ShinyProfile profile) => ButtonSegment<ShinyProfile>(
|
||||
value: profile,
|
||||
label: Text(_profileLabel(profile)),
|
||||
))
|
||||
.toList(),
|
||||
selected: <HolographStyle>{selectedStyle},
|
||||
onSelectionChanged: (Set<HolographStyle> value) {
|
||||
selected: <ShinyProfile>{selectedProfile},
|
||||
onSelectionChanged: (Set<ShinyProfile> 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<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.
|
||||
class _TiltPresetPicker extends StatelessWidget {
|
||||
const _TiltPresetPicker({required this.onChanged});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
flutter/ephemeral
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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}")
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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_
|
||||
+43
-191
@@ -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<Shiny> {
|
||||
static Future<ui.FragmentProgram>? _programFuture;
|
||||
static final Map<ShinyProfile, Future<ui.FragmentProgram>> _programFutures =
|
||||
<ShinyProfile, Future<ui.FragmentProgram>>{};
|
||||
|
||||
ui.FragmentShader? _shader;
|
||||
StreamSubscription<Offset>? _tiltSub;
|
||||
@@ -269,30 +118,43 @@ class _ShinyState extends State<Shiny> {
|
||||
}
|
||||
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<void> _loadShader() async {
|
||||
final ShinyProfile profile = widget.profile;
|
||||
try {
|
||||
_programFuture ??= _loadProgram();
|
||||
final ui.FragmentProgram program = await _programFuture!;
|
||||
if (!mounted) return;
|
||||
final Future<ui.FragmentProgram> 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<ui.FragmentProgram> _loadProgram() async {
|
||||
Future<ui.FragmentProgram> _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<Shiny> {
|
||||
..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<ShinyCard>
|
||||
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!,
|
||||
|
||||
+4
-1
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -8,22 +8,12 @@ const ValueKey<String> _cardTransformKey =
|
||||
ValueKey<String>('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',
|
||||
|
||||
Reference in New Issue
Block a user