Skip to content

Commit 98fe9d4

Browse files
author
DavidQ
committed
Fix rectangle square rounding add rounding radius and move picker into Palette - PR_26133_085-rounding-radius-rectangle-square-and-palette-picker-placement
1 parent a74c89d commit 98fe9d4

8 files changed

Lines changed: 317 additions & 40 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

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

3-
PR: PR_26133_084-fix-actual-middle-rounding-and-snap-angle-rotate-ui
3+
PR: PR_26133_085-rounding-radius-rectangle-square-and-palette-picker-placement
44

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

@@ -21,10 +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 7294/7294; executed functions 731/769
24+
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 7391/7391; executed functions 738/776
2525

2626
## Changed JS Files Considered
2727

28-
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - Object Vector Studio V2 runtime covered by browser V8 coverage while rounded point rendering and Snap Angle dropdown behavior were exercised.
28+
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - Object Vector Studio V2 runtime covered by browser V8 coverage while radius-based rectangle/square rounding, Snap Angle UI hiding, and Palette Picker sampling were exercised.
2929
- (0%) tools/object-vector-studio-v2/styles/toolStarter.css - CSS layout file, not collected as browser JS coverage.
3030
- (0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - Playwright test file, not collected as browser runtime coverage.

docs/dev/reports/playwright_workspace_v2_results.md

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

3-
PR: PR_26133_084-fix-actual-middle-rounding-and-snap-angle-rotate-ui
3+
PR: PR_26133_085-rounding-radius-rectangle-square-and-palette-picker-placement
44

55
## Validation
66

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

1313
## Targeted Checks Covered
1414

15-
- Shape Geometry point rows still render exactly one Round checkbox per point row.
16-
- Row-local plus buttons still insert a copied point directly after the current row.
17-
- Row-local trash buttons still delete only their row and reject invalid minimum-count deletion.
18-
- Polygon and polyline middle/interior rounding now verifies actual rendered SVG geometry changes to a path with quadratic `Q` corner curves.
19-
- Checking two middle/interior points, then unchecking one, keeps the other rendered as the only rounded joint.
20-
- Start/end rounding still passes through the existing arc endpoint coverage.
21-
- Snap Angle enabled disables the free numeric Rotate textbox and enables the Rotate dropdown plus Step selector.
22-
- Snap Angle disabled re-enables the numeric Rotate textbox and disables the constrained dropdown controls.
23-
- Default 15 degree dropdown values and 45 degree Step-generated dropdown values were verified.
15+
- Rectangle point rounding now renders through a rounded SVG path and updates when Rounding Radius changes.
16+
- Square point rounding now renders through the same rounded rectangle path behavior while preserving equal width and height.
17+
- Invalid negative Rounding Radius values are visibly rejected and do not mutate the selected shape style.
18+
- Existing arc endpoint rounding and polygon/polyline/triangle point rounding coverage remained green.
19+
- Snap Angle disabled shows the Rotate numeric textbox while the dropdown and Step selector are hidden.
20+
- Snap Angle enabled hides the Rotate numeric textbox while showing the constrained dropdown and Step selector.
21+
- Palette primary row now contains Paint, Stroke, Width, and an icon-only Picker button to the right of Stroke controls.
22+
- Picker behavior still samples fill, stroke, opacities, and stroke width from a clicked shape without recoloring it.
2423

2524
## Console/Runtime Errors
2625

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 129 additions & 16 deletions
Large diffs are not rendered by default.

tools/object-vector-studio-v2/index.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
185185
<span>Width</span>
186186
<input id="objectVectorStudioV2StrokeWidth" type="number" min="0.1" step="0.1" value="2">
187187
</label>
188+
<button id="objectVectorStudioV2PalettePickerButton" class="object-vector-studio-v2__palette-picker-button" type="button" aria-pressed="false" data-shape-tool="picker" title="Sample a shape style into Palette controls" aria-label="Picker">
189+
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--picker" aria-hidden="true"></span>
190+
</button>
188191
</div>
189192
<div class="object-vector-studio-v2__palette-opacity-row">
190193
<span class="object-vector-studio-v2__palette-opacity-heading">Opacity</span>
@@ -237,10 +240,6 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
237240
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--line" aria-hidden="true"></span>
238241
<span class="object-vector-studio-v2__tool-label">Line</span>
239242
</button>
240-
<button class="object-vector-studio-v2__tool-toggle" type="button" aria-pressed="false" data-shape-tool="picker" title="Sample a shape style into the palette controls">
241-
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--picker" aria-hidden="true"></span>
242-
<span class="object-vector-studio-v2__tool-label">Picker</span>
243-
</button>
244243
<button class="object-vector-studio-v2__tool-toggle" type="button" aria-pressed="false" data-shape-tool="polygon" title="Create a polygon shape on the selected object. Click to add points.&#10;&#10;Double-click to finish.">
245244
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--polygon" aria-hidden="true"></span>
246245
<span class="object-vector-studio-v2__tool-label">Polygon</span>

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

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,18 @@ export class ToolStarterApp {
874874
return !["circle", "ellipse", "text"].includes(geometryTool) && this.shapeGeometryPointCount(shape) > 0;
875875
}
876876

877+
shapeSupportsRoundingRadiusControl(shape) {
878+
return ["polygon", "polyline", "rectangle"].includes(shapeGeometryTool(shape)) && this.shapeGeometryPointCount(shape) > 0;
879+
}
880+
881+
shapeRoundingRadius(shape) {
882+
const value = Number(shape?.style?.roundingRadius);
883+
if (Number.isFinite(value) && value >= 0) {
884+
return value;
885+
}
886+
return 4;
887+
}
888+
877889
shapeUnifiedPointStyle(shape) {
878890
const pointStyles = this.shapePointStyleValues(shape);
879891
if (pointStyles.length) {
@@ -1097,17 +1109,26 @@ export class ToolStarterApp {
10971109
const numericInput = root.querySelector?.("#objectVectorStudioV2RotateInput") || this.window.document.getElementById("objectVectorStudioV2RotateInput");
10981110
const snapSelect = root.querySelector?.("#objectVectorStudioV2RotateSnapSelect") || this.window.document.getElementById("objectVectorStudioV2RotateSnapSelect");
10991111
const stepSelect = root.querySelector?.("#objectVectorStudioV2SnapAngleStepSelect") || this.window.document.getElementById("objectVectorStudioV2SnapAngleStepSelect");
1112+
const row = (numericInput || snapSelect || stepSelect)?.closest?.(".object-vector-studio-v2__transform-control-row--rotate") || null;
1113+
row?.classList.toggle("is-angle-snap-enabled", this.angleSnapEnabled);
11001114
if (stepSelect) {
11011115
stepSelect.value = String(this.angleSnapStep);
11021116
stepSelect.disabled = !this.angleSnapEnabled;
1117+
stepSelect.hidden = !this.angleSnapEnabled;
1118+
const stepField = stepSelect.closest(".object-vector-studio-v2__snap-angle-step-field");
1119+
if (stepField) {
1120+
stepField.hidden = !this.angleSnapEnabled;
1121+
}
11031122
}
11041123
if (snapSelect) {
11051124
const preferred = snapSelect.value || numericInput?.value || this.transformInputValue(snapSelect.id, "15");
11061125
this.populateRotateSnapSelect(snapSelect, this.angleSnapStep, preferred);
11071126
snapSelect.disabled = !this.angleSnapEnabled;
1127+
snapSelect.hidden = !this.angleSnapEnabled;
11081128
}
11091129
if (numericInput) {
11101130
numericInput.disabled = this.angleSnapEnabled;
1131+
numericInput.hidden = this.angleSnapEnabled;
11111132
}
11121133
}
11131134

@@ -2317,13 +2338,82 @@ export class ToolStarterApp {
23172338
});
23182339
}
23192340
section.append(heading);
2341+
const roundingRadiusControl = this.createRoundingRadiusControl(shape);
2342+
if (roundingRadiusControl) {
2343+
section.append(roundingRadiusControl);
2344+
}
23202345
section.append(grid);
23212346
if (geometryTool !== "polygon" && geometryTool !== "polyline" && this.shapeSupportsPointRoundingControls(shape)) {
23222347
section.append(this.createShapePointRoundingControls(shape));
23232348
}
23242349
return section;
23252350
}
23262351

2352+
createRoundingRadiusControl(shape) {
2353+
if (!this.shapeSupportsRoundingRadiusControl(shape)) {
2354+
return null;
2355+
}
2356+
const label = document.createElement("label");
2357+
label.className = "object-vector-studio-v2__rounding-radius-field";
2358+
const caption = document.createElement("span");
2359+
caption.textContent = "Rounding Radius";
2360+
const input = document.createElement("input");
2361+
input.type = "number";
2362+
input.min = "0";
2363+
input.step = "0.1";
2364+
input.value = String(this.shapeRoundingRadius(shape));
2365+
input.dataset.shapeRoundingRadius = "true";
2366+
input.setAttribute("aria-label", "Rounding radius");
2367+
input.addEventListener("input", () => this.clearInputValidity(input));
2368+
input.addEventListener("change", () => {
2369+
const shapeIndex = this.selectedShapeIndex;
2370+
this.window.setTimeout(() => {
2371+
if (this.selectedShapeIndex !== shapeIndex) {
2372+
return;
2373+
}
2374+
this.updateSelectedShapeRoundingRadius(input);
2375+
}, 0);
2376+
});
2377+
label.append(caption, input);
2378+
return label;
2379+
}
2380+
2381+
updateSelectedShapeRoundingRadius(input) {
2382+
const selected = this.selectedShape();
2383+
if (!selected) {
2384+
this.statusLog.write("WARN Rounding radius update skipped: no shape is selected.");
2385+
return;
2386+
}
2387+
if (!this.shapeSupportsPointRoundingControls(selected)) {
2388+
this.statusLog.write(`WARN Rounding radius update skipped: ${shapeTypeLabel(selected)} does not use rounded points.`);
2389+
return;
2390+
}
2391+
const rawValue = String(input?.value ?? "").trim();
2392+
const value = Number(rawValue);
2393+
if (rawValue === "" || !Number.isFinite(value) || value < 0) {
2394+
const error = "Rounding Radius must be a finite number greater than or equal to 0.";
2395+
this.markInputInvalid(input, error);
2396+
this.statusLog.write(`FAIL Invalid rounding radius rejected for shape row ${this.selectedShapeIndex}: ${error}`);
2397+
return;
2398+
}
2399+
if (this.guardSelectedObjectMutation("Rounding radius update")) {
2400+
return;
2401+
}
2402+
this.clearInputValidity(input);
2403+
const roundedValue = Number(value.toFixed(3));
2404+
const nextPayload = this.cloneCurrentPayload();
2405+
const nextShape = this.findShapeInPayload(nextPayload, this.selectedShapeIndex);
2406+
if (!nextShape) {
2407+
this.statusLog.write(`WARN Rounding radius update skipped: selected shape row ${this.selectedShapeIndex} was not found.`);
2408+
return;
2409+
}
2410+
nextShape.style = {
2411+
...nextShape.style,
2412+
roundingRadius: roundedValue
2413+
};
2414+
this.commitPayloadUpdate(nextPayload, this.selectedObjectId, this.selectedShapeIndex, `OK Updated rounding radius to ${roundedValue} for shape row ${this.selectedShapeIndex}.`, "Rounding radius update failed schema validation");
2415+
}
2416+
23272417
updateSelectedShapePointRounding(pointIndex, isRounded) {
23282418
const selected = this.selectedShape();
23292419
if (!selected) {
@@ -3256,7 +3346,13 @@ export class ToolStarterApp {
32563346
createSvgShape(shape, { drawingScale = 1 } = {}) {
32573347
const geometryTool = shapeGeometryTool(shape);
32583348
if (geometryTool === "rectangle") {
3259-
const element = document.createElementNS(SVG_NS, "rect");
3349+
const roundedPath = this.roundedPointPath(shape, { closed: true, drawingScale });
3350+
const element = document.createElementNS(SVG_NS, roundedPath ? "path" : "rect");
3351+
if (roundedPath) {
3352+
element.setAttribute("d", roundedPath);
3353+
element.dataset.roundedPointRender = "path";
3354+
element.setAttribute("points", this.svgPointList(this.shapeGeometryPoints(shape), drawingScale));
3355+
}
32603356
element.setAttribute("x", this.scaleDrawingValue(shape.geometry.x, drawingScale));
32613357
element.setAttribute("y", this.scaleDrawingValue(shape.geometry.y, drawingScale));
32623358
element.setAttribute("width", this.scaleDrawingValue(shape.geometry.width, drawingScale));
@@ -3343,10 +3439,13 @@ export class ToolStarterApp {
33433439

33443440
roundedPointPath(shape, { closed, drawingScale = 1 } = {}) {
33453441
const geometryTool = shapeGeometryTool(shape);
3346-
if (!["polygon", "polyline"].includes(geometryTool) || !Array.isArray(shape.geometry?.points)) {
3442+
if (!["polygon", "polyline", "rectangle"].includes(geometryTool)) {
3443+
return "";
3444+
}
3445+
const sourcePoints = geometryTool === "rectangle" ? this.shapeGeometryPoints(shape) : shape.geometry.points;
3446+
if (!Array.isArray(sourcePoints)) {
33473447
return "";
33483448
}
3349-
const sourcePoints = shape.geometry.points;
33503449
const pointCount = sourcePoints.length;
33513450
if (pointCount < (closed ? 3 : 3)) {
33523451
return "";
@@ -3362,8 +3461,10 @@ export class ToolStarterApp {
33623461
x: Number(this.scaleDrawingValue(point.x, drawingScale)),
33633462
y: Number(this.scaleDrawingValue(point.y, drawingScale))
33643463
}));
3365-
const strokeWidth = Math.max(1, Number(shape.style?.strokeWidth) || 1);
3366-
const preferredRadius = Math.max(2, strokeWidth * 2) * drawingScale;
3464+
const preferredRadius = this.shapeRoundingRadius(shape) * drawingScale;
3465+
if (preferredRadius <= 0) {
3466+
return "";
3467+
}
33673468
const roundedVertex = (index) => {
33683469
if (pointRounding[index] !== true || (!closed && (index === 0 || index === pointCount - 1))) {
33693470
return null;

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,18 @@ textarea:hover {
13131313
padding: 3px 5px !important;
13141314
}
13151315

1316+
.object-vector-studio-v2__palette-picker-button {
1317+
width: 30px;
1318+
min-width: 30px;
1319+
height: 30px;
1320+
padding: 0 !important;
1321+
}
1322+
1323+
.object-vector-studio-v2__palette-picker-button .object-vector-studio-v2__shape-icon {
1324+
width: 20px;
1325+
height: 20px;
1326+
}
1327+
13161328
.object-vector-studio-v2__palette-primary-row,
13171329
.object-vector-studio-v2__palette-opacity-row {
13181330
display: flex;
@@ -1734,6 +1746,25 @@ textarea:hover {
17341746
padding: 5px 7px;
17351747
}
17361748

1749+
.object-vector-studio-v2__rounding-radius-field {
1750+
display: grid;
1751+
grid-template-columns: max-content minmax(0, 74px);
1752+
align-items: center;
1753+
justify-content: start;
1754+
gap: 6px;
1755+
width: 100%;
1756+
color: var(--tool-starter-muted);
1757+
font-size: 0.74rem;
1758+
font-weight: 800;
1759+
}
1760+
1761+
.object-vector-studio-v2__rounding-radius-field input {
1762+
min-width: 0;
1763+
width: 100%;
1764+
min-height: 22px;
1765+
padding: 2px 5px;
1766+
}
1767+
17371768
.object-vector-studio-v2__polygon-point-axis--readonly {
17381769
grid-template-columns: auto minmax(0, 1fr);
17391770
}
@@ -1817,7 +1848,31 @@ textarea:hover {
18171848
}
18181849

18191850
.object-vector-studio-v2__transform-control-row--rotate {
1820-
grid-template-columns: 50px minmax(42px, 1fr) minmax(54px, 1fr) minmax(54px, max-content) max-content;
1851+
grid-template-columns: 50px minmax(42px, 1fr) max-content;
1852+
}
1853+
1854+
.object-vector-studio-v2__transform-control-row--rotate.is-angle-snap-enabled {
1855+
grid-template-columns: 50px minmax(54px, 1fr) minmax(54px, max-content) max-content;
1856+
}
1857+
1858+
.object-vector-studio-v2__transform-control-row--rotate #objectVectorStudioV2RotateInput,
1859+
.object-vector-studio-v2__transform-control-row--rotate #objectVectorStudioV2RotateSnapSelect {
1860+
grid-column: 2;
1861+
grid-row: 1;
1862+
}
1863+
1864+
.object-vector-studio-v2__transform-control-row--rotate .object-vector-studio-v2__snap-angle-step-field {
1865+
grid-column: 3;
1866+
grid-row: 1;
1867+
}
1868+
1869+
.object-vector-studio-v2__transform-control-row--rotate #objectVectorStudioV2RotateShapeButton {
1870+
grid-column: 3;
1871+
grid-row: 1;
1872+
}
1873+
1874+
.object-vector-studio-v2__transform-control-row--rotate.is-angle-snap-enabled #objectVectorStudioV2RotateShapeButton {
1875+
grid-column: 4;
18211876
}
18221877

18231878
.object-vector-studio-v2__transform-control-label {

tools/schemas/game.manifest.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@
448448
"fill": "#ffffff",
449449
"stroke": "#ffffff",
450450
"strokeWidth": 3,
451+
"roundingRadius": 4,
451452
"pointStyle": "square",
452453
"startPointStyle": "square",
453454
"endPointStyle": "square",
@@ -469,6 +470,10 @@
469470
"type": "string",
470471
"enum": ["round", "square"]
471472
},
473+
"roundingRadius": {
474+
"type": "number",
475+
"minimum": 0
476+
},
472477
"pointStyle": {
473478
"type": "string",
474479
"enum": ["round", "square"]

tools/schemas/tools/object-vector-studio-v2.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
"fill": "#ffffff",
188188
"stroke": "#ffffff",
189189
"strokeWidth": 3,
190+
"roundingRadius": 4,
190191
"pointStyle": "square",
191192
"startPointStyle": "square",
192193
"endPointStyle": "square",
@@ -444,6 +445,10 @@
444445
"type": "string",
445446
"enum": ["round", "square"]
446447
},
448+
"roundingRadius": {
449+
"type": "number",
450+
"minimum": 0
451+
},
447452
"pointStyle": {
448453
"type": "string",
449454
"enum": ["round", "square"]

0 commit comments

Comments
 (0)