botw-completer/source/ui.c
badbl0cks 6bff4b6fcd
Refactor codebase and complete implementation
- Consolidate development files into final implementation
- Replace basic prototypes with full feature implementations
- Add complete hash-based save data access system
- Implement advanced map pin management algorithms
- Add comprehensive error handling and user feedback
- Complete Korok seed database with all 908 locations
2023-05-24 09:08:09 -07:00

272 lines
No EOL
8 KiB
C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <switch.h>
#include "ui.h"
#include "save.h"
#include "map.h"
#define FB_MAX_COLS 80
#define FB_MAX_LINES 45
#define MSG_MAX_LINE_LEN 45
#define MSG_START_COL (FB_MAX_COLS-MSG_MAX_LINE_LEN)
#define MSG_MAX_LEN (MSG_MAX_LINE_LEN*FB_MAX_LINES)
#define FB_MIDDLE_LINE (FB_MAX_LINES/2)
// this converts to string
#define STR_(X) #X
// this makes sure the argument is expanded before converting to string
#define STR(X) STR_(X)
MenuItem main_menu_items[6] = {
{"Add Missing Koroks to Map", &sortKoroksByDistance},
{"Erase Completed Korok Pins", &eraseCompletedKorokPins},
{"Erase All Map Pins", &eraseMapPins},
{"Convert Invalid Map Pins", &convertInvalidMapPinIcons},
{"Save and Exit", &saveAndExitApp},
{"Exit w/o Saving", &exitApp}
};
Menu main_menu = {
.HeaderStr = "BOTW KOROK COMPLETER",
.ItemCount = 6,
.Menu_Items = main_menu_items
};
MenuItem confirm_menu_items[2] = {
{"No, cancel!", NULL},
{"Yes, do it!", NULL}
};
Menu confirm_menu = {
.HeaderStr = "ARE YOU SURE??",
.ItemCount = 2,
.Menu_Items = confirm_menu_items
};
Menu *current_menu;
Menu *previous_menu;
char MSG_BUFFER[FB_MAX_LINES][MSG_MAX_LINE_LEN+1];
u32 msg_buf_write_line = 0;
u32 msg_buf_line_count = 0;
bool exit_state = false;
bool confirmed = false;
void _clearScreen() {
printf(CONSOLE_ESC(2J)); //clear screen and reset home cursor
}
void loadMenu(Menu* menu) {
current_menu = menu;
current_menu->StartLine = FB_MIDDLE_LINE - (current_menu->ItemCount / 2);
current_menu->SelectedIndex = 0;
_clearScreen();
_drawMenu();
_drawMessagePane();
}
void _drawMenu() {
//new code
printfAtCursor(2, current_menu->StartLine-2, current_menu->HeaderStr);
printfAtCursor(2, current_menu->StartLine, ">");
for (int i = 0; i < current_menu->ItemCount; i++) {
printfAtCursor(4, current_menu->StartLine+i, current_menu->Menu_Items[i].ItemStr);
}
//end new code
/*/old code
printfAtCursor(2, MENU_START_COL-2, "BOTW KOROK COMPLETER");
printfAtCursor(2, MENU_START_COL, "> Add Missing Koroks to Map");
printfAtCursor(2, MENU_START_COL+1, " Erase all map pins");
printfAtCursor(2, MENU_START_COL+2, " Convert invalid map pins");
printfAtCursor(2, MENU_START_COL+3, " Save and Exit");
printfAtCursor(2, MENU_START_COL+4, " Exit w/o Saving");
//end old code*/
//print vertical line between menu and messages panel
for (int i = 1; i <= FB_MAX_LINES; i++) {
printfAtCursor(FB_MAX_COLS - MSG_MAX_LINE_LEN - 2, i, "|");
}
}
void initUI() {
loadMenu(&main_menu);
}
void exitApp() {
exit_state = true;
}
void saveAndExitApp() {
closeAndCommitSave();
exitApp();
}
void handleMenuButtonPress(u64 button) {
//clear menu cursor
printfAtCursor(2, current_menu->StartLine+current_menu->SelectedIndex, " ");
switch (button) {
case HidNpadButton_Down:
if (current_menu->SelectedIndex == current_menu->ItemCount - 1) {
current_menu->SelectedIndex = 0;
} else {
current_menu->SelectedIndex++;
}
break;
case HidNpadButton_Up:
if (current_menu->SelectedIndex == 0) {
current_menu->SelectedIndex = current_menu->ItemCount-1;
} else {
current_menu->SelectedIndex--;
}
break;
case HidNpadButton_A: //run menu function, using menu item index: position - start_line
current_menu->Menu_Items[current_menu->SelectedIndex].ItemFunc();
break;
default:
break;
}
//print menu cursor
printfAtCursor(2, current_menu->StartLine+current_menu->SelectedIndex, ">");
}
bool handleButtonPress(u64 button) {
if (button & HidNpadButton_Plus) { return true; } // exit if + button pressed
handleMenuButtonPress(button);
return exit_state;
}
/*u32 handleMenuButtonPress(u64 button) {
switch (button) {
case HidNpadButton_Down:
printf(CONSOLE_ESC(%d;2H) " ", menu_pos);
if (menu_pos == MENU_END_LINE) { menu_pos = MENU_START_LINE; }
else { menu_pos++; }
printf(CONSOLE_ESC(%d;2H) ">", menu_pos);
break;
case HidNpadButton_Up:
printf(CONSOLE_ESC(%d;2H) " ", menu_pos);
if (menu_pos == MENU_START_LINE) { menu_pos = MENU_END_LINE; }
else { menu_pos--; }
printf(CONSOLE_ESC(%d;2H) ">", menu_pos);
break;
case HidNpadButton_A:
return menu_pos;
break;
default:
break;
}
return (enum menu_items)none;
}
bool handleButtonPress(u64 button) {
switch(handleMenuButtonPress(button)) {
case add_missing_koroks:
sortKoroksByDistance();
break;
case erase_all_map_pins:
eraseMapPins();
break;
case convert_invalid_map_pins:
convertInvalidMapPinIcons();
break;
case save_file:
writeSave();
closeAndCommitSave();
return true;
break;
case exit_app:
return true;
break;
default:
break;
}
return false;
}*/
void _confirm_no() {
loadMenu(previous_menu);
}
void confirmThenRunElse(func_t yes_func, func_t no_func) {
//save current menu
previous_menu = current_menu;
confirm_menu.Menu_Items[1].ItemFunc = yes_func;
confirm_menu.Menu_Items[0].ItemFunc = no_func;
loadMenu(&confirm_menu);
}
void confirmThenRun(func_t yes_func) {
confirmThenRunElse(yes_func, &_confirm_no);
}
void printfAtCursor(u32 x_pos, u32 y_pos, const char* format, ...) {
va_list args;
va_start(args, format);
_setCursorPos(x_pos, y_pos);
vprintf(format, args);
va_end(args);
}
void _setCursorPos(u32 x_pos, u32 y_pos) {
if (x_pos >= 1 && x_pos <= FB_MAX_COLS && y_pos >= 1 && y_pos <= FB_MAX_LINES) {
printf(CONSOLE_ESC(%d;%dH) , y_pos, x_pos);
}
}
void printMessage(const char* format, ...) {
//get variable args
va_list args;
va_start(args, format);
//process the format string
char message[MSG_MAX_LEN-FB_MAX_LINES];
vsnprintf(message, MSG_MAX_LEN-FB_MAX_LINES, format, args);
va_end(args);
//find out how many lines we are about to add to the buffer/print
size_t length = strlen(message);
u32 num_of_lines = length / MSG_MAX_LINE_LEN;
if (length%MSG_MAX_LINE_LEN != 0) {num_of_lines++;}
char *line;
for (int i = 0; i < num_of_lines; i++) {
//extract the next set of characters and store in line
line = strndup(message + (MSG_MAX_LINE_LEN*i), MSG_MAX_LINE_LEN);
//clear previous line
memset(MSG_BUFFER[msg_buf_write_line], 0, MSG_MAX_LINE_LEN+1);
//copy line to MSG_BUFFER at write_ptr
strncpy(MSG_BUFFER[msg_buf_write_line], line, MSG_MAX_LINE_LEN);
MSG_BUFFER[msg_buf_write_line][MSG_MAX_LINE_LEN] = '\0';
msg_buf_write_line++;
//free original line after we've copied it
free(line);
//if count is below max lines, then increase count
if (msg_buf_line_count < FB_MAX_LINES) { msg_buf_line_count++; }
// if the next write line is past the last index, set the next index to 0.
if (msg_buf_write_line == FB_MAX_LINES) { msg_buf_write_line = 0; }
_drawMessagePane();
}
}
void _drawMessagePane() {
//print all lines in buffer, from top to bottom, oldest to newest
if (msg_buf_line_count > 0) {
u32 start_line = 0;
if (msg_buf_line_count == FB_MAX_LINES) {
start_line = msg_buf_write_line;
}
for (int i = 0; i < msg_buf_line_count; i++) {
_setCursorPos(MSG_START_COL, i+1);
printf("%-" STR(MSG_MAX_LINE_LEN) "s", MSG_BUFFER[(start_line + i) % FB_MAX_LINES]);
}
}
}