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 rowCount = Math.max(1, Math.ceil(totalBars / barsPerRow));
|
||||
const isGrand = clef === 'grand';
|
||||
const rowHeight = isGrand ? 200 : 105;
|
||||
const height = rowCount * rowHeight + 14;
|
||||
const rowHeight = isGrand ? 250 : 140;
|
||||
const height = rowCount * rowHeight + 30;
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
@@ -120,7 +120,9 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
||||
const css = getComputedStyle(document.documentElement);
|
||||
const resolve = (name: string, fallback: string) => css.getPropertyValue(name).trim() || fallback;
|
||||
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 errorColor = resolve('--color-error', '#ef4444');
|
||||
const mutedColor = resolve('--color-text-muted', '#888888');
|
||||
@@ -222,7 +224,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
||||
|
||||
// ---- Staves, voices, formatting, drawing -----------------------------
|
||||
for (let r = 0; r < rowFragmentList.length; r++) {
|
||||
const y = r * rowHeight + 10;
|
||||
const y = r * rowHeight + 26;
|
||||
const rowMeasureNumbers: number[] = [];
|
||||
for (let b = 0; b < barsPerRow; b++) {
|
||||
const m = r * barsPerRow + b;
|
||||
@@ -248,7 +250,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
||||
}
|
||||
let bass: Stave | null = null;
|
||||
if (isGrand) {
|
||||
bass = new Stave(x, y + 110, barWidth);
|
||||
bass = new Stave(x, y + 130, barWidth);
|
||||
bass.setDefaultLedgerLineStyle(ledgerStyle);
|
||||
if (isFirstOfRow) {
|
||||
bass.addClef('bass').addKeySignature(vexKeySpec);
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Fretboard } from './Fretboard';
|
||||
import { PianoKeys } from './PianoKeys';
|
||||
import { TUNINGS, getFretboardPositions } from '../music/Tunings';
|
||||
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 {
|
||||
pauseToggle: () => void;
|
||||
@@ -318,34 +318,73 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
||||
<div className="instruction-text phrase-status">{statusText}</div>
|
||||
</div>
|
||||
|
||||
{/* Transport */}
|
||||
{/* Transport — reuses the single-note button styles/behavior */}
|
||||
<div className="action-row phrase-transport">
|
||||
<button className="skip-button" onClick={startAction} title="Start / Pause (Space)">
|
||||
{trainer.phase === 'playing' || trainer.phase === 'countIn' ? <Pause size={18} /> : <Play size={18} />}
|
||||
{startLabel}
|
||||
</button>
|
||||
<button
|
||||
className="hint-button"
|
||||
onClick={trainer.previewToggle}
|
||||
title="Preview the phrase (P)"
|
||||
>
|
||||
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
|
||||
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
|
||||
</button>
|
||||
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
|
||||
<RotateCcw size={18} />
|
||||
Retry
|
||||
</button>
|
||||
{(phrase.material === 'melody' || fixedMaterial) && (
|
||||
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
||||
<SkipForward size={18} />
|
||||
{phrase.material === 'melody' ? 'New' : 'Next'}
|
||||
{!settings.zenMode && (
|
||||
<>
|
||||
<button
|
||||
className="hint-button"
|
||||
onClick={trainer.previewToggle}
|
||||
title="Preview the phrase (P)"
|
||||
>
|
||||
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
|
||||
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
|
||||
</button>
|
||||
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
|
||||
<RotateCcw size={18} />
|
||||
Retry
|
||||
</button>
|
||||
{(phrase.material === 'melody' || fixedMaterial) && (
|
||||
<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>
|
||||
)}
|
||||
{phrase.pace === 'step' && (
|
||||
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
|
||||
<SkipForward size={18} />
|
||||
Skip
|
||||
{!settings.zenMode && (
|
||||
<button
|
||||
className={`hint-button ${settings.showHint ? 'active' : ''}`}
|
||||
onClick={() => setSettings(s => ({ ...s, showHint: !s.showHint }))}
|
||||
title="Keyboard Shortcut: H"
|
||||
>
|
||||
<HelpCircle size={18} />
|
||||
{settings.showHint ? "Hide Hint" : "Show Hint"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user