feat: add bar width multiplier (1x–8x horizontal zoom) for track grid

This commit is contained in:
Xiaohan-Tian
2026-04-10 18:17:36 -07:00
parent 4917e54048
commit 139684b413
8 changed files with 153 additions and 9 deletions
+46
View File
@@ -153,3 +153,49 @@
width: 100px;
left: 0;
}
/* Clickable zoom styling */
.current-zoom {
font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-weight: normal;
letter-spacing: 0.5px;
cursor: pointer;
transition: background-color 0.2s ease;
padding: 2px 4px;
border-radius: 2px;
}
.current-zoom:hover {
background-color: #4a4a4a;
color: #fff;
}
.zoom-slider-popup {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #2d2d2d;
border: 1px solid #444;
border-radius: 3px;
padding: 8px 12px;
z-index: 10000;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
white-space: nowrap;
margin-top: 2px;
}
.zoom-slider-popup input[type="range"] {
width: 120px;
}
.zoom-slider-label {
font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
color: #e0e0e0;
min-width: 20px;
text-align: center;
}
+39
View File
@@ -36,6 +36,7 @@ const Toolbar: React.FC = () => {
isPlaying, startPlaying, stopPlaying, setPlayheadPosition,
currentTime, setBpm, setTimeSignature, setKeySignature,
maxBars, setMaxBars,
barWidthMultiplier, setBarWidthMultiplier,
isLooping, toggleLoop,
canUndo, canRedo, undoDescription, redoDescription, undo, redo,
toggleChatBox, toggleSettings, cleanupProjectState,
@@ -57,9 +58,25 @@ const Toolbar: React.FC = () => {
// State for import modal
const [showImportModal, setShowImportModal] = React.useState(false);
// State for zoom slider popup
const [showZoomSlider, setShowZoomSlider] = React.useState(false);
const zoomSliderRef = React.useRef<HTMLDivElement>(null);
// State for open project modal
const [showOpenProject, setShowOpenProject] = React.useState(false);
// Close zoom slider on click outside
React.useEffect(() => {
if (!showZoomSlider) return;
const handleClickOutside = (e: MouseEvent) => {
if (zoomSliderRef.current && !zoomSliderRef.current.contains(e.target as Node)) {
setShowZoomSlider(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [showZoomSlider]);
// Key signature options
const keySignatureOptions = Object.keys(KEY_SIGNATURE_MAP) as KeySignature[];
@@ -790,6 +807,28 @@ const Toolbar: React.FC = () => {
<div className="toolbar-right">
<div className="transport-control">
<div className="transport-item" style={{ position: 'relative' }} ref={zoomSliderRef}>
<span
className='current-zoom'
onClick={() => setShowZoomSlider(!showZoomSlider)}
style={{ cursor: 'pointer' }}
>
{barWidthMultiplier}x
</span>
{showZoomSlider && (
<div className="zoom-slider-popup">
<input
type="range"
min="1"
max="8"
step="1"
value={barWidthMultiplier}
onChange={(e) => setBarWidthMultiplier(parseInt(e.target.value))}
/>
<span className="zoom-slider-label">{barWidthMultiplier}x</span>
</div>
)}
</div>
<div className="transport-item">
<span className='current-time' onClick={handleCurrentTimeClick} style={{ cursor: 'pointer' }}>{currentTime}</span>
</div>