Skip to content

Commit 17510e8

Browse files
committed
text box,resize, text box in canvas objects
1 parent 11ffb1d commit 17510e8

File tree

12 files changed

+179
-170
lines changed

12 files changed

+179
-170
lines changed

lib/features/models/canvas_models/canvas_object.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// lib/features/models/canvas_models/canvas_object.dart
2+
13
import 'dart:convert';
24
import 'dart:math';
35
import 'package:cookethflow/features/models/canvas_models/objects/circle_object.dart';
@@ -61,11 +63,11 @@ abstract class CanvasObject extends SyncedObject {
6163
}
6264

6365
bool intersectsWith(Offset point);
64-
CanvasObject copyWith({String? textDelta});
66+
CanvasObject copyWith({String? textDelta, Color? color});
6567
CanvasObject move(Offset delta);
6668
Rect getBounds();
6769
CanvasObject resize(
6870
Offset newTopLeft,
6971
Offset newBottomRight,
7072
);
71-
}
73+
}

lib/features/models/canvas_models/canvas_painter.dart

Lines changed: 73 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:cookethflow/features/models/canvas_models/objects/text_box_objec
1616
import 'package:cookethflow/features/models/canvas_models/objects/triangle_object.dart';
1717
import 'package:cookethflow/features/models/canvas_models/user_cursor.dart';
1818
import 'package:flutter/material.dart';
19+
import 'package:path_drawing/path_drawing.dart';
1920

2021
class CanvasPainter extends CustomPainter {
2122
final Map<String, UserCursor> userCursors;
@@ -42,7 +43,6 @@ class CanvasPainter extends CustomPainter {
4243
}
4344
}
4445

45-
// FIX: Added robust font size parsing
4646
double _getFontSize(dynamic size) {
4747
if (size == null) return 14.0;
4848
if (size is double) return size;
@@ -67,15 +67,23 @@ class CanvasPainter extends CustomPainter {
6767

6868
final isLink = attributes['link'] != null;
6969
final isCode = attributes['code'] == true || attributes['code-block'] == true;
70+
final headerLevel = attributes['header'];
71+
72+
double fontSize = 14.0;
73+
if (headerLevel == 1) {
74+
fontSize = 22.0;
75+
} else if (headerLevel == 2) {
76+
fontSize = 18.0;
77+
} else if (attributes['size'] != null) {
78+
fontSize = _getFontSize(attributes['size']);
79+
}
7080

7181
return TextStyle(
7282
fontWeight: attributes['bold'] == true ? FontWeight.bold : FontWeight.normal,
7383
fontStyle: attributes['italic'] == true ? FontStyle.italic : FontStyle.normal,
74-
// FIX: Handle links correctly
7584
color: isLink ? Colors.blue : _parseColor(attributes['color'] as String?),
76-
fontSize: _getFontSize(attributes['size'] ?? (attributes['header'] == 1 ? 'huge' : (attributes['header'] == 2 ? 'large' : null))),
85+
fontSize: fontSize,
7786
fontFamily: isCode ? 'monospace' : (attributes['font'] as String?),
78-
// FIX: Handle links and inline code underline
7987
decoration: attributes['underline'] == true || isLink ? TextDecoration.underline : TextDecoration.none,
8088
backgroundColor: attributes['background'] != null
8189
? _parseColor(attributes['background'] as String?)
@@ -130,82 +138,88 @@ class CanvasPainter extends CustomPainter {
130138

131139
final bool isEditingText = interactionMode == InteractionMode.editingText && currentlySelectedObjectId == canvasObject.id;
132140

133-
// FIX: Complete rewrite of the text rendering logic.
134141
if (canvasObject.textDelta != null && canvasObject.textDelta!.isNotEmpty && !isEditingText) {
135142
try {
136143
final List<dynamic> delta = jsonDecode(canvasObject.textDelta!);
137144
final double textPadding = 8.0;
138145
double yOffset = rect.top + textPadding;
139146

147+
final List<Map<String, dynamic>> lines = [];
140148
List<Map<String, dynamic>> currentLineOps = [];
149+
141150
for (final op in delta) {
142151
final String text = op['insert'];
143152
final Map<String, dynamic>? attributes = op['attributes'] as Map<String, dynamic>?;
144153

145154
if (text.contains('\n')) {
146-
final lines = text.split('\n');
147-
for (int i = 0; i < lines.length; i++) {
148-
if (lines[i].isNotEmpty) {
149-
currentLineOps.add({'insert': lines[i], 'attributes': attributes});
155+
final textLines = text.split('\n');
156+
for (int i = 0; i < textLines.length; i++) {
157+
if (textLines[i].isNotEmpty) {
158+
currentLineOps.add({'insert': textLines[i], 'attributes': attributes});
150159
}
151160

152-
if (i < lines.length - 1) { // This is a line break
153-
final lineSpans = currentLineOps.map((o) => TextSpan(text: o['insert'], style: _getTextStyle(o['attributes']))).toList();
154-
155-
// Check for block attributes on the line break
156-
final blockAttributes = attributes ?? {};
157-
String prefix = '';
158-
double indent = 0;
159-
if(blockAttributes['list'] == 'bullet') {
160-
prefix = '• ';
161-
indent = 10.0;
162-
} else if(blockAttributes['list'] == 'ordered') {
163-
prefix = '1. '; // This is simplified, a proper implementation needs a counter
164-
indent = 10.0;
165-
} else if (blockAttributes['blockquote'] == true) {
166-
indent = 20.0;
167-
}
168-
169-
if(blockAttributes['code-block'] == true) {
170-
final blockPaint = Paint()..color = Colors.grey.shade200;
171-
// This is a simplified block drawing, would need to calculate total block height for a perfect rect
172-
// For now, it draws a rect behind each line of the code block.
173-
canvas.drawRect(Rect.fromLTWH(rect.left, yOffset, rect.width, 20), blockPaint); // Approximate height
174-
}
175-
176-
final textPainter = TextPainter(
177-
text: TextSpan(children: [TextSpan(text: prefix), ...lineSpans]),
178-
textDirection: TextDirection.ltr,
179-
);
180-
181-
final availableWidth = rect.width - (2 * textPadding) - indent;
182-
if (availableWidth > 0) {
183-
textPainter.layout(maxWidth: availableWidth);
184-
textPainter.paint(canvas, Offset(rect.left + textPadding + indent, yOffset));
185-
yOffset += textPainter.height;
186-
}
187-
currentLineOps = [];
161+
if (i < textLines.length - 1) { // Line break
162+
lines.add({
163+
'ops': List.from(currentLineOps),
164+
'attributes': attributes ?? {},
165+
});
166+
currentLineOps.clear();
188167
}
189168
}
190169
} else {
191170
currentLineOps.add(op);
192171
}
193172
}
194-
195-
// Paint any remaining text that didn't end with a newline
196173
if (currentLineOps.isNotEmpty) {
197-
final lineSpans = currentLineOps.map((o) => TextSpan(text: o['insert'], style: _getTextStyle(o['attributes']))).toList();
198-
final textPainter = TextPainter(
199-
text: TextSpan(children: lineSpans),
200-
textDirection: TextDirection.ltr,
201-
);
202-
final availableWidth = rect.width - (2 * textPadding);
203-
if (availableWidth > 0) {
204-
textPainter.layout(maxWidth: availableWidth);
205-
textPainter.paint(canvas, Offset(rect.left + textPadding, yOffset));
206-
}
174+
lines.add({'ops': currentLineOps, 'attributes': {}});
207175
}
208176

177+
int orderedListCounter = 1;
178+
for(final line in lines) {
179+
// FIX: Safely create a typed list from the dynamic list.
180+
final lineOps = List<Map<String, dynamic>>.from(line['ops'] as List);
181+
final blockAttributes = line['attributes'] as Map<String, dynamic>;
182+
183+
final lineSpans = lineOps.map((o) => TextSpan(text: o['insert'], style: _getTextStyle(o['attributes'] as Map<String, dynamic>?))).toList();
184+
185+
String prefix = '';
186+
double indent = 0;
187+
if (blockAttributes['list'] == 'bullet') {
188+
prefix = '• ';
189+
indent = 10.0;
190+
orderedListCounter = 1; // Reset ordered list
191+
} else if (blockAttributes['list'] == 'ordered') {
192+
prefix = '$orderedListCounter. ';
193+
indent = 10.0;
194+
orderedListCounter++;
195+
} else {
196+
orderedListCounter = 1; // Reset
197+
}
198+
199+
if (blockAttributes['blockquote'] == true) {
200+
indent = 20.0;
201+
final blockPaint = Paint()..color = Colors.grey.shade300..strokeWidth = 2;
202+
canvas.drawLine(Offset(rect.left + textPadding, yOffset), Offset(rect.left + textPadding, yOffset + 20), blockPaint); // Approximate height
203+
}
204+
205+
if (blockAttributes['code-block'] == true) {
206+
final blockPaint = Paint()..color = Colors.grey.shade200;
207+
canvas.drawRect(Rect.fromLTWH(rect.left, yOffset, rect.width, 20), blockPaint); // Approximate height
208+
}
209+
210+
final textPainter = TextPainter(
211+
text: TextSpan(children: [TextSpan(text: prefix, style: _getTextStyle(blockAttributes)), ...lineSpans]),
212+
textDirection: TextDirection.ltr,
213+
textAlign: TextAlign.start,
214+
);
215+
216+
final availableWidth = rect.width - (2 * textPadding) - indent;
217+
if (availableWidth > 0) {
218+
textPainter.layout(maxWidth: availableWidth);
219+
textPainter.paint(canvas, Offset(rect.left + textPadding + indent, yOffset));
220+
yOffset += textPainter.height;
221+
}
222+
}
209223
} catch (e) {
210224
print("Error painting text: $e");
211225
}
@@ -220,12 +234,8 @@ class CanvasPainter extends CustomPainter {
220234
canvas.drawCircle(rect.bottomRight, handleRadius, handlePaint);
221235

222236
final borderPaint = Paint()..color = Colors.blue..style = PaintingStyle.stroke..strokeWidth = 2.0;
223-
if (canvasObject is TextBoxObject && canvasObject.color == Colors.transparent) {
224-
final path = Path()..addRect(rect);
225-
canvas.drawPath(dashPath(path, dashArray: CircularIntervalList<double>([5.0, 3.0])), borderPaint);
226-
} else {
227-
canvas.drawRect(rect, borderPaint);
228-
}
237+
final path = Path()..addRect(rect);
238+
canvas.drawPath(dashPath(path, dashArray: CircularIntervalList<double>([5.0, 3.0])), borderPaint);
229239
}
230240
}
231241

@@ -258,33 +268,4 @@ class CanvasPainter extends CustomPainter {
258268
}
259269
return false;
260270
}
261-
}
262-
263-
Path dashPath(Path source, {required CircularIntervalList<double> dashArray}) {
264-
final Path dest = Path();
265-
for (final metric in source.computeMetrics()) {
266-
double distance = 0.0;
267-
bool draw = true;
268-
while (distance < metric.length) {
269-
final len = dashArray.next;
270-
if (draw) {
271-
dest.addPath(metric.extractPath(distance, distance + len), Offset.zero);
272-
}
273-
distance += len;
274-
draw = !draw;
275-
}
276-
}
277-
return dest;
278-
}
279-
280-
class CircularIntervalList<T> {
281-
CircularIntervalList(this._values);
282-
final List<T> _values;
283-
int _idx = 0;
284-
T get next {
285-
if (_idx >= _values.length) {
286-
_idx = 0;
287-
}
288-
return _values[_idx++];
289-
}
290271
}

lib/features/models/canvas_models/objects/rectangle_object.dart

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Rectangle extends CanvasObject {
1818
required super.color,
1919
required this.topLeft,
2020
required this.bottomRight,
21-
super.textDelta, // ADDED: textDelta to constructor
21+
super.textDelta,
2222
});
2323

2424
Rectangle.fromJson(Map<String, dynamic> json)
@@ -30,7 +30,7 @@ class Rectangle extends CanvasObject {
3030
super(
3131
id: json['id'],
3232
color: Color(json['color'] as int),
33-
textDelta: json['text_delta'], // ADDED: Load text_delta
33+
textDelta: json['text_delta'],
3434
);
3535

3636
/// Constructor to be used when first creating the object on the canvas with a default size
@@ -40,7 +40,7 @@ class Rectangle extends CanvasObject {
4040
super(
4141
color: RandomColor.getRandom(),
4242
id: const Uuid().v4(),
43-
textDelta: null, // Initial text is null
43+
textDelta: null,
4444
);
4545

4646
@override
@@ -51,7 +51,7 @@ class Rectangle extends CanvasObject {
5151
'color': color.value,
5252
'top_left': {'x': topLeft.dx, 'y': topLeft.dy},
5353
'bottom_right': {'x': bottomRight.dx, 'y': bottomRight.dy},
54-
'text_delta': textDelta, // ADDED: Save text_delta
54+
'text_delta': textDelta,
5555
};
5656
}
5757

@@ -62,13 +62,12 @@ class Rectangle extends CanvasObject {
6262
Color? color,
6363
String? textDelta,
6464
}) {
65-
// ADDED: textDelta to copyWith signature
6665
return Rectangle(
6766
topLeft: topLeft ?? this.topLeft,
6867
id: id,
6968
bottomRight: bottomRight ?? this.bottomRight,
7069
color: color ?? this.color,
71-
textDelta: textDelta ?? this.textDelta, // ADDED: Copy textDelta
70+
textDelta: textDelta ?? this.textDelta,
7271
);
7372
}
7473

@@ -96,7 +95,6 @@ class Rectangle extends CanvasObject {
9695

9796
@override
9897
Rectangle resize(Offset newTopLeft, Offset newBottomRight) {
99-
// Ensure that width and height are not negative
10098
final correctedTopLeft = Offset(
10199
min(newTopLeft.dx, newBottomRight.dx),
102100
min(newTopLeft.dy, newBottomRight.dy),
@@ -110,4 +108,4 @@ class Rectangle extends CanvasObject {
110108
bottomRight: correctedBottomRight,
111109
);
112110
}
113-
}
111+
}

lib/features/models/canvas_models/objects/rounded_square_object.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// lib/features/models/canvas_models/objects/rounded_square_object.dart
22

3-
import 'dart:math'; // Added for min/max in resize
3+
import 'dart:math';
44
import 'package:cookethflow/features/models/canvas_models/canvas_object.dart';
55
import 'package:flutter/material.dart';
66
import 'package:uuid/uuid.dart';
@@ -17,7 +17,7 @@ class RoundedSquare extends CanvasObject {
1717
required this.topLeft,
1818
required this.bottomRight,
1919
this.cornerRadius = 10.0,
20-
super.textDelta, // ADDED: textDelta to constructor
20+
super.textDelta,
2121
});
2222

2323
RoundedSquare.fromJson(Map<String, dynamic> json)
@@ -30,7 +30,7 @@ class RoundedSquare extends CanvasObject {
3030
super(
3131
id: json['id'],
3232
color: Color(json['color'] as int),
33-
textDelta: json['text_delta'], // ADDED: Load text_delta
33+
textDelta: json['text_delta'],
3434
);
3535

3636
RoundedSquare.createNew(Offset defaultTopLeft, Offset defaultBottomRight)
@@ -40,7 +40,7 @@ class RoundedSquare extends CanvasObject {
4040
super(
4141
color: RandomColor.getRandom(),
4242
id: const Uuid().v4(),
43-
textDelta: null, // Initial text is null
43+
textDelta: null,
4444
);
4545

4646
@override
@@ -52,7 +52,7 @@ class RoundedSquare extends CanvasObject {
5252
'top_left': {'x': topLeft.dx, 'y': topLeft.dy},
5353
'bottom_right': {'x': bottomRight.dx, 'y': bottomRight.dy},
5454
'corner_radius': cornerRadius,
55-
'text_delta': textDelta, // ADDED: Save text_delta
55+
'text_delta': textDelta,
5656
};
5757
}
5858

@@ -63,14 +63,13 @@ class RoundedSquare extends CanvasObject {
6363
Color? color,
6464
String? textDelta,
6565
}) {
66-
// ADDED: textDelta to copyWith signature
6766
return RoundedSquare(
6867
topLeft: topLeft ?? this.topLeft,
6968
id: id,
7069
bottomRight: bottomRight ?? this.bottomRight,
7170
color: color ?? this.color,
7271
cornerRadius: cornerRadius,
73-
textDelta: textDelta ?? this.textDelta, // ADDED: Copy textDelta
72+
textDelta: textDelta ?? this.textDelta,
7473
);
7574
}
7675

@@ -92,7 +91,6 @@ class RoundedSquare extends CanvasObject {
9291

9392
@override
9493
RoundedSquare resize(Offset newTopLeft, Offset newBottomRight) {
95-
// Ensure that width and height are not negative
9694
final correctedTopLeft = Offset(
9795
min(newTopLeft.dx, newBottomRight.dx),
9896
min(newTopLeft.dy, newBottomRight.dy),
@@ -106,4 +104,4 @@ class RoundedSquare extends CanvasObject {
106104
bottomRight: correctedBottomRight,
107105
);
108106
}
109-
}
107+
}

0 commit comments

Comments
 (0)