summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNebuleon <nebuleon@alakazam>2013-10-24 07:22:39 +0000
committerNebuleon <nebuleon@alakazam>2013-10-24 07:22:39 +0000
commit1de6ac458e2cc9e8715d571700664925d74fb562 (patch)
tree7519f902dc54f84865e6e7935941ebf3cfcfb126
parentc3a5e478d9bc54e7008d2fb53919918c0c727cfd (diff)
downloadReGBA-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.c76
-rw-r--r--source/opendingux/draw.h13
-rw-r--r--source/opendingux/gui.c223
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);
}