@@ -3,6 +3,7 @@ import 'dart:math';
33import 'package:cookethflow/core/utils/enums.dart' ;
44import 'package:cookethflow/features/models/canvas_models/canvas_object.dart' ;
55import 'package:cookethflow/features/models/canvas_models/objects/circle_object.dart' ;
6+ import 'package:cookethflow/features/models/canvas_models/objects/connector_object.dart' ;
67import 'package:cookethflow/features/models/canvas_models/objects/cylinder_object.dart' ;
78import 'package:cookethflow/features/models/canvas_models/objects/diamond_object.dart' ;
89import 'package:cookethflow/features/models/canvas_models/objects/inverted_triangle_object.dart' ;
@@ -24,15 +25,27 @@ class CanvasPainter extends CustomPainter {
2425 final double handleRadius;
2526 final InteractionMode interactionMode;
2627
28+ // NEW: Connector-related properties
29+ final double connectionPointRadius;
30+ final String ? connectorSourceId;
31+ final Alignment ? connectorSourceAlignment;
32+ final Offset ? connectorDragPosition;
33+
2734 CanvasPainter ({
2835 required this .userCursors,
2936 required this .canvasObjects,
3037 this .currentlySelectedObjectId,
3138 this .handleRadius = 8.0 ,
3239 required this .interactionMode,
40+ // NEW: Initialize connector properties
41+ this .connectionPointRadius = 6.0 ,
42+ this .connectorSourceId,
43+ this .connectorSourceAlignment,
44+ this .connectorDragPosition,
3345 });
3446
35- Color _parseColor (String ? colorString) {
47+ // ... (_parseColor, _getFontSize, _getTextStyle methods remain the same)
48+ Color _parseColor (String ? colorString) {
3649 if (colorString == null ) return Colors .black;
3750 try {
3851 final hex = colorString.replaceAll ('#' , '' );
@@ -90,31 +103,56 @@ class CanvasPainter extends CustomPainter {
90103 );
91104 }
92105
106+ void _drawArrowhead (Canvas canvas, Offset start, Offset end, Paint paint) {
107+ final double arrowSize = 12 ;
108+ final double arrowAngle = 25 * pi / 180 ;
109+ final angle = atan2 (end.dy - start.dy, end.dx - start.dx);
110+
111+ final path = Path ();
112+ path.moveTo (end.dx - arrowSize * cos (angle - arrowAngle), end.dy - arrowSize * sin (angle - arrowAngle));
113+ path.lineTo (end.dx, end.dy);
114+ path.lineTo (end.dx - arrowSize * cos (angle + arrowAngle), end.dy - arrowSize * sin (angle + arrowAngle));
115+ canvas.drawPath (path, paint..style= PaintingStyle .stroke);
116+ }
117+
93118 @override
94119 void paint (Canvas canvas, Size size) {
95- for (final canvasObject in canvasObjects.values) {
120+ final shapeObjects = canvasObjects.values.where ((obj) => obj is ! ConnectorObject );
121+ final connectorObjects = canvasObjects.values.whereType <ConnectorObject >();
122+
123+ // 1. Draw all connectors first (so they appear behind shapes)
124+ for (final connector in connectorObjects) {
125+ final source = canvasObjects[connector.sourceId];
126+ final target = canvasObjects[connector.targetId];
127+
128+ if (source != null && target != null ) {
129+ final startPoint = source.getConnectionPoint (connector.sourceAlignment);
130+ final endPoint = target.getConnectionPoint (connector.targetAlignment);
131+ final paint = Paint ()
132+ // ..color(Colors.black87)
133+ ..strokeWidth = 2.0
134+ ..style = PaintingStyle .stroke;
135+
136+ canvas.drawLine (startPoint, endPoint, paint);
137+ _drawArrowhead (canvas, startPoint, endPoint, paint);
138+ }
139+ }
140+
141+ // 2. Draw all shapes and their decorations
142+ for (final canvasObject in shapeObjects) {
96143 final paint = Paint ()..color = canvasObject.color;
97144 Rect rect;
98145
146+ // ... (existing shape drawing logic remains the same)
99147 if (canvasObject is Circle ) {
100148 canvas.drawCircle (canvasObject.center, canvasObject.radius, paint);
101149 rect = Rect .fromCircle (center: canvasObject.center, radius: canvasObject.radius);
102150 } else if (canvasObject is StickyNoteObject ) {
103151 rect = canvasObject.getBounds ();
104- // Define a dynamic border thickness
105152 final double borderThickness = min (min (rect.width, rect.height) * 0.05 , 5.0 );
106-
107- // The outer rect is the darker border
108153 final borderPaint = Paint ()..color = Color .lerp (canvasObject.color, Colors .black, 0.1 )! ;
109154 canvas.drawRect (rect, borderPaint);
110-
111- // The inner rect is the lighter main body
112- final bodyRect = Rect .fromLTRB (
113- rect.left + borderThickness,
114- rect.top + borderThickness,
115- rect.right - borderThickness,
116- rect.bottom - borderThickness,
117- );
155+ final bodyRect = Rect .fromLTRB (rect.left + borderThickness, rect.top + borderThickness, rect.right - borderThickness, rect.bottom - borderThickness);
118156 final bodyPaint = Paint ()..color = canvasObject.color;
119157 canvas.drawRect (bodyRect, bodyPaint);
120158 } else if (canvasObject is TextBoxObject ) {
@@ -152,41 +190,31 @@ class CanvasPainter extends CustomPainter {
152190 canvas.drawOval (Rect .fromCenter (center: bodyRect.bottomCenter, width: rect.width, height: ellipseHeight), paint);
153191 }
154192 }
155-
193+
194+ // ... (existing text drawing logic remains the same)
156195 final bool isEditingText = interactionMode == InteractionMode .editingText && currentlySelectedObjectId == canvasObject.id;
157-
158196 if (canvasObject.textDelta != null && canvasObject.textDelta! .isNotEmpty && ! isEditingText) {
159197 try {
160198 final List <dynamic > delta = jsonDecode (canvasObject.textDelta! );
161-
162- // Define a base padding and adjust it for sticky notes
163199 double textPadding = 8.0 ;
164200 if (canvasObject is StickyNoteObject ) {
165201 final double borderThickness = min (min (rect.width, rect.height) * 0.05 , 5.0 );
166202 textPadding += borderThickness;
167203 }
168-
169204 double yOffset = rect.top + textPadding;
170-
171205 final List <Map <String , dynamic >> lines = [];
172206 List <Map <String , dynamic >> currentLineOps = [];
173-
174207 for (final op in delta) {
175208 final String text = op['insert' ];
176209 final Map <String , dynamic >? attributes = op['attributes' ] as Map <String , dynamic >? ;
177-
178210 if (text.contains ('\n ' )) {
179211 final textLines = text.split ('\n ' );
180212 for (int i = 0 ; i < textLines.length; i++ ) {
181213 if (textLines[i].isNotEmpty) {
182214 currentLineOps.add ({'insert' : textLines[i], 'attributes' : attributes});
183215 }
184-
185- if (i < textLines.length - 1 ) { // Line break
186- lines.add ({
187- 'ops' : List .from (currentLineOps),
188- 'attributes' : attributes ?? {},
189- });
216+ if (i < textLines.length - 1 ) {
217+ lines.add ({'ops' : List .from (currentLineOps), 'attributes' : attributes ?? {}});
190218 currentLineOps.clear ();
191219 }
192220 }
@@ -197,45 +225,38 @@ class CanvasPainter extends CustomPainter {
197225 if (currentLineOps.isNotEmpty) {
198226 lines.add ({'ops' : currentLineOps, 'attributes' : {}});
199227 }
200-
201228 int orderedListCounter = 1 ;
202229 for (final line in lines) {
203230 final lineOps = List <Map <String , dynamic >>.from (line['ops' ] as List );
204231 final blockAttributes = line['attributes' ] as Map <String , dynamic >;
205-
206232 final lineSpans = lineOps.map ((o) => TextSpan (text: o['insert' ], style: _getTextStyle (o['attributes' ] as Map <String , dynamic >? ))).toList ();
207-
208233 String prefix = '' ;
209234 double indent = 0 ;
210235 if (blockAttributes['list' ] == 'bullet' ) {
211236 prefix = '• ' ;
212237 indent = 10.0 ;
213- orderedListCounter = 1 ; // Reset ordered list
238+ orderedListCounter = 1 ;
214239 } else if (blockAttributes['list' ] == 'ordered' ) {
215240 prefix = '$orderedListCounter . ' ;
216241 indent = 10.0 ;
217242 orderedListCounter++ ;
218243 } else {
219- orderedListCounter = 1 ; // Reset
244+ orderedListCounter = 1 ;
220245 }
221-
222246 if (blockAttributes['blockquote' ] == true ) {
223247 indent = 20.0 ;
224248 final blockPaint = Paint ()..color = Colors .grey.shade300..strokeWidth = 2 ;
225- canvas.drawLine (Offset (rect.left + textPadding, yOffset), Offset (rect.left + textPadding, yOffset + 20 ), blockPaint); // Approximate height
249+ canvas.drawLine (Offset (rect.left + textPadding, yOffset), Offset (rect.left + textPadding, yOffset + 20 ), blockPaint);
226250 }
227-
228251 if (blockAttributes['code-block' ] == true ) {
229252 final blockPaint = Paint ()..color = Colors .grey.shade200;
230- canvas.drawRect (Rect .fromLTWH (rect.left, yOffset, rect.width, 20 ), blockPaint); // Approximate height
253+ canvas.drawRect (Rect .fromLTWH (rect.left, yOffset, rect.width, 20 ), blockPaint);
231254 }
232-
233255 final textPainter = TextPainter (
234256 text: TextSpan (children: [TextSpan (text: prefix, style: _getTextStyle (blockAttributes)), ...lineSpans]),
235257 textDirection: TextDirection .ltr,
236258 textAlign: TextAlign .start,
237259 );
238-
239260 final availableWidth = rect.width - (2 * textPadding) - indent;
240261 if (availableWidth > 0 ) {
241262 textPainter.layout (maxWidth: availableWidth);
@@ -248,7 +269,7 @@ class CanvasPainter extends CustomPainter {
248269 }
249270 }
250271
251-
272+ // Draw resize handles and connection points for the selected object
252273 if (canvasObject.id == currentlySelectedObjectId && ! isEditingText) {
253274 final handlePaint = Paint ()..color = Colors .blue..style = PaintingStyle .fill;
254275 canvas.drawCircle (rect.topLeft, handleRadius, handlePaint);
@@ -259,9 +280,42 @@ class CanvasPainter extends CustomPainter {
259280 final borderPaint = Paint ()..color = Colors .blue..style = PaintingStyle .stroke..strokeWidth = 2.0 ;
260281 final path = Path ()..addRect (rect);
261282 canvas.drawPath (dashPath (path, dashArray: CircularIntervalList <double >([5.0 , 3.0 ])), borderPaint);
283+
284+ // NEW: Draw connection points for the selected object
285+ final connectionPointPaint = Paint ()..color = Colors .white..style = PaintingStyle .fill;
286+ final connectionPointBorderPaint = Paint ()..color = Colors .blue..style = PaintingStyle .stroke..strokeWidth = 1.5 ;
287+
288+ final points = [
289+ canvasObject.getConnectionPoint (Alignment .topCenter),
290+ canvasObject.getConnectionPoint (Alignment .bottomCenter),
291+ canvasObject.getConnectionPoint (Alignment .centerLeft),
292+ canvasObject.getConnectionPoint (Alignment .centerRight),
293+ ];
294+
295+ for (final point in points) {
296+ canvas.drawCircle (point, connectionPointRadius, connectionPointPaint);
297+ canvas.drawCircle (point, connectionPointRadius, connectionPointBorderPaint);
298+ }
299+ }
300+ }
301+
302+ // 3. Draw the temporary connector line while dragging
303+ if (interactionMode == InteractionMode .drawingConnector &&
304+ connectorSourceId != null &&
305+ connectorDragPosition != null ) {
306+ final sourceObject = canvasObjects[connectorSourceId! ];
307+ if (sourceObject != null ) {
308+ final startPoint = sourceObject.getConnectionPoint (connectorSourceAlignment! );
309+ final endPoint = connectorDragPosition! ;
310+ final paint = Paint ()..color = Colors .blue..strokeWidth = 2.0 ..style = PaintingStyle .stroke;
311+
312+ final path = Path ()..moveTo (startPoint.dx, startPoint.dy)..lineTo (endPoint.dx, endPoint.dy);
313+ canvas.drawPath (dashPath (path, dashArray: CircularIntervalList <double >([5.0 , 3.0 ])), paint);
314+ _drawArrowhead (canvas, startPoint, endPoint, paint);
262315 }
263316 }
264317
318+ // 4. Draw user cursors on top of everything
265319 for (final userCursor in userCursors.values) {
266320 final position = userCursor.position;
267321 final paint = Paint ()..color = userCursor.color..strokeWidth = 2 ;
@@ -277,7 +331,8 @@ class CanvasPainter extends CustomPainter {
277331 oldPainter.canvasObjects.length != canvasObjects.length ||
278332 oldPainter.currentlySelectedObjectId != currentlySelectedObjectId ||
279333 oldPainter.interactionMode != interactionMode ||
280- _hasCanvasObjectsChanged (oldPainter.canvasObjects, canvasObjects);
334+ _hasCanvasObjectsChanged (oldPainter.canvasObjects, canvasObjects) ||
335+ oldPainter.connectorDragPosition != connectorDragPosition; // Add check for connector drag
281336 }
282337
283338 bool _hasCanvasObjectsChanged (Map <String , CanvasObject > oldObjects, Map <String , CanvasObject > newObjects) {
0 commit comments