#include <3ds.h> #include #include #include #include #include #include "gui.h" #include "metadata.h" static C3D_RenderTarget* topTarget = NULL; static C3D_RenderTarget* bottomTarget = NULL; static C2D_TextBuf textBuf = NULL; /** * Initialize the GUI system */ int guiInit(void) { gfxInitDefault(); C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); C2D_Init(C2D_DEFAULT_MAX_OBJECTS); C2D_Prepare(); /* Create render targets for top and bottom screens */ topTarget = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT); bottomTarget = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT); if(!topTarget || !bottomTarget) return -1; /* Create text buffer */ textBuf = C2D_TextBufNew(4096); if(!textBuf) return -1; return 0; } /** * Clean up and exit the GUI system */ void guiExit(void) { if(textBuf) C2D_TextBufDelete(textBuf); C2D_Fini(); C3D_Fini(); gfxExit(); } /** * Begin rendering a frame */ void guiBeginFrame(void) { C3D_FrameBegin(C3D_FRAME_SYNCDRAW); } /** * End rendering a frame and display it */ void guiEndFrame(void) { C3D_FrameEnd(0); } /** * Clear the top screen */ void guiClearTopScreen(void) { C2D_TargetClear(topTarget, GUI_COLOR_BG_TOP); } /** * Clear the bottom screen */ void guiClearBottomScreen(void) { C2D_TargetClear(bottomTarget, GUI_COLOR_BG_BOTTOM); } /** * Draw a simple text string at specified position * @param screen Screen to draw on (GFX_TOP or GFX_BOTTOM) * @param x X position * @param y Y position * @param text Text string to draw * @param color Text color * @param scale Text scale */ void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 color, float scale) { if(!text || !textBuf) return; C2D_Text c2dText; C2D_TextBufClear(textBuf); C2D_TextParse(&c2dText, textBuf, text); C2D_TextOptimize(&c2dText); C3D_RenderTarget* target = (screen == GFX_TOP) ? topTarget : bottomTarget; C2D_SceneBegin(target); C2D_DrawText(&c2dText, C2D_WithColor, x, y, 0.5f, scale, scale, color); } /** * Display metadata on the top screen * @param metadata Pointer to metadata structure to display * @param filename Filename to display if no title is available */ void guiDisplayMetadata(struct metadata_t* metadata, const char* filename) { if(!metadata || !filename || !textBuf) return; C2D_SceneBegin(topTarget); /* Extract just the filename without path and extension for fallback */ const char* basename = strrchr(filename, '/'); if(!basename) basename = filename; else basename++; /* Remove file extension for display */ char displayName[64]; strncpy(displayName, basename, sizeof(displayName) - 1); displayName[sizeof(displayName) - 1] = '\0'; char* dot = strrchr(displayName, '.'); if(dot) *dot = '\0'; C2D_Text text; float y = 10.0f; float scale = 0.6f; float lineHeight = 20.0f; C2D_TextBufClear(textBuf); /* Draw title */ if(metadata->title[0]) { char titleBuf[64]; snprintf(titleBuf, sizeof(titleBuf), "%.47s", metadata->title); C2D_TextParse(&text, textBuf, titleBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale, scale, GUI_COLOR_TEXT); } else { char titleBuf[64]; snprintf(titleBuf, sizeof(titleBuf), "%.47s", displayName); C2D_TextParse(&text, textBuf, titleBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale, scale, GUI_COLOR_TEXT); } y += lineHeight; /* Draw artist */ if(metadata->artist[0]) { char artistBuf[64]; snprintf(artistBuf, sizeof(artistBuf), "%.45s", metadata->artist); C2D_TextParse(&text, textBuf, artistBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM); } else { C2D_TextParse(&text, textBuf, "Unknown Artist"); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM); } y += lineHeight; /* Draw album */ if(metadata->album[0]) { char albumBuf[64]; snprintf(albumBuf, sizeof(albumBuf), "%.45s", metadata->album); C2D_TextParse(&text, textBuf, albumBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM); } else { C2D_TextParse(&text, textBuf, "Unknown Album"); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM); } } /** * Display log messages on the top screen * @param messages Array of log message strings * @param count Number of messages in the array * @param scroll Index of first visible message for scrolling */ void guiDisplayLog(const char** messages, int count, int scroll) { if(!messages || count <= 0 || !textBuf) return; C2D_SceneBegin(topTarget); C2D_Text text; float y = 70.0f; /* Start below metadata area */ float scale = 0.4f; float lineHeight = 12.0f; int maxLines = 14; C2D_TextBufClear(textBuf); for(int i = scroll; i < count && (i - scroll) < maxLines; i++) { if(messages[i]) { C2D_TextParse(&text, textBuf, messages[i]); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale, scale, GUI_COLOR_TEXT); y += lineHeight; } } } /** * Display file list on the bottom screen * @param files Array of file/folder names * @param count Number of entries in the array * @param selected Index of currently selected entry * @param scroll Index of first visible entry for scrolling */ void guiDisplayFileList(const char** files, int count, int selected, int scroll) { if(!files || count <= 0 || !textBuf) return; C2D_SceneBegin(bottomTarget); C2D_Text text; float y = 18.0f; /* Start below path display */ float scale = 0.5f; float lineHeight = 16.0f; int maxLines = 13; /* One less line due to path at top */ C2D_TextBufClear(textBuf); for(int i = scroll; i < count && (i - scroll) < maxLines; i++) { if(files[i]) { /* Check if this is a directory */ size_t len = strlen(files[i]); bool isDir = (len > 0 && files[i][len-1] == '/'); /* Draw selection highlight */ if(i == selected) { C2D_DrawRectSolid(5.0f, y - 2.0f, 0.5f, 310.0f, lineHeight, GUI_COLOR_HIGHLIGHT); } /* Truncate if too long */ char displayName[48]; snprintf(displayName, sizeof(displayName), "%.40s", files[i]); C2D_TextParse(&text, textBuf, displayName); C2D_TextOptimize(&text); /* Use different color for directories */ u32 color; if(i == selected) color = GUI_COLOR_ACCENT; else if(isDir) color = C2D_Color32(100, 200, 255, 255); /* Light blue for folders */ else color = GUI_COLOR_TEXT; C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale, scale, color); y += lineHeight; } } } /** * Display playback controls and status on the top screen * @param isPlaying Whether playback is active * @param isPaused Whether playback is paused * @param position Current playback position in seconds * @param duration Total duration in seconds */ void guiDisplayPlaybackStatus(bool isPlaying, bool isPaused, float position, float duration) { if(!textBuf) return; C2D_SceneBegin(topTarget); C2D_Text text; C2D_TextBufClear(textBuf); /* Display status and time at bottom of top screen */ float y = 215.0f; /* Display status */ char statusBuf[64]; if(isPlaying) { if(isPaused) snprintf(statusBuf, sizeof(statusBuf), "Paused"); else snprintf(statusBuf, sizeof(statusBuf), "Playing"); } else { snprintf(statusBuf, sizeof(statusBuf), "Stopped"); } C2D_TextParse(&text, textBuf, statusBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, 0.5f, 0.5f, GUI_COLOR_TEXT); /* Display time if playing */ if(isPlaying && duration > 0) { char timeBuf[32]; int posMin = (int)position / 60; int posSec = (int)position % 60; int durMin = (int)duration / 60; int durSec = (int)duration % 60; snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d / %02d:%02d", posMin, posSec, durMin, durSec); C2D_TextParse(&text, textBuf, timeBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 280.0f, y, 0.5f, 0.5f, 0.5f, GUI_COLOR_TEXT); } } /** * Display version text and credits at bottom of bottom screen * @param version Version string to display */ void guiDisplayVersion(const char* version) { if(!textBuf) return; C2D_SceneBegin(bottomTarget); C2D_Text text; C2D_TextBufClear(textBuf); /* Display "mice - by sillyangel" at bottom center */ char credits[64]; if(version && version[0]) snprintf(credits, sizeof(credits), "mice %s - by sillyangel", version); else snprintf(credits, sizeof(credits), "mice - by sillyangel"); C2D_TextParse(&text, textBuf, credits); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 80.0f, 220.0f, 0.5f, 0.45f, 0.45f, GUI_COLOR_TEXT_DIM); } /** * Display progress bar on the top screen * @param position Current playback position in seconds * @param duration Total duration in seconds */ void guiDisplayProgressBar(float position, float duration) { if(duration <= 0) return; C2D_SceneBegin(topTarget); /* Progress bar at bottom of top screen */ float barY = 205.0f; float barX = 10.0f; float barWidth = 380.0f; float barHeight = 6.0f; /* Background bar */ C2D_DrawRectSolid(barX, barY, 0.5f, barWidth, barHeight, C2D_Color32(50, 50, 60, 255)); /* Progress fill */ float progress = position / duration; if(progress > 1.0f) progress = 1.0f; if(progress < 0.0f) progress = 0.0f; float fillWidth = barWidth * progress; if(fillWidth > 0) { C2D_DrawRectSolid(barX, barY, 0.5f, fillWidth, barHeight, GUI_COLOR_ACCENT); } } /** * Display current directory path on bottom screen * @param path Current directory path */ void guiDisplayCurrentPath(const char* path) { if(!path || !textBuf) return; C2D_SceneBegin(bottomTarget); C2D_Text text; C2D_TextBufClear(textBuf); /* Display path at top of bottom screen */ char pathBuf[64]; snprintf(pathBuf, sizeof(pathBuf), "%.55s", path); C2D_TextParse(&text, textBuf, pathBuf); C2D_TextOptimize(&text); C2D_DrawText(&text, C2D_WithColor, 5.0f, 2.0f, 0.5f, 0.35f, 0.35f, GUI_COLOR_TEXT_DIM); }