| @@ -83,6 +83,68 @@ void* nese_alloc(int size) { | |||
| * Platform-specific features and controls | |||
| */ | |||
| #define ACTION_DELAY (60U * 1U) | |||
| #define ACTION_NOTIFY (60U * 3U) | |||
| typedef struct { | |||
| int timer; | |||
| int msg_id; | |||
| nese_Action action; | |||
| } Action_State; | |||
| static void action_immediate(Action_State* state, nese_Action action) { | |||
| state->action = action; | |||
| state->timer = 0; | |||
| } | |||
| static void action_start(Action_State* state, nese_Action action, | |||
| Overlay* overlay) { | |||
| if (state->action != Action_OK) { | |||
| // We can't do two things at once - cancel the current action | |||
| overlay_clear_message(overlay, state->msg_id); | |||
| state->action = Action_OK; | |||
| } else { | |||
| state->action = action; | |||
| const char* op = NULL; | |||
| if (Action_Load == action) op = "Restoring ..."; | |||
| else if (Action_Save == action) op = "Saving ..."; | |||
| else if (Action_Reset == action) op = "Resetting ..."; | |||
| if (NULL != op) { | |||
| state->timer = ACTION_DELAY; | |||
| state->msg_id = overlay_add_message(overlay, op, state->timer); | |||
| } else { | |||
| state->timer = 0; | |||
| } | |||
| } | |||
| } | |||
| static nese_Action action_tick(Action_State* state, Overlay* overlay) { | |||
| nese_Action action = Action_OK; | |||
| if (state->action != Action_OK && --state->timer <= 0) { | |||
| action = state->action; | |||
| state->action = Action_OK; | |||
| overlay_clear_message(overlay, state->msg_id); | |||
| } | |||
| return action; | |||
| } | |||
| static int action_notify(nese_Action action, int status, Overlay* overlay) { | |||
| int msg_id = 0; | |||
| const char* op = NULL; | |||
| if (Action_Load == action) op = (status < 0 ? "Restore failed" : "Restored"); | |||
| else if (Action_Save == action) op = (status < 0 ? "Save failed" : "Saved"); | |||
| else if (Action_Reset == action) op = "Reset"; | |||
| if (NULL != op) { | |||
| msg_id = overlay_add_message(overlay, op, ACTION_NOTIFY); | |||
| } | |||
| return msg_id; | |||
| } | |||
| #define PLAT_FILENAME_SIZE (1024U) | |||
| typedef enum { | |||
| @@ -98,6 +160,7 @@ typedef struct { | |||
| int fps_msg_id; | |||
| Platform_Flags flags; | |||
| char filename[PLAT_FILENAME_SIZE]; | |||
| Action_State delayed_action; | |||
| } platform_data; | |||
| @@ -221,12 +284,21 @@ static nese_Action process_events(nes* sys) { | |||
| input->gamepads[0].buttons &= ~mask; | |||
| } | |||
| } else if (SDL_KEYDOWN == event.type) { | |||
| } else { | |||
| index = keycode_index( | |||
| event.key.keysym.sym, | |||
| sdl_action_keycodes, Action_Max | |||
| ); | |||
| if (index >= 0) action = index; | |||
| if (index >= 0) { | |||
| if (SDL_KEYDOWN == event.type) { | |||
| action = index; | |||
| } else if ( Action_Load == index || | |||
| Action_Save == index || | |||
| Action_Reset == index) { | |||
| // On key release, cancel certain pending delayed actions | |||
| action = Action_Cancel; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // TODO: Controller inputs | |||
| @@ -314,6 +386,8 @@ int nese_frame_ready(void* plat_data) { | |||
| status = render_frame_end(&plat->renderer); | |||
| } | |||
| overlay_step(&plat->overlay); | |||
| // TODO: Check status first? | |||
| @@ -324,11 +398,10 @@ int nese_frame_ready(void* plat_data) { | |||
| break; | |||
| case Action_Save: | |||
| status = save_state(plat->sys, plat->cart.filename); | |||
| break; | |||
| case Action_Load: | |||
| status = load_state(plat->sys, plat->cart.filename); | |||
| case Action_Reset: | |||
| case Action_Cancel: | |||
| action_start(&plat->delayed_action, action, &plat->overlay); | |||
| break; | |||
| case Action_FPS: | |||
| @@ -346,14 +419,17 @@ int nese_frame_ready(void* plat_data) { | |||
| case Action_Menu: | |||
| action = game_menu(plat); | |||
| if (Action_Reset == action) { | |||
| nes_reset(plat->sys); | |||
| } else if (Action_Save == action) { | |||
| status = save_state(plat->sys, plat->cart.filename); | |||
| } else if (Action_Load == action) { | |||
| status = load_state(plat->sys, plat->cart.filename); | |||
| } else if (Action_Quit == action || NULL == plat->sys->cart_header) { | |||
| switch (action) { | |||
| case Action_Reset: | |||
| case Action_Load: | |||
| case Action_Save: | |||
| action_immediate(&plat->delayed_action, action); | |||
| break; | |||
| default: | |||
| if (NULL != plat->sys->cart_header) break; | |||
| case Action_Quit: | |||
| status = -1; | |||
| break; | |||
| } | |||
| // TODO: Other Actions? | |||
| break; | |||
| @@ -362,6 +438,20 @@ int nese_frame_ready(void* plat_data) { | |||
| } | |||
| // TODO: Perform more actions? | |||
| int action_status = 1; | |||
| action = action_tick(&plat->delayed_action, &plat->overlay); | |||
| if (Action_Reset == action) { | |||
| nes_reset(plat->sys); | |||
| action_status = 0; | |||
| } else if (Action_Save == action) { | |||
| action_status = save_state(plat->sys, plat->cart.filename); | |||
| } else if (Action_Load == action) { | |||
| action_status = load_state(plat->sys, plat->cart.filename); | |||
| } | |||
| if (action_status <= 0) { | |||
| action_notify(action, action_status, &plat->overlay); | |||
| } | |||
| if (0 == status) { | |||
| plat->t_target += FRAME_TIME_NS; | |||