|
@@ -1,7 +1,13 @@
|
|
1
|
+import 'dart:developer';
|
|
2
|
+
|
1
|
3
|
import 'package:defer_pointer/defer_pointer.dart';
|
|
4
|
+import 'package:easy_debounce/easy_debounce.dart';
|
|
5
|
+import 'package:easy_debounce/easy_throttle.dart';
|
2
|
6
|
import 'package:flutter/gestures.dart';
|
3
|
7
|
import 'package:flutter/material.dart';
|
|
8
|
+import 'package:flutter_canvas_editor/history.dart';
|
4
|
9
|
import 'package:flutter_canvas_editor/providers/editor.dart';
|
|
10
|
+import 'package:flutter_canvas_editor/utils/debouncer.dart';
|
5
|
11
|
import 'package:intl/intl.dart';
|
6
|
12
|
import 'package:provider/provider.dart';
|
7
|
13
|
import 'dart:math' as math;
|
|
@@ -62,7 +68,7 @@ class ElementSize {
|
62
|
68
|
ElementSize({required this.width, required this.height});
|
63
|
69
|
}
|
64
|
70
|
|
65
|
|
-class ElementProperty {
|
|
71
|
+class ElementState {
|
66
|
72
|
String id;
|
67
|
73
|
TextEditingController valueController;
|
68
|
74
|
ElementType type;
|
|
@@ -76,8 +82,9 @@ class ElementProperty {
|
76
|
82
|
int fontScale;
|
77
|
83
|
int? qrScale;
|
78
|
84
|
bool isLocked;
|
|
85
|
+ String? lastSavedText;
|
79
|
86
|
|
80
|
|
- ElementProperty({
|
|
87
|
+ ElementState({
|
81
|
88
|
required this.id,
|
82
|
89
|
required this.valueController,
|
83
|
90
|
required this.type,
|
|
@@ -90,7 +97,8 @@ class ElementProperty {
|
90
|
97
|
this.fontScale = 3,
|
91
|
98
|
this.qrScale,
|
92
|
99
|
this.isLocked = false,
|
93
|
|
- this.variableType
|
|
100
|
+ this.variableType,
|
|
101
|
+ this.lastSavedText
|
94
|
102
|
});
|
95
|
103
|
|
96
|
104
|
}
|
|
@@ -98,7 +106,7 @@ class ElementProperty {
|
98
|
106
|
|
99
|
107
|
|
100
|
108
|
class ElementWidget extends StatefulWidget {
|
101
|
|
- final ElementProperty elementProperty;
|
|
109
|
+ final ElementState elementProperty;
|
102
|
110
|
final CanvasProperty canvasProperty;
|
103
|
111
|
final GlobalKey globalKey;
|
104
|
112
|
|
|
@@ -114,7 +122,11 @@ class ElementWidget extends StatefulWidget {
|
114
|
122
|
}
|
115
|
123
|
|
116
|
124
|
class _ElementWidgetState extends State<ElementWidget> {
|
117
|
|
- Offset? _dragStartPosition;
|
|
125
|
+ Offset? _textboxDragStartPosition;
|
|
126
|
+ double? _textboxDragStartWidth;
|
|
127
|
+
|
|
128
|
+ ElementPosition? _elementDragStartPosition;
|
|
129
|
+ // String? _valueOnStartEditing;
|
118
|
130
|
|
119
|
131
|
// dragable container
|
120
|
132
|
final double _resizerHeight = 36;
|
|
@@ -125,17 +137,27 @@ class _ElementWidgetState extends State<ElementWidget> {
|
125
|
137
|
double _currentScale = 1.0;
|
126
|
138
|
|
127
|
139
|
late TransformationController _transformController;
|
|
140
|
+
|
|
141
|
+ // // TODO: pindhkan ke state element property agar bisa dipake di widget Toolbar
|
|
142
|
+ // String? _lastSavedText;
|
|
143
|
+
|
|
144
|
+ FocusNode? _focus = FocusNode();
|
128
|
145
|
|
129
|
146
|
@override
|
130
|
147
|
void initState() {
|
131
|
148
|
super.initState();
|
132
|
149
|
|
|
150
|
+ // _valueOnStartEditing = widget.elementProperty.valueController.text;
|
|
151
|
+
|
133
|
152
|
WidgetsBinding.instance.addPostFrameCallback((_) => _updateScale);
|
134
|
153
|
|
135
|
154
|
_transformController = Provider.of<Editor>(context, listen: false).canvasTransformationController;
|
136
|
155
|
|
137
|
156
|
_setInitialScale();
|
138
|
157
|
_listenTransformationChanges();
|
|
158
|
+ _addFocusNodeListener();
|
|
159
|
+
|
|
160
|
+ widget.elementProperty.valueController.addListener(() {});
|
139
|
161
|
}
|
140
|
162
|
|
141
|
163
|
|
|
@@ -158,12 +180,35 @@ class _ElementWidgetState extends State<ElementWidget> {
|
158
|
180
|
_transformController.removeListener(_updateScale);
|
159
|
181
|
}
|
160
|
182
|
|
|
183
|
+ void _addFocusNodeListener() {
|
|
184
|
+ if ([ElementType.text, ElementType.textbox].contains(widget.elementProperty.type)) {
|
|
185
|
+ _focus!.addListener(_onTextFieldFocusChange);
|
|
186
|
+ }
|
|
187
|
+ }
|
|
188
|
+
|
|
189
|
+ void _onTextFieldFocusChange() {
|
|
190
|
+ if (widget.elementProperty.lastSavedText != widget.elementProperty.valueController.text) {
|
|
191
|
+ print('executed');
|
|
192
|
+ // Provider.of<Editor>(context, listen: false).setNewElementsState(CanvasHistoryModifyType.textEdit, Provider.of<Editor>(context, listen: false).elementProperties);
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+ widget.elementProperty.lastSavedText = widget.elementProperty.valueController.text;
|
|
196
|
+ }
|
|
197
|
+ }
|
|
198
|
+
|
|
199
|
+ void _removeFocusNodeListener() {
|
|
200
|
+ if ([ElementType.text, ElementType.textbox].contains(widget.elementProperty.type)) {
|
|
201
|
+ _focus!.removeListener(_onTextFieldFocusChange);
|
|
202
|
+ }
|
|
203
|
+ }
|
|
204
|
+
|
161
|
205
|
|
162
|
206
|
|
163
|
207
|
|
164
|
208
|
@override
|
165
|
209
|
void dispose() {
|
166
|
210
|
_removeTransformationListener();
|
|
211
|
+ _removeFocusNodeListener();
|
167
|
212
|
super.dispose();
|
168
|
213
|
}
|
169
|
214
|
|
|
@@ -172,314 +217,352 @@ class _ElementWidgetState extends State<ElementWidget> {
|
172
|
217
|
Widget build(BuildContext context) {
|
173
|
218
|
final editorProvider = Provider.of<Editor>(context);
|
174
|
219
|
|
175
|
|
- ElementProperty element = widget.elementProperty;
|
|
220
|
+ ElementState element = widget.elementProperty;
|
176
|
221
|
final CanvasProperty canvas = widget.canvasProperty;
|
177
|
222
|
|
178
|
223
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
179
|
224
|
_height = element.elementKey.currentContext!.size!.height;
|
180
|
225
|
});
|
181
|
226
|
|
|
227
|
+ // List<ElementState> currentCanvasState = editorProvider.elementProperties;
|
|
228
|
+
|
182
|
229
|
return Positioned(
|
183
|
230
|
top: element.position.top,
|
184
|
231
|
left: element.position.left,
|
185
|
|
- child: GestureDetector(
|
186
|
|
- onDoubleTap: () {
|
187
|
|
- print('double tap detected');
|
188
|
|
- Provider.of<Editor>(context, listen: false).selectElmById(element.id);
|
189
|
|
- Provider.of<Editor>(context, listen: false).enableEdit();
|
190
|
|
- },
|
191
|
|
- onTap: () {
|
192
|
|
- print('Element Gesture Detector Tapped!');
|
193
|
|
- Provider.of<Editor>(context, listen: false).selectElmById(element.id);
|
194
|
|
- },
|
195
|
|
- onPanUpdate: (details) {
|
196
|
|
- if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
|
197
|
|
- return;
|
198
|
|
- }
|
199
|
|
-
|
200
|
|
- if (element.isLocked) return;
|
201
|
|
-
|
202
|
|
- final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
|
203
|
|
-
|
204
|
|
- if (elmKeyContext == null) {
|
205
|
|
- debugPrint('WARNING, elmKeyContext not found');
|
206
|
|
- }
|
207
|
|
-
|
208
|
|
- bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
|
209
|
|
-
|
210
|
|
- double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
|
211
|
|
- double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
|
212
|
|
-
|
213
|
|
- double right = element.position.left + width;
|
214
|
|
- double bottom = element.position.top + height;
|
215
|
|
-
|
216
|
|
-
|
217
|
|
- bool isElmWidthExceedCanvas = width > canvas.width;
|
218
|
|
- bool isElmHeightExceedCanvas = height > canvas.height;
|
219
|
|
-
|
220
|
|
- // Check if the object is out of the canvas
|
221
|
|
- if (element.position.top < 0) {
|
222
|
|
- setState(() {
|
223
|
|
- element.position.top = 0;
|
224
|
|
- });
|
225
|
|
-
|
226
|
|
- print('object is out of canvas');
|
227
|
|
- return;
|
228
|
|
- }
|
229
|
|
-
|
230
|
|
- if (element.position.left < 0) {
|
231
|
|
- setState(() {
|
232
|
|
- element.position.left = 0;
|
233
|
|
- });
|
234
|
|
-
|
235
|
|
- print('object is out of canvas');
|
236
|
|
- return;
|
237
|
|
- }
|
238
|
|
-
|
239
|
|
- if (!isElmHeightExceedCanvas && bottom > canvas.height) {
|
240
|
|
- setState(() {
|
241
|
|
- element.position.top = (canvas.height - height).roundToDouble() - 1;
|
242
|
|
- });
|
243
|
|
-
|
244
|
|
- print('object is out of canvas');
|
245
|
|
- return;
|
246
|
|
- }
|
247
|
|
-
|
248
|
|
-
|
249
|
|
- if (!isElmWidthExceedCanvas && right > canvas.width) {
|
250
|
|
- setState(() {
|
251
|
|
- element.position.left = (canvas.width - width).roundToDouble() - 1;
|
252
|
|
- });
|
253
|
|
-
|
254
|
|
- print('object is out of canvas');
|
255
|
|
- return;
|
256
|
|
- }
|
257
|
|
-
|
258
|
|
- Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
|
259
|
|
- },
|
260
|
|
- onPanEnd: (details) {
|
261
|
|
- // ? Adjust overflow position
|
262
|
|
- ElementPosition adjustedElementPosition = element.position;
|
263
|
|
- final elmKeyContext = element.elementKey.currentContext;
|
264
|
|
-
|
265
|
|
- bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
|
266
|
|
-
|
267
|
|
- double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
|
268
|
|
- double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
|
269
|
|
-
|
270
|
|
- double right = element.position.left + width;
|
271
|
|
- double bottom = element.position.top + height;
|
272
|
|
-
|
273
|
|
-
|
274
|
|
- if (element.position.top < 0) {
|
275
|
|
- adjustedElementPosition.top = 0;
|
276
|
|
- }
|
277
|
|
-
|
278
|
|
- if (element.position.left < 0) {
|
279
|
|
- adjustedElementPosition.left = 0;
|
280
|
|
- }
|
281
|
|
-
|
282
|
|
- if ((element.position.top + height) > canvas.height) {
|
283
|
|
- adjustedElementPosition.top = (canvas.height - height).roundToDouble() - 1;
|
284
|
|
- }
|
285
|
|
-
|
286
|
|
- if ((element.position.left + width) > canvas.width) {
|
287
|
|
- adjustedElementPosition.left = (canvas.width - width).roundToDouble() - 1;
|
|
232
|
+ child: IgnorePointer(
|
|
233
|
+ ignoring: editorProvider.shouldIgnoreTouch(element.id),
|
|
234
|
+ child: GestureDetector(
|
|
235
|
+ onDoubleTap: () {
|
|
236
|
+ print('double tap detected');
|
|
237
|
+ Provider.of<Editor>(context, listen: false).selectElmById(element.id);
|
|
238
|
+ Provider.of<Editor>(context, listen: false).enableEdit();
|
|
239
|
+ },
|
|
240
|
+ onTap: () {
|
|
241
|
+ print('Element Gesture Detector Tapped!');
|
|
242
|
+ Provider.of<Editor>(context, listen: false).selectElmById(element.id);
|
|
243
|
+ },
|
|
244
|
+ onPanStart: (details) {
|
|
245
|
+ if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
|
|
246
|
+ return;
|
288
|
247
|
}
|
289
|
|
-
|
290
|
|
-
|
291
|
|
- Provider.of<Editor>(context, listen: false).updateElmPosition(Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top));
|
292
|
|
- },
|
293
|
|
- child: Stack(
|
294
|
|
- clipBehavior: Clip.none,
|
295
|
|
- children: [
|
296
|
|
- RotatedBox(
|
297
|
|
- // angle: element.quarterTurns * (math.pi / 2),
|
298
|
|
- quarterTurns: element.quarterTurns,
|
299
|
|
- child: Stack(
|
300
|
|
- clipBehavior: Clip.none,
|
301
|
|
- children: [
|
302
|
|
- Container(
|
303
|
|
- width: element.type == ElementType.text
|
304
|
|
- ? null
|
|
248
|
+
|
|
249
|
+ if (element.isLocked) return;
|
|
250
|
+
|
|
251
|
+ log('Pan Start');
|
|
252
|
+ // inspect(editorProvider.elementProperties);
|
|
253
|
+ // editorProvider.setNewElementsState(CanvasHistoryModifyType.move, editorProvider.elementProperties);
|
|
254
|
+
|
|
255
|
+ _elementDragStartPosition = Provider.of<Editor>(context, listen: false).getClonedElementPosition(element);
|
|
256
|
+ },
|
|
257
|
+ onPanUpdate: (details) {
|
|
258
|
+ if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
|
|
259
|
+ return;
|
|
260
|
+ }
|
|
261
|
+
|
|
262
|
+ if (element.isLocked) return;
|
305
|
263
|
|
306
|
|
- : element.type == ElementType.qr
|
307
|
|
- ? CanvasStyle.getQrSize(element.qrScale!)
|
|
264
|
+ final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
|
308
|
265
|
|
309
|
|
- : element.width ,
|
310
|
|
- height: element.type == ElementType.qr
|
311
|
|
- ? CanvasStyle.getQrSize(element.qrScale!)
|
312
|
|
- : null,
|
313
|
|
- // child: Text('Top: ${element.position.top}, Left: ${element.position.left}, isSelected: ${Provider.of<Editor>(context, listen: false).isSelected(element.id)}'),
|
314
|
|
- key: widget.globalKey,
|
315
|
|
- decoration: BoxDecoration(
|
316
|
|
- // color: element.color ?? Colors.blue,
|
317
|
|
- border: Provider.of<Editor>(context, listen: true).isSelected(element.id) ? Border.all(
|
318
|
|
- color:Colors.red,
|
319
|
|
- width: 2,
|
320
|
|
- strokeAlign: BorderSide.strokeAlignOutside,
|
321
|
|
- ) : null,
|
322
|
|
- ),
|
323
|
|
- // ? child element
|
324
|
|
- child: _buildChildElement(element),
|
325
|
|
- ),
|
|
266
|
+ if (elmKeyContext == null) {
|
|
267
|
+ debugPrint('WARNING, elmKeyContext not found');
|
|
268
|
+ }
|
326
|
269
|
|
327
|
|
- // ? Textbox Resizer
|
328
|
|
- if (editorProvider.shouldShowTextboxResizer(element.id)) Positioned(
|
329
|
|
- right: _resizerWidth / -2,
|
330
|
|
- top:_height / 2 - (_resizerHeight / 2),
|
331
|
|
- child: DeferPointer(
|
332
|
|
- link: editorProvider.textboxResizerDeferredPointerHandlerLink,
|
333
|
|
- // paintOnTop: true,
|
334
|
|
- child: Transform.scale(
|
335
|
|
- scale: 1 / _currentScale,
|
336
|
|
- child: Listener(
|
337
|
|
- onPointerDown: (details) {
|
338
|
|
- _dragStartPosition = details.position;
|
339
|
|
- },
|
340
|
|
- onPointerMove: (details) {
|
341
|
|
- if (element.isLocked) return;
|
342
|
|
-
|
343
|
|
- setState(() {
|
344
|
|
- final selectedElm = Provider.of<Editor>(context, listen: false).selectedElm;
|
345
|
|
-
|
346
|
|
- if (selectedElm == null) return;
|
347
|
|
-
|
348
|
|
-
|
349
|
|
- final elmKeyContext = selectedElm.elementKey.currentContext;
|
350
|
|
-
|
351
|
|
- double width = elmKeyContext!.size!.width;
|
352
|
|
-
|
353
|
|
- print(MediaQuery.of(context).devicePixelRatio);
|
354
|
|
-
|
355
|
|
-
|
356
|
|
- var delta;
|
357
|
|
- double canvasWidth = 1.0;
|
358
|
|
-
|
359
|
|
- print (_dragStartPosition!.dx);
|
360
|
|
- print (_dragStartPosition!.dy);
|
361
|
|
-
|
362
|
|
- // adjust width based on rotation
|
363
|
|
- print('quarter turn: ${element.quarterTurns}');
|
364
|
|
- switch(element.quarterTurns) {
|
365
|
|
- case 0:
|
366
|
|
- delta = details.position.dx - _dragStartPosition!.dx;
|
367
|
|
- canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.width;
|
368
|
|
- case 3:
|
369
|
|
- delta = _dragStartPosition!.dy - details.position.dy;
|
370
|
|
- element.position.top -= delta / _currentScale;
|
371
|
|
- canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.height;
|
372
|
|
- }
|
373
|
|
-
|
374
|
|
- element.width += delta / _currentScale; // Adjust width
|
375
|
|
- print('current scale $_currentScale');
|
376
|
|
-
|
377
|
|
- // Enforce minimum size
|
378
|
|
- element.width = element.width.clamp(75.0, canvasWidth);
|
379
|
|
-
|
380
|
|
- _dragStartPosition = details.position;
|
381
|
|
- if (width <= 0) return;
|
382
|
|
-
|
383
|
|
- Provider.of<Editor>(context, listen: false).updateElmWitdh(element.width);
|
384
|
|
- });
|
385
|
|
- },
|
386
|
|
- onPointerUp: (details) {
|
387
|
|
- _dragStartPosition = null;
|
388
|
|
- },
|
389
|
|
- child: Container(
|
390
|
|
- decoration: BoxDecoration(
|
391
|
|
- shape: BoxShape.circle,
|
392
|
|
- color: Colors.blue.withOpacity(0.7),
|
393
|
|
- ),
|
394
|
|
- width: _resizerWidth,
|
395
|
|
- height: _resizerHeight,
|
396
|
|
- child: const RotatedBox(
|
397
|
|
- quarterTurns: 1,
|
398
|
|
- child: Icon(
|
399
|
|
- Icons.height,
|
400
|
|
- size: 24,
|
401
|
|
- color: Colors.white,
|
|
270
|
+ bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
|
|
271
|
+
|
|
272
|
+ double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
|
|
273
|
+ double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
|
|
274
|
+
|
|
275
|
+ double right = element.position.left + width;
|
|
276
|
+ double bottom = element.position.top + height;
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+ bool isElmWidthExceedCanvas = width > canvas.width;
|
|
280
|
+ bool isElmHeightExceedCanvas = height > canvas.height;
|
|
281
|
+
|
|
282
|
+ // Check if the object is out of the canvas
|
|
283
|
+ if (element.position.top < 0) {
|
|
284
|
+ setState(() {
|
|
285
|
+ element.position.top = 0;
|
|
286
|
+ });
|
|
287
|
+
|
|
288
|
+ print('object is out of canvas');
|
|
289
|
+ return;
|
|
290
|
+ }
|
|
291
|
+
|
|
292
|
+ if (element.position.left < 0) {
|
|
293
|
+ setState(() {
|
|
294
|
+ element.position.left = 0;
|
|
295
|
+ });
|
|
296
|
+
|
|
297
|
+ print('object is out of canvas');
|
|
298
|
+ return;
|
|
299
|
+ }
|
|
300
|
+
|
|
301
|
+ if (!isElmHeightExceedCanvas && bottom > canvas.height) {
|
|
302
|
+ setState(() {
|
|
303
|
+ element.position.top = (canvas.height - height).roundToDouble() - 1;
|
|
304
|
+ });
|
|
305
|
+
|
|
306
|
+ print('object is out of canvas');
|
|
307
|
+ return;
|
|
308
|
+ }
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+ if (!isElmWidthExceedCanvas && right > canvas.width) {
|
|
312
|
+ setState(() {
|
|
313
|
+ element.position.left = (canvas.width - width).roundToDouble() - 1;
|
|
314
|
+ });
|
|
315
|
+
|
|
316
|
+ print('object is out of canvas');
|
|
317
|
+ return;
|
|
318
|
+ }
|
|
319
|
+
|
|
320
|
+ Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
|
|
321
|
+ },
|
|
322
|
+ onPanEnd: (details) {
|
|
323
|
+ if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
|
|
324
|
+ return;
|
|
325
|
+ }
|
|
326
|
+
|
|
327
|
+ if (element.isLocked) return;
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+ // ? Adjust overflow position
|
|
331
|
+ ElementPosition adjustedElementPosition = element.position;
|
|
332
|
+ final elmKeyContext = element.elementKey.currentContext;
|
|
333
|
+
|
|
334
|
+ bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
|
|
335
|
+
|
|
336
|
+ double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
|
|
337
|
+ double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
|
|
338
|
+
|
|
339
|
+ double right = element.position.left + width;
|
|
340
|
+ double bottom = element.position.top + height;
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+ if (element.position.top < 0) {
|
|
344
|
+ adjustedElementPosition.top = 0;
|
|
345
|
+ }
|
|
346
|
+
|
|
347
|
+ if (element.position.left < 0) {
|
|
348
|
+ adjustedElementPosition.left = 0;
|
|
349
|
+ }
|
|
350
|
+
|
|
351
|
+ if ((element.position.top + height) > canvas.height) {
|
|
352
|
+ adjustedElementPosition.top = (canvas.height - height).roundToDouble() - 1;
|
|
353
|
+ }
|
|
354
|
+
|
|
355
|
+ if ((element.position.left + width) > canvas.width) {
|
|
356
|
+ adjustedElementPosition.left = (canvas.width - width).roundToDouble() - 1;
|
|
357
|
+ }
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+ // Provider.of<Editor>(context, listen: false).updateElmPosition(
|
|
361
|
+ // Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top),
|
|
362
|
+ // // Provider.of<Editor>(context, listen: false).elementProperties
|
|
363
|
+ // );
|
|
364
|
+
|
|
365
|
+ Provider.of<Editor>(context, listen: false).resetElmPosition(_elementDragStartPosition);
|
|
366
|
+
|
|
367
|
+ Provider.of<Editor>(context, listen: false).moveElement(
|
|
368
|
+ Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top)
|
|
369
|
+ );
|
|
370
|
+ },
|
|
371
|
+ child: Stack(
|
|
372
|
+ clipBehavior: Clip.none,
|
|
373
|
+ children: [
|
|
374
|
+ RotatedBox(
|
|
375
|
+ // angle: element.quarterTurns * (math.pi / 2),
|
|
376
|
+ quarterTurns: element.quarterTurns,
|
|
377
|
+ child: Stack(
|
|
378
|
+ clipBehavior: Clip.none,
|
|
379
|
+ children: [
|
|
380
|
+ Container(
|
|
381
|
+ width: element.type == ElementType.text
|
|
382
|
+ ? null
|
|
383
|
+
|
|
384
|
+ : element.type == ElementType.qr
|
|
385
|
+ ? CanvasStyle.getQrSize(element.qrScale!)
|
|
386
|
+
|
|
387
|
+ : element.width ,
|
|
388
|
+ height: element.type == ElementType.qr
|
|
389
|
+ ? CanvasStyle.getQrSize(element.qrScale!)
|
|
390
|
+ : null,
|
|
391
|
+ // child: Text('Top: ${element.position.top}, Left: ${element.position.left}, isSelected: ${Provider.of<Editor>(context, listen: false).isSelected(element.id)}'),
|
|
392
|
+ key: widget.globalKey,
|
|
393
|
+ decoration: BoxDecoration(
|
|
394
|
+ // color: element.color ?? Colors.blue,
|
|
395
|
+ border: Provider.of<Editor>(context, listen: true).isSelected(element.id) ? Border.all(
|
|
396
|
+ color:Colors.red,
|
|
397
|
+ width: 2,
|
|
398
|
+ strokeAlign: BorderSide.strokeAlignOutside,
|
|
399
|
+ ) : null,
|
|
400
|
+ ),
|
|
401
|
+ // ? child element
|
|
402
|
+ child: _buildChildElement(element),
|
|
403
|
+ ),
|
|
404
|
+
|
|
405
|
+ // ? Textbox Resizer
|
|
406
|
+ if (editorProvider.shouldShowTextboxResizer(element.id)) Positioned(
|
|
407
|
+ right: _resizerWidth / -2,
|
|
408
|
+ top:_height / 2 - (_resizerHeight / 2),
|
|
409
|
+ child: DeferPointer(
|
|
410
|
+ link: editorProvider.textboxResizerDeferredPointerHandlerLink,
|
|
411
|
+ // paintOnTop: true,
|
|
412
|
+ child: Transform.scale(
|
|
413
|
+ scale: 1 / _currentScale,
|
|
414
|
+ child: Listener(
|
|
415
|
+ onPointerDown: (details) {
|
|
416
|
+ _textboxDragStartPosition = details.position;
|
|
417
|
+ _textboxDragStartWidth = element.width;
|
|
418
|
+ },
|
|
419
|
+ onPointerMove: (details) {
|
|
420
|
+ if (element.isLocked) return;
|
|
421
|
+
|
|
422
|
+ setState(() {
|
|
423
|
+ final selectedElm = Provider.of<Editor>(context, listen: false).selectedElm;
|
|
424
|
+
|
|
425
|
+ if (selectedElm == null) return;
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+ final elmKeyContext = selectedElm.elementKey.currentContext;
|
|
429
|
+
|
|
430
|
+ double width = elmKeyContext!.size!.width;
|
|
431
|
+
|
|
432
|
+ print(MediaQuery.of(context).devicePixelRatio);
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+ var delta;
|
|
436
|
+ double canvasWidth = 1.0;
|
|
437
|
+
|
|
438
|
+ print (_textboxDragStartPosition!.dx);
|
|
439
|
+ print (_textboxDragStartPosition!.dy);
|
|
440
|
+
|
|
441
|
+ // adjust width based on rotation
|
|
442
|
+ print('quarter turn: ${element.quarterTurns}');
|
|
443
|
+ switch(element.quarterTurns) {
|
|
444
|
+ case 0:
|
|
445
|
+ delta = details.position.dx - _textboxDragStartPosition!.dx;
|
|
446
|
+ canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.width;
|
|
447
|
+ case 3:
|
|
448
|
+ delta = _textboxDragStartPosition!.dy - details.position.dy;
|
|
449
|
+ element.position.top -= delta / _currentScale;
|
|
450
|
+ canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.height;
|
|
451
|
+ }
|
|
452
|
+
|
|
453
|
+ element.width += delta / _currentScale; // Adjust width
|
|
454
|
+ print('current scale $_currentScale');
|
|
455
|
+
|
|
456
|
+ // Enforce minimum size
|
|
457
|
+ element.width = element.width.clamp(75.0, canvasWidth);
|
|
458
|
+
|
|
459
|
+ _textboxDragStartPosition = details.position;
|
|
460
|
+ if (width <= 0) return;
|
|
461
|
+
|
|
462
|
+ Provider.of<Editor>(context, listen: false).updateElmWitdh(element.width);
|
|
463
|
+ });
|
|
464
|
+ },
|
|
465
|
+ onPointerUp: (details) {
|
|
466
|
+ editorProvider.commitTextboxResize(element.width, _textboxDragStartWidth ?? 70);
|
|
467
|
+
|
|
468
|
+ _textboxDragStartPosition = null;
|
|
469
|
+ _textboxDragStartWidth = null;
|
|
470
|
+ },
|
|
471
|
+ child: Container(
|
|
472
|
+ decoration: BoxDecoration(
|
|
473
|
+ shape: BoxShape.circle,
|
|
474
|
+ color: Colors.blue.withOpacity(0.7),
|
|
475
|
+ ),
|
|
476
|
+ width: _resizerWidth,
|
|
477
|
+ height: _resizerHeight,
|
|
478
|
+ child: const RotatedBox(
|
|
479
|
+ quarterTurns: 1,
|
|
480
|
+ child: Icon(
|
|
481
|
+ Icons.height,
|
|
482
|
+ size: 24,
|
|
483
|
+ color: Colors.white,
|
|
484
|
+ ),
|
402
|
485
|
),
|
403
|
486
|
),
|
404
|
487
|
),
|
405
|
488
|
),
|
406
|
489
|
),
|
407
|
490
|
),
|
408
|
|
- ),
|
409
|
|
-
|
410
|
|
-
|
411
|
|
-
|
412
|
|
-
|
413
|
|
-
|
414
|
|
-
|
415
|
|
- // if (Provider.of<Editor>(context).selectedElmId == element.id && element.type != ElementType.qr) ... [
|
416
|
|
-
|
|
491
|
+
|
|
492
|
+
|
417
|
493
|
|
418
|
|
- // ]
|
419
|
|
- ],
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+ // if (Provider.of<Editor>(context).selectedElmId == element.id && element.type != ElementType.qr) ... [
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+ // ]
|
|
501
|
+ ],
|
|
502
|
+ ),
|
420
|
503
|
),
|
421
|
|
- ),
|
422
|
|
-
|
423
|
|
- //? Overlay Button
|
424
|
|
- if (editorProvider.shouldShowOverlay(element.id)) Positioned(
|
425
|
|
- top: -60 / _currentScale,
|
426
|
|
- left: 0,
|
427
|
|
- child: DeferPointer(
|
428
|
|
- paintOnTop: true,
|
429
|
|
- child: Transform.scale(
|
430
|
|
- scale: 1 / _currentScale,
|
431
|
|
- alignment: Alignment.topLeft,
|
432
|
|
- child: Row(
|
433
|
|
- children: [
|
434
|
|
- IconButton.filled(
|
435
|
|
- onPressed: () {
|
436
|
|
- print('delete overlay tapped');
|
437
|
|
- editorProvider.deleteElement(context);
|
438
|
|
- },
|
439
|
|
- icon: const Icon(Icons.delete),
|
440
|
|
- color: Theme.of(context).colorScheme.error,
|
441
|
|
- style: ButtonStyle(
|
442
|
|
- backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
|
|
504
|
+
|
|
505
|
+ //? Overlay Button
|
|
506
|
+ if (editorProvider.shouldShowOverlay(element.id)) Positioned(
|
|
507
|
+ top: -60 / _currentScale,
|
|
508
|
+ left: 0,
|
|
509
|
+ child: DeferPointer(
|
|
510
|
+ paintOnTop: true,
|
|
511
|
+ child: Transform.scale(
|
|
512
|
+ scale: 1 / _currentScale,
|
|
513
|
+ alignment: Alignment.topLeft,
|
|
514
|
+ child: Row(
|
|
515
|
+ children: [
|
|
516
|
+ IconButton.filled(
|
|
517
|
+ onPressed: () {
|
|
518
|
+ print('delete overlay tapped');
|
|
519
|
+ editorProvider.deleteElement(context);
|
|
520
|
+ },
|
|
521
|
+ icon: const Icon(Icons.delete),
|
|
522
|
+ color: Theme.of(context).colorScheme.error,
|
|
523
|
+ style: ButtonStyle(
|
|
524
|
+ backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
|
|
525
|
+ ),
|
443
|
526
|
),
|
444
|
|
- ),
|
445
|
|
- IconButton.filled(
|
446
|
|
- onPressed: () {
|
447
|
|
- print('rotate overlay tapped');
|
448
|
|
- editorProvider.rotate();
|
449
|
|
-
|
450
|
|
- // test
|
451
|
|
- var getBox = element.elementKey.currentContext!.findRenderObject();
|
452
|
|
- },
|
453
|
|
- icon: const Icon(Icons.rotate_90_degrees_cw),
|
454
|
|
- // color: Theme.of(context).colorScheme.error,
|
455
|
|
- style: const ButtonStyle(
|
456
|
|
- // backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
|
|
527
|
+ IconButton.filled(
|
|
528
|
+ onPressed: () {
|
|
529
|
+ print('rotate overlay tapped');
|
|
530
|
+ editorProvider.rotate();
|
|
531
|
+
|
|
532
|
+ // test
|
|
533
|
+ var getBox = element.elementKey.currentContext!.findRenderObject();
|
|
534
|
+ },
|
|
535
|
+ icon: const Icon(Icons.rotate_90_degrees_cw),
|
|
536
|
+ // color: Theme.of(context).colorScheme.error,
|
|
537
|
+ style: const ButtonStyle(
|
|
538
|
+ // backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
|
|
539
|
+ ),
|
457
|
540
|
),
|
458
|
|
- ),
|
459
|
|
- IconButton.filled(
|
460
|
|
- onPressed: () {
|
461
|
|
- print('lock overlay tapped');
|
462
|
|
- editorProvider.toggleLockElement();
|
463
|
|
- },
|
464
|
|
- icon: Icon(element.isLocked ? Icons.lock_outline : Icons.lock_open),
|
465
|
|
- // color: element.isLocked ? Theme.of(context).colorScheme.error,
|
466
|
|
- style: ButtonStyle(
|
467
|
|
- backgroundColor: element.isLocked ? WidgetStatePropertyAll(Theme.of(context).colorScheme.error) : null
|
|
541
|
+ IconButton.filled(
|
|
542
|
+ onPressed: () {
|
|
543
|
+ print('lock overlay tapped');
|
|
544
|
+ editorProvider.toggleLockElement();
|
|
545
|
+ },
|
|
546
|
+ icon: Icon(element.isLocked ? Icons.lock_outline : Icons.lock_open),
|
|
547
|
+ // color: element.isLocked ? Theme.of(context).colorScheme.error,
|
|
548
|
+ style: ButtonStyle(
|
|
549
|
+ backgroundColor: element.isLocked ? WidgetStatePropertyAll(Theme.of(context).colorScheme.error) : null
|
|
550
|
+ ),
|
468
|
551
|
),
|
469
|
|
- ),
|
470
|
|
- ],
|
|
552
|
+ ],
|
|
553
|
+ ),
|
471
|
554
|
),
|
472
|
|
- ),
|
|
555
|
+ )
|
473
|
556
|
)
|
474
|
|
- )
|
475
|
|
- ],
|
|
557
|
+ ],
|
|
558
|
+ ),
|
476
|
559
|
),
|
477
|
560
|
),
|
478
|
561
|
);
|
479
|
562
|
}
|
480
|
563
|
|
481
|
564
|
|
482
|
|
- Widget _buildChildElement(ElementProperty element) {
|
|
565
|
+ Widget _buildChildElement(ElementState element) {
|
483
|
566
|
// ? build QR element
|
484
|
567
|
if (element.type == ElementType.qr) {
|
485
|
568
|
return const Image(image: AssetImage('asset/images/qr_template.png'));
|
|
@@ -489,7 +572,7 @@ class _ElementWidgetState extends State<ElementWidget> {
|
489
|
572
|
if (Provider.of<Editor>(context, listen: true).isEditing && (Provider.of<Editor>(context, listen: true).selectedElmId == element.id)) {
|
490
|
573
|
return IntrinsicWidth(
|
491
|
574
|
child: TextField(
|
492
|
|
- controller: Provider.of<Editor>(context).selectedElm!.valueController,
|
|
575
|
+ controller: element.valueController,
|
493
|
576
|
autofocus: true,
|
494
|
577
|
keyboardType: TextInputType.multiline,
|
495
|
578
|
enableSuggestions: false,
|
|
@@ -501,11 +584,19 @@ class _ElementWidgetState extends State<ElementWidget> {
|
501
|
584
|
contentPadding: EdgeInsets.zero,
|
502
|
585
|
border: InputBorder.none,
|
503
|
586
|
),
|
504
|
|
- onChanged: (_) {
|
505
|
|
- setState(() {
|
506
|
|
-
|
|
587
|
+ onChanged: (newText) {
|
|
588
|
+
|
|
589
|
+ Debouncer.run(() {
|
|
590
|
+ log('[SAVING TO HISTORY]');
|
|
591
|
+ Provider.of<Editor>(context, listen: false).editText(element.valueController);
|
|
592
|
+ element.valueController.text = Provider.of<Editor>(context, listen: false).valueOnStartEditing ?? '';
|
|
593
|
+
|
|
594
|
+ Provider.of<Editor>(context, listen: false).setValueOnStartEditing(newText);
|
507
|
595
|
});
|
508
|
|
- }
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+ setState(() {});
|
|
599
|
+ },
|
509
|
600
|
),
|
510
|
601
|
);
|
511
|
602
|
}
|