2023-12-16 17:36:57 +01:00
|
|
|
#include "ConfigUtils.h"
|
|
|
|
#include "../../globals.h"
|
|
|
|
#include "../DrawUtils.h"
|
2024-05-10 16:36:47 +02:00
|
|
|
#include "../dc.h"
|
2023-12-16 17:36:57 +01:00
|
|
|
#include "../logger.h"
|
|
|
|
#include "ConfigRenderer.h"
|
|
|
|
#include "config/WUPSConfigAPI.h"
|
|
|
|
#include "hooks.h"
|
|
|
|
#include "utils/input/CombinedInput.h"
|
|
|
|
#include "utils/input/VPADInput.h"
|
|
|
|
#include "utils/input/WPADInput.h"
|
|
|
|
|
2024-05-09 16:34:28 +02:00
|
|
|
#include <avm/tv.h>
|
2023-12-16 17:36:57 +01:00
|
|
|
#include <coreinit/screen.h>
|
|
|
|
#include <gx2/display.h>
|
|
|
|
#include <memory/mappedmemory.h>
|
2024-05-07 14:22:56 +02:00
|
|
|
#include <ranges>
|
2023-12-16 17:36:57 +01:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
WUPS_CONFIG_SIMPLE_INPUT ConfigUtils::convertInputs(uint32_t buttons) {
|
|
|
|
WUPSConfigButtons pressedButtons = WUPS_CONFIG_BUTTON_NONE;
|
|
|
|
if (buttons & Input::eButtons::BUTTON_A) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_A;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_LEFT) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_LEFT;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_RIGHT) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_RIGHT;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_L) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_L;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_R) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_R;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_ZL) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_ZL;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_ZR) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_ZR;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_X) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_X;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_Y) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_Y;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_STICK_L) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_STICK_L;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_STICK_R) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_STICK_R;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_PLUS) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_PLUS;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_MINUS) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_MINUS;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_B) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_B;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_UP) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_UP;
|
|
|
|
}
|
|
|
|
if (buttons & Input::eButtons::BUTTON_DOWN) {
|
|
|
|
pressedButtons |= WUPS_CONFIG_BUTTON_DOWN;
|
|
|
|
}
|
|
|
|
return (WUPS_CONFIG_SIMPLE_INPUT) pressedButtons;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConfigUtils::displayMenu() {
|
|
|
|
renderBasicScreen("Loading configs...");
|
|
|
|
|
|
|
|
std::vector<ConfigDisplayItem> configs;
|
2024-03-24 07:40:58 +01:00
|
|
|
for (const auto &plugin : gLoadedPlugins) {
|
2023-12-16 17:36:57 +01:00
|
|
|
GeneralConfigInformation info;
|
2024-03-24 07:40:58 +01:00
|
|
|
info.name = plugin.getMetaInformation().getName();
|
|
|
|
info.author = plugin.getMetaInformation().getAuthor();
|
|
|
|
info.version = plugin.getMetaInformation().getVersion();
|
2023-12-16 17:36:57 +01:00
|
|
|
|
|
|
|
std::unique_ptr<WUPSConfigAPIBackend::WUPSConfig> config;
|
2024-03-24 07:40:58 +01:00
|
|
|
const auto configData = plugin.getConfigData();
|
2023-12-16 17:36:57 +01:00
|
|
|
if (configData) {
|
2024-03-24 07:40:58 +01:00
|
|
|
const auto configHandleOpt = configData->createConfig();
|
2023-12-16 17:36:57 +01:00
|
|
|
if (configHandleOpt) {
|
|
|
|
WUPSConfigAPIStatus callbackResult = configData->CallMenuOpenendCallback(configHandleOpt.value());
|
|
|
|
config = WUPSConfigAPIBackend::Intern::PopConfigByHandle(configHandleOpt.value());
|
|
|
|
if (!config) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to get config for handle: %08X", configHandleOpt.value().handle);
|
|
|
|
} else if (callbackResult != WUPSCONFIG_API_RESULT_SUCCESS) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Callback failed for %s: %s", info.name.c_str(), WUPSConfigAPI_GetStatusStr(callbackResult));
|
|
|
|
config.reset();
|
2024-03-02 10:02:27 +01:00
|
|
|
} else {
|
|
|
|
info.name = config->getName();
|
2023-12-16 17:36:57 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to create config for plugin: \"%s\"", info.name.c_str());
|
|
|
|
}
|
|
|
|
} else {
|
2024-03-24 07:40:58 +01:00
|
|
|
for (const auto &hook : plugin.getPluginInformation().getHookDataList()) {
|
|
|
|
if (hook.getType() == WUPS_LOADER_HOOK_GET_CONFIG_DEPRECATED) {
|
|
|
|
if (hook.getFunctionPointer() == nullptr) {
|
2023-12-16 17:36:57 +01:00
|
|
|
DEBUG_FUNCTION_LINE_ERR("Hook had invalid ptr");
|
|
|
|
break;
|
|
|
|
}
|
2024-03-24 07:40:58 +01:00
|
|
|
auto cur_config_handle = ((void *(*) ())((uint32_t *) hook.getFunctionPointer()))();
|
2023-12-16 17:36:57 +01:00
|
|
|
if (cur_config_handle == nullptr) {
|
|
|
|
DEBUG_FUNCTION_LINE_WARN("Hook returned empty handle");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
config = WUPSConfigAPIBackend::Intern::PopConfigByHandle(WUPSConfigHandle(cur_config_handle));
|
|
|
|
if (!config) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to find config for handle: %08X", cur_config_handle);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!config) {
|
2024-03-02 10:02:27 +01:00
|
|
|
config = make_unique_nothrow<WUPSConfigAPIBackend::WUPSConfig>(info.name);
|
2023-12-16 17:36:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
configs.emplace_back(info, std::move(config));
|
|
|
|
}
|
|
|
|
|
2024-05-07 14:22:56 +02:00
|
|
|
// Sort Configs by name
|
|
|
|
std::sort(
|
|
|
|
configs.begin(),
|
|
|
|
configs.end(),
|
|
|
|
[](const ConfigDisplayItem &lhs, const ConfigDisplayItem &rhs) {
|
|
|
|
auto &str1 = lhs.getConfigInformation().name;
|
|
|
|
auto &str2 = rhs.getConfigInformation().name;
|
|
|
|
return lexicographical_compare(
|
|
|
|
begin(str1), end(str1),
|
|
|
|
begin(str2), end(str2),
|
|
|
|
[](const char &char1, const char &char2) {
|
|
|
|
return tolower(char1) < tolower(char2);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
ConfigRenderer renderer(std::move(configs));
|
|
|
|
configs.clear();
|
|
|
|
|
|
|
|
CombinedInput baseInput;
|
|
|
|
VPadInput vpadInput;
|
2024-05-05 14:46:35 +02:00
|
|
|
WPADInput wpadInputs[7] = {
|
2023-12-16 17:36:57 +01:00
|
|
|
WPAD_CHAN_0,
|
|
|
|
WPAD_CHAN_1,
|
|
|
|
WPAD_CHAN_2,
|
2024-05-05 14:46:35 +02:00
|
|
|
WPAD_CHAN_3,
|
|
|
|
WPAD_CHAN_4,
|
|
|
|
WPAD_CHAN_5,
|
|
|
|
WPAD_CHAN_6,
|
|
|
|
};
|
2023-12-16 17:36:57 +01:00
|
|
|
|
2024-03-23 08:29:03 +01:00
|
|
|
auto startTime = OSGetTime();
|
|
|
|
bool skipFirstInput = true;
|
2024-05-03 13:00:18 +02:00
|
|
|
|
|
|
|
gOnlyAcceptFromThread = OSGetCurrentThread();
|
2023-12-16 17:36:57 +01:00
|
|
|
while (true) {
|
2024-05-10 15:41:40 +02:00
|
|
|
startTime = OSGetTime();
|
2024-05-03 15:08:24 +02:00
|
|
|
if (gConfigMenuShouldClose) {
|
|
|
|
gConfigMenuShouldClose = false;
|
|
|
|
break;
|
|
|
|
}
|
2023-12-16 17:36:57 +01:00
|
|
|
baseInput.reset();
|
|
|
|
if (vpadInput.update(1280, 720)) {
|
|
|
|
baseInput.combine(vpadInput);
|
|
|
|
}
|
|
|
|
for (auto &wpadInput : wpadInputs) {
|
|
|
|
if (wpadInput.update(1280, 720)) {
|
|
|
|
baseInput.combine(wpadInput);
|
|
|
|
}
|
|
|
|
}
|
2024-03-23 08:29:03 +01:00
|
|
|
|
|
|
|
if (skipFirstInput) {
|
|
|
|
skipFirstInput = false;
|
|
|
|
baseInput.lastData = baseInput.data;
|
|
|
|
}
|
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
WUPSConfigSimplePadData simpleData;
|
|
|
|
simpleData.buttons_d = convertInputs(baseInput.data.buttons_d);
|
|
|
|
simpleData.buttons_r = convertInputs(baseInput.data.buttons_r);
|
|
|
|
simpleData.buttons_h = convertInputs(baseInput.data.buttons_h);
|
|
|
|
simpleData.x = baseInput.data.x;
|
|
|
|
simpleData.y = baseInput.data.y;
|
|
|
|
simpleData.touched = baseInput.data.touched;
|
|
|
|
simpleData.validPointer = baseInput.data.validPointer;
|
|
|
|
|
|
|
|
WUPSConfigComplexPadData complexData;
|
|
|
|
complexData.vpad.data = vpadInput.vpad;
|
|
|
|
complexData.vpad.tpCalib = vpadInput.tpCalib;
|
|
|
|
complexData.vpad.vpadError = vpadInput.vpadError;
|
2024-05-05 14:46:35 +02:00
|
|
|
for (int i = 0; i < 7; i++) {
|
2023-12-16 17:36:57 +01:00
|
|
|
complexData.kpad.kpadError[i] = wpadInputs[i].kpadError;
|
|
|
|
complexData.kpad.data[i] = wpadInputs[i].kpad;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto subState = renderer.Update(baseInput, simpleData, complexData);
|
|
|
|
if (subState != SUB_STATE_RUNNING) {
|
|
|
|
break;
|
|
|
|
}
|
2024-05-10 15:55:10 +02:00
|
|
|
if (renderer.NeedsRedraw() || baseInput.data.buttons_d || baseInput.data.buttons_r) {
|
2024-05-03 11:55:27 +02:00
|
|
|
renderer.Render();
|
|
|
|
}
|
|
|
|
renderer.ResetNeedsRedraw();
|
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
auto diffTime = OSTicksToMicroseconds(OSGetTime() - startTime);
|
|
|
|
if (diffTime < 16000) {
|
|
|
|
OSSleepTicks(OSMicrosecondsToTicks(16000 - diffTime));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-10 15:41:40 +02:00
|
|
|
startTime = OSGetTime();
|
|
|
|
renderBasicScreen("Saving configs...");
|
|
|
|
|
2024-03-24 07:40:58 +01:00
|
|
|
for (const auto &plugin : gLoadedPlugins) {
|
|
|
|
const auto configData = plugin.getConfigData();
|
2023-12-16 17:36:57 +01:00
|
|
|
if (configData) {
|
2024-03-24 07:40:58 +01:00
|
|
|
if (configData->CallMenuClosedCallback() == WUPSCONFIG_API_RESULT_MISSING_CALLBACK) {
|
|
|
|
DEBUG_FUNCTION_LINE_WARN("CallMenuClosedCallback is missing for %s", plugin.getMetaInformation().getName().c_str());
|
|
|
|
}
|
2023-12-16 17:36:57 +01:00
|
|
|
} else {
|
2024-03-24 07:40:58 +01:00
|
|
|
CallHook(plugin, WUPS_LOADER_HOOK_CONFIG_CLOSED_DEPRECATED);
|
2023-12-16 17:36:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WUPSConfigAPIBackend::Intern::CleanAllHandles();
|
2024-05-10 15:41:40 +02:00
|
|
|
|
|
|
|
// we want wait at least 300ms to avoid leaking inputs from the config menu to the application
|
|
|
|
auto diffTime = OSTicksToMilliseconds(OSGetTime() - startTime);
|
|
|
|
if (diffTime < 300) {
|
|
|
|
OSSleepTicks(OSMillisecondsToTicks(300 - diffTime));
|
|
|
|
}
|
2023-12-16 17:36:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConfigUtils::openConfigMenu() {
|
2024-05-03 15:08:24 +02:00
|
|
|
gOnlyAcceptFromThread = OSGetCurrentThread();
|
2023-12-16 17:36:57 +01:00
|
|
|
bool wasHomeButtonMenuEnabled = OSIsHomeButtonMenuEnabled();
|
|
|
|
|
2024-05-09 16:34:28 +02:00
|
|
|
// Save copy of DC reg values
|
2024-05-10 16:36:47 +02:00
|
|
|
auto tvRender1 = DCReadReg32(SCREEN_TV, D1GRPH_CONTROL_REG);
|
|
|
|
auto tvRender2 = DCReadReg32(SCREEN_TV, D1GRPH_ENABLE_REG);
|
|
|
|
auto tvPitch1 = DCReadReg32(SCREEN_TV, D1GRPH_PITCH_REG);
|
|
|
|
auto tvPitch2 = DCReadReg32(SCREEN_TV, D1OVL_PITCH_REG);
|
|
|
|
|
|
|
|
auto drcRender1 = DCReadReg32(SCREEN_DRC, D1GRPH_CONTROL_REG);
|
|
|
|
auto drcRender2 = DCReadReg32(SCREEN_DRC, D1GRPH_ENABLE_REG);
|
|
|
|
auto drcPitch1 = DCReadReg32(SCREEN_DRC, D1GRPH_PITCH_REG);
|
|
|
|
auto drcPitch2 = DCReadReg32(SCREEN_DRC, D1OVL_PITCH_REG);
|
2024-05-09 16:34:28 +02:00
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
OSScreenInit();
|
|
|
|
|
|
|
|
uint32_t screen_buf0_size = OSScreenGetBufferSizeEx(SCREEN_TV);
|
|
|
|
uint32_t screen_buf1_size = OSScreenGetBufferSizeEx(SCREEN_DRC);
|
|
|
|
void *screenbuffer0 = MEMAllocFromMappedMemoryForGX2Ex(screen_buf0_size, 0x100);
|
|
|
|
void *screenbuffer1 = MEMAllocFromMappedMemoryForGX2Ex(screen_buf1_size, 0x100);
|
|
|
|
|
|
|
|
bool skipScreen0Free = false;
|
|
|
|
bool skipScreen1Free = false;
|
2024-04-28 02:12:42 +02:00
|
|
|
bool doShutdownKPAD = false;
|
2023-12-16 17:36:57 +01:00
|
|
|
|
|
|
|
if (!screenbuffer0 || !screenbuffer1) {
|
|
|
|
if (screenbuffer0 == nullptr) {
|
|
|
|
if (gStoredTVBuffer.buffer_size >= screen_buf0_size) {
|
|
|
|
screenbuffer0 = gStoredTVBuffer.buffer;
|
|
|
|
skipScreen0Free = true;
|
|
|
|
DEBUG_FUNCTION_LINE_VERBOSE("Use storedTVBuffer");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (screenbuffer1 == nullptr) {
|
|
|
|
if (gStoredDRCBuffer.buffer_size >= screen_buf1_size) {
|
|
|
|
screenbuffer1 = gStoredDRCBuffer.buffer;
|
|
|
|
skipScreen1Free = true;
|
|
|
|
DEBUG_FUNCTION_LINE_VERBOSE("Use storedDRCBuffer");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!screenbuffer0 || !screenbuffer1) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to alloc buffers");
|
|
|
|
goto error_exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
OSScreenSetBufferEx(SCREEN_TV, screenbuffer0);
|
|
|
|
OSScreenSetBufferEx(SCREEN_DRC, screenbuffer1);
|
|
|
|
|
2024-05-09 16:34:28 +02:00
|
|
|
// Clear screens
|
|
|
|
OSScreenClearBufferEx(SCREEN_TV, 0);
|
|
|
|
OSScreenClearBufferEx(SCREEN_DRC, 0);
|
|
|
|
|
|
|
|
// Flip buffers
|
|
|
|
OSScreenFlipBuffersEx(SCREEN_TV);
|
|
|
|
OSScreenFlipBuffersEx(SCREEN_DRC);
|
2023-12-16 17:36:57 +01:00
|
|
|
|
|
|
|
// Clear screens
|
|
|
|
OSScreenClearBufferEx(SCREEN_TV, 0);
|
|
|
|
OSScreenClearBufferEx(SCREEN_DRC, 0);
|
|
|
|
|
|
|
|
// Flip buffers
|
|
|
|
OSScreenFlipBuffersEx(SCREEN_TV);
|
|
|
|
OSScreenFlipBuffersEx(SCREEN_DRC);
|
|
|
|
|
2024-05-09 16:34:28 +02:00
|
|
|
OSScreenEnableEx(SCREEN_TV, 1);
|
|
|
|
OSScreenEnableEx(SCREEN_DRC, 1);
|
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
DrawUtils::initBuffers(screenbuffer0, screen_buf0_size, screenbuffer1, screen_buf1_size);
|
|
|
|
if (!DrawUtils::initFont()) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to init Font");
|
|
|
|
goto error_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// disable the home button menu to prevent opening it when exiting
|
|
|
|
OSEnableHomeButtonMenu(false);
|
|
|
|
|
2024-04-28 02:12:42 +02:00
|
|
|
KPADStatus status;
|
|
|
|
KPADError err;
|
|
|
|
if (KPADReadEx(WPAD_CHAN_0, &status, 0, &err) == 0 && err == KPAD_ERROR_UNINITIALIZED) {
|
|
|
|
doShutdownKPAD = true;
|
|
|
|
KPADInit();
|
|
|
|
}
|
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
displayMenu();
|
|
|
|
|
2024-04-28 02:12:42 +02:00
|
|
|
if (doShutdownKPAD) {
|
|
|
|
KPADShutdown();
|
|
|
|
}
|
|
|
|
|
2023-12-16 17:36:57 +01:00
|
|
|
OSEnableHomeButtonMenu(wasHomeButtonMenuEnabled);
|
|
|
|
|
|
|
|
DrawUtils::deinitFont();
|
|
|
|
|
|
|
|
error_exit:
|
2024-05-09 16:34:28 +02:00
|
|
|
// Restore DC reg values
|
2024-05-10 16:36:47 +02:00
|
|
|
DCWriteReg32(SCREEN_TV, D1GRPH_CONTROL_REG, tvRender1);
|
|
|
|
DCWriteReg32(SCREEN_TV, D1GRPH_ENABLE_REG, tvRender2);
|
|
|
|
DCWriteReg32(SCREEN_TV, D1GRPH_PITCH_REG, tvPitch1);
|
|
|
|
DCWriteReg32(SCREEN_TV, D1OVL_PITCH_REG, tvPitch2);
|
|
|
|
|
|
|
|
DCWriteReg32(SCREEN_DRC, D1GRPH_CONTROL_REG, drcRender1);
|
|
|
|
DCWriteReg32(SCREEN_DRC, D1GRPH_ENABLE_REG, drcRender2);
|
|
|
|
DCWriteReg32(SCREEN_DRC, D1GRPH_PITCH_REG, drcPitch1);
|
|
|
|
DCWriteReg32(SCREEN_DRC, D1OVL_PITCH_REG, drcPitch2);
|
2023-12-16 17:36:57 +01:00
|
|
|
|
|
|
|
if (!skipScreen0Free && screenbuffer0) {
|
|
|
|
MEMFreeToMappedMemory(screenbuffer0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!skipScreen1Free && screenbuffer1) {
|
|
|
|
MEMFreeToMappedMemory(screenbuffer1);
|
|
|
|
}
|
2024-05-03 15:08:24 +02:00
|
|
|
gOnlyAcceptFromThread = nullptr;
|
2023-12-16 17:36:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConfigUtils::renderBasicScreen(std::string_view text) {
|
|
|
|
DrawUtils::beginDraw();
|
|
|
|
DrawUtils::clear(COLOR_BACKGROUND);
|
|
|
|
DrawUtils::setFontColor(COLOR_TEXT);
|
|
|
|
|
|
|
|
// draw top bar
|
|
|
|
DrawUtils::setFontSize(24);
|
|
|
|
DrawUtils::print(16, 6 + 24, "Wii U Plugin System Config Menu");
|
|
|
|
DrawUtils::setFontSize(18);
|
|
|
|
DrawUtils::print(SCREEN_WIDTH - 16, 8 + 24, VERSION_FULL, true);
|
|
|
|
DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK);
|
|
|
|
|
|
|
|
// draw bottom bar
|
|
|
|
DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK);
|
|
|
|
DrawUtils::setFontSize(18);
|
|
|
|
DrawUtils::print(16, SCREEN_HEIGHT - 10, "\ue07d Navigate ");
|
|
|
|
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, "\ue000 Select", true);
|
|
|
|
|
|
|
|
DrawUtils::setFontSize(24);
|
|
|
|
uint32_t sz = DrawUtils::getTextWidth(text.data());
|
|
|
|
|
|
|
|
DrawUtils::print((SCREEN_WIDTH / 2) - (sz / 2), (SCREEN_HEIGHT / 2), text.data());
|
|
|
|
|
|
|
|
// draw home button
|
|
|
|
DrawUtils::setFontSize(18);
|
|
|
|
const char *exitHint = "\ue044 Exit";
|
|
|
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(exitHint) / 2, SCREEN_HEIGHT - 10, exitHint, true);
|
|
|
|
|
|
|
|
DrawUtils::endDraw();
|
|
|
|
}
|