diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
index 40956c9..0ba3a74 100644
--- a/.idea/libraries/Dart_Packages.xml
+++ b/.idea/libraries/Dart_Packages.xml
@@ -44,6 +44,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -51,6 +65,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -65,6 +93,13 @@
+
+
+
+
+
+
+
@@ -79,6 +114,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -128,6 +191,13 @@
+
+
+
+
+
+
+
@@ -135,6 +205,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -198,6 +359,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -207,9 +389,16 @@
+
+
+
+
+
+
+
@@ -217,7 +406,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -226,9 +429,14 @@
+
+
+
+
+
diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml
index b0f6971..86004d3 100644
--- a/.idea/libraries/Flutter_Plugins.xml
+++ b/.idea/libraries/Flutter_Plugins.xml
@@ -1,6 +1,15 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/flutter_custom_visuals.iml b/flutter_custom_visuals.iml
index a461e81..b3a2b0c 100644
--- a/flutter_custom_visuals.iml
+++ b/flutter_custom_visuals.iml
@@ -10,5 +10,6 @@
+
\ No newline at end of file
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index 592ceee..ec97fc6 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 592ceee..c4855bf 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..d97f17e
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/lib/main.dart b/lib/main.dart
index c8b2ae0..3f69d9e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,50 +1,73 @@
import 'package:flutter/material.dart';
+import 'package:flutter_custom_visuals/ripple/blur_demo.dart';
+import 'package:flutter_custom_visuals/ripple/electro_demo.dart';
+import 'package:flutter_custom_visuals/ripple/gradient_flow_demo.dart';
+import 'package:flutter_custom_visuals/ripple/lightning_demo.dart';
+import 'package:flutter_custom_visuals/ripple/meatballs_demo.dart';
+import 'package:flutter_custom_visuals/ripple/mesh_gradient_demo.dart';
import 'package:flutter_custom_visuals/ripple/ripple_demo.dart';
+import 'package:flutter_custom_visuals/ripple/sparkles_demo.dart';
+import 'package:flutter_custom_visuals/ripple/sweep_demo.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterCustomVisuals',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
+ theme: ThemeData(primarySwatch: Colors.blue),
home: DemoGridPage(),
);
}
}
-class DemoGridPage extends StatelessWidget {
+class DemoGridPage extends StatefulWidget {
+ const DemoGridPage({super.key});
+
+ @override
+ State createState() => _DemoGridPageState();
+}
+
+class _DemoGridPageState extends State {
+ final examples = [
+ "Ripple",
+ "BoxBlur",
+ "Meatballs",
+ "GradientFlow",
+ "MeshGradient",
+ "Waterr?",
+ "Noise?",
+ "Sparkles",
+ "Stars",
+ "PageCurl",
+ "lightning",
+ ];
+
@override
Widget build(BuildContext context) {
+ return GradientFlowDemoPage();
return Scaffold(
appBar: AppBar(
- title: Text('Flutter Custom Visuals'),
+ title: const Text('Flutter Custom Visuals'),
),
body: GridView.builder(
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 2,
+ gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 4,
),
- itemCount: 1,
+ itemCount: examples.length,
itemBuilder: (context, index) {
return GestureDetector(
- onTap: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => RippleDemoPage(),
- ),
- );
- },
+ onTap: () => _showDemo(context, examples[index]),
child: Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
- child: Text("Ripple"),
+ child: Text(examples[index]),
),
),
),
@@ -53,4 +76,35 @@ class DemoGridPage extends StatelessWidget {
),
);
}
+
+ void _showDemo(BuildContext context, String demo) {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) {
+ switch (demo) {
+ case "Ripple":
+ return const RippleDemoPage();
+ case "lightning":
+ return const LightningDemoPage();
+ case "BoxBlur":
+ return const BlurDemoPage();
+ case "Meatballs":
+ return const MeatballsDemoPage();
+ case "GradientFlow":
+ return const GradientFlowDemoPage();
+ case "MeshGradient":
+ return const MeshGradientDemoPage();
+ case "Sweep":
+ return const SweepDemoPage();
+ case "Sparkles":
+ return const SparklesDemoPage();
+ case "electro":
+ return const ElectroDemoPage();
+ }
+ return const SizedBox();
+ },
+ ),
+ );
+ }
}
diff --git a/lib/ripple/blur_demo.dart b/lib/ripple/blur_demo.dart
new file mode 100644
index 0000000..2ec1a37
--- /dev/null
+++ b/lib/ripple/blur_demo.dart
@@ -0,0 +1,147 @@
+import 'dart:ui' as ui;
+import 'package:device_preview/device_preview.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_shaders/flutter_shaders.dart';
+
+class BlurDemoPage extends StatefulWidget {
+ const BlurDemoPage({super.key});
+
+ @override
+ State createState() => _BlurDemoPageState();
+}
+
+class _BlurDemoPageState extends State
+ with SingleTickerProviderStateMixin {
+ late AnimationController _controller;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = AnimationController(
+ vsync: this,
+ duration: const Duration(seconds: 5),
+ );
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Material(
+ child: Padding(
+ padding: const EdgeInsets.all(32.0),
+ child: DeviceFrame(
+ device: Devices.ios.iPhone13ProMax,
+ isFrameVisible: true,
+ orientation: Orientation.portrait,
+ screen: Scaffold(
+ body: PageView.builder(
+ itemBuilder: (BuildContext context, int index) {
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: buildImage(),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Offset _pointer = Offset.zero;
+
+ void _updatePointer(PointerEvent details) {
+ // _controller.animateTo(1.0);
+ _controller.reset();
+ _controller.forward();
+ setState(() {
+ _pointer = details.localPosition;
+ });
+ }
+
+ Widget buildImage() {
+ return Listener(
+ onPointerMove: _updatePointer,
+ onPointerDown: _updatePointer,
+ child: AnimatedBuilder(
+ animation: _controller,
+ builder: (context, child) {
+ return ShaderBuilder(
+ (context, shader, _) {
+ return AnimatedSampler(
+ (image, size, canvas) {
+ ShaderHelper.configureShader(
+ shader,
+ size,
+ image,
+ time: _controller.value * 10.0,
+ pointer: _pointer,
+ );
+ ShaderHelper.drawShaderRect(shader, size, canvas);
+ },
+ child: Container(
+ color: Theme.of(context).scaffoldBackgroundColor,
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: AspectRatio(
+ aspectRatio: 0.7,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: Image.asset(
+ "assets/palm_tree_cropped.jpeg",
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ assetKey: "shaders/ripple.frag",
+ );
+ },
+ ),
+ );
+ }
+}
+
+class ShaderHelper {
+ static void configureShader(
+ ui.FragmentShader shader,
+ ui.Size size,
+ ui.Image image, {
+ required double time,
+ required Offset pointer,
+ }) {
+ shader
+ ..setFloat(0, size.width) // iResolution
+ ..setFloat(1, size.height) // iResolution
+ ..setFloat(2, pointer.dx) // iMouse
+ ..setFloat(3, pointer.dy) // iMouse
+ ..setFloat(4, time) // iTime
+ ..setImageSampler(0, image); // image
+ }
+
+ static void drawShaderRect(
+ ui.FragmentShader shader,
+ ui.Size size,
+ ui.Canvas canvas,
+ ) {
+ canvas.drawRect(
+ Rect.fromLTWH(
+ 0,
+ 0,
+ size.width,
+ size.height,
+ ),
+ Paint()..shader = shader,
+ );
+ }
+}
diff --git a/lib/ripple/electro_demo.dart b/lib/ripple/electro_demo.dart
new file mode 100644
index 0000000..e9e6133
--- /dev/null
+++ b/lib/ripple/electro_demo.dart
@@ -0,0 +1,147 @@
+import 'dart:ui' as ui;
+import 'package:device_preview/device_preview.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_shaders/flutter_shaders.dart';
+
+class ElectroDemoPage extends StatefulWidget {
+ const ElectroDemoPage({super.key});
+
+ @override
+ State createState() => _ElectroDemoPageState();
+}
+
+class _ElectroDemoPageState extends State
+ with SingleTickerProviderStateMixin {
+ late AnimationController _controller;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = AnimationController(
+ vsync: this,
+ duration: const Duration(seconds: 5),
+ );
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Material(
+ child: Padding(
+ padding: const EdgeInsets.all(32.0),
+ child: DeviceFrame(
+ device: Devices.ios.iPhone13ProMax,
+ isFrameVisible: true,
+ orientation: Orientation.portrait,
+ screen: Scaffold(
+ body: PageView.builder(
+ itemBuilder: (BuildContext context, int index) {
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: buildImage(),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Offset _pointer = Offset.zero;
+
+ void _updatePointer(PointerEvent details) {
+ // _controller.animateTo(1.0);
+ _controller.reset();
+ _controller.forward();
+ setState(() {
+ _pointer = details.localPosition;
+ });
+ }
+
+ Widget buildImage() {
+ return Listener(
+ onPointerMove: _updatePointer,
+ onPointerDown: _updatePointer,
+ child: AnimatedBuilder(
+ animation: _controller,
+ builder: (context, child) {
+ return ShaderBuilder(
+ (context, shader, _) {
+ return AnimatedSampler(
+ (image, size, canvas) {
+ ShaderHelper.configureShader(
+ shader,
+ size,
+ image,
+ time: _controller.value * 10.0,
+ pointer: _pointer,
+ );
+ ShaderHelper.drawShaderRect(shader, size, canvas);
+ },
+ child: Container(
+ color: Theme.of(context).scaffoldBackgroundColor,
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: AspectRatio(
+ aspectRatio: 0.7,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: Image.asset(
+ "assets/palm_tree_cropped.jpeg",
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ assetKey: "shaders/ripple.frag",
+ );
+ },
+ ),
+ );
+ }
+}
+
+class ShaderHelper {
+ static void configureShader(
+ ui.FragmentShader shader,
+ ui.Size size,
+ ui.Image image, {
+ required double time,
+ required Offset pointer,
+ }) {
+ shader
+ ..setFloat(0, size.width) // iResolution
+ ..setFloat(1, size.height) // iResolution
+ ..setFloat(2, pointer.dx) // iMouse
+ ..setFloat(3, pointer.dy) // iMouse
+ ..setFloat(4, time) // iTime
+ ..setImageSampler(0, image); // image
+ }
+
+ static void drawShaderRect(
+ ui.FragmentShader shader,
+ ui.Size size,
+ ui.Canvas canvas,
+ ) {
+ canvas.drawRect(
+ Rect.fromLTWH(
+ 0,
+ 0,
+ size.width,
+ size.height,
+ ),
+ Paint()..shader = shader,
+ );
+ }
+}
diff --git a/lib/ripple/gradient_flow_demo.dart b/lib/ripple/gradient_flow_demo.dart
new file mode 100644
index 0000000..713d3ed
--- /dev/null
+++ b/lib/ripple/gradient_flow_demo.dart
@@ -0,0 +1,212 @@
+import 'dart:ui' as ui;
+import 'package:device_preview/device_preview.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_shaders/flutter_shaders.dart';
+
+class GradientFlowDemoPage extends StatefulWidget {
+ const GradientFlowDemoPage({super.key});
+
+ @override
+ State createState() => _GradientFlowDemoPageState();
+}
+
+class _GradientFlowDemoPageState extends State
+ with SingleTickerProviderStateMixin {
+ late final AnimationController animationController;
+
+ @override
+ void initState() {
+ super.initState();
+ animationController =
+ AnimationController(vsync: this, duration: Duration(milliseconds: 800));
+ animationController.repeat();
+ }
+
+ @override
+ void dispose() {
+ animationController.dispose();
+ super.dispose();
+ }
+
+ double value = 0;
+
+ int currentColorSetIndex = 0;
+
+ final List