feat: home button to center view + fix sequencer step count growth
- Add ⌂ button to zoom bar (sandbox + puzzle) that centers camera on all modules - Fix sequencer _steps array not growing when step count param increases (e.g. 8→32 now properly adds new empty steps) - Make piano roll width dynamic based on bar count (BEAT_PX constant density instead of fixed ROLL_W) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
21
src/App.jsx
21
src/App.jsx
@@ -191,6 +191,26 @@ export default function App({ onSwitchToGame }) {
|
|||||||
emit();
|
emit();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Center view on all modules
|
||||||
|
const handleCenterView = useCallback(() => {
|
||||||
|
if (state.modules.length === 0) { state.camX = 0; state.camY = 0; emit(); return; }
|
||||||
|
const container = containerRef.current;
|
||||||
|
const cw = container?.clientWidth || 800;
|
||||||
|
const ch = container?.clientHeight || 600;
|
||||||
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
|
for (const m of state.modules) {
|
||||||
|
minX = Math.min(minX, m.x);
|
||||||
|
minY = Math.min(minY, m.y);
|
||||||
|
maxX = Math.max(maxX, m.x + 200);
|
||||||
|
maxY = Math.max(maxY, m.y + 150);
|
||||||
|
}
|
||||||
|
const cx = (minX + maxX) / 2 * state.zoom;
|
||||||
|
const cy = (minY + maxY) / 2 * state.zoom;
|
||||||
|
state.camX = cw / 2 - cx;
|
||||||
|
state.camY = ch / 2 - cy;
|
||||||
|
emit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleToggleAudio = async () => {
|
const handleToggleAudio = async () => {
|
||||||
if (state.isRunning) {
|
if (state.isRunning) {
|
||||||
stopAudio();
|
stopAudio();
|
||||||
@@ -305,6 +325,7 @@ export default function App({ onSwitchToGame }) {
|
|||||||
{(state.zoom * 100).toFixed(0)}%
|
{(state.zoom * 100).toFixed(0)}%
|
||||||
</button>
|
</button>
|
||||||
<button className="zoom-btn" onClick={handleZoomOut} title="Zoom out">−</button>
|
<button className="zoom-btn" onClick={handleZoomOut} title="Zoom out">−</button>
|
||||||
|
<button className="zoom-btn" onClick={handleCenterView} title="Centrar vista">⌂</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Module palette */}
|
{/* Module palette */}
|
||||||
|
|||||||
@@ -22,13 +22,21 @@ export default function SequencerWidget({ moduleId }) {
|
|||||||
const clockRef = useRef(null);
|
const clockRef = useRef(null);
|
||||||
const stepsRef = useRef(null);
|
const stepsRef = useRef(null);
|
||||||
|
|
||||||
// Init steps data
|
// Init steps data — also grow/shrink when numSteps changes
|
||||||
const numSteps = parseInt(mod?.params?.steps || '16');
|
const numSteps = parseInt(mod?.params?.steps || '16');
|
||||||
if (!mod?.params?._steps) {
|
if (mod) {
|
||||||
const initial = DEFAULT_STEPS.slice(0, numSteps);
|
if (!mod.params._steps) {
|
||||||
while (initial.length < numSteps) initial.push({ midi: 60, gate: false });
|
const initial = DEFAULT_STEPS.slice(0, numSteps);
|
||||||
if (mod) {
|
while (initial.length < numSteps) initial.push({ midi: 60, gate: false });
|
||||||
mod.params._steps = initial;
|
mod.params._steps = initial;
|
||||||
|
} else if (mod.params._steps.length < numSteps) {
|
||||||
|
// Grow: pad with empty steps
|
||||||
|
while (mod.params._steps.length < numSteps) {
|
||||||
|
mod.params._steps.push({ midi: 60, gate: false });
|
||||||
|
}
|
||||||
|
} else if (mod.params._steps.length > numSteps) {
|
||||||
|
// Shrink: truncate
|
||||||
|
mod.params._steps = mod.params._steps.slice(0, numSteps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const steps = mod?.params?._steps || DEFAULT_STEPS;
|
const steps = mod?.params?._steps || DEFAULT_STEPS;
|
||||||
|
|||||||
@@ -222,6 +222,26 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN
|
|||||||
emit();
|
emit();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Center view on all modules
|
||||||
|
const handleCenterView = useCallback(() => {
|
||||||
|
if (state.modules.length === 0) { state.camX = 0; state.camY = 0; emit(); return; }
|
||||||
|
const container = containerRef.current;
|
||||||
|
const cw = container?.clientWidth || 800;
|
||||||
|
const ch = container?.clientHeight || 600;
|
||||||
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
|
for (const m of state.modules) {
|
||||||
|
minX = Math.min(minX, m.x);
|
||||||
|
minY = Math.min(minY, m.y);
|
||||||
|
maxX = Math.max(maxX, m.x + 200);
|
||||||
|
maxY = Math.max(maxY, m.y + 150);
|
||||||
|
}
|
||||||
|
const cx = (minX + maxX) / 2 * state.zoom;
|
||||||
|
const cy = (minY + maxY) / 2 * state.zoom;
|
||||||
|
state.camX = cw / 2 - cx;
|
||||||
|
state.camY = ch / 2 - cy;
|
||||||
|
emit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleAddModule = (type) => {
|
const handleAddModule = (type) => {
|
||||||
const x = (-state.camX + 250) / state.zoom + Math.random() * 30;
|
const x = (-state.camX + 250) / state.zoom + Math.random() * 30;
|
||||||
const y = (-state.camY + 150) / state.zoom + Math.random() * 30;
|
const y = (-state.camY + 150) / state.zoom + Math.random() * 30;
|
||||||
@@ -471,6 +491,7 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN
|
|||||||
{(state.zoom * 100).toFixed(0)}%
|
{(state.zoom * 100).toFixed(0)}%
|
||||||
</button>
|
</button>
|
||||||
<button className="zoom-btn" onClick={handleZoomOut} title="Alejar">−</button>
|
<button className="zoom-btn" onClick={handleZoomOut} title="Alejar">−</button>
|
||||||
|
<button className="zoom-btn" onClick={handleCenterView} title="Centrar vista">⌂</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.modules.length > 0 && state.connections.length === 0 && (
|
{state.modules.length > 0 && state.connections.length === 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user