11// lib/features/models/canvas_models/canvas_painter.dart
22
3+ import 'dart:convert' ;
34import 'dart:math' ;
45import 'dart:ui' ;
5- import 'dart:convert' ; // Import for jsonDecode
66
7+ import 'package:cookethflow/core/utils/enums.dart' ; // Make sure this import is correct
78import 'package:cookethflow/features/models/canvas_models/canvas_object.dart' ;
89import 'package:cookethflow/features/models/canvas_models/objects/circle_object.dart' ;
910import 'package:cookethflow/features/models/canvas_models/objects/cylinder_object.dart' ;
@@ -13,58 +14,56 @@ import 'package:cookethflow/features/models/canvas_models/objects/parallelogram_
1314import 'package:cookethflow/features/models/canvas_models/objects/rectangle_object.dart' ;
1415import 'package:cookethflow/features/models/canvas_models/objects/rounded_square_object.dart' ;
1516import 'package:cookethflow/features/models/canvas_models/objects/square_object.dart' ;
16- import 'package:cookethflow/features/models/canvas_models/objects/text_box_object.dart' ; // NEW: Import TextBoxObject
17+ import 'package:cookethflow/features/models/canvas_models/objects/text_box_object.dart' ;
1718import 'package:cookethflow/features/models/canvas_models/objects/triangle_object.dart' ;
1819import 'package:cookethflow/features/models/canvas_models/user_cursor.dart' ;
1920import 'package:flutter/material.dart' ;
20- import 'package:flutter_quill/flutter_quill.dart' ; // NEW: Import flutter_quill
21+ import 'package:flutter_quill/flutter_quill.dart' ;
2122
2223class CanvasPainter extends CustomPainter {
2324 final Map <String , UserCursor > userCursors;
2425 final Map <String , CanvasObject > canvasObjects;
2526 final String ? currentlySelectedObjectId;
2627 final double handleRadius;
28+ final InteractionMode interactionMode; // UPDATED: Added interactionMode
2729
2830 CanvasPainter ({
2931 required this .userCursors,
3032 required this .canvasObjects,
3133 this .currentlySelectedObjectId,
3234 this .handleRadius = 8.0 ,
35+ required this .interactionMode, // UPDATED: Added to constructor
3336 });
3437
3538 @override
3639 void paint (Canvas canvas, Size size) {
3740 // Draw each canvas object
3841 for (final canvasObject in canvasObjects.values) {
3942 final paint = Paint ()..color = canvasObject.color;
40-
4143 Rect rect;
44+
4245 if (canvasObject is Circle ) {
4346 canvas.drawCircle (canvasObject.center, canvasObject.radius, paint);
44- rect = Rect .fromCircle (center: canvasObject.center, radius: canvasObject.radius);
45- } else if (canvasObject is TextBoxObject ) { // NEW: Handle TextBoxObject drawing
47+ rect = Rect .fromCircle (
48+ center: canvasObject.center, radius: canvasObject.radius);
49+ } else if (canvasObject is TextBoxObject ) {
4650 rect = canvasObject.getBounds ();
47- // For text boxes, we might draw a subtle border if it's transparent,
48- // or just let the text render.
49- if (canvasObject.color == Colors .transparent) {
50- final borderPaint = Paint ()
51- ..color = Colors .grey.shade400
52- ..style = PaintingStyle .stroke
53- ..strokeWidth = 1.0 ;
54- canvas.drawRect (rect, borderPaint);
55- } else {
51+ // FIX: Only draw the background if the color is NOT transparent.
52+ if (canvasObject.color != Colors .transparent) {
5653 canvas.drawRect (rect, paint);
5754 }
58- }
59- else {
55+ } else {
6056 // For other shapes, use their getBounds() method
6157 rect = canvasObject.getBounds ();
6258 if (canvasObject is Rectangle ) {
6359 canvas.drawRect (rect, paint);
6460 } else if (canvasObject is Square ) {
6561 canvas.drawRect (rect, paint);
6662 } else if (canvasObject is RoundedSquare ) {
67- canvas.drawRRect (RRect .fromRectAndRadius (rect, Radius .circular (canvasObject.cornerRadius)), paint);
63+ canvas.drawRRect (
64+ RRect .fromRectAndRadius (
65+ rect, Radius .circular (canvasObject.cornerRadius)),
66+ paint);
6867 } else if (canvasObject is Diamond ) {
6968 final path = Path ()
7069 ..moveTo (rect.center.dx, rect.top)
@@ -98,15 +97,32 @@ class CanvasPainter extends CustomPainter {
9897 canvas.drawPath (path, paint);
9998 } else if (canvasObject is Cylinder ) {
10099 final ellipseHeight = min (rect.height * 0.3 , 40.0 );
101- final bodyRect = Rect .fromLTRB (rect.left, rect.top + ellipseHeight / 2 , rect.right, rect.bottom - ellipseHeight / 2 );
100+ final bodyRect = Rect .fromLTRB (rect.left,
101+ rect.top + ellipseHeight / 2 , rect.right, rect.bottom - ellipseHeight / 2 );
102102 canvas.drawRect (bodyRect, paint);
103- canvas.drawOval (Rect .fromCenter (center: bodyRect.topCenter, width: rect.width, height: ellipseHeight), paint);
104- canvas.drawOval (Rect .fromCenter (center: bodyRect.bottomCenter, width: rect.width, height: ellipseHeight), paint);
103+ canvas.drawOval (
104+ Rect .fromCenter (
105+ center: bodyRect.topCenter,
106+ width: rect.width,
107+ height: ellipseHeight),
108+ paint);
109+ canvas.drawOval (
110+ Rect .fromCenter (
111+ center: bodyRect.bottomCenter,
112+ width: rect.width,
113+ height: ellipseHeight),
114+ paint);
105115 }
106116 }
107117
108- // NEW: Draw text content for any object that has it
109- if (canvasObject.textDelta != null && canvasObject.textDelta! .isNotEmpty) {
118+ // Check if this object is currently being edited
119+ final bool isEditingText = interactionMode == InteractionMode .editingText &&
120+ currentlySelectedObjectId == canvasObject.id;
121+
122+ // Draw text content for any object that has it, but NOT if it's being edited
123+ if (canvasObject.textDelta != null &&
124+ canvasObject.textDelta! .isNotEmpty &&
125+ ! isEditingText) {
110126 try {
111127 final doc = Document .fromJson (jsonDecode (canvasObject.textDelta! ));
112128 final richText = TextSpan (
@@ -115,11 +131,22 @@ class CanvasPainter extends CustomPainter {
115131 return TextSpan (
116132 text: op.data as String ,
117133 style: TextStyle (
118- fontSize: (op.attributes? ['size' ] as double ? ) ?? 14.0 , // Default font size
119- fontWeight: op.attributes? ['bold' ] == true ? FontWeight .bold : FontWeight .normal,
120- fontStyle: op.attributes? ['italic' ] == true ? FontStyle .italic : FontStyle .normal,
121- decoration: op.attributes? ['underline' ] == true ? TextDecoration .underline : TextDecoration .none,
122- color: Color (int .tryParse ((op.attributes? ['color' ] as String ? )? .replaceAll ('#' , '0xff' ) ?? '' , radix: 16 ) ?? Colors .black.value),
134+ fontSize: (op.attributes? ['size' ] as double ? ) ?? 14.0 ,
135+ fontWeight: op.attributes? ['bold' ] == true
136+ ? FontWeight .bold
137+ : FontWeight .normal,
138+ fontStyle: op.attributes? ['italic' ] == true
139+ ? FontStyle .italic
140+ : FontStyle .normal,
141+ decoration: op.attributes? ['underline' ] == true
142+ ? TextDecoration .underline
143+ : TextDecoration .none,
144+ color: Color (int .tryParse (
145+ (op.attributes? ['color' ] as String ? )
146+ ? .replaceAll ('#' , '0xff' ) ??
147+ '' ,
148+ radix: 16 ) ??
149+ Colors .black.value),
123150 ),
124151 );
125152 }
@@ -129,60 +156,47 @@ class CanvasPainter extends CustomPainter {
129156
130157 final textPainter = TextPainter (
131158 text: richText,
132- textDirection: TextDirection .ltr, // Assuming LTR for most cases
133- maxLines : null , // Allow multiple lines
159+ textDirection: TextDirection .ltr,
160+ textAlign : TextAlign .center , // Center text
134161 );
135162
136- // Constrain text to object's bounds.
137- // For TextBoxObject, the text fills the box.
138- // For other shapes, it can be centered with some padding.
139- double textPadding = 5.0 ; // Small padding inside shapes
163+ double textPadding = 5.0 ;
140164 double availableWidth = rect.width - 2 * textPadding;
141- double availableHeight = rect.height - 2 * textPadding;
142-
143- if (availableWidth <= 0 || availableHeight <= 0 ) {
144- continue ; // Skip drawing text if object is too small
145- }
165+
166+ if (availableWidth <= 0 ) continue ;
146167
147168 textPainter.layout (maxWidth: availableWidth);
148-
149- // Calculate offset to center text vertically and horizontally
169+
150170 final textOffset = Offset (
151171 rect.left + textPadding + (availableWidth - textPainter.width) / 2 ,
152- rect.top + textPadding + (availableHeight - textPainter.height) / 2 ,
172+ rect.top + textPadding + (rect.height - 2 * textPadding - textPainter.height) / 2 ,
153173 );
154174
155- // Save canvas state before clipping, restore after
156175 canvas.save ();
157- // Clip text to the object's bounds to prevent overflow
158176 canvas.clipRect (rect);
159177 textPainter.paint (canvas, textOffset);
160178 canvas.restore ();
161179
162180 } catch (e) {
163- // Fallback for malformed Quill Delta (or plain text directly saved)
181+ // Fallback for plain text
164182 final textPainter = TextPainter (
165183 text: TextSpan (
166- text: canvasObject.textDelta, // Render as plain text
184+ text: canvasObject.textDelta,
167185 style: const TextStyle (color: Colors .black, fontSize: 14.0 ),
168186 ),
169187 textDirection: TextDirection .ltr,
170- maxLines : null ,
188+ textAlign : TextAlign .center ,
171189 );
172-
173190 double textPadding = 5.0 ;
174191 double availableWidth = rect.width - 2 * textPadding;
175- double availableHeight = rect.height - 2 * textPadding;
176192
177- if (availableWidth <= 0 || availableHeight <= 0 ) {
178- continue ;
179- }
193+ if (availableWidth <= 0 ) continue ;
180194
181195 textPainter.layout (maxWidth: availableWidth);
182196
183197 final textOffset = Offset (
184198 rect.left + textPadding + (availableWidth - textPainter.width) / 2 ,
185- rect.top + textPadding + (availableHeight - textPainter.height) / 2 ,
199+ rect.top + textPadding + (rect.height - 2 * textPadding - textPainter.height) / 2 ,
186200 );
187201 canvas.save ();
188202 canvas.clipRect (rect);
@@ -198,26 +212,22 @@ class CanvasPainter extends CustomPainter {
198212 final handlePaint = Paint ()
199213 ..color = Colors .blue
200214 ..style = PaintingStyle .fill;
201-
202- // Draw corner handles
215+
203216 canvas.drawCircle (rect.topLeft, handleRadius, handlePaint);
204217 canvas.drawCircle (rect.topRight, handleRadius, handlePaint);
205218 canvas.drawCircle (rect.bottomLeft, handleRadius, handlePaint);
206219 canvas.drawCircle (rect.bottomRight, handleRadius, handlePaint);
207220
208- // Draw selection border (dashed for text box if transparent)
209221 final borderPaint = Paint ()
210222 ..color = Colors .blue
211223 ..style = PaintingStyle .stroke
212224 ..strokeWidth = 2.0 ;
213-
225+
226+ // Draw dashed border for transparent text box when selected
214227 if (canvasObject is TextBoxObject && canvasObject.color == Colors .transparent) {
215- // Draw dashed border for transparent text box
216228 const double dashWidth = 5.0 ;
217229 const double dashSpace = 3.0 ;
218230 double currentX = rect.left;
219- double currentY = rect.top;
220-
221231 // Top line
222232 while (currentX < rect.right) {
223233 canvas.drawLine (
@@ -228,7 +238,7 @@ class CanvasPainter extends CustomPainter {
228238 currentX += dashWidth + dashSpace;
229239 }
230240 // Right line
231- currentX = rect.right ;
241+ double currentY = rect.top ;
232242 while (currentY < rect.bottom) {
233243 canvas.drawLine (
234244 Offset (rect.right, currentY),
@@ -238,26 +248,24 @@ class CanvasPainter extends CustomPainter {
238248 currentY += dashWidth + dashSpace;
239249 }
240250 // Bottom line
241- currentY = rect.bottom;
242- currentX = rect.right;
243- while (currentX > rect.left) {
251+ currentX = rect.left;
252+ while (currentX < rect.right) {
244253 canvas.drawLine (
245- Offset (currentX, rect.bottom),
246- Offset (max ( currentX - dashWidth, rect.left ), rect.bottom),
254+ Offset (rect.right - ( currentX - rect.left) , rect.bottom),
255+ Offset (rect.right - min (( currentX - rect.left) + dashWidth, rect.width ), rect.bottom),
247256 borderPaint,
248257 );
249- currentX - = dashWidth + dashSpace;
258+ currentX + = dashWidth + dashSpace;
250259 }
251260 // Left line
252- currentX = rect.left;
253- currentY = rect.bottom;
254- while (currentY > rect.top) {
261+ currentY = rect.top;
262+ while (currentY < rect.bottom) {
255263 canvas.drawLine (
256- Offset (rect.left, currentY),
257- Offset (rect.left, max ( currentY - dashWidth, rect.top )),
264+ Offset (rect.left, rect.bottom - ( currentY - rect.top) ),
265+ Offset (rect.left, rect.bottom - min (( currentY - rect.top) + dashWidth, rect.height )),
258266 borderPaint,
259267 );
260- currentY - = dashWidth + dashSpace;
268+ currentY + = dashWidth + dashSpace;
261269 }
262270 } else {
263271 canvas.drawRect (rect, borderPaint);
@@ -282,40 +290,26 @@ class CanvasPainter extends CustomPainter {
282290
283291 @override
284292 bool shouldRepaint (CanvasPainter oldPainter) {
285- // Only repaint if the data or selection changes significantly
286293 return oldPainter.userCursors != userCursors ||
287294 oldPainter.canvasObjects.length != canvasObjects.length ||
288295 oldPainter.currentlySelectedObjectId != currentlySelectedObjectId ||
289- // Deep comparison of canvas objects is expensive, but necessary if text content changes frequently
296+ oldPainter.interactionMode != interactionMode || // UPDATED: Add interactionMode check
290297 _hasCanvasObjectsChanged (oldPainter.canvasObjects, canvasObjects);
291298 }
292299
293- // Helper method for deep comparison of canvas objects
294300 bool _hasCanvasObjectsChanged (Map <String , CanvasObject > oldObjects, Map <String , CanvasObject > newObjects) {
295301 if (oldObjects.length != newObjects.length) return true ;
296-
297302 for (final id in newObjects.keys) {
298303 final newObj = newObjects[id];
299304 final oldObj = oldObjects[id];
300-
301- if (oldObj == null || newObj == null ) return true ; // Object added/removed
302-
303- // Check if ID is different (shouldn't happen for same key)
305+ if (oldObj == null || newObj == null ) return true ;
304306 if (newObj.id != oldObj.id) return true ;
305-
306- // Check basic properties
307- if (newObj.color != oldObj.color ||
308- newObj.getBounds () != oldObj.getBounds ()) {
307+ if (newObj.color != oldObj.color || newObj.getBounds () != oldObj.getBounds ()) {
309308 return true ;
310309 }
311-
312- // Check textDelta content
313310 if (newObj.textDelta != oldObj.textDelta) {
314311 return true ;
315312 }
316- // Add more specific checks if objects have other unique properties that can change.
317- // For instance, if circle's radius or center changed.
318- // A more robust check might involve comparing their toJson() output.
319313 }
320314 return false ;
321315 }
0 commit comments