refactor: group selection state into a Selection sub-struct + ClearSelection()
The box-selection state was 11 fields spread across three comment blocks (time sel, freq sel, drag state). Consolidate them into one Selection sub-struct (app.sel.*) and extract the 4-line 'clear to full range' block — duplicated in 5 places — into a ClearSelection() helper. Pure mechanical rename (anchored to app./spa-> so the ScopeView fields of the same name are untouched) + behavior-identical dedup. Fully-settled render verified pixel-identical (AE=0). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+4
-4
@@ -182,8 +182,8 @@ void PlaySelectedRegion(void)
|
|||||||
{
|
{
|
||||||
if (!app.loaded || !app.stftComputed) return;
|
if (!app.loaded || !app.stftComputed) return;
|
||||||
|
|
||||||
int startSample = (int)(app.timeSelectionStart * app.signal.numSamples);
|
int startSample = (int)(app.sel.timeStart * app.signal.numSamples);
|
||||||
int endSample = (int)(app.timeSelectionEnd * app.signal.numSamples);
|
int endSample = (int)(app.sel.timeEnd * app.signal.numSamples);
|
||||||
int numSamples = endSample - startSample;
|
int numSamples = endSample - startSample;
|
||||||
if (numSamples <= 0 || startSample < 0 || endSample > app.signal.numSamples) return;
|
if (numSamples <= 0 || startSample < 0 || endSample > app.signal.numSamples) return;
|
||||||
|
|
||||||
@@ -191,8 +191,8 @@ void PlaySelectedRegion(void)
|
|||||||
memcpy(regionSamples, app.signal.samples + startSample, numSamples * sizeof(float));
|
memcpy(regionSamples, app.signal.samples + startSample, numSamples * sizeof(float));
|
||||||
|
|
||||||
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
||||||
float freqLow = app.freqSelectionStart * maxFreq;
|
float freqLow = app.sel.freqStart * maxFreq;
|
||||||
float freqHigh = app.freqSelectionEnd * maxFreq;
|
float freqHigh = app.sel.freqEnd * maxFreq;
|
||||||
if (freqLow > 10.0f || freqHigh < maxFreq - 10.0f) {
|
if (freqLow > 10.0f || freqHigh < maxFreq - 10.0f) {
|
||||||
TraceLog(LOG_INFO, "Applying bandpass filter: %.0f - %.0f Hz", freqLow, freqHigh);
|
TraceLog(LOG_INFO, "Applying bandpass filter: %.0f - %.0f Hz", freqLow, freqHigh);
|
||||||
ApplyBandpassFilter(regionSamples, numSamples, app.signal.sampleRate, freqLow, freqHigh);
|
ApplyBandpassFilter(regionSamples, numSamples, app.signal.sampleRate, freqLow, freqHigh);
|
||||||
|
|||||||
+18
-18
@@ -341,9 +341,9 @@ void DrawLabels(Rectangle bounds)
|
|||||||
void DrawSelection(Rectangle bounds)
|
void DrawSelection(Rectangle bounds)
|
||||||
{
|
{
|
||||||
// Only draw if selection is not full range AND not currently dragging
|
// Only draw if selection is not full range AND not currently dragging
|
||||||
bool hasSelection = (app.timeSelectionStart > 0.001f || app.timeSelectionEnd < 0.999f ||
|
bool hasSelection = (app.sel.timeStart > 0.001f || app.sel.timeEnd < 0.999f ||
|
||||||
app.freqSelectionStart > 0.001f || app.freqSelectionEnd < 0.999f);
|
app.sel.freqStart > 0.001f || app.sel.freqEnd < 0.999f);
|
||||||
if (!hasSelection || app.isTimeSelecting) return; // Don't draw overlay while dragging
|
if (!hasSelection || app.sel.isTimeSelecting) return; // Don't draw overlay while dragging
|
||||||
|
|
||||||
Color overlayColor = Fade(BLACK, 0.25f); // Lighter overlay
|
Color overlayColor = Fade(BLACK, 0.25f); // Lighter overlay
|
||||||
|
|
||||||
@@ -351,10 +351,10 @@ void DrawSelection(Rectangle bounds)
|
|||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.viewEnd - app.viewStart;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||||
|
|
||||||
float selStartX = bounds.x + ((app.timeSelectionStart - app.viewStart) / viewWidth) * bounds.width;
|
float selStartX = bounds.x + ((app.sel.timeStart - app.viewStart) / viewWidth) * bounds.width;
|
||||||
float selEndX = bounds.x + ((app.timeSelectionEnd - app.viewStart) / viewWidth) * bounds.width;
|
float selEndX = bounds.x + ((app.sel.timeEnd - app.viewStart) / viewWidth) * bounds.width;
|
||||||
float selStartY = bounds.y + bounds.height - ((app.freqSelectionEnd - app.freqViewStart) / freqWidth) * bounds.height;
|
float selStartY = bounds.y + bounds.height - ((app.sel.freqEnd - app.freqViewStart) / freqWidth) * bounds.height;
|
||||||
float selEndY = bounds.y + bounds.height - ((app.freqSelectionStart - app.freqViewStart) / freqWidth) * bounds.height;
|
float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.freqViewStart) / freqWidth) * bounds.height;
|
||||||
|
|
||||||
// Clamp to viewport bounds
|
// Clamp to viewport bounds
|
||||||
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
||||||
@@ -373,8 +373,8 @@ void DrawSelection(Rectangle bounds)
|
|||||||
|
|
||||||
// Display selection stats inside viewport, clamped to fit
|
// Display selection stats inside viewport, clamped to fit
|
||||||
{
|
{
|
||||||
int startSample = (int)(app.timeSelectionStart * app.signal.numSamples);
|
int startSample = (int)(app.sel.timeStart * app.signal.numSamples);
|
||||||
int endSample = (int)(app.timeSelectionEnd * app.signal.numSamples);
|
int endSample = (int)(app.sel.timeEnd * app.signal.numSamples);
|
||||||
SignalStats stats = ComputeSignalStats(&app.signal, startSample, endSample);
|
SignalStats stats = ComputeSignalStats(&app.signal, startSample, endSample);
|
||||||
|
|
||||||
if (stats.durationSec > 0.0f && app.signal.samples != NULL) {
|
if (stats.durationSec > 0.0f && app.signal.samples != NULL) {
|
||||||
@@ -439,17 +439,17 @@ void DrawSelection(Rectangle bounds)
|
|||||||
void DrawSelectionDrag(Rectangle bounds)
|
void DrawSelectionDrag(Rectangle bounds)
|
||||||
{
|
{
|
||||||
// Draw bounding box while dragging (no overlay)
|
// Draw bounding box while dragging (no overlay)
|
||||||
if ((!app.isTimeSelecting && !app.isFreqSelecting && !app.isDraggingSelection) ||
|
if ((!app.sel.isTimeSelecting && !app.sel.isFreqSelecting && !app.sel.isDragging) ||
|
||||||
(app.isDraggingSelection && !IsMouseButtonDown(MOUSE_LEFT_BUTTON))) return;
|
(app.sel.isDragging && !IsMouseButtonDown(MOUSE_LEFT_BUTTON))) return;
|
||||||
|
|
||||||
// Convert signal coordinates to viewport coordinates
|
// Convert signal coordinates to viewport coordinates
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.viewEnd - app.viewStart;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||||
|
|
||||||
float selStartX = bounds.x + ((app.timeSelectionStart - app.viewStart) / viewWidth) * bounds.width;
|
float selStartX = bounds.x + ((app.sel.timeStart - app.viewStart) / viewWidth) * bounds.width;
|
||||||
float selEndX = bounds.x + ((app.timeSelectionEnd - app.viewStart) / viewWidth) * bounds.width;
|
float selEndX = bounds.x + ((app.sel.timeEnd - app.viewStart) / viewWidth) * bounds.width;
|
||||||
float selStartY = bounds.y + bounds.height - ((app.freqSelectionEnd - app.freqViewStart) / freqWidth) * bounds.height;
|
float selStartY = bounds.y + bounds.height - ((app.sel.freqEnd - app.freqViewStart) / freqWidth) * bounds.height;
|
||||||
float selEndY = bounds.y + bounds.height - ((app.freqSelectionStart - app.freqViewStart) / freqWidth) * bounds.height;
|
float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.freqViewStart) / freqWidth) * bounds.height;
|
||||||
|
|
||||||
// Clamp to viewport bounds
|
// Clamp to viewport bounds
|
||||||
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
||||||
@@ -467,8 +467,8 @@ void DrawSelectionDrag(Rectangle bounds)
|
|||||||
|
|
||||||
// Display live stats while dragging (inside viewport, clamped to fit)
|
// Display live stats while dragging (inside viewport, clamped to fit)
|
||||||
{
|
{
|
||||||
int startSample = (int)(app.timeSelectionStart * app.signal.numSamples);
|
int startSample = (int)(app.sel.timeStart * app.signal.numSamples);
|
||||||
int endSample = (int)(app.timeSelectionEnd * app.signal.numSamples);
|
int endSample = (int)(app.sel.timeEnd * app.signal.numSamples);
|
||||||
SignalStats stats = ComputeSignalStats(&app.signal, startSample, endSample);
|
SignalStats stats = ComputeSignalStats(&app.signal, startSample, endSample);
|
||||||
|
|
||||||
if (stats.durationSec > 0.0f && app.signal.samples != NULL) {
|
if (stats.durationSec > 0.0f && app.signal.samples != NULL) {
|
||||||
@@ -538,7 +538,7 @@ void DrawPlayhead(Rectangle bounds)
|
|||||||
{
|
{
|
||||||
if (!app.isPlaying || app.playheadT < 0.0f || app.playheadT > 1.0f) return;
|
if (!app.isPlaying || app.playheadT < 0.0f || app.playheadT > 1.0f) return;
|
||||||
|
|
||||||
float timePos = app.timeSelectionStart + app.playheadT * (app.timeSelectionEnd - app.timeSelectionStart);
|
float timePos = app.sel.timeStart + app.playheadT * (app.sel.timeEnd - app.sel.timeStart);
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.viewEnd - app.viewStart;
|
||||||
float t = (timePos - app.viewStart) / viewWidth;
|
float t = (timePos - app.viewStart) / viewWidth;
|
||||||
float x = bounds.x + t * bounds.width;
|
float x = bounds.x + t * bounds.width;
|
||||||
|
|||||||
+52
-62
@@ -120,8 +120,7 @@ void ResetForNewSignal(void)
|
|||||||
|
|
||||||
// Reset view + selection to full range.
|
// Reset view + selection to full range.
|
||||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
||||||
app.timeSelectionStart = 0.0f; app.timeSelectionEnd = 1.0f;
|
ClearSelection();
|
||||||
app.freqSelectionStart = 0.0f; app.freqSelectionEnd = 1.0f;
|
|
||||||
|
|
||||||
// Invalidate the cached visible texture.
|
// Invalidate the cached visible texture.
|
||||||
if (app.visibleTexture.id != 0) UnloadTexture(app.visibleTexture);
|
if (app.visibleTexture.id != 0) UnloadTexture(app.visibleTexture);
|
||||||
@@ -223,8 +222,8 @@ int main(int argc, char* argv[])
|
|||||||
TraceLog(LOG_WARNING, "Failed to load TTF font, using default bitmap font");
|
TraceLog(LOG_WARNING, "Failed to load TTF font, using default bitmap font");
|
||||||
}
|
}
|
||||||
|
|
||||||
app.timeSelectionStart = 0.0f; app.timeSelectionEnd = 1.0f;
|
app.sel.timeStart = 0.0f; app.sel.timeEnd = 1.0f;
|
||||||
app.freqSelectionStart = 0.0f; app.freqSelectionEnd = 1.0f;
|
app.sel.freqStart = 0.0f; app.sel.freqEnd = 1.0f;
|
||||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
||||||
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
||||||
app.showGrid = true;
|
app.showGrid = true;
|
||||||
@@ -326,7 +325,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
// Track playhead position manually
|
// Track playhead position manually
|
||||||
app.playheadElapsed += GetFrameTime();
|
app.playheadElapsed += GetFrameTime();
|
||||||
float selectionDuration = (app.timeSelectionEnd - app.timeSelectionStart) * app.signal.duration;
|
float selectionDuration = (app.sel.timeEnd - app.sel.timeStart) * app.signal.duration;
|
||||||
if (selectionDuration > 0) {
|
if (selectionDuration > 0) {
|
||||||
app.playheadT = app.playheadElapsed / selectionDuration;
|
app.playheadT = app.playheadElapsed / selectionDuration;
|
||||||
}
|
}
|
||||||
@@ -528,10 +527,7 @@ int main(int argc, char* argv[])
|
|||||||
app.showFileBrowser = false;
|
app.showFileBrowser = false;
|
||||||
} else {
|
} else {
|
||||||
// Clear selections instead of exiting
|
// Clear selections instead of exiting
|
||||||
app.timeSelectionStart = 0.0f;
|
ClearSelection();
|
||||||
app.timeSelectionEnd = 1.0f;
|
|
||||||
app.freqSelectionStart = 0.0f;
|
|
||||||
app.freqSelectionEnd = 1.0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,15 +552,12 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
// Right-click clears selection
|
// Right-click clears selection
|
||||||
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) && CheckCollisionPointRec(mousePos, selBounds)) {
|
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||||
app.timeSelectionStart = 0.0f;
|
ClearSelection();
|
||||||
app.timeSelectionEnd = 1.0f;
|
|
||||||
app.freqSelectionStart = 0.0f;
|
|
||||||
app.freqSelectionEnd = 1.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if click is inside existing selection (for dragging)
|
// Check if click is inside existing selection (for dragging)
|
||||||
bool hasSelection = (app.timeSelectionStart > 0.001f || app.timeSelectionEnd < 0.999f ||
|
bool hasSelection = (app.sel.timeStart > 0.001f || app.sel.timeEnd < 0.999f ||
|
||||||
app.freqSelectionStart > 0.001f || app.freqSelectionEnd < 0.999f);
|
app.sel.freqStart > 0.001f || app.sel.freqEnd < 0.999f);
|
||||||
bool clickInsideSelection = false;
|
bool clickInsideSelection = false;
|
||||||
bool hoverInsideSelection = false;
|
bool hoverInsideSelection = false;
|
||||||
if (hasSelection && CheckCollisionPointRec(mousePos, selBounds)) {
|
if (hasSelection && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||||
@@ -574,8 +567,8 @@ int main(int argc, char* argv[])
|
|||||||
float mouseTime = app.viewStart + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth;
|
float mouseTime = app.viewStart + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth;
|
||||||
float mouseFreq = app.freqViewStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth;
|
float mouseFreq = app.freqViewStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth;
|
||||||
|
|
||||||
if (mouseTime >= app.timeSelectionStart && mouseTime <= app.timeSelectionEnd &&
|
if (mouseTime >= app.sel.timeStart && mouseTime <= app.sel.timeEnd &&
|
||||||
mouseFreq >= app.freqSelectionStart && mouseFreq <= app.freqSelectionEnd) {
|
mouseFreq >= app.sel.freqStart && mouseFreq <= app.sel.freqEnd) {
|
||||||
hoverInsideSelection = true;
|
hoverInsideSelection = true;
|
||||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
clickInsideSelection = true;
|
clickInsideSelection = true;
|
||||||
@@ -584,7 +577,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set cursor based on context
|
// Set cursor based on context
|
||||||
if (app.isDraggingSelection) {
|
if (app.sel.isDragging) {
|
||||||
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); // 4-way arrow while dragging
|
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); // 4-way arrow while dragging
|
||||||
} else if (hoverInsideSelection) {
|
} else if (hoverInsideSelection) {
|
||||||
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); // Pointing hand on hover
|
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); // Pointing hand on hover
|
||||||
@@ -597,7 +590,7 @@ int main(int argc, char* argv[])
|
|||||||
// Set cursor to resize all when near divider
|
// Set cursor to resize all when near divider
|
||||||
if (mouseNearDivider && !app.isDividing) {
|
if (mouseNearDivider && !app.isDividing) {
|
||||||
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL);
|
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL);
|
||||||
} else if (app.isDraggingSelection) {
|
} else if (app.sel.isDragging) {
|
||||||
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); // 4-way arrow while dragging
|
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); // 4-way arrow while dragging
|
||||||
} else if (hoverInsideSelection) {
|
} else if (hoverInsideSelection) {
|
||||||
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); // Pointing hand on hover
|
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); // Pointing hand on hover
|
||||||
@@ -607,85 +600,82 @@ int main(int argc, char* argv[])
|
|||||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
if (clickInsideSelection) {
|
if (clickInsideSelection) {
|
||||||
// Start dragging existing selection
|
// Start dragging existing selection
|
||||||
app.isDraggingSelection = true;
|
app.sel.isDragging = true;
|
||||||
app.dragSelectionStartPos = mousePos;
|
app.sel.dragStartPos = mousePos;
|
||||||
app.dragSelectionTimeStart = app.timeSelectionStart;
|
app.sel.dragTimeStart = app.sel.timeStart;
|
||||||
app.dragSelectionFreqStart = app.freqSelectionStart;
|
app.sel.dragFreqStart = app.sel.freqStart;
|
||||||
} else {
|
} else {
|
||||||
// Start new box selection
|
// Start new box selection
|
||||||
app.isTimeSelecting = true;
|
app.sel.isTimeSelecting = true;
|
||||||
app.isFreqSelecting = true;
|
app.sel.isFreqSelecting = true;
|
||||||
app.selectStartPos = mousePos;
|
app.sel.selectStartPos = mousePos;
|
||||||
|
|
||||||
// Convert screen position to signal coordinates (accounting for zoom)
|
// Convert screen position to signal coordinates (accounting for zoom)
|
||||||
float viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
float viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
||||||
float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
||||||
|
|
||||||
app.timeSelectionStart = Clamp(app.viewStart + viewportT * (app.viewEnd - app.viewStart), 0.0f, 1.0f);
|
app.sel.timeStart = Clamp(app.viewStart + viewportT * (app.viewEnd - app.viewStart), 0.0f, 1.0f);
|
||||||
app.timeSelectionEnd = app.timeSelectionStart;
|
app.sel.timeEnd = app.sel.timeStart;
|
||||||
app.freqSelectionStart = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f);
|
app.sel.freqStart = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f);
|
||||||
app.freqSelectionEnd = app.freqSelectionStart;
|
app.sel.freqEnd = app.sel.freqStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dragging existing selection
|
// Dragging existing selection
|
||||||
if (app.isDraggingSelection && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
if (app.sel.isDragging && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.viewEnd - app.viewStart;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||||
|
|
||||||
float dx = (mousePos.x - app.dragSelectionStartPos.x) / selBounds.width;
|
float dx = (mousePos.x - app.sel.dragStartPos.x) / selBounds.width;
|
||||||
float dy = (mousePos.y - app.dragSelectionStartPos.y) / selBounds.height;
|
float dy = (mousePos.y - app.sel.dragStartPos.y) / selBounds.height;
|
||||||
|
|
||||||
float timeShift = dx * viewWidth;
|
float timeShift = dx * viewWidth;
|
||||||
float freqShift = -dy * freqWidth; // Y is inverted
|
float freqShift = -dy * freqWidth; // Y is inverted
|
||||||
|
|
||||||
float timeWidth = app.timeSelectionEnd - app.timeSelectionStart;
|
float timeWidth = app.sel.timeEnd - app.sel.timeStart;
|
||||||
float freqHeight = app.freqSelectionEnd - app.freqSelectionStart;
|
float freqHeight = app.sel.freqEnd - app.sel.freqStart;
|
||||||
|
|
||||||
app.timeSelectionStart = Clamp(app.dragSelectionTimeStart + timeShift, 0.0f, 1.0f - timeWidth);
|
app.sel.timeStart = Clamp(app.sel.dragTimeStart + timeShift, 0.0f, 1.0f - timeWidth);
|
||||||
app.timeSelectionEnd = app.timeSelectionStart + timeWidth;
|
app.sel.timeEnd = app.sel.timeStart + timeWidth;
|
||||||
app.freqSelectionStart = Clamp(app.dragSelectionFreqStart + freqShift, 0.0f, 1.0f - freqHeight);
|
app.sel.freqStart = Clamp(app.sel.dragFreqStart + freqShift, 0.0f, 1.0f - freqHeight);
|
||||||
app.freqSelectionEnd = app.freqSelectionStart + freqHeight;
|
app.sel.freqEnd = app.sel.freqStart + freqHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating new box selection
|
// Creating new box selection
|
||||||
if ((app.isTimeSelecting || app.isFreqSelecting) && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
if ((app.sel.isTimeSelecting || app.sel.isFreqSelecting) && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||||
float viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
float viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
||||||
float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
||||||
|
|
||||||
app.timeSelectionEnd = Clamp(app.viewStart + viewportT * (app.viewEnd - app.viewStart), 0.0f, 1.0f);
|
app.sel.timeEnd = Clamp(app.viewStart + viewportT * (app.viewEnd - app.viewStart), 0.0f, 1.0f);
|
||||||
app.freqSelectionEnd = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f);
|
app.sel.freqEnd = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
if (app.isDraggingSelection) {
|
if (app.sel.isDragging) {
|
||||||
app.isDraggingSelection = false;
|
app.sel.isDragging = false;
|
||||||
} else if (app.isTimeSelecting || app.isFreqSelecting) {
|
} else if (app.sel.isTimeSelecting || app.sel.isFreqSelecting) {
|
||||||
// Check if drag was large enough (minimum 5 pixels)
|
// Check if drag was large enough (minimum 5 pixels)
|
||||||
float dx = mousePos.x - app.selectStartPos.x;
|
float dx = mousePos.x - app.sel.selectStartPos.x;
|
||||||
float dy = mousePos.y - app.selectStartPos.y;
|
float dy = mousePos.y - app.sel.selectStartPos.y;
|
||||||
float dragDist = sqrtf(dx * dx + dy * dy);
|
float dragDist = sqrtf(dx * dx + dy * dy);
|
||||||
if (dragDist > 5.0f) {
|
if (dragDist > 5.0f) {
|
||||||
// Normalize so start < end
|
// Normalize so start < end
|
||||||
if (app.timeSelectionEnd < app.timeSelectionStart) {
|
if (app.sel.timeEnd < app.sel.timeStart) {
|
||||||
float tmp = app.timeSelectionStart;
|
float tmp = app.sel.timeStart;
|
||||||
app.timeSelectionStart = app.timeSelectionEnd;
|
app.sel.timeStart = app.sel.timeEnd;
|
||||||
app.timeSelectionEnd = tmp;
|
app.sel.timeEnd = tmp;
|
||||||
}
|
}
|
||||||
if (app.freqSelectionEnd < app.freqSelectionStart) {
|
if (app.sel.freqEnd < app.sel.freqStart) {
|
||||||
float tmp = app.freqSelectionStart;
|
float tmp = app.sel.freqStart;
|
||||||
app.freqSelectionStart = app.freqSelectionEnd;
|
app.sel.freqStart = app.sel.freqEnd;
|
||||||
app.freqSelectionEnd = tmp;
|
app.sel.freqEnd = tmp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Drag too small - revert to full range
|
// Drag too small - revert to full range
|
||||||
app.timeSelectionStart = 0.0f;
|
ClearSelection();
|
||||||
app.timeSelectionEnd = 1.0f;
|
|
||||||
app.freqSelectionStart = 0.0f;
|
|
||||||
app.freqSelectionEnd = 1.0f;
|
|
||||||
}
|
}
|
||||||
app.isTimeSelecting = false;
|
app.sel.isTimeSelecting = false;
|
||||||
app.isFreqSelecting = false;
|
app.sel.isFreqSelecting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -982,7 +972,7 @@ int main(int argc, char* argv[])
|
|||||||
app.scopeView.data.sampleRate = app.signal.sampleRate;
|
app.scopeView.data.sampleRate = app.signal.sampleRate;
|
||||||
// Show playhead if playing
|
// Show playhead if playing
|
||||||
if (app.isPlaying) {
|
if (app.isPlaying) {
|
||||||
DrawScopeView(&app.scopeView, app.timeSelectionStart + app.playheadT * (app.timeSelectionEnd - app.timeSelectionStart));
|
DrawScopeView(&app.scopeView, app.sel.timeStart + app.playheadT * (app.sel.timeEnd - app.sel.timeStart));
|
||||||
} else {
|
} else {
|
||||||
DrawScopeView(&app.scopeView, -1.0f);
|
DrawScopeView(&app.scopeView, -1.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-15
@@ -91,6 +91,21 @@ typedef struct {
|
|||||||
int nextOrder;
|
int nextOrder;
|
||||||
} FFTSizeCache;
|
} FFTSizeCache;
|
||||||
|
|
||||||
|
// The time+frequency box selection and its drag/move interaction state.
|
||||||
|
// All coordinates are 0-1 normalized. A "box-select" drags out a new box;
|
||||||
|
// a "move" drags an existing box around.
|
||||||
|
typedef struct {
|
||||||
|
float timeStart, timeEnd; // selected time span
|
||||||
|
float freqStart, freqEnd; // selected frequency span
|
||||||
|
bool isTimeSelecting; // dragging out a new time span
|
||||||
|
bool isFreqSelecting; // dragging out a new frequency span
|
||||||
|
Vector2 selectStartPos; // mouse pos when a box-select began (min-drag check)
|
||||||
|
bool isDragging; // moving an existing selection box
|
||||||
|
Vector2 dragStartPos; // mouse pos when the move began
|
||||||
|
float dragTimeStart; // selection start time when the move began
|
||||||
|
float dragFreqStart; // selection freq start when the move began
|
||||||
|
} Selection;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
AudioSignal signal;
|
AudioSignal signal;
|
||||||
StftResult stft;
|
StftResult stft;
|
||||||
@@ -103,27 +118,14 @@ typedef struct {
|
|||||||
float playheadT; // 0-1 normalized position in selection
|
float playheadT; // 0-1 normalized position in selection
|
||||||
float playheadElapsed; // Elapsed seconds since play started
|
float playheadElapsed; // Elapsed seconds since play started
|
||||||
|
|
||||||
// Time selection (0-1 normalized)
|
// Time + frequency box selection and its drag/move interaction state.
|
||||||
float timeSelectionStart;
|
Selection sel;
|
||||||
float timeSelectionEnd;
|
|
||||||
bool isTimeSelecting;
|
|
||||||
|
|
||||||
// Frequency selection (0-1 normalized)
|
|
||||||
float freqSelectionStart;
|
|
||||||
float freqSelectionEnd;
|
|
||||||
bool isFreqSelecting;
|
|
||||||
|
|
||||||
// Export settings
|
// Export settings
|
||||||
float exportScale;
|
float exportScale;
|
||||||
char exportDir[4096];
|
char exportDir[4096];
|
||||||
char exportMessage[256];
|
char exportMessage[256];
|
||||||
|
|
||||||
Vector2 selectStartPos; // For minimum drag distance check
|
|
||||||
bool isDraggingSelection; // Dragging existing selection box
|
|
||||||
Vector2 dragSelectionStartPos; // Mouse position when started dragging selection
|
|
||||||
float dragSelectionTimeStart; // Selection start time when dragging
|
|
||||||
float dragSelectionFreqStart; // Selection freq start when dragging
|
|
||||||
|
|
||||||
// Viewport/zoom controls
|
// Viewport/zoom controls
|
||||||
float viewStart; // 0-1, start of visible time region
|
float viewStart; // 0-1, start of visible time region
|
||||||
float viewEnd; // 0-1, end of visible time region
|
float viewEnd; // 0-1, end of visible time region
|
||||||
@@ -233,6 +235,13 @@ static inline bool UiModalOpen(void)
|
|||||||
return app.showFileBrowser || app.showAbout;
|
return app.showFileBrowser || app.showAbout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the box selection to the full signal (the "no selection" state).
|
||||||
|
static inline void ClearSelection(void)
|
||||||
|
{
|
||||||
|
app.sel.timeStart = 0.0f; app.sel.timeEnd = 1.0f;
|
||||||
|
app.sel.freqStart = 0.0f; app.sel.freqEnd = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Keymap — single source of truth for global key bindings.
|
// Keymap — single source of truth for global key bindings.
|
||||||
// The dispatcher (DispatchKeymap in spectrogram.c) runs every entry whose
|
// The dispatcher (DispatchKeymap in spectrogram.c) runs every entry whose
|
||||||
|
|||||||
@@ -266,10 +266,10 @@ void ExportPNG(const SpectrogramApp* spa, const char* dirPath)
|
|||||||
int imgH = spa->spectrogramImage.height;
|
int imgH = spa->spectrogramImage.height;
|
||||||
|
|
||||||
// Selection region in image-pixel coordinates
|
// Selection region in image-pixel coordinates
|
||||||
int selX0 = (int)(spa->timeSelectionStart * imgW);
|
int selX0 = (int)(spa->sel.timeStart * imgW);
|
||||||
int selX1 = (int)(spa->timeSelectionEnd * imgW);
|
int selX1 = (int)(spa->sel.timeEnd * imgW);
|
||||||
int selY0 = (int)((1.0f - spa->freqSelectionEnd) * imgH);
|
int selY0 = (int)((1.0f - spa->sel.freqEnd) * imgH);
|
||||||
int selY1 = (int)((1.0f - spa->freqSelectionStart) * imgH);
|
int selY1 = (int)((1.0f - spa->sel.freqStart) * imgH);
|
||||||
|
|
||||||
// Clamp to image bounds
|
// Clamp to image bounds
|
||||||
selX0 = Clamp(selX0, 0, imgW);
|
selX0 = Clamp(selX0, 0, imgW);
|
||||||
@@ -468,10 +468,7 @@ void DrawSidebar(void)
|
|||||||
// Clear selection (identical to the Esc key)
|
// Clear selection (identical to the Esc key)
|
||||||
Rectangle clearButton = { x, y, sidebarWidth - 10 * scale, 25 * scale };
|
Rectangle clearButton = { x, y, sidebarWidth - 10 * scale, 25 * scale };
|
||||||
if (Clicked(clearButton)) {
|
if (Clicked(clearButton)) {
|
||||||
app.timeSelectionStart = 0.0f;
|
ClearSelection();
|
||||||
app.timeSelectionEnd = 1.0f;
|
|
||||||
app.freqSelectionStart = 0.0f;
|
|
||||||
app.freqSelectionEnd = 1.0f;
|
|
||||||
}
|
}
|
||||||
DrawPanelBox(clearButton, (Color){ 80, 50, 50, 255 }, RED);
|
DrawPanelBox(clearButton, (Color){ 80, 50, 50, 255 }, RED);
|
||||||
DrawTextScaled("Clear Selection (ESC)", clearButton.x + 10 * scale, clearButton.y + 6 * scale, 14, WHITE);
|
DrawTextScaled("Clear Selection (ESC)", clearButton.x + 10 * scale, clearButton.y + 6 * scale, 14, WHITE);
|
||||||
|
|||||||
Reference in New Issue
Block a user