Browse Source

feat: editor element alignment guide, magnet

Raihan Rizal 1 month ago
parent
commit
a30aac33b6
4 changed files with 146 additions and 22 deletions
  1. 1 1
      ios/Runner/AppDelegate.swift
  2. 68 15
      lib/main.dart
  3. 30 3
      lib/providers/editor.dart
  4. 47 3
      lib/widgets/elements.dart

+ 1 - 1
ios/Runner/AppDelegate.swift

@@ -1,7 +1,7 @@
1 1
 import UIKit
2 2
 import Flutter
3 3
 
4
-@UIApplicationMain
4
+@main
5 5
 @objc class AppDelegate: FlutterAppDelegate {
6 6
   override func application(
7 7
     _ application: UIApplication,

+ 68 - 15
lib/main.dart

@@ -158,21 +158,31 @@ class _HomePageState extends State<HomePage> {
158 158
                       constrained: false,
159 159
                       child: DeferredPointerHandler(
160 160
                         child: Center(
161
-                          child: Container(
162
-                            margin: EdgeInsets.all(16),
163
-                            color: Colors.white,
164
-                            height: Provider.of<Editor>(context).canvasProperty.height,
165
-                            width: Provider.of<Editor>(context).canvasProperty.width,
166
-                            child: Stack(
167
-                              children: [
168
-                                for (ElementState elementProperty in Provider.of<Editor>(context).currentElementsState) ... [
169
-                                  ElementWidget(
170
-                                    elementProperty: elementProperty,
171
-                                    canvasProperty: Provider.of<Editor>(context).canvasProperty,
172
-                                    globalKey: elementProperty.elementKey,
173
-                                  )
174
-                                ]
175
-                              ],
161
+                          child: Padding(
162
+                            padding: const EdgeInsets.all(16),
163
+                            child: CustomPaint(
164
+                              size: Size(Provider.of<Editor>(context).canvasProperty.width, Provider.of<Editor>(context).canvasProperty.height),
165
+                              foregroundPainter: CanvasAlignmentHelper(
166
+                                showHorizontalCenterLine: Provider.of<Editor>(context).shouldShowHorizontalCenterLine, 
167
+                                showVerticalCenterLine: Provider.of<Editor>(context).shouldShowVerticalCenterLine
168
+                              ),
169
+                              child: Container(
170
+                                // margin: EdgeInsets.all(16),
171
+                                color: Colors.white,
172
+                                height: Provider.of<Editor>(context).canvasProperty.height,
173
+                                width: Provider.of<Editor>(context).canvasProperty.width,
174
+                                child: Stack(
175
+                                  children: [
176
+                                    for (ElementState elementProperty in Provider.of<Editor>(context).currentElementsState) ... [
177
+                                      ElementWidget(
178
+                                        elementProperty: elementProperty,
179
+                                        canvasProperty: Provider.of<Editor>(context).canvasProperty,
180
+                                        globalKey: elementProperty.elementKey,
181
+                                      )
182
+                                    ]
183
+                                  ],
184
+                                ),
185
+                              ),
176 186
                             ),
177 187
                           ),
178 188
                         ),
@@ -217,4 +227,47 @@ class CanvasProperty {
217 227
     required this.width,
218 228
     required this.height,
219 229
   });
230
+
231
+  Offset getCanvasCenterPoint() {
232
+    return Offset(width / 2, height / 2);
233
+  }
234
+}
235
+
236
+
237
+class CanvasAlignmentHelper extends CustomPainter {
238
+  CanvasAlignmentHelper({required  this.showHorizontalCenterLine, required this.showVerticalCenterLine});
239
+
240
+  bool showHorizontalCenterLine;
241
+  bool showVerticalCenterLine;
242
+
243
+  @override
244
+  void paint(Canvas canvas, Size size) {
245
+    // ? horizontal center line
246
+    if (showHorizontalCenterLine) {
247
+      canvas.drawLine(
248
+        Offset(size.width / 2, 0),
249
+        Offset(size.width / 2, size.height),
250
+        Paint()
251
+          ..color = Colors.orange
252
+          ..strokeWidth = 1.5
253
+      );
254
+    }
255
+
256
+    // ? vertial center line
257
+    if (showVerticalCenterLine) {
258
+      canvas.drawLine(
259
+        Offset(0, size.height / 2),
260
+        Offset(size.width, size.height / 2),
261
+        Paint()
262
+          ..color = Colors.orange
263
+          ..strokeWidth = 1.5
264
+      );
265
+    }
266
+  }
267
+
268
+  @override
269
+  bool shouldRepaint(covariant CustomPainter oldDelegate) {
270
+    return true;
271
+  }
272
+
220 273
 }

+ 30 - 3
lib/providers/editor.dart

@@ -373,15 +373,26 @@ class Editor extends ChangeNotifier {
373 373
 
374 374
 
375 375
 
376
-  void updateElmPosition(Offset offset) {
376
+  void updateElmPosition(Offset offset, {double? fixedTop, double? fixedLeft}) {
377 377
     ElementState? element = selectedElm;
378 378
 
379 379
     if (element == null) return;
380 380
 
381 381
     // element.position = ElementPosition(top: offset.dy, left: offset.dx);
382 382
     print(offset.dx);
383
-    element.position.top += offset.dy.round();
384
-    element.position.left += offset.dx.round();
383
+    if (fixedTop != null) {
384
+      element.position.top = fixedTop;
385
+    } else {
386
+      element.position.top += offset.dy.round();
387
+    }
388
+
389
+    if (fixedLeft != null) {
390
+      print('fixed left: $fixedLeft');
391
+      element.position.left = fixedLeft;
392
+    } else {
393
+      element.position.left += offset.dx.round();
394
+    }
395
+    
385 396
     notifyListeners();
386 397
   }
387 398
 
@@ -988,6 +999,22 @@ class Editor extends ChangeNotifier {
988 999
       newElement.valueController.selection = TextSelection.collapsed(offset: selectedElm!.valueController.selection.base.offset);
989 1000
     }
990 1001
   }
1002
+
1003
+
1004
+  // canvas alignment line helper
1005
+  bool shouldShowHorizontalCenterLine = false;
1006
+
1007
+  void updateShouldShowHorizontalCenterLine(bool value) {
1008
+    shouldShowHorizontalCenterLine = value;
1009
+    notifyListeners();
1010
+  }
1011
+
1012
+  bool shouldShowVerticalCenterLine = false;
1013
+
1014
+  void updateShouldShowVerticalCenterLine(bool value) {
1015
+    shouldShowVerticalCenterLine = value;
1016
+    notifyListeners();
1017
+  }
991 1018
 }
992 1019
 
993 1020
 

+ 47 - 3
lib/widgets/elements.dart

@@ -255,12 +255,15 @@ class _ElementWidgetState extends State<ElementWidget> {
255 255
             _elementDragStartPosition = Provider.of<Editor>(context, listen: false).getClonedElementPosition(element);
256 256
           },
257 257
           onPanUpdate: (details) {
258
+            // return if moved element is not selected
258 259
             if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
259 260
               return;
260 261
             }
261
-        
262
+
263
+            // return if element is locked
262 264
             if (element.isLocked) return;
263 265
             
266
+
264 267
             final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
265 268
             
266 269
             if (elmKeyContext == null) {
@@ -278,7 +281,42 @@ class _ElementWidgetState extends State<ElementWidget> {
278 281
             
279 282
             bool isElmWidthExceedCanvas = width > canvas.width; 
280 283
             bool isElmHeightExceedCanvas = height > canvas.height;
281
-        
284
+
285
+            // ! todo: lanjut fix magnet
286
+            // * alignment test
287
+            double alignmentTreshold = 5;
288
+            log('The center offset of this canvas is ${canvas.getCanvasCenterPoint().dx}, ${canvas.getCanvasCenterPoint().dy}');
289
+            log('The center offset of this element is ${width / 2}, ${height/2}');
290
+            // log('delta ${details.delta.dx}, ${details.delta.dy}');
291
+            log('delta distance ${details.delta.distanceSquared}');
292
+            
293
+            final selectedElm = Provider.of<Editor>(context, listen: false).selectedElm;
294
+            final elementCenter =  Offset(selectedElm!.position.left + (width / 2), selectedElm!.position.top + (height / 2));
295
+            bool shouldAborUpdate = false;
296
+            
297
+            final canvasCenter = canvas.getCanvasCenterPoint();
298
+            if (details.delta.distance < 2) {
299
+              if ((canvasCenter.dx - elementCenter.dx).abs() < alignmentTreshold) {
300
+                shouldAborUpdate = true;
301
+                Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta, fixedLeft: canvasCenter.dx - (width / 2));
302
+                Provider.of<Editor>(context, listen: false).updateShouldShowHorizontalCenterLine(true);
303
+                // return;
304
+              } 
305
+            } else {
306
+              Provider.of<Editor>(context, listen: false).updateShouldShowHorizontalCenterLine(false);
307
+            }
308
+
309
+            if (details.delta.distance < 2) {
310
+              if ((canvasCenter.dy - elementCenter.dy).abs() < alignmentTreshold) {
311
+                shouldAborUpdate = true;
312
+                Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta, fixedTop: canvasCenter.dy - (height / 2));
313
+                Provider.of<Editor>(context, listen: false).updateShouldShowVerticalCenterLine(true);
314
+              } 
315
+            } else {
316
+              Provider.of<Editor>(context, listen: false).updateShouldShowVerticalCenterLine(false);
317
+            }
318
+
319
+
282 320
             // Check if the object is out of the canvas
283 321
             // ? top side
284 322
             if (element.position.top < 0) {
@@ -322,7 +360,9 @@ class _ElementWidgetState extends State<ElementWidget> {
322 360
               log('Adjusting right position, if isElmHeightExceedCanvas');
323 361
               return;
324 362
             }
325
-        
363
+
364
+            if (shouldAborUpdate) return;
365
+
326 366
             Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
327 367
           },
328 368
           onPanEnd: (details) {
@@ -379,6 +419,10 @@ class _ElementWidgetState extends State<ElementWidget> {
379 419
             Provider.of<Editor>(context, listen: false).moveElement(
380 420
               Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top)
381 421
             );
422
+
423
+            // reset canvas alignment helper
424
+            Provider.of<Editor>(context, listen: false).updateShouldShowHorizontalCenterLine(false);
425
+            Provider.of<Editor>(context, listen: false).updateShouldShowVerticalCenterLine(false);
382 426
           },
383 427
           child: Stack(
384 428
             clipBehavior: Clip.none,