mirror of
https://github.com/Matysh/houseplan-card
synced 2026-08-01 00:48:29 +00:00
room settings button: the VISUAL centre for L-shaped rooms
The owner's kitchen-living room is L-shaped, and interiorPoint() only promises 'somewhere inside' — the button sat near the seam, visibly off-centre. poleOfInaccessibility() (largest inscribed circle, grid search plus one refinement pass) puts it in the middle of the widest open space: the slab of an L, the exact centre of a rectangle or square. Cached per poly array in a WeakMap — the memoized model keeps the arrays stable, so the search runs once per geometry, not per render. Unit: square -> centre; thick-slab L -> mid-slab, always inside.
This commit is contained in:
+13
-4
@@ -15,7 +15,7 @@ import {
|
||||
lqiColor, snapToGrid, samePoint, pointInPolygon, markerIdForBinding,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, pointStrictlyInside, roomsOverlap,
|
||||
pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, openZoneOf, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, contentUrl,
|
||||
snapToWall, openingAmount, interiorPoint,
|
||||
snapToWall, openingAmount, interiorPoint, poleOfInaccessibility,
|
||||
averageLqi, fitView, declump, safeUrl, resolveTapAction, floorsOf, type FloorInfo,
|
||||
stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices, glowColorOf, doorSector, hasRoomBehind, controlsAction, isControllable,
|
||||
spaceDisplayOf, roomFillStyle, fillColorsOf, DEFAULT_FILL_COLORS, type FillColors,
|
||||
@@ -4505,13 +4505,22 @@ class HouseplanCard extends LitElement {
|
||||
/** The room-settings button: detached from the (movable) label, always at
|
||||
* the geometric CENTRE of the room, sized at 70% of a device icon and
|
||||
* therefore zooming with the plan (owner's spec, 2026-07-29). */
|
||||
private _gearPtCache = new WeakMap<number[][], number[]>();
|
||||
|
||||
private _renderRoomGear(
|
||||
r: RoomCfg, space: SpaceModel, view: { x: number; y: number; w: number; h: number },
|
||||
): TemplateResult | typeof nothing {
|
||||
if (!r.id) return nothing;
|
||||
const c = r.poly
|
||||
? (interiorPoint(r.poly) || [r.poly[0][0], r.poly[0][1]])
|
||||
: r.x != null && r.y != null ? [r.x + (r.w || 0) / 2, r.y + (r.h || 0) / 2] : null;
|
||||
let c: number[] | null = null;
|
||||
if (r.poly) {
|
||||
// the VISUAL centre (largest inscribed circle) — interiorPoint only
|
||||
// promises "inside", which sat visibly off-centre on an L-shaped room.
|
||||
// The model is memoized, so the poly array is a stable cache key.
|
||||
c = this._gearPtCache.get(r.poly) || null;
|
||||
if (!c) { c = poleOfInaccessibility(r.poly); this._gearPtCache.set(r.poly, c); }
|
||||
} else if (r.x != null && r.y != null) {
|
||||
c = [r.x + (r.w || 0) / 2, r.y + (r.h || 0) / 2];
|
||||
}
|
||||
if (!c) return nothing;
|
||||
const left = ((c[0] - view.x) / view.w) * 100;
|
||||
const top = ((c[1] - view.y) / view.h) * 100;
|
||||
|
||||
Reference in New Issue
Block a user