From 8b3e21b5f323152d610bd5c59a11cdd79f987742 Mon Sep 17 00:00:00 2001 From: Ville Lindholm Date: Fri, 5 Jun 2026 11:42:32 +0300 Subject: [PATCH] Add board-name editor + auto-prompt for global leaderboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the global leaderboard is enabled but no board name is configured, prompt the user for one on first main-menu entry of the session. The editor itself is also reachable any time via Options > Leaderboard Board Name. - edit_board_t / st_edit_board_t: clones the new_player_t arrow-key text editor. Bound to prefs->leaderboard_board; allows [A-Za-z0-9_-]; OK trims trailing arrow-key noise, writes back, and marks prefs dirty. - st_main_menu_t::open: on first main-menu entry of the session with global_leaderboard on and board empty, push st_edit_board. Static guard prevents re-prompting after cancel (user can always reach the editor via the options menu). - options_main_t: adds "Leaderboard Board Name" entry, gated on global_leaderboard being on so it doesn't clutter the menu otherwise. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- states.cpp | 252 +++++++++++++++++++++++++++++++++++++++++++++++++++++ states.h | 32 +++++++ 2 files changed, 284 insertions(+) diff --git a/states.cpp b/states.cpp index 367f1bc..8502938 100644 --- a/states.cpp +++ b/states.cpp @@ -1357,6 +1357,18 @@ kobo_form_t *st_main_menu_t::open() menu = new main_menu_t; menu->open(); + + // First time we reach the main menu with the leaderboard on but no + // board name configured, prompt for one. Only once per session — if + // the user cancels, they can edit it from Options later. + static bool prompted_for_board = false; + if(!prompted_for_board && prefs->global_leaderboard + && prefs->leaderboard_board[0] == '\0') + { + prompted_for_board = true; + gsm.push(&st_edit_board); + } + return menu; } @@ -1561,6 +1573,8 @@ void options_main_t::build() button("Graphics", 6); button("Audio", 2); button("System", 5); + if(prefs->global_leaderboard) + button("Leaderboard Board Name", 7); space(); button("DONE!", 0); @@ -1595,6 +1609,9 @@ void st_options_main_t::select(int tag) case 6: gsm.push(&st_options_graphics); break; + case 7: + gsm.push(&st_edit_board); + break; } } @@ -2142,3 +2159,238 @@ void st_global_leaderboard_t::post_render() st_global_leaderboard_t st_global_leaderboard; + +/*---------------------------------------------------------- + Edit leaderboard board name +----------------------------------------------------------*/ +// +// Cloned from new_player_t. Differences: bound to prefs->leaderboard_board, +// allows alphanumerics + '-' / '_' (room-code shape), no profile creation — +// OK writes the value back into prefs and pops. + +void edit_board_t::open() +{ + init(gengine); + place(wmain->x(), wmain->y(), wmain->width(), wmain->height()); + font(B_NORMAL_FONT); + foreground(wmain->map_rgb(255, 255, 255)); + background(wmain->map_rgb(0, 0, 0)); + memset(buf, 0, sizeof(buf)); + strncpy(buf, prefs->leaderboard_board, sizeof(buf) - 1); + currentIndex = strlen(buf); + if(currentIndex == 0) + { + buf[0] = 'A'; + currentIndex = 0; + } + editing = 1; + build_all(); + if(gengine->window_handle()) + SDL_StartTextInput(gengine->window_handle()); +} + +void edit_board_t::close() +{ + if(gengine->window_handle()) + SDL_StopTextInput(gengine->window_handle()); + clean(); +} + +void edit_board_t::change(int delta) +{ + kobo_form_t::change(delta); + if(!selected()) return; + selection = selected()->tag; +} + +void edit_board_t::build() +{ + medium(); + space(2); + label("Global Leaderboard"); + label("Use arrows or keyboard to enter board name"); + label("(share it with friends to compete)"); + + big(); + space(); + button(buf, 1); + space(); + + button("Ok", MENU_TAG_OK); + button("Cancel", MENU_TAG_CANCEL); +} + +void edit_board_t::rebuild() +{ + int sel = selected_index(); + build_all(); + select(sel); +} + +static bool board_char_valid(int c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '-' || c == '_'; +} + +st_edit_board_t::st_edit_board_t() +{ + name = "edit_board"; +} + +void st_edit_board_t::frame() { manage.run_intro(); } +void st_edit_board_t::enter() { menu.open(); run_intro = 0; sound.ui_ok(); } +void st_edit_board_t::leave() { menu.close(); } +void st_edit_board_t::post_render() { kobo_basestate_t::post_render(); menu.render(); } + +void st_edit_board_t::press(int button) +{ + if(menu.editing) + { + switch(button) + { + case BTN_EXIT: + sound.ui_ok(); + menu.editing = 0; + menu.next(); // Select the CANCEL option. + menu.next(); + break; + + case BTN_FIRE: + if(!prefs->use_joystick) break; + // fallthrough + case BTN_START: + case BTN_SELECT: + sound.ui_ok(); + menu.editing = 0; + menu.next(); // Select the OK option. + break; + + case BTN_UP: + case BTN_INC: + { + char &c = menu.buf[menu.currentIndex]; + if(!c) c = 'A'; + else if(c == 'Z') c = 'a'; + else if(c == 'z') c = '0'; + else if(c == '9') c = '-'; + else if(c == '-') c = '_'; + else if(c == '_') c = 'A'; + else c++; + sound.ui_tick(); + break; + } + + case BTN_DOWN: + case BTN_DEC: + { + char &c = menu.buf[menu.currentIndex]; + if(!c) c = 'A'; + else if(c == 'A') c = '_'; + else if(c == '_') c = '-'; + else if(c == '-') c = '9'; + else if(c == '0') c = 'z'; + else if(c == 'a') c = 'Z'; + else c--; + sound.ui_tick(); + break; + } + + case BTN_RIGHT: + if(menu.currentIndex < sizeof(menu.buf) - 2) + { + menu.currentIndex++; + sound.ui_tick(); + } + else + { + sound.ui_error(); + break; + } + if(menu.buf[menu.currentIndex] == '\0') + menu.buf[menu.currentIndex] = 'A'; + break; + + case BTN_LEFT: + case BTN_BACK: + if(menu.currentIndex > 0) + { + menu.buf[menu.currentIndex] = '\0'; + menu.currentIndex--; + sound.ui_tick(); + } + else + sound.ui_error(); + break; + + default: + if(board_char_valid(unicode)) + { + menu.buf[menu.currentIndex] = (char)unicode; + if(menu.currentIndex < sizeof(menu.buf) - 2) + { + menu.currentIndex++; + sound.ui_tick(); + } + else + sound.ui_error(); + } + else + sound.ui_error(); + break; + } + menu.rebuild(); + } + else + { + menu.selection = -1; + switch(button) + { + case BTN_EXIT: menu.selection = MENU_TAG_CANCEL; break; + case BTN_CLOSE: menu.selection = MENU_TAG_OK; break; + case BTN_FIRE: + case BTN_START: + case BTN_SELECT: menu.change(0); break; + case BTN_INC: + case BTN_UP: menu.prev(); break; + case BTN_DEC: + case BTN_DOWN: menu.next(); break; + } + + switch(menu.selection) + { + case 1: + if(button == BTN_START || button == BTN_SELECT + || button == BTN_FIRE) + { + sound.ui_ok(); + menu.editing = 1; + } + break; + + case MENU_TAG_OK: + { + // Trim trailing garbage from arrow-key wandering. + size_t n = strlen(menu.buf); + while(n > 0 && !board_char_valid(menu.buf[n - 1])) + menu.buf[--n] = '\0'; + strncpy(prefs->leaderboard_board, menu.buf, + sizeof(prefs->leaderboard_board) - 1); + prefs->leaderboard_board[ + sizeof(prefs->leaderboard_board) - 1] = '\0'; + prefs->changed = 1; + sound.ui_ok(); + pop(); + break; + } + + case MENU_TAG_CANCEL: + sound.ui_cancel(); + pop(); + break; + } + } +} + +st_edit_board_t st_edit_board; + diff --git a/states.h b/states.h index def3136..2ae5675 100644 --- a/states.h +++ b/states.h @@ -473,6 +473,37 @@ class st_ask_abort_game_t : public st_yesno_base_t }; +/*---------------------------------------------------------- + Edit leaderboard board name +----------------------------------------------------------*/ + +class edit_board_t : public kobo_form_t +{ + public: + int editing; + char buf[32]; + unsigned currentIndex; + int selection; + virtual void change(int delta); + virtual void build(); + void open(); + void close(); + void rebuild(); +}; + +class st_edit_board_t : public kobo_basestate_t +{ + public: + edit_board_t menu; + st_edit_board_t(); + void enter(); + void leave(); + void press(int button); + void frame(); + void post_render(); +}; + + /*---------------------------------------------------------- Global Leaderboard browser ----------------------------------------------------------*/ @@ -550,6 +581,7 @@ extern st_options_graphics_t st_options_graphics; extern st_options_audio_t st_options_audio; extern st_options_control_t st_options_control; extern st_options_game_t st_options_game; +extern st_edit_board_t st_edit_board; extern st_global_leaderboard_t st_global_leaderboard; extern st_error_t st_error; #ifdef PROFILE_AUDIO