Add board-name editor + auto-prompt for global leaderboard

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 <noreply@anthropic.com>
This commit is contained in:
Ville Lindholm 2026-06-05 11:42:32 +03:00
parent 9238ce61c6
commit 8b3e21b5f3
No known key found for this signature in database
GPG Key ID: 89AE9EAA3B6FDE7C
2 changed files with 284 additions and 0 deletions

View File

@ -1357,6 +1357,18 @@ kobo_form_t *st_main_menu_t::open()
menu = new main_menu_t; menu = new main_menu_t;
menu->open(); 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; return menu;
} }
@ -1561,6 +1573,8 @@ void options_main_t::build()
button("Graphics", 6); button("Graphics", 6);
button("Audio", 2); button("Audio", 2);
button("System", 5); button("System", 5);
if(prefs->global_leaderboard)
button("Leaderboard Board Name", 7);
space(); space();
button("DONE!", 0); button("DONE!", 0);
@ -1595,6 +1609,9 @@ void st_options_main_t::select(int tag)
case 6: case 6:
gsm.push(&st_options_graphics); gsm.push(&st_options_graphics);
break; 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; 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;

View File

@ -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 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_audio_t st_options_audio;
extern st_options_control_t st_options_control; extern st_options_control_t st_options_control;
extern st_options_game_t st_options_game; 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_global_leaderboard_t st_global_leaderboard;
extern st_error_t st_error; extern st_error_t st_error;
#ifdef PROFILE_AUDIO #ifdef PROFILE_AUDIO