diff --git a/Makefile b/Makefile index df989653..3fc53828 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ SRCS = \ libs/miniz/miniz_zip.c \ libs/miniz/miniz.c \ menu/actions.c \ + menu/bookkeeping.c \ menu/cart_load.c \ menu/disk_info.c \ menu/fonts.c \ @@ -56,12 +57,14 @@ SRCS = \ menu/ui_components/common.c \ menu/ui_components/context_menu.c \ menu/ui_components/file_list.c \ + menu/ui_components/tabs.c \ menu/usb_comm.c \ menu/views/browser.c \ menu/views/credits.c \ menu/views/error.c \ menu/views/fault.c \ menu/views/file_info.c \ + menu/views/history_favorites.c \ menu/views/image_viewer.c \ menu/views/text_viewer.c \ menu/views/load_disk.c \ diff --git a/src/menu/actions.c b/src/menu/actions.c index 4cafbce7..659c134d 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -22,6 +22,8 @@ static void actions_clear (menu_t *menu) { menu->actions.options = false; menu->actions.settings = false; menu->actions.lz_context = false; + menu->actions.previous_tab = false; + menu->actions.next_tab = false; } static void actions_update_direction (menu_t *menu) { @@ -109,6 +111,10 @@ static void actions_update_buttons (menu_t *menu) { menu->actions.settings = true; } else if (pressed.l || pressed.z) { menu->actions.lz_context = true; + } else if (pressed.c_left) { + menu->actions.previous_tab = true; + } else if (pressed.c_right) { + menu->actions.next_tab = true; } } diff --git a/src/menu/bookkeeping.c b/src/menu/bookkeeping.c new file mode 100644 index 00000000..dbd0045a --- /dev/null +++ b/src/menu/bookkeeping.c @@ -0,0 +1,204 @@ +#include +#include + +#include "bookkeeping.h" +#include "utils/fs.h" +#include "path.h" + +static char *history_path = NULL; + +static path_t *empty_path = NULL; +static bookkeeping_t init; + +/** @brief Init history path */ +void bookkeeping_init (char *path) { + if (history_path) { + free(history_path); + } + history_path = strdup(path); + empty_path = path_create(""); +} + + +void bookkeeping_ini_load_list(bookkeeping_item_t *list, int count, mini_t *ini, const char *group) +{ + char buf[64]; + for(int i=0; ihistory_items, HISTORY_COUNT, bookkeeping_ini, "history"); + bookkeeping_ini_load_list(history->favorite_items, HISTORY_COUNT, bookkeeping_ini, "favorite"); + + + mini_free(bookkeeping_ini); +} + +static void bookkeeping_ini_save_list(bookkeeping_item_t *list, int count, mini_t *ini, const char *group) +{ + char buf[64]; + for(int i=0; ihistory_items, HISTORY_COUNT, bookkeeping_ini, "history"); + bookkeeping_ini_save_list(history->favorite_items, FAVORITES_COUNT, bookkeeping_ini, "favorite"); + + mini_save(bookkeeping_ini, MINI_FLAGS_SKIP_EMPTY_GROUPS); + mini_free(bookkeeping_ini); +} + +static bool bookkeeping_item_match(bookkeeping_item_t *left, bookkeeping_item_t *right) { + if(left != NULL && right != NULL) { + return path_are_match(left->primary_path, right->primary_path) && path_are_match(left->secondary_path, right->secondary_path) && left->bookkeeping_type == right->bookkeeping_type; + } + + return false; +} + +static void bookkeeping_clear_item(bookkeeping_item_t *item, bool leave_null) { + if(item->primary_path != NULL){ + path_free(item->primary_path); + + if(leave_null) { + item->primary_path = NULL; + } else { + item->primary_path = path_create(""); + } + } + if(item->secondary_path != NULL){ + path_free(item->secondary_path); + + if(leave_null) { + item->secondary_path = NULL; + } else { + item->secondary_path = path_create(""); + } + } + item->bookkeeping_type = BOOKKEEPING_TYPE_EMPTY; +} + +static void bookkeeping_copy_item(bookkeeping_item_t *source, bookkeeping_item_t *destination) { + bookkeeping_clear_item(destination, true); + + destination->primary_path = path_clone(source->primary_path); + destination->secondary_path = source->secondary_path != NULL ? path_clone(source->secondary_path) : path_create(""); + destination->bookkeeping_type = source->bookkeeping_type; +} + +static void bookkeeping_move_items_down(bookkeeping_item_t *list, int start, int end) { + int current = end; + + do { + if(current <= start || current < 0) { + break; + } + + bookkeeping_copy_item(&list[current - 1], &list[current]); + current--; + } while(true); +} + + +static void bookkeeping_move_items_up(bookkeeping_item_t *list, int start, int end) { + int current = start; + + do { + if(current > end) { + break; + } + + bookkeeping_copy_item(&list[current + 1], &list[current]); + current++; + } while(true); +} + + +static void bookkeeping_insert_top(bookkeeping_item_t *list, int count, bookkeeping_item_t *new_item) { + // if it matches the top of the list already then nothing to do + if(bookkeeping_item_match(&list[0], new_item)) { + return; + } + + // if the top isn't empty then we need to move things around + if(list[0].bookkeeping_type != BOOKKEEPING_TYPE_EMPTY) { + int found_at = -1; + for(int i=1; i < count; i++) { + if(bookkeeping_item_match(&list[i], new_item)){ + found_at = i; + break; + } + } + + if(found_at == -1) { + bookkeeping_move_items_down(list, 0, count - 1); + } else { + bookkeeping_move_items_down(list, 0, found_at); + } + } + + bookkeeping_copy_item(new_item, &list[0]); +} + +void bookkeeping_history_add(bookkeeping_t *bookkeeping, path_t *primary_path, path_t *secondary_path, bookkeeping_item_types_t type ) { + bookkeeping_item_t new_item = { + .primary_path = primary_path, + .secondary_path = secondary_path, + .bookkeeping_type = type + }; + + bookkeeping_insert_top(bookkeeping->history_items, HISTORY_COUNT, &new_item); + bookkeeping_save(bookkeeping); +} + + +void bookkeeping_favorite_add(bookkeeping_t *bookkeeping, path_t *primary_path, path_t *secondary_path, bookkeeping_item_types_t type ) { + bookkeeping_item_t new_item = { + .primary_path = primary_path, + .secondary_path = secondary_path, + .bookkeeping_type = type + }; + + bookkeeping_insert_top(bookkeeping->favorite_items, FAVORITES_COUNT, &new_item); + bookkeeping_save(bookkeeping); +} + +void bookkeeping_favorite_remove(bookkeeping_t *bookkeeping, int selection) { + if(bookkeeping->favorite_items[selection].bookkeeping_type != BOOKKEEPING_TYPE_EMPTY) { + + bookkeeping_move_items_up(bookkeeping->favorite_items, selection, FAVORITES_COUNT -1); + bookkeeping_clear_item(&bookkeeping->favorite_items[FAVORITES_COUNT -1], false); + + bookkeeping_save(bookkeeping); + } +} \ No newline at end of file diff --git a/src/menu/bookkeeping.h b/src/menu/bookkeeping.h new file mode 100644 index 00000000..1525cca2 --- /dev/null +++ b/src/menu/bookkeeping.h @@ -0,0 +1,75 @@ +/** + * @file bookkeeping.h + * @brief Bookkeeping of loadded ROM's. + * @ingroup menu + */ + +#ifndef BOOKKEEPING_H__ +#define BOOKKEEPING_H__ + +#include "path.h" + + +#define FAVORITES_COUNT 8 +#define HISTORY_COUNT 8 + +typedef enum { + BOOKKEEPING_TYPE_EMPTY, + BOOKKEEPING_TYPE_ROM, + BOOKKEEPING_TYPE_DISK, +} bookkeeping_item_types_t; + +typedef struct { + path_t *primary_path; + path_t *secondary_path; + + bookkeeping_item_types_t bookkeeping_type; + +} bookkeeping_item_t; + +/** @brief ROM bookkeeping Structure */ +typedef struct { + bookkeeping_item_t history_items[HISTORY_COUNT]; + + bookkeeping_item_t favorite_items[HISTORY_COUNT]; +} bookkeeping_t; + + +/** @brief Init ROM bookkeeping path */ +void bookkeeping_init (char *path); + +/** @brief The ROM bookkeeping to load */ +void bookkeeping_load (bookkeeping_t *history); + +/** @brief The ROM bookkeeping to save */ +void bookkeeping_save (bookkeeping_t *history); + +/** + * @brief Add a ROM to the history. + * + * @param bookkeeping The bookkeeping structure. + * @param primary_path The primary path of the ROM. + * @param secondary_path The secondary path of the ROM. + * @param type The type of the bookkeeping item. + */ +void bookkeeping_history_add(bookkeeping_t *bookkeeping, path_t *primary_path, path_t *secondary_path, bookkeeping_item_types_t type ); + +/** + * @brief Add a ROM to the favorites. + * + * @param bookkeeping The bookkeeping structure. + * @param primary_path The primary path of the ROM. + * @param secondary_path The secondary path of the ROM. + * @param type The type of the bookkeeping item. + */ +void bookkeeping_favorite_add(bookkeeping_t *bookkeeping, path_t *primary_path, path_t *secondary_path, bookkeeping_item_types_t type ); + +/** + * @brief Remove a ROM from the favorites. + * + * @param bookkeeping The bookkeeping structure. + * @param selection The index of the favorite item to remove. + */ +void bookkeeping_favorite_remove(bookkeeping_t *bookkeeping, int selection); + +#endif diff --git a/src/menu/menu.c b/src/menu/menu.c index a30ff6f0..626de387 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -20,15 +20,16 @@ #include "views/views.h" -#define MENU_DIRECTORY "/menu" -#define MENU_SETTINGS_FILE "config.ini" -#define MENU_CUSTOM_FONT_FILE "custom.font64" +#define MENU_DIRECTORY "/menu" +#define MENU_SETTINGS_FILE "config.ini" +#define MENU_CUSTOM_FONT_FILE "custom.font64" +#define MENU_ROM_LOAD_HISTORY_FILE "history.ini" -#define MENU_CACHE_DIRECTORY "cache" -#define BACKGROUND_CACHE_FILE "background.data" +#define MENU_CACHE_DIRECTORY "cache" +#define BACKGROUND_CACHE_FILE "background.data" -#define INTERLACED (true) -#define FPS_LIMIT (30.0f) +#define INTERLACED (true) +#define FPS_LIMIT (30.0f) static menu_t *menu; @@ -70,6 +71,13 @@ static void menu_init (boot_params_t *boot_params) { settings_load(&menu->settings); path_pop(path); + path_push(path, MENU_ROM_LOAD_HISTORY_FILE); + bookkeeping_init(path_get(path)); + bookkeeping_load(&menu->bookkeeping); + menu->load.load_history = -1; + menu->load.load_favorite = -1; + path_pop(path); + resolution_t resolution = { .width = 640, .height = 480, @@ -150,6 +158,8 @@ static view_t menu_views[] = { { MENU_MODE_LOAD_EMULATOR, view_load_emulator_init, view_load_emulator_display }, { MENU_MODE_ERROR, view_error_init, view_error_display }, { MENU_MODE_FAULT, view_fault_init, view_fault_display }, + { MENU_MODE_FAVORITE, view_favorite_init, view_favorite_display }, + { MENU_MODE_HISTORY, view_history_init, view_history_display } }; static view_t *menu_get_view (menu_mode_t id) { diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index b34b6d1a..1be91bb7 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -16,6 +16,7 @@ #include "path.h" #include "rom_info.h" #include "settings.h" +#include "bookkeeping.h" /** @brief Menu mode enumeration */ @@ -38,6 +39,8 @@ typedef enum { MENU_MODE_ERROR, MENU_MODE_FAULT, MENU_MODE_BOOT, + MENU_MODE_FAVORITE, + MENU_MODE_HISTORY } menu_mode_t; /** @brief File entry type enumeration */ @@ -67,6 +70,7 @@ typedef struct { const char *storage_prefix; settings_t settings; + bookkeeping_t bookkeeping; boot_params_t *boot_params; char *error_message; @@ -86,6 +90,8 @@ typedef struct { bool options; bool settings; bool lz_context; + bool previous_tab; + bool next_tab; } actions; struct { @@ -103,6 +109,8 @@ typedef struct { rom_info_t rom_info; path_t *disk_path; disk_info_t disk_info; + int load_history; + int load_favorite; bool combined_disk_rom; } load; diff --git a/src/menu/path.c b/src/menu/path.c index 59f622f2..580aec06 100644 --- a/src/menu/path.c +++ b/src/menu/path.c @@ -19,7 +19,7 @@ static void path_resize (path_t *path, size_t min_length) { assert(path->buffer != NULL); } -static path_t *path_create (const char *string) { +path_t *path_create (const char *string) { if (string == NULL) { string = ""; } @@ -141,3 +141,22 @@ void path_ext_replace (path_t *path, char *ext) { path_append(path, "."); path_append(path, ext); } + +bool path_has_value(path_t *path) { + if(path != NULL) { + if(strlen(path->buffer) > 0) { + return true; + } + } + return false; +} + +bool path_are_match(path_t *left, path_t *right) { + if(!path_has_value(left) && !path_has_value(right)) { + return true; + } else if(path_has_value(left) && path_has_value(right)) { + return (strcmp(path_get(left), path_get(right)) == 0); + } else { + return false; + } +} \ No newline at end of file diff --git a/src/menu/path.h b/src/menu/path.h index df2672d2..0b0f534c 100644 --- a/src/menu/path.h +++ b/src/menu/path.h @@ -19,7 +19,7 @@ typedef struct { size_t capacity; } path_t; - +path_t *path_create (const char *string); path_t *path_init (const char *prefix, char *string); void path_free (path_t *path); path_t *path_clone (path_t *string); @@ -33,6 +33,7 @@ void path_push_subdir (path_t *path, char *string); char *path_ext_get (path_t *path); void path_ext_remove (path_t *path); void path_ext_replace (path_t *path, char *ext); - +bool path_has_value(path_t *path); +bool path_are_match(path_t *left, path_t *right); #endif diff --git a/src/menu/ui_components.h b/src/menu/ui_components.h index f0dc6060..4eb6775d 100644 --- a/src/menu/ui_components.h +++ b/src/menu/ui_components.h @@ -50,6 +50,11 @@ void ui_components_box_draw(int x0, int y0, int x1, int y1, color_t color); */ void ui_components_border_draw(int x0, int y0, int x1, int y1); +/** + * @brief Draw the layout component with tabs. + */ +void ui_components_layout_draw_tabbed(void); + /** * @brief Draw the layout component. */ @@ -252,4 +257,21 @@ void ui_components_boxart_free(component_boxart_t *b); */ void ui_components_boxart_draw(component_boxart_t *b); +/** + * @brief Draw the tabs component. + * + * @param text Array of tab labels. + * @param count Number of tabs. + * @param selected Index of the selected tab. + * @param width Width of the tabs. + */ +void ui_components_tabs_draw(const char **text, int count, int selected, float width ); + +/** + * @brief Draw the common part of the tabs component. + * + * @param selected Index of the selected tab. + */ +void ui_components_tabs_common_draw(int selected); + #endif /* UI_COMPONENTS_H__ */ diff --git a/src/menu/ui_components/common.c b/src/menu/ui_components/common.c index dec31885..8e5f0a94 100644 --- a/src/menu/ui_components/common.c +++ b/src/menu/ui_components/common.c @@ -13,9 +13,10 @@ void ui_components_box_draw (int x0, int y0, int x1, int y1, color_t color) { rdpq_mode_pop(); } -void ui_components_border_draw (int x0, int y0, int x1, int y1) { + +static void ui_components_border_draw_internal (int x0, int y0, int x1, int y1, color_t color) { rdpq_mode_push(); - rdpq_set_mode_fill(BORDER_COLOR); + rdpq_set_mode_fill(color); rdpq_fill_rectangle(x0 - BORDER_THICKNESS, y0 - BORDER_THICKNESS, x1 + BORDER_THICKNESS, y0); rdpq_fill_rectangle(x0 - BORDER_THICKNESS, y1, x1 + BORDER_THICKNESS, y1 + BORDER_THICKNESS); @@ -25,6 +26,27 @@ void ui_components_border_draw (int x0, int y0, int x1, int y1) { rdpq_mode_pop(); } +void ui_components_border_draw (int x0, int y0, int x1, int y1) { + ui_components_border_draw_internal(x0, y0, x1, y1, BORDER_COLOR); +} + +void ui_components_layout_draw_tabbed (void) { + ui_components_border_draw( + VISIBLE_AREA_X0, + VISIBLE_AREA_Y0 + TAB_HEIGHT + BORDER_THICKNESS, + VISIBLE_AREA_X1, + VISIBLE_AREA_Y1 + ); + + ui_components_box_draw( + VISIBLE_AREA_X0, + LAYOUT_ACTIONS_SEPARATOR_Y, + VISIBLE_AREA_X1, + LAYOUT_ACTIONS_SEPARATOR_Y + BORDER_THICKNESS, + BORDER_COLOR + ); +} + void ui_components_layout_draw (void) { ui_components_border_draw( VISIBLE_AREA_X0, @@ -195,3 +217,74 @@ void ui_components_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t vali free(formatted); } } + +void ui_components_tabs_draw(const char **text, int count, int selected, float width ) { + float starting_x = VISIBLE_AREA_X0; + + float x = starting_x; + float y = OVERSCAN_HEIGHT; + float height = TAB_HEIGHT; + + // first draw the tabs that are not selected + for(int i=0;i< count;i++) { + if(i != selected) { + + ui_components_box_draw( + x, + y, + x + width, + y + height, + TAB_INACTIVE_BACKGROUND_COLOR + ); + + ui_components_border_draw_internal( + x, + y, + x + width, + y + height, + TAB_INACTIVE_BORDER_COLOR + ); + } + x += width; + } + + // draw the selected tab (so it shows up on top of the others) + if(selected >= 0 && selected < count) { + x = starting_x + (width * selected); + + ui_components_box_draw( + x, + y, + x + width, + y + height, + TAB_ACTIVE_BACKGROUND_COLOR + ); + + ui_components_border_draw_internal( + x, + y, + x + width, + y + height, + TAB_ACTIVE_BORDER_COLOR + ); + } + + // write the text on the tabs + rdpq_textparms_t tab_textparms = { + .width = width, + .height = 24, + .align = ALIGN_CENTER, + .wrap = WRAP_NONE + }; + x = starting_x; + for(int i=0;i< count;i++) { + rdpq_text_print( + &tab_textparms, + FNT_DEFAULT, + x, + y, + text[i] + ); + x += width; + } +} diff --git a/src/menu/ui_components/constants.h b/src/menu/ui_components/constants.h index c468a327..86bde0ce 100644 --- a/src/menu/ui_components/constants.h +++ b/src/menu/ui_components/constants.h @@ -7,6 +7,12 @@ #ifndef COMPONENTS_CONSTANTS_H__ #define COMPONENTS_CONSTANTS_H__ +/** @brief the height of the tabs in the main menu */ +#define TAB_HEIGHT (20) + +/** @brief The thickness of borders. */ +#define BORDER_THICKNESS (4) + /** @brief The display width. */ #define DISPLAY_WIDTH (640) /** @brief The display height. */ @@ -36,9 +42,7 @@ /** @brief The height of the visible display. */ #define VISIBLE_AREA_HEIGHT (VISIBLE_AREA_Y1 - VISIBLE_AREA_Y0) -/** @brief The thickness of borders. */ -#define BORDER_THICKNESS (4) - +/** @brief The layout actions seperator y. */ #define LAYOUT_ACTIONS_SEPARATOR_Y (400) /** @brief The seek bar height. */ @@ -101,14 +105,14 @@ /** @brief The scroll bar width. */ #define LIST_SCROLLBAR_WIDTH (12) /** @brief The scroll bar height. */ -#define LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT) +#define LIST_SCROLLBAR_HEIGHT (LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - TAB_HEIGHT - BORDER_THICKNESS) /** @brief The scroll bar position on the X axis. */ #define LIST_SCROLLBAR_X (VISIBLE_AREA_X1 - LIST_SCROLLBAR_WIDTH) /** @brief The scroll bar position on the Y axis. */ -#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0) +#define LIST_SCROLLBAR_Y (VISIBLE_AREA_Y0 + TAB_HEIGHT + BORDER_THICKNESS) /** @brief The maximum amount of file list entries. */ -#define LIST_ENTRIES (19) +#define LIST_ENTRIES (18) /** @brief The maximum width available for a file list entry. */ #define FILE_LIST_MAX_WIDTH (480) #define FILE_LIST_HIGHLIGHT_WIDTH (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - LIST_SCROLLBAR_WIDTH) @@ -146,5 +150,13 @@ /** @brief The menu highlight colour. */ #define CONTEXT_MENU_HIGHLIGHT_COLOR RGBA32(0x3F, 0x3F, 0x3F, 0xFF) +/** @brief The tab inactive border colour. */ +#define TAB_INACTIVE_BORDER_COLOR RGBA32(0x5F, 0x5F, 0x5F, 0xFF) +/** @brief The tab active border colour. */ +#define TAB_ACTIVE_BORDER_COLOR RGBA32(0xFF, 0xFF, 0xFF, 0xFF) +/** @brief The tab inactive background colour. */ +#define TAB_INACTIVE_BACKGROUND_COLOR RGBA32(0x3F, 0x3F, 0x3F, 0xFF) +/** @brief The tab active background colour. */ +#define TAB_ACTIVE_BACKGROUND_COLOR RGBA32(0x6F, 0x6F, 0x6F, 0xFF) #endif diff --git a/src/menu/ui_components/file_list.c b/src/menu/ui_components/file_list.c index af10e3a0..ce77b3eb 100644 --- a/src/menu/ui_components/file_list.c +++ b/src/menu/ui_components/file_list.c @@ -115,7 +115,7 @@ void ui_components_file_list_draw (entry_t *list, int entries, int selected) { layout = rdpq_paragraph_builder_end(); int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines; - int highlight_y = VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL + ((selected - starting_position) * highlight_height); + int highlight_y = VISIBLE_AREA_Y0 + TAB_HEIGHT + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL + ((selected - starting_position) * highlight_height); ui_components_box_draw( FILE_LIST_HIGHLIGHT_X, @@ -128,7 +128,7 @@ void ui_components_file_list_draw (entry_t *list, int entries, int selected) { rdpq_paragraph_render( layout, VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL, - VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL + VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TAB_HEIGHT + TEXT_OFFSET_VERTICAL ); rdpq_paragraph_free(layout); @@ -166,7 +166,7 @@ void ui_components_file_list_draw (entry_t *list, int entries, int selected) { rdpq_paragraph_render( layout, VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL, - VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL + VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TAB_HEIGHT + TEXT_OFFSET_VERTICAL ); rdpq_paragraph_free(layout); diff --git a/src/menu/ui_components/tabs.c b/src/menu/ui_components/tabs.c new file mode 100644 index 00000000..9b627603 --- /dev/null +++ b/src/menu/ui_components/tabs.c @@ -0,0 +1,23 @@ +#include "../ui_components.h" +#include "constants.h" + + +/* Common tabs used for the main menu */ +static const char *tabs[] = { + "Files", + "History", + "Favorites", + NULL +}; + +/** + * Draws the common tabs used for the main menu. + * + * @param selected The index of the currently selected tab. + */ +void ui_components_tabs_common_draw(int selected) +{ + uint8_t tabs_count = 3; + float width = (VISIBLE_AREA_X1 - VISIBLE_AREA_X0 - 8.0f) / (tabs_count + 1 * 0.5f); + ui_components_tabs_draw(tabs, tabs_count, selected, width); +} \ No newline at end of file diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 62ff2129..21c4f76a 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -358,16 +358,21 @@ static void process (menu_t *menu) { } else if (menu->actions.settings) { ui_components_context_menu_show(&settings_context_menu); sound_play_effect(SFX_SETTING); + } else if (menu->actions.next_tab) { + menu->next_mode = MENU_MODE_HISTORY; + } else if (menu->actions.previous_tab) { + menu->next_mode = MENU_MODE_FAVORITE; } } - static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); ui_components_background_draw(); - ui_components_layout_draw(); + ui_components_tabs_common_draw(0); + + ui_components_layout_draw_tabbed(); ui_components_file_list_draw(menu->browser.list, menu->browser.entries, menu->browser.selected); @@ -395,7 +400,7 @@ static void draw (menu_t *menu, surface_t *d) { ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, - "Start: Settings\n" + "^%02XStart: Settings^00\n" "^%02XR: Options^00", menu->browser.entries == 0 ? STL_GRAY : STL_DEFAULT ); @@ -403,10 +408,16 @@ static void draw (menu_t *menu, surface_t *d) { if (menu->current_time >= 0) { ui_components_actions_bar_text_draw( ALIGN_CENTER, VALIGN_TOP, - "\n" + "\n" "%s", ctime(&menu->current_time) ); + } else { + ui_components_actions_bar_text_draw( + ALIGN_CENTER, VALIGN_TOP, + "\n" + "\n" + ); } ui_components_context_menu_draw(&entry_context_menu); diff --git a/src/menu/views/history_favorites.c b/src/menu/views/history_favorites.c new file mode 100644 index 00000000..10e0c72e --- /dev/null +++ b/src/menu/views/history_favorites.c @@ -0,0 +1,217 @@ +#include +#include "views.h" +#include "../bookkeeping.h" +#include "../fonts.h" +#include "../ui_components/constants.h" +#include "../sound.h" + + +typedef enum { + BOOKKEEPING_TAB_CONTEXT_HISTORY, + BOOKKEEPING_TAB_CONTEXT_FAVORITE, + BOOKKEEPING_TAB_CONTEXT_NONE +} bookkeeping_tab_context_t; + + +static bookkeeping_tab_context_t tab_context = BOOKKEEPING_TAB_CONTEXT_NONE; +static int selected_item = -1; +static bookkeeping_item_t *item_list; +static int item_max; + + +static void reset_selected(menu_t *menu) { + selected_item = -1; + + for(unsigned int i=0; i= item_max) { + selected_item = last; + break; + } else if(item_list[selected_item].bookkeeping_type != BOOKKEEPING_TYPE_EMPTY) { + sound_play_effect(SFX_CURSOR); + break; + } + } while (true); +} + +static void move_back() { + int last = selected_item; + do + { + selected_item--; + + if(selected_item < 0) { + selected_item = last; + break; + } else if(item_list[selected_item].bookkeeping_type != BOOKKEEPING_TYPE_EMPTY) { + sound_play_effect(SFX_CURSOR); + break; + } + } while (true); +} + +static void process(menu_t *menu) { + if(menu->actions.go_down) { + move_next(); + } else if(menu->actions.go_up) { + move_back(); + } else if(menu->actions.enter && selected_item != -1) { + + if(tab_context == BOOKKEEPING_TAB_CONTEXT_FAVORITE) { + menu->load.load_favorite = selected_item; + } else if(tab_context == BOOKKEEPING_TAB_CONTEXT_HISTORY) { + menu->load.load_history = selected_item; + } + + if(item_list[selected_item].bookkeeping_type == BOOKKEEPING_TYPE_DISK) { + menu->next_mode = MENU_MODE_LOAD_DISK; + sound_play_effect(SFX_ENTER); + } else if(item_list[selected_item].bookkeeping_type == BOOKKEEPING_TYPE_ROM) { + menu->next_mode = MENU_MODE_LOAD_ROM; + sound_play_effect(SFX_ENTER); + } + } else if (menu->actions.previous_tab) { + if(tab_context == BOOKKEEPING_TAB_CONTEXT_FAVORITE) { + menu->next_mode = MENU_MODE_HISTORY; + } else if(tab_context == BOOKKEEPING_TAB_CONTEXT_HISTORY) { + menu->next_mode = MENU_MODE_BROWSER; + } + } else if (menu->actions.next_tab) { + if(tab_context == BOOKKEEPING_TAB_CONTEXT_FAVORITE) { + menu->next_mode = MENU_MODE_BROWSER; + } else if(tab_context == BOOKKEEPING_TAB_CONTEXT_HISTORY) { + menu->next_mode = MENU_MODE_FAVORITE; + } + }else if(tab_context == BOOKKEEPING_TAB_CONTEXT_FAVORITE && menu->actions.options && selected_item != -1) { + bookkeeping_favorite_remove(&menu->bookkeeping, selected_item); + reset_selected(menu); + sound_play_effect(SFX_SETTING); + } +} + +static void draw_list(menu_t *menu, surface_t *display) { + if(selected_item != -1) { + float highlight_y = VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TAB_HEIGHT + TEXT_OFFSET_VERTICAL + (selected_item * 20 * 2); + + ui_components_box_draw( + VISIBLE_AREA_X0, + highlight_y, + VISIBLE_AREA_X0 + FILE_LIST_HIGHLIGHT_WIDTH + LIST_SCROLLBAR_WIDTH, + highlight_y + 40, + FILE_LIST_HIGHLIGHT_COLOR + ); + } + + char buffer[1024]; + buffer[0] = 0; + + for(unsigned int i=0; i < item_max; i++) { + if(path_has_value(item_list[i].primary_path)) { + sprintf(buffer, "%s%d : %s\n",buffer ,(i+1), path_last_get(item_list[i].primary_path)); + } else { + sprintf(buffer, "%s%d : \n",buffer ,(i+1)); + } + + if(path_has_value(item_list[i].secondary_path)) { + sprintf(buffer, "%s %s\n", buffer, path_last_get(item_list[i].secondary_path)); + } else { + sprintf(buffer, "%s\n", buffer); + } + } + + int nbytes = strlen(buffer); + rdpq_text_printn( + &(rdpq_textparms_t) { + .width = VISIBLE_AREA_WIDTH - (TEXT_MARGIN_HORIZONTAL * 2), + .height = LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - (TEXT_MARGIN_VERTICAL * 2), + .align = ALIGN_LEFT, + .valign = VALIGN_TOP, + .wrap = WRAP_ELLIPSES, + .line_spacing = TEXT_OFFSET_VERTICAL, + }, + FNT_DEFAULT, + VISIBLE_AREA_X0 + TEXT_MARGIN_HORIZONTAL, + VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TAB_HEIGHT + TEXT_OFFSET_VERTICAL, + buffer, + nbytes + ); +} + +static void draw(menu_t *menu, surface_t *display) { + rdpq_attach(display, NULL); + + ui_components_background_draw(); + + if(tab_context == BOOKKEEPING_TAB_CONTEXT_FAVORITE) { + ui_components_tabs_common_draw(2); + } else if(tab_context == BOOKKEEPING_TAB_CONTEXT_HISTORY) { + ui_components_tabs_common_draw(1); + } + + ui_components_layout_draw_tabbed(); + + draw_list(menu, display); + + if(selected_item != -1) { + ui_components_actions_bar_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "A: Load Game\n" + "\n" + ); + + if(tab_context == BOOKKEEPING_TAB_CONTEXT_FAVORITE && selected_item != -1) { + ui_components_actions_bar_text_draw( + ALIGN_RIGHT, VALIGN_TOP, + "R: Remove item\n" + "\n" + ); + } + } + + ui_components_actions_bar_text_draw( + ALIGN_CENTER, VALIGN_TOP, + "\n" + "\n" + ); + + rdpq_detach_show(); +} + +void view_favorite_init (menu_t *menu) { + tab_context = BOOKKEEPING_TAB_CONTEXT_FAVORITE; + item_list = menu->bookkeeping.favorite_items; + item_max = FAVORITES_COUNT; + + reset_selected(menu); +} + +void view_favorite_display (menu_t *menu, surface_t *display) { + process(menu); + draw(menu, display); +} + +void view_history_init (menu_t *menu) { + tab_context = BOOKKEEPING_TAB_CONTEXT_HISTORY; + item_list = menu->bookkeeping.history_items; + item_max = HISTORY_COUNT; + + reset_selected(menu); +} + +void view_history_display (menu_t *menu, surface_t *display) { + process(menu); + draw(menu, display); +} diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 359812f7..84a515f2 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -3,9 +3,10 @@ #include "boot/boot.h" #include "../sound.h" #include "views.h" +#include "../bookkeeping.h" static component_boxart_t *boxart; - +static char *disk_filename; static char *convert_error_message (disk_err_t err) { switch (err) { @@ -26,7 +27,21 @@ static char *format_disk_region (disk_region_t region) { } +static void add_favorite (menu_t *menu, void *arg) { + bookkeeping_favorite_add(&menu->bookkeeping, menu->load.disk_path, menu->load.rom_path, BOOKKEEPING_TYPE_DISK); +} + +static component_context_menu_t options_context_menu = { .list = { + { .text = "Add to favorites", .action = add_favorite }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + + static void process (menu_t *menu) { + if (ui_components_context_menu_process(menu, &options_context_menu)) { + return; + } + if (menu->actions.enter) { menu->boot_pending.disk_file = true; menu->load.combined_disk_rom = false; @@ -37,6 +52,9 @@ static void process (menu_t *menu) { } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; + } else if (menu->actions.options) { + ui_components_context_menu_show(&options_context_menu); + sound_play_effect(SFX_SETTING); } } @@ -55,7 +73,7 @@ static void draw (menu_t *menu, surface_t *d) { "64DD disk information\n" "\n" "%s", - menu->browser.entry->name + disk_filename ); ui_components_main_text_draw( @@ -64,9 +82,9 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" "\n" - " Region: %s\n" + " Region: %s\n" " Unique ID: %.4s\n" - " Version: %hhu\n" + " Version: %hhu\n" " Disk type: %d\n" "\n" " %s%s", @@ -74,7 +92,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->load.disk_info.id, menu->load.disk_info.version, menu->load.disk_info.disk_type, - menu->load.rom_path ? "ROM: " : "", + menu->load.rom_path ? "ROM: " : "", menu->load.rom_path ? path_last_get(menu->load.rom_path) : "" ); @@ -88,13 +106,21 @@ static void draw (menu_t *menu, surface_t *d) { ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, "L|Z: Load with ROM\n" + "R: Options\n" + ); + } else { + ui_components_actions_bar_text_draw( + ALIGN_RIGHT, VALIGN_TOP, "\n" + "R: Options\n" ); } if (boxart != NULL) { ui_components_boxart_draw(boxart); } + + ui_components_context_menu_draw(&options_context_menu); } rdpq_detach_show(); @@ -131,6 +157,7 @@ static void load (menu_t *menu) { return; } + bookkeeping_history_add(&menu->bookkeeping, menu->load.disk_path, menu->load.rom_path, BOOKKEEPING_TYPE_DISK); menu->next_mode = MENU_MODE_BOOT; if (menu->load.combined_disk_rom) { @@ -155,6 +182,27 @@ static void deinit (void) { ui_components_boxart_free(boxart); } +static bool load_rom(menu_t* menu, path_t* rom_path) { + if(path_has_value(rom_path)) { + if (menu->load.rom_path) { + path_free(menu->load.rom_path); + menu->load.rom_path = NULL; + } + + menu->load.rom_path = path_clone(rom_path); + + rom_err_t err = rom_info_load(rom_path, &menu->load.rom_info); + if (err != ROM_OK) { + path_free(menu->load.rom_path); + menu->load.rom_path = NULL; + menu_show_error(menu, convert_error_message(err)); + return false; + } + } + + return true; +} + void view_load_disk_init (menu_t *menu) { if (menu->load.disk_path) { path_free(menu->load.disk_path); @@ -163,14 +211,41 @@ void view_load_disk_init (menu_t *menu) { menu->boot_pending.disk_file = false; - menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); + if(menu->load.load_history != -1 || menu->load.load_favorite != -1) { + int id = -1; + bookkeeping_item_t* items; + if(menu->load.load_history != -1) { + id = menu->load.load_history; + items = menu->bookkeeping.history_items; + } else if (menu->load.load_favorite != -1) { + id = menu->load.load_favorite; + items = menu->bookkeeping.favorite_items; + } + + menu->load.load_history = -1; + menu->load.load_favorite = -1; + + menu->load.disk_path = path_clone(items[id].primary_path); + if(!load_rom(menu, items[id].secondary_path)) { + return; + } + + } else { + menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); + } + + menu->load.load_favorite = -1; + menu->load.load_history = -1; + + disk_filename = path_last_get(menu->load.disk_path); disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info); if (err != DISK_OK) { menu_show_error(menu, convert_error_message(err)); return; } + ui_components_context_menu_init(&options_context_menu); boxart = ui_components_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT); } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index bdf5f97b..7505a118 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -5,9 +5,11 @@ #include "views.h" #include #include "utils/fs.h" +#include "../bookkeeping.h" static bool show_extra_info_message = false; static component_boxart_t *boxart; +static char *rom_filename = NULL; static char *convert_error_message (rom_err_t err) { switch (err) { @@ -160,6 +162,10 @@ static void set_autoload_type (menu_t *menu, void *arg) { menu->browser.reload = true; } +static void add_favorite (menu_t *menu, void *arg) { + bookkeeping_favorite_add(&menu->bookkeeping, menu->load.rom_path, NULL, BOOKKEEPING_TYPE_ROM); +} + static component_context_menu_t set_cic_type_context_menu = { .list = { {.text = "Automatic", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_AUTOMATIC) }, {.text = "CIC-6101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_6101) }, @@ -203,6 +209,7 @@ static component_context_menu_t options_context_menu = { .list = { { .text = "Set Save Type", .submenu = &set_save_type_context_menu }, { .text = "Set TV Type", .submenu = &set_tv_type_context_menu }, { .text = "Set ROM to autoload", .action = set_autoload_type }, + { .text = "Add to favorites", .action = add_favorite }, COMPONENT_CONTEXT_MENU_LIST_END, }}; @@ -244,7 +251,7 @@ static void draw (menu_t *menu, surface_t *d) { "N64 ROM information\n" "\n" "%s\n", - menu->browser.entry->name + rom_filename ); ui_components_main_text_draw( @@ -343,6 +350,8 @@ static void load (menu_t *menu) { return; } + bookkeeping_history_add(&menu->bookkeeping, menu->load.rom_path, NULL, BOOKKEEPING_TYPE_ROM); + menu->next_mode = MENU_MODE_BOOT; menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM; @@ -368,8 +377,19 @@ void view_load_rom_init (menu_t *menu) { path_free(menu->load.rom_path); } - menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - } + if(menu->load.load_history != -1) { + menu->load.rom_path = path_clone(menu->bookkeeping.history_items[menu->load.load_history].primary_path); + } else if(menu->load.load_favorite != -1) { + menu->load.rom_path = path_clone(menu->bookkeeping.favorite_items[menu->load.load_favorite].primary_path); + } else { + menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); + } + + rom_filename = path_last_get(menu->load.rom_path); + } + + menu->load.load_favorite = -1; + menu->load.load_history = -1; rom_err_t err = rom_info_load(menu->load.rom_path, &menu->load.rom_info); if (err != ROM_OK) { diff --git a/src/menu/views/views.h b/src/menu/views/views.h index 8c475280..4c319b7e 100644 --- a/src/menu/views/views.h +++ b/src/menu/views/views.h @@ -65,6 +65,12 @@ void view_error_display (menu_t *menu, surface_t *display); void view_fault_init (menu_t *menu); void view_fault_display (menu_t *menu, surface_t *display); +void view_favorite_init (menu_t *menu); +void view_favorite_display (menu_t *menu, surface_t *display); + +void view_history_init (menu_t *menu); +void view_history_display (menu_t *menu, surface_t *display); + void menu_show_error (menu_t *menu, char *error_message); /** @} */ /* view */