diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index 279ac95a..82f6617e 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -34,7 +34,7 @@ volatile CustomizeTable C = { /* Disables RAM powerdown */ .hpMode = DISABLED, -.commonEmcMemVolt = 1175000, /* LPDDR4X JEDEC Specification */ +.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */ .eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */ .eristaEmcMaxClock1 = 1600000, .eristaEmcMaxClock2 = 1600000, @@ -103,10 +103,8 @@ volatile CustomizeTable C = { .marikoGpuUV = 0, -/* For automatic vmin detection, set this to AUTO. */ -/* vmin past 795mV won't work due to HOS limitation */ -/* Vmin is automatically set to 800mV when SoC temperature is below 20C */ -.marikoGpuVmin = AUTO, +/* Vmin past 795mV won't work due boot voltage being 800mV. */ +.marikoGpuVmin = 610, .marikoGpuVmax = 800, diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp index 47435515..0cdccae8 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp @@ -90,38 +90,6 @@ namespace ams::ldr::hoc::pcv { static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, }; static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern"); - struct SpeedoVminTable { - u32 speedo; - u32 voltage; - }; - - struct RamVminOffsetTable { - u32 maxClock; - u32 offset; - }; - - static const SpeedoVminTable vminTable[] { - {1400, 610}, // LOW SPEEDO -> use stock vmin - {1560, 590}, - {1583, 570}, - {1620, 565}, - {1670, 560}, - {1694, 555}, - {1731, 550}, - {1750, 540}, - {0xFFFFFFFF, 530}, - }; - - static const RamVminOffsetTable ramOffset[] { - {2400000, 5}, - {2533000, 10}, - {2666000, 15}, - {2800000, 20}, - {2933000, 25}, - {3200000, 30}, - {0xFFFFFFFF, 35}, - }; - /* GPU Max Clock asm Pattern: * * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11) diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp index 64edf931..b1030793 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -24,46 +24,6 @@ namespace ams::ldr::hoc::pcv::mariko { - u32 GetGpuVminVoltage() { - for (auto e : vminTable) { - if (C.gpuSpeedo <= e.speedo) { - return e.voltage; - } - } - - return 530; - } - - u32 GetRamVminAdjustment(u32 vmin) { - if (C.marikoEmcMaxClock < 2133000) { - return vmin; - } - - const u32 ramScale = (((C.marikoEmcMaxClock / 1000) - 2133) / 33) * 5 + vmin; - - for (auto r : ramOffset) { - if (C.marikoEmcMaxClock < r.maxClock) { - return ramScale + r.offset; - } - } - - return ramScale; - } - - /* Note: EOS (probably?) has a bug in this function that always results in high vmin, this is fixed here. */ - u32 GetAutoVoltage() { - u32 voltage = GetGpuVminVoltage(); - voltage = GetRamVminAdjustment(voltage); - - u32 voltageOffset = 590 - C.commonGpuVoltOffset; - - if (voltageOffset < voltage) { - voltage = voltageOffset; - } - - return voltage; - } - Result GpuVoltDVFS(u32 *ptr) { /* Check for valid pattern. */ for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) { @@ -77,15 +37,10 @@ namespace ams::ldr::hoc::pcv::mariko { PATCH_OFFSET(ptr + 1, C.marikoGpuVmax); } - /* C.marikoGpuVmin is non zero, user sets manual voltage. */ if (C.marikoGpuVmin) { PATCH_OFFSET(ptr, C.marikoGpuVmin); - R_SUCCEED(); } - /* C.marikoGpuVmin is zero, auto voltage is applied. */ - u32 autoVmin = GetAutoVoltage(); - PATCH_OFFSET(ptr, autoVmin); R_SUCCEED(); } @@ -94,24 +49,15 @@ namespace ams::ldr::hoc::pcv::mariko { R_THROW(ldr::ResultInvalidGpuDvfs()); } - u32 vmin = C.marikoGpuVmin; - - /* Automatic voltage. */ if (!C.marikoGpuVmin) { - vmin = GetAutoVoltage(); - PATCH_OFFSET(ptr, vmin); - PATCH_OFFSET(ptr + 3, vmin); - PATCH_OFFSET(ptr + 6, vmin); - PATCH_OFFSET(ptr + 9, vmin); - } else { - /* Manual voltage. */ - PATCH_OFFSET(ptr, vmin); - PATCH_OFFSET(ptr + 3, vmin); - PATCH_OFFSET(ptr + 6, vmin); - PATCH_OFFSET(ptr + 9, vmin); + R_SKIP(); } - PATCH_OFFSET(ptr + 12, vmin); + PATCH_OFFSET(ptr + 0, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 3, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 6, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 9, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 12, C.marikoGpuVmin); R_SUCCEED(); } diff --git a/Source/Horizon-OC-Monitor/source/Utils.hpp b/Source/Horizon-OC-Monitor/source/Utils.hpp index f583b733..220c6430 100644 --- a/Source/Horizon-OC-Monitor/source/Utils.hpp +++ b/Source/Horizon-OC-Monitor/source/Utils.hpp @@ -208,8 +208,8 @@ uintptr_t FPSaddress = 0; uintptr_t FPSavgaddress = 0; uint64_t PID = 0; uint32_t FPS = 0xFE; -float FPSmin = 254; -float FPSmax = 0; +float FPSmin = 254; +float FPSmax = 0; float FPSavg = 254; float FPSavg_old = 254; bool useOldFPSavg = false; @@ -223,15 +223,16 @@ uint32_t realCPU_Hz = 0; uint32_t realGPU_Hz = 0; uint32_t realRAM_Hz = 0; uint32_t partLoad[HocClkPartLoad_EnumMax]; -uint32_t realCPU_mV = 0; -uint32_t realGPU_mV = 0; -uint32_t realRAM_mV = 0; -uint32_t realSOC_mV = 0; +uint32_t realCPU_mV = 0; +uint32_t realGPU_mV = 0; +uint32_t realRAM_mV = 0; +uint32_t realSOC_mV = 0; uint8_t refreshRate = 0; //Read real temps from sys-clk sysmodule int32_t realCPU_Temp = 0; int32_t realGPU_Temp = 0; +u32 realPLLX_Temp = 0; int32_t realRAM_Temp = 0; int compare (const void* elem1, const void* elem2) { @@ -271,20 +272,20 @@ void searchSharedMemoryBlock(uintptr_t base) { NxFps = 0; return; } - + ptrdiff_t search_offset = 0; const uintptr_t memory_end = base + 0x1000; - + while (search_offset < 0x1000) { const uintptr_t current_addr = base + search_offset; - + // Ensure we don't read past the end of shared memory if (current_addr + sizeof(NxFpsSharedBlock) > memory_end) { break; } - + NxFps = (NxFpsSharedBlock*)current_addr; - + // Add bounds checking and magic validation if (NxFps && current_addr >= base && NxFps->MAGIC == 0x465053) { return; @@ -297,24 +298,24 @@ void searchSharedMemoryBlock(uintptr_t base) { //Check if SaltyNX is working bool CheckPort() { Handle saltysd; - + // Try up to 67 times with exponential backoff for better responsiveness for (int i = 0; i < 50; i++) { if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) { svcCloseHandle(saltysd); return true; } - + // Progressive sleep - start fast, then slow down //if (i < 10) { // svcSleepThread(100'000); // 0.1ms for first 10 attempts //} else if (i < 30) { - // svcSleepThread(500'000); // 0.5ms for next 20 attempts + // svcSleepThread(500'000); // 0.5ms for next 20 attempts //} else { // svcSleepThread(1'000'000); // 1ms for remaining attempts //} } - + return false; } @@ -442,7 +443,7 @@ void BatteryChecker(void*) { if (batCurrentAvg >= 0) { batTimeEstimate = -1; - } + } else { static float batteryTimeEstimateInMinutes = 0; Max17050ReadReg(MAX17050_TTE, &data); @@ -528,7 +529,7 @@ bool usingEOS() { // === ULTRA-FAST VOLTAGE READING === static constexpr PowerDomainId domains[] = { PcvPowerDomainId_Max77621_Cpu, // [0] CPU - PcvPowerDomainId_Max77621_Gpu, // [1] GPU + PcvPowerDomainId_Max77621_Gpu, // [1] GPU PcvPowerDomainId_Max77812_Dram, // [2] VDD2 (EMC/DRAM) PcvPowerDomainId_Max77620_Sd0, // [3] SOC PcvPowerDomainId_Max77620_Sd1 // [4] VDDQ @@ -539,7 +540,7 @@ static constexpr PowerDomainId domains[] = { void Misc(void*) { const uint64_t timeout_ns = TeslaFPS < 10 ? (1'000'000'000 / TeslaFPS) : 100'000'000; const bool isUsingEOS = usingEOS(); - + // Initialize voltage reading if needed bool canReadVoltages = false; if (!isUsingEOS && realVoltsPolling) { @@ -548,10 +549,10 @@ void Misc(void*) { realVoltsPolling = false; } } - + do { mutexLock(&mutex_Misc); - + // CPU, GPU and RAM Frequency if (R_SUCCEEDED(clkrstCheck)) { ClkrstSession clkSession; @@ -573,7 +574,7 @@ void Misc(void*) { pcvGetClockRate(PcvModule_GPU, &GPU_Hz); pcvGetClockRate(PcvModule_EMC, &RAM_Hz); } - + // Get sys-clk data if (R_SUCCEEDED(hocclkCheck)) { HocClkContext hocclkCTX; @@ -585,11 +586,12 @@ void Misc(void*) { partLoad[HocClkPartLoad_EMCCpu] = hocclkCTX.partLoad[HocClkPartLoad_EMCCpu]; realCPU_Temp = hocclkCTX.temps[HocClkThermalSensor_CPU]; realGPU_Temp = hocclkCTX.temps[HocClkThermalSensor_GPU]; + realPLLX_Temp = hocclkCTX.temps[HocClkThermalSensor_PLLX]; realRAM_Temp = hocclkCTX.temps[HocClkThermalSensor_MEM]; - - realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; - realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; - realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; + + realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; + realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; + realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; realSOC_mV = hocclkCTX.voltages[HocClkVoltage_SOC]; const u32 vdd2_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2] / 1000; // µV to mV const u32 vddq_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDDQ] / 1000; // µV to mV @@ -598,7 +600,7 @@ void Misc(void*) { } } - + // Temperatures if (R_SUCCEEDED(i2cCheck)) { Tmp451GetSocTemp(&SOC_temperatureF); @@ -607,7 +609,7 @@ void Misc(void*) { if (R_SUCCEEDED(tcCheck)) { tcGetSkinTemperatureMilliC(&skin_temperaturemiliC); } - + // RAM Memory Used if (R_SUCCEEDED(Hinted)) { svcGetSystemInfo(&RAM_Total_application_u, 0, INVALID_HANDLE, 0); @@ -619,7 +621,7 @@ void Misc(void*) { svcGetSystemInfo(&RAM_Used_system_u, 1, INVALID_HANDLE, 2); svcGetSystemInfo(&RAM_Used_systemunsafe_u, 1, INVALID_HANDLE, 3); } - + // Fan if (R_SUCCEEDED(pwmCheck)) { double temp = 0; @@ -633,29 +635,29 @@ void Misc(void*) { } } } - + // GPU Load if (R_SUCCEEDED(nvCheck) && GPULoadPerFrame) { nvIoctl(fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &GPU_Load_u); } - + // FPS - with proper null checks if (GameRunning) { if (NxFps && SharedMemoryUsed) { FPS = NxFps->FPS; const size_t element_count = sizeof(NxFps->FPSticks) / sizeof(NxFps->FPSticks[0]); - FPSavg_old = static_cast(systemtickfrequency) / + FPSavg_old = static_cast(systemtickfrequency) / (std::accumulate(&NxFps->FPSticks[0], &NxFps->FPSticks[element_count], 0.0f) / element_count); - + const float FPS_in = static_cast(FPS); if (FPSavg_old >= (FPS_in - 0.25f) && FPSavg_old <= (FPS_in + 0.25f)) { FPSavg = FPS_in; } else { FPSavg = FPSavg_old; } - + lastFrameNumber = NxFps->frameNumber; - + if (FPSavg > FPSmax) FPSmax = FPSavg; if (FPSavg < FPSmin) FPSmin = FPSavg; } @@ -664,11 +666,11 @@ void Misc(void*) { FPSmin = 254; FPSmax = 0; } - + mutexUnlock(&mutex_Misc); - + } while (!leventWait(&threadexit, timeout_ns)); - + // Cleanup voltage reading if initialized if (canReadVoltages) { rgltrExit(); @@ -697,7 +699,7 @@ void Misc2(void*) { void Misc3(void*) { const bool isUsingEOS = usingEOS(); - + // Initialize voltage reading if needed bool canReadVoltages = false; if (!isUsingEOS && realVoltsPolling) { @@ -706,24 +708,25 @@ void Misc3(void*) { realVoltsPolling = false; } } - + do { mutexLock(&mutex_Misc); - + // Get sys-clk data if (R_SUCCEEDED(hocclkCheck)) { HocClkContext hocclkCTX; if (R_SUCCEEDED(hocclkIpcGetCurrentContext(&hocclkCTX))) { partLoad[HocClkPartLoad_EMC] = hocclkCTX.partLoad[HocClkPartLoad_EMC]; partLoad[HocClkPartLoad_EMCCpu] = hocclkCTX.partLoad[HocClkPartLoad_EMCCpu]; - + realCPU_Temp = hocclkCTX.temps[HocClkThermalSensor_CPU]; realGPU_Temp = hocclkCTX.temps[HocClkThermalSensor_GPU]; + realPLLX_Temp = hocclkCTX.temps[HocClkThermalSensor_PLLX]; realRAM_Temp = hocclkCTX.temps[HocClkThermalSensor_MEM]; - - realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; - realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; - realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; + + realCPU_mV = hocclkCTX.voltages[HocClkVoltage_CPU]; + realGPU_mV = hocclkCTX.voltages[HocClkVoltage_GPU]; + realRAM_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2]; realSOC_mV = hocclkCTX.voltages[HocClkVoltage_SOC]; const u32 vdd2_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDD2] / 1000; // µV to mV const u32 vddq_mV = hocclkCTX.voltages[HocClkVoltage_EMCVDDQ] / 1000; // µV to mV @@ -732,7 +735,7 @@ void Misc3(void*) { } } - + // Temperatures if (R_SUCCEEDED(i2cCheck)) { Tmp451GetSocTemp(&SOC_temperatureF); @@ -741,7 +744,7 @@ void Misc3(void*) { if (R_SUCCEEDED(tcCheck)) { tcGetSkinTemperatureMilliC(&skin_temperaturemiliC); } - + // Fan if (R_SUCCEEDED(pwmCheck)) { double temp = 0; @@ -755,16 +758,16 @@ void Misc3(void*) { } } } - + // GPU Load if (R_SUCCEEDED(nvCheck)) { nvIoctl(fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &GPU_Load_u); } - + mutexUnlock(&mutex_Misc); - + } while (!leventWait(&threadexit, 1'000'000'000)); // 1 second timeout - + // Cleanup voltage reading if initialized if (canReadVoltages) { rgltrExit(); @@ -909,7 +912,7 @@ void FPSCounter(void*) { const size_t element_count = sizeof(NxFps -> FPSticks) / sizeof(NxFps -> FPSticks[0]); FPSavg_old = (float)systemtickfrequency / (std::accumulate(&NxFps->FPSticks[0], &NxFps->FPSticks[element_count], 0) / element_count); const float FPS_in = (float)FPS; - if (FPSavg_old >= (FPS_in-0.25) && FPSavg_old <= (FPS_in+0.25)) + if (FPSavg_old >= (FPS_in-0.25) && FPSavg_old <= (FPS_in+0.25)) FPSavg = FPS_in; else FPSavg = FPSavg_old; lastFrameNumber = NxFps -> frameNumber; @@ -937,10 +940,10 @@ void EndFPSCounterThread() { threadClose(&t4); } -void StartInfoThread() { +void StartInfoThread() { // Clear the thread exit event for new threads leventClear(&threadexit); - + threadCreate(&t0, CheckCore, &idletick0, NULL, 0x1000, 0x10, 0); threadCreate(&t1, CheckCore, &idletick1, NULL, 0x1000, 0x10, 1); threadCreate(&t2, CheckCore, &idletick2, NULL, 0x1000, 0x10, 2); @@ -962,14 +965,14 @@ void StartInfoThread() { void EndInfoThread() { // Signal the thread exit event leventSignal(&threadexit); - + // Wait for all threads to exit threadWaitForExit(&t0); threadWaitForExit(&t1); threadWaitForExit(&t2); threadWaitForExit(&t3); threadWaitForExit(&t7); - + // Close thread handles threadClose(&t0); threadClose(&t1); @@ -1053,7 +1056,7 @@ void formatButtonCombination(std::string& line) { button = line.substr(old_pos); if (replaces.find(button) != replaces.end()) { line.replace(old_pos, button.length(), replaces[button]); - } + } } //uint64_t comboBitmask = 0; @@ -1086,7 +1089,7 @@ void formatButtonCombination(std::string& line) { // {"RIGHT", HidNpadButton_AnyRight} // }; // -// +// // std::string comboCopy = buttonCombo; // Make a copy of buttonCombo // // static const std::string delimiter = "+"; @@ -1112,16 +1115,16 @@ void formatButtonCombination(std::string& line) { ALWAYS_INLINE bool isKeyComboPressed(uint64_t keysHeld, uint64_t keysDown) { // Check if any of the combo buttons are pressed down this frame // while the rest of the combo buttons are being held - + const uint64_t comboButtonsDown = keysDown & tsl::cfg::launchCombo; const uint64_t comboButtonsHeld = keysHeld & tsl::cfg::launchCombo; - + // If any combo buttons are pressed down this frame if (comboButtonsDown != 0) { // Check if the remaining combo buttons are being held // (the full combo should be active when combining held + down) const uint64_t totalComboActive = comboButtonsHeld | comboButtonsDown; - + if (totalComboActive == tsl::cfg::launchCombo) { fixHiding = true; // for fixing hiding when returning //triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -1129,7 +1132,7 @@ ALWAYS_INLINE bool isKeyComboPressed(uint64_t keysHeld, uint64_t keysDown) { return true; } } - + return false; } @@ -1177,7 +1180,7 @@ void ParseIniFile() { // Load main config INI once auto configData = ult::getParsedDataFromIniFile(configIniPath); auto statusIt = configData.find("status-monitor"); - + if (statusIt != configData.end()) { const auto& statusSection = statusIt->second; std::string key; @@ -1189,12 +1192,12 @@ void ParseIniFile() { convertToUpper(key); batteryFiltered = (key == "TRUE"); } - + auto refreshRateIt = statusSection.find("battery_time_left_refreshrate"); if (refreshRateIt != statusSection.end()) { batteryTimeLeftRefreshRate = std::clamp(atol(refreshRateIt->second.c_str()), 1L, 60L); } - + auto gpuLoadIt = statusSection.find("average_gpu_load"); if (gpuLoadIt != statusSection.end()) { key = gpuLoadIt->second; @@ -1215,11 +1218,11 @@ void ParseIniFile() { {ultrahandConfigIniPath, "ultrahand"}, {teslaConfigIniPath, "tesla"} }; - + for (const auto& config : externalConfigs) { auto extConfigData = ult::getParsedDataFromIniFile(config.path); auto sectionIt = extConfigData.find(config.section); - + if (sectionIt != extConfigData.end()) { auto keyComboIt = sectionIt->second.find("key_combo"); if (keyComboIt != sectionIt->second.end() && !keyComboIt->second.empty()) { @@ -1230,14 +1233,14 @@ void ParseIniFile() { } } } - + //comboBitmask = MapButtons(keyCombo); } ALWAYS_INLINE bool isValidRGBA4Color(const std::string& hexColor) { const char* data = hexColor.data(); const size_t size = hexColor.size(); - + static unsigned char c; for (size_t i = 0; i < size; ++i) { c = data[i]; @@ -1246,7 +1249,7 @@ ALWAYS_INLINE bool isValidRGBA4Color(const std::string& hexColor) { return false; } } - + return true; } @@ -1254,7 +1257,7 @@ bool convertStrToRGBA4444(std::string hexColor, uint16_t* returnValue) { // Check if # is present if (hexColor.size() != 5 || hexColor[0] != '#') return false; - + hexColor = hexColor.substr(1); if (isValidRGBA4Color(hexColor)) { @@ -1268,7 +1271,7 @@ struct FullSettings { uint8_t refreshRate; bool setPosRight; bool showRealFreqs; - bool realVolts; + bool realVolts; bool realTemps; bool showDeltas; bool showTargetFreqs; @@ -1288,6 +1291,7 @@ struct MiniSettings { bool realFrequencies; bool realVolts; bool realTemps; + bool realTempsDec; bool showFullCPU; bool showFullResolution; bool showFanPercentage; @@ -1322,8 +1326,9 @@ struct MiniSettings { struct MicroSettings { uint8_t refreshRate; bool realFrequencies; - bool realVolts; + bool realVolts; bool realTemps; + bool realTempsDec; bool showFullCPU; bool showFullResolution; bool showSOCVoltage; @@ -1408,6 +1413,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { settings->realFrequencies = true; settings->realVolts = true; settings->realTemps = true; + settings->realTempsDec = false; settings->showFullCPU = false; settings->showFullResolution = true; settings->showFanPercentage = true; @@ -1443,7 +1449,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -1452,13 +1458,13 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("mini"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; @@ -1469,7 +1475,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (it != section.end()) { settings->refreshRate = std::clamp(atol(it->second.c_str()), 1L, 60L); } - + // Process boolean flags it = section.find("real_freqs"); if (it != section.end()) { @@ -1477,29 +1483,37 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { convertToUpper(key); settings->realFrequencies = (key == "TRUE"); } - + it = section.find("real_volts"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realVolts = (key == "TRUE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realTemps = (key == "TRUE"); } + + it = section.find("real_temps_dec"); + if (it != section.end()) { + key = it->second; + convertToUpper(key); + settings->realTempsDec = !(key == "FALSE"); + } + // Process font sizes with shared bounds static constexpr long minFontSize = 8; static constexpr long maxFontSize = 22; - + it = section.find("handheld_font_size"); if (it != section.end()) { settings->handheldFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + it = section.find("docked_font_size"); if (it != section.end()) { settings->dockedFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); @@ -1509,7 +1523,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (it != section.end()) { settings->spacing = atol(it->second.c_str()); } - + // Process colors it = section.find("background_color"); if (it != section.end()) { @@ -1523,7 +1537,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->focusBackgroundColor = temp; } - + it = section.find("separator_color"); if (it != section.end()) { temp = 0; @@ -1537,14 +1551,14 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->catColor = temp; } - + it = section.find("text_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->textColor = temp; } - + // Process RAM load flag it = section.find("show_full_cpu"); if (it != section.end()) { @@ -1622,7 +1636,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { convertToUpper(key); settings->show = std::move(key); } - + // Process RAM load flag it = section.find("replace_MB_with_RAM_load"); if (it != section.end()) { @@ -1662,7 +1676,7 @@ ALWAYS_INLINE void GetConfigSettings(MiniSettings* settings) { convertToUpper(key); settings->sleepExit = (key != "FALSE"); } - + // Process alignment settings //it = section.find("layer_width_align"); //if (it != section.end()) { @@ -1707,6 +1721,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { settings->realFrequencies = true; settings->realVolts = true; settings->realTemps = true; + settings->realTempsDec = false; settings->showFullCPU = false; settings->showFullResolution = false; settings->showSOCVoltage = true; @@ -1735,7 +1750,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -1744,13 +1759,13 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("micro"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; @@ -1761,7 +1776,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { if (it != section.end()) { settings->refreshRate = std::clamp(atol(it->second.c_str()), 1L, 60L); } - + // Process boolean flags it = section.find("real_freqs"); if (it != section.end()) { @@ -1769,28 +1784,35 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->realFrequencies = (key == "TRUE"); } - + it = section.find("real_volts"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realVolts = (key == "TRUE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realTemps = (key == "TRUE"); - } - + } + + it = section.find("real_temps_dec"); + if (it != section.end()) { + key = it->second; + convertToUpper(key); + settings->realTempsDec = !(key == "FALSE"); + } + it = section.find("show_full_cpu"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showFullCPU = (key == "TRUE"); } - + it = section.find("show_full_res"); if (it != section.end()) { key = it->second; @@ -1860,21 +1882,21 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->invertBatteryDisplay = (key != "FALSE"); } - + // Process font sizes with shared bounds static constexpr long minFontSize = 8; static constexpr long maxFontSize = 18; - + it = section.find("handheld_font_size"); if (it != section.end()) { settings->handheldFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + it = section.find("docked_font_size"); if (it != section.end()) { settings->dockedFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + // Process colors it = section.find("background_color"); if (it != section.end()) { @@ -1882,28 +1904,28 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->backgroundColor = temp; } - + it = section.find("separator_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->separatorColor = temp; } - + it = section.find("cat_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->catColor = temp; } - + it = section.find("text_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->textColor = temp; } - + // Process text alignment it = section.find("text_align"); if (it != section.end()) { @@ -1917,7 +1939,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { settings->alignTo = 2; } } - + // Process RAM load flag it = section.find("replace_GB_with_RAM_load"); if (it != section.end()) { @@ -1925,7 +1947,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->showpartLoad = (key != "FALSE"); } - + // Process show string it = section.find("show"); if (it != section.end()) { @@ -1933,7 +1955,7 @@ ALWAYS_INLINE void GetConfigSettings(MicroSettings* settings) { convertToUpper(key); settings->show = std::move(key); } - + // Process layer height alignment it = section.find("layer_height_align"); if (it != section.end()) { @@ -1979,7 +2001,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -1988,33 +2010,33 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("fps-counter"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; const auto& section = sectionIt->second; - + // Process font sizes with shared bounds static constexpr long minFontSize = 8; static constexpr long maxFontSize = 150; - + auto it = section.find("handheld_font_size"); if (it != section.end()) { settings->handheldFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + it = section.find("docked_font_size"); if (it != section.end()) { settings->dockedFontSize = std::clamp(atol(it->second.c_str()), minFontSize, maxFontSize); } - + // Process colors it = section.find("background_color"); if (it != section.end()) { @@ -2030,14 +2052,14 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { settings->focusBackgroundColor = temp; } - + it = section.find("text_color"); if (it != section.end()) { temp = 0; if (convertStrToRGBA4444(it->second, &temp)) settings->textColor = temp; } - + // Process alignment settings //it = section.find("layer_width_align"); //if (it != section.end()) { @@ -2049,7 +2071,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsCounterSettings* settings) { // settings->setPos = 2; // } //} - + //it = section.find("layer_height_align"); //if (it != section.end()) { // key = it->second; @@ -2123,7 +2145,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -2132,18 +2154,18 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("fps-graph"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; const auto& section = sectionIt->second; - + // Process alignment settings //auto it = section.find("layer_width_align"); //if (it != section.end()) { @@ -2166,7 +2188,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { // settings->setPos += 6; // } //} - + // Process show_info boolean auto it = section.find("show_info"); if (it != section.end()) { @@ -2174,7 +2196,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { convertToUpper(key); settings->showInfo = (key == "TRUE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; @@ -2214,13 +2236,13 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { settings->framePadding = atol(it->second.c_str()); } - + // Process colors - using a struct for cleaner code struct ColorMapping { const char* key; uint16_t* target; }; - + const ColorMapping colorMappings[] = { {"min_fps_text_color", &settings->minFPSTextColor}, {"max_fps_text_color", &settings->maxFPSTextColor}, @@ -2235,7 +2257,7 @@ ALWAYS_INLINE void GetConfigSettings(FpsGraphSettings* settings) { {"text_color", &settings->textColor}, {"cat_color", &settings->catColor} }; - + for (const auto& mapping : colorMappings) { it = section.find(mapping.key); if (it != section.end()) { @@ -2267,7 +2289,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -2276,16 +2298,16 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("full"); if (sectionIt == parsedData.end()) return; - + std::string key; uint16_t temp; - + const auto& section = sectionIt->second; // Process refresh_rate @@ -2293,7 +2315,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { if (it != section.end()) { settings->refreshRate = std::clamp(atol(it->second.c_str()), 1L, 60L); } - + // Process layer position it = section.find("layer_width_align"); if (it != section.end()) { @@ -2301,7 +2323,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { convertToUpper(key); settings->setPosRight = (key == "RIGHT"); } - + // Process boolean flags it = section.find("show_real_freqs"); if (it != section.end()) { @@ -2309,49 +2331,49 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { convertToUpper(key); settings->showRealFreqs = !(key == "FALSE"); } - + it = section.find("real_temps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->realTemps = (key == "TRUE"); } - + it = section.find("show_deltas"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showDeltas = !(key == "FALSE"); } - + it = section.find("show_target_freqs"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showTargetFreqs = !(key == "FALSE"); } - + it = section.find("show_fps"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showFPS = !(key == "FALSE"); } - + it = section.find("show_res"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showRES = !(key == "FALSE"); } - + it = section.find("show_read_speed"); if (it != section.end()) { key = it->second; convertToUpper(key); settings->showRDSD = !(key == "FALSE"); } - + it = section.find("use_dynamic_colors"); if (it != section.end()) { key = it->second; @@ -2380,7 +2402,7 @@ ALWAYS_INLINE void GetConfigSettings(FullSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->catColor1 = temp; } - + it = section.find("cat_color_2"); if (it != section.end()) { temp = 0; @@ -2415,7 +2437,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { // Open and read file efficiently FILE* configFile = fopen(configIniPath, "r"); if (!configFile) return; - + fseek(configFile, 0, SEEK_END); const long fileSize = ftell(configFile); fseek(configFile, 0, SEEK_SET); @@ -2424,15 +2446,15 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { fileData.resize(fileSize); fread(fileData.data(), 1, fileSize, configFile); fclose(configFile); - + auto parsedData = ult::parseIni(fileData); // Cache section lookup auto sectionIt = parsedData.find("game_resolutions"); if (sectionIt == parsedData.end()) return; - + std::string key; - + const auto& section = sectionIt->second; // Process refresh_rate @@ -2457,7 +2479,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { if (convertStrToRGBA4444(it->second, &temp)) settings->focusBackgroundColor = temp; } - + it = section.find("cat_color"); if (it != section.end()) { temp = 0; @@ -2472,7 +2494,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { // settings->catColor2 = temp; //} - + it = section.find("text_color"); if (it != section.end()) { temp = 0; @@ -2494,7 +2516,7 @@ ALWAYS_INLINE void GetConfigSettings(ResolutionSettings* settings) { if (it != section.end()) { settings->framePadding = atol(it->second.c_str()); } - + // Process alignment settings //it = section.find("layer_width_align"); //if (it != section.end()) { diff --git a/Source/Horizon-OC-Monitor/source/modes/Configurator.hpp b/Source/Horizon-OC-Monitor/source/modes/Configurator.hpp index 30adbb64..f33b6a17 100644 --- a/Source/Horizon-OC-Monitor/source/modes/Configurator.hpp +++ b/Source/Horizon-OC-Monitor/source/modes/Configurator.hpp @@ -1,24 +1,24 @@ /* * Mode-Specific Configuration Settings - * + * * Based on actual settings structures, each mode only shows applicable settings: - * - * Mini Mode: Refresh Rate, Colors (background, focus_background, separator, category, text), + * + * Mini Mode: Refresh Rate, Colors (background, focus_background, separator, category, text), * Toggles, Font Sizes, Elements, DTC Format - * - * Micro Mode: Refresh Rate, Colors (background, separator, category, text), Toggles, + * + * Micro Mode: Refresh Rate, Colors (background, separator, category, text), Toggles, * Font Sizes, Elements, Text Alignment, Vertical Position (Top/Bottom only), DTC Format - * - * Full Mode: Refresh Rate, Toggles (show_real_freqs, show_deltas, etc.), + * + * Full Mode: Refresh Rate, Toggles (show_real_freqs, show_deltas, etc.), * Horizontal Position (Left/Right only) - NO colors, fonts, or elements - * - * FPS Counter: Refresh Rate, Colors (background, text only), Font Sizes, + * + * FPS Counter: Refresh Rate, Colors (background, text only), Font Sizes, * Horizontal/Vertical Position - * - * FPS Graph: Refresh Rate, Colors (8 graph-specific colors), Toggles (show_info only), + * + * FPS Graph: Refresh Rate, Colors (8 graph-specific colors), Toggles (show_info only), * Horizontal/Vertical Position - NO fonts - * - * Game Resolutions: Refresh Rate, Colors (background, category, text only), + * + * Game Resolutions: Refresh Rate, Colors (background, category, text only), * Horizontal/Vertical Position - NO toggles, fonts, or elements */ @@ -81,9 +81,9 @@ private: bool isFPSCounterMode; bool isFPSGraphMode; bool isGameResolutionsMode; - + public: - AlphaSelector(const std::string& mode, const std::string& key, const std::string& displayTitle) + AlphaSelector(const std::string& mode, const std::string& key, const std::string& displayTitle) : modeName(mode), colorKey(key), title(displayTitle) { isMiniMode = (mode == "Mini"); isMicroMode = (mode == "Micro"); @@ -94,7 +94,7 @@ public: ~AlphaSelector() { lastSelectedListItem = nullptr; } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader(title)); @@ -105,14 +105,14 @@ public: else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; else if (isGameResolutionsMode) section = "game_resolutions"; - + // Get current color value and extract alpha std::string currentColor = ult::parseValueFromIniSection(configIniPath, section, colorKey); if (currentColor.empty()) { currentColor = "#0009"; // Default } std::string currentAlpha = extractAlphaFromColor(currentColor); - + // Alpha options static const std::vector> alphaOptions = { {"Transparent", '0'}, @@ -127,7 +127,7 @@ public: {"90%", 'E'}, {"Opaque", 'F'} }; - + for (const auto& option : alphaOptions) { auto* alphaItem = new tsl::elm::ListItem(option.first); if (currentAlpha[0] == option.second) { @@ -139,10 +139,10 @@ public: // Get current color and update only the alpha std::string color = ult::parseValueFromIniSection(configIniPath, section, colorKey); if (color.empty()) color = "#0009"; - + std::string newColor = setAlphaInColor(color, option.second); ult::setIniFileValue(configIniPath, section, colorKey, newColor); - + alphaItem->setValue(ult::CHECKMARK_SYMBOL); if (lastSelectedListItem && lastSelectedListItem != alphaItem) { lastSelectedListItem->setValue(""); @@ -154,14 +154,14 @@ public: }); list->addItem(alphaItem); } - + list->jumpToItem("", ult::CHECKMARK_SYMBOL, false); - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Alpha"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -169,7 +169,7 @@ public: jumpItemName = title; jumpItemValue = ""; jumpItemExactMatch = false; - + tsl::swapTo(SwapDepth(2), modeName); return true; } @@ -214,7 +214,7 @@ private: std::string modeName; bool isMiniMode; bool isMicroMode; - + public: DTCFormatConfig(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); @@ -223,20 +223,20 @@ public: ~DTCFormatConfig() { lastSelectedListItem = nullptr; } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("DTC Format")); const std::string section = isMiniMode ? "mini" : "micro"; std::string currentValue = ult::parseValueFromIniSection(configIniPath, section, "dtc_format"); - + // Handle default values if (currentValue.empty()) { currentValue = isMiniMode ? "%m-%d-%Y"+ult::DIVIDER_SYMBOL+"%H:%M:%S" : "%H:%M:%S"; } - - + + for (const auto& format : dtcFormats) { auto* formatItem = new tsl::elm::ListItem(format.first); //formatItem->setValue(format.second); @@ -258,15 +258,15 @@ public: }); list->addItem(formatItem); } - + // Jump to currently selected item list->jumpToItem("", ult::CHECKMARK_SYMBOL, false); - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "DTC Format"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -274,7 +274,7 @@ public: jumpItemName = "DTC Format"; jumpItemValue = ""; jumpItemExactMatch = false; - + tsl::swapTo(SwapDepth(2), modeName); return true; } @@ -292,7 +292,7 @@ private: bool isFPSGraphMode; bool isGameResolutionsMode; bool isFPSCounterMode; - + public: TogglesConfig(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); @@ -302,11 +302,11 @@ public: isGameResolutionsMode = (mode == "Game Resolutions"); isFPSCounterMode = (mode == "FPS Counter"); } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Toggles")); - + if (isFPSGraphMode) { // FPS Graph: show_info and disable_screenshots auto* showInfo = new tsl::elm::ToggleListItem("Info", getCurrentShowInfo()); @@ -314,7 +314,7 @@ public: ult::setIniFileValue(configIniPath, "fps-graph", "show_info", state ? "true" : "false"); }); list->addItem(showInfo); - + auto* realTemps = new tsl::elm::ToggleListItem("Real Temperatures", getCurrentFPSGraphRealTemps()); realTemps->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "fps-graph", "real_temps", state ? "true" : "false"); @@ -332,7 +332,7 @@ public: ult::setIniFileValue(configIniPath, "fps-graph", "disable_screenshots", state ? "true" : "false"); }); list->addItem(disableScreenshots); - + } else if (isFullMode) { // Full mode: specific full toggles auto* realFreqs = new tsl::elm::ToggleListItem("Real Freqs", getCurrentShowRealFreqs()); @@ -340,37 +340,37 @@ public: ult::setIniFileValue(configIniPath, "full", "show_real_freqs", state ? "true" : "false"); }); list->addItem(realFreqs); - + auto* realTemps = new tsl::elm::ToggleListItem("Real Temperatures", getCurrentFullRealTemps()); realTemps->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "full", "real_temps", state ? "true" : "false"); }); list->addItem(realTemps); - + auto* showDeltas = new tsl::elm::ToggleListItem("Deltas", getCurrentShowDeltas()); showDeltas->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "full", "show_deltas", state ? "true" : "false"); }); list->addItem(showDeltas); - + auto* targetFreqs = new tsl::elm::ToggleListItem("Target Freqs", getCurrentShowTargetFreqs()); targetFreqs->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "full", "show_target_freqs", state ? "true" : "false"); }); list->addItem(targetFreqs); - + auto* showFPS = new tsl::elm::ToggleListItem("FPS", getCurrentShowFPS()); showFPS->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "full", "show_fps", state ? "true" : "false"); }); list->addItem(showFPS); - + auto* showRES = new tsl::elm::ToggleListItem("RES", getCurrentShowRES()); showRES->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "full", "show_res", state ? "true" : "false"); }); list->addItem(showRES); - + auto* showRDSD = new tsl::elm::ToggleListItem("Read Speed", getCurrentShowRDSD()); showRDSD->setStateChangedListener([this](bool state) { ult::setIniFileValue(configIniPath, "full", "show_read_speed", state ? "true" : "false"); @@ -388,35 +388,41 @@ public: ult::setIniFileValue(configIniPath, "full", "disable_screenshots", state ? "true" : "false"); }); list->addItem(disableScreenshots); - + } else if (isMiniMode || isMicroMode) { // Mini/Micro modes: shared toggles const std::string section = isMiniMode ? "mini" : "micro"; - + auto* realFreqs = new tsl::elm::ToggleListItem("Real Frequencies", getCurrentRealFreqs()); realFreqs->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "real_freqs", state ? "true" : "false"); }); list->addItem(realFreqs); - + auto* realVolts = new tsl::elm::ToggleListItem("Real Voltages", getCurrentRealVolts()); realVolts->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "real_volts", state ? "true" : "false"); }); list->addItem(realVolts); - + auto* realTemps = new tsl::elm::ToggleListItem("Real Temperatures", getCurrentRealTemps()); realTemps->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "real_temps", state ? "true" : "false"); }); list->addItem(realTemps); - + + auto *realTempsDec = new tsl::elm::ToggleListItem("Real Temp Decimals", getCurrentRealTempsDec()); + realTempsDec->setStateChangedListener([this, section](bool state) { + ult::setIniFileValue(configIniPath, section, "real_temps_dec", state ? "true" : "false"); + }); + list->addItem(realTempsDec); + auto* showFullCPU = new tsl::elm::ToggleListItem("Full CPU", getCurrentShowFullCPU()); showFullCPU->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "show_full_cpu", state ? "true" : "false"); }); list->addItem(showFullCPU); - + auto* showVDDQ = new tsl::elm::ToggleListItem("VDD2", getCurrentShowVDDQ()); showVDDQ->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "show_vddq", state ? "true" : "false"); @@ -434,7 +440,7 @@ public: ult::setIniFileValue(configIniPath, section, "show_full_res", state ? "true" : "false"); }); list->addItem(showFullRes); - + auto* socVoltage = new tsl::elm::ToggleListItem("SOC Voltage", getCurrentShowSOCVoltage()); socVoltage->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "show_soc_voltage", state ? "true" : "false"); @@ -456,7 +462,7 @@ public: }); list->addItem(invertBatteryDisplay); } - + auto* dtcSymbol = new tsl::elm::ToggleListItem("Use DTC Symbol", getCurrentUseDTCSymbol()); dtcSymbol->setStateChangedListener([this, section](bool state) { ult::setIniFileValue(configIniPath, section, "use_dtc_symbol", state ? "true" : "false"); @@ -480,7 +486,7 @@ public: ult::setIniFileValue(configIniPath, section, "sleep_exit", state ? "true" : "false"); }); list->addItem(sleepExit); - + } else if (isGameResolutionsMode) { // Game Resolutions mode: only disable_screenshots auto* disableScreenshots = new tsl::elm::ToggleListItem("Disable Screenshots", getCurrentDisableScreenshots("game_resolutions")); @@ -488,7 +494,7 @@ public: ult::setIniFileValue(configIniPath, "game_resolutions", "disable_screenshots", state ? "true" : "false"); }); list->addItem(disableScreenshots); - + } else if (isFPSCounterMode) { // FPS Counter mode: only disable_screenshots auto* integerCounter = new tsl::elm::ToggleListItem("Use Integer Counter", getCurrentUseIntegerCounter("fps-counter")); @@ -504,7 +510,7 @@ public: }); list->addItem(disableScreenshots); } - + list->jumpToItem(jumpItemName, jumpItemValue, jumpItemExactMatch); { jumpItemName = ""; @@ -516,7 +522,7 @@ public: rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -526,7 +532,7 @@ public: } return false; } - + private: // Helper methods for getting current toggle states bool getCurrentShowInfo() { @@ -535,14 +541,14 @@ private: convertToUpper(value); return value == "TRUE"; } - + bool getCurrentFPSGraphRealTemps() { std::string value = ult::parseValueFromIniSection(configIniPath, "fps-graph", "real_temps"); if (value.empty()) return false; convertToUpper(value); return value == "TRUE"; } - + bool getCurrentRealFreqs() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "real_freqs"); @@ -550,7 +556,7 @@ private: convertToUpper(value); return value == "TRUE"; } - + bool getCurrentRealVolts() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "real_volts"); @@ -558,7 +564,7 @@ private: convertToUpper(value); return value == "TRUE"; } - + bool getCurrentRealTemps() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "real_temps"); @@ -566,7 +572,15 @@ private: convertToUpper(value); return value == "TRUE"; } - + + bool getCurrentRealTempsDec() { + const std::string section = isMiniMode ? "mini" : "micro"; + std::string value = ult::parseValueFromIniSection(configIniPath, section, "real_temps_dec"); + if (value.empty()) return true; + convertToUpper(value); + return value == "TRUE"; + } + bool getCurrentShowFullCPU() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "show_full_cpu"); @@ -591,7 +605,7 @@ private: return value == "TRUE"; } - + bool getCurrentShowFullRes() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "show_full_res"); @@ -599,7 +613,7 @@ private: convertToUpper(value); return value != "FALSE"; } - + bool getCurrentShowSOCVoltage() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "show_soc_voltage"); @@ -623,7 +637,7 @@ private: convertToUpper(value); return value != "FALSE"; } - + bool getCurrentUseDTCSymbol() { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "use_dtc_symbol"); @@ -660,7 +674,7 @@ private: convertToUpper(value); return value != "FALSE"; // True if not explicitly "FALSE" } - + // Full mode toggle helpers bool getCurrentShowRealFreqs() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_real_freqs"); @@ -668,42 +682,42 @@ private: convertToUpper(value); return value != "FALSE"; } - + bool getCurrentFullRealTemps() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "real_temps"); if (value.empty()) return false; convertToUpper(value); return value == "TRUE"; } - + bool getCurrentShowDeltas() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_deltas"); if (value.empty()) return true; convertToUpper(value); return value != "FALSE"; } - + bool getCurrentShowTargetFreqs() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_target_freqs"); if (value.empty()) return true; convertToUpper(value); return value != "FALSE"; } - + bool getCurrentShowFPS() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_fps"); if (value.empty()) return true; convertToUpper(value); return value != "FALSE"; } - + bool getCurrentShowRES() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_res"); if (value.empty()) return true; convertToUpper(value); return value != "FALSE"; } - + bool getCurrentShowRDSD() { std::string value = ult::parseValueFromIniSection(configIniPath, "full", "show_read_speed"); if (value.empty()) return true; @@ -723,7 +737,7 @@ private: bool isFPSCounterMode; bool isFPSGraphMode; int currentRate; - + public: RefreshRateConfig(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); @@ -732,7 +746,7 @@ public: isGameResolutionsMode = (mode == "Game Resolutions"); isFPSCounterMode = (mode == "FPS Counter"); isFPSGraphMode = (mode == "FPS Graph"); - + std::string section; if (isMiniMode) section = "mini"; else if (isMicroMode) section = "micro"; @@ -740,7 +754,7 @@ public: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + const std::string value = ult::parseValueFromIniSection(configIniPath, section, "refresh_rate"); int defaultRate = (isGameResolutionsMode) ? 10 : ((isFPSCounterMode || isFPSGraphMode) ? 30 : 1); currentRate = value.empty() ? defaultRate : std::clamp(atoi(value.c_str()), 1, 60); @@ -749,7 +763,7 @@ public: ~RefreshRateConfig() { lastSelectedListItem = nullptr; } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Refresh Rate")); @@ -770,7 +784,7 @@ public: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + ult::setIniFileValue(configIniPath, section, "refresh_rate", std::to_string(rate)); rateItem->setValue(ult::CHECKMARK_SYMBOL); if (lastSelectedListItem && rateItem != lastSelectedListItem) @@ -782,14 +796,14 @@ public: }); list->addItem(rateItem); } - + list->jumpToItem("", ult::CHECKMARK_SYMBOL, false); tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Configuration"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -797,7 +811,7 @@ public: jumpItemName = "Refresh Rate"; jumpItemValue = ""; jumpItemExactMatch = false; - + tsl::swapTo(SwapDepth(2), modeName); return true; } @@ -810,7 +824,7 @@ class FramePaddingConfig : public tsl::Gui { private: std::string modeName; int currentPadding; - + public: FramePaddingConfig(const std::string& mode) : modeName(mode) { const std::string value = ult::parseValueFromIniSection(configIniPath, "mini", "frame_padding"); @@ -820,7 +834,7 @@ public: ~FramePaddingConfig() { lastSelectedListItem = nullptr; } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Frame Padding")); @@ -845,14 +859,14 @@ public: }); list->addItem(paddingItem); } - + list->jumpToItem("", ult::CHECKMARK_SYMBOL, false); tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Configuration"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -860,7 +874,7 @@ public: jumpItemName = "Frame Padding"; jumpItemValue = ""; jumpItemExactMatch = false; - + tsl::swapTo(SwapDepth(2), modeName); return true; } @@ -878,9 +892,9 @@ private: bool isMicroMode; bool isFPSCounterMode; std::string title; - + public: - FontSizeSelector(const std::string& mode, const std::string& type) + FontSizeSelector(const std::string& mode, const std::string& type) : modeName(mode), fontType(type) { isMiniMode = (mode == "Mini"); isMicroMode = (mode == "Micro"); @@ -892,7 +906,7 @@ public: ~FontSizeSelector() { lastSelectedListItem = nullptr; } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader(title)); @@ -901,19 +915,19 @@ public: if (isMiniMode) section = "mini"; else if (isMicroMode) section = "micro"; else if (isFPSCounterMode) section = "fps-counter"; - + const std::string keyName = fontType + "_font_size"; const std::string currentValue = ult::parseValueFromIniSection(configIniPath, section, keyName); int defaultSize = isFPSCounterMode ? 40 : 15; const int currentSize = currentValue.empty() ? defaultSize : atoi(currentValue.c_str()); - + // Font size range depends on mode int minSize = 8; int maxSize; if (isFPSCounterMode) maxSize = 150; else if (isMiniMode) maxSize = 22; else maxSize = 18; // Micro mode - + for (int size = minSize; size <= maxSize; size++) { auto* sizeItem = new tsl::elm::ListItem(std::to_string(size) + " pt"); if (size == currentSize) { @@ -933,14 +947,14 @@ public: }); list->addItem(sizeItem); } - + list->jumpToItem("", ult::CHECKMARK_SYMBOL, false); - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Font Sizes"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -948,7 +962,7 @@ public: jumpItemName = title; jumpItemValue = ""; jumpItemExactMatch = false; - + tsl::swapTo(SwapDepth(2), modeName); return true; } @@ -963,30 +977,30 @@ private: bool isMiniMode; bool isMicroMode; bool isFPSCounterMode; - + public: FontSizeConfig(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); isMicroMode = (mode == "Micro"); isFPSCounterMode = (mode == "FPS Counter"); } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Font Sizes")); - + std::string section; if (isMiniMode) section = "mini"; else if (isMicroMode) section = "micro"; else if (isFPSCounterMode) section = "fps-counter"; - + const std::string handheldValue = ult::parseValueFromIniSection(configIniPath, section, "handheld_font_size"); const std::string dockedValue = ult::parseValueFromIniSection(configIniPath, section, "docked_font_size"); - + int defaultSize = isFPSCounterMode ? 40 : 15; const int handheldSize = handheldValue.empty() ? defaultSize : atoi(handheldValue.c_str()); const int dockedSize = dockedValue.empty() ? defaultSize : atoi(dockedValue.c_str()); - + auto* handheldItem = new tsl::elm::ListItem("Handheld Font Size"); handheldItem->setValue(std::to_string(handheldSize) + " pt"); handheldItem->setClickListener([this](uint64_t keys) { @@ -997,7 +1011,7 @@ public: return false; }); list->addItem(handheldItem); - + auto* dockedItem = new tsl::elm::ListItem("Docked Font Size"); dockedItem->setValue(std::to_string(dockedSize) + " pt"); dockedItem->setClickListener([this](uint64_t keys) { @@ -1008,7 +1022,7 @@ public: return false; }); list->addItem(dockedItem); - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Configuration"); rootFrame->setContent(list); list->jumpToItem(jumpItemName, jumpItemValue, jumpItemExactMatch); @@ -1019,7 +1033,7 @@ public: } return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -1046,9 +1060,9 @@ private: bool isFPSGraphMode; bool isBackgroundColor; bool isTextBasedColor; - + public: - ColorSelector(const std::string& mode, const std::string& title, const std::string& key, const std::string& def) + ColorSelector(const std::string& mode, const std::string& title, const std::string& key, const std::string& def) : modeName(mode), modeTitle(title), colorKey(key), defaultValue(def) { isMiniMode = (mode == "Mini"); isMicroMode = (mode == "Micro"); @@ -1056,25 +1070,25 @@ public: isGameResolutionsMode = (mode == "Game Resolutions"); isFPSCounterMode = (mode == "FPS Counter"); isFPSGraphMode = (mode == "FPS Graph"); - + // Determine if this is a background color or text-based color - isBackgroundColor = (key == "background_color" || key == "focus_background_color" || + isBackgroundColor = (key == "background_color" || key == "focus_background_color" || (isFPSGraphMode && (key == "fps_counter_color" || key == "dashed_line_color"))); - + isTextBasedColor = (key == "text_color" || key == "separator_color" || key == "cat_color" || - (isFPSGraphMode && (key == "border_color" || key == "max_fps_text_color" || - key == "min_fps_text_color" || key == "main_line_color" || + (isFPSGraphMode && (key == "border_color" || key == "max_fps_text_color" || + key == "min_fps_text_color" || key == "main_line_color" || key == "rounded_line_color" || key == "perfect_line_color"))); } ~ColorSelector() { lastSelectedListItem = nullptr; } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader(modeTitle)); - + std::string section; if (isMiniMode) section = "mini"; else if (isMicroMode) section = "micro"; @@ -1082,13 +1096,13 @@ public: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + std::string currentValue = ult::parseValueFromIniSection(configIniPath, section, colorKey); if (currentValue.empty()) currentValue = defaultValue; - + // Extract the color without alpha for comparison (for backgrounds and text colors) std::string currentColorWithoutAlpha = extractColorWithoutAlpha(currentValue); - + // Updated colors list with comprehensive color palette static const std::vector> colors = { // Grays & Basics @@ -1098,54 +1112,54 @@ public: {"Light Gray", "#888F"}, {"Silver", "#CCCF"}, {"White", "#FFFF"}, - + // Reds {"Dark Red", "#800F"}, {"Red", "#F00F"}, {"Light Red", "#F88F"}, {"Pink", "#F8AF"}, - + // Greens {"Dark Green", "#080F"}, {"Green", "#0F0F"}, {"Lime Green", "#0C0F"}, {"Light Green", "#8F8F"}, - + // Blues {"Dark Blue", "#003F"}, {"Blue", "#00FF"}, {"Light Blue", "#2DFF"}, {"Sky Blue", "#8CFF"}, - + // Purples {"Dark Purple", "#808F"}, {"Purple", "#80FF"}, {"Light Purple", "#C8FF"}, {"Violet", "#A0FF"}, - + // Yellows & Oranges {"Orange", "#F80F"}, {"Yellow", "#FF0F"}, {"Light Yellow", "#FFCF"}, - + // Cyans & Teals {"Teal", "#088F"}, {"Cyan", "#0FFF"}, {"Light Cyan", "#8FFF"}, - + // Magentas & Pinks {"Magenta", "#F0FF"}, {"Hot Pink", "#F8CF"}, - + // Browns {"Brown", "#840F"}, {"Light Brown", "#A86F"} }; - + std::string _jumpItemValue; for (const auto& color : colors) { auto* colorItem = new tsl::elm::ListItem(color.first); - + // For display, show the color code based on type std::string displayValue; if (isTextBasedColor || isBackgroundColor) { @@ -1155,9 +1169,9 @@ public: // For any remaining FPS Graph colors (shouldn't happen now), keep original behavior displayValue = color.second; } - + colorItem->setValue(displayValue); - + // Check if this is the selected color bool isSelected = false; if (isBackgroundColor || isTextBasedColor) { @@ -1167,17 +1181,17 @@ public: // For any remaining FPS Graph colors (shouldn't happen now) isSelected = (color.second == currentValue); } - + if (isSelected) { colorItem->setValue(displayValue + " " + ult::CHECKMARK_SYMBOL); lastSelectedListItem = colorItem; _jumpItemValue = displayValue + " " + ult::CHECKMARK_SYMBOL; } - + colorItem->setClickListener([this, colorItem, color, section, displayValue](uint64_t keys) { if (keys & KEY_A) { std::string valueToSave = color.second; - + if (isBackgroundColor) { // For background colors, preserve existing alpha std::string existingColor = ult::parseValueFromIniSection(configIniPath, section, colorKey); @@ -1190,9 +1204,9 @@ public: valueToSave = setAlphaInColor(color.second, 'F'); } // For any remaining FPS Graph colors (shouldn't happen now), use as-is - + ult::setIniFileValue(configIniPath, section, colorKey, valueToSave); - + // Update the UI - clear old checkmark and set new one if (lastSelectedListItem && lastSelectedListItem != colorItem) { // Get the display value for the old selected item @@ -1209,7 +1223,7 @@ public: } lastSelectedListItem->setValue(oldDisplayValue); } - + // Set new checkmark colorItem->setValue(displayValue + " " + ult::CHECKMARK_SYMBOL); lastSelectedListItem = colorItem; @@ -1220,12 +1234,12 @@ public: list->addItem(colorItem); } list->jumpToItem("", _jumpItemValue, false); - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Colors"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -1251,7 +1265,7 @@ private: bool isGameResolutionsMode; bool isFPSCounterMode; bool isFPSGraphMode; - + public: ColorConfig(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); @@ -1260,18 +1274,18 @@ public: isGameResolutionsMode = (mode == "Game Resolutions"); isFPSCounterMode = (mode == "FPS Counter"); isFPSGraphMode = (mode == "FPS Graph"); - + // Full mode should never access color configuration //if (isFullMode) { // // This should not happen, but if it does, go back // tsl::goBack(); //} } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Colors")); - + auto getCurrentColor = [this](const std::string& key, const std::string& def) { std::string section; if (isMiniMode) section = "mini"; @@ -1280,18 +1294,18 @@ public: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + std::string value = ult::parseValueFromIniSection(configIniPath, section, key); return value.empty() ? def : value; }; - + auto getColorName = [](const std::string& hexColor) -> std::string { // Extract RGB without alpha for comparison std::string rgb = hexColor; if (hexColor.length() == 5 && hexColor[0] == '#') { rgb = hexColor.substr(0, 4); } - + // Map of hex colors to names (RGB only, no alpha) static const std::map colorNames = { // Grays & Basics @@ -1301,50 +1315,50 @@ public: {"#888", "Light Gray"}, {"#CCC", "Silver"}, {"#FFF", "White"}, - + // Reds {"#800", "Dark Red"}, {"#F00", "Red"}, {"#F88", "Light Red"}, {"#F8A", "Pink"}, - + // Greens {"#080", "Dark Green"}, {"#0F0", "Green"}, {"#0C0", "Lime Green"}, {"#8F8", "Light Green"}, - + // Blues {"#003", "Dark Blue"}, {"#00F", "Blue"}, {"#2DF", "Light Blue"}, {"#8CF", "Sky Blue"}, - + // Purples {"#808", "Dark Purple"}, {"#80F", "Purple"}, {"#C8F", "Light Purple"}, {"#A0F", "Violet"}, - + // Yellows & Oranges {"#F80", "Orange"}, {"#FF0", "Yellow"}, {"#FFC", "Light Yellow"}, - + // Cyans & Teals {"#088", "Teal"}, {"#0FF", "Cyan"}, {"#8FF", "Light Cyan"}, - + // Magentas & Pinks {"#F0F", "Magenta"}, {"#F8C", "Hot Pink"}, - + // Browns {"#840", "Brown"}, {"#A86", "Light Brown"} }; - + auto it = colorNames.find(rgb); if (it != colorNames.end()) { // Special case for black/transparent disambiguation @@ -1357,7 +1371,7 @@ public: } return rgb; // Return hex if no name found }; - + auto getAlphaPercentage = [](const std::string& color) -> std::string { if (color.length() == 5 && color[0] == '#') { char alpha = color[4]; @@ -1378,7 +1392,7 @@ public: } return "60%"; }; - + if (!isFullMode) { // Background Color (all modes) auto* bgColor = new tsl::elm::ListItem("Background Color"); @@ -1394,7 +1408,7 @@ public: return false; }); list->addItem(bgColor); - + // Background Alpha (new) auto* bgAlpha = new tsl::elm::ListItem("Background Alpha"); bgAlpha->setValue(getAlphaPercentage(bgCurrentColor)); @@ -1406,7 +1420,7 @@ public: return false; }); list->addItem(bgAlpha); - + if (isMiniMode || isFPSCounterMode || isFPSGraphMode || isGameResolutionsMode) { // Mini mode: has focus background auto* focusBgColor = new tsl::elm::ListItem("Focus Color"); @@ -1420,7 +1434,7 @@ public: return false; }); list->addItem(focusBgColor); - + // Focus Alpha (new) auto* focusAlpha = new tsl::elm::ListItem("Focus Alpha"); focusAlpha->setValue(getAlphaPercentage(focusCurrentColor)); @@ -1448,7 +1462,7 @@ public: return false; }); list->addItem(textColor); - + if (isFPSGraphMode) { // FPS Graph specific colors struct ColorSetting { @@ -1457,7 +1471,7 @@ public: std::string defaultVal; bool isBackgroundType; // true for colors that allow alpha adjustment }; - + // Game Resolutions: only category color (no separator) auto* catColor = new tsl::elm::ListItem("Category Color"); catColor->setValue(getColorName(getCurrentColor("cat_color", "#0F0F"))); @@ -1480,11 +1494,11 @@ public: {"Rounded Line", "rounded_line_color", "#F0FF", false}, // text type {"Perfect Line", "perfect_line_color", "#0C0F", false} // text type }; - + for (const auto& color : fpsGraphColors) { auto* colorItem = new tsl::elm::ListItem(color.name + " Color"); const std::string currentVal = getCurrentColor(color.key, color.defaultVal); - + if (color.isBackgroundType) { // For background-type colors, show color name colorItem->setValue(getColorName(currentVal)); @@ -1492,7 +1506,7 @@ public: // For text-type colors, show color name colorItem->setValue(getColorName(currentVal)); } - + colorItem->setClickListener([this, color](uint64_t keys) { if (keys & KEY_A) { tsl::changeTo(modeName, color.name, color.key, color.defaultVal); @@ -1501,7 +1515,7 @@ public: return false; }); list->addItem(colorItem); - + // Add alpha selector for background-type colors if (color.isBackgroundType) { auto* alphaItem = new tsl::elm::ListItem(color.name + " Alpha"); @@ -1541,7 +1555,7 @@ public: return false; }); list->addItem(catColor2); - + auto* sepColor = new tsl::elm::ListItem("Separator Color"); // Display color name for separator colors sepColor->setValue(getColorName(getCurrentColor("separator_color", "#888F"))); @@ -1565,7 +1579,7 @@ public: return false; }); list->addItem(catColor); - + auto* sepColor = new tsl::elm::ListItem("Separator Color"); // Display color name for separator colors sepColor->setValue(getColorName(getCurrentColor("separator_color", "#888F"))); @@ -1577,7 +1591,7 @@ public: return false; }); list->addItem(sepColor); - + } else if (isMicroMode) { auto* catColor = new tsl::elm::ListItem("Category Color"); catColor->setValue(getColorName(getCurrentColor("cat_color", "#2DFF"))); @@ -1589,7 +1603,7 @@ public: return false; }); list->addItem(catColor); - + // Micro mode: separator and category colors (no focus background like Mini) auto* sepColor = new tsl::elm::ListItem("Separator Color"); sepColor->setValue(getColorName(getCurrentColor("separator_color", "#888F"))); @@ -1601,8 +1615,8 @@ public: return false; }); list->addItem(sepColor); - - + + } else if (isGameResolutionsMode) { // Game Resolutions: only category color (no separator) auto* catColor = new tsl::elm::ListItem("Category Color"); @@ -1618,20 +1632,20 @@ public: } // FPS Counter mode: only background and text colors (already added above) // Full mode: NO color settings at all (excluded from this function) - + list->jumpToItem(jumpItemName, jumpItemValue, jumpItemExactMatch); { jumpItemName = ""; jumpItemValue = ""; jumpItemExactMatch = false; } - + //list->disableCaching(); tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Configuration"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -1651,13 +1665,13 @@ private: bool isMicroMode; std::vector elementOrder; std::unordered_set enabledElements; - + public: ShowConfig(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); isMicroMode = (mode == "Micro"); } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Elements " + ult::DIVIDER_SYMBOL + " \uE0E3 Move Down " + ult::DIVIDER_SYMBOL + " \uE0E2 Move Up")); @@ -1665,12 +1679,12 @@ public: const std::string section = isMiniMode ? "mini" : "micro"; std::string showValue = ult::parseValueFromIniSection(configIniPath, section, "show"); std::string orderValue = ult::parseValueFromIniSection(configIniPath, section, "element_order"); - + if (showValue.empty()) { showValue = isMiniMode ? "DTC+BAT+CPU+GPU+RAM+TMP+FPS+RES" : "FPS+CPU+GPU+RAM+SOC+BAT+DTC"; } convertToUpper(showValue); - + enabledElements.clear(); ult::StringStream ss(showValue); std::string item; @@ -1679,7 +1693,7 @@ public: enabledElements.insert(item); } } - + elementOrder.clear(); if (!orderValue.empty()) { convertToUpper(orderValue); @@ -1698,19 +1712,19 @@ public: } } } - + static constexpr std::string_view miniElements[] = { "DTC","BAT","CPU","GPU","RAM","MEM","READ","SOC","TMP","FPS","RES" }; - + static constexpr std::string_view microElements[] = { "FPS","CPU","GPU","RAM","READ","SOC","TMP","RES","BAT","DTC" }; - + // Use span or array reference instead of pointer const auto* allElements = isMiniMode ? miniElements : microElements; const size_t allElementsSize = isMiniMode ? std::size(miniElements) : std::size(microElements); - + elementOrder.clear(); if (!orderValue.empty()) { convertToUpper(orderValue); @@ -1730,14 +1744,14 @@ public: elementOrder.emplace_back(elem); } } - + for (size_t i = 0; i < elementOrder.size(); i++) { const std::string& element = elementOrder[i]; const bool isEnabled = enabledElements.find(element) != enabledElements.end(); - + auto* elementItem = new tsl::elm::ListItem(element); elementItem->setValue(isEnabled ? ult::ON : ult::OFF, !isEnabled); - + elementItem->setClickListener([this, elementItem, element](uint64_t keys) { static bool hasNotTriggeredAnimation = false; @@ -1748,19 +1762,19 @@ public: if (keys & KEY_A) { const bool currentlyEnabled = enabledElements.find(element) != enabledElements.end(); - + if (currentlyEnabled) { enabledElements.erase(element); } else { enabledElements.insert(element); } - + updateShowAndOrder(); jumpItemName = element; jumpItemValue = ""; jumpItemExactMatch = true; hasNotTriggeredAnimation = true; - + tsl::swapTo(SwapDepth(1), modeName); return true; } @@ -1772,7 +1786,7 @@ public: break; } } - + if (keys & KEY_X) { if (currentPos > 0) { std::swap(elementOrder[currentPos], elementOrder[currentPos - 1]); @@ -1798,18 +1812,18 @@ public: triggerRumbleClick.store(true, std::memory_order_release); triggerMoveSound.store(true, std::memory_order_release); } - + updateShowAndOrder(); jumpItemName = element; jumpItemValue = ""; jumpItemExactMatch = true; - + tsl::swapTo(SwapDepth(1), modeName); return true; } return false; }); - + list->addItem(elementItem); } @@ -1819,12 +1833,12 @@ public: jumpItemValue = ""; jumpItemExactMatch = false; } - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", "Configuration"); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -1834,21 +1848,21 @@ public: } return false; } - + private: void updateShowAndOrder() { std::string newShowValue; std::string newOrderValue; bool showFirst = true; bool orderFirst = true; - + for (const std::string& element : elementOrder) { if (!orderFirst) { newOrderValue += "+"; } newOrderValue += element; orderFirst = false; - + if (enabledElements.find(element) != enabledElements.end()) { if (!showFirst) { newShowValue += "+"; @@ -1857,7 +1871,7 @@ private: showFirst = false; } } - + const std::string section = isMiniMode ? "mini" : "micro"; ult::setIniFileValue(configIniPath, section, "show", newShowValue); ult::setIniFileValue(configIniPath, section, "element_order", newOrderValue); @@ -1874,7 +1888,7 @@ private: bool isGameResolutionsMode; bool isFPSCounterMode; bool isFPSGraphMode; - + public: ConfiguratorOverlay(const std::string& mode) : modeName(mode) { isMiniMode = (mode == "Mini"); @@ -1884,11 +1898,11 @@ public: isFPSCounterMode = (mode == "FPS Counter"); isFPSGraphMode = (mode == "FPS Graph"); } - + virtual tsl::elm::Element* createUI() override { auto* list = new tsl::elm::List(); list->addItem(new tsl::elm::CategoryHeader("Configuration")); - + // 5. Elements (Mini/Micro only) if (isMiniMode || isMicroMode) { auto* showSettings = new tsl::elm::ListItem("Elements"); @@ -1930,8 +1944,8 @@ public: }); list->addItem(colors); //} - - + + // 4. Font Sizes (Mini/Micro/FPS Counter only) if (isMiniMode || isMicroMode || isFPSCounterMode) { auto* fontSizes = new tsl::elm::ListItem("Font Sizes"); @@ -1945,7 +1959,7 @@ public: }); list->addItem(fontSizes); } - + // 1. Refresh Rate (all modes) auto* refreshRate = new tsl::elm::ListItem("Refresh Rate"); refreshRate->setValue(std::to_string(getCurrentRefreshRate()) + " Hz"); @@ -1957,7 +1971,7 @@ public: return false; }); list->addItem(refreshRate); - + // 6. DTC Format (Mini/Micro only) - NEW ADDITION if (isMiniMode || isMicroMode) { auto* dtcFormat = new tsl::elm::ListItem("DTC Format"); @@ -1985,7 +1999,7 @@ public: }); list->addItem(framePadding); } - + // 7. Mode-specific positioning settings if (isMicroMode) { // Text Alignment for Micro @@ -2013,7 +2027,7 @@ public: return false; }); list->addItem(layerPos); - + } else if (isFullMode) { // Horizontal Position for Full (Left/Right only) auto* layerPos = new tsl::elm::ListItem("Horizontal Position"); @@ -2027,7 +2041,7 @@ public: return false; }); list->addItem(layerPos); - + //} else if (isGameResolutionsMode || isFPSCounterMode || isFPSGraphMode) { // // Both horizontal and vertical positioning // auto* layerPosH = new tsl::elm::ListItem("Horizontal Position"); @@ -2041,7 +2055,7 @@ public: // return false; // }); // list->addItem(layerPosH); - // + // // auto* layerPosV = new tsl::elm::ListItem("Vertical Position"); // layerPosV->setValue(getCurrentLayerPosBottom()); // layerPosV->setClickListener([this, layerPosV](uint64_t keys) { @@ -2054,19 +2068,19 @@ public: // }); // list->addItem(layerPosV); } - + list->jumpToItem(jumpItemName, jumpItemValue, jumpItemExactMatch.load(std::memory_order_acquire)); { jumpItemName = ""; jumpItemValue = ""; jumpItemExactMatch = false; } - + tsl::elm::OverlayFrame* rootFrame = new tsl::elm::OverlayFrame("Horizon OC Monitor", modeName); rootFrame->setContent(list); return rootFrame; } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysDown & KEY_B) { triggerRumbleDoubleClick.store(true, std::memory_order_release); @@ -2077,7 +2091,7 @@ public: } return false; } - + private: int getCurrentRefreshRate() { std::string section; @@ -2087,42 +2101,42 @@ private: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + std::string value = ult::parseValueFromIniSection(configIniPath, section, "refresh_rate"); int defaultRate = (isGameResolutionsMode) ? 10 : ((isFPSCounterMode || isFPSGraphMode) ? 30 : 1); return value.empty() ? defaultRate : atoi(value.c_str()); } - + // NEW METHOD: Get current DTC format for display std::string getCurrentDTCFormat() { if (isMiniMode || isMicroMode) { const std::string section = isMiniMode ? "mini" : "micro"; std::string value = ult::parseValueFromIniSection(configIniPath, section, "dtc_format"); - + // Handle defaults properly if (value.empty()) { value = isMiniMode ? "%m-%d-%Y"+ult::DIVIDER_SYMBOL+"%H:%M:%S" : "%H:%M:%S"; } - + // Convert format string to display name return getDTCFormatName(value); } return ""; } - + // Helper function to convert format string to display name std::string getDTCFormatName(const std::string& formatStr) { - + for (const auto& format : dtcFormats) { if (format.second == formatStr) { return format.first; } } - + // Return the format string itself if no match found return formatStr; } - + int getCurrentFramePadding() { if (isMiniMode) { std::string value = ult::parseValueFromIniSection(configIniPath, "mini", "frame_padding"); @@ -2141,17 +2155,17 @@ private: } return ""; } - + std::string getCurrentLayerPosRight() { std::string section; if (isFullMode) section = "full"; else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + std::string value = ult::parseValueFromIniSection(configIniPath, section, "layer_width_align"); convertToUpper(value); - + if (isFullMode) { // Full mode: only Left and Right allowed if (value == "RIGHT") return "Right"; @@ -2170,10 +2184,10 @@ private: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + std::string value = ult::parseValueFromIniSection(configIniPath, section, "layer_height_align"); convertToUpper(value); - + if (isMicroMode) { // Micro mode: only Top and Bottom allowed if (value == "BOTTOM") return "Bottom"; @@ -2185,7 +2199,7 @@ private: return "Top"; } } - + std::string cycleTextAlign() { if (isMicroMode) { const std::string current = getCurrentTextAlign(); @@ -2194,23 +2208,23 @@ private: else if (current == "Center") next = "Right"; else if (current == "Right") next = "Left"; else next = "Center"; - + ult::setIniFileValue(configIniPath, "micro", "text_align", next); return next; } return ""; } - + std::string cycleLayerPosRight() { std::string section; if (isFullMode) section = "full"; else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + const std::string current = getCurrentLayerPosRight(); std::string next; - + if (isFullMode) { // Full mode: only Left and Right if (current == "Left") next = "Right"; @@ -2221,7 +2235,7 @@ private: else if (current == "Center") next = "Right"; else next = "Left"; } - + const std::string value = (next == "Right") ? "right" : (next == "Center" ? "center" : "left"); ult::setIniFileValue(configIniPath, section, "layer_width_align", value); return next; @@ -2233,10 +2247,10 @@ private: else if (isGameResolutionsMode) section = "game_resolutions"; else if (isFPSCounterMode) section = "fps-counter"; else if (isFPSGraphMode) section = "fps-graph"; - + const std::string current = getCurrentLayerPosBottom(); std::string next; - + if (isMicroMode) { // Micro mode: only Top and Bottom if (current == "Top") next = "Bottom"; @@ -2247,7 +2261,7 @@ private: else if (current == "Center") next = "Bottom"; else next = "Top"; } - + const std::string value = (next == "Bottom") ? "bottom" : (next == "Center" ? "center" : "top"); ult::setIniFileValue(configIniPath, section, "layer_height_align", value); return next; diff --git a/Source/Horizon-OC-Monitor/source/modes/Micro.hpp b/Source/Horizon-OC-Monitor/source/modes/Micro.hpp index 0a805643..6951703c 100644 --- a/Source/Horizon-OC-Monitor/source/modes/Micro.hpp +++ b/Source/Horizon-OC-Monitor/source/modes/Micro.hpp @@ -36,7 +36,7 @@ private: size_t fontsize = 0; bool showFPS = false; uint64_t systemtickfrequency_impl = systemtickfrequency; - + // Pre-compiled render data structures struct RenderItem { uint8_t type; @@ -45,7 +45,7 @@ private: const char* volt_ptr; bool has_voltage; }; - + // Resolution tracking resolutionCalls m_resolutionRenderCalls[8] = {0}; resolutionCalls m_resolutionViewportCalls[8] = {0}; @@ -62,7 +62,7 @@ private: bool skipOnce = true; bool runOnce = true; - + // Fixed spacing system - calculate actual widths at render time struct LayoutMetrics { uint32_t label_data_gap = 8; // Fixed gap between label and data @@ -72,20 +72,20 @@ private: uint32_t side_margin = 3; // Margins on left and right bool calculated = false; } layout; - + // Lookup table for difference symbols static constexpr const char* diffSymbols[4] = {"△", "@", "▽", "≠"}; - + inline const char* getDifferenceSymbol(int32_t delta) { if (delta > 20000) return diffSymbols[0]; // △ if (delta > -20000) return diffSymbols[1]; // @ if (delta < -50000) return diffSymbols[3]; // ≠ return diffSymbols[2]; // ▽ } - + void calculateLayoutMetrics(tsl::gfx::Renderer *renderer) { if (layout.calculated) return; - + // Use font size to determine appropriate spacing if (fontsize <= 16) { layout.label_data_gap = 6; @@ -103,12 +103,16 @@ private: layout.volt_data_gap = 0; layout.item_spacing = 20; } - + layout.calculated = true; } public: - MicroOverlay() { + MicroOverlay() { + CPU_temp_c[0] = '\0'; + GPU_temp_c[0] = '\0'; + RAM_temp_c[0] = '\0'; + tsl::hlp::requestForeground(false); disableJumpTo = true; //tsl::initializeUltrahandSettings(); @@ -135,7 +139,7 @@ public: //alphabackground = 0x0; deactivateOriginalFooter = true; StartThreads(); - + // Pre-allocate render items vector //renderItems.reserve(8); realVoltsPolling = settings.realVolts; @@ -144,15 +148,15 @@ public: if (R_SUCCEEDED(psmCheck) && R_SUCCEEDED(i2cCheck)) { uint16_t data = 0; float tempA = 0.0; - + // Get initial power consumption Max17050ReadReg(MAX17050_AvgCurrent, &data); tempA = (1.5625 / (max17050SenseResistor * max17050CGain)) * (s16)data; PowerConsumption = tempA * batVoltageAvg / 1000000.0; // Rough initial estimate - + // Get initial battery info psmGetBatteryChargeInfoFields(psmService, &_batteryChargeInfoFields); - + // Get initial time estimate if (tempA >= 0) { batTimeEstimate = -1; @@ -171,18 +175,18 @@ public: batTimeEstimate = -1; _batteryChargeInfoFields = {0}; } - + // Now format the initial Battery_c string char remainingBatteryLife[8]; const float drawW = (fabsf(PowerConsumption) < 0.01f) ? 0.0f : PowerConsumption; - + if (batTimeEstimate >= 0 && !(drawW <= 0.01f && drawW >= -0.01f)) { snprintf(remainingBatteryLife, sizeof(remainingBatteryLife), "%d:%02d", batTimeEstimate / 60, batTimeEstimate % 60); } else { strcpy(remainingBatteryLife, "--:--"); } - + if (!settings.invertBatteryDisplay) { snprintf(Battery_c, sizeof(Battery_c), "%.2f W%.1f%% [%s]", @@ -197,39 +201,39 @@ public: drawW); } - + } - + ~MicroOverlay() { CloseThreads(); fixForeground = true; FullMode = true; } - + // Fast parsing and render item preparation void prepareRenderItems() { if (!renderDataDirty) return; - + renderItems.clear(); - + // Fast manual parsing of settings.show const std::string& show = settings.show; size_t start = 0, end = 0; uint8_t seen_flags = 0; - + static size_t len; static uint32_t key3; while (start < show.length()) { end = show.find('+', start); if (end == std::string::npos) end = show.length(); - + len = end - start; if (len >= 3) { const char* key = &show[start]; - + // Use first 3 chars for fast comparison key3 = (key[0] << 16) | (key[1] << 8) | key[2]; - + switch (key3) { case 0x435055: // "CPU" if (!(seen_flags & 1)) { @@ -286,7 +290,7 @@ public: seen_flags |= 512; } break; - case 0x445443: // "DTC" + case 0x445443: // "DTC" if (!(seen_flags & 256) && settings.showDTC) { renderItems.push_back({8, settings.useDTCSymbol ? "\uE007" : "DTC", DTC_c, nullptr, false}); seen_flags |= 256; @@ -296,38 +300,38 @@ public: } start = end + 1; } - + renderDataDirty = false; } - + virtual tsl::elm::Element* createUI() override { auto* Status = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) { cachedMargin = renderer->getTextDimensions("CPUGPURAMSOCBAT[]", false, fontsize).second; if (!Initialized) { //cachedMargin = renderer->drawString(" ", false, 0, 0, fontsize, renderer->a(0x0000)).first; - + catColorA = settings.catColor; textColorA = settings.textColor; - base_y = settings.setPosBottom ? + base_y = settings.setPosBottom ? tsl::cfg::FramebufferHeight - (fontsize + (fontsize / 4)) +1: 0; Initialized = true; renderDataDirty = true; layout.calculated = false; // Force recalculation tsl::hlp::requestForeground(false); } - + //renderer->drawRect(0, 0, tsl::cfg::FramebufferWidth, cachedMargin + 4, a(settings.backgroundColor)); renderer->drawRect(0, settings.setPosBottom ? base_y-1 : 0, tsl::cfg::FramebufferWidth, cachedMargin + 4, a(settings.backgroundColor)); // Prepare render items if settings changed prepareRenderItems(); calculateLayoutMetrics(renderer); - + // Separate battery from other items std::vector main_items; //RenderItem* battery_item = nullptr; - + for (auto& item : renderItems) { //if (item.type == 6) { // BAT // battery_item = &item; @@ -347,7 +351,7 @@ public: main_items.push_back(item); } } - + // Calculate actual widths for all main items struct ItemLayout { uint32_t label_width; @@ -355,7 +359,7 @@ public: uint32_t volt_width; uint32_t total_width; }; - + std::vector item_layouts; uint32_t total_main_width = 0; @@ -364,81 +368,81 @@ public: static ItemLayout item_layout; for (const auto& item : main_items) { item_layout = {}; - + // Calculate actual label width //auto label_dim = renderer->drawString(item.label, false, 0, 0, fontsize, renderer->a(0x0000)); //auto label_dim = renderer->getTextDimensions(item.label, fontsize); item_layout.label_width = renderer->getTextDimensions(item.label, false, fontsize).first; - + // Calculate actual data width //auto data_dim = renderer->drawString(item.data_ptr, false, 0, 0, fontsize, renderer->a(0x0000)); //auto data_dim = renderer->getTextDimensions(item.data_ptr, fontsize); item_layout.data_width = renderer->getTextDimensions(item.data_ptr, false, fontsize).first; - + // Calculate voltage width if present if (item.has_voltage && item.volt_ptr) { //uto volt_dim = renderer->drawString(item.volt_ptr, false, 0, 0, fontsize, renderer->a(0x0000)); //auto volt_dim = renderer->getTextDimensions(item.volt_ptr, fontsize); item_layout.volt_width = renderer->getTextDimensions(item.volt_ptr, false, fontsize).first; - + // Total: label + gap + data + gap + "|" + gap + voltage //auto sep_width = renderer->drawString("", false, 0, 0, fontsize, renderer->a(0x0000)); - item_layout.total_width = item_layout.label_width + layout.label_data_gap + - item_layout.data_width + layout.volt_separator_gap + + item_layout.total_width = item_layout.label_width + layout.label_data_gap + + item_layout.data_width + layout.volt_separator_gap + sep_width + layout.volt_data_gap + item_layout.volt_width; } else { // Total: label + gap + data item_layout.total_width = item_layout.label_width + layout.label_data_gap + item_layout.data_width; } - + item_layouts.push_back(item_layout); total_main_width += item_layout.total_width; } - - + + // Determine if we have battery and handle it as the rightmost item std::vector all_items_ordered; std::vector all_layouts_ordered; - + // Add main items first for (size_t i = 0; i < main_items.size(); i++) { all_items_ordered.push_back(main_items[i]); all_layouts_ordered.push_back(item_layouts[i]); } - + // Add battery as the last item if present //if (battery_item) { // //auto bat_label_dim = renderer->drawString("BAT", false, 0, 0, fontsize, renderer->a(0x0000)); // //auto bat_label_dim = renderer->getTextDimensions("BAT", fontsize); // //auto bat_data_dim = renderer->drawString(battery_item->data_ptr, false, 0, 0, fontsize, renderer->a(0x0000)); // //auto bat_data_dim = renderer->getTextDimensions(battery_item->data_ptr, fontsize); - // + // // ItemLayout battery_layout = {}; // battery_layout.label_width = renderer->getTextDimensions("BAT", false, fontsize).first; // battery_layout.data_width = renderer->getTextDimensions(battery_item->data_ptr, false, fontsize).first; // battery_layout.volt_width = 0; // battery_layout.total_width = battery_layout.label_width + layout.label_data_gap + battery_layout.data_width; - // + // // all_items_ordered.push_back(*battery_item); // all_layouts_ordered.push_back(battery_layout); //} - + // Calculate total width of all items uint32_t total_all_width = 0; for (const auto& item_layout : all_layouts_ordered) { total_all_width += item_layout.total_width; } - + // Calculate available space for distribution //uint32_t available_width = tsl::cfg::FramebufferWidth - (2 * layout.side_margin); //uint32_t remaining_space = available_width - total_all_width; - + // Calculate positions based on alignment mode std::vector item_positions; const size_t N = all_items_ordered.size(); if (N == 0) return; - + if (N == 1) { // Single item positioning based on alignment if (settings.alignTo == 2) { // RIGHT @@ -452,43 +456,43 @@ public: for (const auto& layout : all_layouts_ordered) { total_widths += layout.total_width; } - + if (settings.alignTo == 0) { // LEFT alignment // All items except last positioned from left with small gaps // Last item (battery if present) positioned at far right const uint32_t small_gap = layout.item_spacing; - + // Position items from left uint32_t current_x = layout.side_margin; for (size_t i = 0; i < N - 1; ++i) { item_positions.push_back(current_x); current_x += all_layouts_ordered[i].total_width + small_gap; } - + // Position last item at far right const uint32_t last_width = all_layouts_ordered[N-1].total_width; item_positions.push_back(tsl::cfg::FramebufferWidth - layout.side_margin - last_width); - + } else if (settings.alignTo == 2) { // RIGHT alignment // First item at far left, remaining items packed at right const uint32_t small_gap = layout.item_spacing; - + // Resize vector to hold all positions item_positions.resize(N); - + // Position first item at far left item_positions[0] = layout.side_margin; - + // Calculate total width of items 1 to N-1 plus gaps between them uint32_t right_group_width = 0; for (size_t i = 1; i < N; ++i) { right_group_width += all_layouts_ordered[i].total_width; if (i < N - 1) right_group_width += small_gap; // Gap after each item except the last } - + // Start positioning from right margin minus total width of right group uint32_t current_x = tsl::cfg::FramebufferWidth - layout.side_margin - right_group_width; - + // Position items 1 to N-1 sequentially from left to right within the right group for (size_t i = 1; i < N; ++i) { item_positions[i] = current_x; @@ -497,10 +501,10 @@ public: } else { // CENTER alignment (default behavior) // Total available width for spacing = framebuffer width minus total item widths minus margins const int32_t total_spacing = (int32_t)tsl::cfg::FramebufferWidth - (2 * (int32_t)layout.side_margin) - (int32_t)total_widths; - + // Number of gaps between items is N-1 const uint32_t gap = total_spacing > 0 ? (uint32_t)(total_spacing / (N - 1)) : 0; - + // Position first item flush left item_positions.push_back(layout.side_margin); static uint32_t prev_pos, prev_width; @@ -510,11 +514,11 @@ public: prev_width = all_layouts_ordered[i - 1].total_width; item_positions.push_back(prev_pos + prev_width + gap); } - + // Fix any rounding error for center alignment const int32_t last_item_end = item_positions.back() + all_layouts_ordered.back().total_width; const int32_t overflow = (int32_t)tsl::cfg::FramebufferWidth - layout.side_margin - last_item_end; - + if (overflow != 0) { for (size_t i = 1; i < item_positions.size(); ++i) { item_positions[i] += overflow; @@ -532,11 +536,11 @@ public: const auto& item = all_items_ordered[i]; const auto& item_layout = all_layouts_ordered[i]; current_x = item_positions[i]; - + // Draw label renderer->drawString(item.label, false, current_x, base_y + cachedMargin, fontsize, catColorA); current_x += item_layout.label_width + layout.label_data_gap; - + // Draw data //renderer->drawString(item.data_ptr, false, current_x, base_y + fontsize, fontsize, textColorA); @@ -552,22 +556,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realCPU_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + uint32_t renderX = current_x; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); renderX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); - + if (!postTempPart.empty()) { renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -581,7 +585,7 @@ public: } else { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 1) { // GPU std::string dataStr(item.data_ptr); const size_t degreesPos = dataStr.find("°"); @@ -592,22 +596,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realGPU_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + uint32_t renderX = current_x; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); renderX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); - + if (!postTempPart.empty()) { renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -621,7 +625,7 @@ public: } else { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 2) { // RAM std::string dataStr(item.data_ptr); const size_t degreesPos = dataStr.find("°"); @@ -632,22 +636,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realRAM_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + uint32_t renderX = current_x; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); renderX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); - + if (!postTempPart.empty()) { renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -661,7 +665,7 @@ public: } else { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 3) { // SOC temperature // Parse SOC temperature: "XX°C (XX%)" std::string dataStr(item.data_ptr); @@ -670,18 +674,18 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; // Include the 'C' - + // Extract temperature value and apply gradient const int temp = atoi(item.data_ptr); const tsl::Color tempColor = tsl::GradientColor((float)temp); - + // Split into temperature part and remaining part const std::string tempPart = dataStr.substr(0, tempEnd); const std::string restPart = dataStr.substr(tempEnd); - + // Render temperature with gradient color renderer->drawString(tempPart, false, current_x, base_y + cachedMargin, fontsize, tempColor); - + // Render remaining text with normal color if (!restPart.empty()) { const uint32_t tempPartWidth = renderer->getTextDimensions(tempPart, false, fontsize).first; @@ -695,14 +699,14 @@ public: // Fallback: render normally renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else if (item.type == 4) { // TMP multiple temperatures // Parse TMP temperatures: "XX°C XX°C XX°C (XX%)" std::string dataStr(item.data_ptr); uint32_t renderX = current_x; size_t pos = 0; bool parseSuccess = true; - + // Parse up to 3 temperatures for (int tempCount = 0; tempCount < 3 && parseSuccess && pos < dataStr.length(); tempCount++) { // Skip any leading spaces @@ -711,47 +715,47 @@ public: renderX += renderer->getTextDimensions(" ", false, fontsize).first; pos++; } - + if (pos >= dataStr.length()) break; - + // Find degrees symbol const size_t degreesPos = dataStr.find("°", pos); if (degreesPos == std::string::npos) { parseSuccess = false; break; } - + // Find 'C' after degrees symbol const size_t cPos = dataStr.find("C", degreesPos); if (cPos == std::string::npos) { parseSuccess = false; break; } - + const size_t tempEnd = cPos + 1; // Include the 'C' - + // Extract and render temperature with gradient const std::string tempPart = dataStr.substr(pos, tempEnd - pos); const int temp = atoi(tempPart.c_str()); const tsl::Color tempColor = tsl::GradientColor((float)temp); - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, tempColor, a(settings.separatorColor)); renderX += renderer->getTextDimensions(tempPart, false, fontsize).first; - + pos = tempEnd; } - + // Render any remaining text (like " (50%)") if (pos < dataStr.length()) { const std::string restPart = dataStr.substr(pos); renderer->drawStringWithColoredSections(restPart, false, specialChars, renderX, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + // If parsing failed, fall back to normal rendering if (!parseSuccess) { renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } - + } else { // Normal rendering for all other item types renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); @@ -761,7 +765,7 @@ public: renderer->drawStringWithColoredSections(item.data_ptr, false, specialChars, current_x, base_y + cachedMargin, fontsize, textColorA, a(settings.separatorColor)); } current_x += item_layout.data_width; - + // Draw voltage if present if (item.has_voltage && item.volt_ptr) { current_x += layout.volt_separator_gap; @@ -773,7 +777,7 @@ public: } } }); - + tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", ""); rootFrame->setContent(Status); return rootFrame; @@ -827,7 +831,7 @@ public: // CPU usage calculations - optimized with fewer conditionals const double inv_freq = 1.0 / systemtickfrequency_impl; - + // Capture systemtickfrequency_impl and inv_freq safely const auto formatUsage = [this](char* buf, size_t size, uint64_t idletick, double inv_freq) { if (idletick > systemtickfrequency_impl) { @@ -836,60 +840,60 @@ public: snprintf(buf, size, "%.0f%%", (1.0 - (idletick * inv_freq)) * 100.0); } }; - + // Atomically load idle ticks before using them const uint64_t idle0 = idletick0.load(std::memory_order_acquire); const uint64_t idle1 = idletick1.load(std::memory_order_acquire); const uint64_t idle2 = idletick2.load(std::memory_order_acquire); const uint64_t idle3 = idletick3.load(std::memory_order_acquire); - + formatUsage(CPU_Usage0, sizeof(CPU_Usage0), idle0, inv_freq); formatUsage(CPU_Usage1, sizeof(CPU_Usage1), idle1, inv_freq); formatUsage(CPU_Usage2, sizeof(CPU_Usage2), idle2, inv_freq); formatUsage(CPU_Usage3, sizeof(CPU_Usage3), idle3, inv_freq); mutexLock(&mutex_Misc); - + // CPU frequency and voltage const char* cpuDiff = "@"; if (realCPU_Hz) { const int32_t deltaCPU = (int32_t)(realCPU_Hz / 1000) - (CPU_Hz / 1000); cpuDiff = getDifferenceSymbol(deltaCPU); } - + const uint32_t cpuFreq = settings.realFrequencies && realCPU_Hz ? realCPU_Hz : CPU_Hz; - + if (settings.showFullCPU) { - snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), - "[%s,%s,%s,%s]%s%u.%u", - CPU_Usage0, CPU_Usage1, CPU_Usage2, CPU_Usage3, + snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), + "[%s,%s,%s,%s]%s%u.%u", + CPU_Usage0, CPU_Usage1, CPU_Usage2, CPU_Usage3, cpuDiff, cpuFreq / 1000000, (cpuFreq / 100000) % 10); } else { // Find max CPU usage across all cores const auto extractUsage = [](const char* usage_str) -> double { return strtod(usage_str, nullptr); }; - + const double usage0 = extractUsage(CPU_Usage0); const double usage1 = extractUsage(CPU_Usage1); const double usage2 = extractUsage(CPU_Usage2); const double usage3 = extractUsage(CPU_Usage3); - + const double maxUsage = std::max({usage0, usage1, usage2, usage3}); - - snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), - "%.0f%%%s%u.%u", + + snprintf(CPU_compressed_c, sizeof(CPU_compressed_c), + "%.0f%%%s%u.%u", maxUsage, cpuDiff, cpuFreq / 1000000, (cpuFreq / 100000) % 10); } - - if (settings.realTemps && realCPU_Temp != 0) { + + if (settings.realTemps && realCPU_Temp != 0 && CPU_temp_c[0] != '\0') { char temp_buffer[48]; snprintf(temp_buffer, sizeof(temp_buffer), " %s", CPU_temp_c); strncat(CPU_compressed_c, temp_buffer, sizeof(CPU_compressed_c) - strlen(CPU_compressed_c) - 1); } - + //if (settings.realVolts) { - // snprintf(CPU_volt_c, sizeof(CPU_volt_c), "%u.%u mV", + // snprintf(CPU_volt_c, sizeof(CPU_volt_c), "%u.%u mV", // realCPU_mV/1000, (isMariko ? (realCPU_mV/100)%10 : (realCPU_mV/10)%100)); //} @@ -898,28 +902,28 @@ public: const uint32_t mv = realCPU_mV / 1000; // µV → mV snprintf(CPU_volt_c, sizeof(CPU_volt_c), "%u mV", mv); } - + // GPU frequency and voltage const char* gpuDiff = "@"; if (realGPU_Hz) { const int32_t deltaGPU = (int32_t)(realGPU_Hz / 1000) - (GPU_Hz / 1000); gpuDiff = getDifferenceSymbol(deltaGPU); } - + const uint32_t gpuFreq = settings.realFrequencies && realGPU_Hz ? realGPU_Hz : GPU_Hz; snprintf(GPU_Load_c, sizeof(GPU_Load_c), "%u%%%s%u.%u", GPU_Load_u / 10, gpuDiff, gpuFreq / 1000000, (gpuFreq / 100000) % 10); - - if (settings.realTemps && realGPU_Temp != 0) { + + if (settings.realTemps && realGPU_Temp != 0 && GPU_temp_c[0] != '\0') { char temp_buffer[48]; snprintf(temp_buffer, sizeof(temp_buffer), " %s", GPU_temp_c); strncat(GPU_Load_c, temp_buffer, sizeof(GPU_Load_c) - strlen(GPU_Load_c) - 1); } - + //if (settings.realVolts) { - // snprintf(GPU_volt_c, sizeof(GPU_volt_c), "%u.%u mV", + // snprintf(GPU_volt_c, sizeof(GPU_volt_c), "%u.%u mV", // realGPU_mV/1000, (isMariko ? (realGPU_mV/100)%10 : (realGPU_mV/10)%100)); //} @@ -930,20 +934,20 @@ public: if (GPU_Hz_int == 0 && lastGPU_Hz_int != 0) { isRendering = false; leventSignal(&renderingStopEvent); - + triggerExitNow = true; return; } lastGPU_Hz_int = GPU_Hz_int; } - + /* ── GPU voltage ───────────────────────────── */ if (settings.realVolts) { const uint32_t mv = realGPU_mV / 1000; snprintf(GPU_volt_c, sizeof(GPU_volt_c), "%u mV", mv); } - + // RAM usage and frequency char MICRO_RAM_all_c[16]; if (!settings.showpartLoad) { @@ -971,23 +975,23 @@ public: const int32_t deltaRAM = (int32_t)(realRAM_Hz / 1000) - (RAM_Hz / 1000); ramDiff = getDifferenceSymbol(deltaRAM); } - + const uint32_t ramFreq = settings.realFrequencies && realRAM_Hz ? realRAM_Hz : RAM_Hz; - snprintf(RAM_var_compressed_c, sizeof(RAM_var_compressed_c), - "%s%s%u.%u", MICRO_RAM_all_c, ramDiff, + snprintf(RAM_var_compressed_c, sizeof(RAM_var_compressed_c), + "%s%s%u.%u", MICRO_RAM_all_c, ramDiff, ramFreq / 1000000, (ramFreq / 100000) % 10); - - if (settings.realTemps && realRAM_Temp != 0) { + + if (settings.realTemps && realRAM_Temp != 0 && RAM_temp_c[0] != '\0') { char temp_buffer[48]; snprintf(temp_buffer, sizeof(temp_buffer), " %s", RAM_temp_c); strncat(RAM_var_compressed_c, temp_buffer, sizeof(RAM_var_compressed_c) - strlen(RAM_var_compressed_c) - 1); } - + //if (settings.realVolts) { // uint32_t vdd2 = realRAM_mV / 10000; // uint32_t vddq = realRAM_mV % 10000; // if (isMariko) { - // snprintf(RAM_volt_c, sizeof(RAM_volt_c), "%u.%u%u.%u mV", + // snprintf(RAM_volt_c, sizeof(RAM_volt_c), "%u.%u%u.%u mV", // vdd2/10, vdd2%10, vddq/10, vddq%10); // } else { // snprintf(RAM_volt_c, sizeof(RAM_volt_c), "%u.%u mV", vdd2/10, vdd2%10); @@ -997,15 +1001,15 @@ public: /* ── RAM voltage ───────────────────────────── */ if (settings.realVolts && (settings.showVDD2 || settings.showVDDQ)) { /* realRAM_mV packs VDD2 | VDDQ in 10-µV units * - * → split, convert to mV + * → split, convert to mV */ const float mv_vdd2 = (realRAM_mV % 100000) / 10.0f; // VDD2 const uint32_t mv_vddq = (realRAM_mV / 10000) / 10; // VDDQ - + // Build voltage string based on settings RAM_volt_c[0] = '\0'; // Start with empty string char temp_buffer[16]; - + if (settings.showVDD2) { if (settings.decimalVDD2) { snprintf(temp_buffer, sizeof(temp_buffer), "%.1f mV", mv_vdd2); @@ -1014,7 +1018,7 @@ public: } strcat(RAM_volt_c, temp_buffer); } - + if (settings.showVDDQ && isMariko) { if (RAM_volt_c[0] != '\0') { strcat(RAM_volt_c, ""); @@ -1025,7 +1029,7 @@ public: } else { RAM_volt_c[0] = '\0'; // Empty if voltages disabled } - + /* ── Battery / power draw ───────────────────────────── */ char remainingBatteryLife[8]; @@ -1075,7 +1079,7 @@ public: "%d°C %d%%", (int)SOC_temperatureF, // SoC °C, no decimals duty); // fan % - + /* Integer SOC, PCB and skin temperatures + duty * * skin_temperaturemiliC is in milli-degrees C → divide by 1000 */ snprintf(skin_temperature_c, sizeof skin_temperature_c, @@ -1084,9 +1088,9 @@ public: (int)PCB_temperatureF, // PCB (uint16_t)(skin_temperaturemiliC / 1000), // skin duty); - + //if (settings.realVolts) { - // snprintf(SOC_volt_c, sizeof(SOC_volt_c), "%u.%u mV", + // snprintf(SOC_volt_c, sizeof(SOC_volt_c), "%u.%u mV", // realSOC_mV/1000, (realSOC_mV/100)%10); //} @@ -1097,18 +1101,30 @@ public: } else { SOC_volt_c[0] = '\0'; // Clear the buffer when disabled } - + if (settings.realTemps) { - if (realCPU_Temp != 0) { - snprintf(CPU_temp_c, sizeof(CPU_temp_c), " %.1f°C", realCPU_Temp / 1000.0f); - } - if (realGPU_Temp != 0) { - snprintf(GPU_temp_c, sizeof(GPU_temp_c), " %.1f°C", realGPU_Temp / 1000.0f); - } - if (realRAM_Temp != 0) { - snprintf(RAM_temp_c, sizeof(RAM_temp_c), " %.1f°C", realRAM_Temp / 1000.0f); - } -} + if (realCPU_Temp != 0) { + if (settings.realTempsDec) { + snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%.1f°C", realCPU_Temp / 1000.0f); + } else { + snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%u°C", static_cast(realCPU_Temp / 1000.0)); + } + } + if (realGPU_Temp != 0) { + if (settings.realTempsDec) { + snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%.1f°C", realGPU_Temp / 1000.0f); + } else { + snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%u°C", static_cast(realGPU_Temp / 1000.0)); + } + } + if (realRAM_Temp != 0) { + if (settings.realTempsDec) { + snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%.1f°C", realRAM_Temp / 1000.0f); + } else { + snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%u°C", static_cast(realRAM_Temp / 1000.0)); + } + } + } // Resolution processing //char RES_var_compressed_c[32] = ""; @@ -1176,20 +1192,20 @@ public: m_resolutionOutput[out_iter].width = m_resolutionViewportCalls[x].width; m_resolutionOutput[out_iter].height = m_resolutionViewportCalls[x].height; m_resolutionOutput[out_iter].calls = m_resolutionViewportCalls[x].calls; - out_iter++; + out_iter++; } found = false; if (out_iter == 8) break; } } qsort(m_resolutionOutput, 8, sizeof(resolutionCalls), compare); - + // Anti-flicker swap logic static std::pair old_res[2]; - + // Only swap if BOTH resolutions exist (prevent swapping with empty slot) if (m_resolutionOutput[0].width && m_resolutionOutput[1].width) { - if ((m_resolutionOutput[0].width == old_res[1].first && m_resolutionOutput[0].height == old_res[1].second) || + if ((m_resolutionOutput[0].width == old_res[1].first && m_resolutionOutput[0].height == old_res[1].second) || (m_resolutionOutput[1].width == old_res[0].first && m_resolutionOutput[1].height == old_res[0].second)) { const uint16_t swap_width = m_resolutionOutput[0].width; const uint16_t swap_height = m_resolutionOutput[0].height; @@ -1199,31 +1215,31 @@ public: m_resolutionOutput[1].height = swap_height; } } - + // Format resolution string if (m_resolutionOutput[0].width) { if (settings.showFullResolution) { if (!m_resolutionOutput[1].width) { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d", + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d", m_resolutionOutput[0].width, m_resolutionOutput[0].height); } else { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d%dx%d", - m_resolutionOutput[0].width, m_resolutionOutput[0].height, + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dx%d%dx%d", + m_resolutionOutput[0].width, m_resolutionOutput[0].height, m_resolutionOutput[1].width, m_resolutionOutput[1].height); } } else { if (!m_resolutionOutput[1].width) { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp", + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp", m_resolutionOutput[0].height); } else { - snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp%dp", + snprintf(RES_var_compressed_c, sizeof(RES_var_compressed_c), "%dp%dp", m_resolutionOutput[0].height, m_resolutionOutput[1].height); } } } - + // Always store current resolutions for next frame comparison old_res[0] = std::make_pair(m_resolutionOutput[0].width, m_resolutionOutput[0].height); old_res[1] = std::make_pair(m_resolutionOutput[1].width, m_resolutionOutput[1].height); @@ -1253,7 +1269,7 @@ public: mutexUnlock(&mutex_Misc); //static bool skipOnce = true; - + if (!skipOnce) { //static bool runOnce = true; if (runOnce) { @@ -1265,7 +1281,7 @@ public: skipOnce = false; } } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (isKeyComboPressed(keysHeld, keysDown)) { isRendering = false; diff --git a/Source/Horizon-OC-Monitor/source/modes/Mini.hpp b/Source/Horizon-OC-Monitor/source/modes/Mini.hpp index 34938fcb..1064522e 100644 --- a/Source/Horizon-OC-Monitor/source/modes/Mini.hpp +++ b/Source/Horizon-OC-Monitor/source/modes/Mini.hpp @@ -38,7 +38,7 @@ private: Thread touchPollThread; std::atomic touchPollRunning{false}; public: - MiniOverlay() { + MiniOverlay() { tsl::hlp::requestForeground(false); disableJumpTo = true; //tsl::initializeUltrahandSettings(); @@ -68,7 +68,7 @@ public: if (ult::limitedMemory) { tsl::gfx::Renderer::get().setLayerPos(std::max(std::min((int)(frameOffsetX*1.5 + 0.5) - tsl::impl::currentUnderscanPixels.first, 1280-32 - tsl::impl::currentUnderscanPixels.first), 0), 0); } - + FullMode = false; TeslaFPS = settings.refreshRate; systemtickfrequency_impl /= settings.refreshRate; @@ -80,15 +80,15 @@ public: if (R_SUCCEEDED(psmCheck) && R_SUCCEEDED(i2cCheck)) { uint16_t data = 0; float tempA = 0.0; - + // Get initial power consumption Max17050ReadReg(MAX17050_AvgCurrent, &data); tempA = (1.5625 / (max17050SenseResistor * max17050CGain)) * (s16)data; PowerConsumption = tempA * batVoltageAvg / 1000000.0; // Rough initial estimate - + // Get initial battery info psmGetBatteryChargeInfoFields(psmService, &_batteryChargeInfoFields); - + // Get initial time estimate if (tempA >= 0) { batTimeEstimate = -1; @@ -107,18 +107,18 @@ public: batTimeEstimate = -1; _batteryChargeInfoFields = {0}; } - + // Now format the initial Battery_c string char remainingBatteryLife[8]; const float drawW = (fabsf(PowerConsumption) < 0.01f) ? 0.0f : PowerConsumption; - + if (batTimeEstimate >= 0 && !(drawW <= 0.01f && drawW >= -0.01f)) { snprintf(remainingBatteryLife, sizeof(remainingBatteryLife), "%d:%02d", batTimeEstimate / 60, batTimeEstimate % 60); } else { strcpy(remainingBatteryLife, "--:--"); } - + if (!settings.invertBatteryDisplay) { snprintf(Battery_c, sizeof(Battery_c), "%.2f W%.1f%% [%s]", @@ -140,46 +140,46 @@ public: touchPollRunning.store(true, std::memory_order_release); threadCreate(&touchPollThread, [](void* arg) -> void { MiniOverlay* overlay = static_cast(arg); - + // Allow only Player 1 and handheld mode const HidNpadIdType id_list[2] = { HidNpadIdType_No1, HidNpadIdType_Handheld }; - + // Configure HID system to only listen to these IDs hidSetSupportedNpadIdType(id_list, 2); - + // Configure input for up to 2 supported controllers (P1 + Handheld) padConfigureInput(2, HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt); - + // Initialize separate pad states for both controllers PadState pad_p1; PadState pad_handheld; padInitialize(&pad_p1, HidNpadIdType_No1); padInitialize(&pad_handheld, HidNpadIdType_Handheld); - + u64 minusHoldStart = 0; u64 plusHoldStart = 0; static constexpr u64 HOLD_THRESHOLD_NS = 500'000'000ULL; - + HidTouchScreenState state = {0}; bool inputDetected; size_t actualEntryCount; - + while (overlay->touchPollRunning.load(std::memory_order_acquire)) { // Only poll when rendering and not dragging { inputDetected = false; - + // Check touch in bounds if (hidGetTouchScreenStates(&state, 1) && state.count > 0) { const int touchX = state.touches[0].x; const int touchY = state.touches[0].y; - + // Calculate bounds (same logic as handleInput) const uint32_t margin = (overlay->fontsize * 4); const int overlayX = overlay->frameOffsetX; const int overlayY = overlay->frameOffsetY; const int overlayWidth = margin + overlay->rectangleWidth + (overlay->fontsize / 3); - + // Calculate height from Variables string actualEntryCount = 1; for (size_t i = 0; overlay->Variables[i] != '\0'; i++) { @@ -187,34 +187,34 @@ public: actualEntryCount++; } } - const int overlayHeight = ((overlay->fontsize + overlay->settings.spacing) * actualEntryCount) + - (overlay->fontsize / 3) + overlay->settings.spacing + + const int overlayHeight = ((overlay->fontsize + overlay->settings.spacing) * actualEntryCount) + + (overlay->fontsize / 3) + overlay->settings.spacing + overlay->topPadding + overlay->bottomPadding; - + // Add touch padding const int touchPadding = 4; const int touchableX = overlayX - touchPadding; const int touchableY = overlayY - touchPadding; const int touchableWidth = overlayWidth + (touchPadding * 2); const int touchableHeight = overlayHeight + (touchPadding * 2); - + // Check if touch is within bounds if (touchX >= touchableX && touchX <= touchableX + touchableWidth && touchY >= touchableY && touchY <= touchableY + touchableHeight) { inputDetected = true; } } - + // Poll buttons from both controllers padUpdate(&pad_p1); padUpdate(&pad_handheld); //const u64 keysHeld_p1 = padGetButtons(&pad_p1); //const u64 keysHeld_handheld = padGetButtons(&pad_handheld); - + // Combine input from both controllers const u64 keysHeld = padGetButtons(&pad_p1) | padGetButtons(&pad_handheld); const u64 now = armTicksToNs(armGetSystemTick()); - + // Track MINUS hold duration if ((keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK)) { if (minusHoldStart == 0) { @@ -226,7 +226,7 @@ public: overlay->buttonState.minusDragActive.exchange(true, std::memory_order_acq_rel); } } - + // Track PLUS hold duration else if ((keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK)) { if (plusHoldStart == 0) { @@ -244,7 +244,7 @@ public: overlay->buttonState.minusDragActive.exchange(false, std::memory_order_acq_rel); overlay->buttonState.plusDragActive.exchange(false, std::memory_order_acq_rel); } - + // Disable rendering on any input, re-enable when no input static bool resetOnce = true; if (inputDetected) { @@ -269,7 +269,7 @@ public: } lastUnderscanPixels = tsl::impl::currentUnderscanPixels; } - + svcSleepThread(32000000ULL); // 32ms polling } }, this, NULL, 0x1000, 0x2B, -2); @@ -310,15 +310,15 @@ public: static uint32_t cachedHeight = 0; static int cachedBaseX = 0, cachedBaseY = 0; static bool lastGameRunning = false; // Track game state changes - + // Check if we need to recalculate due to content changes - const bool contentChanged = (std::string(Variables) != lastVariables) || + const bool contentChanged = (std::string(Variables) != lastVariables) || (GameRunning != lastGameRunning); - + // Only recalculate if settings changed or content changed if (settings.show != lastShowSetting || !Initialized || contentChanged) { showKeys.clear(); - + // Parse once and cache size_t start = 0, end = 0; const std::string& show = settings.show; @@ -329,20 +329,20 @@ public: if (start < show.length()) { showKeys.emplace_back(show.substr(start)); } - + lastShowSetting = settings.show; lastVariables = Variables; lastGameRunning = GameRunning; needsRecalc = true; } - + // Initial width calculation (only once) if (!Initialized) { - + rectangleWidth = 0; //std::pair dimensions; u32 width; - + for (const auto& key : showKeys) { if (key == "CPU") { //dimensions = renderer->drawString("[100%,100%,100%,100%]@4444.4", false, 0, 0, fontsize, renderer->a(0x0000)); @@ -467,10 +467,10 @@ public: } else if (key == "DTC" && settings.showDTC) { // Calculate width based on the datetime format // Use multiple sample dates to ensure longest possible textual output - + char sampleDateTime[64]; size_t maxWidth = 0; - + // Representative dates that produce long names in most locales constexpr int testDays[][3] = { {2025, 11, 26}, // Wednesday – longest weekday @@ -478,10 +478,10 @@ public: {2025, 12, 31}, // December – another long month {2025, 2, 28}, // February – to cover locales where it's long }; - + struct tm t = {}; for (auto &d : testDays) { - + t.tm_year = d[0] - 1900; t.tm_mon = d[1] - 1; t.tm_mday = d[2]; @@ -489,19 +489,19 @@ public: t.tm_min = 59; t.tm_sec = 59; mktime(&t); // normalize (sets weekday, etc.) - + strftime(sampleDateTime, sizeof(sampleDateTime), settings.dtcFormat.c_str(), &t); const size_t w = renderer->getTextDimensions(std::string(sampleDateTime) + " ", false, fontsize).first; if (w > maxWidth) maxWidth = w; } - + width = maxWidth; - + } else { continue; } - + if (rectangleWidth < width) { rectangleWidth = width; } @@ -509,20 +509,20 @@ public: Initialized = true; needsRecalc = true; } - + // Recalculate layout when needed (including content changes) if (needsRecalc) { // Build label lines array for individual centering labelLines.clear(); entryCount = 0; uint16_t flags = 0; - + bool shouldAdd; std::string labelText; for (const auto& key : showKeys) { shouldAdd = false; labelText = ""; - + if (key == "CPU" && !(flags & 1)) { shouldAdd = true; labelText = "CPU"; @@ -569,19 +569,19 @@ public: labelText = "MEM"; flags |= 1024; } - - + + if (shouldAdd) { labelLines.push_back(labelText); entryCount++; - + //if (settings.realVolts && key != "BAT" && key != "DRAW" && key != "FPS" && key != "RES") { // labelLines.push_back(""); // Empty line for voltage info // entryCount++; //} } } - + // Calculate actual entry count from Variables string size_t actualEntryCount = 1; // Start with 1 for the first line for (size_t i = 0; Variables[i] != '\0'; i++) { @@ -589,54 +589,54 @@ public: actualEntryCount++; } } - + // Use the actual entry count for height calculation cachedHeight = ((fontsize + settings.spacing) * actualEntryCount) + (fontsize / 3) + settings.spacing + topPadding + bottomPadding; //const uint32_t margin = (fontsize * 4); - + cachedBaseX = 0; cachedBaseY = 0; - + needsRecalc = false; } - + // Fast rendering using cached values const uint32_t margin = (fontsize * 4); - + // Draw background const tsl::Color bgColor = !isDragging ? settings.backgroundColor // full opacity : settings.focusBackgroundColor; int clippingOffsetX = 0, clippingOffsetY = 0; - + int _frameOffsetX = ult::limitedMemory ? std::max(0, frameOffsetX - (1280-448)) : frameOffsetX; - + // Check X bounds and calculate clipping offset if (cachedBaseX + _frameOffsetX < int(framePadding)) { clippingOffsetX = framePadding - (cachedBaseX + _frameOffsetX); } else if ((cachedBaseX + _frameOffsetX + margin + rectangleWidth + (fontsize / 3)) > screenWidth - framePadding) { clippingOffsetX = (screenWidth - framePadding) - (cachedBaseX + _frameOffsetX + margin + rectangleWidth + (fontsize / 3)); } - - // Check Y bounds and calculate clipping offset + + // Check Y bounds and calculate clipping offset if (cachedBaseY + frameOffsetY < int(framePadding)) { clippingOffsetY = framePadding - (cachedBaseY + frameOffsetY); } else if ((cachedBaseY + frameOffsetY + cachedHeight) > screenHeight - framePadding) { clippingOffsetY = (screenHeight - framePadding) - (cachedBaseY + frameOffsetY + cachedHeight); } - + // Apply to all drawing calls renderer->drawRoundedRectSingleThreaded( - cachedBaseX + _frameOffsetX + clippingOffsetX, - cachedBaseY + frameOffsetY + clippingOffsetY, - margin + rectangleWidth + (fontsize / 3), - cachedHeight, - 16, + cachedBaseX + _frameOffsetX + clippingOffsetX, + cachedBaseY + frameOffsetY + clippingOffsetY, + margin + rectangleWidth + (fontsize / 3), + cachedHeight, + 16, a(bgColor) ); - - + + // Split Variables into lines for individual positioning std::vector variableLines; const std::string variablesStr(Variables); @@ -649,11 +649,11 @@ public: if (start < variablesStr.length()) { variableLines.push_back(variablesStr.substr(start)); } - + // Draw each label and variable line individually uint32_t currentY = cachedBaseY + fontsize + settings.spacing + topPadding; size_t labelIndex = 0; - + static const std::vector specialChars = {""}; static uint32_t labelWidth, labelCenterX; static std::vector _variableLines; @@ -665,7 +665,7 @@ public: //const std::string& currentLine = _variableLines[i]; // Check if this data line looks like resolution data (contains 'x' or 'p') bool isResolutionData = m_resolutionOutput[0].width; - + if (!isResolutionData) { // This is RES label but data isn't resolution data - skip the label ++labelIndex; @@ -681,12 +681,12 @@ public: labelCenterX = cachedBaseX + (margin / 2) - (labelWidth / 2); renderer->drawString(labelLines[labelIndex], false, labelCenterX + _frameOffsetX + clippingOffsetX, currentY + frameOffsetY + clippingOffsetY, fontsize, settings.catColor); } - + // Determine rendering method based on label type const std::string& currentLine = _variableLines[i]; const int baseX = cachedBaseX + margin + _frameOffsetX + clippingOffsetX; const int baseY = currentY + frameOffsetY + clippingOffsetY; - + if (settings.useDynamicColors) { if (labelIndex < labelLines.size() && labelLines[labelIndex] == "CPU") { std::string dataStr = currentLine; @@ -699,22 +699,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); const float temp = realCPU_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + int currentX = baseX; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); currentX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, currentX, baseY, fontsize, tempColor, settings.separatorColor); - + if (!postTempPart.empty()) { currentX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); @@ -728,10 +728,10 @@ public: } else { renderer->drawStringWithColoredSections(currentLine, false, specialChars, baseX, baseY, fontsize, settings.textColor, settings.separatorColor); } - + } else if (labelIndex < labelLines.size() && labelLines[labelIndex] == "GPU") { std::string dataStr = currentLine; - + const size_t degreesPos = dataStr.find("°"); if (degreesPos != std::string::npos && settings.realTemps && realGPU_Temp != 0) { size_t tempStart = dataStr.rfind(' ', degreesPos); @@ -740,22 +740,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realGPU_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + int currentX = baseX; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); currentX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, currentX, baseY, fontsize, tempColor, settings.separatorColor); - + if (!postTempPart.empty()) { currentX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); @@ -769,10 +769,10 @@ public: } else { renderer->drawStringWithColoredSections(currentLine, false, specialChars, baseX, baseY, fontsize, settings.textColor, settings.separatorColor); } - + } else if (labelIndex < labelLines.size() && labelLines[labelIndex] == "RAM") { std::string dataStr = currentLine; - + const size_t degreesPos = dataStr.find("°"); if (degreesPos != std::string::npos && settings.realTemps && realRAM_Temp != 0) { size_t tempStart = dataStr.rfind(' ', degreesPos); @@ -781,22 +781,22 @@ public: const size_t cPos = dataStr.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; - + const std::string preTempPart = dataStr.substr(0, tempStart); const std::string tempPart = dataStr.substr(tempStart, tempEnd - tempStart); const std::string postTempPart = dataStr.substr(tempEnd); - + const float temp = realRAM_Temp / 1000.0f; const tsl::Color tempColor = tsl::GradientColor(temp); - + int currentX = baseX; if (!preTempPart.empty()) { renderer->drawStringWithColoredSections(preTempPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); currentX += renderer->getTextDimensions(preTempPart, false, fontsize).first; } - + renderer->drawStringWithColoredSections(tempPart, false, specialChars, currentX, baseY, fontsize, tempColor, settings.separatorColor); - + if (!postTempPart.empty()) { currentX += renderer->getTextDimensions(tempPart, false, fontsize).first; renderer->drawStringWithColoredSections(postTempPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); @@ -810,7 +810,7 @@ public: } else { renderer->drawStringWithColoredSections(currentLine, false, specialChars, baseX, baseY, fontsize, settings.textColor, settings.separatorColor); } - + } else if (labelIndex < labelLines.size() && labelLines[labelIndex] == "SOC") { // SOC temperature rendering with gradient const size_t degreesPos = currentLine.find("°"); @@ -819,19 +819,19 @@ public: const size_t cPos = currentLine.find("C", degreesPos); if (cPos != std::string::npos) { const size_t tempEnd = cPos + 1; // Include the 'C' - + // Extract temperature value and apply gradient const int temp = atoi(currentLine.c_str()); const tsl::Color tempColor = tsl::GradientColor((float)temp); - + // Split into temperature part and remaining part const std::string tempPart = currentLine.substr(0, tempEnd); const std::string restPart = currentLine.substr(tempEnd); - + // Render temperature with gradient color int currentX = baseX; renderer->drawString(tempPart, false, currentX, baseY, fontsize, tempColor); - + // Render remaining text with normal color if (!restPart.empty()) { currentX += renderer->getTextDimensions(tempPart, false, fontsize).first; @@ -845,13 +845,13 @@ public: // Fallback: no degrees symbol found, render normally renderer->drawStringWithColoredSections(currentLine, false, specialChars, baseX, baseY, fontsize, settings.textColor, settings.separatorColor); } - + } else if (labelIndex < labelLines.size() && labelLines[labelIndex] == "TMP") { // TMP multiple temperatures rendering with gradients int currentX = baseX; size_t pos = 0; bool parseSuccess = true; - + // Parse up to 3 temperatures in the format "XX°C XX°C XX°C (XX%)" for (int tempCount = 0; tempCount < 3 && parseSuccess && pos < currentLine.length(); tempCount++) { // Find start of current temperature (skip any leading spaces) @@ -860,42 +860,42 @@ public: currentX += renderer->getTextDimensions(" ", false, fontsize).first; pos++; } - + if (pos >= currentLine.length()) break; - + // Find degrees symbol const size_t degreesPos = currentLine.find("°", pos); if (degreesPos == std::string::npos) { parseSuccess = false; break; } - + // Find 'C' after degrees symbol const size_t cPos = currentLine.find("C", degreesPos); if (cPos == std::string::npos) { parseSuccess = false; break; } - + const size_t tempEnd = cPos + 1; // Include the 'C' - + // Extract and render temperature with gradient const std::string tempPart = currentLine.substr(pos, tempEnd - pos); const int temp = atoi(tempPart.c_str()); const tsl::Color tempColor = tsl::GradientColor((float)temp); - + renderer->drawString(tempPart, false, currentX, baseY, fontsize, tempColor); currentX += renderer->getTextDimensions(tempPart, false, fontsize).first; - + pos = tempEnd; } - + // Render any remaining text (like " (50%)" or voltage info) if (pos < currentLine.length()) { std::string restPart = currentLine.substr(pos); renderer->drawStringWithColoredSections(restPart, false, specialChars, currentX, baseY, fontsize, settings.textColor, settings.separatorColor); } - + // If parsing failed, fall back to normal rendering if (!parseSuccess) { renderer->drawStringWithColoredSections(currentLine, false, specialChars, baseX, baseY, fontsize, settings.textColor, settings.separatorColor); @@ -905,11 +905,11 @@ public: // Extract numeric value for color determination float sysValue = 0.0f; sscanf(currentLine.c_str(), "%f", &sysValue); - + // Determine unit from string const bool isGB = (currentLine.find("GB") != std::string::npos); const float sysValueMB = isGB ? (sysValue * 1024.0f) : sysValue; - + // Apply same color scheme as in drawMemoryWidget tsl::Color sysColor = settings.textColor; if (sysValueMB >= 9.0f) { @@ -919,16 +919,16 @@ public: } else { sysColor = settings.useDynamicColors ? tsl::badRamTextColor : settings.textColor; } - + // Draw the memory value with color on the left int currentX = baseX; renderer->drawStringWithColoredSections(currentLine, false, specialChars, currentX, baseY, fontsize, sysColor, settings.separatorColor); currentX += renderer->getTextDimensions(currentLine, false, fontsize).first; - + // Draw separator " | " renderer->drawString(ult::DIVIDER_SYMBOL, false, currentX, baseY, fontsize, settings.separatorColor); currentX += renderer->getTextDimensions(ult::DIVIDER_SYMBOL, false, fontsize).first; - + // Draw "System" on the right renderer->drawString("System", false, currentX, baseY, fontsize, settings.textColor); } else { @@ -939,12 +939,12 @@ public: // Normal rendering for all other line types (CPU, GPU, RAM, BAT, FPS, RES, DTC) renderer->drawStringWithColoredSections(currentLine, false, specialChars, baseX, baseY, fontsize, settings.textColor, settings.separatorColor); } - + currentY += fontsize + settings.spacing; ++labelIndex; } }); - + tsl::elm::HeaderOverlayFrame* rootFrame = new tsl::elm::HeaderOverlayFrame("", ""); rootFrame->setContent(Status); return rootFrame; @@ -953,7 +953,7 @@ public: virtual void update() override { if (triggerExitNow) return; - + if (!SaltySD) { SaltySD = CheckPort(); if (SaltySD) { @@ -962,7 +962,7 @@ public: threadStart(&t6); } } - + // Handle performance mode changes apmGetPerformanceMode(&performanceMode); if (performanceMode == ApmPerformanceMode_Normal) { @@ -977,7 +977,7 @@ public: fontsize = settings.dockedFontSize; } } - + // Parse show settings to determine what to calculate std::vector showKeys; { @@ -991,14 +991,14 @@ public: showKeys.emplace_back(show.substr(start)); } } - + // Helper to check if a key is active static auto isActive = [&showKeys](const std::string& key) { return std::find(showKeys.begin(), showKeys.end(), key) != showKeys.end(); }; - + mutexLock(&mutex_Misc); - + // Variables to store formatted strings char MINI_CPU_compressed_c[42] = ""; char MINI_CPU_volt_c[16] = ""; @@ -1008,16 +1008,16 @@ public: char MINI_RAM_volt_c[32] = ""; char MINI_MEM_RAM_c[32] = ""; char MINI_SOC_volt_c[16] = ""; - + // Only process CPU if needed if (isActive("CPU")) { char MINI_CPU_Usage0[7], MINI_CPU_Usage1[7], MINI_CPU_Usage2[7], MINI_CPU_Usage3[7]; - + const uint64_t idle0 = idletick0.load(std::memory_order_acquire); const uint64_t idle1 = idletick1.load(std::memory_order_acquire); const uint64_t idle2 = idletick2.load(std::memory_order_acquire); const uint64_t idle3 = idletick3.load(std::memory_order_acquire); - + auto formatMiniUsage = [this](char* buf, size_t size, uint64_t idletick) { if (idletick > systemtickfrequency_impl) { strcpy(buf, "0%"); @@ -1026,22 +1026,22 @@ public: (1.0 - (static_cast(idletick) / systemtickfrequency_impl)) * 100.0); } }; - + formatMiniUsage(MINI_CPU_Usage0, sizeof(MINI_CPU_Usage0), idle0); formatMiniUsage(MINI_CPU_Usage1, sizeof(MINI_CPU_Usage1), idle1); formatMiniUsage(MINI_CPU_Usage2, sizeof(MINI_CPU_Usage2), idle2); formatMiniUsage(MINI_CPU_Usage3, sizeof(MINI_CPU_Usage3), idle3); - + if (settings.showFullCPU) { if (settings.realFrequencies && realCPU_Hz) { - snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), - "[%s,%s,%s,%s]@%hu.%hhu", - MINI_CPU_Usage0, MINI_CPU_Usage1, MINI_CPU_Usage2, MINI_CPU_Usage3, + snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), + "[%s,%s,%s,%s]@%hu.%hhu", + MINI_CPU_Usage0, MINI_CPU_Usage1, MINI_CPU_Usage2, MINI_CPU_Usage3, realCPU_Hz / 1000000, (realCPU_Hz / 100000) % 10); } else { - snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), - "[%s,%s,%s,%s]@%hu.%hhu", - MINI_CPU_Usage0, MINI_CPU_Usage1, MINI_CPU_Usage2, MINI_CPU_Usage3, + snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), + "[%s,%s,%s,%s]@%hu.%hhu", + MINI_CPU_Usage0, MINI_CPU_Usage1, MINI_CPU_Usage2, MINI_CPU_Usage3, CPU_Hz / 1000000, (CPU_Hz / 100000) % 10); } } else { @@ -1050,32 +1050,32 @@ public: sscanf(MINI_CPU_Usage1, "%lf%%", &usage1); sscanf(MINI_CPU_Usage2, "%lf%%", &usage2); sscanf(MINI_CPU_Usage3, "%lf%%", &usage3); - + double maxUsage = std::max({usage0, usage1, usage2, usage3}); - + if (settings.realFrequencies && realCPU_Hz) { - snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), + snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), "%.0f%%@%hu.%hhu", maxUsage, realCPU_Hz / 1000000, (realCPU_Hz / 100000) % 10); } else { - snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), + snprintf(MINI_CPU_compressed_c, sizeof(MINI_CPU_compressed_c), "%.0f%%@%hu.%hhu", maxUsage, CPU_Hz / 1000000, (CPU_Hz / 100000) % 10); } } - + if (settings.realVolts) { const uint32_t mv = realCPU_mV / 1000; snprintf(MINI_CPU_volt_c, sizeof(MINI_CPU_volt_c), "%u mV", mv); } } - + if (settings.realTemps && realCPU_Temp != 0) { char temp_buffer[48]; snprintf(temp_buffer, sizeof(temp_buffer), " %s", CPU_temp_c); strncat(MINI_CPU_compressed_c, temp_buffer, sizeof(MINI_CPU_compressed_c) - strlen(MINI_CPU_compressed_c) - 1); } - + // Only process GPU if needed if (isActive("GPU")) { if (settings.realFrequencies && realGPU_Hz) { @@ -1089,7 +1089,7 @@ public: GPU_Load_u / 10, "@", GPU_Hz / 1000000, (GPU_Hz / 100000) % 10); } - + // Handle sleep exit if (settings.sleepExit) { const auto GPU_Hz_int = int(GPU_Hz / 1000000); @@ -1103,19 +1103,19 @@ public: } lastGPU_Hz_int = GPU_Hz_int; } - + if (settings.realVolts) { const uint32_t mv = realGPU_mV / 1000; snprintf(MINI_GPU_volt_c, sizeof(MINI_GPU_volt_c), "%u mV", mv); } } - + if (settings.realTemps && realGPU_Temp != 0) { char temp_buffer[48]; snprintf(temp_buffer, sizeof(temp_buffer), " %s", GPU_temp_c); strncat(MINI_GPU_Load_c, temp_buffer, sizeof(MINI_GPU_Load_c) - strlen(MINI_GPU_Load_c) - 1); } - + // Only process RAM if needed if (isActive("RAM")) { if (!settings.showpartLoad) { @@ -1125,7 +1125,7 @@ public: const float ramUsedGiB = (RAM_Used_application_u + RAM_Used_applet_u + RAM_Used_system_u + RAM_Used_systemunsafe_u) / (1024.0f * 1024.0f); - + if (settings.realFrequencies && realRAM_Hz) { snprintf(MINI_RAM_var_compressed_c, sizeof(MINI_RAM_var_compressed_c), "%.0f/%.0fMB@%hu.%hhu", @@ -1139,15 +1139,15 @@ public: } } else { unsigned partLoadInt; - + if (R_SUCCEEDED(hocclkCheck)) { partLoadInt = partLoad[HocClkPartLoad_EMC] / 10; - + if (settings.showpartLoadCPUGPU) { unsigned ramCpuLoadInt = partLoad[HocClkPartLoad_EMCCpu] / 10; int RAM_GPU_Load = partLoad[HocClkPartLoad_EMC] - partLoad[HocClkPartLoad_EMCCpu]; unsigned ramGpuLoadInt = RAM_GPU_Load / 10; - + if (settings.realFrequencies && realRAM_Hz) { snprintf(MINI_RAM_var_compressed_c, sizeof(MINI_RAM_var_compressed_c), "%u%%[%u%%,%u%%]@%hu.%hhu", @@ -1171,12 +1171,12 @@ public: } } } else { - const uint64_t RAM_Total_all = RAM_Total_application_u + RAM_Total_applet_u + + const uint64_t RAM_Total_all = RAM_Total_application_u + RAM_Total_applet_u + RAM_Total_system_u + RAM_Total_systemunsafe_u; - const uint64_t RAM_Used_all = RAM_Used_application_u + RAM_Used_applet_u + + const uint64_t RAM_Used_all = RAM_Used_application_u + RAM_Used_applet_u + RAM_Used_system_u + RAM_Used_systemunsafe_u; partLoadInt = (RAM_Total_all > 0) ? (unsigned)((RAM_Used_all * 100) / RAM_Total_all) : 0; - + if (settings.realFrequencies && realRAM_Hz) { snprintf(MINI_RAM_var_compressed_c, sizeof(MINI_RAM_var_compressed_c), "%u%%@%hu.%hhu", partLoadInt, @@ -1188,7 +1188,7 @@ public: } } } - + if (settings.realVolts) { const float mv_vdd2_f = (realRAM_mV % 100000) / 10.0f; // VDD2 const uint32_t mv_vddq = (realRAM_mV / 10000) / 10; // VDDQ @@ -1223,14 +1223,14 @@ public: snprintf(temp_buffer, sizeof(temp_buffer), " %s", RAM_temp_c); strncat(MINI_RAM_var_compressed_c, temp_buffer, sizeof(MINI_RAM_var_compressed_c) - strlen(MINI_RAM_var_compressed_c) - 1); } - + // Only process MEM if needed if (isActive("MEM")) { const u64 freeRamBytes = RAM_Total_system_u - RAM_Used_system_u; - + float value; const char* unit; - + if (freeRamBytes >= 1024ULL * 1024 * 1024) { value = static_cast(freeRamBytes) / (1024.0f * 1024.0f * 1024.0f); unit = "GB"; @@ -1238,7 +1238,7 @@ public: value = static_cast(freeRamBytes) / (1024.0f * 1024.0f); unit = "MB"; } - + int decimalPlaces; if (value >= 1000.0f) { decimalPlaces = 0; @@ -1249,20 +1249,20 @@ public: } else { decimalPlaces = 3; } - - snprintf(MINI_MEM_RAM_c, sizeof(MINI_MEM_RAM_c), "%.*f %s %s", + + snprintf(MINI_MEM_RAM_c, sizeof(MINI_MEM_RAM_c), "%.*f %s %s", decimalPlaces, value, unit, ult::FREE.c_str()); } - + // Only process temperature/fan data if SOC or TMP is active if (isActive("TMP") || isActive("SOC")) { const int duty = safeFanDuty((int)Rotation_Duty); - + if (settings.realVolts && settings.showSOCVoltage) { const uint32_t mv = realSOC_mV / 1000; snprintf(MINI_SOC_volt_c, sizeof(MINI_SOC_volt_c), "%u mV", mv); } - + if (isActive("SOC")) { if (settings.showFanPercentage) { snprintf(soc_temperature_c, sizeof(soc_temperature_c), @@ -1272,7 +1272,7 @@ public: "%d°C", (int)SOC_temperatureF); } } - + if (isActive("TMP")) { if (settings.showFanPercentage) { snprintf(skin_temperature_c, sizeof(skin_temperature_c), @@ -1287,19 +1287,31 @@ public: } } } - + if (settings.realTemps) { if (realCPU_Temp != 0) { - snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%.1f°C", realCPU_Temp / 1000.0f); + if (settings.realTempsDec) { + snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%.1f°C", realCPU_Temp / 1000.0f); + } else { + snprintf(CPU_temp_c, sizeof(CPU_temp_c), "%u°C", static_cast(realCPU_Temp / 1000.0)); + } } if (realGPU_Temp != 0) { - snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%.1f°C", realGPU_Temp / 1000.0f); + if (settings.realTempsDec) { + snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%.1f°C", realGPU_Temp / 1000.0f); + } else { + snprintf(GPU_temp_c, sizeof(GPU_temp_c), "%u°C", static_cast(realGPU_Temp / 1000.0)); + } } if (realRAM_Temp != 0) { - snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%.1f°C", realRAM_Temp / 1000.0f); + if (settings.realTempsDec) { + snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%.1f°C", realRAM_Temp / 1000.0f); + } else { + snprintf(RAM_temp_c, sizeof(RAM_temp_c), "%u°C", static_cast(realRAM_Temp / 1000.0)); + } } - } - + } + // Only process resolution if RES is active and game is running if (isActive("RES") && GameRunning && NxFps) { if (!resolutionLookup) { @@ -1318,11 +1330,11 @@ public: for (size_t i = 0; i < 8; i++) { for (size_t x = 0; x < 8; x++) { if (m_resolutionRenderCalls[i].width == 0) break; - if ((m_resolutionRenderCalls[i].width == m_resolutionViewportCalls[x].width) && + if ((m_resolutionRenderCalls[i].width == m_resolutionViewportCalls[x].width) && (m_resolutionRenderCalls[i].height == m_resolutionViewportCalls[x].height)) { m_resolutionOutput[out_iter].width = m_resolutionRenderCalls[i].width; m_resolutionOutput[out_iter].height = m_resolutionRenderCalls[i].height; - m_resolutionOutput[out_iter].calls = (m_resolutionRenderCalls[i].calls > m_resolutionViewportCalls[x].calls) + m_resolutionOutput[out_iter].calls = (m_resolutionRenderCalls[i].calls > m_resolutionViewportCalls[x].calls) ? m_resolutionRenderCalls[i].calls : m_resolutionViewportCalls[x].calls; out_iter++; found = true; @@ -1343,7 +1355,7 @@ public: for (size_t x = 0; x < 8; x++) { for (size_t y = 0; y < out_iter_s; y++) { if (m_resolutionViewportCalls[x].width == 0) break; - if ((m_resolutionViewportCalls[x].width == m_resolutionOutput[y].width) && + if ((m_resolutionViewportCalls[x].width == m_resolutionOutput[y].width) && (m_resolutionViewportCalls[x].height == m_resolutionOutput[y].height)) { found = true; break; @@ -1353,7 +1365,7 @@ public: m_resolutionOutput[out_iter].width = m_resolutionViewportCalls[x].width; m_resolutionOutput[out_iter].height = m_resolutionViewportCalls[x].height; m_resolutionOutput[out_iter].calls = m_resolutionViewportCalls[x].calls; - out_iter++; + out_iter++; } found = false; if (out_iter == 8) break; @@ -1364,23 +1376,23 @@ public: } else if (!GameRunning && resolutionLookup != 0) { resolutionLookup = 0; } - + mutexUnlock(&mutex_Misc); - + // Battery/power draw - always update since BAT/DRAW might be displayed if (isActive("BAT") || isActive("DRAW")) { char remainingBatteryLife[8]; const float drawW = (fabsf(PowerConsumption) < 0.01f) ? 0.0f : PowerConsumption; - + mutexLock(&mutex_BatteryChecker); - + if (batTimeEstimate >= 0 && !(drawW <= 0.01f && drawW >= -0.01f)) { snprintf(remainingBatteryLife, sizeof(remainingBatteryLife), "%d:%02d", batTimeEstimate / 60, batTimeEstimate % 60); } else { strcpy(remainingBatteryLife, "--:--"); } - + if (!settings.invertBatteryDisplay) { snprintf(Battery_c, sizeof(Battery_c), "%.2f W%.1f%% [%s]", @@ -1394,14 +1406,14 @@ public: remainingBatteryLife, drawW); } - + mutexUnlock(&mutex_BatteryChecker); } - + // Build Variables string char Temp[512] = ""; uint16_t flags = 0; - + for (const auto& key : showKeys) { if (key == "CPU" && !(flags & 1)) { if (Temp[0]) strcat(Temp, "\n"); @@ -1464,12 +1476,12 @@ public: if (Temp[0]) strcat(Temp, "\n"); char Temp_s[32] = ""; static std::pair old_res[2]; - + uint16_t w0 = m_resolutionOutput[0].width; uint16_t h0 = m_resolutionOutput[0].height; uint16_t w1 = m_resolutionOutput[1].width; uint16_t h1 = m_resolutionOutput[1].height; - + if (w0 || w1) { if (w0 && w1) { if ((w0 == old_res[1].first && h0 == old_res[1].second) || @@ -1478,8 +1490,8 @@ public: std::swap(h0, h1); } } - - + + // Format based on whether we show full resolution or just height (p) if (settings.showFullResolution) { if (!w1 || !h1) @@ -1492,11 +1504,11 @@ public: else snprintf(Temp_s, sizeof(Temp_s), "%dp%dp", h0, h1); } - + // Update last-frame cache old_res[0] = {m_resolutionOutput[0].width, m_resolutionOutput[0].height}; old_res[1] = {m_resolutionOutput[1].width, m_resolutionOutput[1].height}; - + strcat(Temp, Temp_s); flags |= 128; } @@ -1527,9 +1539,9 @@ public: flags |= 1024; } } - + strcpy(Variables, Temp); - + // Enable rendering if needed if (!skipOnce) { if (runOnce) { @@ -1541,7 +1553,7 @@ public: skipOnce = false; } } - + virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { // Static variables to maintain drag state between function calls static bool oldTouchDetected = false; @@ -1555,14 +1567,14 @@ public: static bool hasMoved = false; //static u64 lastTouchTime = 0; //static constexpr u64 TOUCH_DEBOUNCE_NS = 16000000ULL; // 16ms debounce - + // Get current time for debouncing //const u64 currentTime = armTicksToNs(armGetSystemTick()); - + // Better touch detection - check if coordinates are within reasonable screen bounds - const bool currentTouchDetected = (touchPos.x > 0 && touchPos.y > 0 && + const bool currentTouchDetected = (touchPos.x > 0 && touchPos.y > 0 && touchPos.x < screenWidth && touchPos.y < screenHeight); - + // Debounce touch detection //if (currentTouchDetected && !oldTouchDetected) { // if (currentTime - lastTouchTime < TOUCH_DEBOUNCE_NS) { @@ -1579,17 +1591,17 @@ public: isRendering = true; leventClear(&renderingStopEvent); } - + // Calculate overlay bounds const uint32_t margin = (fontsize * 4); - + // Cache bounds calculation static int cachedBaseX = 0; static int cachedBaseY = 0; static uint32_t cachedOverlayHeight = 0; static bool boundsNeedUpdate = true; static std::string lastVariables = ""; - + // Only recalculate bounds when Variables change or first time if (boundsNeedUpdate || std::string(Variables) != lastVariables) { // Calculate actual entry count from Variables string @@ -1599,54 +1611,54 @@ public: actualEntryCount++; } } - - cachedOverlayHeight = ((fontsize + settings.spacing) * actualEntryCount) + + + cachedOverlayHeight = ((fontsize + settings.spacing) * actualEntryCount) + (fontsize / 3) + settings.spacing + topPadding + bottomPadding; - + // Position calculation based on settings cachedBaseX = 0; cachedBaseY = 0; - + boundsNeedUpdate = false; lastVariables = Variables; } - + const int overlayX = frameOffsetX;//ult::limitedMemory ? cachedBaseX + std::max(0, frameOffsetX - (1280-448)) : cachedBaseX + frameOffsetX; const int overlayY = cachedBaseY + frameOffsetY; const int overlayWidth = margin + rectangleWidth + (fontsize / 3); const int overlayHeight = cachedOverlayHeight; - + // Add padding to make touch detection more forgiving static constexpr int touchPadding = 4; const int touchableX = overlayX - touchPadding; const int touchableY = overlayY - touchPadding; const int touchableWidth = overlayWidth + (touchPadding * 2); const int touchableHeight = overlayHeight + (touchPadding * 2); - + // Screen boundaries for clamping const int minX = -cachedBaseX + framePadding; const int maxX = screenWidth - overlayWidth - cachedBaseX - framePadding; const int minY = -cachedBaseY + framePadding; const int maxY = screenHeight - overlayHeight - cachedBaseY - framePadding; - + const bool minusDragReady = buttonState.minusDragActive.load(std::memory_order_acquire); const bool plusDragReady = buttonState.plusDragActive.load(std::memory_order_acquire); // Check button states const bool currentMinusHeld = (keysHeld & KEY_MINUS) && !(keysHeld & ~KEY_MINUS & ALL_KEYS_MASK) && minusDragReady; const bool currentPlusHeld = (keysHeld & KEY_PLUS) && !(keysHeld & ~KEY_PLUS & ALL_KEYS_MASK) && plusDragReady; - + // Handle touch dragging if (currentTouchDetected && !isDragging) { // Touch detected and not currently dragging - check if we should start const int touchX = touchPos.x; const int touchY = touchPos.y; - + if (!oldTouchDetected) { // Touch just started - check if within overlay bounds if (touchX >= touchableX && touchX <= touchableX + touchableWidth && touchY >= touchableY && touchY <= touchableY + touchableHeight) { - + // Start touch dragging isDragging = true; //isRendering = false; @@ -1665,7 +1677,7 @@ public: const int touchY = touchPos.y; const int deltaX = touchX - initialTouchPos.x; const int deltaY = touchY - initialTouchPos.y; - + // Check if we've moved enough to consider this a drag if (!hasMoved) { const int totalMovement = abs(deltaX) + abs(deltaY); @@ -1673,12 +1685,12 @@ public: hasMoved = true; } } - + if (hasMoved) { // Update frame offsets with boundary checking const int newFrameOffsetX = std::max(minX, std::min(maxX, initialFrameOffsetX + deltaX)); const int newFrameOffsetY = std::max(minY, std::min(maxY, initialFrameOffsetY + deltaY)); - + frameOffsetX = newFrameOffsetX; frameOffsetY = newFrameOffsetY; @@ -1696,7 +1708,7 @@ public: iniData["mini"]["frame_offset_y"] = std::to_string(frameOffsetY); ult::saveIniFileData(configIniPath, iniData); } - + // Reset touch drag state isDragging = false; hasMoved = false; @@ -1706,7 +1718,7 @@ public: triggerRumbleDoubleClick.store(true, std::memory_order_release); triggerOffSound.store(true, std::memory_order_release); } - + // Handle joystick dragging (MINUS + right joystick OR PLUS + left joystick) if ((currentMinusHeld || currentPlusHeld) && !isDragging) { @@ -1719,41 +1731,41 @@ public: } else if ((currentMinusHeld || currentPlusHeld) && isDragging) { // Continue joystick dragging static constexpr int JOYSTICK_DEADZONE = 20; - + // Choose the appropriate joystick based on which button is held const HidAnalogStickState& activeJoystick = currentMinusHeld ? joyStickPosRight : joyStickPosLeft; - + // Only move if joystick is outside deadzone if (abs(activeJoystick.x) > JOYSTICK_DEADZONE || abs(activeJoystick.y) > JOYSTICK_DEADZONE) { // Calculate joystick magnitude (distance from center) const float magnitude = sqrt((float)(activeJoystick.x * activeJoystick.x + activeJoystick.y * activeJoystick.y)); const float normalizedMagnitude = magnitude / 32767.0f; // Normalize to 0-1 range - + // Single smooth curve: stays very slow for wide range, then accelerates static constexpr float baseSensitivity = 0.00008f; // Higher so small movements register static constexpr float maxSensitivity = 0.0005f; - + // Use x^8 curve - stays very low until ~70% then curves up sharply const float curveValue = pow(normalizedMagnitude, 8.0f); const float currentSensitivity = baseSensitivity + (maxSensitivity - baseSensitivity) * curveValue; - + // Calculate movement delta with fractional accumulation static float accumulatedX = 0.0f; static float accumulatedY = 0.0f; - + accumulatedX += (float)activeJoystick.x * currentSensitivity; accumulatedY += -(float)activeJoystick.y * currentSensitivity; - + // Extract integer movement and keep fractional part const int deltaX = (int)accumulatedX; const int deltaY = (int)accumulatedY; accumulatedX -= deltaX; accumulatedY -= deltaY; - + // Update frame offsets with boundary checking const int newFrameOffsetX = std::max(minX, std::min(maxX, frameOffsetX + deltaX)); const int newFrameOffsetY = std::max(minY, std::min(maxY, frameOffsetY + deltaY)); - + frameOffsetX = newFrameOffsetX; frameOffsetY = newFrameOffsetY; @@ -1775,12 +1787,12 @@ public: triggerRumbleDoubleClick.store(true, std::memory_order_release); triggerOffSound.store(true, std::memory_order_release); } - + // Update state for next frame oldTouchDetected = currentTouchDetected; oldMinusHeld = currentMinusHeld; oldPlusHeld = currentPlusHeld; - + // Handle existing key input logic (but don't interfere with dragging) if (!isDragging) { if (isKeyComboPressed(keysHeld, keysDown)) { @@ -1801,12 +1813,12 @@ public: } return true; } - else if (((keysDown & KEY_L) && (keysDown & KEY_ZL)) || ((keysDown & KEY_L) && (keysHeld & KEY_ZL)) || ((keysHeld & KEY_L) && (keysDown & KEY_ZL))) { - FPSmin = 254; - FPSmax = 0; + else if (((keysDown & KEY_L) && (keysDown & KEY_ZL)) || ((keysDown & KEY_L) && (keysHeld & KEY_ZL)) || ((keysHeld & KEY_L) && (keysDown & KEY_ZL))) { + FPSmin = 254; + FPSmax = 0; } } - + // Return true if we handled the input (during dragging) return isDragging; } diff --git a/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp index 8062cc41..cafaa147 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp @@ -643,9 +643,9 @@ protected: "Max Handheld Display", ValueRange(60, IsAula() ? 65 : 75, 1, " Hz", 1), "Display Clock", - &displayThresholds, - {}, - {}, + &displayThresholds, + {}, + {}, false ); } @@ -1266,7 +1266,6 @@ protected: }; std::vector mGpuVoltsVmin = { - NamedValue("Auto", 0), NamedValue("480mV", 480), NamedValue("485mV", 485), NamedValue("490mV", 490), NamedValue("495mV", 495), NamedValue("500mV", 500), NamedValue("505mV", 505), NamedValue("510mV", 510), NamedValue("515mV", 515), NamedValue("520mV", 520),