mirror of
https://github.com/GearsProgress/Poke_Transporter_GB.git
synced 2026-03-21 17:34:42 -05:00
Merge branch 'release-candidate' into text-debug-screen
This commit is contained in:
commit
bf5cbbb438
|
|
@ -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
94
include/sound.h
Normal 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
245
include/vertical_menu.h
Normal 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
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
312
source/vertical_menu.cpp
Normal 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user