ui(resize): wall handles — half-size wall-with-arrows glyph, grab cursor
Validate / hacs (push) Failing after 10s
Validate / hassfest (push) Failing after 8s
Validate / frontend (push) Successful in 1m22s
Validate / backend (push) Failing after 4m37s
Validate / smoke (push) Failing after 6m17s

Owner request: in the resize tool make the wall-drag handles twice
smaller, replace the circle with a 'wall + two opposite arrows' icon,
drag cursor.

- visible glyph: wall segment with two arrows perpendicular to the
  edge (the drag directions), rotated per wall orientation; accent ink
  over a --hp-bg halo, readable on any plan
- HIT area unchanged: an invisible circle of the original finger-sized
  radius keeps touch targets and the HP-1550-04 hit priority over
  openings (04_handle_wins_hit_test still passes)
- cursor: grab on hover, grabbing while dragging (:active)
- scale-frame corner handles untouched (classic filled circles,
  nwse-resize)
- docs/RESIZE.md: handle appearance updated
This commit is contained in:
Matysh
2026-08-02 11:50:49 +03:00
parent 21786ed5a1
commit 3e23ff58c3
6 changed files with 138 additions and 36 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30 -9
View File
File diff suppressed because one or more lines are too long
+8 -3
View File
@@ -19,9 +19,14 @@ before — no handles, no new hit areas.
## Mechanism A — wall drag
- Every visible room shows a small handle at the midpoint of every
wall (handles for all rooms at once — owner's UX pick; they are
finger-sized but unobtrusive, radius derived from `view.w` like
`.vacfithandle`).
wall (handles for all rooms at once — owner's UX pick). The visible
glyph is a compact icon — a wall segment with two arrows pointing
perpendicular to it (the directions the wall drags), rotated to the
wall's orientation; accent ink over a `--hp-bg` halo. It is half the
size of the old circle, but the HIT area is an invisible circle of
the original finger-sized radius (derived from `view.w` like
`.vacfithandle`), so touch targets did not shrink. Cursor: `grab`,
`grabbing` while dragging.
- Dragging a handle moves the wall along its outward normal; **both
ends of the edge translate together** (the wall stays parallel to
itself; adjacent walls stretch/shrink). Works for any polygon
+15 -2
View File
@@ -2538,19 +2538,32 @@ class HouseplanCard extends LitElement {
/** Handles of the resize tool: wall midpoints + the scale frame of the selected room. */
private _renderResizeLayer(view: { x: number; y: number; w: number; h: number }): TemplateResult {
const hr = Math.max(view.w * 0.013, 5); // finger-sized on touch, like .vacfithandle
const hr = Math.max(view.w * 0.013, 5); // finger-sized HIT radius on touch, like .vacfithandle
// Wall-handle glyph: half the old circle — a wall segment with two arrows
// pointing perpendicular to it (the directions the wall can be dragged).
// Drawn in local coords (wall along X), rotated per edge at render time.
// The invisible circle above keeps the full finger-sized hit area and the
// HP-1550-04 hit-test priority over openings.
const s = hr / 2, f = (v: number) => v.toFixed(1);
const iconD =
`M ${f(-0.7 * s)} 0 H ${f(0.7 * s)}` +
` M 0 ${f(-0.22 * s)} V ${f(-s)} M ${f(-0.32 * s)} ${f(-0.6 * s)} L 0 ${f(-s)} L ${f(0.32 * s)} ${f(-0.6 * s)}` +
` M 0 ${f(0.22 * s)} V ${f(s)} M ${f(-0.32 * s)} ${f(0.6 * s)} L 0 ${f(s)} L ${f(0.32 * s)} ${f(0.6 * s)}`;
const parts: TemplateResult[] = [];
const rooms = this._rszRooms();
for (const r of rooms) {
for (let i = 0; i < r.poly.length; i++) {
const a = r.poly[i], b = r.poly[(i + 1) % r.poly.length];
if (Math.hypot(b[0] - a[0], b[1] - a[1]) < this._gridPitch) continue;
parts.push(svg`<circle class="rszhandle" cx="${((a[0] + b[0]) / 2).toFixed(1)}" cy="${((a[1] + b[1]) / 2).toFixed(1)}" r="${hr.toFixed(1)}"
const mx = f((a[0] + b[0]) / 2), my = f((a[1] + b[1]) / 2);
const ang = f(Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI);
parts.push(svg`<circle class="rszhandle" cx="${mx}" cy="${my}" r="${f(hr)}"
@pointerdown=${(e: PointerEvent) => this._rszEdgeDown(e, r.id, i)}
@pointermove=${(e: PointerEvent) => this._rszMove(e)}
@pointerup=${(e: PointerEvent) => this._rszUp(e)}
@pointercancel=${(e: PointerEvent) => this._rszPointerCancel(e)}
@lostpointercapture=${(e: PointerEvent) => this._rszPointerCancel(e)}></circle>`);
parts.push(svg`<g class="rszicon" transform="translate(${mx} ${my}) rotate(${ang})"><path class="rszhalo" d="${iconD}"></path><path class="rszink" d="${iconD}"></path></g>`);
}
}
const sel = this._rszSel ? rooms.find((r) => r.id === this._rszSel) : null;
+25 -4
View File
@@ -1450,16 +1450,37 @@ export const cardStyles = css`
user-select: none;
}
/* room resize tool (docs/RESIZE.md) */
/* wall handle: invisible finger-sized hit circle (HP-1550-04 hit priority
kept), the visible glyph lives in the sibling .rszicon */
.rszhandle {
fill: transparent;
stroke: none;
pointer-events: all;
cursor: grab;
touch-action: none;
}
.rszhandle:active { cursor: grabbing; }
/* wall-with-arrows glyph: accent ink over a bg halo, readable on any plan */
.rszicon { pointer-events: none; }
.rszicon path {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
vector-effect: non-scaling-stroke;
}
.rszhalo { stroke: var(--hp-bg); stroke-width: 6; }
.rszink { stroke: var(--hp-accent); stroke-width: 2; }
.rszhandle:hover + .rszicon .rszink { stroke-width: 3; }
/* corner (scale-frame) handles keep the classic filled circle */
.rszcorner {
fill: var(--hp-bg);
stroke: var(--hp-accent);
stroke-width: 2;
vector-effect: non-scaling-stroke;
cursor: move;
touch-action: none;
cursor: nwse-resize;
}
.rszhandle:hover { fill: var(--hp-accent); }
.rszcorner { cursor: nwse-resize; }
.rszcorner:hover { fill: var(--hp-accent); }
.rszcorner:active { cursor: nwse-resize; }
.rszframe {
fill: none;
stroke: var(--hp-accent);