瀏覽代碼

feat: editor element alignment guide, magnet

Raihan Rizal 1 月之前
父節點
當前提交
a30aac33b6
共有 4 個文件被更改,包括 146 次插入22 次删除
  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
 import UIKit
1
 import UIKit
2
 import Flutter
2
 import Flutter
3
 
3
 
4
-@UIApplicationMain
4
+@main
5
 @objc class AppDelegate: FlutterAppDelegate {
5
 @objc class AppDelegate: FlutterAppDelegate {
6
   override func application(
6
   override func application(
7
     _ application: UIApplication,
7
     _ application: UIApplication,

+ 68 - 15
lib/main.dart

158
                       constrained: false,
158
                       constrained: false,
159
                       child: DeferredPointerHandler(
159
                       child: DeferredPointerHandler(
160
                         child: Center(
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
     required this.width,
227
     required this.width,
218
     required this.height,
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
 
373
 
374
 
374
 
375
 
375
 
376
-  void updateElmPosition(Offset offset) {
376
+  void updateElmPosition(Offset offset, {double? fixedTop, double? fixedLeft}) {
377
     ElementState? element = selectedElm;
377
     ElementState? element = selectedElm;
378
 
378
 
379
     if (element == null) return;
379
     if (element == null) return;
380
 
380
 
381
     // element.position = ElementPosition(top: offset.dy, left: offset.dx);
381
     // element.position = ElementPosition(top: offset.dy, left: offset.dx);
382
     print(offset.dx);
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
     notifyListeners();
396
     notifyListeners();
386
   }
397
   }
387
 
398
 
988
       newElement.valueController.selection = TextSelection.collapsed(offset: selectedElm!.valueController.selection.base.offset);
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
             _elementDragStartPosition = Provider.of<Editor>(context, listen: false).getClonedElementPosition(element);
255
             _elementDragStartPosition = Provider.of<Editor>(context, listen: false).getClonedElementPosition(element);
256
           },
256
           },
257
           onPanUpdate: (details) {
257
           onPanUpdate: (details) {
258
+            // return if moved element is not selected
258
             if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
259
             if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
259
               return;
260
               return;
260
             }
261
             }
261
-        
262
+
263
+            // return if element is locked
262
             if (element.isLocked) return;
264
             if (element.isLocked) return;
263
             
265
             
266
+
264
             final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
267
             final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
265
             
268
             
266
             if (elmKeyContext == null) {
269
             if (elmKeyContext == null) {
278
             
281
             
279
             bool isElmWidthExceedCanvas = width > canvas.width; 
282
             bool isElmWidthExceedCanvas = width > canvas.width; 
280
             bool isElmHeightExceedCanvas = height > canvas.height;
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
             // Check if the object is out of the canvas
320
             // Check if the object is out of the canvas
283
             // ? top side
321
             // ? top side
284
             if (element.position.top < 0) {
322
             if (element.position.top < 0) {
322
               log('Adjusting right position, if isElmHeightExceedCanvas');
360
               log('Adjusting right position, if isElmHeightExceedCanvas');
323
               return;
361
               return;
324
             }
362
             }
325
-        
363
+
364
+            if (shouldAborUpdate) return;
365
+
326
             Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
366
             Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
327
           },
367
           },
328
           onPanEnd: (details) {
368
           onPanEnd: (details) {
379
             Provider.of<Editor>(context, listen: false).moveElement(
419
             Provider.of<Editor>(context, listen: false).moveElement(
380
               Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top)
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
           child: Stack(
427
           child: Stack(
384
             clipBehavior: Clip.none,
428
             clipBehavior: Clip.none,