Add web build support with custom shell HTML

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-04-12 21:28:18 -07:00
parent ecbbba36db
commit da37cecb59
3 changed files with 310 additions and 51 deletions
+184 -51
View File
@@ -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;