Skip to content

Commit f4c25ab

Browse files
author
DavidQ
committed
Fix drawing hint stroke width preview and text placement rendering - PR_26133_071-preview-hint-stroke-width-and-text-placement-fixes
1 parent 5e6ee39 commit f4c25ab

5 files changed

Lines changed: 175 additions & 10 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Playwright V8 Coverage Report
22

3-
PR: PR_26133_070-unified-click-preview-click-shape-creation
3+
PR: PR_26133_071-preview-hint-stroke-width-and-text-placement-fixes
44

55
Source: docs/dev/reports/playwright_v8_coverage_report.txt generated by the latest npm run test:workspace-v2 run.
66

@@ -21,9 +21,10 @@ Source: docs/dev/reports/playwright_v8_coverage_report.txt generated by the late
2121

2222
## Changed Runtime JS Files Covered
2323

24-
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 6352/6352; executed functions 645/676
24+
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 6410/6410; executed functions 649/680
2525

2626
## Changed JS Files Considered
2727

2828
- (0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - Playwright test file, not collected as browser runtime coverage
2929
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - changed runtime JS file with browser V8 coverage
30+
- (0%) tools/object-vector-studio-v2/styles/toolStarter.css - CSS file, not collected by browser JavaScript V8 coverage

docs/dev/reports/playwright_workspace_v2_results.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
# Playwright Workspace V2 Results
22

3-
PR: PR_26133_070-unified-click-preview-click-shape-creation
3+
PR: PR_26133_071-preview-hint-stroke-width-and-text-placement-fixes
44

55
## Validation
66

77
- PASS: npm run test:workspace-v2
88
- Result: 54 passed
9-
- Runtime: 4.9m
9+
- Runtime: 5.2m
1010
- Browser project: playwright
1111
- Workers: 1
1212

1313
## Targeted Checks Covered
1414

15-
- Simple/bounded Object Vector Studio V2 tools use click -> live preview -> click commit for line, rectangle, square, circle, ellipse, arc, text, and triangle.
16-
- Polygon and Polyline keep multi-point click behavior and still finish through Enter/double-click completion paths.
17-
- Drawing preview remains visible after first click and mouse move; Escape does not cancel Object Vector Studio V2 drawing.
18-
- Committed shapes capture active Stroke color, Stroke opacity, and Stroke Width consistently from drawing start through commit.
19-
- Newly committed shapes keep transparent fill unless Paint is applied later.
20-
- Snap Grid, Snap Point, and Snap None behaviors remain covered during drawing flows.
15+
- Polygon drawing shows a cursor-following "Double-click / Enter to complete" hint with pointer-events disabled.
16+
- Polygon and Polyline keep existing multi-point completion behavior.
17+
- Stroke width 20 drawing previews use proportional dash spacing instead of the small default dash pattern.
18+
- Completed wide-stroke shapes render solid and immediately keep the active Stroke Width.
19+
- Text placement preview renders readable text instead of a wide stroked blob.
20+
- Committed text keeps transparent fill plus active stroke color, opacity, and width under current style rules.
2121

2222
## Console/Runtime Errors
2323

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3486,6 +3486,31 @@ test.describe("Workspace Manager V2 bootstrap", () => {
34863486

34873487
await page.locator('[data-shape-tool="polygon"]').click();
34883488
await expect(page.locator("#statusLog")).toHaveValue(/OK Drawing mode selected: Polygon\. Click to add points\.\n\nDouble-click to finish\./);
3489+
await moveObjectVectorLogicalPoint(page, { x: -30, y: -20 });
3490+
await expect(page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-hint")).toHaveText("Double-click / Enter to complete");
3491+
const polygonHintState = await page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-hint").evaluate((hint) => {
3492+
const text = hint.querySelector("text");
3493+
const app = window.__objectVectorStudioV2App;
3494+
return {
3495+
hintX: Number(text.getAttribute("x")),
3496+
hintY: Number(text.getAttribute("y")),
3497+
pointerEvents: getComputedStyle(hint).pointerEvents,
3498+
pointerX: app.drawingPreviewPoint.x * 10,
3499+
pointerY: app.drawingPreviewPoint.y * 10,
3500+
textPointerEvents: getComputedStyle(text).pointerEvents
3501+
};
3502+
});
3503+
expect(polygonHintState.pointerEvents).toBe("none");
3504+
expect(polygonHintState.textPointerEvents).toBe("none");
3505+
expect(polygonHintState.hintX).toBeGreaterThan(polygonHintState.pointerX);
3506+
expect(polygonHintState.hintY).toBeGreaterThan(polygonHintState.pointerY);
3507+
await moveObjectVectorLogicalPoint(page, { x: -25, y: -15 });
3508+
const movedPolygonHint = await page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-hint text").evaluate((text) => ({
3509+
x: Number(text.getAttribute("x")),
3510+
y: Number(text.getAttribute("y"))
3511+
}));
3512+
expect(movedPolygonHint.x).toBeGreaterThan(polygonHintState.hintX);
3513+
expect(movedPolygonHint.y).toBeGreaterThan(polygonHintState.hintY);
34893514
await page.locator('[data-shape-tool="polyline"]').click();
34903515
await expect(page.locator("#statusLog")).toHaveValue(/OK Drawing mode selected: Polyline\. Click to add points\.\n\nDouble-click to finish\./);
34913516
await page.locator('[data-shape-tool="rectangle"]').click();
@@ -3623,6 +3648,68 @@ test.describe("Workspace Manager V2 bootstrap", () => {
36233648
{ fill: "#00000000", fillOpacity: 1, stroke: "#6fd3ff", strokeOpacity: 1, strokeWidth: 4.5, tool: "text" }
36243649
]);
36253650

3651+
await page.locator("#objectVectorStudioV2StrokeWidth").fill("20");
3652+
await page.locator("#objectVectorStudioV2StrokeWidth").dispatchEvent("change");
3653+
await page.locator('[data-shape-tool="line"]').click();
3654+
await clickObjectVectorLogicalPoint(page, -40, -50);
3655+
await moveObjectVectorLogicalPoint(page, { x: -10, y: -50 });
3656+
const wideStrokePreview = await page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-preview").evaluate((preview) => ({
3657+
dash: preview.style.strokeDasharray,
3658+
strokeWidth: Number(preview.getAttribute("stroke-width")),
3659+
tool: preview.dataset.drawingPreviewTool
3660+
}));
3661+
const wideStrokeDash = wideStrokePreview.dash.match(/[\d.]+/g).map(Number);
3662+
expect(wideStrokePreview).toMatchObject({ strokeWidth: 20, tool: "line" });
3663+
expect(wideStrokeDash[0]).toBeGreaterThan(5);
3664+
expect(wideStrokeDash[0]).toBeLessThan(220);
3665+
expect(wideStrokeDash[1]).toBeGreaterThan(4);
3666+
await clickObjectVectorLogicalPoint(page, -10, -50);
3667+
const wideStrokeLine = await page.evaluate(() => {
3668+
const app = window.__objectVectorStudioV2App;
3669+
return {
3670+
index: app.selectedShapeIndex,
3671+
style: JSON.parse(JSON.stringify(app.selectedShape().style))
3672+
};
3673+
});
3674+
expect(wideStrokeLine.style).toMatchObject({
3675+
fill: "#00000000",
3676+
stroke: "#6fd3ff",
3677+
strokeOpacity: 1,
3678+
strokeWidth: 20
3679+
});
3680+
const committedWideLine = await page.locator(`#objectVectorStudioV2RenderSurface [data-shape-index="${wideStrokeLine.index}"]`).evaluate((shape) => ({
3681+
dash: getComputedStyle(shape).strokeDasharray,
3682+
isPreview: shape.classList.contains("object-vector-studio-v2__drawing-preview"),
3683+
strokeWidth: Number(shape.getAttribute("stroke-width"))
3684+
}));
3685+
expect(committedWideLine).toEqual({ dash: "none", isPreview: false, strokeWidth: 20 });
3686+
3687+
await page.locator('[data-shape-tool="text"]').click();
3688+
await clickObjectVectorLogicalPoint(page, 70, 60);
3689+
await moveObjectVectorLogicalPoint(page, { x: 76, y: 66 });
3690+
const textPreview = await page.locator("#objectVectorStudioV2RenderSurface text.object-vector-studio-v2__drawing-preview").evaluate((preview) => ({
3691+
fill: preview.style.fill || preview.getAttribute("fill"),
3692+
stroke: preview.style.stroke,
3693+
strokeWidth: preview.style.strokeWidth,
3694+
text: preview.textContent.trim(),
3695+
tool: preview.dataset.drawingPreviewTool
3696+
}));
3697+
expect(textPreview.fill).toMatch(/#6fd3ff|rgb\(111, 211, 255\)/);
3698+
expect(textPreview).toMatchObject({
3699+
stroke: "none",
3700+
text: "Text",
3701+
tool: "text"
3702+
});
3703+
expect(["0", "0px"]).toContain(textPreview.strokeWidth);
3704+
await clickObjectVectorLogicalPoint(page, 76, 66);
3705+
const committedTextStyle = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
3706+
expect(committedTextStyle).toMatchObject({
3707+
fill: "#00000000",
3708+
stroke: "#6fd3ff",
3709+
strokeOpacity: 1,
3710+
strokeWidth: 20
3711+
});
3712+
36263713
expect(pageErrors).toEqual([]);
36273714
expect(consoleErrors).toEqual([]);
36283715
} finally {

tools/object-vector-studio-v2/js/ToolStarterApp.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,6 +2486,7 @@ export class ToolStarterApp {
24862486
this.renderObjectBounds(object);
24872487
this.renderSnapPointTargets(object);
24882488
this.renderDrawingPreview();
2489+
this.renderDrawingHint();
24892490
this.renderSelectionOverlay(object);
24902491
this.renderCenterOriginMarker();
24912492

@@ -2687,12 +2688,69 @@ export class ToolStarterApp {
26872688
const element = this.createSvgShape(previewShape, { drawingScale: OBJECT_PREVIEW_DRAWING_SCALE });
26882689
element.classList.add("object-vector-studio-v2__drawing-preview");
26892690
element.dataset.drawingPreviewTool = this.activeDrawing.tool;
2691+
this.applyDrawingPreviewPresentation(element, previewShape);
26902692
this.elements.renderSurface.append(element);
26912693
} catch (error) {
26922694
this.statusLog.write(`FAIL Drawing preview failed for ${this.activeDrawing.tool}: ${error.message}`);
26932695
}
26942696
}
26952697

2698+
renderDrawingHint() {
2699+
const drawing = this.activeDrawing;
2700+
if (!drawing || !["polygon", "polyline"].includes(drawing.tool) || !this.drawingPreviewPoint) {
2701+
return;
2702+
}
2703+
const point = {
2704+
x: Number(this.scaleDrawingValue(this.drawingPreviewPoint.x, OBJECT_PREVIEW_DRAWING_SCALE)),
2705+
y: Number(this.scaleDrawingValue(this.drawingPreviewPoint.y, OBJECT_PREVIEW_DRAWING_SCALE))
2706+
};
2707+
const unitsPerPixel = this.svgUnitsPerCssPixel();
2708+
const group = document.createElementNS(SVG_NS, "g");
2709+
const text = document.createElementNS(SVG_NS, "text");
2710+
group.classList.add("object-vector-studio-v2__drawing-hint");
2711+
group.dataset.drawingHintTool = drawing.tool;
2712+
group.setAttribute("pointer-events", "none");
2713+
text.classList.add("object-vector-studio-v2__drawing-hint-text");
2714+
text.setAttribute("x", this.formatViewportNumber(point.x + 12 * unitsPerPixel));
2715+
text.setAttribute("y", this.formatViewportNumber(point.y + 16 * unitsPerPixel));
2716+
text.textContent = "Double-click / Enter to complete";
2717+
group.append(text);
2718+
this.elements.renderSurface.append(group);
2719+
}
2720+
2721+
applyDrawingPreviewPresentation(element, previewShape) {
2722+
if (shapeGeometryTool(previewShape) === "text") {
2723+
element.style.fill = previewShape.style.stroke || "#ffffff";
2724+
element.style.fillOpacity = String(previewShape.style.strokeOpacity ?? 1);
2725+
element.style.stroke = "none";
2726+
element.style.strokeDasharray = "none";
2727+
element.style.strokeWidth = "0";
2728+
element.style.paintOrder = "normal";
2729+
return;
2730+
}
2731+
element.style.strokeDasharray = this.drawingPreviewDashArray(previewShape.style.strokeWidth);
2732+
element.style.strokeLinecap = "round";
2733+
element.style.strokeLinejoin = "round";
2734+
}
2735+
2736+
drawingPreviewDashArray(strokeWidth) {
2737+
const normalizedWidth = Number(strokeWidth);
2738+
const width = Number.isFinite(normalizedWidth) && normalizedWidth > 0 ? normalizedWidth : 2;
2739+
const unitsPerPixel = this.svgUnitsPerCssPixel();
2740+
const dashPixels = Math.max(12, Math.min(24, width * 1.1));
2741+
const gapPixels = Math.max(8, Math.min(18, width * 0.75));
2742+
return `${this.formatViewportNumber(dashPixels * unitsPerPixel)} ${this.formatViewportNumber(gapPixels * unitsPerPixel)}`;
2743+
}
2744+
2745+
svgUnitsPerCssPixel() {
2746+
const bounds = this.elements.renderSurface.getBoundingClientRect();
2747+
const viewBox = this.elements.renderSurface.viewBox?.baseVal;
2748+
if (!bounds.width || !viewBox?.width) {
2749+
return 1;
2750+
}
2751+
return viewBox.width / bounds.width;
2752+
}
2753+
26962754
visibleSnapPoints(object = this.selectedObject(), options = {}) {
26972755
if (!object) {
26982756
return [];
@@ -3460,6 +3518,9 @@ export class ToolStarterApp {
34603518
const point = this.snapCanvasPoint(this.pointerPreviewPoint(event));
34613519
this.drawingPreviewPoint = point;
34623520
if (drawing.flow === "points") {
3521+
if (!drawing.points.length) {
3522+
drawing.style = this.drawingStyleFromActivePalette();
3523+
}
34633524
drawing.points.push(point);
34643525
drawing.preview = point;
34653526
this.renderWorkSurface();
@@ -3468,6 +3529,7 @@ export class ToolStarterApp {
34683529
}
34693530
if (drawing.flow === "two-click") {
34703531
if (!drawing.start) {
3532+
drawing.style = this.drawingStyleFromActivePalette();
34713533
drawing.start = point;
34723534
drawing.preview = null;
34733535
this.renderWorkSurface();

tools/object-vector-studio-v2/styles/toolStarter.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,21 @@ textarea:hover {
14441444
pointer-events: none;
14451445
}
14461446

1447+
.object-vector-studio-v2__drawing-hint {
1448+
pointer-events: none;
1449+
}
1450+
1451+
.object-vector-studio-v2__drawing-hint-text {
1452+
fill: var(--tool-starter-text);
1453+
font-family: "0xProto Nerd Font", "Cascadia Mono", monospace;
1454+
font-size: 12px;
1455+
paint-order: stroke;
1456+
stroke: rgba(2, 6, 23, 0.95);
1457+
stroke-linejoin: round;
1458+
stroke-width: 4px;
1459+
vector-effect: non-scaling-stroke;
1460+
}
1461+
14471462
.object-vector-studio-v2__snap-targets {
14481463
pointer-events: none;
14491464
}

0 commit comments

Comments
 (0)