mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-11-03 13:53:55 +01:00
Phrase mode UI fixes: visible current-note accent, staff headroom, reused virtual-instrument/hint toggles, zen decluttering
This commit is contained in:
@@ -105,8 +105,8 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
|||||||
const barsPerRow = Math.max(1, Math.min(4, Math.floor((width - 40) / 210)));
|
const barsPerRow = Math.max(1, Math.min(4, Math.floor((width - 40) / 210)));
|
||||||
const rowCount = Math.max(1, Math.ceil(totalBars / barsPerRow));
|
const rowCount = Math.max(1, Math.ceil(totalBars / barsPerRow));
|
||||||
const isGrand = clef === 'grand';
|
const isGrand = clef === 'grand';
|
||||||
const rowHeight = isGrand ? 200 : 105;
|
const rowHeight = isGrand ? 250 : 140;
|
||||||
const height = rowCount * rowHeight + 14;
|
const height = rowCount * rowHeight + 30;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
@@ -120,7 +120,9 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
|||||||
const css = getComputedStyle(document.documentElement);
|
const css = getComputedStyle(document.documentElement);
|
||||||
const resolve = (name: string, fallback: string) => css.getPropertyValue(name).trim() || fallback;
|
const resolve = (name: string, fallback: string) => css.getPropertyValue(name).trim() || fallback;
|
||||||
const textColor = resolve('--color-text-main', '#333333');
|
const textColor = resolve('--color-text-main', '#333333');
|
||||||
const currentColor = resolve('--color-primary', '#e63946');
|
// --color-primary is near-black in light mode — invisible as a highlight.
|
||||||
|
// Use the warm accent that reads clearly in both themes.
|
||||||
|
const currentColor = resolve('--color-warning', '#f59e0b');
|
||||||
const successColor = resolve('--color-success', '#22c55e');
|
const successColor = resolve('--color-success', '#22c55e');
|
||||||
const errorColor = resolve('--color-error', '#ef4444');
|
const errorColor = resolve('--color-error', '#ef4444');
|
||||||
const mutedColor = resolve('--color-text-muted', '#888888');
|
const mutedColor = resolve('--color-text-muted', '#888888');
|
||||||
@@ -222,7 +224,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
|||||||
|
|
||||||
// ---- Staves, voices, formatting, drawing -----------------------------
|
// ---- Staves, voices, formatting, drawing -----------------------------
|
||||||
for (let r = 0; r < rowFragmentList.length; r++) {
|
for (let r = 0; r < rowFragmentList.length; r++) {
|
||||||
const y = r * rowHeight + 10;
|
const y = r * rowHeight + 26;
|
||||||
const rowMeasureNumbers: number[] = [];
|
const rowMeasureNumbers: number[] = [];
|
||||||
for (let b = 0; b < barsPerRow; b++) {
|
for (let b = 0; b < barsPerRow; b++) {
|
||||||
const m = r * barsPerRow + b;
|
const m = r * barsPerRow + b;
|
||||||
@@ -248,7 +250,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
|||||||
}
|
}
|
||||||
let bass: Stave | null = null;
|
let bass: Stave | null = null;
|
||||||
if (isGrand) {
|
if (isGrand) {
|
||||||
bass = new Stave(x, y + 110, barWidth);
|
bass = new Stave(x, y + 130, barWidth);
|
||||||
bass.setDefaultLedgerLineStyle(ledgerStyle);
|
bass.setDefaultLedgerLineStyle(ledgerStyle);
|
||||||
if (isFirstOfRow) {
|
if (isFirstOfRow) {
|
||||||
bass.addClef('bass').addKeySignature(vexKeySpec);
|
bass.addClef('bass').addKeySignature(vexKeySpec);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { Fretboard } from './Fretboard';
|
|||||||
import { PianoKeys } from './PianoKeys';
|
import { PianoKeys } from './PianoKeys';
|
||||||
import { TUNINGS, getFretboardPositions } from '../music/Tunings';
|
import { TUNINGS, getFretboardPositions } from '../music/Tunings';
|
||||||
import { INSTRUMENT_DEFINITIONS } from '../music/InstrumentConfigs';
|
import { INSTRUMENT_DEFINITIONS } from '../music/InstrumentConfigs';
|
||||||
import { Play, Pause, RotateCcw, SkipForward, Volume2, Square, ChevronDown, Music2, Upload } from 'lucide-react';
|
import { Play, Pause, RotateCcw, SkipForward, Volume2, Square, ChevronDown, Music2, Upload, HelpCircle, Guitar } from 'lucide-react';
|
||||||
|
|
||||||
export interface PhraseHandle {
|
export interface PhraseHandle {
|
||||||
pauseToggle: () => void;
|
pauseToggle: () => void;
|
||||||
@@ -318,34 +318,73 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
<div className="instruction-text phrase-status">{statusText}</div>
|
<div className="instruction-text phrase-status">{statusText}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Transport */}
|
{/* Transport — reuses the single-note button styles/behavior */}
|
||||||
<div className="action-row phrase-transport">
|
<div className="action-row phrase-transport">
|
||||||
<button className="skip-button" onClick={startAction} title="Start / Pause (Space)">
|
<button className="skip-button" onClick={startAction} title="Start / Pause (Space)">
|
||||||
{trainer.phase === 'playing' || trainer.phase === 'countIn' ? <Pause size={18} /> : <Play size={18} />}
|
{trainer.phase === 'playing' || trainer.phase === 'countIn' ? <Pause size={18} /> : <Play size={18} />}
|
||||||
{startLabel}
|
{startLabel}
|
||||||
</button>
|
</button>
|
||||||
<button
|
{!settings.zenMode && (
|
||||||
className="hint-button"
|
<>
|
||||||
onClick={trainer.previewToggle}
|
<button
|
||||||
title="Preview the phrase (P)"
|
className="hint-button"
|
||||||
>
|
onClick={trainer.previewToggle}
|
||||||
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
|
title="Preview the phrase (P)"
|
||||||
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
|
>
|
||||||
</button>
|
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
|
||||||
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
|
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
|
||||||
<RotateCcw size={18} />
|
</button>
|
||||||
Retry
|
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
|
||||||
</button>
|
<RotateCcw size={18} />
|
||||||
{(phrase.material === 'melody' || fixedMaterial) && (
|
Retry
|
||||||
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
</button>
|
||||||
<SkipForward size={18} />
|
{(phrase.material === 'melody' || fixedMaterial) && (
|
||||||
{phrase.material === 'melody' ? 'New' : 'Next'}
|
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
||||||
|
<SkipForward size={18} />
|
||||||
|
{phrase.material === 'melody' ? 'New' : 'Next'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{phrase.pace === 'step' && (
|
||||||
|
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
|
||||||
|
<SkipForward size={18} />
|
||||||
|
Skip
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* Same virtual-instrument / hint toggles as single-note mode */}
|
||||||
|
{trainer.phase === 'done' && settings.zenMode && (
|
||||||
|
<>
|
||||||
|
<button className="hint-button" onClick={trainer.retry} title="Retry (R)">
|
||||||
|
<RotateCcw size={18} />
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
{(phrase.material === 'melody' || fixedMaterial) && (
|
||||||
|
<button className="hint-button" onClick={nextAction} title="New / Next (N)">
|
||||||
|
<SkipForward size={18} />
|
||||||
|
{phrase.material === 'melody' ? 'New' : 'Next'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!settings.zenMode && currentInstrumentDef.showTuning && (
|
||||||
|
<button
|
||||||
|
className={`hint-button ${settings.showFretboard ? 'active' : ''}`}
|
||||||
|
onClick={() => setSettings(s => ({ ...s, showFretboard: !s.showFretboard }))}
|
||||||
|
title={`Toggle Virtual ${currentInstrumentDef.displayName} (Keyboard Shortcut: V)`}
|
||||||
|
>
|
||||||
|
<Guitar size={18} />
|
||||||
|
{currentInstrumentDef.id === 'piano' ? 'Piano' : 'Guitar'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{phrase.pace === 'step' && (
|
{!settings.zenMode && (
|
||||||
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
|
<button
|
||||||
<SkipForward size={18} />
|
className={`hint-button ${settings.showHint ? 'active' : ''}`}
|
||||||
Skip
|
onClick={() => setSettings(s => ({ ...s, showHint: !s.showHint }))}
|
||||||
|
title="Keyboard Shortcut: H"
|
||||||
|
>
|
||||||
|
<HelpCircle size={18} />
|
||||||
|
{settings.showHint ? "Hide Hint" : "Show Hint"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user