Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ SRCS = \
menu/views/extract_file.c \
menu/views/fault.c \
menu/views/file_info.c \
menu/views/grid.c \
menu/views/history_favorites.c \
menu/views/image_viewer.c \
menu/views/text_viewer.c \
Expand Down Expand Up @@ -142,6 +143,7 @@ DEPS = $(OBJS:.o=.d)

FILESYSTEM = \
$(addprefix $(FILESYSTEM_DIR)/menu/fonts/, $(notdir $(FONTS:%.ttf=%.font64))) \
$(FILESYSTEM_DIR)/menu/fonts/digits/Firple-Bold.font64 \
$(addprefix $(FILESYSTEM_DIR)/menu/sounds/, $(notdir $(SOUNDS_WAV:%.wav=%.wav64))) \
$(addprefix $(FILESYSTEM_DIR)/menu/sounds/, $(notdir $(SOUNDS_XM:%.xm=%.xm64))) \
$(addprefix $(FILESYSTEM_DIR)/menu/sprites/, $(notdir $(IMAGES:%.png=%.sprite)))
Expand All @@ -150,6 +152,12 @@ $(MINIZ_OBJS): N64_CFLAGS+=-Wno-unused-function -fcompare-debug-second
$(SPNG_OBJS): N64_CFLAGS+=-DSPNG_USE_MINIZ -fcompare-debug-second
$(FILESYSTEM_DIR)/menu/fonts/Firple-Bold.font64: MKFONT_FLAGS+=--compress 1 --outline 1 --size 15 --charset $(ASSETS_DIR)/fonts/charset.txt --ellipsis 2026,1
$(FILESYSTEM_DIR)/menu/sounds/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1
# Large digits for Grid group counts. mkfont names its output after the source,
# and needs the ellipsis glyph (.) even though the counts never truncate.
$(FILESYSTEM_DIR)/menu/fonts/digits/Firple-Bold.font64: $(ASSETS_DIR)/fonts/Firple-Bold.ttf
@echo " [FONT] $@"
@mkdir -p $(dir $@)
@$(N64_MKFONT) --compress 1 --outline 3 --size 24 --range 2E-2E --range 30-39 -o $(dir $@) "$<"

$(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null))

Expand Down
7 changes: 5 additions & 2 deletions docs/11_menu_controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ panel opacity, and hue editing.

#### File options
Press `Z` in the file browser to open the options menu. Here you can view entry
properties, delete an entry, or set the current directory as the default folder.
Choose "Controller Pak manager" to open the Controller pane directly.
properties, delete an entry, or open the Controller Pak manager. **Set current
directory as Files start** and **Set current directory as Grid library** apply to
the directory being shown, not the highlighted entry, so open a directory with
`A` before choosing them. See [File operations](31_file_browser.md#usage-instructions)
for the full list.

#### Additional ROM information
<!-- Could use a sample screenshot here -->
Expand Down
9 changes: 8 additions & 1 deletion docs/31_file_browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ The File Browser allows you to navigate and manage files on your N64 flashcart.
2. **Performing File Operations**:
- Highlight the file or directory you want to operate on.
- Press the `Z` Button to open the operations menu.
- Select the desired operation (delete, show properties, set as default, extract) and follow the on-screen prompts.
- Select the desired operation and follow the on-screen prompts:
- **Show entry properties**: Shows file information for the highlighted entry; inside a ZIP archive it opens the extract screen instead.
- **Delete selected entry**: Deletes the highlighted file, or the highlighted directory if it is empty.
- **Extract selected entry**: Only inside a ZIP archive; extracts the highlighted file.
- **Controller Pak manager**: Opens the Controller Pak pane of Settings.
- **Set current directory as Files start**: Files opens in the directory currently being shown (not the highlighted entry) at startup. Stored as `default_directory`.
- **Set current directory as Grid library**: Grid shows the ROMs directly inside the directory currently being shown (not the highlighted entry). Stored as `grid_directory`, and listed as **Grid Library** in Settings > Menu. Only present when the SD card has a `menu/metadata` folder. See [Grid](./34_grid.md#library).
- To choose a directory with either "Set current directory" option, press `A` on it first so its contents are listed, then press `Z`.

3. **Viewing Settings menu**:
- Use `L` and `R` to reach the Settings tab, then select a category and press `A`.
Expand Down
22 changes: 20 additions & 2 deletions docs/32_menu_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Press `B` to return to categories. Tab switching is blocked while a pane has a
modal confirmation or operation open.

In the Menu pane, use Up and Down to select a row and `A` or Left/Right to toggle
a boolean setting. Actions such as Reset to Defaults and clearing Autoload ROM
require `A`. Default Directory is a read-only status row.
a boolean setting. Actions such as Reset to Defaults, clearing Autoload ROM, and
clearing Grid Cache require `A`. Default Directory and Grid Library are
read-only status rows.

### Autoload ROM

Expand Down Expand Up @@ -102,3 +103,20 @@ The selected theme is stored as `theme` in `[menu]`. `[custom_theme]` stores the
preset copied into Custom (`base`), its exact RGBA `palette`, `hue_shift`, and
whether the saved background image is enabled (`background_image`). Settings
files from before themes existed keep showing a previously set background image.

### Grid

These settings are hidden when the SD card has no `menu/metadata` folder, since
Grid is too.

**Boot Into** in Settings > Menu selects **Grid** or **Files**. It defaults to
Files and does not override ROM autoload. The corresponding `[menu]` INI key is
`boot_into_grid` (`false` for Files, `true` for Grid).

**Grid Library** shows the Grid library directory, stored as `grid_directory`
independently of `default_directory`. When absent, it inherits
`default_directory`. To change it, open the directory in Files, press `Z`, and
select **Set current directory as Grid library**. **Grid Cache** shows how many
ROMs Grid has indexed; press `A` to delete the index, including the tile arrangement and view choices. Grid
rebuilds it the next time it opens.
See [Grid](34_grid.md) for controls, artwork, and indexing.
54 changes: 54 additions & 0 deletions docs/34_grid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Grid

Grid is an artwork view of the N64 ROMs in one library directory. It is the
first of the main tabs: Grid, Files, History, Favorites, and Settings.

Grid needs the `menu/metadata` folder described in
[Game Pak and box art](19_gamepak_boxart.md). Without it, the Grid tab, its
settings, and **Set current directory as Grid library** are hidden, and the
menu boots into Files.

**Boot Into** in Settings > Menu opens Grid or Files at startup. Files is the
default. An enabled ROM autoload still takes precedence.

## Library

Grid indexes the `.z64`, `.n64`, `.v64`, and `.rom` files directly inside its
library directory, up to 512 ROMs. Subdirectories and `sc64menu` are not
included. To choose the library, open the directory in Files (press `A` on it
so its contents are listed), press `Z`, and select **Set current directory as
Grid library**. The option always uses the directory being shown, not the
highlighted entry, so pressing it with a subdirectory highlighted sets the
parent. Configurations without a library directory use the Files start
directory.

The index, artwork choice, sorting, grouping, and manual order are stored in
`menu/cache/grid.index`. When Grid first opens after startup, and after
deleting or extracting files in Files, it picks up added ROMs and drops removed
ones, showing a progress bar if reading new ROMs takes a while. Details of ROMs
already in the index are cached; after changing their metadata, select Settings >
Menu > **Grid Cache** and press A to delete the index. This also resets the tile
arrangement and view choices. If the library can't be read, Grid keeps its index
and the Grid Cache row shows **Scan failed**.

Titles, authors, release dates, and artwork come from the `menu/metadata`
layout described in [Game Pak and box art](19_gamepak_boxart.md), or from a
ROM's `.meta` companion or embedded metadata, in the same order ROM details uses.
Without metadata, Grid uses the ROM header title or filename and a placeholder.

On first opening, Grid selects the most recently played ROM.

## Controls

- D-pad or stick: move between tiles and pages.
- A: open ROM details, or expand an author group.
- Hold A and move: rearrange tiles in the ungrouped title order; release to save.
- Start: launch the selected ROM immediately.
- C-Up: title order, which keeps your arrangement. Press again to re-sort by title.
- C-Left: sort by release date, with unknown dates last.
- C-Right: group by the first listed author.
- Z: switch between box and cartridge artwork.
- B: leave an author group, or go to Files.
- L/R: switch tabs.

Inside an author group, C-Up and C-Left sort that group only.
6 changes: 6 additions & 0 deletions src/menu/fonts.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
#include "utils/fs.h"

static rdpq_font_t *default_font;
static rdpq_font_t *large_numbers_font;

void fonts_apply_theme(void) {
const menu_theme_t *theme = theme_get();
if (large_numbers_font) {
rdpq_font_style(large_numbers_font, STL_DEFAULT, &((rdpq_fontstyle_t) { .color = theme->warning }));
}
if (default_font) {
rdpq_font_style(default_font, STL_DEFAULT, &((rdpq_fontstyle_t) { .color = theme->text }));
rdpq_font_style(default_font, STL_GREEN, &((rdpq_fontstyle_t) { .color = theme->success }));
Expand All @@ -31,6 +35,8 @@ static void load_default_font (char *custom_font_path) {
}

default_font = rdpq_font_load(font_path);
large_numbers_font = rdpq_font_load("rom:/menu/fonts/digits/Firple-Bold.font64");
rdpq_text_register_font(FNT_LARGE_NUMBERS, large_numbers_font);
fonts_apply_theme();

rdpq_text_register_font(FNT_DEFAULT, default_font);
Expand Down
1 change: 1 addition & 0 deletions src/menu/fonts.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
typedef enum {
FNT_DEFAULT = 1, /**< Default font type */
FNT_LARGE_NUMBERS = 2, /**< Large digits for Grid group counts */
Comment thread
networkfusion marked this conversation as resolved.
} menu_font_type_t;

/**
Expand Down
7 changes: 7 additions & 0 deletions src/menu/menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ static void menu_init (boot_params_t *boot_params) {
menu->next_mode = MENU_MODE_FAULT;
}

// The menu runs with or without an Expansion Pak, checking is_memory_expanded()
// where it matters. Checking before anything large is allocated also stops
// libdragon warning that the ROM requires one.
debugf("Expansion Pak: %s\n", is_memory_expanded() ? "installed" : "not installed");

joypad_init();
timer_init();
rtc_init();
Expand Down Expand Up @@ -174,6 +179,7 @@ static void menu_deinit (menu_t *menu) {
}
free(menu->browser.list);
path_free(menu->browser.directory);
view_grid_shutdown();
free(menu);

display_close();
Expand All @@ -199,6 +205,7 @@ typedef const struct {

static view_t menu_views[] = {
{ MENU_MODE_STARTUP, view_startup_init, view_startup_display },
{ MENU_MODE_GRID, view_grid_init, view_grid_display },
{ MENU_MODE_BROWSER, view_browser_init, view_browser_display },
{ MENU_MODE_FILE_INFO, view_file_info_init, view_file_info_display },
{ MENU_MODE_IMAGE_VIEWER, view_image_viewer_init, view_image_viewer_display },
Expand Down
4 changes: 4 additions & 0 deletions src/menu/menu_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
typedef enum {
MENU_MODE_NONE,
MENU_MODE_STARTUP,
MENU_MODE_GRID,
MENU_MODE_BROWSER,
MENU_MODE_FILE_INFO,
MENU_MODE_IMAGE_VIEWER,
Expand Down Expand Up @@ -89,6 +90,8 @@ typedef struct {
menu_mode_t next_mode;

const char *storage_prefix;
/** @brief Whether menu/metadata exists; Grid and its settings are hidden without it. */
bool grid_enabled;
settings_t settings;
bookkeeping_t bookkeeping;
boot_params_t *boot_params;
Expand Down Expand Up @@ -130,6 +133,7 @@ typedef struct {

struct {
path_t *rom_path;
bool from_grid;
rom_info_t rom_info;
disk_slot_t disk_slots;
int32_t load_history_id;
Expand Down
7 changes: 7 additions & 0 deletions src/menu/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ static settings_t init = {
.force_progressive_scan = false,
.show_protected_entries = false,
.default_directory = "/",
.boot_into_grid = false,
.grid_directory = "/",
.use_saves_folder = true,
.show_saves_folder = false,
.show_save_files = false,
Expand Down Expand Up @@ -85,6 +87,9 @@ void settings_load (settings_t *settings) {
settings->show_protected_entries = ini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries);
free(settings->default_directory);
settings->default_directory = strdup(ini_get_string(ini, "menu", "default_directory", init.default_directory));
settings->boot_into_grid = ini_get_bool(ini, "menu", "boot_into_grid", init.boot_into_grid);
free(settings->grid_directory);
settings->grid_directory = strdup(ini_get_string(ini, "menu", "grid_directory", settings->default_directory));
settings->use_saves_folder = ini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder);
settings->show_saves_folder = ini_get_bool(ini, "menu", "show_saves_folder", init.show_saves_folder);
settings->show_save_files = ini_get_bool(ini, "menu", "show_save_files", init.show_save_files);
Expand Down Expand Up @@ -129,6 +134,8 @@ void settings_save (settings_t *settings) {
ini_set_bool(ini, "menu", "force_progressive_scan", settings->force_progressive_scan);
ini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries);
ini_set_string(ini, "menu", "default_directory", settings->default_directory);
ini_set_bool(ini, "menu", "boot_into_grid", settings->boot_into_grid);
ini_set_string(ini, "menu", "grid_directory", settings->grid_directory);
ini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder);
ini_set_bool(ini, "menu", "show_saves_folder", settings->show_saves_folder);
ini_set_bool(ini, "menu", "show_save_files", settings->show_save_files);
Expand Down
6 changes: 6 additions & 0 deletions src/menu/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ typedef struct {
/** @brief Default directory to navigate to when menu loads */
char *default_directory;

/** @brief Start in Grid instead of Files (autoload still takes precedence). */
bool boot_into_grid;

/** @brief Directory indexed by Grid, without descending into subdirectories. */
char *grid_directory;

/** @brief Put saves into separate directory */
bool use_saves_folder;

Expand Down
5 changes: 3 additions & 2 deletions src/menu/ui_components.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,10 @@ void ui_components_tabs_draw(const char **text, int count, int selected, float w
/**
* @brief Draw the common part of the tabs component.
*
* @param selected Index of the selected tab.
* @param menu Pointer to the menu structure; Grid is omitted unless enabled.
* @param selected Index of the selected tab, counting Grid as 0.
*/
void ui_components_tabs_common_draw(int selected);
void ui_components_tabs_common_draw(menu_t *menu, int selected);

/**
* @brief Draw a value editor component.
Expand Down
12 changes: 8 additions & 4 deletions src/menu/ui_components/tabs.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @brief Common tab labels used for the main menu.
*/
static const char *tabs[] = {
"Grid",
"Files",
"History",
"Favorites",
Expand All @@ -21,10 +22,13 @@ static const char *tabs[] = {
/**
* @brief Draw the common tabs used for the main menu.
*
* @param selected Index of the currently selected tab.
* @param menu Pointer to the menu structure; Grid is omitted unless enabled.
* @param selected Index of the currently selected tab, counting Grid as 0.
*/
void ui_components_tabs_common_draw(int selected)
void ui_components_tabs_common_draw(menu_t *menu, int selected)
{
uint8_t tabs_count = sizeof(tabs) / sizeof(tabs[0]) - 1;
ui_components_tabs_draw(tabs, tabs_count, selected, (float) VISIBLE_AREA_WIDTH / tabs_count);
/* Grid is the first tab, so hiding it starts the list one later. */
int first = menu->grid_enabled ? 0 : 1;
uint8_t tabs_count = sizeof(tabs) / sizeof(tabs[0]) - 1 - first;
ui_components_tabs_draw(&tabs[first], tabs_count, selected - first, (float) VISIBLE_AREA_WIDTH / tabs_count);
}
24 changes: 18 additions & 6 deletions src/menu/views/browser.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ static const struct substr hidden_prefixes[] = {
// return false;
// }

static bool path_is_hidden (path_t *path) {
char *stripped_path = strip_fs_prefix(path_get(path));
bool view_browser_path_is_hidden (const char *full_path) {
char *stripped_path = strip_fs_prefix((char *) full_path);

// Check for hidden files based on full path
for (size_t i = 0; hidden_root_paths[i] != NULL; i++) {
Expand Down Expand Up @@ -298,7 +298,7 @@ static bool load_directory (menu_t *menu) {

if (!menu->settings.show_protected_entries) {
path_push(path, info.d_name);
hide = path_is_hidden(path);
hide = view_browser_path_is_hidden(path_get(path));
path_pop(path);
}

Expand Down Expand Up @@ -506,6 +506,7 @@ static void delete_entry (menu_t *menu, void *arg) {
}

path_free(path);
view_grid_library_changed();

if (reload_directory(menu)) {
menu->browser.valid = false;
Expand All @@ -530,12 +531,20 @@ static void open_controller_pak (menu_t *menu, void *arg) {
view_settings_open_pane(menu, &settings_pane_controller_pak);
}

static void set_grid_directory (menu_t *menu, void *arg) {
free(menu->settings.grid_directory);
menu->settings.grid_directory = strdup(strip_fs_prefix(path_get(menu->browser.directory)));
settings_save(&menu->settings);
}

static component_context_menu_t entry_context_menu = {
.list = {
{ .text = "Show entry properties", .action = show_properties },
{ .text = "Delete selected entry", .action = delete_entry },
{ .text = "Set current directory as default", .action = set_default_directory },
{ .text = "Controller Pak manager", .action = open_controller_pak },
{ .text = "Set current directory as Files start", .action = set_default_directory },
/* Last, so that hiding it without Grid only shortens the menu. */
{ .text = "Set current directory as Grid library", .action = set_grid_directory },
COMPONENT_CONTEXT_MENU_LIST_END,
}
};
Expand Down Expand Up @@ -665,7 +674,7 @@ static void process (menu_t *menu) {
menu->next_mode = MENU_MODE_HISTORY;
sound_play_effect(SFX_CURSOR);
} else if (menu->actions.tab_left) {
menu->next_mode = MENU_MODE_SETTINGS;
menu->next_mode = menu->grid_enabled ? MENU_MODE_GRID : MENU_MODE_SETTINGS;
sound_play_effect(SFX_CURSOR);
}
}
Expand All @@ -675,7 +684,7 @@ static void draw (menu_t *menu, surface_t *d) {

ui_components_background_draw();

ui_components_tabs_common_draw(0);
ui_components_tabs_common_draw(menu, 1);

ui_components_layout_draw_tabbed();

Expand Down Expand Up @@ -739,6 +748,9 @@ static void draw (menu_t *menu, surface_t *d) {
void view_browser_init (menu_t *menu) {
if (!menu->browser.valid) {
ui_components_context_menu_init(&entry_context_menu);
if (!menu->grid_enabled) {
entry_context_menu.row_count--;
}
ui_components_context_menu_init(&archive_context_menu);
if (load_directory(menu)) {
path_free(menu->browser.directory);
Expand Down
2 changes: 1 addition & 1 deletion src/menu/views/credits.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static void process (menu_t *menu) {
if (menu->actions.back) {
show_oss_lib_info_message = false;
sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER;
menu->next_mode = menu->settings.boot_into_grid && menu->grid_enabled ? MENU_MODE_GRID : MENU_MODE_BROWSER;
} else if (menu->actions.context) {
if (show_oss_lib_info_message) {
show_oss_lib_info_message = false;
Expand Down
Loading
Loading