Browse Source

feat: main canvas, overlay, element property controller, etc

Raihan Rizal 4 months ago
parent
commit
671e910703

+ 1 - 0
.fvm/flutter_sdk

@@ -0,0 +1 @@
1
+C:/Users/raiha/fvm/versions/3.24.4

+ 3 - 0
.fvm/fvm_config.json

@@ -0,0 +1,3 @@
1
+{
2
+  "flutterSdkVersion": "3.24.4"
3
+}

+ 1 - 0
.fvm/release

@@ -0,0 +1 @@
1
+3.24.4

+ 1 - 0
.fvm/version

@@ -0,0 +1 @@
1
+3.24.4

+ 1 - 0
.fvm/versions/3.24.4

@@ -0,0 +1 @@
1
+C:/Users/raiha/fvm/versions/3.24.4

BIN
asset/fonts/Roboto_Condensed-Medium.ttf


BIN
asset/images/qr_template.png


+ 3 - 0
devtools_options.yaml

@@ -0,0 +1,3 @@
1
+description: This file stores settings for Dart & Flutter DevTools.
2
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3
+extensions:

+ 0 - 78
lib/arc_curves_painter.dart

@@ -1,78 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-import 'dart:math' as math;
30
-
31
-import 'package:flutter/material.dart';
32
-
33
-class ArcsCurvesPainter extends CustomPainter {
34
-  final curvesPaint = Paint()
35
-    ..strokeWidth = 5
36
-    ..color = Colors.greenAccent[700]!
37
-    ..style = PaintingStyle.stroke;
38
-
39
-  @override
40
-  void paint(Canvas canvas, Size size) {
41
-    const arcCenter = Offset(200, 80);
42
-    final arcRect = Rect.fromCircle(center: arcCenter, radius: 75);
43
-    final startAngle = degreesToRadians(10);
44
-    final sweepAngle = degreesToRadians(-90);
45
-    canvas.drawArc(arcRect, startAngle, sweepAngle, false, curvesPaint);
46
-
47
-    // Quadratic Bézier
48
-    final qCurve1 = Path()
49
-      ..moveTo(50, 150)
50
-      ..relativeQuadraticBezierTo(100, -100, 300, 0);
51
-    canvas.drawPath(qCurve1, curvesPaint..color = Colors.deepPurpleAccent);
52
-
53
-    final qCurve2 = Path()
54
-      ..moveTo(0, 150)
55
-      ..relativeQuadraticBezierTo(150, 300, 300, 100);
56
-    canvas.drawPath(qCurve2, curvesPaint..color = Colors.blue);
57
-
58
-    // Cubic Bézier
59
-    final cCurve1 = Path()
60
-      ..moveTo(0, 450)
61
-      ..relativeCubicTo(50, -100, 250, -100, 300, 0);
62
-    canvas.drawPath(cCurve1, curvesPaint..color = Colors.black);
63
-
64
-    final cCurve2 = Path()
65
-      ..moveTo(380, 300)
66
-      ..relativeCubicTo(0, 450, -300, 300, -150, 250);
67
-    canvas.drawPath(cCurve2, curvesPaint..color = Colors.pink);
68
-
69
-
70
-  }
71
-
72
-  @override
73
-  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
74
-
75
-  double degreesToRadians(double degrees) {
76
-    return (degrees * math.pi) / 180;
77
-  }
78
-}

+ 0 - 73
lib/battery/animated_battery.dart

@@ -1,73 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-
30
-import 'battery_painter.dart';
31
-import 'package:flutter/material.dart';
32
-
33
-class AnimatedBattery extends StatefulWidget {
34
-
35
-  const AnimatedBattery({Key? key}) : super(key: key);
36
-
37
-  @override
38
-  _AnimatedBatteryState createState() => _AnimatedBatteryState();
39
-}
40
-
41
-class _AnimatedBatteryState extends State<AnimatedBattery>
42
-    with SingleTickerProviderStateMixin {
43
-  final duration = const Duration(seconds: 5);
44
-  late AnimationController _ctrl;
45
-
46
-  @override
47
-  void initState() {
48
-    _ctrl = AnimationController(duration: duration, vsync: this)
49
-      ..addListener(() => setState(() {}))
50
-      ..forward()
51
-      ..addStatusListener((status) {
52
-        if (status == AnimationStatus.completed) {
53
-          _ctrl.reverse();
54
-        } else if (status == AnimationStatus.dismissed) {
55
-          _ctrl.forward();
56
-        }
57
-      });
58
-    super.initState();
59
-  }
60
-
61
-  @override
62
-  Widget build(BuildContext context) {
63
-    return CustomPaint(
64
-      painter: BatteryPainter(charge: _ctrl.value),
65
-    );
66
-  }
67
-
68
-  @override
69
-  void dispose() {
70
-    _ctrl.dispose();
71
-    super.dispose();
72
-  }
73
-}

+ 0 - 120
lib/battery/battery_painter.dart

@@ -1,120 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-
30
-import 'package:flutter/material.dart';
31
-import 'dart:math' as math;
32
-
33
-class BatteryPainter extends CustomPainter {
34
-  final margin = 30.0; // The space between the battery and the parent widget
35
-  final padding = 10.0; // The space between the charge & pin and the border
36
-  final pinWidth = 22.0; // The width of the pin
37
-  final minCharge = 0.2; // The minimum charge until the battery disappears
38
-  final double charge;
39
-  final borderPaint = Paint()
40
-    ..color = Colors.black
41
-    ..strokeWidth = 4
42
-    ..isAntiAlias = true
43
-    ..style = PaintingStyle.stroke;
44
-
45
-  final pinPaint = Paint()
46
-    ..color = Colors.black
47
-    ..isAntiAlias = true
48
-    ..style = PaintingStyle.fill;
49
-
50
-  final chargePaint = Paint()
51
-    ..color = Colors.greenAccent[700]!
52
-    ..isAntiAlias = true
53
-    ..style = PaintingStyle.fill;
54
-
55
-  BatteryPainter({required this.charge});
56
-
57
-  @override
58
-  void paint(Canvas canvas, Size size) {
59
-    RRect _borderRRect(Size size) {
60
-      // 1
61
-      final symmetricalMargin = margin * 2;
62
-      // 2
63
-      final width = size.width - symmetricalMargin - padding - pinWidth;
64
-      // 3
65
-      final height = width / 2;
66
-      // 4
67
-      final top = (size.height / 2) - (height / 2);
68
-      // 5
69
-      final radius = Radius.circular(height * 0.2);
70
-      // 6
71
-      final bounds = Rect.fromLTWH(margin, top, width, height);
72
-      // 7
73
-      return RRect.fromRectAndRadius(bounds, radius);
74
-    }
75
-
76
-    final bdr = _borderRRect(size);
77
-    canvas.drawRRect(bdr, borderPaint);
78
-
79
-    Rect _pinRect(RRect bdr) {
80
-      // 1
81
-      final center = Offset(bdr.right + padding, bdr.top + (bdr.height / 2.0));
82
-      // 2
83
-      final height = bdr.height * 0.38;
84
-      // 3
85
-      final width = pinWidth * 2;
86
-      // 4
87
-      return Rect.fromCenter(center: center, width: width, height: height);
88
-    
89
-    }
90
-
91
-    // Battery pin
92
-    final pinRect = _pinRect(bdr);
93
-    canvas.drawArc(pinRect, math.pi / 2, -math.pi, true, pinPaint);
94
-
95
-    RRect _chargeRRect(RRect bdr) {
96
-        final percent = minCharge * ((charge / minCharge).round());
97
-
98
-        final left = bdr.left + padding;
99
-        final top = bdr.top + padding;
100
-        final right = bdr.right - padding;
101
-        final bottom = bdr.bottom - padding;
102
-        final height = bottom - top;
103
-        final width = right - left;
104
-        final radius = Radius.circular(height * 0.15);
105
-        final rect = Rect.fromLTWH(left, top, width * percent, height);
106
-        return RRect.fromRectAndRadius(rect, radius);
107
-    }
108
-
109
-    // Battery charge progress
110
-    final chargeRRect = _chargeRRect(bdr);
111
-    canvas.drawRRect(chargeRRect, chargePaint);
112
-
113
-
114
-  }
115
-
116
-  @override
117
-  bool shouldRepaint(covariant BatteryPainter oldDelegate) {
118
-    return oldDelegate.charge != charge;
119
-  }
120
-}

+ 117 - 0
lib/canvas_setup_page.dart

@@ -0,0 +1,117 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:flutter_canvas_editor/providers/editor.dart';
3
+import 'package:provider/provider.dart';
4
+
5
+class CanvasSetupPage extends StatefulWidget {
6
+  @override
7
+  _CanvasSetupPageState createState() => _CanvasSetupPageState();
8
+}
9
+
10
+class _CanvasSetupPageState extends State<CanvasSetupPage> {
11
+  final _formKey = GlobalKey<FormState>();
12
+  final TextEditingController _widthController = TextEditingController();
13
+  final TextEditingController _heightController = TextEditingController();
14
+
15
+  @override
16
+  void initState() {
17
+    // TODO: implement initState
18
+    super.initState();
19
+    final editorProvider = Provider.of<Editor>(context, listen: false);
20
+
21
+    _widthController.text = editorProvider.canvasProperty.width.toString();
22
+    _heightController.text = editorProvider.canvasProperty.height.toString();
23
+
24
+  }
25
+
26
+  @override
27
+  Widget build(BuildContext context) {
28
+    final editorProvider = Provider.of<Editor>(context);
29
+
30
+    return Scaffold(
31
+      appBar: AppBar(
32
+        title: Text('Canvas Setup'),
33
+      ),
34
+      body: Padding(
35
+        padding: const EdgeInsets.all(16.0),
36
+        child: Form(
37
+          key: _formKey,
38
+          child: Column(
39
+            children: <Widget>[
40
+              Container(
41
+                padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
42
+                decoration: BoxDecoration(
43
+                  color: Colors.amber,
44
+                  borderRadius: BorderRadius.circular(2)
45
+                ),
46
+                width: double.infinity,
47
+                child: Text('Warning Test'),
48
+              ),
49
+
50
+              SizedBox(height: 16),
51
+
52
+              TextFormField(
53
+                controller: _widthController,
54
+                decoration: InputDecoration(labelText: 'Width'),
55
+                keyboardType: TextInputType.number,
56
+                validator: (value) {
57
+                  if (value == null || value.isEmpty) {
58
+                    return 'Please enter width';
59
+                  }
60
+                  return null;
61
+                },
62
+              ),
63
+              TextFormField(
64
+                controller: _heightController,
65
+                decoration: InputDecoration(labelText: 'Height'),
66
+                keyboardType: TextInputType.number,
67
+                validator: (value) {
68
+                  if (value == null || value.isEmpty) {
69
+                    return 'Please enter height';
70
+                  }
71
+                  return null;
72
+                },
73
+              ),
74
+              SizedBox(height: 20),
75
+              ElevatedButton(
76
+                onPressed: () async {
77
+                  if (_formKey.currentState!.validate()) {
78
+                    // confirmation
79
+                    final confirmationResult = await showDialog(
80
+                      barrierDismissible: false,
81
+                      context: context, 
82
+                      builder: (context) => AlertDialog(
83
+                        title: Text('Update Label Size'),
84
+                        content: Text('updating to smaller label size may cause element lost, Are you sure want to update label size ?'),
85
+                        actions: [
86
+                          TextButton(
87
+                            onPressed: () => Navigator.pop(context, true), 
88
+                            child: Text('Update label size')
89
+                          ),
90
+                          TextButton(
91
+                            onPressed: () => Navigator.pop(context, false), 
92
+                            child: Text('Cancel')
93
+                          )
94
+                        ],
95
+                      ),
96
+                    );
97
+
98
+                    if (!confirmationResult) return;
99
+                    
100
+                    // Process data
101
+                    editorProvider.updateCanvasProperty(
102
+                      context,
103
+                      double.parse(_widthController.text) , 
104
+                      double.parse(_heightController.text)
105
+                    ); 
106
+                    Navigator.pop(context);
107
+                  }
108
+                },
109
+                child: Text('Submit'),
110
+              ),
111
+            ],
112
+          ),
113
+        ),
114
+      ),
115
+    );
116
+  }
117
+}

+ 0 - 51
lib/common/common_scaffold.dart

@@ -1,51 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-
30
-import 'package:flutter/material.dart';
31
-
32
-class CommonScaffold extends StatelessWidget {
33
-  final String title;
34
-  final Widget child;
35
-
36
-  const CommonScaffold({required this.title, required this.child, Key? key})
37
-      : super(key: key);
38
-
39
-  @override
40
-  Widget build(BuildContext context) {
41
-    return Scaffold(
42
-      backgroundColor: Colors.white,
43
-      appBar: AppBar(
44
-        backgroundColor: Colors.white,
45
-        foregroundColor: Colors.black,
46
-        title: Text(title),
47
-      ),
48
-      body: SizedBox.expand(child: child),
49
-    );
50
-  }
51
-}

+ 0 - 66
lib/grid/grid_painter.dart

@@ -1,66 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-import 'package:flutter/material.dart';
30
-
31
-class GridPainter extends CustomPainter {
32
-  final double boxSize = 50;
33
-
34
-  @override
35
-  void paint(Canvas canvas, Size size) {
36
-    final vLines = (size.width ~/ boxSize) + 1;
37
-    final hLines = (size.height ~/ boxSize) + 1;
38
-
39
-    final paint = Paint()
40
-      ..strokeWidth = 1
41
-      ..color = Colors.red
42
-      ..style = PaintingStyle.stroke;
43
-
44
-    final path = Path();
45
-
46
-    // Draw vertical lines
47
-    for (var i = 0; i < vLines; ++i) {
48
-      final x = boxSize * i;
49
-      path.moveTo(x, 0);
50
-      path.relativeLineTo(0, size.height);
51
-    }
52
-
53
-    // Draw horizontal lines
54
-    for (var i = 0; i < hLines; ++i) {
55
-      final y = boxSize * i;
56
-      path.moveTo(0, y);
57
-      path.relativeLineTo(size.width, 0);
58
-    }
59
-    canvas.drawPath(path, paint);
60
-  }
61
-
62
-  @override
63
-  bool shouldRepaint(covariant CustomPainter oldDelegate) {
64
-    return false;
65
-  }
66
-}

+ 0 - 49
lib/grid/grid_widget.dart

@@ -1,49 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-import 'package:flutter/material.dart';
30
-import 'grid_painter.dart';
31
-
32
-class GridWidget extends StatelessWidget {
33
-  final CustomPainter foreground;
34
-
35
-  const GridWidget(this.foreground, {Key? key}) : super(key: key);
36
-
37
-  @override
38
-  Widget build(BuildContext context) {
39
-    return Padding(
40
-      padding: const EdgeInsets.all(5),
41
-      child: SafeArea(
42
-        child: CustomPaint(
43
-          foregroundPainter: foreground,
44
-          painter: GridPainter(),
45
-        ),
46
-      ),
47
-    );
48
-  }
49
-}

+ 201 - 93
lib/main.dart

@@ -1,115 +1,219 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-import 'arc_curves_painter.dart';
30
-import 'common/common_scaffold.dart';
31
-import 'grid/grid_widget.dart';
32
-import 'ovals_painter.dart';
33
-import 'polygon_painter.dart';
34
-
35
-import 'battery/animated_battery.dart';
1
+import 'dart:math';
2
+
3
+import 'package:defer_pointer/defer_pointer.dart';
36 4
 import 'package:flutter/material.dart';
37
-import 'package:flutter/services.dart';
5
+import 'package:flutter_canvas_editor/canvas_setup_page.dart';
6
+import 'package:flutter_canvas_editor/providers/editor.dart';
7
+import 'package:flutter_canvas_editor/snaptest_page.dart';
8
+import 'package:flutter_canvas_editor/test_page.dart';
9
+import 'package:flutter_canvas_editor/widgets/elements.dart';
10
+import 'package:provider/provider.dart';
11
+import 'package:toastification/toastification.dart';
12
+
13
+import 'resizeable_element.dart';
14
+import 'widgets/toolbar.dart';
38 15
 
39 16
 void main() {
40
-  runApp(const MyApp());
17
+  runApp(MyApp());
41 18
 }
42 19
 
43 20
 class MyApp extends StatelessWidget {
44
-  const MyApp({Key? key}) : super(key: key);
45
-
46 21
   @override
47 22
   Widget build(BuildContext context) {
48
-    SystemChrome.setPreferredOrientations([
49
-      DeviceOrientation.portraitUp,
50
-      DeviceOrientation.portraitDown,
51
-    ]);
52
-    return MaterialApp(
53
-      title: 'Canvas Basics',
54
-      debugShowCheckedModeBanner: false,
55
-      theme: ThemeData(
56
-        primarySwatch: Colors.blue,
23
+    return MultiProvider(
24
+      providers: [
25
+        ChangeNotifierProvider(create: (_) => Editor()),
26
+        ChangeNotifierProvider(create: (_) => EditorTest()),
27
+      ],
28
+      child: ToastificationWrapper(
29
+        child: MaterialApp(
30
+          theme: ThemeData(
31
+            textTheme: TextTheme(
32
+              // bodyMedium: TextStyle(fontSize: 20)
33
+            )
34
+          ),
35
+          home: HomePage(),
36
+        ),
57 37
       ),
58
-      home: HomeWidget(),
59 38
     );
60 39
   }
61 40
 }
62 41
 
63
-class HomeWidget extends StatelessWidget {
64
-  HomeWidget({Key? key}) : super(key: key);
42
+class HomePage extends StatefulWidget {
43
+  @override
44
+  State<HomePage> createState() => _HomePageState();
45
+}
46
+
47
+class _HomePageState extends State<HomePage> {
48
+   double initialScale = 1;
49
+
50
+  // CanvasProperty _canvasProperty = CanvasProperty(
51
+  //   width: 0,
52
+  //   height: 0,
53
+  // );
54
+
55
+  String? selectedElmId;
56
+
57
+  bool _showResetPosition = false;
58
+
65 59
 
66
-  final items = <Item>[
67
-    Item('Polygons', GridWidget(PolygonPainter())),
68
-    Item('Ovals and Circles', GridWidget(OvalPainter())),
69
-    Item('Arc and Curves', GridWidget(ArcsCurvesPainter())),
70
-    Item('Animated Battery', const AnimatedBattery()),
71
-  ];
72 60
 
73 61
   @override
74
-  Widget build(BuildContext context) {
75
-    return CommonScaffold(
76
-      title: 'Canvas Basics',
77
-      child: Column(
78
-        mainAxisSize: MainAxisSize.max,
79
-        mainAxisAlignment: MainAxisAlignment.center,
80
-        crossAxisAlignment: CrossAxisAlignment.center,
81
-        children: items.map((e) => ItemWidget(e)).toList(),
82
-      ),
83
-    );
62
+  void initState() {
63
+    super.initState();
64
+
65
+
66
+    WidgetsBinding.instance!.addPostFrameCallback((_) {
67
+      // _canvasProperty = Provider.of<Editor>(context, listen: false).canvasProperty;
68
+      setInitialZoom();
69
+    });
70
+
71
+    setTransformControllerListener();
72
+  }
73
+
74
+  @override
75
+  void dispose() {
76
+    Provider.of<Editor>(context, listen: false).disposeCanvasTransformationController();
77
+    super.dispose();
84 78
   }
85
-}
86 79
 
87
-// TODO: Rename project
88 80
 
89
-class ItemWidget extends StatelessWidget {
90
-  final Item item;
81
+  // functions
82
+  void setInitialZoom() {
83
+    Provider.of<Editor>(context, listen: false).setCanvasTransformationInitialZoom(context);
84
+
85
+    // double deviceWidth = MediaQuery.of(context).size.width;
86
+    // double deviceHeight = MediaQuery.of(context).size.height;
87
+
88
+    // print('device width: $deviceWidth');
89
+    // print('device height: $deviceHeight');
90
+
91
+    // initialScale = deviceWidth / Provider.of<Editor>(context, listen: false).canvasProperty.width * 0.9;
92
+    
93
+    // print('initialScale $initialScale');
94
+
95
+    // _transformationController.value = Matrix4.identity()..scale(initialScale);
96
+    
97
+  }
98
+
99
+  void setTransformControllerListener() {
100
+    Provider.of<Editor>(context, listen: false).canvasTransformationController.addListener(() {
101
+      if (!_showResetPosition && (Provider.of<Editor>(context, listen: false).canvasTransformationController.value != (Matrix4.identity()..scale(initialScale)))) {
102
+        print('triggered');
103
+        setState(() {
104
+          _showResetPosition = true;
105
+        });
106
+      }
107
+    });
108
+  }
91 109
 
92
-  const ItemWidget(this.item, {Key? key}) : super(key: key);
93 110
 
94 111
   @override
95 112
   Widget build(BuildContext context) {
96
-    return Container(
97
-      width: double.infinity,
98
-      margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 7),
99
-      child: TextButton(
100
-        style: TextButton.styleFrom(
101
-            foregroundColor: Colors.white, backgroundColor: const Color(0xFF158443),
102
-            padding: const EdgeInsets.symmetric(vertical: 15)),
103
-        onPressed: () {
104
-          final widget = CommonScaffold(title: item.title, child: item.widget);
105
-          Navigator.of(context)
106
-              .push(MaterialPageRoute<void>(builder: (_) => widget));
113
+    return DeferredPointerHandler(
114
+      link: Provider.of<Editor>(context).textboxResizerDeferredPointerHandlerLink,
115
+      child: GestureDetector(
116
+        onDoubleTap: () {
117
+          // print((_transformationController.value) == Matrix4.identity());
118
+          // print(_transformationController.value.getMaxScaleOnAxis());
119
+          
120
+
107 121
         },
108
-        child: Text(
109
-          item.title,
110
-          style: const TextStyle(
111
-            fontSize: 18,
112
-            fontWeight: FontWeight.bold,
122
+        onTap: () {
123
+          print('Main Gesture Detector Tapped!');
124
+          setState(() {
125
+            Provider.of<Editor>(context, listen: false).unSelectElm();
126
+          });
127
+        },
128
+      
129
+        // onScaleStart: (details) {
130
+        //   print('Scale start $details');
131
+        //   _previousScale = _scale;
132
+        //   _previousPosition = details.focalPoint;
133
+        // },
134
+        // onScaleUpdate: (details) {
135
+        //   print('Scale update $details');
136
+        //   setState(() {
137
+        //     _scale = _previousScale * details.scale;
138
+        //     _position += details.focalPoint - _previousPosition;
139
+        //     _previousPosition = details.focalPoint;
140
+        //   });
141
+        // },
142
+        // onScaleEnd: (details) {
143
+        //   _previousScale = 1.0;
144
+        // },
145
+        child: Scaffold(
146
+          backgroundColor: Colors.grey,
147
+          appBar: AppBar(
148
+            title: Text('Template Editor'),
149
+            actions: [
150
+              IconButton(
151
+                onPressed: () {
152
+                  Navigator.push(
153
+                    context, 
154
+                    MaterialPageRoute(builder:  (context) => CanvasSetupPage(),)
155
+                  );
156
+                }, 
157
+                icon: Icon(Icons.link)
158
+              )
159
+            ],
160
+          ),
161
+          body: Column(
162
+            children: [
163
+              Expanded(
164
+                child: Stack(
165
+                  children: [
166
+                    InteractiveViewer(
167
+                      transformationController: Provider.of<Editor>(context).canvasTransformationController,
168
+                      boundaryMargin: EdgeInsets.all(double.infinity),
169
+                      maxScale: 4,
170
+                      minScale: 0.1,
171
+                      constrained: false,
172
+                      child: DeferredPointerHandler(
173
+                        child: Center(
174
+                          child: Container(
175
+                            margin: EdgeInsets.all(16),
176
+                            color: Colors.white,
177
+                            height: Provider.of<Editor>(context).canvasProperty.height,
178
+                            width: Provider.of<Editor>(context).canvasProperty.width,
179
+                            child: Stack(
180
+                              children: [
181
+                                for (ElementProperty elementProperty in Provider.of<Editor>(context).elementProperties) ... [
182
+                                  ElementWidget(
183
+                                    elementProperty: elementProperty,
184
+                                    canvasProperty: Provider.of<Editor>(context).canvasProperty,
185
+                                    globalKey: elementProperty.elementKey,
186
+                                  )
187
+                                ]
188
+                              ],
189
+                            ),
190
+                          ),
191
+                        ),
192
+                      ),
193
+                    ),
194
+      
195
+                    if (_showResetPosition) Positioned(
196
+                      bottom: 16,
197
+                      right: 16,
198
+                      child: IconButton.filled(
199
+                        tooltip: 'Reset Position',
200
+                        onPressed: () {
201
+                          Provider.of<Editor>(context, listen: false).resetCanvasTransformationScale();
202
+                          _showResetPosition = false;
203
+      
204
+                          setState(() {}); 
205
+                        }, 
206
+                        icon: Icon(Icons.center_focus_strong),
207
+                        color: Colors.white,
208
+                      ),
209
+                    )
210
+                  ],
211
+                ),
212
+              ),
213
+      
214
+              // Toolbar
215
+              ToolbarWidget()
216
+            ],
113 217
           ),
114 218
         ),
115 219
       ),
@@ -117,9 +221,13 @@ class ItemWidget extends StatelessWidget {
117 221
   }
118 222
 }
119 223
 
120
-class Item {
121
-  final String title;
122
-  final Widget widget;
123 224
 
124
-  Item(this.title, this.widget);
125
-}
225
+class CanvasProperty {
226
+  double width;
227
+  double height;
228
+
229
+  CanvasProperty({
230
+    required this.width,
231
+    required this.height,
232
+  });
233
+}

+ 0 - 59
lib/ovals_painter.dart

@@ -1,59 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-import 'package:flutter/material.dart';
30
-
31
-class OvalPainter extends CustomPainter {
32
-  @override
33
-  void paint(Canvas canvas, Size size) {
34
-    final paint = Paint()
35
-      ..strokeWidth = 4
36
-      ..color = Colors.blueAccent
37
-      ..style = PaintingStyle.stroke;
38
-
39
-    
40
-    const circleRadius = 75.0;
41
-    const circleCenter = Offset(200, 150);
42
-    canvas.drawCircle(circleCenter, circleRadius, paint);
43
-
44
-    const ovalCenter = Offset(200, 275);
45
-    final oval = Rect.fromCenter(center: ovalCenter, width: 250, height: 100);
46
-    canvas.drawOval(oval, paint);
47
-
48
-    var concentricCircleRadius = 100.0;
49
-    const center = Offset(200, 500);
50
-    while (concentricCircleRadius > 0) {
51
-      canvas.drawCircle(center, concentricCircleRadius, paint);
52
-      concentricCircleRadius -= 10;
53
-    }
54
-
55
-  }
56
-
57
-  @override
58
-  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
59
-}

+ 0 - 96
lib/polygon_painter.dart

@@ -1,96 +0,0 @@
1
-// Copyright (c) 2021 Razeware LLC
2
-//
3
-// Permission is hereby granted, free of charge, to any person obtaining a copy
4
-// of this software and associated documentation files (the "Software"), to deal
5
-// in the Software without restriction, including without limitation the rights
6
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
-// copies of the Software, and to permit persons to whom the Software is
8
-// furnished to do so, subject to the following conditions:
9
-//
10
-// The above copyright notice and this permission notice shall be included in
11
-// all copies or substantial portions of the Software.
12
-//
13
-// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14
-// distribute, sublicense, create a derivative work, and/or sell copies of the
15
-// Software in any work that is designed, intended, or marketed for pedagogical
16
-// or instructional purposes related to programming, coding, application
17
-// development, or information technology.  Permission for such use, copying,
18
-// modification, merger, publication, distribution, sublicensing, creation of
19
-// derivative works, or sale is expressly withheld.
20
-//
21
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
-// THE SOFTWARE.
28
-
29
-
30
-import 'package:flutter/material.dart';
31
-
32
-class PolygonPainter extends CustomPainter {
33
-  @override
34
-  void paint(Canvas canvas, Size size) {
35
-    final paint = Paint()
36
-      ..strokeWidth = 5
37
-      ..color = Colors.indigoAccent
38
-      ..style = PaintingStyle.fill;
39
-
40
-    final triangle = Path();
41
-    triangle.moveTo(150, 0);
42
-    triangle.relativeLineTo(100, 100);
43
-    triangle.relativeLineTo(-150, 0);
44
-    triangle.close();
45
-
46
-    final square1 = Path();
47
-    square1.moveTo(50, 150);
48
-    square1.relativeLineTo(100, 0);
49
-    square1.relativeLineTo(0, 100);
50
-    square1.relativeLineTo(-100, 0);
51
-    square1.close();
52
-
53
-    const square2 = Rect.fromLTWH(200, 150, 100, 100);
54
-
55
-    final hexagon = Path()
56
-    // 1
57
-      ..moveTo(175, 300)
58
-    // 2
59
-      ..relativeLineTo(75, 50)
60
-    // 3
61
-      ..relativeLineTo(0, 75)
62
-    // 4
63
-      ..relativeLineTo(-75, 50)
64
-    // 5
65
-      ..relativeLineTo(-75, -50)
66
-    // 6
67
-      ..relativeLineTo(0, -75)
68
-    // 7
69
-      ..close();
70
-
71
-    final cross = Path()
72
-      ..moveTo(150, 500)
73
-      ..relativeLineTo(50, 0)
74
-      ..relativeLineTo(0, 50)
75
-      ..relativeLineTo(50, 0)
76
-      ..relativeLineTo(0, 50)
77
-      ..relativeLineTo(-50, 0)
78
-      ..relativeLineTo(0, 50)
79
-      ..relativeLineTo(-50, 0)
80
-      ..relativeLineTo(0, -50)
81
-      ..relativeLineTo(-50, 0)
82
-      ..relativeLineTo(0, -50)
83
-      ..relativeLineTo(50, 0)
84
-      ..close();
85
-  
86
-  
87
-    canvas.drawPath(triangle, paint);
88
-    canvas.drawPath(square1, paint);
89
-    canvas.drawRect(square2, paint);
90
-    canvas.drawPath(cross, paint);
91
-    canvas.drawPath(hexagon, paint);  
92
-  }
93
-
94
-  @override
95
-  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
96
-}

+ 506 - 0
lib/providers/editor.dart

@@ -0,0 +1,506 @@
1
+import 'dart:developer';
2
+
3
+import 'package:defer_pointer/defer_pointer.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:flutter_canvas_editor/style/canvas_style.dart';
6
+import 'package:flutter_canvas_editor/widgets/elements.dart';
7
+import 'package:toastification/toastification.dart';
8
+import 'package:uuid/uuid.dart';
9
+
10
+import '../main.dart';
11
+
12
+var uuid = Uuid();
13
+
14
+class Editor extends ChangeNotifier {
15
+  String? selectedElmId;
16
+  
17
+  bool isEditing = false;
18
+
19
+  final textboxResizerDeferredPointerHandlerLink = DeferredPointerHandlerLink();
20
+
21
+
22
+
23
+  // ? Canvas State
24
+  double canvasScale = 1.0;
25
+
26
+  final CanvasProperty canvasProperty = CanvasProperty(
27
+    width: 780,
28
+    height: 1000,
29
+  );
30
+
31
+  TransformationController canvasTransformationController = TransformationController();
32
+
33
+  void setCanvasTransformationInitialZoom(BuildContext context) {
34
+    final deviceWidth = MediaQuery.of(context).size.width;
35
+
36
+    canvasScale = deviceWidth / canvasProperty.width * 0.9;
37
+
38
+    canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
39
+  }
40
+
41
+  void resetCanvasTransformationScale() {
42
+    print('canvas scale $canvasScale');
43
+    canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
44
+  }
45
+
46
+  void disposeCanvasTransformationController() {
47
+    canvasTransformationController.dispose();
48
+  }
49
+
50
+
51
+
52
+
53
+  List<ElementProperty> elementProperties = [
54
+    ElementProperty(
55
+      id: uuid.v4(), 
56
+      valueController: TextEditingController(text: '{{QRCODE}}'), 
57
+      type: ElementType.qr, 
58
+      position: ElementPosition(top: 0, left: 0), 
59
+      width: 80, 
60
+      quarterTurns: 0, 
61
+      elementKey: GlobalKey(),
62
+      qrScale: 3
63
+    )
64
+  ];
65
+  // int _reservedIdUntil = 0;
66
+
67
+  // ? udpate canvas
68
+  void updateCanvasProperty(BuildContext context, double width, double height) {
69
+    canvasProperty.height = height;
70
+    canvasProperty.width = width;
71
+
72
+    _adjustOutOfBoundElement();
73
+
74
+    setCanvasTransformationInitialZoom(context);
75
+
76
+    notifyListeners();
77
+  }
78
+
79
+  void _adjustOutOfBoundElement() {
80
+    for (var element in elementProperties) {
81
+      bool isOutOfBoundFromTop = (element.position.top + 10) > canvasProperty.height;
82
+      bool isOutOfBoundFromLeft = (element.position.left + 10) > canvasProperty.width;
83
+
84
+      // if (isOutOfBoundFromTop | isOutOfBoundFromLeft) {
85
+      //   element.position.top = 0;
86
+      //   element.position.left = 0;
87
+      // }
88
+
89
+      if (isOutOfBoundFromTop) {
90
+        element.position.top = canvasProperty.height - (element.elementKey.currentContext?.size?.height ?? 0);
91
+      }
92
+
93
+      if (isOutOfBoundFromLeft) {
94
+        element.position.left = canvasProperty.width - (element.elementKey.currentContext?.size?.width ?? 0);
95
+      }
96
+      
97
+    }
98
+
99
+  }
100
+
101
+  void populateElement(List<ElementProperty> elementProperties) {
102
+    this.elementProperties.addAll(elementProperties);
103
+    // _reservedIdUntil = this.elementProperties.length - 1;
104
+    notifyListeners();
105
+  }
106
+
107
+  void addTextElement(){
108
+    String id = uuid.v4();
109
+
110
+    ElementProperty element = ElementProperty(
111
+      id: id, 
112
+      valueController: TextEditingController(text: 'Double tap to edit text'), 
113
+      type: ElementType.text, 
114
+      position: ElementPosition(top: 0, left: 0), 
115
+      // size: ElementSize(width: 20, height: 70), 
116
+      width: 20,
117
+      elementKey: GlobalKey(),
118
+      quarterTurns: 0
119
+    );
120
+
121
+
122
+    elementProperties.add(element);
123
+    // _reservedIdUntil = elementProperties.length - 1;r
124
+    notifyListeners();
125
+
126
+    selectElmById(id);
127
+  } 
128
+
129
+  void addTextboxElement() {
130
+    String id = uuid.v4();
131
+
132
+    ElementProperty element = ElementProperty(
133
+      id: id, 
134
+      valueController: TextEditingController(text: 'Double tap to edit text'), 
135
+      type: ElementType.textbox, 
136
+      position: ElementPosition(top: 0, left: 0), 
137
+      width: 70, 
138
+      quarterTurns: 0, 
139
+      elementKey: GlobalKey()
140
+    );
141
+
142
+    elementProperties.add(element);
143
+    notifyListeners();
144
+
145
+    selectElmById(id);
146
+  }
147
+
148
+  void updateElmPosition(Offset offset) {
149
+    ElementProperty? element = selectedElm;
150
+
151
+    if (element == null) return;
152
+
153
+    // element.position = ElementPosition(top: offset.dy, left: offset.dx);
154
+    print(offset.dx);
155
+    element.position.top += offset.dy.round();
156
+    element.position.left += offset.dx.round();
157
+    notifyListeners();
158
+  }
159
+
160
+  void updateElmWitdh(double width) {
161
+    ElementProperty? element = selectedElm;
162
+
163
+    if (element == null) return;
164
+
165
+
166
+    element.width = width;
167
+    notifyListeners();
168
+  }
169
+
170
+  void toggleLockElement() {
171
+    if (selectedElm == null) return;
172
+
173
+    selectedElm!.isLocked = !selectedElm!.isLocked;
174
+    notifyListeners();
175
+  }
176
+
177
+
178
+  void selectElmById(String id) {
179
+    selectedElmId = id;
180
+    notifyListeners();
181
+  }
182
+
183
+  void enableEdit() {
184
+    if (selectedElm!.isLocked) {
185
+      _showLockedToast('Cant modify locked element');
186
+      return;
187
+    }
188
+
189
+    isEditing = true;
190
+    notifyListeners();
191
+  }
192
+
193
+  void disableEdit() {
194
+    isEditing = false;
195
+    notifyListeners();
196
+  }
197
+
198
+  void unSelectElm() {
199
+    selectedElmId = null;
200
+    isEditing = false;
201
+    notifyListeners();
202
+  }
203
+
204
+  bool isSelected(String id) {
205
+    return selectedElmId == id;
206
+  }
207
+
208
+  // ? Getters
209
+  String get selectedElmType {
210
+    if (elementProperties.isNotEmpty && selectedElmId != null) {
211
+      final selectedElm = elementProperties.firstWhere((element) => element.id == selectedElmId);
212
+      return elementGetter(selectedElm.type);
213
+    }
214
+
215
+    return '';
216
+  }
217
+
218
+  ElementProperty? get selectedElm {
219
+    if (elementProperties.isNotEmpty && selectedElmId != null) {
220
+      return elementProperties.firstWhere((element) => element.id == selectedElmId);
221
+    }
222
+
223
+    return null;
224
+  }
225
+
226
+  GlobalKey? get selectedElmKey {
227
+    if (elementProperties.isNotEmpty && selectedElmId != null) {
228
+      return elementProperties.firstWhere((element) => element.id == selectedElmId).elementKey;
229
+    }
230
+
231
+    return null;
232
+  }
233
+
234
+  bool get shouldShowFontResizer {
235
+    if (selectedElm == null) return false;
236
+
237
+    if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return false;
238
+
239
+    return true;
240
+  }
241
+
242
+  bool get shouldShowQrResizer {
243
+    if (selectedElm == null) return false;
244
+
245
+    if (selectedElm!.type != ElementType.qr) return false;
246
+
247
+    return true;
248
+  }
249
+
250
+  bool get shouldShowDeleteElementButton {
251
+    if (selectedElm == null) return false;
252
+
253
+    if ([ElementType.qr].contains(selectedElm!.type)) return false;
254
+
255
+    return true;
256
+  }
257
+
258
+
259
+  // ? Element overlay
260
+  bool shouldShowTextboxResizer(String elmId) {
261
+    if (selectedElmId == null) return false;
262
+
263
+    if (selectedElmId != elmId ) return false;
264
+
265
+    if (selectedElm!.type != ElementType.textbox) return false;
266
+
267
+    return true;
268
+  }
269
+
270
+  bool shouldShowOverlay(String elmId) {
271
+    if (selectedElmId == null) return false;
272
+
273
+    if (selectedElmId != elmId ) return false;
274
+
275
+    if (selectedElm!.type == ElementType.qr) return false;
276
+
277
+    return true;
278
+  }
279
+
280
+
281
+  // Toolbar
282
+  bool get insertElementMode{
283
+    if (selectedElm == null) return true;
284
+    return false;
285
+  }
286
+
287
+
288
+  /// Can only rotate [ElementType.text, ElementType.textBox]
289
+  void rotate() {
290
+    ElementProperty? element = selectedElm;
291
+
292
+    if (element == null) return;
293
+
294
+    if (element.isLocked) {
295
+      _showLockedToast('Cant rotate locked element');
296
+      return;
297
+    }
298
+    
299
+    if (![ElementType.text, ElementType.textbox].contains(element.type)) return;
300
+
301
+
302
+    if (element.type == ElementType.text) _rotateText(element);
303
+
304
+    if (element.type == ElementType.textbox) _rotateTextBox(element);
305
+
306
+
307
+    // Adjust Size
308
+    // double currentElementHeight = selectedElmKey!.currentContext!.size!.height;
309
+    // double currentElementWidth = selectedElmKey!.currentContext!.size!.width;
310
+
311
+    //   selectedElmKey!.currentContext!.size!.height = currentElementWidth;
312
+    //   selectedElmKey!.currentContext!.size!.width  = currentElementHeight;
313
+    
314
+
315
+    notifyListeners();
316
+
317
+  }
318
+
319
+  void _rotateText(ElementProperty element) {
320
+    if (element.quarterTurns < 3) {
321
+      element.quarterTurns += 1;
322
+    } else {
323
+      element.quarterTurns = 0;
324
+    }
325
+  }
326
+
327
+  void _rotateTextBox(ElementProperty element) {
328
+    if (element.quarterTurns == 0) {
329
+      element.quarterTurns = 3;
330
+    } else {
331
+      element.quarterTurns = 0;
332
+    }
333
+  }
334
+
335
+  // FontSize Handler
336
+  void changeFontSize(int? fontSize) {
337
+    if (fontSize == null) return;
338
+
339
+    if (selectedElm == null) return;
340
+
341
+    if (selectedElm!.isLocked) {
342
+      _showLockedToast('Cant modify locked element');
343
+      return;
344
+    }
345
+
346
+    selectedElm!.fontScale = fontSize;
347
+    notifyListeners();
348
+  }
349
+  
350
+  void incrementFontSize() {
351
+    if (selectedElm == null) return;
352
+
353
+    final incrementTo = selectedElm!.fontScale + 1;
354
+
355
+    if (selectedElm!.isLocked) {
356
+      _showLockedToast('Cant modify locked element');
357
+      return;
358
+    }
359
+    // check if value is allowed for resize
360
+    if (CanvasStyle.fontSizeMap.containsKey(incrementTo)) {
361
+      selectedElm!.fontScale = incrementTo;
362
+      print('kepenjet increase');
363
+    } else {
364
+      print('cant increment');
365
+    }
366
+
367
+    notifyListeners();
368
+  }
369
+
370
+  void decrementFontSize() {
371
+    if (selectedElm == null) return;
372
+
373
+    final decrementTo = selectedElm!.fontScale - 1;
374
+
375
+    if (selectedElm!.isLocked) {
376
+      _showLockedToast('Cant modify locked element');
377
+      return;
378
+    }
379
+    // check if value is allowed for resize
380
+    if (CanvasStyle.fontSizeMap.containsKey(decrementTo)) {
381
+      selectedElm!.fontScale = decrementTo;
382
+    } else {
383
+      print('cant decrement');
384
+    }
385
+
386
+    notifyListeners();
387
+  }
388
+
389
+  // Qr Size Handler
390
+  void changeQrSize(int? fontSize) {
391
+    if (fontSize == null) return;
392
+
393
+    if (selectedElm == null) return;
394
+
395
+    if (selectedElm!.isLocked) {
396
+      _showLockedToast('Cant modify locked element');
397
+      return;
398
+    }
399
+
400
+    selectedElm!.qrScale = fontSize;
401
+    notifyListeners();
402
+  }
403
+  
404
+  void incrementQrSize() {
405
+    if (selectedElm == null) return;
406
+
407
+    if (selectedElm!.type != ElementType.qr) return;
408
+
409
+    final incrementTo = selectedElm!.qrScale! + 1;
410
+
411
+    if (selectedElm!.isLocked) {
412
+      _showLockedToast('Cant modify locked element');
413
+      return;
414
+    }
415
+    // check if value is allowed for resize
416
+    if (CanvasStyle.qrSizeMap.containsKey(incrementTo)) {
417
+      selectedElm!.qrScale = incrementTo;
418
+    } else {
419
+      print('cant increment');
420
+    }
421
+
422
+    notifyListeners();
423
+  }
424
+
425
+  void decrementQrSize() {
426
+    if (selectedElm == null) return;
427
+
428
+
429
+    if (selectedElm!.type != ElementType.qr) return;
430
+
431
+    final decrementTo = selectedElm!.qrScale! - 1;
432
+
433
+    if (selectedElm!.isLocked) {
434
+      _showLockedToast('Cant modify locked element');
435
+      return;
436
+    }
437
+
438
+    // check if value is allowed for resize
439
+    if (CanvasStyle.qrSizeMap.containsKey(decrementTo)) {
440
+      selectedElm!.qrScale = decrementTo;
441
+    } else {
442
+      print('cant decrement');
443
+    }
444
+
445
+    notifyListeners();
446
+  }
447
+
448
+  // Delete Element
449
+  Future<void> deleteElement(BuildContext context) async {
450
+    if (selectedElm == null) return;
451
+
452
+    if (selectedElm!.isLocked) {
453
+      _showLockedToast('Cant delete locked element');
454
+      return;
455
+    } 
456
+
457
+    final shouldDelete = await showDialog(
458
+      context: context, 
459
+      builder: (context) {
460
+        return AlertDialog(
461
+          title: Text('Delete Element ?'),
462
+          content: Text('Are you sure want to delete this element ?'),
463
+          actions: [
464
+            TextButton(
465
+              onPressed: () => Navigator.pop(context, false), 
466
+              child: Text('Cancel')
467
+            ),
468
+            TextButton(
469
+              onPressed: () => Navigator.pop(context, true), 
470
+              child: Text('Delete')
471
+            ),
472
+          ],
473
+        );
474
+      },
475
+    );
476
+
477
+    if (!shouldDelete) return;
478
+
479
+    elementProperties.removeWhere((e) => e.id == selectedElm!.id);
480
+    unSelectElm();
481
+
482
+    notifyListeners();
483
+  }
484
+
485
+  // ? snapping element
486
+  // Offset? snapToElement(Offset newPosition) {
487
+  //   for (var element in elementProperties) {
488
+  //     if ((newPosition.dx - element.))
489
+
490
+  //   }
491
+  // }
492
+
493
+  // ? helper method
494
+  void _showLockedToast(String titleText) {
495
+    toastification.show(
496
+      title: Text(titleText),
497
+      description: Text('unlock to change element property'),
498
+      closeButtonShowType: CloseButtonShowType.none,
499
+      style: ToastificationStyle.minimal,
500
+      type: ToastificationType.warning,
501
+      autoCloseDuration: const Duration(seconds: 3),
502
+      alignment: Alignment.bottomCenter,
503
+      dragToClose: true
504
+    );
505
+  }
506
+}

+ 85 - 0
lib/resizeable_element.dart

@@ -0,0 +1,85 @@
1
+import 'package:defer_pointer/defer_pointer.dart';
2
+import 'package:flutter/material.dart';
3
+import 'package:provider/provider.dart';
4
+
5
+class EditorTest with ChangeNotifier {
6
+  double _selectedWidth = 150.0;
7
+
8
+  double get selectedWidth => _selectedWidth;
9
+
10
+  void updateElmWidth(double newWidth) {
11
+    _selectedWidth = newWidth;
12
+    notifyListeners();
13
+  }
14
+}
15
+
16
+class ResizableElement extends StatefulWidget {
17
+  @override
18
+  _ResizableElementState createState() => _ResizableElementState();
19
+}
20
+
21
+class _ResizableElementState extends State<ResizableElement> {
22
+  double _initialWidth = 150.0; // Initial width of the element
23
+  Offset? _dragStartPosition; // Starting position of the drag
24
+
25
+  @override
26
+  Widget build(BuildContext context) {
27
+    return Scaffold(
28
+      appBar: AppBar(title: Text("Resizable Element ")),
29
+      body: DeferredPointerHandler(
30
+        child: Stack(
31
+          clipBehavior: Clip.none,
32
+          children: [
33
+            // Resizable container
34
+            Container(
35
+              width: _initialWidth,
36
+              // height: 100,
37
+              color: Colors.blue[100],
38
+              // alignment: Alignment.center,
39
+              child: Text(
40
+                "Resizable Element $_initialWidth",
41
+                textAlign: TextAlign.center,
42
+              ),
43
+            ),
44
+            // Drag handle
45
+            Positioned(
46
+              right: -30,
47
+              top: 0,
48
+              bottom: 0,
49
+              child: DeferPointer(
50
+                child: Listener(
51
+                  onPointerDown: (event) {
52
+                    _dragStartPosition = event.position;
53
+                  },
54
+                  onPointerMove: (event) {
55
+                    setState(() {
56
+                      if (_dragStartPosition != null) {
57
+                        // Calculate drag delta
58
+                        final delta = event.position.dx - _dragStartPosition!.dx;
59
+                        _initialWidth = (_initialWidth + delta).clamp(75.0, MediaQuery.of(context).size.width);
60
+                        _dragStartPosition = event.position; // Update drag position
61
+                      }
62
+                    });
63
+                  },
64
+                  onPointerUp: (event) {
65
+                    _dragStartPosition = null; // Reset drag start position
66
+                  },
67
+                  child: Container(
68
+                    width: 30,
69
+                    height: 100,
70
+                    color: Colors.blue,
71
+                    child: Icon(
72
+                      Icons.drag_handle,
73
+                      size: 16,
74
+                      color: Colors.white,
75
+                    ),
76
+                  ),
77
+                ),
78
+              ),
79
+            ),
80
+          ],
81
+        ),
82
+      ),
83
+    );
84
+  }
85
+}

+ 93 - 0
lib/snaptest_page.dart

@@ -0,0 +1,93 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class SnapToGridCanvas extends StatefulWidget {
4
+  @override
5
+  _SnapToGridCanvasState createState() => _SnapToGridCanvasState();
6
+}
7
+
8
+class _SnapToGridCanvasState extends State<SnapToGridCanvas> {
9
+  final double snappingThreshold = 10.0;
10
+  final List<Rect> elements = []; // Store elements' bounds
11
+  Offset selectedElementPosition = Offset(100, 100);
12
+  Size selectedElementSize = Size(100, 100);
13
+
14
+  Offset? snapToElementOrCanvas(Offset newPosition, Size canvasSize) {
15
+    for (var element in elements) {
16
+      // Snap to other elements
17
+      if ((newPosition.dx - element.right).abs() <= snappingThreshold) {
18
+        return Offset(element.right, newPosition.dy); // Snap to the right of another element
19
+      } else if ((newPosition.dx - element.left).abs() <= snappingThreshold) {
20
+        return Offset(element.left, newPosition.dy); // Snap to the left of another element
21
+      } else if ((newPosition.dy - element.bottom).abs() <= snappingThreshold) {
22
+        return Offset(newPosition.dx, element.bottom); // Snap to the bottom of another element
23
+      } else if ((newPosition.dy - element.top).abs() <= snappingThreshold) {
24
+        return Offset(newPosition.dx, element.top); // Snap to the top of another element
25
+      }
26
+    }
27
+
28
+    // Snap to canvas center
29
+    final canvasCenter = Offset(canvasSize.width / 2, canvasSize.height / 2);
30
+    if ((newPosition.dx - canvasCenter.dx).abs() <= snappingThreshold) {
31
+      return Offset(canvasCenter.dx, newPosition.dy); // Snap horizontally to canvas center
32
+    } else if ((newPosition.dy - canvasCenter.dy).abs() <= snappingThreshold) {
33
+      return Offset(newPosition.dx, canvasCenter.dy); // Snap vertically to canvas center
34
+    }
35
+
36
+    return null; // No snapping
37
+  }
38
+
39
+  @override
40
+  Widget build(BuildContext context) {
41
+    final canvasSize = MediaQuery.of(context).size;
42
+
43
+    return Scaffold(
44
+      appBar: AppBar(title: Text("Snap to Grid Canvas")),
45
+      body: GestureDetector(
46
+        onPanUpdate: (details) {
47
+          setState(() {
48
+            final newPosition = selectedElementPosition + details.delta;
49
+
50
+            // Check for snapping
51
+            final snappedPosition = snapToElementOrCanvas(newPosition, canvasSize);
52
+            if (snappedPosition != null) {
53
+              selectedElementPosition = snappedPosition;
54
+            } else {
55
+              selectedElementPosition = newPosition;
56
+            }
57
+          });
58
+        },
59
+        child: Stack(
60
+          children: [
61
+            // Canvas background
62
+            Container(color: Colors.grey[200]),
63
+
64
+            // Render all elements
65
+            ...elements.map((element) {
66
+              return Positioned(
67
+                left: element.left,
68
+                top: element.top,
69
+                child: Container(
70
+                  width: element.width,
71
+                  height: element.height,
72
+                  color: Colors.blue,
73
+                ),
74
+              );
75
+            }).toList(),
76
+
77
+            // Selected element
78
+            Positioned(
79
+              left: selectedElementPosition.dx,
80
+              top: selectedElementPosition.dy,
81
+              child: Container(
82
+                width: selectedElementSize.width,
83
+                height: selectedElementSize.height,
84
+                color: Colors.red,
85
+                child: Center(child: Text("Drag Me")),
86
+              ),
87
+            ),
88
+          ],
89
+        ),
90
+      ),
91
+    );
92
+  }
93
+}

+ 64 - 0
lib/style/canvas_style.dart

@@ -0,0 +1,64 @@
1
+// import 'package:flutter/material.dart';
2
+
3
+import 'package:flutter/material.dart';
4
+
5
+class CanvasStyle {
6
+  static double fontSizeFallback = 18;
7
+  static double qrSizeFallback = 82;
8
+
9
+  static Map<int, double> fontSizeMap = {
10
+    1: 18,
11
+    2: 25,
12
+    3: 31.5,
13
+    4: 37,
14
+    5: 43,
15
+    6: 50,
16
+    7: 57,
17
+    8: 63.5,
18
+    9: 70
19
+  };
20
+
21
+  static Map<int, double> qrSizeMap = {
22
+    1: 82,
23
+    2: 124,
24
+    3: 164,
25
+    4: 204,
26
+    5: 248,
27
+    6: 286,
28
+    7: 330,
29
+    8: 370,
30
+    9: 412
31
+  };
32
+
33
+  static double getFontSize(int elmFontSize) {
34
+    return fontSizeMap[elmFontSize] ?? fontSizeFallback;
35
+  }
36
+
37
+  static TextStyle getTextStyle(int elmFontSize) {
38
+    return TextStyle(
39
+      fontFamily: 'RobotoCondensed', 
40
+      fontSize: CanvasStyle.getFontSize(elmFontSize),
41
+      letterSpacing: 0
42
+    );
43
+  }
44
+
45
+  static double getQrSize(int elmQrScale) {
46
+    return qrSizeMap[elmQrScale] ?? qrSizeFallback;
47
+  }
48
+
49
+}
50
+
51
+
52
+// void main() {
53
+//   int elmFontSize = 2;
54
+//   print(CanvasStyle.fontSizeMap[elmFontSize]);
55
+
56
+//   elmFontSize -= 1;
57
+//   print(CanvasStyle.fontSizeMap[elmFontSize]);
58
+
59
+//   if (CanvasStyle.fontSizeMap.containsKey(elmFontSize)) {
60
+//     print('resize');
61
+//   } else {
62
+//     print('cant resize');
63
+//   }
64
+// }

+ 39 - 0
lib/test_page.dart

@@ -0,0 +1,39 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class TestPage extends StatelessWidget {
4
+  @override
5
+  Widget build(BuildContext context) {
6
+    return Scaffold(
7
+      appBar: AppBar(
8
+        title: Text('Hit Test'),
9
+      ),
10
+      body: Stack(
11
+        clipBehavior: Clip.none,
12
+        children: [
13
+          GestureDetector(
14
+            onTap: () {
15
+              print('Red Container tapped');
16
+            },
17
+            child: Container(
18
+              width: 100,
19
+              height: 100,
20
+              color: Colors.red,
21
+              margin: EdgeInsets.all(20),
22
+            ),
23
+          ),
24
+          GestureDetector(
25
+            onTap: () {
26
+              print('Blue Container tapped');
27
+            },
28
+            child: Container(
29
+              width: 50,
30
+              height: 50,
31
+              color: Colors.blue,
32
+              margin: EdgeInsets.fromLTRB(30, 20, 20, 20),
33
+            ),
34
+          ),
35
+        ],
36
+      ),
37
+    );
38
+  }
39
+}

+ 446 - 0
lib/widgets/elements.dart

@@ -0,0 +1,446 @@
1
+import 'package:defer_pointer/defer_pointer.dart';
2
+import 'package:flutter/gestures.dart';
3
+import 'package:flutter/material.dart';
4
+import 'package:flutter_canvas_editor/providers/editor.dart';
5
+import 'package:provider/provider.dart';
6
+import 'dart:math' as math;
7
+
8
+import '../main.dart';
9
+import '../style/canvas_style.dart';
10
+
11
+enum ElementType {text, textbox, qr, image}
12
+
13
+String elementGetter(ElementType type) {
14
+  switch (type) {
15
+    case ElementType.text:
16
+      return 'Text';
17
+    case ElementType.textbox:
18
+      return 'TextBox';
19
+    case ElementType.qr:
20
+      return 'QR';
21
+    case ElementType.image:
22
+      return 'Image';
23
+    default:
24
+      return '';
25
+  }
26
+}
27
+
28
+class ElementPosition {
29
+  double top;
30
+  double left;
31
+
32
+  ElementPosition({required this.top, required this.left});
33
+}
34
+
35
+class ElementSize {
36
+  double width;
37
+  double height;
38
+
39
+  ElementSize({required this.width, required this.height});
40
+}
41
+
42
+class ElementProperty {
43
+  String id;
44
+  TextEditingController valueController;
45
+  ElementType type;
46
+  ElementPosition position;
47
+  // ElementSize size;
48
+  double width;
49
+  int quarterTurns;
50
+  GlobalKey elementKey;
51
+  Color? color;
52
+  int fontScale;
53
+  int? qrScale; 
54
+  bool isLocked;
55
+
56
+  ElementProperty({
57
+    required this.id,
58
+    required this.valueController,
59
+    required this.type,
60
+    required this.position,
61
+    // required this.size,
62
+    required this.width,
63
+    required this.quarterTurns,
64
+    required this.elementKey,
65
+    this.color,
66
+    this.fontScale = 3,
67
+    this.qrScale,
68
+    this.isLocked = false
69
+  });
70
+
71
+}
72
+
73
+
74
+
75
+class ElementWidget extends StatefulWidget {
76
+  final ElementProperty elementProperty;
77
+  final CanvasProperty canvasProperty;
78
+  final GlobalKey globalKey;
79
+  
80
+  const ElementWidget({
81
+    required this.elementProperty, 
82
+    required this.canvasProperty, 
83
+    required this.globalKey,
84
+    super.key
85
+  });
86
+
87
+  @override
88
+  State<ElementWidget> createState() => _ElementWidgetState();
89
+}
90
+
91
+class _ElementWidgetState extends State<ElementWidget> {
92
+  Offset? _dragStartPosition;
93
+
94
+  // dragable container 
95
+  final double _resizerHeight = 36;
96
+  final double _resizerWidth = 36;
97
+
98
+  double _height = 0;
99
+
100
+  double _currentScale = 1.0;
101
+
102
+  late TransformationController _transformController;
103
+
104
+  @override
105
+  void initState() {
106
+    super.initState();
107
+    
108
+    WidgetsBinding.instance.addPostFrameCallback((_) => _updateScale);
109
+
110
+    _transformController = Provider.of<Editor>(context, listen: false).canvasTransformationController;
111
+
112
+    _setInitialScale();
113
+    _listenTransformationChanges();
114
+  }
115
+
116
+
117
+
118
+  void _updateScale() {
119
+    _currentScale = Provider.of<Editor>(context, listen: false).canvasTransformationController.value.getMaxScaleOnAxis();
120
+    
121
+    setState(() {});
122
+  }
123
+
124
+  void _setInitialScale() {
125
+    _updateScale();
126
+  }
127
+
128
+  void _listenTransformationChanges() {
129
+     _transformController.addListener(_updateScale);
130
+  }
131
+
132
+  void _removeTransformationListener() {
133
+    _transformController.removeListener(_updateScale);
134
+  }
135
+
136
+
137
+
138
+
139
+  @override
140
+  void dispose() {
141
+   _removeTransformationListener();
142
+    super.dispose();
143
+  }
144
+
145
+
146
+  @override
147
+  Widget build(BuildContext context) {
148
+    final editorProvider = Provider.of<Editor>(context);
149
+
150
+    ElementProperty element = widget.elementProperty;
151
+    final CanvasProperty canvas = widget.canvasProperty;
152
+
153
+    WidgetsBinding.instance.addPostFrameCallback((_) {
154
+      _height = element.elementKey.currentContext!.size!.height;
155
+    });
156
+
157
+    return Positioned(
158
+      top: element.position.top,
159
+      left: element.position.left,
160
+      child: GestureDetector(
161
+        onDoubleTap: () {
162
+          print('double tap detected');
163
+          Provider.of<Editor>(context, listen: false).selectElmById(element.id);
164
+          Provider.of<Editor>(context, listen: false).enableEdit();
165
+        },
166
+        onTap: () {
167
+          print('Element Gesture Detector Tapped!');
168
+          Provider.of<Editor>(context, listen: false).selectElmById(element.id);
169
+        },
170
+        onPanUpdate: (details) {
171
+          if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
172
+            return;
173
+          }
174
+
175
+          if (element.isLocked) return;
176
+    
177
+          final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
178
+    
179
+          if (elmKeyContext == null) {
180
+            debugPrint('WARNING, elmKeyContext not found');
181
+          }
182
+    
183
+          bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
184
+    
185
+          double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
186
+          double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
187
+    
188
+          double right = element.position.left + width;
189
+          double bottom = element.position.top + height;
190
+          
191
+    
192
+          bool isElmWidthExceedCanvas = width > canvas.width; 
193
+          bool isElmHeightExceedCanvas = height > canvas.height;
194
+      
195
+          // Check if the object is out of the canvas
196
+          if (element.position.top < 0) {
197
+            setState(() {
198
+              element.position.top = 0;
199
+            });
200
+      
201
+            print('object is out of canvas');
202
+            return;
203
+          }
204
+      
205
+          if (element.position.left < 0) {
206
+            setState(() {
207
+              element.position.left = 0;
208
+            });
209
+      
210
+            print('object is out of canvas');
211
+            return;
212
+          }
213
+    
214
+          if (!isElmHeightExceedCanvas && bottom > canvas.height) {
215
+            setState(() {
216
+              element.position.top = canvas.height - height;
217
+            });
218
+      
219
+            print('object is out of canvas');
220
+            return;
221
+          }
222
+      
223
+      
224
+          if (!isElmWidthExceedCanvas && right > canvas.width) {
225
+            setState(() {
226
+              element.position.left = canvas.width - width;
227
+            });
228
+      
229
+            print('object is out of canvas');
230
+            return;
231
+          }
232
+      
233
+          Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
234
+        },
235
+        child: Stack(
236
+          clipBehavior: Clip.none,
237
+          children: [
238
+            RotatedBox(
239
+              // angle: element.quarterTurns * (math.pi / 2),
240
+              quarterTurns: element.quarterTurns,
241
+              child: Stack(
242
+                clipBehavior: Clip.none,
243
+                children: [
244
+                  Container(
245
+                    width: element.type == ElementType.text 
246
+                      ? null 
247
+            
248
+                      : element.type == ElementType.qr 
249
+                      ? CanvasStyle.getQrSize(element.qrScale!)
250
+            
251
+                      : element.width ,
252
+                    height: element.type == ElementType.qr 
253
+                      ? CanvasStyle.getQrSize(element.qrScale!)
254
+                      : null,
255
+                    // child: Text('Top: ${element.position.top}, Left: ${element.position.left}, isSelected: ${Provider.of<Editor>(context, listen: false).isSelected(element.id)}'),
256
+                    key: widget.globalKey,
257
+                    decoration: BoxDecoration(
258
+                      // color: element.color ?? Colors.blue,
259
+                      border: Provider.of<Editor>(context, listen: true).isSelected(element.id) ? Border.all(
260
+                        color:Colors.red,
261
+                        width: 2,
262
+                        strokeAlign: BorderSide.strokeAlignOutside,
263
+                      ) : null,
264
+                    ),
265
+                    child:element.type == ElementType.qr ? const Image(image: AssetImage('asset/images/qr_template.png'),) : Provider.of<Editor>(context, listen: true).isEditing && (Provider.of<Editor>(context, listen: true).selectedElmId == element.id) ?
266
+                      IntrinsicWidth(
267
+                        child: TextField(
268
+                          controller: Provider.of<Editor>(context).selectedElm!.valueController,
269
+                          autofocus: true,
270
+                          keyboardType: TextInputType.multiline,
271
+                          enableSuggestions: false,
272
+                          autocorrect: false,
273
+                          maxLines: null,
274
+                          style: CanvasStyle.getTextStyle(element.fontScale),
275
+                          decoration: const InputDecoration(
276
+                            isDense: true,
277
+                            contentPadding: EdgeInsets.zero,
278
+                            border: InputBorder.none,
279
+                          ),
280
+                          onChanged: (_) {
281
+                            setState(() {
282
+                              
283
+                            });
284
+                          }
285
+                        ),
286
+                      ):
287
+                    Text(
288
+                      element.valueController.text,
289
+                      style: CanvasStyle.getTextStyle(element.fontScale),
290
+                    ),
291
+                  ),
292
+            
293
+                  // ? Textbox Resizer
294
+                  if (editorProvider.shouldShowTextboxResizer(element.id)) Positioned(
295
+                    right: _resizerWidth / -2,
296
+                    top:_height / 2 - (_resizerHeight / 2),
297
+                    child: DeferPointer(
298
+                      link: editorProvider.textboxResizerDeferredPointerHandlerLink,
299
+                      // paintOnTop: true,
300
+                      child: Transform.scale(
301
+                        scale: 1 / _currentScale,
302
+                        child: Listener(
303
+                          onPointerDown: (details) {
304
+                            _dragStartPosition = details.position;
305
+                          },
306
+                          onPointerMove: (details) {
307
+                            if (element.isLocked) return;
308
+                        
309
+                            setState(() {
310
+                              final selectedElm = Provider.of<Editor>(context, listen: false).selectedElm;
311
+                        
312
+                              if (selectedElm == null) return;
313
+                        
314
+                        
315
+                              final elmKeyContext = selectedElm.elementKey.currentContext;
316
+                        
317
+                              double width = elmKeyContext!.size!.width;
318
+                        
319
+                              print(MediaQuery.of(context).devicePixelRatio);
320
+                                    
321
+                                    
322
+                              var delta;
323
+                              double canvasWidth = 1.0;
324
+                                    
325
+                              print (_dragStartPosition!.dx);
326
+                              print (_dragStartPosition!.dy);
327
+                                    
328
+                              // adjust width based on rotation
329
+                              print('quarter turn: ${element.quarterTurns}');
330
+                              switch(element.quarterTurns) {
331
+                                case 0:
332
+                                  delta = details.position.dx - _dragStartPosition!.dx;
333
+                                  canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.width;
334
+                                case 3:
335
+                                  delta = _dragStartPosition!.dy - details.position.dy;
336
+                                  element.position.top -= delta / _currentScale;
337
+                                  canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.height;
338
+                              }
339
+                              
340
+                              element.width += delta / _currentScale; // Adjust width
341
+                              print('current scale $_currentScale');
342
+                        
343
+                              // Enforce minimum size
344
+                              element.width = element.width.clamp(75.0, canvasWidth);
345
+                               
346
+                              _dragStartPosition = details.position;
347
+                              if (width <= 0) return;
348
+                        
349
+                              Provider.of<Editor>(context, listen: false).updateElmWitdh(element.width);
350
+                            });
351
+                          },
352
+                          onPointerUp: (details) {
353
+                            _dragStartPosition = null;
354
+                          },
355
+                          child: Container(
356
+                            decoration: BoxDecoration(
357
+                              shape: BoxShape.circle,
358
+                              color: Colors.blue.withOpacity(0.7),
359
+                            ),
360
+                            width: _resizerWidth,
361
+                            height: _resizerHeight,
362
+                            child: const RotatedBox(
363
+                              quarterTurns: 1,
364
+                              child: Icon(
365
+                                Icons.height,
366
+                                size: 24,
367
+                                color: Colors.white,
368
+                              ),
369
+                            ),
370
+                          ),
371
+                        ),
372
+                      ),
373
+                    ),
374
+                  ),
375
+            
376
+            
377
+                  
378
+            
379
+            
380
+            
381
+                  // if (Provider.of<Editor>(context).selectedElmId == element.id && element.type != ElementType.qr) ... [
382
+                    
383
+                    
384
+                  // ]
385
+                ],
386
+              ),
387
+            ),
388
+
389
+            //? Overlay Button
390
+            if (editorProvider.shouldShowOverlay(element.id)) Positioned(
391
+              top: -60 / _currentScale,
392
+              left: 0,
393
+              child: DeferPointer(
394
+                paintOnTop: true,
395
+                child: Transform.scale(
396
+                  scale: 1 / _currentScale,
397
+                  alignment: Alignment.topLeft,
398
+                  child: Row(
399
+                    children: [
400
+                      IconButton.filled(
401
+                        onPressed: () {
402
+                          print('delete overlay tapped');
403
+                          editorProvider.deleteElement(context);
404
+                        }, 
405
+                        icon: const Icon(Icons.delete),
406
+                        color: Theme.of(context).colorScheme.error,
407
+                        style: ButtonStyle(
408
+                          backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
409
+                        ),
410
+                      ),
411
+                      IconButton.filled(
412
+                        onPressed: () {
413
+                          print('rotate overlay tapped');
414
+                          editorProvider.rotate();
415
+                  
416
+                          // test
417
+                          var getBox = element.elementKey.currentContext!.findRenderObject();
418
+                        }, 
419
+                        icon: const Icon(Icons.rotate_90_degrees_cw),
420
+                        // color: Theme.of(context).colorScheme.error,
421
+                        style: const ButtonStyle(
422
+                          // backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
423
+                        ),
424
+                      ),
425
+                      IconButton.filled(
426
+                        onPressed: () {
427
+                          print('lock overlay tapped');
428
+                          editorProvider.toggleLockElement();
429
+                        }, 
430
+                        icon: Icon(element.isLocked ? Icons.lock_outline : Icons.lock_open),
431
+                        // color: element.isLocked ? Theme.of(context).colorScheme.error,
432
+                        style: ButtonStyle(
433
+                          backgroundColor: element.isLocked ? WidgetStatePropertyAll(Theme.of(context).colorScheme.error) : null
434
+                        ),
435
+                      ),
436
+                    ],
437
+                  ),
438
+                ),
439
+              )
440
+            )
441
+          ],
442
+        ),
443
+      ),
444
+    );
445
+  }
446
+}

+ 200 - 0
lib/widgets/toolbar.dart

@@ -0,0 +1,200 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:flutter_canvas_editor/providers/editor.dart';
3
+import 'package:flutter_canvas_editor/style/canvas_style.dart';
4
+import 'package:flutter_canvas_editor/widgets/elements.dart';
5
+import 'package:provider/provider.dart';
6
+
7
+class ToolbarWidget extends StatefulWidget {
8
+  const ToolbarWidget({
9
+    super.key,
10
+  });
11
+
12
+  @override
13
+  State<ToolbarWidget> createState() => _ToolbarWidgetState();
14
+}
15
+
16
+class _ToolbarWidgetState extends State<ToolbarWidget> {
17
+  List<DropdownMenuItem<int>> fontSizeDropdownItems= [];
18
+
19
+  List<DropdownMenuItem<int>> qrSizeDropdownItems= [];
20
+
21
+
22
+  @override
23
+  void initState() {
24
+    // TODO: implement initState
25
+    super.initState();
26
+
27
+    populateFontSizeDropdownItems();
28
+    populateQrSizeDropdownItems();
29
+  }
30
+
31
+  //functions
32
+  void populateFontSizeDropdownItems() {
33
+    CanvasStyle.fontSizeMap.forEach((key, val) {
34
+      final item = DropdownMenuItem(
35
+        value: key,
36
+        child: Text(key.toString())
37
+      );
38
+
39
+
40
+      fontSizeDropdownItems.add(item);
41
+    });
42
+  }
43
+
44
+  void populateQrSizeDropdownItems() {
45
+    CanvasStyle.qrSizeMap.forEach((key, val) {
46
+      final item = DropdownMenuItem(
47
+        value: key,
48
+        child: Text(key.toString())
49
+      );
50
+
51
+
52
+      qrSizeDropdownItems.add(item);
53
+    });
54
+  }
55
+
56
+  @override
57
+  Widget build(BuildContext context) {
58
+    final editorProvider = Provider.of<Editor>(context);
59
+
60
+    return Container(
61
+      padding: EdgeInsets.symmetric(horizontal: 16),
62
+      color: Colors.white,
63
+      width: MediaQuery.of(context).size.width,
64
+      height: 150,
65
+      child: ListView(
66
+        children: Provider.of<Editor>(context).insertElementMode ? insertElementSection() : elementPropertiesSection(editorProvider),
67
+      ),
68
+    );
69
+  }
70
+
71
+  List<Widget> insertElementSection() {
72
+    return [
73
+      Text('Insert Element'),
74
+      SizedBox(height: 20),
75
+      ElevatedButton(
76
+        onPressed: Provider.of<Editor>(context, listen: false).addTextElement, 
77
+        child: Text('Add Text Element')
78
+      ),
79
+      ElevatedButton(
80
+        onPressed: Provider.of<Editor>(context, listen: false).addTextboxElement, 
81
+        child: Text('Add Textbox Element')
82
+      ),
83
+      // ElevatedButton(
84
+      //   onPressed: Provider.of<Editor>(context, listen: false).addQrCodeElement, 
85
+      //   child: Text('Add Qr Code Element')
86
+      // ),
87
+    ];
88
+  }
89
+
90
+  List<Widget> elementPropertiesSection(Editor editorProvider) {
91
+    final element = Provider.of<Editor>(context).selectedElm;
92
+
93
+    return [
94
+      Text('Properties'),
95
+      SizedBox(height: 20),
96
+      Text('Selected elements: ${Provider.of<Editor>(context).selectedElmType}'),
97
+
98
+      Text('Top: ${Provider.of<Editor>(context, listen: true).selectedElm!.position.top}, Left: ${Provider.of<Editor>(context, listen: true).selectedElm!.position.left}}'),
99
+      
100
+      // ? Value Editor
101
+      if([ElementType.text, ElementType.textbox].contains(editorProvider.selectedElm!.type)) TextField(
102
+        readOnly: element!.isLocked, 
103
+        controller: element!.valueController,
104
+        onTap: editorProvider.enableEdit,
105
+        onEditingComplete: () {
106
+          FocusManager.instance.primaryFocus?.unfocus();
107
+
108
+          editorProvider.disableEdit();
109
+
110
+          print('kepenjet');
111
+        },
112
+        onChanged: (value) {
113
+          setState(() {
114
+            
115
+          });
116
+        },
117
+      ),
118
+      
119
+      if (editorProvider.selectedElm!.type != ElementType.qr) ElevatedButton(
120
+        onPressed: Provider.of<Editor>(context).rotate, 
121
+        child: Text('Rotate')
122
+      ),
123
+
124
+      // ? Font Resizer (Only show when selected element is [ElementType.text, ElementType.textbox])
125
+      if (Provider.of<Editor>(context).shouldShowFontResizer) Row(
126
+        children: [
127
+          DropdownButton<int>(
128
+            value: element?.fontScale,
129
+            items: fontSizeDropdownItems, 
130
+            onChanged: (val) {
131
+              print('dropdown value: $val');
132
+              Provider.of<Editor>(context, listen: false).changeFontSize(val);
133
+            }
134
+          ),
135
+          IconButton.filled(
136
+            onPressed: Provider.of<Editor>(context, listen: false).incrementFontSize, 
137
+            icon: Icon(Icons.add)
138
+          ),
139
+          IconButton.filled(
140
+            onPressed:  Provider.of<Editor>(context, listen: false).decrementFontSize,
141
+            icon: Icon(Icons.remove)
142
+          ),
143
+          IconButton.filled(
144
+            onPressed:  () => print('kepenjet tombol test'),
145
+            icon: Icon(Icons.remove)
146
+          ),
147
+        ],
148
+      ),
149
+
150
+      // ? Qr Resizer (Only show when selected element is ElementType.qr)
151
+      if (Provider.of<Editor>(context).shouldShowQrResizer) Row(
152
+        children: [
153
+          DropdownButton<int>(
154
+            value: element?.qrScale,
155
+            items: qrSizeDropdownItems, 
156
+            onChanged: (val) {
157
+              print('dropdown value: $val');
158
+              Provider.of<Editor>(context, listen: false).changeQrSize(val);
159
+            }
160
+          ),
161
+          IconButton.filled(
162
+            onPressed: Provider.of<Editor>(context, listen: false).incrementQrSize, 
163
+            icon: Icon(Icons.add)
164
+          ),
165
+          IconButton.filled(
166
+            onPressed:  Provider.of<Editor>(context, listen: false).decrementQrSize,
167
+            icon: Icon(Icons.remove)
168
+          ),
169
+        ],
170
+      ),
171
+
172
+      // ? Lock Element
173
+      ElevatedButton(
174
+        style: ButtonStyle(
175
+          // backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
176
+        ),
177
+        onPressed: () async {
178
+          Provider.of<Editor>(context, listen: false).toggleLockElement();
179
+          FocusScope.of(context).unfocus();
180
+        },
181
+        child: Text(
182
+          Provider.of<Editor>(context).selectedElm!.isLocked ? 'Unlock Element' : 'Lock Element',
183
+          // style: TextStyle(color: Theme.of(context).colorScheme.error),
184
+        )
185
+      ),
186
+
187
+      // ? Delete elm button (only show when type is not ElementType.qr)
188
+      if (Provider.of<Editor>(context).shouldShowDeleteElementButton) ElevatedButton(
189
+        style: ButtonStyle(
190
+          backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
191
+        ),
192
+        onPressed: () async => Provider.of<Editor>(context, listen: false).deleteElement(context), 
193
+        child: Text(
194
+          'Delete Element',
195
+          style: TextStyle(color: Theme.of(context).colorScheme.error),
196
+        )
197
+      ),
198
+    ];
199
+  }
200
+}

+ 120 - 15
pubspec.lock

@@ -41,6 +41,14 @@ packages:
41 41
       url: "https://pub.dev"
42 42
     source: hosted
43 43
     version: "1.18.0"
44
+  crypto:
45
+    dependency: transitive
46
+    description:
47
+      name: crypto
48
+      sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
49
+      url: "https://pub.dev"
50
+    source: hosted
51
+    version: "3.0.6"
44 52
   cupertino_icons:
45 53
     dependency: "direct main"
46 54
     description:
@@ -49,6 +57,22 @@ packages:
49 57
       url: "https://pub.dev"
50 58
     source: hosted
51 59
     version: "1.0.8"
60
+  defer_pointer:
61
+    dependency: "direct main"
62
+    description:
63
+      name: defer_pointer
64
+      sha256: d69e6f8c1d0f052d2616cc1db3782e0ea73f42e4c6f6122fd1a548dfe79faf02
65
+      url: "https://pub.dev"
66
+    source: hosted
67
+    version: "0.0.2"
68
+  equatable:
69
+    dependency: transitive
70
+    description:
71
+      name: equatable
72
+      sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
73
+      url: "https://pub.dev"
74
+    source: hosted
75
+    version: "2.0.7"
52 76
   fake_async:
53 77
     dependency: transitive
54 78
     description:
@@ -57,6 +81,14 @@ packages:
57 81
       url: "https://pub.dev"
58 82
     source: hosted
59 83
     version: "1.3.1"
84
+  fixnum:
85
+    dependency: transitive
86
+    description:
87
+      name: fixnum
88
+      sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
89
+      url: "https://pub.dev"
90
+    source: hosted
91
+    version: "1.1.1"
60 92
   flutter:
61 93
     dependency: "direct main"
62 94
     description: flutter
@@ -75,30 +107,38 @@ packages:
75 107
     description: flutter
76 108
     source: sdk
77 109
     version: "0.0.0"
110
+  iconsax_flutter:
111
+    dependency: transitive
112
+    description:
113
+      name: iconsax_flutter
114
+      sha256: "95b65699da8ea98f87c5d232f06b0debaaf1ec1332b697e4d90969ec9a93037d"
115
+      url: "https://pub.dev"
116
+    source: hosted
117
+    version: "1.0.0"
78 118
   leak_tracker:
79 119
     dependency: transitive
80 120
     description:
81 121
       name: leak_tracker
82
-      sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
122
+      sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
83 123
       url: "https://pub.dev"
84 124
     source: hosted
85
-    version: "10.0.0"
125
+    version: "10.0.5"
86 126
   leak_tracker_flutter_testing:
87 127
     dependency: transitive
88 128
     description:
89 129
       name: leak_tracker_flutter_testing
90
-      sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
130
+      sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
91 131
       url: "https://pub.dev"
92 132
     source: hosted
93
-    version: "2.0.1"
133
+    version: "3.0.5"
94 134
   leak_tracker_testing:
95 135
     dependency: transitive
96 136
     description:
97 137
       name: leak_tracker_testing
98
-      sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
138
+      sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
99 139
       url: "https://pub.dev"
100 140
     source: hosted
101
-    version: "2.0.1"
141
+    version: "3.0.1"
102 142
   lints:
103 143
     dependency: transitive
104 144
     description:
@@ -119,18 +159,26 @@ packages:
119 159
     dependency: transitive
120 160
     description:
121 161
       name: material_color_utilities
122
-      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
162
+      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
123 163
       url: "https://pub.dev"
124 164
     source: hosted
125
-    version: "0.8.0"
165
+    version: "0.11.1"
126 166
   meta:
127 167
     dependency: transitive
128 168
     description:
129 169
       name: meta
130
-      sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
170
+      sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
131 171
       url: "https://pub.dev"
132 172
     source: hosted
133
-    version: "1.11.0"
173
+    version: "1.15.0"
174
+  nested:
175
+    dependency: transitive
176
+    description:
177
+      name: nested
178
+      sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
179
+      url: "https://pub.dev"
180
+    source: hosted
181
+    version: "1.0.0"
134 182
   path:
135 183
     dependency: transitive
136 184
     description:
@@ -139,6 +187,30 @@ packages:
139 187
       url: "https://pub.dev"
140 188
     source: hosted
141 189
     version: "1.9.0"
190
+  pausable_timer:
191
+    dependency: transitive
192
+    description:
193
+      name: pausable_timer
194
+      sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074"
195
+      url: "https://pub.dev"
196
+    source: hosted
197
+    version: "3.1.0+3"
198
+  provider:
199
+    dependency: "direct main"
200
+    description:
201
+      name: provider
202
+      sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
203
+      url: "https://pub.dev"
204
+    source: hosted
205
+    version: "6.1.2"
206
+  resizable_widget:
207
+    dependency: "direct main"
208
+    description:
209
+      name: resizable_widget
210
+      sha256: db2919754b93f386b9b3fb15e9f48f6c9d6d41f00a24397629133c99df86606a
211
+      url: "https://pub.dev"
212
+    source: hosted
213
+    version: "1.0.5"
142 214
   sky_engine:
143 215
     dependency: transitive
144 216
     description: flutter
@@ -152,6 +224,14 @@ packages:
152 224
       url: "https://pub.dev"
153 225
     source: hosted
154 226
     version: "1.10.0"
227
+  sprintf:
228
+    dependency: transitive
229
+    description:
230
+      name: sprintf
231
+      sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
232
+      url: "https://pub.dev"
233
+    source: hosted
234
+    version: "7.0.0"
155 235
   stack_trace:
156 236
     dependency: transitive
157 237
     description:
@@ -188,10 +268,34 @@ packages:
188 268
     dependency: transitive
189 269
     description:
190 270
       name: test_api
191
-      sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
271
+      sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
272
+      url: "https://pub.dev"
273
+    source: hosted
274
+    version: "0.7.2"
275
+  toastification:
276
+    dependency: "direct main"
277
+    description:
278
+      name: toastification
279
+      sha256: "4d97fbfa463dfe83691044cba9f37cb185a79bb9205cfecb655fa1f6be126a13"
280
+      url: "https://pub.dev"
281
+    source: hosted
282
+    version: "2.3.0"
283
+  typed_data:
284
+    dependency: transitive
285
+    description:
286
+      name: typed_data
287
+      sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
288
+      url: "https://pub.dev"
289
+    source: hosted
290
+    version: "1.4.0"
291
+  uuid:
292
+    dependency: "direct main"
293
+    description:
294
+      name: uuid
295
+      sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
192 296
       url: "https://pub.dev"
193 297
     source: hosted
194
-    version: "0.6.1"
298
+    version: "4.5.1"
195 299
   vector_math:
196 300
     dependency: transitive
197 301
     description:
@@ -204,9 +308,10 @@ packages:
204 308
     dependency: transitive
205 309
     description:
206 310
       name: vm_service
207
-      sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
311
+      sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
208 312
       url: "https://pub.dev"
209 313
     source: hosted
210
-    version: "13.0.0"
314
+    version: "14.2.5"
211 315
 sdks:
212
-  dart: ">=3.3.1 <4.0.0"
316
+  dart: ">=3.5.0 <4.0.0"
317
+  flutter: ">=3.18.0-18.0.pre.54"

+ 15 - 19
pubspec.yaml

@@ -28,28 +28,22 @@ environment:
28 28
 # the latest version available on pub.dev. To see which dependencies have newer
29 29
 # versions available, run `flutter pub outdated`.
30 30
 dependencies:
31
+  cupertino_icons: ^1.0.6
32
+  defer_pointer: ^0.0.2
31 33
   flutter:
32 34
     sdk: flutter
33
-
34
-
35
-  # The following adds the Cupertino Icons font to your application.
36
-  # Use with the CupertinoIcons class for iOS style icons.
37
-  cupertino_icons: ^1.0.6
35
+  provider: ^6.1.2
36
+  resizable_widget: ^1.0.5
37
+  uuid: ^4.5.1
38
+  toastification: ^2.3.0
38 39
 
39 40
 dev_dependencies:
41
+  flutter_lints: ^3.0.0
40 42
   flutter_test:
41 43
     sdk: flutter
42 44
 
43
-  # The "flutter_lints" package below contains a set of recommended lints to
44
-  # encourage good coding practices. The lint set provided by the package is
45
-  # activated in the `analysis_options.yaml` file located at the root of your
46
-  # package. See that file for information about deactivating specific lint
47
-  # rules and activating additional ones.
48
-  flutter_lints: ^3.0.0
49
-
50 45
 # For information on the generic Dart part of this file, see the
51 46
 # following page: https://dart.dev/tools/pub/pubspec
52
-
53 47
 # The following section is specific to Flutter packages.
54 48
 flutter:
55 49
 
@@ -57,23 +51,25 @@ flutter:
57 51
   # included with your application, so that you can use the icons in
58 52
   # the material Icons class.
59 53
   uses-material-design: true
60
-
61 54
   # To add assets to your application, add an assets section, like this:
62
-  # assets:
63
-  #   - images/a_dot_burr.jpeg
55
+  assets:
56
+    - asset/images/
64 57
   #   - images/a_dot_ham.jpeg
65
-
66 58
   # An image asset can refer to one or more resolution-specific "variants", see
67 59
   # https://flutter.dev/assets-and-images/#resolution-aware
68
-
69 60
   # For details regarding adding assets from package dependencies, see
70 61
   # https://flutter.dev/assets-and-images/#from-packages
71
-
72 62
   # To add custom fonts to your application, add a fonts section here,
73 63
   # in this "flutter" section. Each entry in this list should have a
74 64
   # "family" key with the font family name, and a "fonts" key with a
75 65
   # list giving the asset and other descriptors for the font. For
76 66
   # example:
67
+  fonts:
68
+    - family: RobotoCondensed
69
+      fonts:
70
+        - asset: asset/fonts/Roboto_Condensed-Medium.ttf
71
+          weight: 500
72
+
77 73
   # fonts:
78 74
   #   - family: Schyler
79 75
   #     fonts:

+ 37 - 30
test/widget_test.dart

@@ -1,30 +1,37 @@
1
-// This is a basic Flutter widget test.
2
-//
3
-// To perform an interaction with a widget in your test, use the WidgetTester
4
-// utility in the flutter_test package. For example, you can send tap and scroll
5
-// gestures. You can also use WidgetTester to find child widgets in the widget
6
-// tree, read text, and verify that the values of widget properties are correct.
7
-
8
-import 'package:flutter/material.dart';
9
-import 'package:flutter_test/flutter_test.dart';
10
-
11
-import 'package:flutter_canvas_editor/main.dart';
12
-
13
-void main() {
14
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15
-    // Build our app and trigger a frame.
16
-    await tester.pumpWidget(const MyApp());
17
-
18
-    // Verify that our counter starts at 0.
19
-    expect(find.text('0'), findsOneWidget);
20
-    expect(find.text('1'), findsNothing);
21
-
22
-    // Tap the '+' icon and trigger a frame.
23
-    await tester.tap(find.byIcon(Icons.add));
24
-    await tester.pump();
25
-
26
-    // Verify that our counter has incremented.
27
-    expect(find.text('0'), findsNothing);
28
-    expect(find.text('1'), findsOneWidget);
29
-  });
30
-}
1
+// // This is a basic Flutter widget test.
2
+// //
3
+// // To perform an interaction with a widget in your test, use the WidgetTester
4
+// // utility in the flutter_test package. For example, you can send tap and scroll
5
+// // gestures. You can also use WidgetTester to find child widgets in the widget
6
+// // tree, read text, and verify that the values of widget properties are correct.
7
+
8
+// import 'package:flutter/material.dart';
9
+// import 'package:flutter_test/flutter_test.dart';
10
+
11
+// import 'package:flutter_canvas_editor/main.dart';
12
+
13
+// void main() {
14
+//   testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15
+//     // Build our app and trigger a frame.
16
+//     await tester.pumpWidget(const MyApp());
17
+
18
+//     // Verify that our counter starts at 0.
19
+//     expect(find.text('0'), findsOneWidget);
20
+//     expect(find.text('1'), findsNothing);
21
+
22
+//     // Tap the '+' icon and trigger a frame.
23
+//     await tester.tap(find.byIcon(Icons.add));
24
+//     await tester.pump();
25
+
26
+//     // Verify that our counter has incremented.
27
+//     expect(find.text('0'), findsNothing);
28
+//     expect(find.text('1'), findsOneWidget);
29
+//   });
30
+// }
31
+
32
+
33
+// import 'package:flutter/material.dart';
34
+
35
+// void main() {
36
+//   Rect newRect = Rect
37
+// }