#include <3ds.h> #include #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 */ 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 */ 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 */ 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 */ 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 = 10.0f; float scale = 0.5f; float lineHeight = 16.0f; int maxLines = 14; 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 */ 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 */ void guiDisplayVersion(const char* version) { if(!textBuf) return; C2D_SceneBegin(bottomTarget); C2D_Text text; C2D_TextBufClear(textBuf); /* Display "mice - by sillyangel" at bottom center */ const char* 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); } /** * PNG read callback for memory buffer */ static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length) { uint8_t** buffer_ptr = (uint8_t**)png_get_io_ptr(png_ptr); memcpy(data, *buffer_ptr, length); *buffer_ptr += length; } /** * Display album art on top screen */ void guiDisplayAlbumArt(struct metadata_t* metadata) { if(!metadata || !metadata->hasAlbumArt || !metadata->albumArt) return; C2D_SceneBegin(topTarget); /* Decode PNG image */ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if(!png_ptr) return; png_infop info_ptr = png_create_info_struct(png_ptr); if(!info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); return; } if(setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return; } /* Set up custom read function */ uint8_t* buffer_ptr = metadata->albumArt; png_set_read_fn(png_ptr, &buffer_ptr, pngReadCallback); /* Read PNG info */ png_read_info(png_ptr, info_ptr); int width = png_get_image_width(png_ptr, info_ptr); int height = png_get_image_height(png_ptr, info_ptr); png_byte color_type = png_get_color_type(png_ptr, info_ptr); png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); /* Convert to RGBA8 */ if(bit_depth == 16) png_set_strip_16(png_ptr); if(color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png_ptr); if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); if(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); png_read_update_info(png_ptr, info_ptr); /* Allocate image buffer */ png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); if(!row_pointers) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return; } for(int y = 0; y < height; y++) { row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, info_ptr)); if(!row_pointers[y]) { for(int i = 0; i < y; i++) free(row_pointers[i]); free(row_pointers); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return; } } /* Read image data */ png_read_image(png_ptr, row_pointers); /* Scale to fit in top-right corner (max 60x60) */ int displayWidth = width; int displayHeight = height; if(width > 60 || height > 60) { float scale = 60.0f / (width > height ? width : height); displayWidth = (int)(width * scale); displayHeight = (int)(height * scale); } /* Draw the image pixel by pixel */ float startX = 330.0f; /* Top right corner */ float startY = 10.0f; for(int y = 0; y < displayHeight; y++) { int srcY = (y * height) / displayHeight; for(int x = 0; x < displayWidth; x++) { int srcX = (x * width) / displayWidth; png_bytep px = &(row_pointers[srcY][srcX * 4]); u32 color = C2D_Color32(px[0], px[1], px[2], px[3]); C2D_DrawRectSolid(startX + x, startY + y, 0.5f, 1, 1, color); } } /* Clean up */ for(int y = 0; y < height; y++) free(row_pointers[y]); free(row_pointers); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); }