From 48258f6a18e7f22a9f9df76f95e4203ed7420a9e Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Mon, 18 Sep 2023 03:39:47 -0600 Subject: [PATCH] Added volume sliders --- include/banks_table.h | 76 ++--- include/sequences_table.h | 70 ++--- src/audio/effects.c | 4 +- src/audio/external.c | 5 + src/audio/external.h | 1 + src/audio/internal.h | 3 +- src/audio/load.c | 5 + src/audio/mixer.c | 2 +- src/audio/synthesis.c | 4 + src/port/Engine.cpp | 108 ++++++- src/port/Engine.h | 7 + src/port/Game.cpp | 18 +- src/port/ImguiUI.cpp | 58 ---- src/port/audio/GameAudio.h | 12 + src/port/ui/ImguiUI.cpp | 102 ++++++ src/port/{ => ui}/ImguiUI.h | 0 src/port/ui/UIWidgets.cpp | 599 ++++++++++++++++++++++++++++++++++++ src/port/ui/UIWidgets.h | 90 ++++++ 18 files changed, 999 insertions(+), 165 deletions(-) delete mode 100644 src/port/ImguiUI.cpp create mode 100644 src/port/audio/GameAudio.h create mode 100644 src/port/ui/ImguiUI.cpp rename src/port/{ => ui}/ImguiUI.h (100%) create mode 100644 src/port/ui/UIWidgets.cpp create mode 100644 src/port/ui/UIWidgets.h diff --git a/include/banks_table.h b/include/banks_table.h index 1c05cb14..b1a3bad5 100644 --- a/include/banks_table.h +++ b/include/banks_table.h @@ -1,118 +1,118 @@ #pragma once #include "align_asset_macro.h" -#define dgBank00 "__OTR__sound/banks/us/00_bank" +#define dgBank00 "__OTR__sound/banks/00_bank" static const ALIGN_ASSET(2) char gBank00[] = dgBank00; -#define dgBank01 "__OTR__sound/banks/us/01_bank" +#define dgBank01 "__OTR__sound/banks/01_bank" static const ALIGN_ASSET(2) char gBank01[] = dgBank01; -#define dgBank02 "__OTR__sound/banks/us/02_bank" +#define dgBank02 "__OTR__sound/banks/02_bank" static const ALIGN_ASSET(2) char gBank02[] = dgBank02; -#define dgBank03 "__OTR__sound/banks/us/03_bank" +#define dgBank03 "__OTR__sound/banks/03_bank" static const ALIGN_ASSET(2) char gBank03[] = dgBank03; -#define dgBank04 "__OTR__sound/banks/us/04_bank" +#define dgBank04 "__OTR__sound/banks/04_bank" static const ALIGN_ASSET(2) char gBank04[] = dgBank04; -#define dgBank05 "__OTR__sound/banks/us/05_bank" +#define dgBank05 "__OTR__sound/banks/05_bank" static const ALIGN_ASSET(2) char gBank05[] = dgBank05; -#define dgBank06 "__OTR__sound/banks/us/06_bank" +#define dgBank06 "__OTR__sound/banks/06_bank" static const ALIGN_ASSET(2) char gBank06[] = dgBank06; -#define dgBank07 "__OTR__sound/banks/us/07_bank" +#define dgBank07 "__OTR__sound/banks/07_bank" static const ALIGN_ASSET(2) char gBank07[] = dgBank07; -#define dgBank08 "__OTR__sound/banks/us/08_bank" +#define dgBank08 "__OTR__sound/banks/08_bank" static const ALIGN_ASSET(2) char gBank08[] = dgBank08; -#define dgBank09 "__OTR__sound/banks/us/09_bank" +#define dgBank09 "__OTR__sound/banks/09_bank" static const ALIGN_ASSET(2) char gBank09[] = dgBank09; -#define dgBank0A "__OTR__sound/banks/us/0A_bank" +#define dgBank0A "__OTR__sound/banks/0A_bank" static const ALIGN_ASSET(2) char gBank0A[] = dgBank0A; -#define dgBank0B "__OTR__sound/banks/us/0B_bank" +#define dgBank0B "__OTR__sound/banks/0B_bank" static const ALIGN_ASSET(2) char gBank0B[] = dgBank0B; -#define dgBank0C "__OTR__sound/banks/us/0C_bank" +#define dgBank0C "__OTR__sound/banks/0C_bank" static const ALIGN_ASSET(2) char gBank0C[] = dgBank0C; -#define dgBank0D "__OTR__sound/banks/us/0D_bank" +#define dgBank0D "__OTR__sound/banks/0D_bank" static const ALIGN_ASSET(2) char gBank0D[] = dgBank0D; -#define dgBank0E "__OTR__sound/banks/us/0E_bank" +#define dgBank0E "__OTR__sound/banks/0E_bank" static const ALIGN_ASSET(2) char gBank0E[] = dgBank0E; -#define dgBank0F "__OTR__sound/banks/us/0F_bank" +#define dgBank0F "__OTR__sound/banks/0F_bank" static const ALIGN_ASSET(2) char gBank0F[] = dgBank0F; -#define dgBank10 "__OTR__sound/banks/us/10_bank" +#define dgBank10 "__OTR__sound/banks/10_bank" static const ALIGN_ASSET(2) char gBank10[] = dgBank10; -#define dgBank11 "__OTR__sound/banks/us/11_bank" +#define dgBank11 "__OTR__sound/banks/11_bank" static const ALIGN_ASSET(2) char gBank11[] = dgBank11; -#define dgBank12 "__OTR__sound/banks/us/12_bank" +#define dgBank12 "__OTR__sound/banks/12_bank" static const ALIGN_ASSET(2) char gBank12[] = dgBank12; -#define dgBank13 "__OTR__sound/banks/us/13_bank" +#define dgBank13 "__OTR__sound/banks/13_bank" static const ALIGN_ASSET(2) char gBank13[] = dgBank13; -#define dgBank14 "__OTR__sound/banks/us/14_bank" +#define dgBank14 "__OTR__sound/banks/14_bank" static const ALIGN_ASSET(2) char gBank14[] = dgBank14; -#define dgBank15 "__OTR__sound/banks/us/15_bank" +#define dgBank15 "__OTR__sound/banks/15_bank" static const ALIGN_ASSET(2) char gBank15[] = dgBank15; -#define dgBank16 "__OTR__sound/banks/us/16_bank" +#define dgBank16 "__OTR__sound/banks/16_bank" static const ALIGN_ASSET(2) char gBank16[] = dgBank16; -#define dgBank17 "__OTR__sound/banks/us/17_bank" +#define dgBank17 "__OTR__sound/banks/17_bank" static const ALIGN_ASSET(2) char gBank17[] = dgBank17; -#define dgBank18 "__OTR__sound/banks/us/18_bank" +#define dgBank18 "__OTR__sound/banks/18_bank" static const ALIGN_ASSET(2) char gBank18[] = dgBank18; -#define dgBank19 "__OTR__sound/banks/us/19_bank" +#define dgBank19 "__OTR__sound/banks/19_bank" static const ALIGN_ASSET(2) char gBank19[] = dgBank19; -#define dgBank1A "__OTR__sound/banks/us/1A_bank" +#define dgBank1A "__OTR__sound/banks/1A_bank" static const ALIGN_ASSET(2) char gBank1A[] = dgBank1A; -#define dgBank1B "__OTR__sound/banks/us/1B_bank" +#define dgBank1B "__OTR__sound/banks/1B_bank" static const ALIGN_ASSET(2) char gBank1B[] = dgBank1B; -#define dgBank1C "__OTR__sound/banks/us/1C_bank" +#define dgBank1C "__OTR__sound/banks/1C_bank" static const ALIGN_ASSET(2) char gBank1C[] = dgBank1C; -#define dgBank1D "__OTR__sound/banks/us/1D_bank" +#define dgBank1D "__OTR__sound/banks/1D_bank" static const ALIGN_ASSET(2) char gBank1D[] = dgBank1D; -#define dgBank1E "__OTR__sound/banks/us/1E_bank" +#define dgBank1E "__OTR__sound/banks/1E_bank" static const ALIGN_ASSET(2) char gBank1E[] = dgBank1E; -#define dgBank1F "__OTR__sound/banks/us/1F_bank" +#define dgBank1F "__OTR__sound/banks/1F_bank" static const ALIGN_ASSET(2) char gBank1F[] = dgBank1F; -#define dgBank20 "__OTR__sound/banks/us/20_bank" +#define dgBank20 "__OTR__sound/banks/20_bank" static const ALIGN_ASSET(2) char gBank20[] = dgBank20; -#define dgBank21 "__OTR__sound/banks/us/21_bank" +#define dgBank21 "__OTR__sound/banks/21_bank" static const ALIGN_ASSET(2) char gBank21[] = dgBank21; -#define dgBank22 "__OTR__sound/banks/us/22_bank" +#define dgBank22 "__OTR__sound/banks/22_bank" static const ALIGN_ASSET(2) char gBank22[] = dgBank22; -#define dgBank23 "__OTR__sound/banks/us/23_bank" +#define dgBank23 "__OTR__sound/banks/23_bank" static const ALIGN_ASSET(2) char gBank23[] = dgBank23; -#define dgBank24 "__OTR__sound/banks/us/24_bank" +#define dgBank24 "__OTR__sound/banks/24_bank" static const ALIGN_ASSET(2) char gBank24[] = dgBank24; -#define dgBank25 "__OTR__sound/banks/us/25_bank" +#define dgBank25 "__OTR__sound/banks/25_bank" static const ALIGN_ASSET(2) char gBank25[] = dgBank25; static const char* gBankTable[] = { diff --git a/include/sequences_table.h b/include/sequences_table.h index fa36557a..5572b8f1 100644 --- a/include/sequences_table.h +++ b/include/sequences_table.h @@ -1,109 +1,109 @@ #pragma once #include "align_asset_macro.h" -#define dgSequence00 "__OTR__sound/sequences/us/00_sound_player" +#define dgSequence00 "__OTR__sound/sequences/00_sound_player" static const ALIGN_ASSET(2) char gSequence00[] = dgSequence00; -#define dgSequence01 "__OTR__sound/sequences/us/01_cutscene_collect_star" +#define dgSequence01 "__OTR__sound/sequences/01_cutscene_collect_star" static const ALIGN_ASSET(2) char gSequence01[] = dgSequence01; -#define dgSequence02 "__OTR__sound/sequences/us/02_menu_title_screen" +#define dgSequence02 "__OTR__sound/sequences/02_menu_title_screen" static const ALIGN_ASSET(2) char gSequence02[] = dgSequence02; -#define dgSequence03 "__OTR__sound/sequences/us/03_level_grass" +#define dgSequence03 "__OTR__sound/sequences/03_level_grass" static const ALIGN_ASSET(2) char gSequence03[] = dgSequence03; -#define dgSequence04 "__OTR__sound/sequences/us/04_level_inside_castle" +#define dgSequence04 "__OTR__sound/sequences/04_level_inside_castle" static const ALIGN_ASSET(2) char gSequence04[] = dgSequence04; -#define dgSequence05 "__OTR__sound/sequences/us/05_level_water" +#define dgSequence05 "__OTR__sound/sequences/05_level_water" static const ALIGN_ASSET(2) char gSequence05[] = dgSequence05; -#define dgSequence06 "__OTR__sound/sequences/us/06_level_hot" +#define dgSequence06 "__OTR__sound/sequences/06_level_hot" static const ALIGN_ASSET(2) char gSequence06[] = dgSequence06; -#define dgSequence07 "__OTR__sound/sequences/us/07_level_boss_koopa" +#define dgSequence07 "__OTR__sound/sequences/07_level_boss_koopa" static const ALIGN_ASSET(2) char gSequence07[] = dgSequence07; -#define dgSequence08 "__OTR__sound/sequences/us/08_level_snow" +#define dgSequence08 "__OTR__sound/sequences/08_level_snow" static const ALIGN_ASSET(2) char gSequence08[] = dgSequence08; -#define dgSequence09 "__OTR__sound/sequences/us/09_level_slide" +#define dgSequence09 "__OTR__sound/sequences/09_level_slide" static const ALIGN_ASSET(2) char gSequence09[] = dgSequence09; -#define dgSequence0A "__OTR__sound/sequences/us/0A_level_spooky" +#define dgSequence0A "__OTR__sound/sequences/0A_level_spooky" static const ALIGN_ASSET(2) char gSequence0A[] = dgSequence0A; -#define dgSequence0B "__OTR__sound/sequences/us/0B_event_piranha_plant" +#define dgSequence0B "__OTR__sound/sequences/0B_event_piranha_plant" static const ALIGN_ASSET(2) char gSequence0B[] = dgSequence0B; -#define dgSequence0C "__OTR__sound/sequences/us/0C_level_underground" +#define dgSequence0C "__OTR__sound/sequences/0C_level_underground" static const ALIGN_ASSET(2) char gSequence0C[] = dgSequence0C; -#define dgSequence0D "__OTR__sound/sequences/us/0D_menu_star_select" +#define dgSequence0D "__OTR__sound/sequences/0D_menu_star_select" static const ALIGN_ASSET(2) char gSequence0D[] = dgSequence0D; -#define dgSequence0E "__OTR__sound/sequences/us/0E_event_powerup" +#define dgSequence0E "__OTR__sound/sequences/0E_event_powerup" static const ALIGN_ASSET(2) char gSequence0E[] = dgSequence0E; -#define dgSequence0F "__OTR__sound/sequences/us/0F_event_metal_cap" +#define dgSequence0F "__OTR__sound/sequences/0F_event_metal_cap" static const ALIGN_ASSET(2) char gSequence0F[] = dgSequence0F; -#define dgSequence10 "__OTR__sound/sequences/us/10_event_koopa_message" +#define dgSequence10 "__OTR__sound/sequences/10_event_koopa_message" static const ALIGN_ASSET(2) char gSequence10[] = dgSequence10; -#define dgSequence11 "__OTR__sound/sequences/us/11_level_koopa_road" +#define dgSequence11 "__OTR__sound/sequences/11_level_koopa_road" static const ALIGN_ASSET(2) char gSequence11[] = dgSequence11; -#define dgSequence12 "__OTR__sound/sequences/us/12_event_high_score" +#define dgSequence12 "__OTR__sound/sequences/12_event_high_score" static const ALIGN_ASSET(2) char gSequence12[] = dgSequence12; -#define dgSequence13 "__OTR__sound/sequences/us/13_event_merry_go_round" +#define dgSequence13 "__OTR__sound/sequences/13_event_merry_go_round" static const ALIGN_ASSET(2) char gSequence13[] = dgSequence13; -#define dgSequence14 "__OTR__sound/sequences/us/14_event_race" +#define dgSequence14 "__OTR__sound/sequences/14_event_race" static const ALIGN_ASSET(2) char gSequence14[] = dgSequence14; -#define dgSequence15 "__OTR__sound/sequences/us/15_cutscene_star_spawn" +#define dgSequence15 "__OTR__sound/sequences/15_cutscene_star_spawn" static const ALIGN_ASSET(2) char gSequence15[] = dgSequence15; -#define dgSequence16 "__OTR__sound/sequences/us/16_event_boss" +#define dgSequence16 "__OTR__sound/sequences/16_event_boss" static const ALIGN_ASSET(2) char gSequence16[] = dgSequence16; -#define dgSequence17 "__OTR__sound/sequences/us/17_cutscene_collect_key" +#define dgSequence17 "__OTR__sound/sequences/17_cutscene_collect_key" static const ALIGN_ASSET(2) char gSequence17[] = dgSequence17; -#define dgSequence18 "__OTR__sound/sequences/us/18_event_endless_stairs" +#define dgSequence18 "__OTR__sound/sequences/18_event_endless_stairs" static const ALIGN_ASSET(2) char gSequence18[] = dgSequence18; -#define dgSequence19 "__OTR__sound/sequences/us/19_level_boss_koopa_final" +#define dgSequence19 "__OTR__sound/sequences/19_level_boss_koopa_final" static const ALIGN_ASSET(2) char gSequence19[] = dgSequence19; -#define dgSequence1A "__OTR__sound/sequences/us/1A_cutscene_credits" +#define dgSequence1A "__OTR__sound/sequences/1A_cutscene_credits" static const ALIGN_ASSET(2) char gSequence1A[] = dgSequence1A; -#define dgSequence1B "__OTR__sound/sequences/us/1B_event_solve_puzzle" +#define dgSequence1B "__OTR__sound/sequences/1B_event_solve_puzzle" static const ALIGN_ASSET(2) char gSequence1B[] = dgSequence1B; -#define dgSequence1C "__OTR__sound/sequences/us/1C_event_toad_message" +#define dgSequence1C "__OTR__sound/sequences/1C_event_toad_message" static const ALIGN_ASSET(2) char gSequence1C[] = dgSequence1C; -#define dgSequence1D "__OTR__sound/sequences/us/1D_event_peach_message" +#define dgSequence1D "__OTR__sound/sequences/1D_event_peach_message" static const ALIGN_ASSET(2) char gSequence1D[] = dgSequence1D; -#define dgSequence1E "__OTR__sound/sequences/us/1E_cutscene_intro" +#define dgSequence1E "__OTR__sound/sequences/1E_cutscene_intro" static const ALIGN_ASSET(2) char gSequence1E[] = dgSequence1E; -#define dgSequence1F "__OTR__sound/sequences/us/1F_cutscene_victory" +#define dgSequence1F "__OTR__sound/sequences/1F_cutscene_victory" static const ALIGN_ASSET(2) char gSequence1F[] = dgSequence1F; -#define dgSequence20 "__OTR__sound/sequences/us/20_cutscene_ending" +#define dgSequence20 "__OTR__sound/sequences/20_cutscene_ending" static const ALIGN_ASSET(2) char gSequence20[] = dgSequence20; -#define dgSequence21 "__OTR__sound/sequences/us/21_menu_file_select" +#define dgSequence21 "__OTR__sound/sequences/21_menu_file_select" static const ALIGN_ASSET(2) char gSequence21[] = dgSequence21; -#define dgSequence22 "__OTR__sound/sequences/us/22_cutscene_lakitu" +#define dgSequence22 "__OTR__sound/sequences/22_cutscene_lakitu" static const ALIGN_ASSET(2) char gSequence22[] = dgSequence22; static const char* gSequenceTable[] = { diff --git a/src/audio/effects.c b/src/audio/effects.c index c2cc79f2..3f98feba 100644 --- a/src/audio/effects.c +++ b/src/audio/effects.c @@ -63,7 +63,7 @@ static void sequence_channel_process_sound(struct SequenceChannel *seqChannel) { f32 panFromChannel; s32 i; - channelVolume = seqChannel->volume * seqChannel->volumeScale * seqChannel->seqPlayer->fadeVolume; + channelVolume = (seqChannel->volume * (seqChannel->volumeScale * seqChannel->seqPlayer->fadeVolume)) * seqChannel->seqPlayer->gameVolume; if (seqChannel->seqPlayer->muted && (seqChannel->muteBehavior & MUTE_BEHAVIOR_SOFTEN) != 0) { channelVolume *= seqChannel->seqPlayer->muteVolumeScale; } @@ -73,7 +73,7 @@ static void sequence_channel_process_sound(struct SequenceChannel *seqChannel) { for (i = 0; i < 4; i++) { struct SequenceChannelLayer *layer = seqChannel->layers[i]; - if (layer != NULL && layer->enabled && layer->note != NULL) { + if (layer != NULL && layer->enabled && layer->note != NULL || (layer != NULL && seqChannel->seqPlayer->recalculateVolume)) { layer->noteFreqScale = layer->freqScale * seqChannel->freqScale; layer->noteVelocity = layer->velocitySquare * channelVolume; layer->notePan = (layer->pan * panLayerWeight) + panFromChannel; diff --git a/src/audio/external.c b/src/audio/external.c index 63532ee7..7076816f 100644 --- a/src/audio/external.c +++ b/src/audio/external.c @@ -2575,6 +2575,11 @@ void audio_set_sound_mode(u8 soundMode) { gSoundMode = soundMode; } +void audio_set_player_volume(u8 player, f32 volume) { + gSequencePlayers[player].gameVolume = volume; + gSequencePlayers[player].recalculateVolume = TRUE; +} + #if defined(VERSION_JP) || defined(VERSION_US) void unused_80321460(UNUSED s32 arg0, UNUSED s32 arg1, UNUSED s32 arg2, UNUSED s32 arg3) { } diff --git a/src/audio/external.h b/src/audio/external.h index 5e514450..f668b95c 100644 --- a/src/audio/external.h +++ b/src/audio/external.h @@ -60,6 +60,7 @@ void play_race_fanfare(void); void play_toads_jingle(void); void sound_reset(u8 presetId); void audio_set_sound_mode(u8 arg0); +void audio_set_player_volume(u8 player, f32 volume); void create_next_audio_buffer(s16 *samples, u32 num_samples); void audio_init(void); // in load.c diff --git a/src/audio/internal.h b/src/audio/internal.h index 97ea4cec..fc742066 100644 --- a/src/audio/internal.h +++ b/src/audio/internal.h @@ -259,9 +259,7 @@ struct SequencePlayer { /*0x000, 0x000*/ u8 muted : 1; /*0x000, 0x000*/ u8 seqDmaInProgress : 1; /*0x000, 0x000*/ u8 bankDmaInProgress : 1; -#if defined(VERSION_EU) || defined(VERSION_SH) /* 0x000*/ u8 recalculateVolume : 1; -#endif #ifdef VERSION_SH /* 0x000*/ u8 unkSh: 1; #endif @@ -324,6 +322,7 @@ struct SequencePlayer { #endif /*0x138, 0x140*/ uintptr_t bankDmaCurrDevAddr; /*0x13C, 0x144*/ ssize_t bankDmaRemaining; + f32 gameVolume; }; // size = 0x140, 0x148 on EU, 0x14C on SH struct AdsrSettings { diff --git a/src/audio/load.c b/src/audio/load.c index 93e3bd6a..05ac803d 100644 --- a/src/audio/load.c +++ b/src/audio/load.c @@ -551,6 +551,11 @@ void audio_init() { gSequenceCount = sizeof(gSequenceTable) / sizeof(gSequenceTable[0]); init_sequence_players(); gAudioLoadLock = AUDIO_LOCK_NOT_LOADING; + + audio_set_player_volume(SEQ_PLAYER_LEVEL, CVarGetFloat("gMainMusicVolume", 1.0f)); + audio_set_player_volume(SEQ_PLAYER_ENV, CVarGetFloat("gEnvironmentVolume", 1.0f)); + audio_set_player_volume(SEQ_PLAYER_SFX, CVarGetFloat("gSFXMusicVolume", 1.0f)); + // Should probably contain the sizes of the data banks, but those aren't // easily accessible from here. eu_stubbed_printf_0("---------- Init Completed. ------------\n"); diff --git a/src/audio/mixer.c b/src/audio/mixer.c index 1e9d62c9..1071811a 100644 --- a/src/audio/mixer.c +++ b/src/audio/mixer.c @@ -444,7 +444,7 @@ void aADPCMdecImpl(uint8_t flags, ADPCM_STATE state) { } void aResampleImpl(uint8_t flags, uint16_t pitch, RESAMPLE_STATE state) { - int16_t tmp[16]; + int16_t tmp[32]; int16_t *in_initial = BUF_S16(rspa.in); int16_t *in = in_initial; int16_t *out = BUF_S16(rspa.out); diff --git a/src/audio/synthesis.c b/src/audio/synthesis.c index d35a9e5e..435a0ac5 100644 --- a/src/audio/synthesis.c +++ b/src/audio/synthesis.c @@ -1490,6 +1490,10 @@ void note_set_vel_pan_reverb(struct Note *note, f32 velocity, f32 pan, u8 reverb if (note->targetVolRight == 0) { note->targetVolRight++; } + float master_vol = CVarGetFloat("gGameMasterVolume", 1.0f); + note->targetVolLeft *= master_vol; + note->targetVolRight *= master_vol; + if (note->reverbVol != reverbVol) { note->reverbVol = reverbVol; note->reverbVolShifted = reverbVol << 8; diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 6c97c129..981b6100 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -1,5 +1,5 @@ #include "Engine.h" -#include "ImguiUI.h" +#include "ui/ImguiUI.h" #include "port/importer/AnimationFactory.h" #include "port/importer/AudioBankFactory.h" #include "port/importer/AudioSampleFactory.h" @@ -7,12 +7,15 @@ #include "audio/internal.h" #include "banks_table.h" #include "sequences_table.h" +#include "audio/GameAudio.h" #include "ZAPDUtils/Utils/StringHelper.h" - -#include #include #include +extern "C" { +#include "audio/external.h" +} + GameEngine* GameEngine::Instance; GameEngine::GameEngine(){ @@ -42,6 +45,7 @@ GameEngine::GameEngine(){ this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Bank, "Bank", std::make_shared()); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Sample, "Sample", std::make_shared()); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Sequence, "Sequence", std::make_shared()); + GameEngine::AudioInit(); } void GameEngine::Create(){ @@ -49,6 +53,11 @@ void GameEngine::Create(){ GameUI::SetupGuiElements(); } +void GameEngine::Destroy(){ + GameEngine::AudioExit(); + this->context = nullptr; +} + void GameEngine::StartFrame() const{ this->context->GetWindow()->StartFrame(); } @@ -62,26 +71,97 @@ void GameEngine::ProcessFrame(void (*run_one_game_iter)()) const { this->context->GetWindow()->MainLoop(run_one_game_iter); } -extern "C" uint32_t GameEngine_GetSampleRate() { - auto audio = LUS::Context::GetInstance()->GetAudio()->GetAudioPlayer(); - if (audio == nullptr) { - return 0; - } +// Audio - if (!audio->IsInitialized()) { - return 0; - } +void GameEngine::HandleAudioThread(){ + while (audio.running) { + { + std::unique_lock Lock(audio.mutex); + while (!audio.processing && audio.running) { + audio.cv_to_thread.wait(Lock); + } - return audio->GetSampleRate(); + if (!audio.running) { + break; + } + } + std::unique_lock Lock(audio.mutex); + + int samples_left = AudioPlayerBuffered(); + u32 num_audio_samples = samples_left < AudioPlayerGetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW; + + s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3]; + for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) { + create_next_audio_buffer(audio_buffer + i * (num_audio_samples * 2), num_audio_samples); + } + + AudioPlayerPlayFrame((u8 *) audio_buffer, 2 * num_audio_samples * 4); + + audio.processing = false; + audio.cv_from_thread.notify_one(); + } } +void GameEngine::StartAudioFrame(){ + { + std::unique_lock Lock(audio.mutex); + audio.processing = true; + } + + audio.cv_to_thread.notify_one(); +} + +void GameEngine::EndAudioFrame(){ + { + std::unique_lock Lock(audio.mutex); + while (audio.processing) { + audio.cv_from_thread.wait(Lock); + } + } +} + +void GameEngine::AudioInit() { + LUS::Context::GetInstance()->GetResourceManager()->LoadDirectory("sound"); + + if (!audio.running) { + audio.running = true; + audio.thread = std::thread(GameEngine::HandleAudioThread); + } +} + +void GameEngine::AudioExit() { + { + std::unique_lock Lock(audio.mutex); + audio.running = false; + } + audio.cv_to_thread.notify_all(); + + // Wait until the audio thread quit + audio.thread.join(); +} + +extern "C" uint32_t GameEngine_GetSampleRate() { + auto player = LUS::Context::GetInstance()->GetAudio()->GetAudioPlayer(); + if (player == nullptr) { + return 0; + } + + if (!player->IsInitialized()) { + return 0; + } + + return player->GetSampleRate(); +} + +// End + extern "C" float GameEngine_GetAspectRatio() { return gfx_current_dimensions.aspect_ratio; } extern "C" CtlEntry* GameEngine_LoadBank(uint8_t bankId) { auto engine = GameEngine::Instance; - if(bankId > (sizeof(gBankTable) / sizeof(gBankTable[0]))){ + if(bankId > (sizeof(gBankTable) / sizeof(gBankTable[0])) - 1){ return nullptr; } if(engine->banks.contains(bankId)){ @@ -107,7 +187,7 @@ extern "C" void GameEngine_UnloadBank(uint8_t bankId) { extern "C" AudioSequenceData* GameEngine_LoadSequence(uint8_t seqId) { auto engine = GameEngine::Instance; - if(seqId > (sizeof(gSequenceTable) / sizeof(gSequenceTable[0]))){ + if(seqId > (sizeof(gSequenceTable) / sizeof(gSequenceTable[0])) - 1){ return nullptr; } if(engine->sequences.contains(seqId)){ diff --git a/src/port/Engine.h b/src/port/Engine.h index 689f558b..0cc50e05 100644 --- a/src/port/Engine.h +++ b/src/port/Engine.h @@ -26,6 +26,13 @@ class GameEngine { void StartFrame() const; static void RunCommands(Gfx* Commands); void ProcessFrame(void (*run_one_game_iter)()) const; + static void HandleAudioThread(); + static void StartAudioFrame(); + static void EndAudioFrame(); + static void AudioInit(); + static void AudioExit(); + + void Destroy(); }; #else uint32_t GameEngine_GetSampleRate(); diff --git a/src/port/Game.cpp b/src/port/Game.cpp index d732746c..92ed6460 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -8,8 +8,6 @@ extern "C" { #include "game/game_init.h" } -bool sAudioEnabled = true; - void alloc_pool() { static u64 pool[1024 * 1024 * 4]; main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0])); @@ -22,20 +20,10 @@ void exec_display_list(SPTask *spTask) { } void push_frame() { + GameEngine::StartAudioFrame(); GameEngine::Instance->StartFrame(); thread5_iteration(); - - if(sAudioEnabled) { - int samples_left = AudioPlayerBuffered(); - u32 num_audio_samples = samples_left < AudioPlayerGetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW; - - s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3]; - for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) { - create_next_audio_buffer(audio_buffer + i * (num_audio_samples * 2), num_audio_samples); - } - - AudioPlayerPlayFrame((u8 *) audio_buffer, 2 * num_audio_samples * 4); - } + GameEngine::EndAudioFrame(); } #ifdef _WIN32 @@ -47,8 +35,8 @@ int main(){ alloc_pool(); audio_init(); sound_init(); - thread5_game_loop(); GameEngine::Instance->ProcessFrame(push_frame); + GameEngine::Instance->Destroy(); return 0; } \ No newline at end of file diff --git a/src/port/ImguiUI.cpp b/src/port/ImguiUI.cpp deleted file mode 100644 index 66bb8a8e..00000000 --- a/src/port/ImguiUI.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "ImguiUI.h" - -#include -#include -#define IMGUI_DEFINE_MATH_OPERATORS -#include -#include -#include - -namespace GameUI { -std::shared_ptr mGameMenuBar; -std::shared_ptr mConsoleWindow; -std::shared_ptr mStatsWindow; -std::shared_ptr mInputEditorWindow; - -void SetupGuiElements() { - auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); - - mGameMenuBar = std::make_shared("gOpenMenuBar", CVarGetInteger("gOpenMenuBar", 0)); - gui->SetMenuBar(mGameMenuBar); - mStatsWindow = gui->GetGuiWindow("Stats"); - if (mStatsWindow == nullptr) { - SPDLOG_ERROR("Could not find stats window"); - } - - mConsoleWindow = gui->GetGuiWindow("Console"); - if (mConsoleWindow == nullptr) { - SPDLOG_ERROR("Could not find console window"); - } - - mInputEditorWindow = gui->GetGuiWindow("Input Editor"); - if (mInputEditorWindow == nullptr) { - SPDLOG_ERROR("Could not find input editor window"); - return; - } -} - -void Destroy() { - mConsoleWindow = nullptr; - mStatsWindow = nullptr; - mInputEditorWindow = nullptr; -} -} - -void GameMenuBar::DrawElement() { - if(ImGui::BeginMenuBar()){ - if(ImGui::BeginMenu("Settings")){ - if (GameUI::mInputEditorWindow) { - if (ImGui::Button("Controller Mapping", ImVec2 (-1.0f, 0.0f))) { - GameUI::mInputEditorWindow->ToggleVisibility(); - } - } - LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } -} \ No newline at end of file diff --git a/src/port/audio/GameAudio.h b/src/port/audio/GameAudio.h new file mode 100644 index 00000000..1135576f --- /dev/null +++ b/src/port/audio/GameAudio.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +static struct { + std::thread thread; + std::condition_variable cv_to_thread, cv_from_thread; + std::mutex mutex; + bool running; + bool processing; +} audio; diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp new file mode 100644 index 00000000..f6ff69db --- /dev/null +++ b/src/port/ui/ImguiUI.cpp @@ -0,0 +1,102 @@ +#include "ImguiUI.h" +#include "UIWidgets.h" + +#include +#include +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include +#include +extern "C" { +#include "audio/external.h" +} + +namespace GameUI { +std::shared_ptr mGameMenuBar; +std::shared_ptr mConsoleWindow; +std::shared_ptr mStatsWindow; +std::shared_ptr mInputEditorWindow; + +void SetupGuiElements() { + auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); + + mGameMenuBar = std::make_shared("gOpenMenuBar", CVarGetInteger("gOpenMenuBar", 0)); + gui->SetMenuBar(mGameMenuBar); + mStatsWindow = gui->GetGuiWindow("Stats"); + if (mStatsWindow == nullptr) { + SPDLOG_ERROR("Could not find stats window"); + } + + mConsoleWindow = gui->GetGuiWindow("Console"); + if (mConsoleWindow == nullptr) { + SPDLOG_ERROR("Could not find console window"); + } + + mInputEditorWindow = gui->GetGuiWindow("Input Editor"); + if (mInputEditorWindow == nullptr) { + SPDLOG_ERROR("Could not find input editor window"); + return; + } +} + +void Destroy() { + mConsoleWindow = nullptr; + mStatsWindow = nullptr; + mInputEditorWindow = nullptr; +} +} + +void GameMenuBar::DrawElement() { + if(ImGui::BeginMenuBar()){ + if(ImGui::BeginMenu("Settings")){ + if (ImGui::BeginMenu("Audio")) { + UIWidgets::PaddedEnhancementSliderFloat("Master Volume: %d %%", "##Master_Vol", "gGameMasterVolume", 0.0f, 1.0f, "", 1.0f, true, true, false, true); + if (UIWidgets::PaddedEnhancementSliderFloat("Main Music Volume: %d %%", "##Main_Music_Vol", "gMainMusicVolume", 0.0f, 1.0f, "", 1.0f, true, true, false, true)) { + audio_set_player_volume(SEQ_PLAYER_LEVEL, CVarGetFloat("gMainMusicVolume", 1.0f)); + } + if (UIWidgets::PaddedEnhancementSliderFloat("Sound Effects Volume: %d %%", "##Sound_Effect_Vol", "gSFXMusicVolume", 0.0f, 1.0f, "", 1.0f, true, true, false, true)) { + audio_set_player_volume(SEQ_PLAYER_SFX, CVarGetFloat("gSFXMusicVolume", 1.0f)); + } + if (UIWidgets::PaddedEnhancementSliderFloat("Environment Volume: %d %%", "##Environment_Vol", "gEnvironmentVolume", 0.0f, 1.0f, "", 1.0f, true, true, false, true)) { + audio_set_player_volume(SEQ_PLAYER_ENV, CVarGetFloat("gEnvironmentVolume", 1.0f)); + } + + static std::unordered_map audioBackendNames = { + { LUS::AudioBackend::WASAPI, "Windows Audio Session API" }, + { LUS::AudioBackend::PULSE, "PulseAudio" }, + { LUS::AudioBackend::SDL, "SDL" }, + }; + + ImGui::Text("Audio API (Needs reload)"); + auto currentAudioBackend = LUS::Context::GetInstance()->GetAudio()->GetAudioBackend(); + + if (LUS::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) { + UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + if (ImGui::BeginCombo("##AApi", audioBackendNames[currentAudioBackend])) { + for (uint8_t i = 0; i < LUS::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size(); i++) { + auto backend = LUS::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->data()[i]; + if (ImGui::Selectable(audioBackendNames[backend], backend == currentAudioBackend)) { + LUS::Context::GetInstance()->GetAudio()->SetAudioBackend(backend); + } + } + ImGui::EndCombo(); + } + if (LUS::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) { + UIWidgets::ReEnableComponent(""); + } + + ImGui::EndMenu(); + } + + if (GameUI::mInputEditorWindow) { + if (ImGui::Button("Controller Mapping", ImVec2 (-1.0f, 0.0f))) { + GameUI::mInputEditorWindow->ToggleVisibility(); + } + } + LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } +} \ No newline at end of file diff --git a/src/port/ImguiUI.h b/src/port/ui/ImguiUI.h similarity index 100% rename from src/port/ImguiUI.h rename to src/port/ui/ImguiUI.h diff --git a/src/port/ui/UIWidgets.cpp b/src/port/ui/UIWidgets.cpp new file mode 100644 index 00000000..a5a2f934 --- /dev/null +++ b/src/port/ui/UIWidgets.cpp @@ -0,0 +1,599 @@ +// +// UIWidgets.cpp +// soh +// +// Created by David Chavez on 25.08.22. +// + +#include "UIWidgets.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include + +#include + +namespace UIWidgets { + + // MARK: - Layout Helper + + // Automatically adds newlines to break up text longer than a specified number of characters + // Manually included newlines will still be respected and reset the line length + // If line is midword when it hits the limit, text should break at the last encountered space + char* WrappedText(const char* text, unsigned int charactersPerLine) { + std::string newText(text); + const size_t tipLength = newText.length(); + int lastSpace = -1; + int currentLineLength = 0; + for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) { + if (newText[currentCharacter] == '\n') { + currentLineLength = 0; + lastSpace = -1; + continue; + } else if (newText[currentCharacter] == ' ') { + lastSpace = currentCharacter; + } + + if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) { + newText[lastSpace] = '\n'; + currentLineLength = currentCharacter - lastSpace - 1; + lastSpace = -1; + } + currentLineLength++; + } + + return strdup(newText.c_str()); + } + + char* WrappedText(const std::string& text, unsigned int charactersPerLine) { + return WrappedText(text.c_str(), charactersPerLine); + } + + void SetLastItemHoverText(const std::string& text) { + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60)); + ImGui::EndTooltip(); + } + } + + void SetLastItemHoverText(const char* text) { + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60)); + ImGui::EndTooltip(); + } + } + + // Adds a "?" next to the previous ImGui item with a custom tooltip + void InsertHelpHoverText(const std::string& text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60)); + ImGui::EndTooltip(); + } + } + + void InsertHelpHoverText(const char* text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60)); + ImGui::EndTooltip(); + } + } + + + // MARK: - UI Elements + + void Tooltip(const char* text) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", WrappedText(text)); + } + } + + void Spacer(float height) { + ImGui::Dummy(ImVec2(0.0f, height)); + } + + void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { + if (padTop) { + Spacer(extraVerticalTopPadding); + } + ImGui::Separator(); + if (padBottom) { + Spacer(extraVerticalBottomPadding); + } + } + + void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) { + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness * 0.5f; + pos += ImVec2(thickness * 0.25f, thickness * 0.25f); + + draw_list->PathLineTo(ImVec2(pos.x, pos.y)); + draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y + sz)); + draw_list->PathStroke(col, 0, thickness); + + draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y)); + draw_list->PathLineTo(ImVec2(pos.x, pos.y + sz)); + draw_list->PathStroke(col, 0, thickness); + } + + bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) { + return false; + } + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) { + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) { + *v = !(*v); + ImGui::MarkItemEdited(id); + } + + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + ImGui::RenderNavHighlight(total_bb, id); + ImGui::RenderFrame(check_bb.Min, check_bb.Max, ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark); + ImU32 cross_col = ImGui::GetColorU32(ImVec4(0.50f, 0.50f, 0.50f, 1.00f)); + bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; + if (mixed_value) { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } else if ((!disabled && *v) || (disabled && disabledGraphic == CheckboxGraphics::Checkmark)) { + const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); + ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } else if (disabled && disabledGraphic == CheckboxGraphics::Cross) { + const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); + RenderCross(window->DrawList, check_bb.Min + ImVec2(pad, pad), cross_col, square_sz - pad * 2.0f); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) { + ImGui::LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); + } + if (label_size.x > 0.0f) { + ImGui::RenderText(label_pos, label); + } + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; + } + + void ReEnableComponent(const char* disabledTooltipText) { + // End of disable region of previous component + ImGui::PopStyleVar(1); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(disabledTooltipText, "") != 0) { + ImGui::SetTooltip("%s", disabledTooltipText); + } + ImGui::PopItemFlag(); + } + + void DisableComponent(const float alpha) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + } + + bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { + bool changed = false; + if (disabled) { + DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + + bool val = (bool)CVarGetInteger(cvarName, defaultValue); + if (CustomCheckbox(text, &val, disabled, disabledGraphic)) { + CVarSetInteger(cvarName, val); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + changed = true; + } + + if (disabled) { + ReEnableComponent(disabledTooltipText); + } + return changed; + } + + bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { + ImGui::BeginGroup(); + if (padTop) Spacer(0); + + bool changed = EnhancementCheckbox(text, cvarName, disabled, disabledTooltipText, disabledGraphic, defaultValue); + + if (padBottom) Spacer(0); + ImGui::EndGroup(); + return changed; + } + + bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) { + bool changed = false; + if (defaultIndex <= 0) { + defaultIndex = 0; + } + + if (disabled) { + DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + + uint8_t selected = CVarGetInteger(cvarName, defaultIndex); + std::string comboName = std::string("##") + std::string(cvarName); + if (ImGui::BeginCombo(comboName.c_str(), comboArray[selected])) { + for (uint8_t i = 0; i < comboArray.size(); i++) { + if (strlen(comboArray[i]) > 1) { + if (ImGui::Selectable(comboArray[i], i == selected)) { + CVarSetInteger(cvarName, i); + selected = i; + changed = true; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + } + } + ImGui::EndCombo(); + } + + if (disabled) { + ReEnableComponent(disabledTooltipText); + + if (disabledValue >= 0 && selected != disabledValue) { + CVarSetInteger(cvarName, disabledValue); + changed = true; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + } + + return changed; + } + + bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) { + ImGui::Text(label); + s32 currentValue = CVarGetInteger(cvarName, defaultIndex); + +#ifdef __WIIU__ + ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 40.0f)); + ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 60.0f); +#else + ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 20.0f)); + ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 30.0f); +#endif + + bool changed = EnhancementCombobox(cvarName, comboArray, defaultIndex, disabled, disabledTooltipText, disabledValue); + + ImGui::PopItemWidth(); + return changed; + } + + void PaddedText(const char* text, bool padTop, bool padBottom) { + if (padTop) Spacer(0); + + ImGui::Text("%s", text); + + if (padBottom) Spacer(0); + } + + bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) { + bool changed = false; + int val = CVarGetInteger(cvarName, defaultValue); + + if (disabled) { + DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + + ImGui::Text(text, val); + Spacer(0); + + ImGui::BeginGroup(); + if (PlusMinusButton) { + std::string MinusBTNName = " - ##" + std::string(cvarName); + if (ImGui::Button(MinusBTNName.c_str())) { + val--; + changed = true; + } + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + } + + ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth)); + if (ImGui::SliderInt(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) + { + changed = true; + } + ImGui::PopItemWidth(); + + if (PlusMinusButton) { + std::string PlusBTNName = " + ##" + std::string(cvarName); + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + if (ImGui::Button(PlusBTNName.c_str())) { + val++; + changed = true; + } + } + ImGui::EndGroup(); + + if (disabled) { + ReEnableComponent(disabledTooltipText); + } + + if (val < min) { + val = min; + changed = true; + } + + if (val > max) { + val = max; + changed = true; + } + + if (changed) { + CVarSetInteger(cvarName, val); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + + return changed; + } + + bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) { + bool changed = false; + float val = CVarGetFloat(cvarName, defaultValue); + + if (disabled) { + DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + + if (!isPercentage) { + ImGui::Text(text, val); + } else { + ImGui::Text(text, static_cast(100 * val)); + } + Spacer(0); + + ImGui::BeginGroup(); + if (PlusMinusButton) { + std::string MinusBTNName = " - ##" + std::string(cvarName); + if (ImGui::Button(MinusBTNName.c_str())) { + if (isPercentage) { + val -= 0.01f; + } else { + val -= 0.1f; + } + changed = true; + } + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + } + + ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth)); + if (ImGui::SliderFloat(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) { + if (isPercentage) { + val = roundf(val * 100) / 100; + } + changed = true; + } + ImGui::PopItemWidth(); + + if (PlusMinusButton) { + std::string PlusBTNName = " + ##" + std::string(cvarName); + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + if (ImGui::Button(PlusBTNName.c_str())) { + if (isPercentage) { + val += 0.01f; + } else { + val += 0.1f; + } + changed = true; + } + } + ImGui::EndGroup(); + + if (disabled) { + ReEnableComponent(disabledTooltipText); + } + + if (val < min) { + val = min; + changed = true; + } + + if (val > max) { + val = max; + changed = true; + } + + if (changed) { + CVarSetFloat(cvarName, val); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + + return changed; + } + + bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) { + bool changed = false; + ImGui::BeginGroup(); + if (padTop) Spacer(0); + + changed = EnhancementSliderInt(text, id, cvarName, min, max, format, defaultValue, PlusMinusButton, disabled, disabledTooltipText); + + if (padBottom) Spacer(0); + ImGui::EndGroup(); + return changed; + } + + bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) { + bool changed = false; + ImGui::BeginGroup(); + if (padTop) Spacer(0); + + changed = EnhancementSliderFloat(text, id, cvarName, min, max, format, defaultValue, isPercentage, PlusMinusButton, disabled, disabledTooltipText); + + if (padBottom) Spacer(0); + ImGui::EndGroup(); + return changed; + } + + bool EnhancementRadioButton(const char* text, const char* cvarName, int id) { + /*Usage : + EnhancementRadioButton("My Visible Name","gMyCVarName", MyID); + First arg is the visible name of the Radio button + Second is the cvar name where MyID will be saved. + Note: the CVar name should be the same to each Buddies. + Example : + EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG); + EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER); + EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA); + */ + std::string make_invisible = "##" + std::string(text) + std::string(cvarName); + + bool ret = false; + int val = CVarGetInteger(cvarName, 0); + if (ImGui::RadioButton(make_invisible.c_str(), id == val)) { + CVarSetInteger(cvarName, id); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ret = true; + } + ImGui::SameLine(); + ImGui::Text("%s", text); + + return ret; + } + + bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) { + bool changed = false; + std::string Cvar_RBM = std::string(cvarName) + "RBM"; + std::string MakeInvisible = "Reset##" + std::string(cvarName) + "Reset"; + if (ImGui::Button(MakeInvisible.c_str())) { + colors->x = defaultcolors.x; + colors->y = defaultcolors.y; + colors->z = defaultcolors.z; + colors->w = has_alpha ? defaultcolors.w : 255.0f; + + Color_RGBA8 colorsRGBA; + colorsRGBA.r = defaultcolors.x; + colorsRGBA.g = defaultcolors.y; + colorsRGBA.b = defaultcolors.z; + colorsRGBA.a = has_alpha ? defaultcolors.w : 255.0f; + + CVarSetColor(cvarName, colorsRGBA); + CVarSetInteger(Cvar_RBM.c_str(), 0); //On click disable rainbow mode. + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + changed = true; + } + Tooltip("Revert colors to the game's original colors (GameCube version)\nOverwrites previously chosen color"); + return changed; + } + + void DrawLockColorCheckbox(const char* cvarName) { + std::string Cvar_Lock = std::string(cvarName) + "Lock"; + s32 lock = CVarGetInteger(Cvar_Lock.c_str(), 0); + std::string FullName = "Lock##" + Cvar_Lock; + EnhancementCheckbox(FullName.c_str(), Cvar_Lock.c_str()); + Tooltip("Prevents this color from being changed upon selecting \"Randomize all\""); + } + + void RainbowColor(const char* cvarName, ImVec4* colors) { + std::string Cvar_RBM = std::string(cvarName) + "RBM"; + std::string MakeInvisible = "Rainbow##" + std::string(cvarName) + "Rainbow"; + + EnhancementCheckbox(MakeInvisible.c_str(), Cvar_RBM.c_str()); + Tooltip("Cycles through colors on a timer\nOverwrites previously chosen color"); + } + + void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) { + Color_RGBA8 defaultColors; + defaultColors.r = default_colors.x; + defaultColors.g = default_colors.y; + defaultColors.b = default_colors.z; + defaultColors.a = default_colors.w; + + Color_RGBA8 cvarColor = CVarGetColor(cvarname, defaultColors); + + ColorArray.x = cvarColor.r / 255.0; + ColorArray.y = cvarColor.g / 255.0; + ColorArray.z = cvarColor.b / 255.0; + ColorArray.w = cvarColor.a / 255.0; + } + + void DrawFlagArray32(const std::string& name, uint32_t& flags) { + ImGui::PushID(name.c_str()); + for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint32_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + if (ImGui::Checkbox("##check", &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } + + void DrawFlagArray16(const std::string& name, uint16_t& flags) { + ImGui::PushID(name.c_str()); + for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint16_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + if (ImGui::Checkbox("##check", &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } + + void DrawFlagArray8(const std::string& name, uint8_t& flags) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + if (ImGui::Checkbox("##check", &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } +} diff --git a/src/port/ui/UIWidgets.h b/src/port/ui/UIWidgets.h new file mode 100644 index 00000000..bdaf63b4 --- /dev/null +++ b/src/port/ui/UIWidgets.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace UIWidgets { + + struct TextFilters { + static int FilterNumbers(ImGuiInputTextCallbackData* data) { + if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) { + return 0; + } + return 1; + } + + static int FilterAlphaNum(ImGuiInputTextCallbackData* data) { + const char* alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ0123456789"; + if (data->EventChar < 256 && strchr(alphanum, (char)data->EventChar)) { + return 0; + } + return 1; + } + + }; + + // MARK: - Enums + + enum class CheckboxGraphics { + Cross, + Checkmark, + None + }; + constexpr float maxSliderWidth = 260.0f; +#ifdef __SWITCH__ + constexpr float sliderButtonWidth = 42.0f; +#elif defined(__WIIU__) + constexpr float sliderButtonWidth = 60.0f; +#else + constexpr float sliderButtonWidth = 30.0f; +#endif + + char* WrappedText(const char* text, unsigned int charactersPerLine = 60); + char* WrappedText(const std::string& text, unsigned int charactersPerLine); + + void SetLastItemHoverText(const std::string& text); + void SetLastItemHoverText(const char* text); + + void InsertHelpHoverText(const std::string& text); + void InsertHelpHoverText(const char* text); + + void Tooltip(const char* text); + void Spacer(float height); + void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); + + void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); + bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic); + + void ReEnableComponent(const char* disabledTooltipText); + void DisableComponent(const float alpha); + + bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); + bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); + + bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1); + bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1); + + void PaddedText(const char* text, bool padTop = true, bool padBottom = true); + + bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = true, bool disabled = false, const char* disabledTooltipText = ""); + bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = true, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = ""); + bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true, bool disabled = false, const char* disabledTooltipText = ""); + bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = ""); + + bool EnhancementRadioButton(const char* text, const char* cvarName, int id); + + bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha); + bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors); + void DrawLockColorCheckbox(const char* cvarName); + void RainbowColor(const char* cvarName, ImVec4* colors); + + void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha); + bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha = false, bool TitleSameLine = false); + + void DrawFlagArray32(const std::string& name, uint32_t& flags); + void DrawFlagArray16(const std::string& name, uint16_t& flags); + void DrawFlagArray8(const std::string& name, uint8_t& flags); +} \ No newline at end of file