Render the post-process shader picker as a single combo widget that opens onto a tree of bundled + user-installed shaders. Drives the two LUS cvars LUS::Run reads each frame: gPostProcessEnabled (int, 0/1) gPostProcessShader (string, short name as accepted by Fast::LoadPostProcessShader) The combo's preview text reflects the current cvar state, so a fresh launch picks up the previously-selected s
| 140 | // fresh launch picks up the previously-selected shader without the |
| 141 | // picker needing to know about state-restore. |
| 142 | void RenderPostProcessShaderPicker(WidgetInfo& /*widget*/) { |
| 143 | const bool enabled = CVarGetInteger("gPostProcessEnabled", 0) != 0; |
| 144 | const char* currentRaw = CVarGetString("gPostProcessShader", ""); |
| 145 | const std::string current = currentRaw ? currentRaw : std::string(); |
| 146 | |
| 147 | std::string preview = "Off"; |
| 148 | if (enabled && !current.empty()) { |
| 149 | if (const char* friendly = PostProcessShaderLabel(current)) { |
| 150 | preview = friendly; |
| 151 | } else { |
| 152 | preview = current; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | ImGui::TextUnformatted("Post-Process Shader"); |
| 157 | ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); |
| 158 | if (!ImGui::BeginCombo("##gPostProcessShaderPicker", preview.c_str())) { |
| 159 | return; |
| 160 | } |
| 161 | |
| 162 | // Filter input persists across frames inside the open popup |
| 163 | // only — closing the combo clears it. ImGui draws the combo |
| 164 | // content inside its own internal child window each frame, so |
| 165 | // a static-local is the simplest persistence story without a |
| 166 | // companion cvar. |
| 167 | static char filterBuf[128] = ""; |
| 168 | ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); |
| 169 | ImGui::InputTextWithHint("##gPostProcessShaderFilter", "Filter...", filterBuf, sizeof(filterBuf)); |
| 170 | const std::string filter = filterBuf; |
| 171 | |
| 172 | auto matchesFilter = [&filter](const std::string& s) -> bool { |
| 173 | if (filter.empty()) { |
| 174 | return true; |
| 175 | } |
| 176 | // Case-insensitive substring match. |
| 177 | std::string hay = s; |
| 178 | std::string needle = filter; |
| 179 | std::transform(hay.begin(), hay.end(), hay.begin(), |
| 180 | [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); |
| 181 | std::transform(needle.begin(), needle.end(), needle.begin(), |
| 182 | [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); |
| 183 | return hay.find(needle) != std::string::npos; |
| 184 | }; |
| 185 | |
| 186 | auto selectShader = [](const std::string& name) { |
| 187 | if (name.empty()) { |
| 188 | CVarSetInteger("gPostProcessEnabled", 0); |
| 189 | return; |
| 190 | } |
| 191 | CVarSetString("gPostProcessShader", name.c_str()); |
| 192 | CVarSetInteger("gPostProcessEnabled", 1); |
| 193 | |
| 194 | // Trigger the Low Resolution Mode warn modal when the picked |
| 195 | // shader expects a native-resolution source FB but the user's |
| 196 | // current latched mode is "off". The shader still loads — we |
| 197 | // never block selection — but the modal nudges them toward |
| 198 | // enabling N64 mode + restart so the picture isn't a tiny |
| 199 | // render in the corner of the window. |
nothing calls this directly
no test coverage detected