mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 23:19:14 +00:00
Icons scale with the plan again, and a 5 degree angle step
Two owner corrections after the infinite canvas. - --icon-size goes back to being a percentage of the PLAN: a marker grows and shrinks with the zoom, like everything else drawn on the plan. The infinite canvas had made it a percentage of the viewport (fixed pixel size) — the owner looked at it and asked for the original contract back. What survives from the canvas work is the NUMERATOR. The old expression divided by `vb.w`, the stored view_box, which is not a frame any more; a fixed NORM_W in its place would have shrunk every marker on a plan drawn past the old square by exactly the factor the plan is outsized (an invisible dot 50 canvases out). So it is now `iconCqw() = iconPct * iconUnit(space) * kioskScale / view.w`, one pure helper both renderers call. `iconUnit` is exactly NORM_W for any plan that fits the old square — and the editor has never written anything but `view_box: [0,0,1,1]` — so the rendered size is bit-identical to the pre-canvas card: measured against the v1.56.0 bundle at a fixed view, both give 3.400 / 3.091 / 6.182 / 12.364 cqw = 28.52 / 26.11 / 50.22 / 98.44 px. On a plan drawn at 1.5..3.8 the marker is 26.1 px, the same as on an ordinary plan, instead of the ~11 px a fixed numerator would have given. The static space-card uses the same helper: it has no zoom, but its frame is the content now, so a bare iconPct shrank its markers as the frame tightened. marker.size, the kiosk scales and every satellite still ride on --dev-size, untouched. - the icon angle in the device dialog steps by 5 degrees, not 10 (0..355): a marker often has to line up with a wall that is not on a 10-degree grid. Tests: three unit tests on iconCqw (the legacy expression reproduced digit for digit, the runaway plan, the no-view fallback); the infinite canvas smoke's "same pixel size at zoom 1/4/1/3" assert is turned back into "scales 4x / 1/3 with the zoom" plus a new one that the marker on the far plan measures the same as on an ordinary one; the angle step is pinned in smoke_size_angle_parity. docs/CANVAS.md §6 rewritten.
This commit is contained in:
+14
-9
@@ -51,7 +51,7 @@ import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import {
|
||||
fitInSquare, contentBounds, spaceModels, contentFrame, contentItems, spaceFrame,
|
||||
spaceCenter, iconUnit, gridLevels, itemOf,
|
||||
spaceCenter, iconUnit, iconCqw, gridLevels, itemOf,
|
||||
MIN_ZOOM, PAN_SLACK, CANVAS_LIMIT, type ContentItem, type Rect,
|
||||
} from './space-geometry';
|
||||
import { langOf, t, type I18nKey } from './i18n';
|
||||
@@ -5663,14 +5663,17 @@ class HouseplanCard extends LitElement {
|
||||
${this._renderOpenings(disp)}
|
||||
${this._markup && this._tool === 'resize' ? this._renderResizeLayer(view) : nothing}
|
||||
</svg>
|
||||
${''/* docs/CANVAS.md §6: --icon-size is a percentage of the VISIBLE
|
||||
viewport (cqw), no longer of the canvas — the old
|
||||
`* vb.w / view.w` made a marker grow with the zoom until it
|
||||
covered a room, and on an unbounded canvas "a percent of the
|
||||
canvas" has no meaning at all. Same expression as the static
|
||||
space-card, so the two renderers finally agree. The per-device
|
||||
${''/* docs/CANVAS.md §6: an icon is a percentage of the PLAN and
|
||||
scales with it when you zoom — the behaviour the card always
|
||||
had, restored by the owner. `iconCqw` is `iconPct * iconUnit
|
||||
/ view.w`: the old expression with the stored `vb.w` replaced
|
||||
by the plan's own base unit, which is the same NORM_W for an
|
||||
ordinary plan (pixel-identical) but grows with a plan drawn
|
||||
past the old square, where a fixed 1000 would have shrunk
|
||||
every marker to a dot. Same expression as the static
|
||||
space-card, so the two renderers agree. The per-device
|
||||
multiplier and the kiosk scales still feed --dev-size. */}
|
||||
<div class="devlayer" style="--icon-size:${(iconPct * (this._kiosk ? this._kioskScale.icon : 1)).toFixed(3)}cqw;--rl-font:${this._kiosk ? this._kioskScale.font : 1}">
|
||||
<div class="devlayer" style="--icon-size:${iconCqw(iconPct, space, view.w, this._kiosk ? this._kioskScale.icon : 1).toFixed(3)}cqw;--rl-font:${this._kiosk ? this._kioskScale.font : 1}">
|
||||
${devs.map((d) => this._renderDevice(d, view, showLqi, disp.fill === 'glow' && !this._markup))}
|
||||
${this._renderVacuums(devs, view)}
|
||||
${this._renderVacFit(view)}
|
||||
@@ -7553,7 +7556,9 @@ class HouseplanCard extends LitElement {
|
||||
${this._rangeInput(0.5, 3, 0.1, d.size, (n) => (this._markerDialog = { ...d, size: n }))}
|
||||
<span class="opv">×${d.size.toFixed(1)}</span>
|
||||
<span class="opl">${this._t('marker.angle_label')}</span>
|
||||
${this._rangeInput(0, 350, 10, d.angle, (n) => (this._markerDialog = { ...d, angle: n }))}
|
||||
${''/* 5 degrees, not 10 (owner 2026-08-03): a marker often has to
|
||||
line up with a wall that is not on a 10-degree grid. */}
|
||||
${this._rangeInput(0, 355, 5, d.angle, (n) => (this._markerDialog = { ...d, angle: n }))}
|
||||
<span class="opv">${d.angle}°</span>
|
||||
</div>
|
||||
|
||||
|
||||
+38
-4
@@ -281,10 +281,16 @@ export function spaceCenter(space: SpaceModel): Pt {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render units one icon-percent is measured against for AUTO-PLACEMENT
|
||||
* spacing (docs/CANVAS.md §6). For any plan that fits the old square this
|
||||
* is exactly NORM_W, so existing layouts do not move by a hair; a plan
|
||||
* three canvases wide gets proportionally wider spacing.
|
||||
* The BASE UNIT one icon-percent is measured against, in render units
|
||||
* (docs/CANVAS.md §6). It is both the icon's own footprint and the
|
||||
* auto-placement spacing, so the two can never drift apart.
|
||||
*
|
||||
* For any plan that fits the old square this is exactly NORM_W — which is
|
||||
* what `vb.w` was for every plan the card itself ever wrote (the editor only
|
||||
* ever stored `view_box: [0,0,1,1]`), so sizes and layouts are bit-identical
|
||||
* to the pre-infinite-canvas card. A plan drawn three canvases wide gets a
|
||||
* proportionally bigger unit, which is what keeps its markers from
|
||||
* degenerating into dots once the frame is the content (see `iconCqw`).
|
||||
* Rooms only — deterministic, so the full card and the static card agree.
|
||||
*/
|
||||
export function iconUnit(space: SpaceModel): number {
|
||||
@@ -295,6 +301,34 @@ export function iconUnit(space: SpaceModel): number {
|
||||
return Math.max(NORM_W, Math.min(SANE_LIMIT, Math.max(b.w, b.h)));
|
||||
}
|
||||
|
||||
/**
|
||||
* `--icon-size` in cqw (docs/CANVAS.md §6) — the ONE expression both
|
||||
* renderers use.
|
||||
*
|
||||
* An icon is a percentage of the PLAN, not of the viewport: it scales with
|
||||
* the plan as you zoom, exactly as it did before the infinite canvas (owner,
|
||||
* 2026-08-03). In render units the marker always occupies
|
||||
* `iconPct/100 * iconUnit`, whatever the frame or the zoom happens to be;
|
||||
* dividing by the width of the visible view turns that into a percentage of
|
||||
* the container, which is what `cqw` means.
|
||||
*
|
||||
* The only thing the infinite canvas changed here is the numerator: it used
|
||||
* to be `vb.w`, the stored view_box, and a plan drawn past the old square
|
||||
* kept a 1000-unit numerator while its frame grew to tens of thousands —
|
||||
* markers would shrink to invisible dots. `iconUnit` is that same 1000 for
|
||||
* an ordinary plan and grows with an outsized one.
|
||||
*/
|
||||
export function iconCqw(
|
||||
iconPct: number, space: SpaceModel, viewW: number, kioskIcon = 1,
|
||||
): number {
|
||||
const w = Number(viewW);
|
||||
const k = Number.isFinite(kioskIcon) && kioskIcon > 0 ? kioskIcon : 1;
|
||||
// No view yet (first paint, zero-width stage): fall back to the plain
|
||||
// percentage rather than to Infinity/NaN — the frame arrives a tick later.
|
||||
if (!Number.isFinite(w) || w <= 0) return iconPct * k;
|
||||
return (iconPct * iconUnit(space) * k) / w;
|
||||
}
|
||||
|
||||
/** Grid step multipliers offered to the adaptive grid (docs/CANVAS.md §7). */
|
||||
export const GRID_STEPS = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000];
|
||||
|
||||
|
||||
+8
-2
@@ -14,7 +14,7 @@ import { t, type Lang } from './i18n';
|
||||
import { bgModeOf, northDegOf, sunStateOf, dayPhase } from './sun';
|
||||
import type { ServerConfig } from './types';
|
||||
import {
|
||||
spaceModels, roomCenter, defaultPositions, markerPos, labelPos, spaceFrame, NORM_W,
|
||||
spaceModels, roomCenter, defaultPositions, markerPos, labelPos, spaceFrame, iconCqw, NORM_W,
|
||||
type Layout, type ContentItem,
|
||||
} from './space-geometry';
|
||||
|
||||
@@ -198,7 +198,13 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
: nothing}
|
||||
${roomShapes}
|
||||
</svg>
|
||||
<div class="devlayer" style="--icon-size:${iconPct}cqw">${markers}${labels}</div>
|
||||
${''/* docs/CANVAS.md §6: the same expression as the full card. The
|
||||
static card has no zoom, but its frame is the CONTENT now, so a
|
||||
bare `iconPct` would make markers shrink relative to the plan the
|
||||
tighter the frame got. `iconCqw` keeps the marker's footprint at
|
||||
iconPct% of the plan's base unit, which is what it was when the
|
||||
frame was the stored view_box. */}
|
||||
<div class="devlayer" style="--icon-size:${iconCqw(iconPct, space, vb[2]).toFixed(3)}cqw">${markers}${labels}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user