Start migrating audio options to use a menu builder

This commit is contained in:
James Lambert 2023-11-04 22:52:25 -06:00
parent 12a236de5c
commit 9ec21b9c1a
5 changed files with 448 additions and 31 deletions

View file

@ -232,7 +232,7 @@ build/assets/materials/hud.h build/assets/materials/hud_mat.c: assets/materials/
src/levels/level_def_gen.h: build/assets/materials/static.h
build/src/scene/hud.o: build/assets/materials/hud.h
build/src/scene/hud.o: build/assets/materials/hud.h build/src/audio/subtitles.h
build/src/scene/elevator.o: build/assets/models/props/round_elevator_collision.h \
build/assets/models/props/round_elevator.h \

View file

@ -84,8 +84,6 @@ void audioOptionsHandleSlider(short selectedItem, unsigned short* settingValue,
}
void audioOptionsRenderText(struct AudioOptions* audioOptions) {
audioOptions->gameVolumeText = menuBuildPrerenderedText(&gDejaVuSansFont, translationsGet(GAMEUI_SOUNDEFFECTVOLUME), GAMEPLAY_X + 8, GAMEPLAY_Y + 8);
audioOptions->musicVolumeText = menuBuildPrerenderedText(&gDejaVuSansFont, translationsGet(GAMEUI_MUSICVOLUME), GAMEPLAY_X + 8, GAMEPLAY_Y + 28);
audioOptions->subtitlesEnabled.prerenderedText = menuBuildPrerenderedText(
&gDejaVuSansFont,
translationsGet(GAMEUI_SUBTITLESANDSOUNDEFFECTS),
@ -104,16 +102,85 @@ void audioOptionsRenderText(struct AudioOptions* audioOptions) {
audioOptions->audioLanguageDynamicText = menuBuildPrerenderedText(&gDejaVuSansFont, AudioLanguages[gSaveData.audio.audioLanguage], GAMEPLAY_X + 125, GAMEPLAY_Y + 124);
}
struct MenuElementParams gAudioMenuParams[] = {
{
.type = MenuElementTypeText,
.x = GAMEPLAY_X + 8,
.y = GAMEPLAY_Y + 8,
.params = {
.text = {
.font = &gDejaVuSansFont,
.messageId = GAMEUI_SOUNDEFFECTVOLUME,
},
},
.selectionIndex = AudioOptionGameVolume,
},
{
.type = MenuElementTypeSlider,
.x = GAMEPLAY_X + 120,
.y = GAMEPLAY_Y + 8,
.params = {
.slider = {
.width = 120,
.numberOfTicks = 9,
.discrete = 0,
},
},
.selectionIndex = AudioOptionGameVolume,
},
{
.type = MenuElementTypeText,
.x = GAMEPLAY_X + 8,
.y = GAMEPLAY_Y + 28,
.params = {
.text = {
.font = &gDejaVuSansFont,
.messageId = GAMEUI_MUSICVOLUME,
},
},
.selectionIndex = AudioOptionMusicVolume,
},
{
.type = MenuElementTypeSlider,
.x = GAMEPLAY_X + 120,
.y = GAMEPLAY_Y + 28,
.params = {
.slider = {
.width = 120,
.numberOfTicks = 9,
.discrete = 0,
},
},
.selectionIndex = AudioOptionMusicVolume,
},
};
void audioOptionsActoin(void* data, int selection, struct MenuAction* action) {
switch (selection) {
case AudioOptionGameVolume:
gSaveData.audio.soundVolume = (int)(0xFFFF * action->state.fSlider.value);
soundPlayerGameVolumeUpdate();
break;
case AudioOptionMusicVolume:
gSaveData.audio.musicVolume = (int)(0xFFFF * action->state.fSlider.value);
soundPlayerGameVolumeUpdate();
break;
}
}
void audioOptionsInit(struct AudioOptions* audioOptions) {
menuBuilderInit(
&audioOptions->menuBuilder,
gAudioMenuParams,
sizeof(gAudioMenuParams) / sizeof(*gAudioMenuParams),
AudioOptionCount,
audioOptionsActoin,
audioOptions
);
audioOptions->selectedItem = AudioOptionGameVolume;
int temp;
audioOptions->gameVolume = menuBuildSlider(GAMEPLAY_X + 120, GAMEPLAY_Y + 8, 120, SCROLL_TICKS_VOLUME);
audioOptions->gameVolume.value = (float)gSaveData.audio.soundVolume/0xFFFF;
audioOptions->musicVolume = menuBuildSlider(GAMEPLAY_X + 120, GAMEPLAY_Y + 28, 120, SCROLL_TICKS_VOLUME);
audioOptions->musicVolume.value = (float)gSaveData.audio.musicVolume/0xFFFF;
audioOptions->subtitlesEnabled = menuBuildCheckbox(&gDejaVuSansFont, translationsGet(GAMEUI_SUBTITLESANDSOUNDEFFECTS), GAMEPLAY_X + 8, GAMEPLAY_Y + 48, 1);
audioOptions->subtitlesEnabled.checked = (gSaveData.controls.flags & ControlSaveSubtitlesEnabled) != 0;
@ -139,8 +206,6 @@ void audioOptionsInit(struct AudioOptions* audioOptions) {
}
void audioOptionsRebuildtext(struct AudioOptions* audioOptions) {
prerenderedTextFree(audioOptions->gameVolumeText);
prerenderedTextFree(audioOptions->musicVolumeText);
prerenderedTextFree(audioOptions->subtitlesEnabled.prerenderedText);
prerenderedTextFree(audioOptions->allSubtitlesEnabled.prerenderedText);
prerenderedTextFree(audioOptions->subtitlesLanguageText);
@ -149,6 +214,8 @@ void audioOptionsRebuildtext(struct AudioOptions* audioOptions) {
prerenderedTextFree(audioOptions->audioLanguageDynamicText);
audioOptionsRenderText(audioOptions);
menuBuilderRebuildText(&audioOptions->menuBuilder);
}
enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions) {
@ -177,14 +244,6 @@ enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions) {
}
switch (audioOptions->selectedItem) {
case AudioOptionGameVolume:
audioOptionsHandleSlider(audioOptions->selectedItem, &gSaveData.audio.soundVolume, &audioOptions->gameVolume.value);
soundPlayerGameVolumeUpdate();
break;
case AudioOptionMusicVolume:
audioOptionsHandleSlider(audioOptions->selectedItem, &gSaveData.audio.musicVolume, &audioOptions->musicVolume.value);
soundPlayerGameVolumeUpdate();
break;
case AudioOptionSubtitlesEnabled:
if (controllerGetButtonDown(0, A_BUTTON)) {
audioOptions->subtitlesEnabled.checked = !audioOptions->subtitlesEnabled.checked;
@ -258,12 +317,6 @@ enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions) {
void audioOptionsRender(struct AudioOptions* audioOptions, struct RenderState* renderState, struct GraphicsTask* task) {
gSPDisplayList(renderState->dl++, ui_material_list[SOLID_ENV_INDEX]);
gSPDisplayList(renderState->dl++, audioOptions->gameVolume.back);
renderState->dl = menuSliderRender(&audioOptions->gameVolume, renderState->dl);
gSPDisplayList(renderState->dl++, audioOptions->musicVolume.back);
renderState->dl = menuSliderRender(&audioOptions->musicVolume, renderState->dl);
gSPDisplayList(renderState->dl++, audioOptions->subtitlesEnabled.outline);
renderState->dl = menuCheckboxRender(&audioOptions->subtitlesEnabled, renderState->dl);
@ -280,8 +333,6 @@ void audioOptionsRender(struct AudioOptions* audioOptions, struct RenderState* r
struct PrerenderedTextBatch* batch = prerenderedBatchStart();
prerenderedBatchAdd(batch, audioOptions->gameVolumeText, audioOptions->selectedItem == AudioOptionGameVolume ? &gSelectionGray : &gColorWhite);
prerenderedBatchAdd(batch, audioOptions->musicVolumeText, audioOptions->selectedItem == AudioOptionMusicVolume ? &gSelectionGray : &gColorWhite);
prerenderedBatchAdd(batch, audioOptions->subtitlesEnabled.prerenderedText, audioOptions->selectedItem == AudioOptionSubtitlesEnabled ? &gSelectionGray : &gColorWhite);
prerenderedBatchAdd(batch, audioOptions->allSubtitlesEnabled.prerenderedText, audioOptions->selectedItem == AudioOptionAllSubtitlesEnabled ? &gSelectionGray : &gColorWhite);
prerenderedBatchAdd(batch, audioOptions->subtitlesLanguageText, audioOptions->selectedItem == AudioOptionSubtitlesLanguage ? &gSelectionGray : &gColorWhite);
@ -292,4 +343,6 @@ void audioOptionsRender(struct AudioOptions* audioOptions, struct RenderState* r
renderState->dl = prerenderedBatchFinish(batch, gDejaVuSansImages, renderState->dl);
gSPDisplayList(renderState->dl++, ui_material_revert_list[DEJAVU_SANS_0_INDEX]);
menuBuilderRender(&audioOptions->menuBuilder, renderState);
}

View file

@ -3,6 +3,7 @@
#include "./menu.h"
#include "../graphics/graphics.h"
#include "menu_builder.h"
enum AudioOption {
AudioOptionGameVolume,
@ -15,13 +16,11 @@ enum AudioOption {
};
struct AudioOptions {
struct MenuSlider gameVolume;
struct MenuSlider musicVolume;
struct MenuBuilder menuBuilder;
struct MenuCheckbox subtitlesEnabled;
struct MenuCheckbox allSubtitlesEnabled;
struct MenuSlider subtitlesLanguage;
struct PrerenderedText* gameVolumeText;
struct PrerenderedText* musicVolumeText;
struct PrerenderedText* subtitlesLanguageText;
struct PrerenderedText* subtitlesLanguageDynamicText;
unsigned short subtitles_language_temp;

263
src/menu/menu_builder.c Normal file
View file

@ -0,0 +1,263 @@
#include "menu_builder.h"
#include "../util/memory.h"
#include "../controls/controller.h"
#include "./translations.h"
#include "../audio/soundplayer.h"
#include "../util/time.h"
#include "../math/mathf.h"
#include "../font/dejavusans.h"
#include "../build/assets/materials/ui.h"
#include "../build/src/audio/clips.h"
void textMenuItemInit(struct MenuBuilderElement* element) {
element->data = menuBuildPrerenderedText(
element->params->params.text.font,
translationsGet(element->params->params.text.messageId),
element->params->x,
element->params->y
);
}
void textMenuItemRebuildText(struct MenuBuilderElement* element) {
prerenderedTextFree(element->data);
textMenuItemInit(element);
}
void textMenuItemRender(struct MenuBuilderElement* element, int selection, int materialIndex, struct PrerenderedTextBatch* textBatch, struct RenderState* renderState) {
if (textBatch) {
prerenderedBatchAdd(textBatch, element->data, selection == element->selectionIndex ? &gSelectionGray : &gColorWhite);
}
}
void checkboxMenuItemInit(struct MenuBuilderElement* element) {
struct MenuCheckbox* checkbox = malloc(sizeof(struct MenuCheckbox));
*checkbox = menuBuildCheckbox(
element->params->params.checkbox.font,
translationsGet(element->params->params.checkbox.messageId),
element->params->x,
element->params->y,
1
);
element->data = checkbox;
}
enum MenuDirection checkboxMenuItemUpdate(struct MenuBuilderElement* element, MenuActionCalback actionCallback, void* data) {
if (controllerGetButtonDown(0, A_BUTTON)) {
struct MenuCheckbox* checkbox = (struct MenuCheckbox*)element->data;
checkbox->checked = !checkbox->checked;
struct MenuAction action;
action.type = MenuElementTypeCheckbox;
action.state.checkbox.isChecked = checkbox->checked;
actionCallback(data, element->selectionIndex, &action);
}
return MenuDirectionStay;
}
void checkboxMenuItemRebuildText(struct MenuBuilderElement* element) {
struct MenuCheckbox* checkbox = (struct MenuCheckbox*)element->data;
prerenderedTextFree(checkbox->prerenderedText);
checkbox->prerenderedText = menuBuildPrerenderedText(
element->params->params.checkbox.font,
translationsGet(element->params->params.checkbox.messageId),
element->params->x + CHECKBOX_SIZE + 6,
element->params->y
);
}
void checkboxMenuItemRender(struct MenuBuilderElement* element, int selection, int materialIndex, struct PrerenderedTextBatch* textBatch, struct RenderState* renderState) {
struct MenuCheckbox* checkbox = (struct MenuCheckbox*)element->data;
if (materialIndex == SOLID_ENV_INDEX) {
gSPDisplayList(renderState->dl++, checkbox->outline);
renderState->dl = menuCheckboxRender(checkbox, renderState->dl);
} else if (textBatch) {
prerenderedBatchAdd(textBatch, checkbox->prerenderedText, selection == element->selectionIndex ? &gSelectionGray : &gColorWhite);
}
}
void sliderMenuItemInit(struct MenuBuilderElement* element) {
struct MenuSlider* slider = malloc(sizeof(struct MenuSlider));
short steps = element->params->params.slider.numberOfTicks;
if (steps == 0) {
steps = 9;
}
*slider = menuBuildSlider(
element->params->x,
element->params->y,
element->params->params.slider.width,
steps
);
element->data = slider;
}
#define FULL_SCROLL_TIME 2.0f
#define SCROLL_MULTIPLIER (1.0f * FIXED_DELTA_TIME / (80 * FULL_SCROLL_TIME))
enum MenuDirection sliderMenuItemUpdate(struct MenuBuilderElement* element, MenuActionCalback actionCallback, void* data) {
struct MenuSlider* slider = (struct MenuSlider*)element->data;
if (element->params->params.slider.discrete) {
int controllerDir = controllerGetDirectionDown(0);
int numTicks = element->params->params.slider.numberOfTicks;
int currentValue = (int)floorf(slider->value * numTicks);
int newValue = currentValue;
if (controllerDir & ControllerDirectionRight) {
++numTicks;
} else if (controllerDir & ControllerDirectionLeft) {
--numTicks;
}
if (newValue < 0) {
newValue = 0;
} else if (newValue >= numTicks) {
newValue = numTicks - 1;
}
if (newValue != currentValue) {
struct MenuAction action;
action.type = MenuElementTypeSlider;
action.state.iSlider.value = newValue;
actionCallback(data, element->selectionIndex, &action);
slider->value = newValue;
}
slider->value = (float)newValue / (float)numTicks;
} else {
OSContPad* pad = controllersGetControllerData(0);
float newValue = slider->value + pad->stick_x * SCROLL_MULTIPLIER;
if (newValue > 1.0f) {
newValue = 1.0f;
} else if (newValue < 0.0f) {
newValue = 0.0f;
}
if (newValue != slider->value) {
struct MenuAction action;
action.type = MenuElementTypeSlider;
action.state.fSlider.value = newValue;
actionCallback(data, element->selectionIndex, &action);
slider->value = newValue;
}
}
return MenuDirectionStay;
}
void sliderMenuItemRender(struct MenuBuilderElement* element, int selection, int materialIndex, struct PrerenderedTextBatch* textBatch, struct RenderState* renderState) {
struct MenuSlider* slider = (struct MenuSlider*)element->data;
gSPDisplayList(renderState->dl++, slider->back);
renderState->dl = menuSliderRender(slider, renderState->dl);
}
struct MenuBuilderCallbacks gMenuTypeCallbacks[] = {
{textMenuItemInit, NULL, textMenuItemRebuildText, textMenuItemRender},
{checkboxMenuItemInit, checkboxMenuItemUpdate, checkboxMenuItemRebuildText, checkboxMenuItemRender},
{sliderMenuItemInit, sliderMenuItemUpdate, NULL, sliderMenuItemRender},
};
void menuBuilderInit(
struct MenuBuilder* menuBuilder,
struct MenuElementParams* params,
int elementCount,
int maxSelection,
MenuActionCalback actionCallback,
void* data
) {
menuBuilder->elements = malloc(sizeof(struct MenuBuilderElement) * elementCount);
menuBuilder->elementCount = elementCount;
menuBuilder->selection = 0;
menuBuilder->maxSelection = maxSelection;
menuBuilder->actionCallback = actionCallback;
menuBuilder->data = data;
for (int i = 0; i < elementCount; ++i) {
menuBuilder->elements[i].data = NULL;
menuBuilder->elements[i].selectionIndex = params[i].selectionIndex;
menuBuilder->elements[i].callbacks = &gMenuTypeCallbacks[params[i].type];
menuBuilder->elements[i].params = &params[i];
menuBuilder->elements[i].callbacks->init(&menuBuilder->elements[i]);
}
}
enum MenuDirection menuBuilderUpdate(struct MenuBuilder* menuBuilder) {
if (controllerGetButtonDown(0, B_BUTTON)) {
return MenuDirectionUp;
}
int controllerDir = controllerGetDirectionDown(0);
if (controllerDir & ControllerDirectionDown) {
++menuBuilder->selection;
if (menuBuilder->selection == menuBuilder->maxSelection) {
menuBuilder->selection = 0;
}
soundPlayerPlay(SOUNDS_BUTTONROLLOVER, 1.0f, 0.5f, NULL, NULL, SoundTypeAll);
}
if (controllerDir & ControllerDirectionUp) {
if (menuBuilder->selection == 0) {
menuBuilder->selection = menuBuilder->maxSelection - 1;
} else {
--menuBuilder->selection;
}
soundPlayerPlay(SOUNDS_BUTTONROLLOVER, 1.0f, 0.5f, NULL, NULL, SoundTypeAll);
}
for (int i = 0; i < menuBuilder->elementCount; ++i) {
struct MenuBuilderElement* element = &menuBuilder->elements[i];
if (element->callbacks->update && element->selectionIndex == menuBuilder->selection) {
enum MenuDirection direction = element->callbacks->update(element, menuBuilder->actionCallback, menuBuilder->data);
if (direction != MenuDirectionStay) {
return direction;
}
}
}
if (controllerGetButtonDown(0, L_TRIG) || controllerGetButtonDown(0, Z_TRIG)) {
return MenuDirectionLeft;
}
if (controllerGetButtonDown(0, R_TRIG)) {
return MenuDirectionRight;
}
return MenuDirectionStay;
}
void menuBuilderRebuildText(struct MenuBuilder* menuBuilder) {
for (int i = 0; i < menuBuilder->elementCount; ++i) {
if (menuBuilder->elements[i].callbacks->rebuildText) {
menuBuilder->elements[i].callbacks->rebuildText(&menuBuilder->elements[i]);
}
}
}
void menuBuilderRender(struct MenuBuilder* menuBuilder, struct RenderState* renderState) {
gSPDisplayList(renderState->dl++, ui_material_list[SOLID_ENV_INDEX]);
for (int i = 0; i < menuBuilder->elementCount; ++i) {
menuBuilder->elements[i].callbacks->render(&menuBuilder->elements[i], menuBuilder->selection, SOLID_ENV_INDEX, NULL, renderState);
}
gSPDisplayList(renderState->dl++, ui_material_revert_list[SOLID_ENV_INDEX]);
struct PrerenderedTextBatch* batch = prerenderedBatchStart();
for (int i = 0; i < menuBuilder->elementCount; ++i) {
menuBuilder->elements[i].callbacks->render(&menuBuilder->elements[i], menuBuilder->selection, -1, batch, renderState);
}
renderState->dl = prerenderedBatchFinish(batch, gDejaVuSansImages, renderState->dl);
gSPDisplayList(renderState->dl++, ui_material_revert_list[DEJAVU_SANS_0_INDEX]);
}

102
src/menu/menu_builder.h Normal file
View file

@ -0,0 +1,102 @@
#ifndef __MENU_MENU_BUILDER_H__
#define __MENU_MENU_BUILDER_H__
#include "../font/font.h"
#include "./menu.h"
#include "../graphics/renderstate.h"
#include <ctype.h>
struct MenuBuilderElement;
struct MenuElementParams;
enum MenuElementType {
MenuElementTypeText,
MenuElementTypeCheckbox,
MenuElementTypeSlider,
};
struct MenuAction {
enum MenuElementType type;
union
{
struct {
unsigned char isChecked;
} checkbox;
struct {
float value;
} fSlider;
struct {
short value;
} iSlider;
} state;
};
typedef void (*MenuActionCalback)(void* data, int selection, struct MenuAction* action);
typedef void (*MenuItemInit)(struct MenuBuilderElement* element);
typedef enum MenuDirection (*MenuItemUpdate)(struct MenuBuilderElement* element, MenuActionCalback actionCallback, void* data);
typedef void (*MenuItemRebuildText)(struct MenuBuilderElement* element);
typedef void (*MenuItemRender)(struct MenuBuilderElement* element, int selection, int materialIndex, struct PrerenderedTextBatch* textBatch, struct RenderState* renderState);
struct MenuBuilderCallbacks {
MenuItemInit init;
MenuItemUpdate update;
MenuItemRebuildText rebuildText;
MenuItemRender render;
};
struct MenuElementParams {
enum MenuElementType type;
short x;
short y;
union
{
struct {
struct Font* font;
short messageId;
} text;
struct {
struct Font* font;
short messageId;
} checkbox;
struct {
struct Font* font;
short messageId;
short width;
short numberOfTicks;
short discrete;
} slider;
} params;
short selectionIndex;
};
struct MenuBuilderElement {
void* data;
struct MenuBuilderCallbacks* callbacks;
struct MenuElementParams* params;
short selectionIndex;
};
struct MenuBuilder {
struct MenuBuilderElement* elements;
MenuActionCalback actionCallback;
void* data;
short elementCount;
short selection;
short maxSelection;
};
void menuBuilderInit(
struct MenuBuilder* menuBuilder,
struct MenuElementParams* params,
int elementCount,
int maxSelection,
MenuActionCalback actionCallback,
void* data
);
enum MenuDirection menuBuilderUpdate(struct MenuBuilder* menuBuilder);
void menuBuilderRebuildText(struct MenuBuilder* menuBuilder);
void menuBuilderRender(struct MenuBuilder* menuBuilder, struct RenderState* renderState);
#endif