mirror of
https://github.com/Polprzewodnikowy/N64FlashcartMenu.git
synced 2024-11-21 18:19:19 +01:00
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:
parent
9f30e44625
commit
b53bbf7dae
25
Makefile
25
Makefile
@ -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)
|
||||
|
||||
@ -41,7 +58,7 @@ $(PROJECT_NAME).z64: N64_ROM_TITLE=$(PROJECT_NAME)
|
||||
|
||||
$(PROJECT_NAME): $(PROJECT_NAME).z64
|
||||
$(shell mkdir -p $(OUTPUT_DIR))
|
||||
$(shell mv $(PROJECT_NAME).z64 $(OUTPUT_DIR))
|
||||
$(shell mv $(PROJECT_NAME).z64 $(OUTPUT_DIR))
|
||||
|
||||
sc64_minify: $(PROJECT_NAME)
|
||||
$(shell python3 ./tools/sc64/minify.py $(BUILD_DIR)/$(PROJECT_NAME).elf $(OUTPUT_DIR)/$(PROJECT_NAME).z64 $(OUTPUT_DIR)/sc64menu.n64)
|
||||
|
BIN
assets/FiraMono-Bold.ttf
Normal file
BIN
assets/FiraMono-Bold.ttf
Normal file
Binary file not shown.
@ -1 +1 @@
|
||||
Subproject commit 7241811bbc5d89e64d6048931c958b0765a1ef70
|
||||
Subproject commit afa6c25f9e8186432d9480b5fe1aff4f29ab05a1
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
1865
src/libs/minimp3/minimp3.h
Normal file
File diff suppressed because it is too large
Load Diff
1397
src/libs/minimp3/minimp3_ex.h
Normal file
1397
src/libs/minimp3/minimp3_ex.h
Normal file
File diff suppressed because it is too large
Load Diff
33
src/main.c
33
src/main.c
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
37
src/menu/assets.c
Normal 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);
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
79
src/menu/menu_state.h
Normal 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
295
src/menu/mp3player.c
Normal 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
31
src/menu/mp3player.h
Normal 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
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
path_free(previous_directory);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void pop_directory (menu_t *menu) {
|
||||
path_t *current_directory = path_clone(menu->browser.directory);
|
||||
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) {
|
||||
menu->next_mode = MENU_MODE_FILE_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);
|
||||
break;
|
||||
default:
|
||||
color = graphics_make_color(64, 64, 64, 0);
|
||||
break;
|
||||
}
|
||||
graphics_draw_box(d, x, y, (640 - x * 2), font_vertical_pixels, color);
|
||||
}
|
||||
snprintf(str_buffer, 1024, "%.74s", menu->browser.list[i].name);
|
||||
graphics_draw_text(d, x, y, str_buffer);
|
||||
|
||||
y += font_vertical_pixels;
|
||||
|
||||
entries_drawn += 1;
|
||||
if (entries_drawn == BROWSER_LIST_ROWS) {
|
||||
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:
|
||||
rdpq_set_prim_color(text_color);
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
text_y += layout->line_height;
|
||||
}
|
||||
|
||||
graphics_draw_line(d, 0, d->height - overscan_vertical_pixels - font_vertical_pixels, d->width, d->height - overscan_vertical_pixels - font_vertical_pixels, 0xff);
|
||||
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 **");
|
||||
}
|
||||
|
||||
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);
|
||||
// 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_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) {
|
||||
menu->browser.valid = true;
|
||||
load_directory(menu);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include <libdragon.h>
|
||||
#include "../menu.h"
|
||||
|
||||
#include "views.h"
|
||||
|
||||
#include "../menu_res_setup.h"
|
||||
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <libdragon.h>
|
||||
#include "../menu.h"
|
||||
|
||||
#include "views.h"
|
||||
|
||||
|
||||
static void process (menu_t *menu) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
66
src/menu/views/fragments/fragments.c
Normal file
66
src/menu/views/fragments/fragments.c
Normal 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
|
||||
);
|
||||
}
|
41
src/menu/views/fragments/fragments.h
Normal file
41
src/menu/views/fragments/fragments.h
Normal 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
|
55
src/menu/views/fragments/widgets.c
Normal file
55
src/menu/views/fragments/widgets.c
Normal 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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
load(menu);
|
||||
if (load_pending) {
|
||||
load_pending = false;
|
||||
load(menu);
|
||||
}
|
||||
}
|
||||
|
117
src/menu/views/player.c
Normal file
117
src/menu/views/player.c
Normal 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
23
src/menu/views/startup.c
Normal 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);
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user