Add web build support with custom shell HTML
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
+184
-51
@@ -96,6 +96,11 @@ typedef struct {
|
||||
float freqSelectionStart;
|
||||
float freqSelectionEnd;
|
||||
bool isFreqSelecting;
|
||||
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
|
||||
float viewStart; // 0-1, start of visible time region
|
||||
@@ -941,26 +946,66 @@ static void DrawSidebar(void);
|
||||
|
||||
static void DrawSelection(Rectangle bounds)
|
||||
{
|
||||
Color overlayColor = Fade(BLACK, 0.5f);
|
||||
float selStartX = bounds.x + app.timeSelectionStart * bounds.width;
|
||||
float selEndX = bounds.x + app.timeSelectionEnd * bounds.width;
|
||||
// Only draw if selection is not full range AND not currently dragging
|
||||
bool hasSelection = (app.timeSelectionStart > 0.001f || app.timeSelectionEnd < 0.999f ||
|
||||
app.freqSelectionStart > 0.001f || app.freqSelectionEnd < 0.999f);
|
||||
if (!hasSelection || app.isTimeSelecting) return; // Don't draw overlay while dragging
|
||||
|
||||
Color overlayColor = Fade(BLACK, 0.25f); // Lighter overlay
|
||||
|
||||
// Convert signal coordinates to viewport coordinates
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||
|
||||
float selStartX = bounds.x + ((app.timeSelectionStart - app.viewStart) / viewWidth) * bounds.width;
|
||||
float selEndX = bounds.x + ((app.timeSelectionEnd - app.viewStart) / viewWidth) * bounds.width;
|
||||
float selStartY = bounds.y + bounds.height - ((app.freqSelectionEnd - app.freqViewStart) / freqWidth) * bounds.height;
|
||||
float selEndY = bounds.y + bounds.height - ((app.freqSelectionStart - app.freqViewStart) / freqWidth) * bounds.height;
|
||||
|
||||
// Clamp to viewport bounds
|
||||
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
||||
selEndX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selEndX));
|
||||
selStartY = fmaxf(bounds.y, fminf(bounds.y + bounds.height, selStartY));
|
||||
selEndY = fmaxf(bounds.y, fminf(bounds.y + bounds.height, selEndY));
|
||||
|
||||
// Draw overlay outside the selection box
|
||||
DrawRectangle(bounds.x, bounds.y, selStartX - bounds.x, bounds.height, overlayColor);
|
||||
DrawRectangle(selEndX, bounds.y, bounds.x + bounds.width - selEndX, bounds.height, overlayColor);
|
||||
DrawRectangle(selStartX, bounds.y, selEndX - selStartX, selStartY - bounds.y, overlayColor);
|
||||
DrawRectangle(selStartX, selEndY, selEndX - selStartX, bounds.y + bounds.height - selEndY, overlayColor);
|
||||
|
||||
// Draw selection box border
|
||||
DrawRectangleLinesEx((Rectangle){ selStartX, selStartY, selEndX - selStartX, selEndY - selStartY }, 2, YELLOW);
|
||||
}
|
||||
|
||||
if (selStartX > bounds.x) DrawRectangle(bounds.x, bounds.y, selStartX - bounds.x, bounds.height, overlayColor);
|
||||
if (selEndX < bounds.x + bounds.width) DrawRectangle(selEndX, bounds.y, bounds.x + bounds.width - selEndX, bounds.height, overlayColor);
|
||||
|
||||
float selStartY = bounds.y + bounds.height - app.freqSelectionEnd * bounds.height;
|
||||
float selEndY = bounds.y + bounds.height - app.freqSelectionStart * bounds.height;
|
||||
|
||||
if (selStartY > bounds.y) DrawRectangle(selStartX, bounds.y, selEndX - selStartX, selStartY - bounds.y, overlayColor);
|
||||
if (selEndY < bounds.y + bounds.height) DrawRectangle(selStartX, selEndY, selEndX - selStartX, bounds.y + bounds.height - selEndY, overlayColor);
|
||||
|
||||
DrawLineV((Vector2){ selStartX, bounds.y }, (Vector2){ selStartX, bounds.y + bounds.height }, YELLOW);
|
||||
DrawLineV((Vector2){ selEndX, bounds.y }, (Vector2){ selEndX, bounds.y + bounds.height }, YELLOW);
|
||||
|
||||
if (app.freqSelectionStart > 0.0f || app.freqSelectionEnd < 1.0f) {
|
||||
DrawLineV((Vector2){ selStartX, selStartY }, (Vector2){ selEndX, selStartY }, CYAN);
|
||||
DrawLineV((Vector2){ selStartX, selEndY }, (Vector2){ selEndX, selEndY }, CYAN);
|
||||
}
|
||||
static void DrawSelectionDrag(Rectangle bounds)
|
||||
{
|
||||
// Draw bounding box while dragging (no overlay)
|
||||
if ((!app.isTimeSelecting && !app.isFreqSelecting && !app.isDraggingSelection) ||
|
||||
(app.isDraggingSelection && !IsMouseButtonDown(MOUSE_LEFT_BUTTON))) return;
|
||||
|
||||
// Convert signal coordinates to viewport coordinates
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||
|
||||
float selStartX = bounds.x + ((app.timeSelectionStart - app.viewStart) / viewWidth) * bounds.width;
|
||||
float selEndX = bounds.x + ((app.timeSelectionEnd - app.viewStart) / viewWidth) * bounds.width;
|
||||
float selStartY = bounds.y + bounds.height - ((app.freqSelectionEnd - app.freqViewStart) / freqWidth) * bounds.height;
|
||||
float selEndY = bounds.y + bounds.height - ((app.freqSelectionStart - app.freqViewStart) / freqWidth) * bounds.height;
|
||||
|
||||
// Clamp to viewport bounds
|
||||
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
||||
selEndX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selEndX));
|
||||
selStartY = fmaxf(bounds.y, fminf(bounds.y + bounds.height, selStartY));
|
||||
selEndY = fmaxf(bounds.y, fminf(bounds.y + bounds.height, selEndY));
|
||||
|
||||
// Normalize coordinates for drawing
|
||||
float x = selStartX < selEndX ? selStartX : selEndX;
|
||||
float w = fabsf(selEndX - selStartX);
|
||||
float y = selStartY < selEndY ? selStartY : selEndY;
|
||||
float h = fabsf(selEndY - selStartY);
|
||||
|
||||
DrawRectangleLinesEx((Rectangle){ x, y, w, h }, 2, YELLOW);
|
||||
}
|
||||
|
||||
static void DrawSidebar(void)
|
||||
@@ -1139,7 +1184,7 @@ static bool UpdateSlider(Rectangle bounds, float* value)
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
|
||||
SetConfigFlags(FLAG_VSYNC_HINT);
|
||||
InitWindow(1280, 800, "Spectrogram Viewer");
|
||||
SetTargetFPS(60);
|
||||
SetTraceLogLevel(LOG_WARNING); // Suppress INFO texture logs
|
||||
@@ -1227,6 +1272,11 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle window resize
|
||||
if (IsWindowResized()) {
|
||||
app.visibleTextureValid = false;
|
||||
}
|
||||
|
||||
// View controls
|
||||
if (app.loaded && !app.showFileBrowser) {
|
||||
// Spectrogram area fills remaining window space
|
||||
@@ -1357,7 +1407,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
}
|
||||
|
||||
// Selection (only when mouse is in spectrogram area, not sidebar)
|
||||
// Selection: box select with LMB drag, right-click to clear
|
||||
float selSidebarWidth = 320;
|
||||
float selLabelHeight = 15;
|
||||
float selScrollbarHeight = 18;
|
||||
@@ -1374,46 +1424,128 @@ int main(int argc, char* argv[])
|
||||
};
|
||||
Vector2 mousePos = GetMousePosition();
|
||||
|
||||
if (app.loaded && !app.showFileBrowser && mousePos.x > 385 && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
bool shiftHeld = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
|
||||
// Right-click clears selection
|
||||
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
app.timeSelectionStart = 0.0f;
|
||||
app.timeSelectionEnd = 1.0f;
|
||||
app.freqSelectionStart = 0.0f;
|
||||
app.freqSelectionEnd = 1.0f;
|
||||
}
|
||||
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && !app.isPanning) {
|
||||
if (shiftHeld) {
|
||||
app.isFreqSelecting = true;
|
||||
float t = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
||||
app.freqSelectionStart = Clamp(t, 0.0f, 1.0f);
|
||||
app.freqSelectionEnd = app.freqSelectionStart;
|
||||
} else if (!IsKeyDown(KEY_LEFT_ALT) && !IsKeyDown(KEY_RIGHT_ALT)) {
|
||||
app.isTimeSelecting = true;
|
||||
app.timeSelectionStart = Clamp((mousePos.x - selBounds.x) / selBounds.width, 0.0f, 1.0f);
|
||||
app.timeSelectionEnd = app.timeSelectionStart;
|
||||
// Check if click is inside existing selection (for dragging)
|
||||
bool hasSelection = (app.timeSelectionStart > 0.001f || app.timeSelectionEnd < 0.999f ||
|
||||
app.freqSelectionStart > 0.001f || app.freqSelectionEnd < 0.999f);
|
||||
bool clickInsideSelection = false;
|
||||
bool hoverInsideSelection = false;
|
||||
if (hasSelection && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
// Convert mouse position to signal coordinates
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||
float mouseTime = app.viewStart + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth;
|
||||
float mouseFreq = app.freqViewStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth;
|
||||
|
||||
if (mouseTime >= app.timeSelectionStart && mouseTime <= app.timeSelectionEnd &&
|
||||
mouseFreq >= app.freqSelectionStart && mouseFreq <= app.freqSelectionEnd) {
|
||||
hoverInsideSelection = true;
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
clickInsideSelection = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set cursor based on context
|
||||
if (app.isDraggingSelection) {
|
||||
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); // 4-way arrow while dragging
|
||||
} else if (hoverInsideSelection) {
|
||||
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); // Pointing hand on hover
|
||||
} else {
|
||||
SetMouseCursor(MOUSE_CURSOR_DEFAULT); // Normal arrow
|
||||
}
|
||||
|
||||
if (app.isTimeSelecting && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
app.timeSelectionEnd = Clamp((mousePos.x - selBounds.x) / selBounds.width, 0.0f, 1.0f);
|
||||
// LMB drag = box select (time + frequency) OR drag existing selection
|
||||
if (app.loaded && !app.showFileBrowser && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
if (clickInsideSelection) {
|
||||
// Start dragging existing selection
|
||||
app.isDraggingSelection = true;
|
||||
app.dragSelectionStartPos = mousePos;
|
||||
app.dragSelectionTimeStart = app.timeSelectionStart;
|
||||
app.dragSelectionFreqStart = app.freqSelectionStart;
|
||||
} else {
|
||||
// Start new box selection
|
||||
app.isTimeSelecting = true;
|
||||
app.isFreqSelecting = true;
|
||||
app.selectStartPos = mousePos;
|
||||
|
||||
// Convert screen position to signal coordinates (accounting for zoom)
|
||||
float viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
||||
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.timeSelectionEnd = app.timeSelectionStart;
|
||||
app.freqSelectionStart = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f);
|
||||
app.freqSelectionEnd = app.freqSelectionStart;
|
||||
}
|
||||
}
|
||||
if (app.isFreqSelecting && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
float t = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
||||
app.freqSelectionEnd = Clamp(t, 0.0f, 1.0f);
|
||||
|
||||
// Dragging existing selection
|
||||
if (app.isDraggingSelection && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||
|
||||
float dx = (mousePos.x - app.dragSelectionStartPos.x) / selBounds.width;
|
||||
float dy = (mousePos.y - app.dragSelectionStartPos.y) / selBounds.height;
|
||||
|
||||
float timeShift = dx * viewWidth;
|
||||
float freqShift = -dy * freqWidth; // Y is inverted
|
||||
|
||||
float timeWidth = app.timeSelectionEnd - app.timeSelectionStart;
|
||||
float freqHeight = app.freqSelectionEnd - app.freqSelectionStart;
|
||||
|
||||
app.timeSelectionStart = Clamp(app.dragSelectionTimeStart + timeShift, 0.0f, 1.0f - timeWidth);
|
||||
app.timeSelectionEnd = app.timeSelectionStart + timeWidth;
|
||||
app.freqSelectionStart = Clamp(app.dragSelectionFreqStart + freqShift, 0.0f, 1.0f - freqHeight);
|
||||
app.freqSelectionEnd = app.freqSelectionStart + freqHeight;
|
||||
}
|
||||
|
||||
// Creating new box selection
|
||||
if ((app.isTimeSelecting || app.isFreqSelecting) && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
float viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
||||
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.freqSelectionEnd = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
if (app.isTimeSelecting) {
|
||||
if (app.isDraggingSelection) {
|
||||
app.isDraggingSelection = false;
|
||||
} else if (app.isTimeSelecting || app.isFreqSelecting) {
|
||||
// Check if drag was large enough (minimum 5 pixels)
|
||||
float dx = mousePos.x - app.selectStartPos.x;
|
||||
float dy = mousePos.y - app.selectStartPos.y;
|
||||
float dragDist = sqrtf(dx * dx + dy * dy);
|
||||
if (dragDist > 5.0f) {
|
||||
// Normalize so start < end
|
||||
if (app.timeSelectionEnd < app.timeSelectionStart) {
|
||||
float tmp = app.timeSelectionStart;
|
||||
app.timeSelectionStart = app.timeSelectionEnd;
|
||||
app.timeSelectionEnd = tmp;
|
||||
}
|
||||
if (app.freqSelectionEnd < app.freqSelectionStart) {
|
||||
float tmp = app.freqSelectionStart;
|
||||
app.freqSelectionStart = app.freqSelectionEnd;
|
||||
app.freqSelectionEnd = tmp;
|
||||
}
|
||||
} else {
|
||||
// Drag too small - revert to full range
|
||||
app.timeSelectionStart = 0.0f;
|
||||
app.timeSelectionEnd = 1.0f;
|
||||
app.freqSelectionStart = 0.0f;
|
||||
app.freqSelectionEnd = 1.0f;
|
||||
}
|
||||
app.isTimeSelecting = false;
|
||||
if (app.timeSelectionEnd < app.timeSelectionStart) {
|
||||
float tmp = app.timeSelectionStart;
|
||||
app.timeSelectionStart = app.timeSelectionEnd;
|
||||
app.timeSelectionEnd = tmp;
|
||||
}
|
||||
}
|
||||
if (app.isFreqSelecting) {
|
||||
app.isFreqSelecting = false;
|
||||
if (app.freqSelectionEnd < app.freqSelectionStart) {
|
||||
float tmp = app.freqSelectionStart;
|
||||
app.freqSelectionStart = app.freqSelectionEnd;
|
||||
app.freqSelectionEnd = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1566,6 +1698,7 @@ int main(int argc, char* argv[])
|
||||
|
||||
if (app.showGrid) DrawSpectrogramGrid(viewBounds, 10, 8, Fade(GRAY, 0.3f));
|
||||
DrawSelection(viewBounds);
|
||||
DrawSelectionDrag(viewBounds);
|
||||
DrawLabels(viewBounds);
|
||||
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
||||
float freqMin = app.freqViewStart * maxFreq;
|
||||
|
||||
Reference in New Issue
Block a user