Skip to content

Commit d9f229e

Browse files
committed
feat: Implement real-time BCP handle updates and curved path preview in pen tool
- Add real-time handle updates during modifier+drag in onPointerMove - Update pen preview overlay to render curved paths with Bézier control points - Track lastAnchorHandleOut to properly connect curves between anchors - Fix shape rendering and selection issues for transformed shapes (Star/Polygon) - Apply transforms to paths instead of canvas to prevent stroke width scaling - Update hit testing and bounds calculation to account for shape transforms - Add bounding box parameters for non-uniform scaling of shapes - Implement FontForge-style pen tool behavior with immediate anchor creation The pen tool now provides immediate visual feedback: - Click down creates anchor and line immediately - Normal drag moves the anchor position - Alt/Cmd + drag keeps anchor at original position but extends Bézier handles - Path curves update in real-time according to BCP handles during drag
1 parent cc02e08 commit d9f229e

15 files changed

Lines changed: 1190 additions & 154 deletions

File tree

lib/application/services/document_event_applier.dart

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class DocumentEventApplier {
156156
final centerY = event.parameters['centerY'] ?? 0.0;
157157
final radius = event.parameters['radius'] ?? 50.0;
158158
final sides = event.parameters['sides']?.toInt() ?? 6;
159+
159160
shape = Shape.polygon(
160161
center: events.Point(x: centerX, y: centerY),
161162
radius: radius,
@@ -168,6 +169,7 @@ class DocumentEventApplier {
168169
final outerRadius = event.parameters['outerRadius'] ?? 50.0;
169170
final innerRadius = event.parameters['innerRadius'] ?? 25.0;
170171
final points = event.parameters['points']?.toInt() ?? 5;
172+
171173
shape = Shape.star(
172174
center: events.Point(x: centerX, y: centerY),
173175
outerRadius: outerRadius,
@@ -177,9 +179,53 @@ class DocumentEventApplier {
177179
break;
178180
}
179181

182+
// Check if we need to apply a transform for non-uniform scaling
183+
Transform? transform;
184+
if (event.shapeType == events.ShapeType.polygon || event.shapeType == events.ShapeType.star) {
185+
// Check for bounding box parameters (new approach)
186+
final boundingLeft = event.parameters['boundingLeft'];
187+
final boundingTop = event.parameters['boundingTop'];
188+
final boundingWidth = event.parameters['boundingWidth'];
189+
final boundingHeight = event.parameters['boundingHeight'];
190+
191+
if (boundingLeft != null && boundingTop != null &&
192+
boundingWidth != null && boundingHeight != null) {
193+
// Create a composite transform:
194+
// 1. Scale the unit shape to the bounding box size
195+
// 2. Translate to the bounding box position
196+
final scaleX = boundingWidth / 2; // Shape has unit radius = 1, diameter = 2
197+
final scaleY = boundingHeight / 2;
198+
final translateX = boundingLeft + boundingWidth / 2; // Center of bounding box
199+
final translateY = boundingTop + boundingHeight / 2;
200+
201+
// Create scale transform at origin
202+
final scaleTransform = Transform.scale(scaleX, scaleY);
203+
// Create translation transform
204+
final translateTransform = Transform.translate(translateX, translateY);
205+
// Compose: scale first, then translate
206+
transform = scaleTransform.compose(translateTransform);
207+
} else {
208+
// Fallback to old approach if using old parameters
209+
final scaleX = event.parameters['scaleX'];
210+
final scaleY = event.parameters['scaleY'];
211+
212+
if (scaleX != null && scaleY != null) {
213+
// Create a scale transform centered at the shape's center
214+
final centerX = event.parameters['centerX'] ?? 0.0;
215+
final centerY = event.parameters['centerY'] ?? 0.0;
216+
transform = Transform.scaleAround(
217+
sx: scaleX,
218+
sy: scaleY,
219+
center: events.Point(x: centerX, y: centerY),
220+
);
221+
}
222+
}
223+
}
224+
180225
final shapeObject = VectorObject.shape(
181226
id: event.shapeId,
182227
shape: shape,
228+
transform: transform,
183229
);
184230

185231
_addObjectToFirstLayer(shapeObject);
@@ -256,7 +302,13 @@ class DocumentEventApplier {
256302

257303
for (final layer in layers) {
258304
final updatedObjects = layer.objects
259-
.where((obj) => !event.objectIds.contains(obj.id))
305+
.where((obj) {
306+
final objId = obj.when(
307+
path: (id, _, __) => id,
308+
shape: (id, _, __) => id,
309+
);
310+
return !event.objectIds.contains(objId);
311+
})
260312
.toList();
261313

262314
updatedLayers.add(layer.copyWith(objects: updatedObjects));

0 commit comments

Comments
 (0)