New browser view using rdpq acceleration + MP3 player (#10)

<!--- Provide a general summary of your changes in the Title above -->

## Description
This change replaces deprecated graphics libdragon api with rdpq
hardware accelerated drawing calls.
New view has been added: MP3 player
<!--- Describe your changes in detail -->

## Motivation and Context
Use newest and supported features of libdragon api
<!--- What does this sample do? What problem does it solve? -->
<!--- If it fixes/closes/resolves an open issue, please link to the
issue here -->

## How Has This Been Tested?
On hardware with SC64 flashcart
<!-- (if applicable) -->
<!--- Please describe in detail how you tested your sample/changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Screenshots
![Screenshot 2023-07-08
23-57-56](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/3756990/3f791246-5f70-43d1-8c54-aeac62513ff3)
![Screenshot 2023-07-08
23-58-16](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/3756990/fdf436bf-6201-4b43-bebc-70be993ebc50)

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Improvement (non-breaking change that adds a new feature)
- [ ] Bug fix (fixes an issue)
- [x] Breaking change (breaking change)
- [ ] Config and build (change in the configuration and build system,
has no impact on code or features)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.

<!--- It would be nice if you could sign off your contribution by
replacing the name with your GitHub user name and GitHub email contact.
-->
Signed-off-by: GITHUB_USER <GITHUB_USER_EMAIL>
This commit is contained in:
Mateusz Faderewski 2023-07-09 00:01:41 +02:00 committed by GitHub
parent 9f30e44625
commit b53bbf7dae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 4544 additions and 230 deletions

View File

@ -1,15 +1,16 @@
PROJECT_NAME = N64FlashcartMenu
.DEFAULT_GOAL := $(PROJECT_NAME)
SOURCE_DIR = src
ASSETS_DIR = assets
BUILD_DIR = build
OUTPUT_DIR = output
include $(N64_INST)/include/n64.mk
N64_CFLAGS += -iquote $(SOURCE_DIR)
N64_LDFLAGS += --wrap asset_load
SRCS = \
main.c \
@ -21,7 +22,9 @@ SRCS = \
flashcart/sc64/sc64.c \
libs/toml/toml.c \
menu/actions.c \
menu/assets.c \
menu/menu.c \
menu/mp3player.c \
menu/path.c \
menu/rom_database.c \
menu/settings.c \
@ -29,11 +32,25 @@ SRCS = \
menu/views/credits.c \
menu/views/error.c \
menu/views/file_info.c \
menu/views/init.c \
menu/views/fragments/fragments.c \
menu/views/fragments/widgets.c \
menu/views/load.c \
menu/views/player.c \
menu/views/startup.c \
utils/fs.c
OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS))))
ASSETS = \
FiraMono-Bold.ttf
$(BUILD_DIR)/FiraMono-Bold.o: MKFONT_FLAGS+=--size 16 -r 20-7F -r 2000-206F -r 2190-21FF
$(BUILD_DIR)/%.o: $(ASSETS_DIR)/%.ttf
@echo " [FONT] $@"
@$(N64_MKFONT) $(MKFONT_FLAGS) -o $(ASSETS_DIR) "$<"
@$(N64_OBJCOPY) -I binary -O elf32-bigmips -B mips4300 $(basename $<).font64 $@
@rm $(basename $<).font64
OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS) $(ASSETS))))
$(BUILD_DIR)/$(PROJECT_NAME).elf: $(OBJS)

View File

BIN
assets/FiraMono-Bold.ttf Normal file

Binary file not shown.

@ -1 +1 @@
Subproject commit 7241811bbc5d89e64d6048931c958b0765a1ef70
Subproject commit afa6c25f9e8186432d9480b5fe1aff4f29ab05a1

View File

@ -42,11 +42,11 @@ flashcart_error_t flashcart_deinit (void) {
return flashcart->deinit();
}
flashcart_error_t flashcart_load_rom (char *rom_path) {
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap) {
if ((rom_path == NULL) || (!file_exists(rom_path)) || (file_get_size(rom_path) < KiB(4))) {
return FLASHCART_ERROR_ARGS;
}
return flashcart->load_rom(rom_path);
return flashcart->load_rom(rom_path, byte_swap);
}
flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type, bool save_writeback) {
@ -57,7 +57,9 @@ flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t sa
return FLASHCART_ERROR_ARGS;
}
flashcart->set_save_type(save_type);
if ((error = flashcart->set_save_type(save_type)) != FLASHCART_OK) {
return error;
}
if ((save_path == NULL) || (save_type == FLASHCART_SAVE_TYPE_NONE)) {
return FLASHCART_OK;

View File

@ -29,7 +29,7 @@ typedef enum {
typedef struct {
flashcart_error_t (*init) (void);
flashcart_error_t (*deinit) (void);
flashcart_error_t (*load_rom) (char *rom_path);
flashcart_error_t (*load_rom) (char *rom_path, bool byte_swap);
flashcart_error_t (*load_save) (char *save_path);
flashcart_error_t (*set_save_type) (flashcart_save_type_t save_type);
flashcart_error_t (*set_save_writeback) (uint32_t *sectors);
@ -38,7 +38,7 @@ typedef struct {
flashcart_error_t flashcart_init (void);
flashcart_error_t flashcart_deinit (void);
flashcart_error_t flashcart_load_rom (char *rom_path);
flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap);
flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type, bool save_writeback);

View File

@ -19,7 +19,7 @@
#define EEPROM_ADDRESS (0x1FFE2000)
#define SUPPORTED_MAJOR_VERSION (2)
#define SUPPORTED_MINOR_VERSION (12)
#define SUPPORTED_MINOR_VERSION (16)
static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UINT *br) {
@ -51,6 +51,11 @@ static flashcart_error_t load_to_flash (FIL *fil, void *address, size_t size, UI
return FLASHCART_OK;
}
static void load_cleanup (FIL *fil) {
sc64_sd_set_byte_swap(false);
f_close(fil);
}
static flashcart_error_t sc64_init (void) {
uint16_t major;
@ -71,6 +76,13 @@ static flashcart_error_t sc64_init (void) {
return FLASHCART_ERROR_OUTDATED;
}
bool writeback_pending;
do {
if (sc64_writeback_pending(&writeback_pending) != SC64_OK) {
return FLASHCART_ERROR_INT;
}
} while (writeback_pending);
return FLASHCART_OK;
}
@ -79,7 +91,7 @@ static flashcart_error_t sc64_deinit (void) {
return FLASHCART_OK;
}
static flashcart_error_t sc64_load_rom (char *rom_path) {
static flashcart_error_t sc64_load_rom (char *rom_path, bool byte_swap) {
FIL fil;
UINT br;
@ -99,6 +111,11 @@ static flashcart_error_t sc64_load_rom (char *rom_path) {
return FLASHCART_ERROR_LOAD;
}
if (sc64_sd_set_byte_swap(byte_swap) != SC64_OK) {
load_cleanup(&fil);
return FLASHCART_ERROR_INT;
}
bool shadow_enabled = (rom_size > (MiB(64) - KiB(128)));
bool extended_enabled = (rom_size > MiB(64));
@ -107,48 +124,53 @@ static flashcart_error_t sc64_load_rom (char *rom_path) {
size_t extended_size = extended_enabled ? rom_size - MiB(64) : 0;
if (f_read(&fil, (void *) (ROM_ADDRESS), sdram_size, &br) != FR_OK) {
f_close(&fil);
load_cleanup(&fil);
return FLASHCART_ERROR_LOAD;
}
if (br != sdram_size) {
f_close(&fil);
load_cleanup(&fil);
return FLASHCART_ERROR_LOAD;
}
if (sc64_set_config(CFG_ROM_SHADOW_ENABLE, shadow_enabled) != SC64_OK) {
f_close(&fil);
load_cleanup(&fil);
return FLASHCART_ERROR_INT;
}
if (shadow_enabled) {
flashcart_error_t error = load_to_flash(&fil, (void *) (SHADOW_ADDRESS), shadow_size, &br);
if (error != FLASHCART_OK) {
f_close(&fil);
load_cleanup(&fil);
return error;
}
if (br != shadow_size) {
f_close(&fil);
load_cleanup(&fil);
return FLASHCART_ERROR_LOAD;
}
}
if (sc64_set_config(CFG_ROM_EXTENDED_ENABLE, extended_enabled) != SC64_OK) {
f_close(&fil);
load_cleanup(&fil);
return FLASHCART_ERROR_INT;
}
if (extended_enabled) {
flashcart_error_t error = load_to_flash(&fil, (void *) (EXTENDED_ADDRESS), extended_size, &br);
if (error != FLASHCART_OK) {
f_close(&fil);
load_cleanup(&fil);
return error;
}
if (br != extended_size) {
f_close(&fil);
load_cleanup(&fil);
return FLASHCART_ERROR_LOAD;
}
}
if (sc64_sd_set_byte_swap(false) != SC64_OK) {
load_cleanup(&fil);
return FLASHCART_ERROR_INT;
}
if (f_close(&fil) != FR_OK) {
return FLASHCART_ERROR_LOAD;
}
@ -186,7 +208,7 @@ static flashcart_error_t sc64_load_save (char *save_path) {
return FLASHCART_ERROR_LOAD;
}
size_t save_size = ALIGN(f_size(&fil), FS_SECTOR_SIZE);
size_t save_size = f_size(&fil);
if (f_read(&fil, address, save_size, &br) != FR_OK) {
f_close(&fil);
@ -224,7 +246,7 @@ static flashcart_error_t sc64_set_save_type (flashcart_save_type_t save_type) {
type = SAVE_TYPE_SRAM_BANKED;
break;
case FLASHCART_SAVE_TYPE_SRAM_128K:
type = SAVE_TYPE_SRAM;
type = SAVE_TYPE_SRAM_128K;
break;
case FLASHCART_SAVE_TYPE_FLASHRAM:
type = SAVE_TYPE_FLASHRAM;

View File

@ -29,12 +29,23 @@ typedef enum {
CMD_ID_VERSION_GET = 'V',
CMD_ID_CONFIG_GET = 'c',
CMD_ID_CONFIG_SET = 'C',
CMD_ID_SD_CARD_OP = 'i',
CMD_ID_WRITEBACK_PENDING = 'w',
CMD_ID_WRITEBACK_SD_INFO = 'W',
CMD_ID_FLASH_PROGRAM = 'K',
CMD_ID_FLASH_WAIT_BUSY = 'p',
CMD_ID_FLASH_ERASE_BLOCK = 'P',
} sc64_cmd_id_t;
typedef enum {
SD_CARD_OP_DEINIT = 0,
SD_CARD_OP_INIT = 1,
SD_CARD_OP_GET_STATUS = 2,
SD_CARD_OP_GET_INFO = 3,
SD_CARD_OP_BYTE_SWAP_ON = 4,
SD_CARD_OP_BYTE_SWAP_OFF = 5,
} sc64_sd_card_op_t;
typedef struct {
sc64_cmd_id_t id;
uint32_t arg[2];
@ -110,6 +121,21 @@ sc64_error_t sc64_set_config (sc64_cfg_t id, uint32_t value) {
return sc64_execute_cmd(&cmd);
}
sc64_error_t sc64_sd_set_byte_swap (bool enabled) {
sc64_cmd_t cmd = {
.id = CMD_ID_SD_CARD_OP,
.arg = { 0, enabled ? SD_CARD_OP_BYTE_SWAP_ON : SD_CARD_OP_BYTE_SWAP_OFF }
};
return sc64_execute_cmd(&cmd);
}
sc64_error_t sc64_writeback_pending (bool *pending) {
sc64_cmd_t cmd = { .id = CMD_ID_WRITEBACK_PENDING };
sc64_error_t error = sc64_execute_cmd(&cmd);
*pending = (cmd.rsp[0] != 0);
return error;
}
sc64_error_t sc64_writeback_enable (void *address) {
sc64_cmd_t cmd = { .id = CMD_ID_WRITEBACK_SD_INFO, .arg = { (uint32_t) (address) } };
return sc64_execute_cmd(&cmd);

View File

@ -52,6 +52,7 @@ typedef enum {
SAVE_TYPE_SRAM,
SAVE_TYPE_FLASHRAM,
SAVE_TYPE_SRAM_BANKED,
SAVE_TYPE_SRAM_128K,
} sc64_save_type_t;
@ -63,6 +64,8 @@ void sc64_write_data (void *src, void *dst, size_t length);
sc64_error_t sc64_get_version (uint16_t *major, uint16_t *minor);
sc64_error_t sc64_get_config (sc64_cfg_t cfg, void *value);
sc64_error_t sc64_set_config (sc64_cfg_t cfg, uint32_t value);
sc64_error_t sc64_sd_set_byte_swap (bool enabled);
sc64_error_t sc64_writeback_pending (bool *pending);
sc64_error_t sc64_writeback_enable (void *address);
sc64_error_t sc64_flash_program (void *address, size_t length);
sc64_error_t sc64_flash_wait_busy (void);

1865
src/libs/minimp3/minimp3.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,45 +9,38 @@
#include "menu/settings.h"
static void init (void) {
static void hw_init (void) {
assertf(usb_initialize() != CART_NONE, "No flashcart was detected");
//debug_init_usblog();
assertf(debug_init_sdfs("sd:/", -1), "Couldn't initialize SD card");
flashcart_error_t error = flashcart_init();
assertf(error != FLASHCART_ERROR_OUTDATED, "Outdated flashcart firmware");
assertf(error != FLASHCART_ERROR_UNSUPPORTED, "Unsupported flashcart");
assertf(error == FLASHCART_OK, "Unknown error while initializing flashcart");
controller_init();
display_init(RESOLUTION_640x240, DEPTH_16_BPP, 2, GAMMA_NONE, ANTIALIAS_RESAMPLE);
graphics_set_color(0xFFFFFFFF, 0x00000000);
graphics_set_default_font();
assertf(debug_init_sdfs("sd:/", -1), "Couldn't initialize SD card");
#ifdef DEBUG
debug_init_usblog();
#endif
}
static void deinit (void) {
static void hw_deinit (void) {
flashcart_deinit();
rdpq_close();
rspq_close();
audio_close();
display_close();
disable_interrupts();
}
int main (void) {
init();
settings_t settings;
settings_load_default_state(&settings);
//settings_load_from_file(&settings); // FIXME: this needs a rethink.
// if (boot_is_warm()) {
// menu_restore(&settings);
// }
hw_init();
settings_load_default_state(&settings);
settings_load_from_file(&settings);
menu_run(&settings);
deinit();
hw_deinit();
boot(&settings.boot_params);

View File

@ -3,50 +3,59 @@
#include "actions.h"
#define ACTIONS_REPEAT_DELAY 20
#define ACTIONS_REPEAT_DELAY 16
#define ACTIONS_REPEAT_RATE 2
void actions_update (menu_t *menu) {
static void actions_clear (menu_t *menu) {
menu->actions.go_up = false;
menu->actions.go_down = false;
menu->actions.go_left = false;
menu->actions.go_right = false;
menu->actions.fast = false;
menu->actions.enter = false;
menu->actions.back = false;
menu->actions.info = false;
menu->actions.settings = false;
menu->actions.override = false;
}
void actions_update (menu_t *menu) {
controller_scan();
struct controller_data down = get_keys_down();
struct controller_data held = get_keys_held();
if (down.c[0].err != 0) {
if (down.c[0].err != ERROR_NONE) {
return;
}
actions_clear(menu);
if (down.c[0].up || down.c[0].C_up) {
menu->actions.go_up = true;
menu->actions.held_counter = 0;
menu->actions.vertical_held_counter = 0;
if (down.c[0].C_up) {
menu->actions.fast = true;
}
} else if (down.c[0].down || down.c[0].C_down) {
menu->actions.go_down = true;
menu->actions.held_counter = 0;
menu->actions.vertical_held_counter = 0;
if (down.c[0].C_down) {
menu->actions.fast = true;
}
} else if (held.c[0].up || held.c[0].C_up) {
menu->actions.held_counter += 1;
if ((menu->actions.held_counter >= ACTIONS_REPEAT_DELAY) && (menu->actions.held_counter % ACTIONS_REPEAT_RATE)) {
menu->actions.vertical_held_counter += 1;
if ((menu->actions.vertical_held_counter >= ACTIONS_REPEAT_DELAY) && (menu->actions.vertical_held_counter % ACTIONS_REPEAT_RATE)) {
menu->actions.go_up = true;
if (held.c[0].C_up) {
menu->actions.fast = true;
}
}
} else if (held.c[0].down || held.c[0].C_down) {
menu->actions.held_counter += 1;
if ((menu->actions.held_counter >= ACTIONS_REPEAT_DELAY) && (menu->actions.held_counter % ACTIONS_REPEAT_RATE)) {
menu->actions.vertical_held_counter += 1;
if ((menu->actions.vertical_held_counter >= ACTIONS_REPEAT_DELAY) && (menu->actions.vertical_held_counter % ACTIONS_REPEAT_RATE)) {
menu->actions.go_down = true;
if (held.c[0].C_down) {
menu->actions.fast = true;
@ -54,13 +63,35 @@ void actions_update (menu_t *menu) {
}
}
if (down.c[0].left) {
menu->actions.go_left = true;
menu->actions.horizontal_held_counter = 0;
} else if (down.c[0].right) {
menu->actions.go_right = true;
menu->actions.horizontal_held_counter = 0;
} else if (held.c[0].left) {
menu->actions.horizontal_held_counter += 1;
if ((menu->actions.horizontal_held_counter >= ACTIONS_REPEAT_DELAY) && (menu->actions.horizontal_held_counter % ACTIONS_REPEAT_RATE)) {
menu->actions.go_left = true;
}
} else if (held.c[0].right) {
menu->actions.horizontal_held_counter += 1;
if ((menu->actions.horizontal_held_counter >= ACTIONS_REPEAT_DELAY) && (menu->actions.horizontal_held_counter % ACTIONS_REPEAT_RATE)) {
menu->actions.go_right = true;
}
}
if (down.c[0].A) {
menu->actions.enter = true;
} else if (down.c[0].B) {
menu->actions.back = true;
} else if (down.c[0].Z) {
menu->actions.info = true;
} else if (down.c[0].start) {
} else if (down.c[0].R) {
menu->actions.settings = true;
}
if (down.c[0].B || held.c[0].B) {
menu->actions.override = true;
}
}

View File

@ -2,7 +2,7 @@
#define ACTIONS_H__
#include "menu.h"
#include "menu_state.h"
void actions_update (menu_t *menu);

37
src/menu/assets.c Normal file
View File

@ -0,0 +1,37 @@
#include <stdint.h>
#include <string.h>
typedef struct {
char *name;
uint8_t *data;
int size;
} asset_t;
#define ASSET_IMPORT(a) \
extern uint8_t *_binary_assets_##a##_start __attribute__((section(".data"))); \
extern int _binary_assets_##a##_size __attribute__((section(".data")));
#define ASSET(n, a) { n, (uint8_t *) (&_binary_assets_##a##_start), (int) (&_binary_assets_##a##_size) }
ASSET_IMPORT(FiraMono_Bold_font64);
static asset_t assets[] = {
ASSET("assets:/font", FiraMono_Bold_font64),
};
extern void *__real_asset_load (char *fn, int *sz);
void *__wrap_asset_load (char *fn, int *sz) {
for (int i = 0; i < sizeof(assets) / sizeof(assets[0]); i++) {
asset_t *asset = &assets[i];
if (strcmp(asset->name, fn) == 0) {
*sz = asset->size;
return asset->data;
}
}
return __real_asset_load(fn, sz);
}

View File

@ -3,42 +3,73 @@
#include <libdragon.h>
#include "actions.h"
#include "menu_state.h"
#include "menu.h"
#include "views.h"
#include "settings.h"
#include "utils/fs.h"
#include "views/views.h"
static menu_t *menu_init (settings_t *settings) {
menu_t *menu = calloc(1, sizeof(menu_t));
static menu_t *menu;
static bool boot_pending;
menu->mode = MENU_MODE_INIT;
menu->next_mode = MENU_MODE_BROWSER;
static void menu_init (settings_t *settings) {
controller_init();
audio_init(44100, 2);
mixer_init(2);
display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, ANTIALIAS_OFF);
rspq_init();
rdpq_init();
boot_pending = false;
menu = calloc(1, sizeof(menu_t));
assert(menu != NULL);
menu->mode = MENU_MODE_NONE;
menu->next_mode = MENU_MODE_STARTUP;
menu->assets.font = rdpq_font_load("assets:/font");
menu->assets.font_height = 16;
menu->browser.valid = false;
menu->browser.directory = path_init(NULL); // TODO: load starting directory from settings
return menu;
if (file_exists(settings->last_state.directory)) {
menu->browser.directory = path_init(settings->last_state.directory);
} else {
menu->browser.directory = path_init(NULL);
}
menu->browser.show_hidden = false;
}
static void menu_deinit (menu_t *menu) {
path_free(menu->browser.directory);
// NOTE: font is not loaded dynamically due to hack in assets.c, so there's no need to free it
// rdpq_font_free(menu->assets.font);
free(menu);
rdpq_close();
rspq_close();
display_close();
mixer_close();
audio_close();
}
void menu_run (settings_t *settings) {
menu_t *menu = menu_init(settings);
menu_init(settings);
bool running = true;
int audio_buffer_length = audio_get_buffer_length();
while (running) {
while (!boot_pending && (exception_reset_time() == 0)) {
surface_t *display = display_try_get();
if (display != NULL) {
actions_update(menu);
switch (menu->mode) {
case MENU_MODE_INIT:
view_init_display(menu, display);
case MENU_MODE_STARTUP:
view_startup_display(menu, display);
break;
case MENU_MODE_BROWSER:
@ -49,6 +80,10 @@ void menu_run (settings_t *settings) {
view_file_info_display(menu, display);
break;
case MENU_MODE_PLAYER:
view_player_display(menu, display);
break;
case MENU_MODE_CREDITS:
view_credits_display(menu, display);
break;
@ -62,6 +97,8 @@ void menu_run (settings_t *settings) {
break;
default:
rdpq_attach_clear(display, NULL);
rdpq_detach_show();
break;
}
@ -69,6 +106,10 @@ void menu_run (settings_t *settings) {
menu->mode = menu->next_mode;
switch (menu->next_mode) {
case MENU_MODE_STARTUP:
view_startup_init(menu);
break;
case MENU_MODE_BROWSER:
view_browser_init(menu);
break;
@ -77,6 +118,10 @@ void menu_run (settings_t *settings) {
view_file_info_init(menu);
break;
case MENU_MODE_PLAYER:
view_player_init(menu);
break;
case MENU_MODE_CREDITS:
view_credits_init(menu);
break;
@ -90,7 +135,7 @@ void menu_run (settings_t *settings) {
break;
case MENU_MODE_BOOT:
running = false;
boot_pending = true;
break;
default:
@ -98,7 +143,17 @@ void menu_run (settings_t *settings) {
}
}
}
if (audio_can_write()) {
short *audio_buffer = audio_write_begin();
mixer_poll(audio_buffer, audio_buffer_length);
audio_write_end();
}
}
menu_deinit(menu);
while (exception_reset_time() > 0) {
// Do nothing if reset button was pressed
}
}

View File

@ -2,60 +2,9 @@
#define MENU_H__
#include "path.h"
#include "settings.h"
#define BROWSER_LIST_SIZE 4096
typedef enum {
MENU_MODE_INIT,
MENU_MODE_BROWSER,
MENU_MODE_FILE_INFO,
MENU_MODE_CREDITS,
MENU_MODE_LOAD,
MENU_MODE_ERROR,
MENU_MODE_BOOT,
} menu_mode_t;
typedef enum {
ENTRY_TYPE_DIR,
ENTRY_TYPE_ROM,
ENTRY_TYPE_SAVE,
ENTRY_TYPE_UNKNOWN,
} entry_type_t;
typedef struct {
char *name;
entry_type_t type;
} entry_t;
typedef struct {
menu_mode_t mode;
menu_mode_t next_mode;
struct {
bool go_up;
bool go_down;
bool fast;
bool enter;
bool back;
bool info;
bool settings;
int held_counter;
} actions;
struct {
bool valid;
path_t *directory;
entry_t list[BROWSER_LIST_SIZE];
int entries;
int selected;
} browser;
} menu_t;
void menu_run (settings_t *settings);

79
src/menu/menu_state.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef MENU_STRUCT_H__
#define MENU_STRUCT_H__
#include <rdpq_font.h>
#include "path.h"
#define BROWSER_LIST_SIZE 10000
typedef enum {
MENU_MODE_NONE,
MENU_MODE_STARTUP,
MENU_MODE_BROWSER,
MENU_MODE_FILE_INFO,
MENU_MODE_PLAYER,
MENU_MODE_CREDITS,
MENU_MODE_LOAD,
MENU_MODE_ERROR,
MENU_MODE_BOOT,
} menu_mode_t;
typedef enum {
ENTRY_TYPE_DIR,
ENTRY_TYPE_ROM,
ENTRY_TYPE_SAVE,
ENTRY_TYPE_MUSIC,
ENTRY_TYPE_OTHER,
} entry_type_t;
typedef struct {
char *name;
entry_type_t type;
int size;
} entry_t;
typedef struct {
menu_mode_t mode;
menu_mode_t next_mode;
struct {
rdpq_font_t *font;
int font_height;
} assets;
struct {
bool go_up;
bool go_down;
bool go_left;
bool go_right;
bool fast;
int vertical_held_counter;
int horizontal_held_counter;
bool enter;
bool back;
bool info;
bool settings;
bool override;
} actions;
struct {
bool valid;
path_t *directory;
entry_t list[BROWSER_LIST_SIZE];
int entries;
int selected;
bool show_hidden;
} browser;
struct {
path_t *path;
} player;
} menu_t;
#endif

295
src/menu/mp3player.c Normal file
View File

@ -0,0 +1,295 @@
#include <fatfs/ff.h>
#include <libdragon.h>
#include "mp3player.h"
#include "utils/utils.h"
#define MINIMP3_IMPLEMENTATION
#include "libs/minimp3/minimp3.h"
#include "libs/minimp3/minimp3_ex.h"
#define BUFFER_SIZE (16 * 1024)
#define MIXER_CHANNEL (0)
typedef struct {
bool loaded;
bool io_error;
mp3dec_t dec;
mp3dec_frame_info_t info;
FIL fil;
FSIZE_t data_start;
uint8_t buffer[BUFFER_SIZE];
uint8_t *buffer_ptr;
size_t buffer_left;
short samples[MINIMP3_MAX_SAMPLES_PER_FRAME];
short *samples_ptr;
int samples_left;
waveform_t wave;
} mp3player_t;
static mp3player_t *p = NULL;
static void mp3player_reset_decoder (void) {
mp3dec_init(&p->dec);
p->buffer_ptr = p->buffer;
p->buffer_left = 0;
p->samples_ptr = p->samples;
p->samples_left = 0;
}
static void mp3player_fill_buffer (void) {
UINT bytes_read;
if (f_eof(&p->fil)) {
return;
}
if (p->buffer_left >= (MAX_FREE_FORMAT_FRAME_SIZE * 3)) {
return;
}
if ((p->buffer_ptr != p->buffer) && (p->buffer_left > 0)) {
memmove(p->buffer, p->buffer_ptr, p->buffer_left);
p->buffer_ptr = p->buffer;
}
if (f_read(&p->fil, p->buffer + p->buffer_left, BUFFER_SIZE - p->buffer_left, &bytes_read) == FR_OK) {
p->buffer_left += bytes_read;
} else {
p->io_error = true;
}
}
static void mp3player_decode_samples (short *buffer, int buffer_samples) {
if (p->samples_left > 0) {
int samples_to_copy = MIN(p->samples_left, buffer_samples);
memcpy(buffer, p->samples_ptr, samples_to_copy * sizeof(short) * p->info.channels);
p->samples_ptr += samples_to_copy * p->info.channels;
p->samples_left -= samples_to_copy;
buffer += samples_to_copy * p->info.channels;
buffer_samples -= samples_to_copy;
}
while (buffer_samples > 0) {
mp3player_fill_buffer();
int samples = mp3dec_decode_frame(&p->dec, p->buffer_ptr, p->buffer_left, p->samples, &p->info);
p->buffer_ptr += p->info.frame_bytes;
p->buffer_left -= p->info.frame_bytes;
if (samples > 0) {
int samples_to_copy = MIN(samples, buffer_samples);
memcpy(buffer, p->samples, samples_to_copy * sizeof(short) * p->info.channels);
p->samples_ptr = p->samples + samples_to_copy * p->info.channels;
p->samples_left = samples - samples_to_copy;
buffer += samples_to_copy * p->info.channels;
buffer_samples -= samples_to_copy;
}
if (p->info.frame_bytes == 0) {
memset(buffer, 0, buffer_samples * sizeof(short) * p->info.channels);
buffer_samples = 0;
}
}
}
static void mp3player_wave_read (void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
short *buf = (short *) (samplebuffer_append(sbuf, wlen));
mp3player_decode_samples(buf, wlen);
}
mp3player_err_t mp3player_init (void) {
p = calloc(1, sizeof(mp3player_t));
if (p == NULL) {
return MP3PLAYER_ERR_MALLOC;
}
mp3player_reset_decoder();
p->loaded = false;
p->io_error = false;
p->wave = (waveform_t) {
.name = "mp3player",
.bits = 16,
.channels = 2,
.frequency = 44100,
.len = WAVEFORM_MAX_LEN - 1,
.loop_len = WAVEFORM_MAX_LEN - 1,
.read = mp3player_wave_read,
.ctx = p,
};
return MP3PLAYER_OK;
}
void mp3player_deinit (void) {
mp3player_unload();
free(p);
p = NULL;
}
mp3player_err_t mp3player_load (char *path) {
if (p->loaded) {
mp3player_unload();
}
if (f_open(&p->fil, path, FA_READ) != FR_OK) {
return MP3PLAYER_ERR_IO;
}
mp3player_reset_decoder();
while (!(f_eof(&p->fil) && p->buffer_left == 0)) {
mp3player_fill_buffer();
if (p->io_error) {
return MP3PLAYER_ERR_IO;
}
size_t id3v2_skip = mp3dec_skip_id3v2((const uint8_t *) (p->buffer_ptr), p->buffer_left);
if (id3v2_skip > 0) {
f_lseek(&p->fil, f_tell(&p->fil) - p->buffer_left + id3v2_skip);
mp3player_reset_decoder();
continue;
}
if (mp3dec_decode_frame(&p->dec, p->buffer_ptr, p->buffer_left, NULL, &p->info) > 0) {
mp3dec_init(&p->dec);
p->loaded = true;
p->data_start = f_tell(&p->fil) - p->buffer_left + p->info.frame_offset;
p->wave.channels = p->info.channels;
p->wave.frequency = p->info.hz;
return MP3PLAYER_OK;
}
p->buffer_ptr += p->info.frame_bytes;
p->buffer_left -= p->info.frame_bytes;
}
if (f_close(&p->fil) != FR_OK) {
return MP3PLAYER_ERR_IO;
}
return MP3PLAYER_ERR_INVALID_FILE;
}
void mp3player_unload (void) {
mp3player_stop();
if (p->loaded) {
p->loaded = false;
f_close(&p->fil);
}
}
mp3player_err_t mp3player_process (void) {
if (p->io_error) {
mp3player_unload();
return MP3PLAYER_ERR_IO;
}
if (mp3player_is_finished()) {
mp3player_stop();
}
return MP3PLAYER_OK;
}
bool mp3player_is_playing (void) {
return mixer_ch_playing(MIXER_CHANNEL);
}
bool mp3player_is_finished (void) {
return f_eof(&p->fil) && p->buffer_left == 0 && p->samples_left == 0;
}
mp3player_err_t mp3player_play (void) {
if (!p->loaded) {
return MP3PLAYER_ERR_NO_FILE;
}
if (!mp3player_is_playing()) {
if (mp3player_is_finished()) {
if (f_lseek(&p->fil, p->data_start) != FR_OK) {
p->io_error = true;
return MP3PLAYER_ERR_IO;
}
mp3player_reset_decoder();
}
mixer_ch_play(MIXER_CHANNEL, &p->wave);
}
return MP3PLAYER_OK;
}
void mp3player_stop (void) {
if (mp3player_is_playing()) {
mixer_ch_stop(MIXER_CHANNEL);
}
}
mp3player_err_t mp3player_toggle (void) {
if (mp3player_is_playing()) {
mp3player_stop();
} else {
return mp3player_play();
}
return MP3PLAYER_OK;
}
mp3player_err_t mp3player_seek (int seconds) {
// NOTE: Rough approximation using last frame bitrate to calculate number of bytes to be skipped.
// Good enough but not very accurate for variable bitrate files.
if (!p->loaded) {
return MP3PLAYER_ERR_NO_FILE;
}
long bytes_to_move = (long) (((p->info.bitrate_kbps * 1024) * seconds) / 8);
if (bytes_to_move == 0) {
return MP3PLAYER_OK;
}
long position = ((long) (f_tell(&p->fil)) - p->buffer_left + bytes_to_move);
if (position < (long) (p->data_start)) {
position = p->data_start;
}
if (f_lseek(&p->fil, position) != FR_OK) {
p->io_error = true;
return MP3PLAYER_ERR_IO;
}
mp3player_reset_decoder();
return MP3PLAYER_OK;
}
float mp3player_get_progress (void) {
// NOTE: Rough approximation using file pointer instead of processed samples.
// Good enough but not very accurate for variable bitrate files.
FSIZE_t data_size = f_size(&p->fil) - p->data_start;
FSIZE_t data_consumed = f_tell(&p->fil) - p->buffer_left;
FSIZE_t data_position = (data_consumed > p->data_start) ? (data_consumed - p->data_start) : 0;
return data_position / (float) (data_size);
}

31
src/menu/mp3player.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef MP3PLAYER_H__
#define MP3PLAYER_H__
#include <stdbool.h>
typedef enum {
MP3PLAYER_OK,
MP3PLAYER_ERR_MALLOC,
MP3PLAYER_ERR_IO,
MP3PLAYER_ERR_NO_FILE,
MP3PLAYER_ERR_INVALID_FILE,
} mp3player_err_t;
mp3player_err_t mp3player_init (void);
void mp3player_deinit (void);
mp3player_err_t mp3player_load (char *path);
void mp3player_unload (void);
mp3player_err_t mp3player_process (void);
bool mp3player_is_playing (void);
bool mp3player_is_finished (void);
mp3player_err_t mp3player_play (void);
void mp3player_stop (void);
mp3player_err_t mp3player_toggle (void);
mp3player_err_t mp3player_seek (int seconds);
float mp3player_get_progress (void);
#endif

View File

@ -1,7 +1,7 @@
#include <libdragon.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "path.h"
@ -16,6 +16,7 @@ static void path_resize (path_t *path, size_t min_length) {
path->capacity += PATH_CAPACITY_ALIGNMENT - alignment;
}
path->buffer = realloc(path->buffer, (path->capacity + 1) * sizeof(char));
assert(path->buffer != NULL);
}
path_t *path_init (char *string) {
@ -23,6 +24,7 @@ path_t *path_init (char *string) {
string = "";
}
path_t *path = calloc(1, sizeof(path_t));
assert(path != NULL);
path_resize(path, strlen(string));
memset(path->buffer, 0, path->capacity + 1);
strcpy(path->buffer, string);
@ -47,6 +49,10 @@ char *path_last_get (path_t *path) {
return (last_slash == NULL) ? path->buffer : (last_slash + 1);
}
bool path_is_root (path_t *path) {
return (strcmp(path->buffer, "") == 0) || (strcmp(path->buffer, "/") == 0);
}
void path_append (path_t *path, char *string) {
size_t buffer_length = strlen(path->buffer);
size_t string_length = strlen(string);

View File

@ -2,6 +2,9 @@
#define PATH_H__
#include <stdbool.h>
typedef struct {
char *buffer;
size_t capacity;
@ -13,6 +16,7 @@ void path_free (path_t *path);
path_t *path_clone (path_t *string);
char *path_get (path_t *path);
char *path_last_get (path_t *path);
bool path_is_root (path_t *path);
void path_append (path_t *path, char *string);
void path_concat (path_t *dst, path_t *str);
void path_push (path_t *path, char *string);

View File

@ -13,6 +13,7 @@
void settings_load_from_file(settings_t *settings) {
return;
FILE *fp = fopen(SC64_SETTINGS_FILEPATH, "r");
if (!fp) {
printf("Error loading config file %s\n", SC64_SETTINGS_FILEPATH);
@ -157,7 +158,7 @@ void settings_load_default_state(settings_t *settings) {
settings->last_rom.save_type = FLASHCART_SAVE_TYPE_NONE;
settings->last_rom.save_writeback = false;
settings->last_state.current_directory = "/"; // This must not include the trailing slash on dirs!
settings->last_state.directory = ""; // This must not include the trailing slash on dirs!
settings->last_state.auto_load_last_rom = false;
settings->boot_params.device_type = BOOT_DEVICE_TYPE_ROM;

View File

@ -17,7 +17,7 @@ typedef struct {
typedef struct {
bool auto_load_last_rom;
char* current_directory;
char* directory;
// TODO:
// Menu layout: list vs grid
// Hide extensions

View File

@ -1,12 +1,17 @@
#include <stdlib.h>
#include <string.h>
#include <fatfs/ff.h>
#include <libdragon.h>
#include <stdlib.h>
#include "../menu.h"
#include "../menu_res_setup.h"
#include "../../utils/str_utils.h"
#include "fragments/fragments.h"
#include "utils/fs.h"
#include "views.h"
#define BROWSER_LIST_ROWS 21
static const char *rom_extensions[] = { "z64", "n64", "v64", NULL };
static const char *save_extensions[] = { "sav", NULL };
static const char *music_extensions[] = { "mp3", NULL };
static int compare_entry (const void *pa, const void *pb) {
@ -26,34 +31,37 @@ static int compare_entry (const void *pa, const void *pb) {
return -1;
} else if (b->type == ENTRY_TYPE_SAVE) {
return 1;
} else if (a->type == ENTRY_TYPE_MUSIC) {
return -1;
} else if (b->type == ENTRY_TYPE_MUSIC) {
return 1;
}
}
return strcasecmp((const char *) (a->name), (const char *) (b->name));
}
static void load_directory (menu_t *menu) {
static bool load_directory (menu_t *menu) {
DIR dir;
FILINFO info;
for (int i = 0; i < menu->browser.entries; i++) {
for (int i = menu->browser.entries - 1; i >= 0; i--) {
free(menu->browser.list[i].name);
}
menu->browser.entries = 0;
menu->browser.selected = -1;
if (f_opendir(&dir, path_get(menu->browser.directory)) != FR_OK) {
menu->next_mode = MENU_MODE_ERROR;
return;
return true;
}
while (true) {
while (menu->browser.entries < BROWSER_LIST_SIZE) {
if (f_readdir(&dir, &info) != FR_OK) {
menu->next_mode = MENU_MODE_ERROR;
return;
return true;
}
size_t length = strlen(info.fname);
if (length == 0) {
break;
}
@ -61,59 +69,112 @@ static void load_directory (menu_t *menu) {
if (info.fattrib & AM_SYS) {
continue;
}
if (info.fattrib & AM_HID && !menu->browser.show_hidden) {
continue;
}
entry_t *entry = &menu->browser.list[menu->browser.entries];
entry_t *entry = &menu->browser.list[menu->browser.entries++];
entry->name = malloc((length + 1) * sizeof(char));
strcpy(entry->name, info.fname);
entry->name = strdup(info.fname);
assert(entry->name != NULL);
if (info.fattrib & AM_DIR) {
entry->type = ENTRY_TYPE_DIR;
// TODO: use something like `ext_is_n64_rom(info.fname)` instead of `str_endswith(info.fname, ".xxx")`
} else if (str_endswith(info.fname, ".n64") || str_endswith(info.fname, ".z64") || str_endswith(info.fname, ".v64") || str_endswith(info.fname, ".N64")) {
} else if (file_has_extensions(info.fname, rom_extensions)) {
entry->type = ENTRY_TYPE_ROM;
} else if (str_endswith(info.fname, ".sav")) {
} else if (file_has_extensions(info.fname, save_extensions)) {
entry->type = ENTRY_TYPE_SAVE;
} else if (file_has_extensions(info.fname, music_extensions)) {
entry->type = ENTRY_TYPE_MUSIC;
} else {
entry->type = ENTRY_TYPE_UNKNOWN;
entry->type = ENTRY_TYPE_OTHER;
}
menu->browser.entries += 1;
if (menu->browser.entries == BROWSER_LIST_SIZE) {
break;
}
entry->size = info.fsize;
}
f_closedir(&dir);
if (f_closedir(&dir) != FR_OK) {
return true;
}
if (menu->browser.entries > 0) {
menu->browser.selected = 0;
}
qsort(menu->browser.list, menu->browser.entries, sizeof(entry_t), compare_entry);
return false;
}
void push_directory (menu_t *menu, char *directory) {
static bool push_directory (menu_t *menu, char *directory) {
path_t *previous_directory = path_clone(menu->browser.directory);
path_push(menu->browser.directory, directory);
load_directory(menu);
if (load_directory(menu)) {
path_free(menu->browser.directory);
menu->browser.directory = previous_directory;
return true;
}
void pop_directory (menu_t *menu) {
path_t *current_directory = path_clone(menu->browser.directory);
path_free(previous_directory);
return false;
}
static bool pop_directory (menu_t *menu) {
path_t *previous_directory = path_clone(menu->browser.directory);
path_pop(menu->browser.directory);
load_directory(menu);
if (load_directory(menu)) {
path_free(menu->browser.directory);
menu->browser.directory = previous_directory;
return true;
}
for (int i = 0; i < menu->browser.entries; i++) {
if (strcmp(menu->browser.list[i].name, path_last_get(current_directory)) == 0) {
if (strcmp(menu->browser.list[i].name, path_last_get(previous_directory)) == 0) {
menu->browser.selected = i;
break;
}
}
path_free(current_directory);
path_free(previous_directory);
return false;
}
static void format_size (char *buffer, int size) {
if (size < 10000) {
sprintf(buffer, "%4d B ", size);
} else if (size < 10000000) {
sprintf(buffer, "%4d kB", size / 1024);
} else if (size < 1 * 1024 * 1024 * 1024) {
sprintf(buffer, "%4d MB", size / 1024 / 1024);
} else {
sprintf(buffer, "%4d GB", size / 1024 / 1024 / 1024);
}
}
static void format_entry (char *buffer, entry_t *entry, bool selected) {
int cutoff_length = (entry->type == ENTRY_TYPE_DIR ? 57 : 49);
int name_length = strlen(entry->name);
strcpy(buffer, "");
if (entry->type == ENTRY_TYPE_DIR) {
strcat(buffer, "/");
}
if (name_length > cutoff_length) {
strncat(buffer, entry->name, cutoff_length - 1);
strcat(buffer, "");
} else {
strcat(buffer, entry->name);
}
}
static void process (menu_t *menu) {
int scroll_speed = menu->actions.fast ? BROWSER_LIST_ROWS : 1;
int scroll_speed = menu->actions.fast ? 10 : 1;
if (menu->browser.entries > 1) {
if (menu->actions.go_up) {
@ -134,85 +195,172 @@ static void process (menu_t *menu) {
switch (entry->type) {
case ENTRY_TYPE_DIR:
push_directory(menu, entry->name);
if (push_directory(menu, entry->name)) {
menu->browser.valid = false;
menu->next_mode = MENU_MODE_ERROR;
}
break;
case ENTRY_TYPE_ROM:
menu->next_mode = MENU_MODE_LOAD;
break;
case ENTRY_TYPE_MUSIC:
menu->next_mode = MENU_MODE_PLAYER;
break;
default:
menu->next_mode = MENU_MODE_FILE_INFO;
break;
}
} else if (menu->actions.back) {
pop_directory(menu);
} else if (menu->actions.back && !path_is_root(menu->browser.directory)) {
if (pop_directory(menu)) {
menu->browser.valid = false;
menu->next_mode = MENU_MODE_ERROR;
}
} else if (menu->actions.info) {
if (menu->browser.selected >= 0) {
menu->next_mode = MENU_MODE_FILE_INFO;
}
} else if (menu->actions.settings) {
menu->next_mode = MENU_MODE_CREDITS;
}
}
static void draw (menu_t *menu, surface_t *d) {
int x = 24;
int y = 35;
char buffer[64];
layout_t *layout = get_layout();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const int text_file_size_x = text_x + 478;
const int text_other_actions_x = text_x + 450;
const int highlight_offset = 2;
const color_t bg_color = RGBA32(0x00, 0x00, 0x00, 0xFF);
const color_t highlight_color = RGBA32(0x3F, 0x3F, 0x3F, 0xFF);
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
const color_t directory_color = RGBA32(0xFF, 0xFF, 0x70, 0xFF);
const color_t save_color = RGBA32(0x70, 0xFF, 0x70, 0xFF);
const color_t music_color = RGBA32(0x70, 0xBC, 0xFF, 0xFF);
const color_t other_color = RGBA32(0xA0, 0xA0, 0xA0, 0xFF);
int starting_position = 0;
int entries_drawn = 0;
if (menu->browser.entries > BROWSER_LIST_ROWS && menu->browser.selected >= (BROWSER_LIST_ROWS / 2)) {
starting_position = menu->browser.selected - (BROWSER_LIST_ROWS / 2);
if (starting_position >= menu->browser.entries - BROWSER_LIST_ROWS) {
starting_position = menu->browser.entries - BROWSER_LIST_ROWS;
if (menu->browser.entries > layout->main_lines && menu->browser.selected >= (layout->main_lines / 2)) {
starting_position = menu->browser.selected - (layout->main_lines / 2);
if (starting_position >= menu->browser.entries - layout->main_lines) {
starting_position = menu->browser.entries - layout->main_lines;
}
}
graphics_fill_screen(d, graphics_make_color(0, 0, 0, 255));
rdpq_attach(d, NULL);
rdpq_clear(bg_color);
graphics_draw_text(d, (d->width / 2) - 36, vertical_start_position, "FILE MENU");
graphics_draw_line(d, 0, 30, d->width, 30, 0xff);
char str_buffer[1024];
// Layout
fragment_borders(d);
fragment_scrollbar(d, menu->browser.selected, menu->browser.entries);
// Main screen
rdpq_font_begin(text_color);
for (int i = starting_position; i < menu->browser.entries; i++) {
if (i == menu->browser.selected) {
uint32_t color;
switch (menu->browser.list[i].type) {
case ENTRY_TYPE_ROM:
color = graphics_make_color(0, 64, 0, 0);
if (i == (starting_position + layout->main_lines)) {
break;
}
entry_t *entry = &menu->browser.list[i];
bool selected = (i == menu->browser.selected);
if (selected) {
rdpq_set_mode_fill(highlight_color);
rdpq_fill_rectangle(
layout->offset_x,
text_y + highlight_offset,
d->width - layout->offset_x - layout->scrollbar_width,
text_y + layout->line_height + highlight_offset
);
rdpq_font_begin(text_color);
}
switch (entry->type) {
case ENTRY_TYPE_DIR:
rdpq_set_prim_color(directory_color);
break;
case ENTRY_TYPE_SAVE:
rdpq_set_prim_color(save_color);
break;
case ENTRY_TYPE_OTHER:
rdpq_set_prim_color(other_color);
break;
case ENTRY_TYPE_MUSIC:
rdpq_set_prim_color(music_color);
break;
default:
color = graphics_make_color(64, 64, 64, 0);
rdpq_set_prim_color(text_color);
break;
}
graphics_draw_box(d, x, y, (640 - x * 2), font_vertical_pixels, color);
rdpq_font_position(text_x, text_y + menu->assets.font_height);
format_entry(buffer, entry, selected);
rdpq_font_print(menu->assets.font, buffer);
if (entry->type != ENTRY_TYPE_DIR) {
rdpq_font_position(text_file_size_x, text_y + menu->assets.font_height);
format_size(buffer, entry->size);
rdpq_font_print(menu->assets.font, buffer);
}
snprintf(str_buffer, 1024, "%.74s", menu->browser.list[i].name);
graphics_draw_text(d, x, y, str_buffer);
y += font_vertical_pixels;
text_y += layout->line_height;
}
entries_drawn += 1;
if (entries_drawn == BROWSER_LIST_ROWS) {
if (menu->browser.entries == 0) {
rdpq_set_prim_color(other_color);
rdpq_font_position(text_x, text_y + menu->assets.font_height);
rdpq_font_print(menu->assets.font, "** empty directory **");
}
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
rdpq_set_prim_color(text_color);
if (menu->browser.entries > 0) {
rdpq_font_position(text_x, text_y + menu->assets.font_height);
switch (menu->browser.list[menu->browser.selected].type) {
case ENTRY_TYPE_DIR:
rdpq_font_print(menu->assets.font, "A: Enter");
break;
case ENTRY_TYPE_ROM:
rdpq_font_print(menu->assets.font, "A: Load");
break;
case ENTRY_TYPE_MUSIC:
rdpq_font_print(menu->assets.font, "A: Play");
break;
default:
rdpq_font_print(menu->assets.font, "A: Info");
break;
}
rdpq_font_position(text_other_actions_x, text_y + menu->assets.font_height);
rdpq_font_print(menu->assets.font, "Z: Info");
}
text_y += layout->line_height;
if (!path_is_root(menu->browser.directory)) {
rdpq_font_position(text_x, text_y + menu->assets.font_height);
rdpq_font_print(menu->assets.font, "B: Back");
}
rdpq_font_position(text_other_actions_x, text_y + menu->assets.font_height);
rdpq_font_print(menu->assets.font, "R: Settings");
rdpq_font_end();
graphics_draw_line(d, 0, d->height - overscan_vertical_pixels - font_vertical_pixels, d->width, d->height - overscan_vertical_pixels - font_vertical_pixels, 0xff);
sprintf(str_buffer, "Current Directory: SD:%s\nFile: %d of %d\n\n", path_get(menu->browser.directory), menu->browser.selected + 1, menu->browser.entries);
graphics_draw_text(d, (d->width / 2) - 160, d->height - overscan_vertical_pixels, str_buffer);
display_show(d);
rdpq_detach_show();
}
void view_browser_init (menu_t *menu) {
if (!menu->browser.valid) {
if (load_directory(menu)) {
path_free(menu->browser.directory);
menu->browser.directory = path_init(NULL);
menu->next_mode = MENU_MODE_ERROR;
} else {
menu->browser.valid = true;
load_directory(menu);
}
}
}

View File

@ -1,5 +1,7 @@
#include <libdragon.h>
#include "../menu.h"
#include "views.h"
#include "../menu_res_setup.h"

View File

@ -1,5 +1,6 @@
#include <libdragon.h>
#include "../menu.h"
#include "views.h"
static void process (menu_t *menu) {

View File

@ -1,9 +1,13 @@
#include <fatfs/ff.h>
#include <libdragon.h>
#include "../menu.h"
#include "views.h"
#include "../menu_res_setup.h"
#include "../../utils/str_utils.h"
#include "fragments/fragments.h"
static FILINFO info;
@ -44,6 +48,14 @@ static void process (menu_t *menu) {
}
static void draw (menu_t *menu, surface_t *d) {
// const color_t bg_color = RGBA32(0x00, 0x00, 0x00, 0xFF);
// rdpq_attach(d, NULL);
// rdpq_clear(bg_color);
// fragment_borders(d);
char str_buffer[1024];
graphics_fill_screen(d, 0x00);
@ -88,6 +100,8 @@ static void draw (menu_t *menu, surface_t *d) {
graphics_draw_text(d, (d->width / 2) - 80,d->height - overscan_vertical_pixels, "Press (B) to return!"); // centre = numchars * font_horizontal_pixels / 2
display_show(d);
// rdpq_detach_show();
}

View File

@ -0,0 +1,66 @@
#include "fragments.h"
// TODO: Prepare layout for PAL display
static layout_t layout = {
.offset_x = 32,
.offset_y = 24,
.offset_text_x = 10,
.offset_text_y = 7,
.line_height = 18,
.scrollbar_width = 10,
.progressbar_height = 16,
.border_thickness = 2,
.main_lines = 20,
.actions_x = 20,
.actions_y = 404,
.actions_lines = 2,
};
layout_t *get_layout(void) {
return &layout;
}
void fragment_borders (surface_t *d) {
widget_border(
layout.offset_x,
layout.offset_y,
d->width - layout.offset_x,
d->height - layout.offset_y,
layout.border_thickness
);
widget_horizontal_line(
layout.offset_x,
d->width - layout.offset_x,
layout.offset_y + ((layout.main_lines + 1) * layout.line_height),
layout.border_thickness
);
}
void fragment_scrollbar (surface_t *d, int position, int items) {
widget_scrollbar(
d->width - layout.offset_x - layout.scrollbar_width,
layout.offset_y,
layout.scrollbar_width,
(layout.main_lines + 1) * layout.line_height,
position,
items,
layout.main_lines
);
}
void fragment_progressbar (surface_t *d, float progress) {
widget_progressbar (
layout.offset_x,
layout.actions_y - layout.border_thickness - layout.progressbar_height,
d->width - (layout.offset_x * 2),
layout.progressbar_height,
progress
);
}

View File

@ -0,0 +1,41 @@
#ifndef FRAGMENTS_H__
#define FRAGMENTS_H__
#include <surface.h>
void widget_horizontal_line (int x1, int x2, int y, int thickness);
void widget_border (int x1, int y1, int x2, int y2, int thickness);
void widget_scrollbar (int x, int y, int width, int height, int position, int items, int visible_items);
void widget_progressbar (int x, int y, int width, int height, float progress);
typedef struct {
int offset_x;
int offset_y;
int offset_text_x;
int offset_text_y;
int line_height;
int scrollbar_width;
int progressbar_height;
int border_thickness;
int main_lines;
int actions_x;
int actions_y;
int actions_lines;
} layout_t;
layout_t *get_layout(void);
void fragment_borders (surface_t *d);
void fragment_scrollbar (surface_t *d, int position, int items);
void fragment_progressbar (surface_t *d, float progress);
#endif

View File

@ -0,0 +1,55 @@
#include <libdragon.h>
void widget_horizontal_line (int x1, int x2, int y, int thickness) {
color_t line_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_set_mode_fill(line_color);
rdpq_fill_rectangle(x1, y, x2, y + thickness);
}
void widget_border (int x1, int y1, int x2, int y2, int thickness) {
color_t border_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_set_mode_fill(border_color);
rdpq_fill_rectangle(x1 - thickness, y1 - thickness, x2 + thickness, y1);
rdpq_fill_rectangle(x1 - thickness, y2, x2 + thickness, y2 + thickness);
rdpq_fill_rectangle(x1 - thickness, y1, x1, y2);
rdpq_fill_rectangle(x2, y1, x2 + thickness, y2);
}
void widget_scrollbar (int x, int y, int width, int height, int position, int items, int visible_items) {
color_t bg_color = RGBA32(0x3F, 0x3F, 0x3F, 0xFF);
color_t inactive_color = RGBA32(0x5F, 0x5F, 0x5F, 0xFF);
color_t active_color = RGBA32(0x7F, 0x7F, 0x7F, 0xFF);
if (items < 2 || items <= visible_items) {
rdpq_set_mode_fill(inactive_color);
rdpq_fill_rectangle(x, y, x + width, y + height);
} else {
int scroll_height = (int) ((visible_items / (float) (items)) * height);
float scroll_position = ((position / (float) (items - 1)) * (height - scroll_height));
rdpq_set_mode_fill(bg_color);
rdpq_fill_rectangle(x, y, x + width, y + height);
rdpq_set_fill_color(active_color);
rdpq_fill_rectangle(x, y + scroll_position, x + width, y + scroll_position + scroll_height);
}
}
void widget_progressbar (int x, int y, int width, int height, float progress) {
color_t bg_color = RGBA32(0x3F, 0x3F, 0x3F, 0xFF);
color_t fg_color = RGBA32(0x7F, 0x7F, 0x7F, 0xFF);
float progress_width = progress * width;
rdpq_set_fill_color(fg_color);
rdpq_fill_rectangle(x, y, x + progress_width, y + height);
rdpq_set_mode_fill(bg_color);
rdpq_fill_rectangle(x + progress_width, y, x + width, y + height);
}

View File

@ -1,13 +0,0 @@
#include <libdragon.h>
#include "../menu.h"
static void draw (menu_t *menu, surface_t *d) {
graphics_fill_screen(d, graphics_make_color(0, 0, 0, 255));
display_show(d);
}
void view_init_display (menu_t *menu, surface_t *display) {
draw(menu, display);
}

View File

@ -1,19 +1,14 @@
#include <libdragon.h>
#include "../menu.h"
#include "flashcart/flashcart.h"
#include "fragments/fragments.h"
#include "views.h"
#include "../rom_database.h"
#include "../../flashcart/flashcart.h"
static void draw (menu_t *menu, surface_t *d) {
int x = 24;
int y = 36;
static bool load_pending;
graphics_fill_screen(d, graphics_make_color(0, 0, 0, 255));
graphics_draw_text(d, x, y, "booting...");
display_show(d);
}
static void load (menu_t *menu) {
menu->next_mode = MENU_MODE_BOOT;
@ -26,7 +21,7 @@ static void load (menu_t *menu) {
uint8_t save_type = rom_db_match_save_type(temp_header);
if (flashcart_load_rom(path_get(path)) != FLASHCART_OK) {
if (flashcart_load_rom(path_get(path), false) != FLASHCART_OK) {
menu->next_mode = MENU_MODE_ERROR;
path_free(path);
return;
@ -43,11 +38,38 @@ static void load (menu_t *menu) {
}
static void process (menu_t *menu) {
if (menu->actions.enter) {
load_pending = true;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
}
}
static void draw (menu_t *menu, surface_t *d) {
// layout_t *layout = get_layout();
const color_t bg_color = RGBA32(0x00, 0x00, 0x00, 0xFF);
rdpq_attach(d, NULL);
rdpq_clear(bg_color);
// Layout
fragment_borders(d);
rdpq_detach_show();
}
void view_load_init (menu_t *menu) {
// Nothing to initialize (yet)
load_pending = false;
}
void view_load_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (load_pending) {
load_pending = false;
load(menu);
}
}

117
src/menu/views/player.c Normal file
View File

@ -0,0 +1,117 @@
#include <libdragon.h>
#include "fragments/fragments.h"
#include "views.h"
#include "../mp3player.h"
static void format_name (char *buffer, char *name) {
int cutoff_length = 57;
int name_length = strlen(name);
strcpy(buffer, " ");
if (name_length > cutoff_length) {
strncat(buffer, name, cutoff_length - 1);
strcat(buffer, "");
} else {
strcat(buffer, name);
}
}
static void process (menu_t *menu) {
if (mp3player_process() != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
} else if (menu->actions.back) {
menu->next_mode = MENU_MODE_BROWSER;
} else if (menu->actions.enter) {
if (mp3player_toggle() != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
}
} else if (menu->actions.go_left || menu->actions.go_right) {
int seconds = ((menu->actions.go_left ? -1 : 1) * 10);
if (mp3player_seek(seconds) != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
}
}
}
static void draw (menu_t *menu, surface_t *d) {
char buffer[64];
layout_t *layout = get_layout();
const int text_x = layout->offset_x + layout->offset_text_x;
int text_y = layout->offset_y + layout->offset_text_y;
const color_t bg_color = RGBA32(0x00, 0x00, 0x00, 0xFF);
const color_t text_color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF);
rdpq_attach(d, NULL);
rdpq_clear(bg_color);
// Layout
fragment_borders(d);
// Progressbar
fragment_progressbar(d, mp3player_get_progress());
// Main screen
rdpq_font_begin(text_color);
rdpq_font_position(text_x, text_y + menu->assets.font_height);
rdpq_font_print(menu->assets.font, "Now playing:");
text_y += layout->line_height;
rdpq_font_position(text_x, text_y + menu->assets.font_height);
format_name(buffer, menu->browser.list[menu->browser.selected].name);
rdpq_font_print(menu->assets.font, buffer);
// Actions bar
text_y = layout->actions_y + layout->offset_text_y;
rdpq_font_position(text_x, text_y + menu->assets.font_height);
if (mp3player_is_playing()) {
rdpq_font_print(menu->assets.font, "A: Pause");
} else if (mp3player_is_finished()) {
rdpq_font_print(menu->assets.font, "A: Play again");
} else {
rdpq_font_print(menu->assets.font, "A: Play");
}
text_y += layout->line_height;
rdpq_font_position(text_x, text_y + menu->assets.font_height);
rdpq_font_print(menu->assets.font, "B: Exit | Left/Right: Rewind/Fast forward");
rdpq_font_end();
rdpq_detach_show();
}
void view_player_init (menu_t *menu) {
mp3player_err_t error;
error = mp3player_init();
if (error != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
mp3player_deinit();
return;
}
path_t *path = path_clone(menu->browser.directory);
path_push(path, menu->browser.list[menu->browser.selected].name);
error = mp3player_load(path_get(path));
if (error != MP3PLAYER_OK) {
menu->next_mode = MENU_MODE_ERROR;
mp3player_deinit();
} else {
mp3player_play();
}
path_free(path);
}
void view_player_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (menu->next_mode != MENU_MODE_PLAYER) {
mp3player_deinit();
}
}

23
src/menu/views/startup.c Normal file
View File

@ -0,0 +1,23 @@
#include <libdragon.h>
#include "views.h"
static void process (menu_t *menu) {
menu->next_mode = MENU_MODE_BROWSER;
}
static void draw (menu_t *menu, surface_t *d) {
rdpq_attach_clear(d, NULL);
rdpq_detach_show();
}
void view_startup_init (menu_t *menu) {
// Nothing to initialize (yet)
}
void view_startup_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
}

View File

@ -4,10 +4,11 @@
#include <surface.h>
#include "menu.h"
#include "../menu_state.h"
void view_init_display (menu_t *menu, surface_t *display);
void view_startup_init (menu_t *menu);
void view_startup_display (menu_t *menu, surface_t *display);
void view_browser_init (menu_t *menu);
void view_browser_display (menu_t *menu, surface_t *display);
@ -15,6 +16,9 @@ void view_browser_display (menu_t *menu, surface_t *display);
void view_file_info_init (menu_t *menu);
void view_file_info_display (menu_t *menu, surface_t *display);
void view_player_init (menu_t *menu);
void view_player_display (menu_t *menu, surface_t *display);
void view_credits_init (menu_t *menu);
void view_credits_display (menu_t *menu, surface_t *display);

View File

@ -1,3 +1,6 @@
#include <string.h>
#include <strings.h>
#include <fatfs/ff.h>
#include "fs.h"
@ -88,3 +91,20 @@ bool file_get_sectors (char *path, uint32_t *sectors, size_t entries) {
return error;
}
bool file_has_extensions (char *path, const char *extensions[]) {
char *ext = strrchr(path, '.');
if (ext == NULL) {
return false;
}
while (*extensions != NULL) {
if (strcasecmp(ext + 1, *extensions) == 0) {
return true;
}
extensions++;
}
return false;
}

View File

@ -14,6 +14,7 @@ bool file_exists (char *path);
size_t file_get_size (char *path);
bool file_allocate (char *path, size_t size);
bool file_get_sectors (char *path, uint32_t *sectors, size_t entries);
bool file_has_extensions (char *path, const char *extensions[]);
#endif