dart(freeform-gradient): feat: implementation
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
		
					parent
					
						
							
								5bbf425e2f
							
						
					
				
			
			
				commit
				
					
						8294895837
					
				
			
		
					 4 changed files with 198 additions and 163 deletions
				
			
		dart/freeform-gradient
|  | @ -1,16 +1,10 @@ | ||||||
| # Hello World application with Flutter + Nix | # Flutter freeform gradiant | ||||||
| 
 | 
 | ||||||
| This is a demonstration project to show usage of Flutter with Nix. | This is a demonstration project to show usage of Flutter with Nix. | ||||||
| 
 | 
 | ||||||
| ## Getting Started | ## Getting Started | ||||||
| 
 | 
 | ||||||
| If you have `direnv` installed, run: | Enter in the `nix-shell`: | ||||||
| 
 |  | ||||||
| ```sh |  | ||||||
| direnv allow |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Otherwise, enter in the `nix-shell`: |  | ||||||
| 
 | 
 | ||||||
| ```sh | ```sh | ||||||
| nix-shell | nix-shell | ||||||
|  | @ -29,49 +23,3 @@ flutter run | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| If you have any issue, you can run `flutter doctor` to get a diagnostic and potential solution to fix your issues. | If you have any issue, you can run `flutter doctor` to get a diagnostic and potential solution to fix your issues. | ||||||
| 
 |  | ||||||
| ## How to use it with your projects |  | ||||||
| 
 |  | ||||||
| Create a `shell.nix` file with this content: |  | ||||||
| 
 |  | ||||||
| ```nix |  | ||||||
| { pkgs ? import <nixpkgs> {} }: |  | ||||||
| 
 |  | ||||||
| let |  | ||||||
|   flutter = (import (builtins.fetchTarball |  | ||||||
|     "https://github.com/babariviere/nixpkgs/archive/flutter-init.tar.gz") {}).flutter; # this will be replaced when merged into NixOS |  | ||||||
| in pkgs.mkShell { |  | ||||||
|   buildInputs = [ flutter ]; |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| After this, enter into your nix-shell and you are good to go. |  | ||||||
| 
 |  | ||||||
| ## Updating |  | ||||||
| 
 |  | ||||||
| ### To update the whole environment, do the following in order: |  | ||||||
| 
 |  | ||||||
| - `niv init` will update nix/sources.nix if a new one is available |  | ||||||
| - Make sure you have a backup of your nix/sources.json. If you're not in a git repo or use another vcs: `cp nix/sources.json nix/sources.json.bak` |  | ||||||
| - `niv update` will update sources.json i.e. your version of nixpkgs used in this environment |  | ||||||
|   - you may need to edit nix/android.nix if any of the attributes in there can't be found after this. |  | ||||||
| - `direnv reload` will rebuild/reload your environment (may not be necessary if it automatically reloads) |  | ||||||
|   - alternatively, run `nix-shell` again (?)  |  | ||||||
| - `flutter packages update` to update flutter/dart packages and get rid of the warning that you're using a different version of flutter than what was last referenced  |  | ||||||
| 
 |  | ||||||
| ## Global install |  | ||||||
| 
 |  | ||||||
| If you want to install `flutter` on your system, add this to your `/etc/nixos/configuration.nix`: |  | ||||||
| 
 |  | ||||||
| ```nix |  | ||||||
| { |  | ||||||
|   environment.systemPackages = [ |  | ||||||
|     (import (builtins.fetchTarball |  | ||||||
|         "https://github.com/babariviere/nixpkgs/archive/flutter-init.tar.gz") {}).flutter |  | ||||||
|   ]; |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Have an issue ? |  | ||||||
| 
 |  | ||||||
| Create an issue on this repository with your issue and I will try to help you as much as I can ! |  | ||||||
|  |  | ||||||
|  | @ -1,111 +1,212 @@ | ||||||
|  | import 'dart:math'; | ||||||
|  | 
 | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'dart:ui' as ui; | ||||||
|  | import 'dart:async'; | ||||||
|  | import 'dart:typed_data'; | ||||||
|  | import 'package:vector_math/vector_math.dart' as vector; | ||||||
| 
 | 
 | ||||||
| void main() => runApp(MyApp()); | void main() => runApp(Root()); | ||||||
| 
 | 
 | ||||||
| class MyApp extends StatelessWidget { | class Root extends StatefulWidget { | ||||||
|   // This widget is the root of your application. |   const Root({ | ||||||
|   @override |     Key key, | ||||||
|   Widget build(BuildContext context) { |   }) : super(key: key); | ||||||
|     return MaterialApp( |  | ||||||
|       title: 'Flutter Demo', |  | ||||||
|       theme: ThemeData( |  | ||||||
|         // This is the theme of your application. |  | ||||||
|         // |  | ||||||
|         // Try running your application with "flutter run". You'll see the |  | ||||||
|         // application has a blue toolbar. Then, without quitting the app, try |  | ||||||
|         // changing the primarySwatch below to Colors.green and then invoke |  | ||||||
|         // "hot reload" (press "r" in the console where you ran "flutter run", |  | ||||||
|         // or simply save your changes to "hot reload" in a Flutter IDE). |  | ||||||
|         // Notice that the counter didn't reset back to zero; the application |  | ||||||
|         // is not restarted. |  | ||||||
|         primarySwatch: Colors.blue, |  | ||||||
|       ), |  | ||||||
|       home: MyHomePage(title: 'Flutter Demo Home Page'), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class MyHomePage extends StatefulWidget { |  | ||||||
|   MyHomePage({Key key, this.title}) : super(key: key); |  | ||||||
| 
 |  | ||||||
|   // This widget is the home page of your application. It is stateful, meaning |  | ||||||
|   // that it has a State object (defined below) that contains fields that affect |  | ||||||
|   // how it looks. |  | ||||||
| 
 |  | ||||||
|   // This class is the configuration for the state. It holds the values (in this |  | ||||||
|   // case the title) provided by the parent (in this case the App widget) and |  | ||||||
|   // used by the build method of the State. Fields in a Widget subclass are |  | ||||||
|   // always marked "final". |  | ||||||
| 
 |  | ||||||
|   final String title; |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   _MyHomePageState createState() => _MyHomePageState(); |   RootState createState() => RootState(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class _MyHomePageState extends State<MyHomePage> { | class RootState extends State<Root> { | ||||||
|   int _counter = 0; |   double angle = 0; | ||||||
| 
 | 
 | ||||||
|   void _incrementCounter() { |   @override | ||||||
|  |   initState() { | ||||||
|  |     super.initState(); | ||||||
|  | 
 | ||||||
|  |     Timer.periodic(Duration(milliseconds: 10), (timer) { | ||||||
|       setState(() { |       setState(() { | ||||||
|       // This call to setState tells the Flutter framework that something has |         angle += 0.04; | ||||||
|       // changed in this State, which causes it to rerun the build method below |       }); | ||||||
|       // so that the display can reflect the updated values. If we changed |  | ||||||
|       // _counter without calling setState(), then the build method would not be |  | ||||||
|       // called again, and so nothing would appear to happen. |  | ||||||
|       _counter++; |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     // This method is rerun every time setState is called, for instance as done |     return MaterialApp( | ||||||
|     // by the _incrementCounter method above. |       title: 'Custom gradient', | ||||||
|     // |       theme: ThemeData( | ||||||
|     // The Flutter framework has been optimized to make rerunning build methods |         primarySwatch: Colors.purple, | ||||||
|     // fast, so that you can just rebuild anything that needs updating rather |  | ||||||
|     // than having to individually change instances of widgets. |  | ||||||
|     return Scaffold( |  | ||||||
|       appBar: AppBar( |  | ||||||
|         // Here we take the value from the MyHomePage object that was created by |  | ||||||
|         // the App.build method, and use it to set our appbar title. |  | ||||||
|         title: Text(widget.title), |  | ||||||
|       ), |       ), | ||||||
|       body: Center( |       home: MyHomePage( | ||||||
|         // Center is a layout widget. It takes a single child and positions it |           title: 'Multi point gradient', | ||||||
|         // in the middle of the parent. |           config: MultiGradientConfig( | ||||||
|         child: Column( |               distanceScaling: 1, | ||||||
|           // Column is also a layout widget. It takes a list of children and |               blendStrength: 2, | ||||||
|           // arranges them vertically. By default, it sizes itself to fit its |               points: [ | ||||||
|           // children horizontally, and tries to be as tall as its parent. |                 MultiGradientPoint( | ||||||
|           // |                   color: Colors.yellow, | ||||||
|           // Invoke "debug painting" (press "p" in the console, choose the |                   intensity: 1.0, | ||||||
|           // "Toggle Debug Paint" action from the Flutter Inspector in Android |                   position: vector.Vector2( | ||||||
|           // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) |                       1.0 / 2 + cos(angle) * 0.3, 1.0 / 2 + sin(angle) * 0.3), | ||||||
|           // to see the wireframe for each widget. |  | ||||||
|           // |  | ||||||
|           // Column has various properties to control how it sizes itself and |  | ||||||
|           // how it positions its children. Here we use mainAxisAlignment to |  | ||||||
|           // center the children vertically; the main axis here is the vertical |  | ||||||
|           // axis because Columns are vertical (the cross axis would be |  | ||||||
|           // horizontal). |  | ||||||
|           mainAxisAlignment: MainAxisAlignment.center, |  | ||||||
|           children: <Widget>[ |  | ||||||
|             Text( |  | ||||||
|               'You have pushed the button this many times:', |  | ||||||
|                 ), |                 ), | ||||||
|             Text( |                 MultiGradientPoint( | ||||||
|               '$_counter', |                     color: Color(0xff6E10AB), | ||||||
|               style: Theme.of(context).textTheme.display1, |                     intensity: 1, | ||||||
|             ), |                     position: vector.Vector2(0.1, 0.09)), | ||||||
|           ], |                 MultiGradientPoint( | ||||||
|         ), |                     color: Color(0xff041E1C), | ||||||
|       ), |                     intensity: 2, | ||||||
|       floatingActionButton: FloatingActionButton( |                     position: vector.Vector2(0.84, 0.14)), | ||||||
|         onPressed: _incrementCounter, |                 MultiGradientPoint( | ||||||
|         tooltip: 'Increment', |                     color: Color(0xff015BBB), | ||||||
|         child: Icon(Icons.add), |                     intensity: 1, | ||||||
|       ), // This trailing comma makes auto-formatting nicer for build methods. |                     position: vector.Vector2(0.07, 0.93)), | ||||||
|  |                 MultiGradientPoint( | ||||||
|  |                     color: Color(0xff06A599), | ||||||
|  |                     intensity: 1, | ||||||
|  |                     position: vector.Vector2(0.9, 0.94)) | ||||||
|  |               ])), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | class MyHomePage extends StatelessWidget { | ||||||
|  |   MyHomePage({Key key, this.title, this.config}) : super(key: key); | ||||||
|  | 
 | ||||||
|  |   final String title; | ||||||
|  |   final MultiGradientConfig config; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Scaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(title), | ||||||
|  |       ), | ||||||
|  |       body: Center( | ||||||
|  |           child: FutureBuilder<ui.Image>( | ||||||
|  |         future: generateImage(MediaQuery.of(context).size, config), | ||||||
|  |         builder: (context, snapshot) { | ||||||
|  |           if (snapshot.hasData) { | ||||||
|  |             MediaQueryData query = MediaQuery.of(context); | ||||||
|  |             return CustomPaint( | ||||||
|  |                 child: Container( | ||||||
|  |                   width: query.size.width, | ||||||
|  |                   height: query.size.height, | ||||||
|  |                 ), | ||||||
|  |                 painter: ImagePainter(image: snapshot.data)); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           return Text("Generating image"); | ||||||
|  |         }, | ||||||
|  |       )), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ImagePainter extends CustomPainter { | ||||||
|  |   ui.Image image; | ||||||
|  | 
 | ||||||
|  |   ImagePainter({this.image}); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   void paint(Canvas canvas, Size size) { | ||||||
|  |     canvas.drawImage(image, Offset.zero, Paint()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   bool shouldRepaint(ImagePainter oldDelegate) { | ||||||
|  |     return oldDelegate.image != image; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Generates a [ui.Image] with certain pixel data | ||||||
|  | Future<ui.Image> generateImage(Size size, MultiGradientConfig config) async { | ||||||
|  |   int width = size.width.ceil(); | ||||||
|  |   int height = size.height.ceil(); | ||||||
|  |   var completer = Completer<ui.Image>(); | ||||||
|  | 
 | ||||||
|  |   Int32List pixels = Int32List(width * height); | ||||||
|  | 
 | ||||||
|  |   for (var x = 0; x < width; x++) { | ||||||
|  |     for (var y = 0; y < height; y++) { | ||||||
|  |       int index = y * width + x; | ||||||
|  |       pixels[index] = generatePixel(x, y, size, config); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ui.decodeImageFromPixels( | ||||||
|  |     pixels.buffer.asUint8List(), | ||||||
|  |     width, | ||||||
|  |     height, | ||||||
|  |     ui.PixelFormat.bgra8888, | ||||||
|  |     (ui.Image img) { | ||||||
|  |       completer.complete(img); | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   return completer.future; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class MultiGradientPoint { | ||||||
|  |   double intensity; | ||||||
|  |   vector.Vector4 color; | ||||||
|  |   vector.Vector2 position; | ||||||
|  | 
 | ||||||
|  |   MultiGradientPoint({Color color, this.position, this.intensity = 1}) { | ||||||
|  |     this.color = vector.Vector4(color.red.toDouble(), color.green.toDouble(), | ||||||
|  |         color.blue.toDouble(), color.opacity); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class MultiGradientConfig { | ||||||
|  |   List<MultiGradientPoint> points; | ||||||
|  | 
 | ||||||
|  |   double distanceScaling; | ||||||
|  |   double blendStrength; | ||||||
|  |   double opacityMultiplier = 1; | ||||||
|  | 
 | ||||||
|  |   MultiGradientConfig( | ||||||
|  |       {this.points, | ||||||
|  |       this.distanceScaling = 1, | ||||||
|  |       this.blendStrength = 3, | ||||||
|  |       this.opacityMultiplier = 1}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double clamp(double min, double max, double value) { | ||||||
|  |   if (value < min) return min; | ||||||
|  |   if (value > max) return max; | ||||||
|  | 
 | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Main area of interest, this function will | ||||||
|  | /// return color for each particular color on our [ui.Image] | ||||||
|  | int generatePixel(int x, int y, Size size, MultiGradientConfig config) { | ||||||
|  |   vector.Vector4 color = vector.Vector4(0, 0, 0, 0); | ||||||
|  |   double scale = 0; | ||||||
|  | 
 | ||||||
|  |   var current = vector.Vector2(x.toDouble(), y.toDouble()); | ||||||
|  | 
 | ||||||
|  |   current.divide(vector.Vector2(size.width, size.height)); | ||||||
|  | 
 | ||||||
|  |   for (var i = 0; i < config.points.length; i++) { | ||||||
|  |     MultiGradientPoint point = config.points[i]; | ||||||
|  | 
 | ||||||
|  |     var distance = pow( | ||||||
|  |         1 - point.position.distanceTo(current) / config.distanceScaling, | ||||||
|  |         config.blendStrength); | ||||||
|  |     var multiplier = distance * point.intensity; | ||||||
|  | 
 | ||||||
|  |     scale += multiplier; | ||||||
|  |     color += point.color.scaled(multiplier); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (scale != 0) { | ||||||
|  |     color.scale(1 / scale); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return Color.fromRGBO(color.x.toInt(), color.y.toInt(), color.z.toInt(), | ||||||
|  |           color.w * config.opacityMultiplier) | ||||||
|  |       .value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,16 +1,6 @@ | ||||||
| name: hello_world | name: hello_world | ||||||
| description: A new Flutter project. | description: A new Flutter project. | ||||||
| 
 | 
 | ||||||
| # The following defines the version and build number for your application. |  | ||||||
| # A version number is three numbers separated by dots, like 1.2.43 |  | ||||||
| # followed by an optional build number separated by a +. |  | ||||||
| # Both the version and the builder number may be overridden in flutter |  | ||||||
| # build by specifying --build-name and --build-number, respectively. |  | ||||||
| # In Android, build-name is used as versionName while build-number used as versionCode. |  | ||||||
| # Read more about Android versioning at https://developer.android.com/studio/publish/versioning |  | ||||||
| # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. |  | ||||||
| # Read more about iOS versioning at |  | ||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html |  | ||||||
| version: 1.0.0+1 | version: 1.0.0+1 | ||||||
| 
 | 
 | ||||||
| environment: | environment: | ||||||
|  | @ -20,21 +10,17 @@ dependencies: | ||||||
|   flutter: |   flutter: | ||||||
|     sdk: flutter |     sdk: flutter | ||||||
| 
 | 
 | ||||||
|   # The following adds the Cupertino Icons font to your application. |  | ||||||
|   # Use with the CupertinoIcons class for iOS style icons. |  | ||||||
|   cupertino_icons: ^0.1.2 |   cupertino_icons: ^0.1.2 | ||||||
| 
 | 
 | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|     sdk: flutter |     sdk: flutter | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| # For information on the generic Dart part of this file, see the | # For information on the generic Dart part of this file, see the | ||||||
| # following page: https://dart.dev/tools/pub/pubspec | # following page: https://dart.dev/tools/pub/pubspec | ||||||
| 
 | 
 | ||||||
| # The following section is specific to Flutter. | # The following section is specific to Flutter. | ||||||
| flutter: | flutter: | ||||||
| 
 |  | ||||||
|   # The following line ensures that the Material Icons font is |   # The following line ensures that the Material Icons font is | ||||||
|   # included with your application, so that you can use the icons in |   # included with your application, so that you can use the icons in | ||||||
|   # the material Icons class. |   # the material Icons class. | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ import 'package:hello_world/main.dart'; | ||||||
| void main() { | void main() { | ||||||
|   testWidgets('Counter increments smoke test', (WidgetTester tester) async { |   testWidgets('Counter increments smoke test', (WidgetTester tester) async { | ||||||
|     // Build our app and trigger a frame. |     // Build our app and trigger a frame. | ||||||
|     await tester.pumpWidget(MyApp()); |     await tester.pumpWidget(Root()); | ||||||
| 
 | 
 | ||||||
|     // Verify that our counter starts at 0. |     // Verify that our counter starts at 0. | ||||||
|     expect(find.text('0'), findsOneWidget); |     expect(find.text('0'), findsOneWidget); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Matei Adriel
				Matei Adriel