From 2ce93147e2435aeaf4b68fddf48aacdbb0f00c55 Mon Sep 17 00:00:00 2001 From: Laura Sach <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:46:30 +0100 Subject: [PATCH 1/2] Change pills --- style.css | 33 ++++++++++++++------------------- tests/status.test.js | 26 +++++++++++--------------- ui/gizmos.js | 11 ++++++++--- ui/status.js | 32 +++++++++++++++++++------------- 4 files changed, 52 insertions(+), 50 deletions(-) diff --git a/style.css b/style.css index ac371977..d852614c 100644 --- a/style.css +++ b/style.css @@ -717,29 +717,24 @@ button { font-weight: bold; } -/* One coordinate, styled like the value field on a block. Border colour is set - inline, from AXIS_HEX. */ -.gizmo-status__pill { +.gizmo-status__reading { display: inline-block; - padding: 0 5px; - border: 2px solid; - border-radius: 999px; - /* Digits are cap-height against the labels' x-height, so they read bigger at - a matching size. */ - font-size: 0.9em; - line-height: 1.25; - background: var(--color-bg); - color: var(--color-text); - /* Steady size and digit width, so 0.6 -> 0 doesn't shift the pills beside it. */ - box-sizing: border-box; - min-width: 2.75rem; - text-align: center; + /* Steady width and digit width, so 1.2 -> 0 doesn't shift the axis beside it. */ + min-width: 4.5rem; font-variant-numeric: tabular-nums; } -/* Separate from #gizmoButtons's own display (toggled directly via inline - style in view.js for show/hide) so the mobile layout below can safely - switch this to a grid without an inline style winning the cascade. */ +/* --axis-color is set inline, from AXIS_HEX. */ +.gizmo-status__reading::before { + content: ''; + display: inline-block; + width: 3px; + height: 0.95em; + margin-right: 6px; + vertical-align: -0.12em; + background: var(--axis-color); +} + .gizmo-buttons-inner { display: flex; flex-wrap: wrap; diff --git a/tests/status.test.js b/tests/status.test.js index 6ec37c14..6490f08f 100644 --- a/tests/status.test.js +++ b/tests/status.test.js @@ -95,28 +95,24 @@ export function runStatusTests() { expect(element.textContent).to.equal('Later'); }); - it('applies the axis and pill classes and the inline border colour', function () { + it('groups a reading behind its rule and keeps the axis bold', function () { showStatus([ { text: 'Position: ' }, - { text: 'x', bold: true }, - { text: ': ' }, - { text: '-5.3', borderColor: '#0072B2' }, + { + barColor: '#0072B2', + parts: [{ text: 'x', bold: true }, { text: ': ' }, { text: '-5.3' }], + }, ]); expect(element.textContent).to.equal('Position: x: -5.3'); - const axis = element.querySelector('.gizmo-status__axis'); + const reading = element.querySelector('.gizmo-status__reading'); + expect(reading).to.exist; + expect(reading.textContent).to.equal('x: -5.3'); + expect(reading.style.getPropertyValue('--axis-color')).to.equal('#0072B2'); + + const axis = reading.querySelector('.gizmo-status__axis'); expect(axis).to.exist; expect(axis.textContent).to.equal('x'); - - const pill = element.querySelector('.gizmo-status__pill'); - expect(pill).to.exist; - expect(pill.textContent).to.equal('-5.3'); - - // Compare against a probe so the assertion survives the browser - // normalising the hex to rgb(). - const probe = document.createElement('span'); - probe.style.borderColor = '#0072B2'; - expect(pill.style.borderColor).to.equal(probe.style.borderColor); }); it('leaves plain segments as text nodes', function () { diff --git a/ui/gizmos.js b/ui/gizmos.js index ea38c130..dab98734 100644 --- a/ui/gizmos.js +++ b/ui/gizmos.js @@ -1344,9 +1344,14 @@ function positionStatus(position) { const [before = '', after = ''] = translate('position_readout').split('{position}'); const axes = ['x', 'y', 'z'].flatMap((axis, i) => [ { text: i ? ' ' : '' }, - { text: axis, bold: true }, - { text: ': ' }, - { text: String(roundToOneDecimal(position?.[axis] ?? 0)), borderColor: AXIS_HEX[axis] }, + { + barColor: AXIS_HEX[axis], + parts: [ + { text: axis, bold: true }, + { text: ': ' }, + { text: String(roundToOneDecimal(position?.[axis] ?? 0)) }, + ], + }, ]); return [{ text: before }, ...axes, { text: after }]; } diff --git a/ui/status.js b/ui/status.js index c4a6cae9..12c8ca19 100644 --- a/ui/status.js +++ b/ui/status.js @@ -14,24 +14,30 @@ function cancelHide() { } } +// A segment is either a leaf ({ text, bold }) or a group ({ parts, barColor }) +// whose parts sit behind a coloured rule. +function toNode({ text, bold, barColor, parts }) { + if (parts) { + const group = document.createElement('span'); + group.classList.add('gizmo-status__reading'); + // The rule is a ::before, so its colour has to travel as a custom property. + if (barColor) group.style.setProperty('--axis-color', barColor); + group.append(...parts.map(toNode)); + return group; + } + if (!bold) return document.createTextNode(text); + const span = document.createElement('span'); + span.textContent = text; + span.classList.add('gizmo-status__axis'); + return span; +} + function render(element, content) { if (typeof content === 'string') { element.textContent = content; return; } - element.replaceChildren( - ...content.map(({ text, bold, borderColor }) => { - if (!bold && !borderColor) return document.createTextNode(text); - const span = document.createElement('span'); - span.textContent = text; - if (bold) span.classList.add('gizmo-status__axis'); - if (borderColor) { - span.classList.add('gizmo-status__pill'); - span.style.borderColor = borderColor; - } - return span; - }) - ); + element.replaceChildren(...content.map(toNode)); } // duration 0 keeps the message up until something replaces or clears it. From fdb116fdf441a6409036b8d6f31d6f0911d02c07 Mon Sep 17 00:00:00 2001 From: Laura Sach <5183697+lawsie@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:03:51 +0100 Subject: [PATCH 2/2] Clear message when new gizmo selected --- tests/gizmos.test.js | 15 +++++++++++++++ ui/gizmos.js | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/gizmos.test.js b/tests/gizmos.test.js index 153c9830..63ec8518 100644 --- a/tests/gizmos.test.js +++ b/tests/gizmos.test.js @@ -13,6 +13,7 @@ import { toggleGizmo, enableGizmos, } from '../ui/gizmos.js'; +import { showStatus, clearStatus } from '../ui/status.js'; export function runGizmoTests(flock) { const BABYLON = flock.BABYLON; @@ -253,6 +254,20 @@ export function runGizmoTests(flock) { exitGizmoState(); expect(mgr.positionGizmoEnabled).to.be.false; }); + + it('clears the position readout, which the next tool would not update', function () { + const status = document.createElement('p'); + status.id = 'gizmoStatus'; + document.body.appendChild(status); + try { + showStatus('Position: x: 0.6 y: 1.2 z: 0', { owner: 'position-readout' }); + exitGizmoState(); + expect(status.textContent).to.equal(''); + } finally { + clearStatus(); + status.remove(); + } + }); }); // ─── toggleGizmo ───────────────────────────────────────────────────────── diff --git a/ui/gizmos.js b/ui/gizmos.js index dab98734..9a8ee5fd 100644 --- a/ui/gizmos.js +++ b/ui/gizmos.js @@ -612,7 +612,7 @@ function applyMeshSelection(pickedMesh, pickedPoint) { } if (pickedMesh && pickedMesh.name === 'ground') { - showStatus(positionStatus(pickedPoint), { duration: 10 }); + showStatus(positionStatus(pickedPoint), { duration: 10, owner: 'position-readout' }); } if (gizmoManager.attachedMesh) { resetChildMeshesOfAttachedMesh(); @@ -962,6 +962,8 @@ export function exitGizmoState() { stopAxisKeyboard = null; clearStatus('axis'); clearStatus('camera'); + // The readout belongs to the tool that took it; the next tool doesn't move it. + clearStatus('position-readout'); // Run all queued cleanup functions runCleanups(); @@ -2346,7 +2348,10 @@ function handleSelectGizmo() { // A pick can land on a child; the attached mesh is the root owning the block. const attached = gizmoManager.attachedMesh; if (attached) { - showStatus(positionStatus(flock.getBlockPositionFromMesh(attached)), { duration: 10 }); + showStatus(positionStatus(flock.getBlockPositionFromMesh(attached)), { + duration: 10, + owner: 'position-readout', + }); } setTimeout(() => { if (!getCanvasCircle()) document.body.style.cursor = 'crosshair';