diff --git a/src/ui/leftPanelRail.js b/src/ui/leftPanelRail.js index 0b3e7ee2f..c5c65378b 100644 --- a/src/ui/leftPanelRail.js +++ b/src/ui/leftPanelRail.js @@ -283,13 +283,16 @@ export function layoutLeftPanelRail({ getComputedStyle(stack) .getPropertyValue?.('--panel-stack-top-pinned') ?.trim() === '1'; - const railOffset = `${(topPinned ? 0 : safeTop - layoutTop).toFixed(1)}px`; + // The stack itself rises by the lift below, by a different amount for a + // tall panel than at rest; the rail takes up the difference. + const lift = (top) => Math.min(0, obstacleSafeTop - top); + const railOffset = `${(topPinned ? 0 : safeTop + lift(safeTop) - layoutTop - lift(layoutTop)).toFixed(1)}px`; if (stack.style.getPropertyValue('--panel-rail-offset') !== railOffset) { stack.style.setProperty('--panel-rail-offset', railOffset); } // How far the column can rise before it meets the HUD readout, so the rail // and any open panel sit just under it, whether or not a panel is open. - const railLift = `${(topPinned ? 0 : Math.min(0, obstacleSafeTop - layoutTop)).toFixed(1)}px`; + const railLift = `${(topPinned ? 0 : lift(layoutTop)).toFixed(1)}px`; if (stack.style.getPropertyValue('--panel-rail-lift') !== railLift) { stack.style.setProperty('--panel-rail-lift', railLift); } diff --git a/src/ui/panelRails.test.mjs b/src/ui/panelRails.test.mjs index 40c2be20b..7edbc854a 100644 --- a/src/ui/panelRails.test.mjs +++ b/src/ui/panelRails.test.mjs @@ -359,14 +359,23 @@ test('right rail aligns to the left rail buttons while the left stack is offset' assert.equal(f.stack.dataset.safeTop, '286.7'); }); -test('left rail buttons hold still while a tall panel moves the stack', () => { +test('left rail buttons hold still whether the open panel is short or tall', () => { + // Where the rail sits: the stack's top, the stack's rise, and the rail's + // own offset inside the stack. + const railTop = (stack) => + (parseFloat(stack.style.getPropertyValue('--left-stack-safe-top')) * 900) / + 100 + + parseFloat(stack.style.getPropertyValue('--panel-rail-lift')) + + parseFloat(stack.style.getPropertyValue('--panel-rail-offset')); + const short = fixture('left'); + short.expand(short.first, 250); + short.run(); const f = fixture('left'); f.expand(f.first, 900); f.run(); assert.equal(f.stack.dataset.layoutMode, 'focus'); - assert.ok( - parseFloat(f.stack.style.getPropertyValue('--panel-rail-offset')) > 0, - ); + assert.notEqual(short.stack.dataset.layoutMode, 'focus'); + assert.ok(Math.abs(railTop(f.stack) - railTop(short.stack)) < 0.2); // A theme that pins the stack's top (Cyber) leaves nothing to offset. f.stack.computed.getPropertyValue = (name) => diff --git a/src/ui/railEdgeScroll.js b/src/ui/railEdgeScroll.js index 4dfd56df6..cf46ef02f 100644 --- a/src/ui/railEdgeScroll.js +++ b/src/ui/railEdgeScroll.js @@ -62,24 +62,9 @@ export function installRailEdgeScroll(doc = globalThis.document) { // highlight is showing is kept here, and the classes are put back each time. const gliding = new WeakSet(); - // Where a rail lifts its open item out to sit beside the panel's title, the - // items left in the rail follow on from it in turn, so the next one in line - // is the one directly under it and a step brings that one up. A rail that - // keeps its open item in place keeps its own order. - function reorder(rail, on) { - const slots = [...rail.children].filter((slot) => slot.querySelector?.('.ui-rail__item')); - const at = on ? slots.findIndex((slot) => slot.contains(on)) : -1; - const lifted = at >= 0 && win.getComputedStyle(slots[at]).position === 'absolute'; - slots.forEach((slot, index) => { - const order = lifted ? String((index - at + slots.length) % slots.length) : ''; - if (slot.style.order !== order) slot.style.order = order; - }); - } - function glide(rail) { rail.classList.toggle('ui-rail--edge-hot', rail === hot); const on = rail.querySelector('.ui-rail__item[aria-pressed="true"]'); - reorder(rail, on); if (!on?.offsetWidth) { gliding.delete(rail); rail.classList.remove('ui-rail--glide'); @@ -263,7 +248,6 @@ export function installRailEdgeScroll(doc = globalThis.document) { win.removeEventListener('resize', onResize); doc.querySelectorAll('.ui-rail').forEach((rail) => { rail.classList.remove('ui-rail--glide', 'ui-rail--glide-jump', 'ui-rail--edge-hot'); - reorder(rail, null); }); }; } diff --git a/src/ui/styles/panel-edge.css b/src/ui/styles/panel-edge.css index c9cfa4faf..79d19b6d2 100644 --- a/src/ui/styles/panel-edge.css +++ b/src/ui/styles/panel-edge.css @@ -71,16 +71,6 @@ body.panels-edge-right:not(.context-docked) .gev-panel-rail--right .ui-rail--col gap: 4px; } -/* The open panel's icon joins the row instead of floating above it. */ -body.panels-edge-left .gev-panel-rail--left .ui-rail--collapsible:not(.ui-rail--expanded) .ui-tooltip { - position: static !important; - padding: 0 !important; - border: 0 !important; - background: none !important; - backdrop-filter: none !important; - -webkit-backdrop-filter: none !important; -} - body.panels-edge-left #left-panel-stack > :not(.gev-panel-rail):not(.collapsed) { transform: translate(calc(-1 * var(--panel-rail-clearance, 48px)), var(--edge-rail-row)); max-height: calc(100% - var(--edge-rail-row)) !important; diff --git a/src/ui/styles/rail-split.css b/src/ui/styles/rail-split.css index 1f8400dd1..d84940233 100644 --- a/src/ui/styles/rail-split.css +++ b/src/ui/styles/rail-split.css @@ -1,37 +1,5 @@ -/* Experiment: with a left panel open, its rail icon leaves the rail and sits - level with the panel's title, as the panel's own tab. The other icons stay - in the rail underneath it and close the gap. With nothing open the rail keeps its - labelled pills. */ -.gev-panel-rail--left:has(.ui-rail--collapsible:not(.ui-rail--expanded) .ui-rail__item--on) { - /* The lifted chip (42px) centres on the panel title (26px into the - stack); the rail starts 6px below the chip. */ - top: 53px; -} - -.gev-panel-rail--left .ui-rail--collapsible:not(.ui-rail--expanded) { - position: relative; -} - -.gev-panel-rail--left - .ui-rail--collapsible:not(.ui-rail--expanded) - .ui-tooltip:has(> .ui-rail__item--on) { - position: absolute; - bottom: calc(100% + 6px); - left: -1px; - padding: 4px; - border: 1px solid var(--glass-border); - border-radius: calc(var(--btn-radius) + 4px); - background: var(--glass-bg); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); -} - -.gev-panel-rail--left - .ui-rail--collapsible:not(.ui-rail--expanded) - .ui-tooltip:has(> .ui-rail__item--on) - .ui-rail__item { - width: 32px; -} +/* The left rail's icons keep their places whether or not a panel is open; + only the highlight moves to the open one. */ /* With nothing open, the icons and their names split into two glass panels side by side: the plain icon rail, and a name panel beside it with one row @@ -179,14 +147,18 @@ color: #fff; } -/* The open item's highlight sits on the button itself, so it shows wherever - the button is (the lifted chip, the edge row). */ +/* The open item takes the accent colour; its highlight is the rail's shared + one, which slides to it. Without that highlight the button carries its own. */ :is(.gev-panel-rail--left, .gev-panel-rail--right) .ui-rail__item.ui-rail__item--on { color: #00d4ff !important; - background: rgba(0, 212, 255, 0.15) !important; - border-color: rgba(0, 212, 255, 0.15) !important; } -:is(.gev-panel-rail--left, .gev-panel-rail--right) .ui-rail.ui-rail--glide::before { - display: none !important; +:is(.gev-panel-rail--left, .gev-panel-rail--right) .ui-rail:not(.ui-rail--glide) .ui-rail__item.ui-rail__item--on { + background: rgba(0, 212, 255, 0.15); + border-color: rgba(0, 212, 255, 0.15); +} + +:is(.gev-panel-rail--left, .gev-panel-rail--right) .ui-rail::before { + background: rgba(0, 212, 255, 0.15); + border-color: rgba(0, 212, 255, 0.28); }