diff --git a/src/editor/graph/SnapToggle.ts b/src/editor/graph/SnapToggle.ts index 96511c08..4ad4a481 100644 --- a/src/editor/graph/SnapToggle.ts +++ b/src/editor/graph/SnapToggle.ts @@ -1,4 +1,4 @@ -import { Button, Theme } from '../../ui'; +import { Button, Stack, Theme } from '../../ui'; import { HEADER_ICON_SVG } from '../panels/headerIcons'; /** Whatever a tab snaps: the graph canvas, the vector art canvas. A graph arranges its nodes too. */ @@ -58,4 +58,15 @@ export class SnapToggle { this.button.setEnabled(on); this.arrangeButton?.setEnabled(on); } + + /** + * Shows or hides *Arrange*, for a tab whose graph is one of its views: the song's timeline has + * no use for it, and a header that wide pushed *Fit* past the edge of the tab. + */ + setArrangeShown(on: boolean): void { + const button = this.arrangeButton; + if (!button || button.visible === on) return; + button.visible = on; + if (button.parent instanceof Stack) button.parent.relayout(); + } } diff --git a/src/editor/song/SongEditor.ts b/src/editor/song/SongEditor.ts index cecef9a2..c6020871 100644 --- a/src/editor/song/SongEditor.ts +++ b/src/editor/song/SongEditor.ts @@ -217,10 +217,11 @@ export class SongEditor extends AssetTabEditor { this.syncPlayButton(); } - /** The grid is the timeline's, the snap the routing's. */ + /** The grid is the timeline's, the snap the routing's, and *Arrange* only shows with the routing. */ private syncViewButtons(asset: SongAsset | null): void { this.gridButton.setEnabled(asset !== null && this.shown === 'timeline'); this.snap.setEnabled(asset !== null && this.shown === 'routing'); + this.snap.setArrangeShown(this.shown === 'routing'); } protected override body(canvas: SongTimeline): Widget { diff --git a/tests/editor/snap-toggle.test.ts b/tests/editor/snap-toggle.test.ts index 68492e8c..3fc78bd0 100644 --- a/tests/editor/snap-toggle.test.ts +++ b/tests/editor/snap-toggle.test.ts @@ -5,7 +5,7 @@ import { beforeAll, describe, expect, it, vi } from 'vitest'; import { CanvasTextMetrics } from 'pixi.js'; import { GRAPH_SNAPS_AT_FIRST, SnapToggle } from '../../src/editor/graph/SnapToggle'; -import { Theme } from '../../src/ui'; +import { Button, HStack, Theme } from '../../src/ui'; // Node has no DOMParser to read the file with; what the magnet looks like is the icon tests' job. vi.mock('../../src/ui/svg', () => ({ drawSvg: () => undefined, tintSvg: (svg: string) => svg })); @@ -67,6 +67,21 @@ describe('SnapToggle', () => { expect(new SnapToggle(() => canvas).arrangeButton).toBeNull(); }); + it('hides Arrange from a header it shares, which then lays the rest out without it', () => { + const snap = new SnapToggle(() => target(), GRAPH_SNAPS_AT_FIRST, true); + const header = new HStack({ gap: 4 }); + const fit = new Button('Fit'); + for (const widget of [...snap.widgets, fit]) header.add(widget); + header.resize(400, Theme.controlHeight); + const shownAt = fit.x; + snap.setArrangeShown(false); + expect(snap.arrangeButton!.visible).toBe(false); + expect(fit.x).toBeLessThan(shownAt); + snap.setArrangeShown(true); + expect(fit.x).toBe(shownAt); + new SnapToggle(() => target()).setArrangeShown(false); + }); + it('asks for the target at the click, so a canvas mounted later is the one switched', () => { let canvas = target(); const snap = new SnapToggle(() => canvas);