Merge branch 'release-candidate' into text-debug-screen

This commit is contained in:
GearsProgress 2026-03-03 14:04:12 -05:00
commit bf5cbbb438
6 changed files with 785 additions and 142 deletions

View File

@ -1,33 +1,29 @@
#ifndef SELECT_MENU_H
#define SELECT_MENU_H
#include <tonc.h>
#include "libstd_replacements.h"
#include "text_engine.h"
#include "vertical_menu.h"
#define LANG_MENU 1
#define CART_MENU 2
class Select_Menu
class Select_Menu : public i_vertical_menu_state_changed_handler, public i_run_cycle_handler
{
public:
Select_Menu(bool enable_cancel, u8 nMenu_type, int nStartX, int nStartY);
Select_Menu(bool enable_cancel, u8 nMenu_type, unsigned nStartX, unsigned nStartY);
int select_menu_main();
void hide_menu();
void show_menu();
void clear_options();
void add_option(const u8 option, u8 return_value);
void clear_options();
void set_lang(u8 nLang);
void on_show() override;
void on_hide() override;
void on_selection_changed(unsigned new_index, unsigned x, unsigned y) override;
void on_run_cycle() override;
private:
ptgb::vector<u8> menu_options;
ptgb::vector<u8> return_values;
u16 curr_selection;
bool cancel_enabled;
vertical_menu menu_widget_;
u8 menu_type;
u8 lang;
int startTileX;
int startTileY;
};
#endif

94
include/sound.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef _SOUND_H
#define _SOUND_H
#include <tonc.h>
#include "soundbank.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef unsigned short PTGBSFXHandle;
/**
* @brief The API's defined here are a thin abstraction layer over the sound engine functions.
* This allows the underlying sound engine to be swapped out without affecting the rest of the codebase.
*/
/**
* @brief This function initializes the sound engine and sets up a VBLANK handler
* to process audio every frame. It should be called during the initialization phase of the program.
*/
bool sound_init(void);
/**
* @brief This function starts playing the song at the given index.
* If loop is true, the song will loop indefinitely until stopped or another song is played.
*
* @param song_index The index of the song to play.
* @param loop Whether the song should loop indefinitely.
*/
void play_song(u32 song_index, bool loop);
/**
* @brief This function checks if a song is currently playing.
*
* @return true if a song is playing, false otherwise.
*/
bool is_song_playing(void);
/**
* @brief This function stops the currently playing song.
*/
void stop_song(void);
/**
* @brief This function plays the sound effect at the given index.
* Sound effects are typically short audio clips that play in response to specific events in the game,
* such as button presses or character actions.
* These are usually raw PCM samples (e.g., WAV files)
*
* To play a MOD- S3M- XM- or IT format based sound effect,
* use the play_jingle function instead.
*/
PTGBSFXHandle play_sound_effect(u32 sound_effect_index);
/**
* @brief This function stops the given sound effect from playing and invalidates the handle
*/
void stop_sound_effect(PTGBSFXHandle handle);
/**
* @brief This function stops all currently playing sound effects and resets
*/
void stop_all_sound_effects(void);
/**
* @brief This function marks the sound effect as unimportant,
* allowing the sound engine to stop it if it needs to free up channels for new sound effects.
*
* It also invalidates the handle, so it should not be used after calling this function.
*/
void release_sound_effect(PTGBSFXHandle handle);
/**
* @brief This function plays a jingle at the given index.
* A jingle is a sound effect that is based on a tracker format like MOD, S3M, XM, or IT.
* They can be played simultaneously with songs.
* If you want to play PCM-based sound effects (e.g., WAV files), use the play_sound_effect function instead.
*
* Note that jingles must be limited to 4 channels only. (https://blocksds.skylyrac.net/maxmod/group__gba__jingle__playback.html)
*/
void play_jingle(u32 jingle_index);
/**
* @brief Checks if a jingle is actively playing.
*/
bool is_jingle_playing(void);
#ifdef __cplusplus
}
#endif
#endif

245
include/vertical_menu.h Normal file
View File

@ -0,0 +1,245 @@
#ifndef _VERTICAL_MENU_H
#define _VERTICAL_MENU_H
#include "typeDefs.h"
#include "libstd_replacements.h"
#include "text_data_table.h"
enum class MenuInputHandleState
{
/**
* @brief This value means no input was handled
*/
NOT_HANDLED,
/**
* @brief This value means that the input was handled.
*/
HANDLED,
/**
* @brief This value means that a choice was made.
* This means that if the vertical_menu is running with ::run(),
* it should exit and return that choice.
*/
CHOICE_MADE,
/**
* @brief The menu was cancelled.
*/
CANCELLED
};
/**
* @brief This interface represents an item widget in the vertical menu.
* It's responsible for rendering itself and handling its input if
* the widget is focused.
*/
class i_item_widget
{
public:
virtual ~i_item_widget();
virtual void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) = 0;
virtual MenuInputHandleState handle_input() = 0;
protected:
private:
};
/**
* @brief This interface allows you to register a handler
* to deal with selection changes.
* This is useful for managing external components, such as a cursor.
*/
class i_vertical_menu_state_changed_handler
{
public:
virtual ~i_vertical_menu_state_changed_handler();
virtual void on_show() = 0;
virtual void on_hide() = 0;
virtual void on_selection_changed(unsigned new_index, unsigned x, unsigned y) = 0;
protected:
private:
};
/**
* @brief If you are using the vertical_menu::run() function,
* you may want to manage external components on very run cycle.
* This interface allows you to do that by implementing the i_run_cycle_handler
*/
class i_run_cycle_handler
{
public:
virtual ~i_run_cycle_handler();
virtual void on_run_cycle() = 0;
protected:
private:
};
typedef struct vertical_menu_settings
{
unsigned x;
unsigned y;
unsigned width;
unsigned height;
unsigned margin_top;
unsigned margin_bottom;
unsigned initial_focus_index;
unsigned item_height;
int text_table_index;
/**
* @brief This boolean indicates whether the menu
* can be "cancelled with the B button."
*/
bool allow_cancel;
/**
* @brief This boolean indicates whether the menu should take
* ownership of its item widgets.
* That means that if this boolean is true,
* the destructor of vertical_menu will delete/free all of its
* item widgets.
*/
bool should_delete_item_widgets_on_destruct;
/**
* @brief This boolean indicates whether the state changed handler (cursor?)
* should be hidden when the menu is not focused.
*/
bool should_hide_state_changed_handler_on_not_focused;
} vertical_menu_settings;
class vertical_menu
{
public:
vertical_menu(const vertical_menu_settings &settings);
~vertical_menu();
void show();
void hide();
/**
* @brief Get the current settings of this widget
*/
const vertical_menu_settings& get_settings() const;
/**
* @brief Replace the settings of this widget.
*/
void set_settings(const vertical_menu_settings &settings);
/**
* @brief This function is a way to bulk add item widgets to the menu.
* The main difference between calling this function and add_item_widget()
* multiple times is that this function will reserve() enough space in the
* vector first, to avoid multiple resizes.
*/
void add_item_widgets(i_item_widget **item_widgets, unsigned num_widgets);
/**
* @brief This function adds an item widget to the menu.
* The menu will take ownership of the pointer, if
* settings_.should_delete_item_widgets_on_destruct is true.
* Otherwise, the caller is responsible for managing the memory of the item widgets.
*/
void add_item_widget(i_item_widget *item_widget);
/**
* @brief This function clears all item widgets from the menu.
* If settings_.should_delete_item_widgets_on_destruct is true,
* it will also delete the item widgets.
*/
void clear_item_widgets();
/**
* @brief This function gets the item widget
* at the given index.
*/
i_item_widget* get_item_widget_at(unsigned index) const;
/**
* @brief Sets whether the menu is focused or not.
* A focused menu will handle input.
*/
void set_focused(bool is_focused);
/**
* @brief This function handles button input for the menu.
*/
MenuInputHandleState handle_input();
/**
* @brief This function updates the viewport by
* re-rendering the visible items.
*/
void update_viewport();
void clear_viewport();
/**
* @brief This function can be used to manage a blocking call
* to vertical_menu, in which you want vertical_menu
* to handle everything.
*
* This can be useful during one of those script_array commands.
* Alternatively, you can just call handle_input() and manage the loop
* manually.
*
* @return Will return the index of the chosen item if a choice was made,
* or UINT32_MAX if no choice was made and the menu was exited in some other way
* (ex: B button).
*/
unsigned run();
/**
* @brief Sets the optional state changed handler.
* This is useful for managing external components, such as a cursor.
*/
void set_state_changed_handler(i_vertical_menu_state_changed_handler *handler);
/**
* @brief This function sets a handler that will run on every
* cycle in the run() function.
*
* It's only relevant if you use ::run() though.
*/
void set_run_cycle_handler(i_run_cycle_handler *handler);
protected:
private:
void handle_selection_change(unsigned new_index, unsigned x, unsigned y);
vertical_menu_settings settings_;
i_vertical_menu_state_changed_handler *state_changed_handler_;
i_run_cycle_handler *run_cycle_handler_;
unsigned focused_index_;
unsigned viewport_start_index_;
ptgb::vector<i_item_widget*> items_;
bool is_focused_;
};
typedef struct simple_item_widget_data
{
struct
{
unsigned text_table_index;
unsigned margin_left;
unsigned margin_top;
} text;
unsigned value;
void (*on_execute_callback)(void *context);
} simple_item_widget_data;
class simple_item_renderer : public i_item_widget
{
public:
simple_item_renderer(const simple_item_widget_data &data);
virtual ~simple_item_renderer();
const simple_item_widget_data& get_data() const;
void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) override;
MenuInputHandleState handle_input() override;
protected:
private:
simple_item_widget_data data_;
};
#endif

View File

@ -682,6 +682,8 @@ const script_obj_params event_script_params[SCRIPT_SIZE] = {
void populate_lang_menu()
{
langs.clear_options();
langs.add_option(GENERAL_option_english, ENG_ID);
langs.add_option(GENERAL_option_japanese, JPN_ID);
langs.add_option(GENERAL_option_spanish, SPA_ID);
@ -696,6 +698,8 @@ void populate_lang_menu()
void populate_game_menu(int lang)
{
games.clear_options();
switch (lang)
{
case (JPN_ID):
@ -849,6 +853,9 @@ bool run_conditional(int index)
case CMD_LANG_MENU:
populate_lang_menu();
lang = langs.select_menu_main();
// We have our choice, we should release
// the memory of the options since we won't need them anymore
langs.clear_options();
if (lang == BUTTON_CANCEL)
{
return false;
@ -856,17 +863,18 @@ bool run_conditional(int index)
games.set_lang(static_cast<u8>(lang));
party_data.set_lang(static_cast<u8>(lang));
return true;
case CMD_GAME_MENU:
populate_game_menu(party_data.get_lang());
game = games.select_menu_main();
// We have our choice, we should release
// the memory of the options since we won't need them anymore
games.clear_options();
if (game == BUTTON_CANCEL)
{
return false;
}
party_data.set_game(game);
return true;
case CMD_SLIDE_PROF_LEFT:
for (int i = 0; i <= (8 * 7); i += 2)
{

View File

@ -9,145 +9,133 @@
#define TILE_HEIGHT 8
#define TILE_WIDTH 8
Select_Menu::Select_Menu(bool enable_cancel, u8 nMenu_type, int nStartTileX, int nStartTileY)
Select_Menu::Select_Menu(bool enable_cancel, u8 nMenu_type, unsigned nStartTileX, unsigned nStartTileY)
: menu_widget_(vertical_menu_settings{
.x = static_cast<unsigned>(nStartTileX * TILE_WIDTH),
.y = static_cast<unsigned>(nStartTileY * TILE_HEIGHT),
.width = 10 * TEXT_WIDTH,
.height = TILE_HEIGHT * 2, // to account for the margins
.margin_top = TILE_HEIGHT,
.margin_bottom = TILE_HEIGHT,
.initial_focus_index = 0,
.item_height = TEXT_HEIGHT,
.text_table_index = GENERAL_INDEX,
.allow_cancel = enable_cancel,
.should_delete_item_widgets_on_destruct = true,
.should_hide_state_changed_handler_on_not_focused = false
})
, menu_type(nMenu_type)
, lang(0)
{
cancel_enabled = enable_cancel;
menu_type = nMenu_type;
startTileX = nStartTileX;
startTileY = nStartTileY;
menu_widget_.set_state_changed_handler(this);
menu_widget_.set_run_cycle_handler(this);
}
void Select_Menu::add_option(const u8 option, u8 return_value)
{
menu_options.push_back(option);
return_values.push_back(return_value);
}
const simple_item_widget_data item_data = {
.text = {
.text_table_index = option,
.margin_left = 2 * TILE_WIDTH,
.margin_top = 0
},
.value = return_value,
.on_execute_callback = nullptr
};
menu_widget_.add_item_widget(new simple_item_renderer(item_data));
int Select_Menu::select_menu_main()
{
show_menu();
curr_selection = 0;
key_poll(); // Reset the buttons
bool update;
bool first = true;
while (true)
{
update = false;
if (key_hit(KEY_DOWN))
{
curr_selection = ((curr_selection + 1) % menu_options.size());
update = true;
}
else if (key_hit(KEY_UP))
{
curr_selection = ((curr_selection + (menu_options.size() - 1)) % menu_options.size());
update = true;
}
else if (key_hit(KEY_A))
{
hide_menu();
return return_values[curr_selection];
}
else if (cancel_enabled && key_hit(KEY_B))
{
hide_menu();
return -1;
}
else if (first)
{
update = true;
first = false;
}
update_y_offset();
obj_set_pos(
point_arrow,
(startTileX + 1) * TEXT_WIDTH,
(startTileY + 1) * TILE_HEIGHT + (curr_selection * TEXT_HEIGHT) + 2);
global_next_frame();
if (update)
{
if (return_values[curr_selection] == UINT8_MAX)
{
switch (menu_type)
{
case CART_MENU:
obj_hide(cart_shell);
obj_hide(cart_label);
break;
case LANG_MENU:
obj_hide(flag);
break;
}
}
else
{
switch (menu_type)
{
case CART_MENU:
load_select_sprites(return_values[curr_selection], lang);
obj_unhide(cart_shell, 0);
obj_unhide(cart_label, 0);
break;
case LANG_MENU:
load_select_sprites(0, return_values[curr_selection]);
obj_unhide(flag, 0);
break;
}
}
}
}
return 0;
}
void Select_Menu::show_menu()
{
u8 decompression_buffer[2048];
text_data_table text_data(decompression_buffer);
text_data.decompress(get_compressed_text_table(GENERAL_INDEX));
add_menu_box(menu_options.size(), startTileX, startTileY);
for (unsigned int i = 0; i < menu_options.size(); i++)
{
tte_set_pos((startTileX + 2) * TEXT_WIDTH, (startTileY + 1) * TILE_HEIGHT + (i * TEXT_HEIGHT));
ptgb_write(text_data.get_text_entry(menu_options[i]), true);
}
obj_unhide(point_arrow, 0);
// obj_set_pos(point_arrow, startTileX + (2 * TEXT_WIDTH), (1 + i) * TEXT_HEIGHT);
}
void Select_Menu::hide_menu()
{
obj_hide(point_arrow);
tte_erase_rect(
startTileX * TILE_WIDTH,
startTileY * TILE_HEIGHT,
(startTileX + 10 + 1) * TEXT_WIDTH,
((startTileY + 2) * TILE_HEIGHT) + (menu_options.size() * TEXT_HEIGHT));
reload_textbox_background();
clear_options();
obj_hide(point_arrow);
switch (menu_type)
{
case CART_MENU:
obj_hide(cart_shell);
obj_hide(cart_label);
break;
case LANG_MENU:
obj_hide(flag);
break;
}
vertical_menu_settings settings = menu_widget_.get_settings();
settings.height += TEXT_HEIGHT + (item_data.text.margin_top * 2);
menu_widget_.set_settings(settings);
}
void Select_Menu::clear_options()
{
menu_options.clear();
return_values.clear();
menu_widget_.clear_item_widgets();
vertical_menu_settings settings = menu_widget_.get_settings();
settings.height = TILE_HEIGHT * 2; // reset to just the margins
menu_widget_.set_settings(settings);
}
int Select_Menu::select_menu_main()
{
unsigned choice_index;
unsigned item_value;
simple_item_renderer* widget = nullptr;
menu_widget_.show();
choice_index = menu_widget_.run();
if(choice_index != UINT32_MAX)
{
widget = static_cast<simple_item_renderer*>(menu_widget_.get_item_widget_at(choice_index));
}
item_value = (widget) ? widget->get_data().value : UINT8_MAX;
menu_widget_.hide();
global_next_frame();
return item_value;
}
void Select_Menu::set_lang(u8 nLang)
{
lang = nLang;
}
void Select_Menu::on_show()
{
obj_unhide(point_arrow, 0);
}
void Select_Menu::on_hide()
{
obj_hide(point_arrow);
obj_hide(cart_shell);
obj_hide(cart_label);
obj_hide(flag);
}
void Select_Menu::on_selection_changed(unsigned new_index, unsigned x, unsigned y)
{
// set the cursor accordingly
obj_set_pos(point_arrow, x + TEXT_WIDTH, y + 3);
simple_item_renderer* widget = static_cast<simple_item_renderer*>(menu_widget_.get_item_widget_at(new_index));
const unsigned item_value = (widget) ? widget->get_data().value : UINT8_MAX;
if (item_value == UINT8_MAX)
{
switch (menu_type)
{
case CART_MENU:
obj_hide(cart_shell);
obj_hide(cart_label);
break;
case LANG_MENU:
obj_hide(flag);
break;
}
}
else
{
switch (menu_type)
{
case CART_MENU:
load_select_sprites(item_value, lang);
obj_unhide(cart_shell, 0);
obj_unhide(cart_label, 0);
break;
case LANG_MENU:
load_select_sprites(0, item_value);
obj_unhide(flag, 0);
break;
}
}
}
void Select_Menu::on_run_cycle()
{
update_y_offset();
}

312
source/vertical_menu.cpp Normal file
View File

@ -0,0 +1,312 @@
#include "vertical_menu.h"
#include "sprite_data.h"
#include "global_frame_controller.h"
#include "text_engine.h"
#include "translated_text.h"
#define TILE_HEIGHT 8
#define TILE_WIDTH 8
i_item_widget::~i_item_widget()
{
}
i_vertical_menu_state_changed_handler::~i_vertical_menu_state_changed_handler()
{
}
i_run_cycle_handler::~i_run_cycle_handler()
{
}
static unsigned get_num_visible_items(unsigned vertical_menu_height, unsigned margin_top, unsigned margin_bottom, unsigned item_height)
{
return ((vertical_menu_height - margin_top - margin_bottom) / item_height);
}
static unsigned get_viewport_end_index(unsigned viewport_start_index, unsigned num_visible_items, unsigned total_items)
{
unsigned end_index = viewport_start_index + num_visible_items;
// bounds check to make sure we don't go past the total number of items
end_index = std::min(end_index, total_items);
return end_index;
}
vertical_menu::vertical_menu(const vertical_menu_settings &settings)
: settings_(settings)
, state_changed_handler_(nullptr)
, run_cycle_handler_(nullptr)
, focused_index_(settings.initial_focus_index)
, viewport_start_index_(0)
, items_()
, is_focused_(true)
{
}
vertical_menu::~vertical_menu()
{
clear_item_widgets();
}
void vertical_menu::show()
{
add_menu_box(settings_.x / TILE_WIDTH, settings_.y / TILE_HEIGHT, settings_.width, settings_.height);
update_viewport();
if(state_changed_handler_)
{
state_changed_handler_->on_show();
}
// initial selection change notification
const unsigned render_index = focused_index_ - viewport_start_index_;
handle_selection_change(focused_index_, settings_.x, settings_.y + settings_.margin_top + (render_index * settings_.item_height));
}
i_item_widget* vertical_menu::get_item_widget_at(unsigned index) const
{
if(index >= items_.size())
{
return nullptr;
}
return items_[index];
}
void vertical_menu::hide()
{
if(state_changed_handler_)
{
state_changed_handler_->on_hide();
}
clear_viewport();
reload_textbox_background();
}
const vertical_menu_settings& vertical_menu::get_settings() const
{
return settings_;
}
void vertical_menu::set_settings(const vertical_menu_settings &settings)
{
settings_ = settings;
}
void vertical_menu::add_item_widgets(i_item_widget **item_widgets, unsigned num_widgets)
{
items_.reserve(items_.size() + num_widgets);
for (unsigned i = 0; i < num_widgets; ++i)
{
add_item_widget(item_widgets[i]);
}
}
void vertical_menu::add_item_widget(i_item_widget *item_widget)
{
items_.push_back(item_widget);
}
void vertical_menu::clear_item_widgets()
{
if (settings_.should_delete_item_widgets_on_destruct)
{
for (unsigned i = 0; i < items_.size(); ++i)
{
delete items_[i];
}
}
items_.clear();
focused_index_ = settings_.initial_focus_index;
viewport_start_index_ = 0;
}
void vertical_menu::set_focused(bool is_focused)
{
is_focused_ = is_focused;
if(state_changed_handler_ && settings_.should_hide_state_changed_handler_on_not_focused)
{
if(is_focused_)
{
state_changed_handler_->on_show();
}
else
{
state_changed_handler_->on_hide();
}
}
}
MenuInputHandleState vertical_menu::handle_input()
{
MenuInputHandleState result;
bool did_navigate = false;
// If no items or not focused, there's nothing to handle.
if(items_.size() <= 1 || !is_focused_)
{
return MenuInputHandleState::NOT_HANDLED;
}
// the focused item widget gets the first chance to handle input,
// since it might have some special behavior for certain keys.
result = items_[focused_index_]->handle_input();
if(result != MenuInputHandleState::NOT_HANDLED)
{
return result;
}
// if the user presses B, and the current settings allow it,
// we will return the CANCELLED state, which the caller can use to know that they should exit the menu.
if(settings_.allow_cancel && key_hit(KEY_B))
{
return MenuInputHandleState::CANCELLED;
}
if(key_hit(KEY_DOWN) && focused_index_ < (items_.size() - 1u))
{
const unsigned current_viewport_end_index = get_viewport_end_index(viewport_start_index_, get_num_visible_items(settings_.height, settings_.margin_top, settings_.margin_bottom, settings_.item_height), items_.size());
++focused_index_;
if(focused_index_ >= current_viewport_end_index)
{
++viewport_start_index_;
}
did_navigate = true;
}
else if(key_hit(KEY_UP) && focused_index_ > 0)
{
--focused_index_;
if(focused_index_ < viewport_start_index_)
{
--viewport_start_index_;
}
did_navigate = true;
}
// if we did navigate, we need to update the viewport and state changed handler.
if(did_navigate)
{
const unsigned render_index = focused_index_ - viewport_start_index_;
update_viewport();
handle_selection_change(focused_index_, settings_.x, settings_.y + settings_.margin_top + (render_index * settings_.item_height));
return MenuInputHandleState::HANDLED;
}
return MenuInputHandleState::NOT_HANDLED;
}
void vertical_menu::update_viewport()
{
uint8_t decompression_buffer[2048];
text_data_table text_table(decompression_buffer);
clear_viewport();
text_table.decompress(get_compressed_text_table(settings_.text_table_index));
const unsigned num_visible_items = get_num_visible_items(settings_.height, settings_.margin_top, settings_.margin_bottom, settings_.item_height);
const unsigned viewport_end_index = get_viewport_end_index(viewport_start_index_, num_visible_items, items_.size());
unsigned cur_y = settings_.y + settings_.margin_top;
for (unsigned i = viewport_start_index_; i < viewport_end_index; ++i)
{
items_[i]->render_item(text_table, settings_.x, cur_y, (i == focused_index_));
cur_y += settings_.item_height;
}
}
void vertical_menu::clear_viewport()
{
// tte_erase_rect expects (left, top, right, bottom), not (x, y, width, height)
tte_erase_rect(settings_.x, settings_.y, settings_.x + settings_.width, settings_.y + settings_.height);
}
unsigned vertical_menu::run()
{
MenuInputHandleState input_result;
while(true)
{
key_poll(); // Reset the buttons
input_result = handle_input();
switch(input_result)
{
case MenuInputHandleState::CHOICE_MADE:
return focused_index_;
case MenuInputHandleState::CANCELLED:
return UINT32_MAX;
default:
break;
}
// if any global elements need to be updated
// on every cycle (animations, for example)
// the run_cycle_handler can be used for that.
if(run_cycle_handler_)
{
run_cycle_handler_->on_run_cycle();
}
global_next_frame();
}
// should never happen
return UINT32_MAX;
}
void vertical_menu::set_state_changed_handler(i_vertical_menu_state_changed_handler *handler)
{
state_changed_handler_ = handler;
}
void vertical_menu::set_run_cycle_handler(i_run_cycle_handler *handler)
{
run_cycle_handler_ = handler;
}
void vertical_menu::handle_selection_change(unsigned new_index, unsigned x, unsigned y)
{
if(state_changed_handler_)
{
state_changed_handler_->on_selection_changed(new_index, x, y);
}
}
simple_item_renderer::simple_item_renderer(const simple_item_widget_data &data)
: data_(data)
{
}
simple_item_renderer::~simple_item_renderer()
{
}
const simple_item_widget_data& simple_item_renderer::get_data() const
{
return data_;
}
void simple_item_renderer::render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused)
{
tte_set_pos(x + data_.text.margin_left, y + data_.text.margin_top);
ptgb_write(text_table.get_text_entry(data_.text.text_table_index), true);
}
MenuInputHandleState simple_item_renderer::handle_input()
{
if(!key_hit(KEY_A))
{
return MenuInputHandleState::NOT_HANDLED;
}
if(data_.on_execute_callback)
{
data_.on_execute_callback(this);
return MenuInputHandleState::HANDLED;
}
return MenuInputHandleState::CHOICE_MADE;
}