Hover mini-map marker in box coordinates

This commit is contained in:
2026-08-21 10:20:38 -04:00
parent d26355cebb
commit fdf3c51cb0

View File

@@ -97,11 +97,15 @@ const blueprintUrl = computed(() => {
// the wrong level's dimensions is precisely how a marker ends up plausibly // the wrong level's dimensions is precisely how a marker ends up plausibly
// placed and wrong. // placed and wrong.
const markerX = computed(() => { const markerX = computed(() => {
return (props.left / dimensionsFor(props.levelid).width) * 100 const fraction = props.left / dimensionsFor(props.levelid).width
const { offsetX, scaleX } = paintedBox.value
return (offsetX + fraction * scaleX) * 100
}) })
const markerY = computed(() => { const markerY = computed(() => {
return (props.top / dimensionsFor(props.levelid).height) * 100 const fraction = props.top / dimensionsFor(props.levelid).height
const { offsetY, scaleY } = paintedBox.value
return (offsetY + fraction * scaleY) * 100
}) })
// Marker style with counter-scale to maintain constant size // Marker style with counter-scale to maintain constant size
@@ -120,9 +124,33 @@ const markerStyle = computed(() => ({
// lands in the grey gutter beside the map. Levels near 390:300 looked correct, // lands in the grey gutter beside the map. Levels near 390:300 looked correct,
// which is why it only affected some assets. // which is why it only affected some assets.
// //
// The fix is to let the IMAGE size the frame: the frame shrink-wraps the drawing, // The preview is a fixed box and object-fit: contain paints the drawing inside it
// so a percentage of the frame IS a percentage of the drawing, whatever the // at the drawing's own aspect ratio, so the drawing does not fill the box: it is
// level's aspect ratio. The letterboxing moves out to the preview around it. // letterboxed on one axis. A marker placed at a percentage of the BOX therefore
// lands somewhere else than the same percentage of the DRAWING, on any level
// whose shape is not the box's.
//
// So convert: work out where the painted drawing sits inside the box, then map
// the marker into box coordinates. Doing it here rather than by resizing the
// frame keeps the centring transform below honest - its percentages resolve
// against the element, so the element has to stay the size of the box.
const PREVIEW_WIDTH = 390 // keep in step with .map-preview in the styles
const PREVIEW_HEIGHT = 300
const PREVIEW_ASPECT = PREVIEW_WIDTH / PREVIEW_HEIGHT
// Fraction of the box the drawing covers, and the gap before it, per axis.
const paintedBox = computed(() => {
const { width, height } = dimensionsFor(props.levelid)
const levelAspect = width / height
if (levelAspect >= PREVIEW_ASPECT) {
// Wider than the box: full width, bars above and below.
const scale = PREVIEW_ASPECT / levelAspect
return { scaleX: 1, offsetX: 0, scaleY: scale, offsetY: (1 - scale) / 2 }
}
// Taller than the box: full height, bars left and right.
const scale = levelAspect / PREVIEW_ASPECT
return { scaleX: scale, offsetX: (1 - scale) / 2, scaleY: 1, offsetY: 0 }
})
// Transform style that centers on the marker and zooms toward it // Transform style that centers on the marker and zooms toward it
const transformStyle = computed(() => { const transformStyle = computed(() => {
// Calculate translation to center the marker in the preview // Calculate translation to center the marker in the preview
@@ -265,12 +293,6 @@ watch(currentTheme, () => {
.map-preview { .map-preview {
position: relative; position: relative;
/* Centres the aspect-fitted frame, which is where the letterboxing now
happens. The frame carries the drawing AND the marker, so the two can no
longer disagree. */
display: flex;
align-items: center;
justify-content: center;
/* Smaller than it was: this is a glance-and-move-on preview, and at 500px it /* Smaller than it was: this is a glance-and-move-on preview, and at 500px it
covered the row it was launched from. Aspect ratio kept. */ covered the row it was launched from. Aspect ratio kept. */
width: 390px; width: 390px;
@@ -281,23 +303,21 @@ watch(currentTheme, () => {
.map-transform { .map-transform {
position: relative; position: relative;
/* Shrink-wraps the image rather than filling the preview, so the box the /* Fills the preview. The centring transform translates by percentages, which
marker is positioned in is the box the drawing is painted in. */ resolve against THIS element, so it must stay the size of the box it is
max-width: 100%; centring within. The letterbox is accounted for in the marker maths. */
max-height: 100%; width: 100%;
height: 100%;
transition: transform 0.15s ease-out; transition: transform 0.15s ease-out;
} }
.map-image { .map-image {
/* Sizes itself from the drawing, scaled down to fit the preview. NOT width: 100%;
width/height 100% with object-fit: contain - that painted the drawing height: 100%;
letterboxed inside a box the marker was measured against, which put the two /* contain, so a level of any shape is shown whole and undistorted. Where the
in different coordinate spaces on any level that is not 390:300. */ bars fall is computed in paintedBox and folded into the marker position. */
object-fit: contain;
display: block; display: block;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
} }
.marker-dot { .marker-dot {