diff options
author | Nebuleon <nebuleon@alakazam> | 2013-10-24 07:22:39 +0000 |
---|---|---|
committer | Nebuleon <nebuleon@alakazam> | 2013-10-24 07:22:39 +0000 |
commit | 1de6ac458e2cc9e8715d571700664925d74fb562 (patch) | |
tree | 7519f902dc54f84865e6e7935941ebf3cfcfb126 | |
parent | c3a5e478d9bc54e7008d2fb53919918c0c727cfd (diff) | |
download | ReGBA-1de6ac458e2cc9e8715d571700664925d74fb562.zip ReGBA-1de6ac458e2cc9e8715d571700664925d74fb562.tar.gz ReGBA-1de6ac458e2cc9e8715d571700664925d74fb562.tar.bz2 |
OpenDingux: Add a prototype Saved States menu.
It has no confirmation when deleting a saved state, and it has absolutely no error handling anywhere.
-rw-r--r-- | source/opendingux/draw.c | 76 | ||||
-rw-r--r-- | source/opendingux/draw.h | 13 | ||||
-rw-r--r-- | source/opendingux/gui.c | 223 |
3 files changed, 282 insertions, 30 deletions
diff --git a/source/opendingux/draw.c b/source/opendingux/draw.c index 3ecf0ca..40fc314 100644 --- a/source/opendingux/draw.c +++ b/source/opendingux/draw.c @@ -475,12 +475,12 @@ static inline void gba_upscale_aspect(uint16_t *to, uint16_t *from, } } -static inline void gba_render(uint16_t* Dest, uint16_t* Src, uint32_t Width, uint32_t Height, +static inline void gba_render(uint16_t* Dest, uint16_t* Src, uint32_t SrcPitch, uint32_t DestPitch) { Dest = (uint16_t*) ((uint8_t*) Dest - + ((Height - GBA_SCREEN_HEIGHT) / 2 * DestPitch) - + ((Width - GBA_SCREEN_WIDTH) / 2 * sizeof(uint16_t)) + + ((GCW0_SCREEN_HEIGHT - GBA_SCREEN_HEIGHT) / 2 * DestPitch) + + ((GCW0_SCREEN_WIDTH - GBA_SCREEN_WIDTH) / 2 * sizeof(uint16_t)) ); uint32_t SrcSkip = SrcPitch - GBA_SCREEN_WIDTH * sizeof(uint16_t); uint32_t DestSkip = DestPitch - GBA_SCREEN_WIDTH * sizeof(uint16_t); @@ -499,6 +499,65 @@ static inline void gba_render(uint16_t* Dest, uint16_t* Src, uint32_t Width, uin } } +/* Downscales an image by half in with and in height; also does color + * conversion using the function above. + * Input: + * Src: A pointer to the pixels member of a 240x160 surface to be read by + * this function. The pixel format of this surface is XBGR 1555. + * DestX: The column to start the thumbnail at in the destination surface. + * DestY: The row to start the thumbnail at in the destination surface. + * SrcPitch: The number of bytes making up a scanline in the source + * surface. + * DstPitch: The number of bytes making up a scanline in the destination + * surface. + * Output: + * Dest: A pointer to the pixels member of a surface to be filled with the + * downscaled GBA image. The pixel format of this surface is RGB 565. + */ +void gba_render_half(uint16_t* Dest, uint16_t* Src, uint32_t DestX, uint32_t DestY, + uint32_t SrcPitch, uint32_t DestPitch) +{ + Dest = (uint16_t*) ((uint8_t*) Dest + + (DestY * DestPitch) + + (DestX * sizeof(uint16_t)) + ); + uint32_t SrcSkip = SrcPitch - GBA_SCREEN_WIDTH * sizeof(uint16_t); + uint32_t DestSkip = DestPitch - (GBA_SCREEN_WIDTH / 2) * sizeof(uint16_t); + + uint32_t X, Y; + for (Y = 0; Y < GBA_SCREEN_HEIGHT / 2; Y++) + { + for (X = 0; X < GBA_SCREEN_WIDTH * sizeof(uint16_t) / (sizeof(uint32_t) * 2); X++) + { + /* Before: + * a b c d + * e f g h + * + * After (multiple letters = average): + * abef cdgh + */ + uint32_t b_a = bgr555_to_rgb565(*(uint32_t*) ((uint8_t*) Src )), + d_c = bgr555_to_rgb565(*(uint32_t*) ((uint8_t*) Src + 4)), + f_e = bgr555_to_rgb565(*(uint32_t*) ((uint8_t*) Src + SrcPitch )), + h_g = bgr555_to_rgb565(*(uint32_t*) ((uint8_t*) Src + SrcPitch + 4)); + uint32_t bf_ae = likely(b_a == f_e) + ? b_a + : Average32(b_a, f_e); + uint32_t dh_cg = likely(d_c == h_g) + ? d_c + : Average32(d_c, h_g); + *(uint32_t*) Dest = likely(bf_ae == dh_cg) + ? bf_ae + : Average(Hi(bf_ae), Lo(bf_ae)) | + Raise(Average(Hi(dh_cg), Lo(dh_cg))); + Dest += 2; + Src += 4; + } + Src = (uint16_t*) ((uint8_t*) Src + SrcSkip + SrcPitch); + Dest = (uint16_t*) ((uint8_t*) Dest + DestSkip); + } +} + void ApplyScaleMode(video_scale_type NewMode) { switch (NewMode) @@ -541,7 +600,7 @@ void ReGBA_RenderScreen(void) switch (ScaleMode) { case unscaled: - gba_render(OutputSurface->pixels, GBAScreen, GCW0_SCREEN_WIDTH, GCW0_SCREEN_HEIGHT, GBAScreenSurface->pitch, OutputSurface->pitch); + gba_render(OutputSurface->pixels, GBAScreen, GBAScreenSurface->pitch, OutputSurface->pitch); break; case fullscreen: @@ -618,16 +677,13 @@ u16 *copy_screen() u16 *copy = malloc(GBA_SCREEN_WIDTH * GBA_SCREEN_HEIGHT * sizeof(uint16_t)); u16 *dest_ptr = copy; u16 *src_ptr = GBAScreen; - u32 line_skip = pitch - GBA_SCREEN_WIDTH; u32 x, y; for(y = 0; y < GBA_SCREEN_HEIGHT; y++) { - for(x = 0; x < GBA_SCREEN_WIDTH; x++, src_ptr++, dest_ptr++) - { - *dest_ptr = *src_ptr; - } - src_ptr += line_skip; + memcpy(dest_ptr, src_ptr, GBA_SCREEN_WIDTH * sizeof(u16)); + src_ptr += pitch; + dest_ptr += GBA_SCREEN_WIDTH; } return copy; diff --git a/source/opendingux/draw.h b/source/opendingux/draw.h index aeea56d..744160c 100644 --- a/source/opendingux/draw.h +++ b/source/opendingux/draw.h @@ -46,6 +46,9 @@ extern void ApplyScaleMode(video_scale_type NewMode); */ extern void ScaleModeUnapplied(); +extern void gba_render_half(uint16_t* Dest, uint16_t* Src, uint32_t DestX, uint32_t DestY, + uint32_t SrcPitch, uint32_t DestPitch); + extern void print_string(const char *str, u16 fg_color, u32 x, u32 y); @@ -56,6 +59,16 @@ extern uint32_t GetRenderedWidth(const char* str); extern uint32_t GetRenderedHeight(const char* str); +/* + * Returns a new allocation containing a copy of the GBA screen. Its pixels + * and lines are packed (the pitch is 480 bytes), and its pixel format is + * XBGR 1555. + * Output assertions: + * The returned pointer is non-NULL. If it is NULL, then this is a fatal + * error. + */ +extern uint16_t* copy_screen(); + #define RGB888_TO_RGB565(r, g, b) ( \ (((r) & 0xF8) << 8) | \ (((g) & 0xFC) << 3) | \ diff --git a/source/opendingux/gui.c b/source/opendingux/gui.c index 752925b..fc24a0a 100644 --- a/source/opendingux/gui.c +++ b/source/opendingux/gui.c @@ -51,10 +51,21 @@ enum MenuDataType { TYPE_UINT64, }; +// -- Data -- + +static uint32_t SelectedState = 0; + +// -- Forward declarations -- + struct MenuEntry; struct Menu; +static struct Menu MainMenu; +static struct Menu DebugMenu; + +static void SavedStateUpdatePreview(struct Menu* ActiveMenu); + /* * MenuModifyFunction is the type of a function that acts on an input in the * menu. The function is assigned this input via the MenuEntry struct's @@ -99,11 +110,21 @@ typedef void (*MenuEntryDisplayFunction) (struct MenuEntry*, struct MenuEntry*); typedef void (*MenuEntryFunction) (struct Menu*, struct MenuEntry*); /* + * MenuInitFunction is the type of a function that runs when a menu is being + * initialised. + * Variables: + * 1: A pointer to a variable holding the menu that is being initialised. + * On exit from the function, the menu may be modified to a new one, in + * which case the function has chosen to activate that new menu. This can + * be used when the initialisation has failed for some reason. + */ +typedef void (*MenuInitFunction) (struct Menu**); + +/* * MenuFunction is the type of a function that runs when a menu is being - * initialised or finalised, depending on which member receives a function of - * this type. + * finalised or drawn. * Input: - * 1: The menu that is being initialised or finalised. + * 1: The menu that is being finalised or drawn. */ typedef void (*MenuFunction) (struct Menu*); @@ -154,7 +175,7 @@ struct Menu { MenuModifyFunction ButtonUpFunction; MenuModifyFunction ButtonDownFunction; MenuModifyFunction ButtonLeaveFunction; - MenuFunction InitFunction; + MenuInitFunction InitFunction; MenuFunction EndFunction; void* UserData; uint32_t ActiveEntryIndex; @@ -180,7 +201,10 @@ static void DefaultDownFunction(struct Menu** ActiveMenu, uint32_t* ActiveMenuEn static void DefaultRightFunction(struct Menu* ActiveMenu, struct MenuEntry* ActiveMenuEntry) { - if (ActiveMenuEntry->Kind == KIND_OPTION) + if (ActiveMenuEntry->Kind == KIND_OPTION + || (ActiveMenuEntry->Kind == KIND_CUSTOM /* chose to use this function */ + && ActiveMenuEntry->Target != NULL) + ) { uint32_t* Target = (uint32_t*) ActiveMenuEntry->Target; (*Target)++; @@ -191,7 +215,10 @@ static void DefaultRightFunction(struct Menu* ActiveMenu, struct MenuEntry* Acti static void DefaultLeftFunction(struct Menu* ActiveMenu, struct MenuEntry* ActiveMenuEntry) { - if (ActiveMenuEntry->Kind == KIND_OPTION) + if (ActiveMenuEntry->Kind == KIND_OPTION + || (ActiveMenuEntry->Kind == KIND_CUSTOM /* chose to use this function */ + && ActiveMenuEntry->Target != NULL) + ) { uint32_t* Target = (uint32_t*) ActiveMenuEntry->Target; if (*Target == 0) @@ -375,6 +402,29 @@ static void DefaultSaveFunction(struct MenuEntry* ActiveMenuEntry, char* Value) ActiveMenuEntry->Choices[*((uint32_t*) ActiveMenuEntry->Target)].Pretty); } +// -- Custom initialisation/finalisation -- + +static void SavedStateMenuInit(struct Menu** ActiveMenu) +{ + (*ActiveMenu)->UserData = calloc(GBA_SCREEN_WIDTH * GBA_SCREEN_HEIGHT, sizeof(uint16_t)); + if ((*ActiveMenu)->UserData == NULL) + { + ReGBA_Trace("E: Memory allocation error while entering the Saved States menu"); + *ActiveMenu = (*ActiveMenu)->Parent; + } + else + SavedStateUpdatePreview(*ActiveMenu); +} + +static void SavedStateMenuEnd(struct Menu* ActiveMenu) +{ + if (ActiveMenu->UserData != NULL) + { + free(ActiveMenu->UserData); + ActiveMenu->UserData = NULL; + } +} + // -- Custom display -- static char* OpenDinguxButtonText[OPENDINGUX_BUTTON_COUNT] = { @@ -499,6 +549,57 @@ static void DisplayHotkeyValue(struct MenuEntry* DrawnMenuEntry, struct MenuEntr ReGBA_Trace("W: Hid value '%s' from the menu due to it being too long", Value); } +static void SavedStateMenuDisplayData(struct Menu* ActiveMenu, struct MenuEntry* ActiveMenuEntry) +{ + print_string_outline("Preview", COLOR_INACTIVE_TEXT, COLOR_INACTIVE_OUTLINE, GCW0_SCREEN_WIDTH - GBA_SCREEN_WIDTH / 2, GetRenderedHeight(" ") * 2 + 1); + + gba_render_half((uint16_t*) OutputSurface->pixels, (uint16_t*) ActiveMenu->UserData, + GCW0_SCREEN_WIDTH - GBA_SCREEN_WIDTH / 2, + GetRenderedHeight(" ") * 3 + 1, + GBA_SCREEN_WIDTH * sizeof(uint16_t), + OutputSurface->pitch); + + DefaultDisplayDataFunction(ActiveMenu, ActiveMenuEntry); +} + +static void SavedStateSelectionDisplayValue(struct MenuEntry* DrawnMenuEntry, struct MenuEntry* ActiveMenuEntry) +{ + char Value[11]; + sprintf(Value, "%" PRIu32, *(uint32_t*) DrawnMenuEntry->Target + 1); + uint32_t TextWidth = GetRenderedWidth(Value); + if (TextWidth <= GCW0_SCREEN_WIDTH - GBA_SCREEN_WIDTH / 2 - 18) + { + bool IsActive = (DrawnMenuEntry == ActiveMenuEntry); + uint16_t TextColor = IsActive ? COLOR_ACTIVE_TEXT : COLOR_INACTIVE_TEXT; + uint16_t OutlineColor = IsActive ? COLOR_ACTIVE_OUTLINE : COLOR_INACTIVE_OUTLINE; + print_string_outline(Value, TextColor, OutlineColor, GCW0_SCREEN_WIDTH - GBA_SCREEN_WIDTH / 2 - TextWidth - 17, GetRenderedHeight(" ") * (DrawnMenuEntry->Position + 2) + 1); + } + else + ReGBA_Trace("W: Hid value '%s' from the menu due to it being too long", Value); +} + +static void SavedStateUpdatePreview(struct Menu* ActiveMenu) +{ + memset(ActiveMenu->UserData, 0, GBA_SCREEN_WIDTH * GBA_SCREEN_HEIGHT * sizeof(u16)); + char SavedStateFilename[MAX_PATH + 1]; + if (!ReGBA_GetSavedStateFilename(SavedStateFilename, CurrentGamePath, SelectedState)) + { + ReGBA_Trace("W: Failed to get the name of saved state #%d for '%s'", SelectedState, CurrentGamePath); + return; + } + + FILE_TAG_TYPE fp; + FILE_OPEN(fp, SavedStateFilename, READ); + if (!FILE_CHECK_VALID(fp)) + return; + + FILE_SEEK(fp, SVS_HEADER_SIZE + sizeof(struct ReGBA_RTC), SEEK_SET); + + FILE_READ(fp, ActiveMenu->UserData, GBA_SCREEN_WIDTH * GBA_SCREEN_HEIGHT * sizeof(u16)); + + FILE_CLOSE(fp); +} + // -- Custom saving -- static char OpenDinguxButtonSave[OPENDINGUX_BUTTON_COUNT] = { @@ -782,10 +883,41 @@ static void ActionSetOrClearHotkey(struct Menu** ActiveMenu, uint32_t* ActiveMen : ButtonTotal; } -// -- Forward declarations -- +static void SavedStateSelectionLeft(struct Menu* ActiveMenu, struct MenuEntry* ActiveMenuEntry) +{ + DefaultLeftFunction(ActiveMenu, ActiveMenuEntry); + SavedStateUpdatePreview(ActiveMenu); +} -static struct Menu MainMenu; -static struct Menu DebugMenu; +static void SavedStateSelectionRight(struct Menu* ActiveMenu, struct MenuEntry* ActiveMenuEntry) +{ + DefaultRightFunction(ActiveMenu, ActiveMenuEntry); + SavedStateUpdatePreview(ActiveMenu); +} + +static void ActionSavedStateRead(struct Menu** ActiveMenu, uint32_t* ActiveMenuEntryIndex) +{ + load_state(SelectedState); + *ActiveMenu = NULL; +} + +static void ActionSavedStateWrite(struct Menu** ActiveMenu, uint32_t* ActiveMenuEntryIndex) +{ + save_state(SelectedState, MainMenu.UserData /* preserved screenshot */); + SavedStateUpdatePreview(*ActiveMenu); +} + +static void ActionSavedStateDelete(struct Menu** ActiveMenu, uint32_t* ActiveMenuEntryIndex) +{ + char SavedStateFilename[MAX_PATH + 1]; + if (!ReGBA_GetSavedStateFilename(SavedStateFilename, CurrentGamePath, SelectedState)) + { + ReGBA_Trace("W: Failed to get the name of saved state #%d for '%s'", SelectedState, CurrentGamePath); + return; + } + remove(SavedStateFilename); + SavedStateUpdatePreview(*ActiveMenu); +} // -- Debug > Native code stats -- @@ -1152,6 +1284,38 @@ static struct Menu HotkeyMenu = { .Entries = { &HotkeyMenu_FastForward, &HotkeyMenu_Menu, &HotkeyMenu_FastForwardToggle, NULL } }; +// -- Saved States -- + +static struct MenuEntry SavedStateMenu_SelectedState = { + .Kind = KIND_CUSTOM, .Position = 0, .Name = "Save slot #", .PersistentName = "", + .Target = &SelectedState, + .ChoiceCount = 100, + .ButtonLeftFunction = SavedStateSelectionLeft, .ButtonRightFunction = SavedStateSelectionRight, + .DisplayValueFunction = SavedStateSelectionDisplayValue +}; + +static struct MenuEntry SavedStateMenu_Read = { + .Kind = KIND_CUSTOM, .Position = 2, .Name = "Load from selected slot", + .ButtonEnterFunction = ActionSavedStateRead +}; + +static struct MenuEntry SavedStateMenu_Write = { + .Kind = KIND_CUSTOM, .Position = 3, .Name = "Save to selected slot", + .ButtonEnterFunction = ActionSavedStateWrite +}; + +static struct MenuEntry SavedStateMenu_Delete = { + .Kind = KIND_CUSTOM, .Position = 4, .Name = "Delete selected state", + .ButtonEnterFunction = ActionSavedStateDelete +}; + +static struct Menu SavedStateMenu = { + .Parent = &MainMenu, .Title = "Saved states", + .InitFunction = SavedStateMenuInit, .EndFunction = SavedStateMenuEnd, + .DisplayDataFunction = SavedStateMenuDisplayData, + .Entries = { &SavedStateMenu_SelectedState, &SavedStateMenu_Read, &SavedStateMenu_Write, &SavedStateMenu_Delete, NULL } +}; + // -- Main Menu -- static struct MenuEntry MainMenu_Display = { @@ -1169,6 +1333,11 @@ static struct MenuEntry MainMenu_Hotkey = { .Target = &HotkeyMenu }; +static struct MenuEntry MainMenu_SavedStates = { + .Kind = KIND_SUBMENU, .Position = 4, .Name = "Saved states...", + .Target = &SavedStateMenu +}; + static struct MenuEntry MainMenu_Debug = { .Kind = KIND_SUBMENU, .Position = 7, .Name = "Performance and debugging...", .Target = &DebugMenu @@ -1191,16 +1360,27 @@ static struct MenuEntry MainMenu_Exit = { static struct Menu MainMenu = { .Parent = NULL, .Title = "ReGBA Main Menu", - .Entries = { &MainMenu_Display, &MainMenu_Input, &MainMenu_Hotkey, &MainMenu_Debug, &MainMenu_Reset, &MainMenu_Return, &MainMenu_Exit, NULL } + .Entries = { &MainMenu_Display, &MainMenu_Input, &MainMenu_Hotkey, &MainMenu_SavedStates, &MainMenu_Debug, &MainMenu_Reset, &MainMenu_Return, &MainMenu_Exit, NULL } }; u32 ReGBA_Menu(enum ReGBA_MenuEntryReason EntryReason) { SDL_PauseAudio(SDL_ENABLE); + MainMenu.UserData = copy_screen(); ScaleModeUnapplied(); - struct Menu* ActiveMenu = &MainMenu; + struct Menu *ActiveMenu = &MainMenu, *PreviousMenu = ActiveMenu; if (MainMenu.InitFunction != NULL) - (*(MainMenu.InitFunction))(&MainMenu); + { + (*(MainMenu.InitFunction))(&ActiveMenu); + while (PreviousMenu != ActiveMenu) + { + if (PreviousMenu->EndFunction != NULL) + (*(PreviousMenu->EndFunction))(PreviousMenu); + PreviousMenu = ActiveMenu; + if (ActiveMenu != NULL && ActiveMenu->InitFunction != NULL) + (*(ActiveMenu->InitFunction))(&ActiveMenu); + } + } while (ActiveMenu != NULL) { @@ -1223,8 +1403,6 @@ u32 ReGBA_Menu(enum ReGBA_MenuEntryReason EntryReason) // sync.) usleep(5000); - struct Menu* PreviousMenu = ActiveMenu; - // Get input. enum GUI_Action Action = GetGUIAction(); @@ -1283,12 +1461,13 @@ u32 ReGBA_Menu(enum ReGBA_MenuEntryReason EntryReason) } // Possibly finalise this menu and activate and initialise a new one. - if (ActiveMenu != PreviousMenu) + while (ActiveMenu != PreviousMenu) { if (PreviousMenu->EndFunction != NULL) (*(PreviousMenu->EndFunction))(PreviousMenu); + PreviousMenu = ActiveMenu; if (ActiveMenu != NULL && ActiveMenu->InitFunction != NULL) - (*(ActiveMenu->InitFunction))(ActiveMenu); + (*(ActiveMenu->InitFunction))(&ActiveMenu); } } @@ -1309,6 +1488,8 @@ u32 ReGBA_Menu(enum ReGBA_MenuEntryReason EntryReason) timespec Now; clock_gettime(CLOCK_MONOTONIC, &Now); Stats.LastFPSCalculationTime = Now; + if (MainMenu.UserData != NULL) + free(MainMenu.UserData); return 0; } @@ -1499,10 +1680,12 @@ void ReGBA_LoadSettings(char *cfg_name) { ReGBA_Trace("W: Option '%s' not found; ignored", opt); } - - MenuPersistenceFunction LoadFunction = entry->LoadFunction; - if (LoadFunction == NULL) LoadFunction = &DefaultLoadFunction; - (*LoadFunction)(entry, arg); + else + { + MenuPersistenceFunction LoadFunction = entry->LoadFunction; + if (LoadFunction == NULL) LoadFunction = &DefaultLoadFunction; + (*LoadFunction)(entry, arg); + } } ReGBA_ProgressUpdate(1, 1); } |