From 3e95cbfc28fa0ccfbc6383fa3fb5301188013647 Mon Sep 17 00:00:00 2001 From: Boof2015 <75185879+Boof2015@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:06:19 -0400 Subject: [PATCH] centering --- src/renderer/visualizers/VUMeter.ts | 7 ++-- src/renderer/visualizers/vectorscopeGrids.ts | 14 +++++--- test/renderer-helpers.test.ts | 38 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/renderer/visualizers/VUMeter.ts b/src/renderer/visualizers/VUMeter.ts index 693bfbd..899f166 100644 --- a/src/renderer/visualizers/VUMeter.ts +++ b/src/renderer/visualizers/VUMeter.ts @@ -151,13 +151,16 @@ export function resolveVUNeedleFaceLayout( const targetHeight = VU_NEEDLE_FACE_HEIGHT_CSS_PX * dpr const safeWidth = Math.max(0, Number.isFinite(canvasWidth) ? canvasWidth : 0) const safeHeight = Math.max(0, Number.isFinite(canvasHeight) ? canvasHeight : 0) - const scale = Math.min(1, safeWidth / targetWidth, safeHeight / targetHeight) + const widthScale = safeWidth / targetWidth + const heightScale = safeHeight / targetHeight + const scale = Math.min(1, widthScale, heightScale) const width = targetWidth * scale const height = targetHeight * scale + const widthLimited = widthScale < Math.min(1, heightScale) return { x: Math.max(0, (safeWidth - width) / 2), - y: Math.max(0, safeHeight - height), + y: Math.max(0, widthLimited ? (safeHeight - height) / 2 : safeHeight - height), width, height, scale, diff --git a/src/renderer/visualizers/vectorscopeGrids.ts b/src/renderer/visualizers/vectorscopeGrids.ts index c17fa8b..3318b5d 100644 --- a/src/renderer/visualizers/vectorscopeGrids.ts +++ b/src/renderer/visualizers/vectorscopeGrids.ts @@ -14,8 +14,8 @@ export interface VectorscopeLayout { /** * Compute the center point and radius for a vectorscope mode. * - * Unipolar modes place the center at the bottom of the canvas so the - * semicircle/triangle fills the full vertical space. + * Unipolar modes place the center at the bottom when height-limited. When + * width-limited, the visible semicircle/triangle is centered vertically. * Bipolar and Lissajous center in the canvas. */ export function getVectorscopeLayout( @@ -27,10 +27,14 @@ export function getVectorscopeLayout( const isUnipolar = mode === 'polar-unipolar' || mode === 'linear-unipolar' if (isUnipolar) { - // Center near the bottom; radius fills upward const margin = height * 0.04 - const centerY = height - margin - const radius = Math.min(width / 2, height - margin) * 0.88 + const availableHeight = height - margin + const halfWidth = width / 2 + const widthLimited = halfWidth < availableHeight + const radius = Math.min(halfWidth, availableHeight) * 0.88 + const centerY = widthLimited + ? (height + radius) / 2 + : availableHeight return { centerX, centerY, radius } } diff --git a/test/renderer-helpers.test.ts b/test/renderer-helpers.test.ts index 6a0236d..3fcc667 100644 --- a/test/renderer-helpers.test.ts +++ b/test/renderer-helpers.test.ts @@ -3385,6 +3385,33 @@ test('Vectorscope uses the base layout radius for projection scale', () => { } }) +test('Vectorscope centers width-constrained unipolar artwork vertically', () => { + for (const mode of ['polar-unipolar', 'linear-unipolar'] as const) { + const layout = getVectorscopeLayout(120, 300, mode) + const artworkTop = layout.centerY - layout.radius + const artworkBottom = layout.centerY + + assertAlmostEqual( + (artworkTop + artworkBottom) / 2, + 150, + 1e-12, + `${mode} artwork should center in a narrow canvas`, + ) + } +}) + +test('Vectorscope preserves height-limited and bipolar layout behavior', () => { + const wideUnipolar = getVectorscopeLayout(600, 200, 'polar-unipolar') + assertAlmostEqual(wideUnipolar.centerY, 192, 1e-12, 'wide unipolar layout should remain bottom-aligned') + assertAlmostEqual(wideUnipolar.radius, 192 * 0.88, 1e-12, 'wide unipolar radius should remain height-limited') + + for (const mode of ['lissajous', 'polar-bipolar', 'linear-bipolar'] as const) { + const layout = getVectorscopeLayout(120, 300, mode) + assertAlmostEqual(layout.centerY, 150, 1e-12, `${mode} should remain centered`) + assertAlmostEqual(layout.radius, 54, 1e-12, `${mode} radius should remain unchanged`) + } +}) + test('vectorscope adds a subtle dashed outer boundary without changing the base graph', () => { const lissajousRecorder = createFakeCanvasRecorder() drawVectorscopeGridForMode( @@ -3566,12 +3593,23 @@ test('VUMeter needle face layout stays fixed instead of scaling up', () => { const retinaWide = resolveVUNeedleFaceLayout(2400, 1000, 2) assert.equal(retinaWide.width, VU_NEEDLE_FACE_WIDTH_CSS_PX * 2) assert.equal(retinaWide.height, VU_NEEDLE_FACE_HEIGHT_CSS_PX * 2) + assert.equal(retinaWide.y, 1000 - VU_NEEDLE_FACE_HEIGHT_CSS_PX * 2) assert.equal(retinaWide.scale, 1) const small = resolveVUNeedleFaceLayout(280, 180) assert.equal(small.width, 280) assert.equal(small.height, 180) assertAlmostEqual(small.scale, 0.5, 1e-12, 'fixed face should only shrink when the canvas is smaller') + + const narrow = resolveVUNeedleFaceLayout(280, 360) + assert.equal(narrow.width, 280) + assert.equal(narrow.height, 180) + assert.equal(narrow.y, 90) + + const retinaNarrow = resolveVUNeedleFaceLayout(560, 720, 2) + assert.equal(retinaNarrow.width, 560) + assert.equal(retinaNarrow.height, 360) + assert.equal(retinaNarrow.y, 180) }) test('VUMeter shared needle helpers switch between stereo needles and combined RMS', () => {