diff --git a/src/common/backends/imgui/launcher_imgui.cpp b/src/common/backends/imgui/launcher_imgui.cpp index eab5f03..83d5ac8 100644 --- a/src/common/backends/imgui/launcher_imgui.cpp +++ b/src/common/backends/imgui/launcher_imgui.cpp @@ -2931,6 +2931,7 @@ static void draw_aspect_row(LauncherModel* m, const LauncherTheme& th, bool any_deep_display(const LauncherModel* m) { return m->has_window_size || m->has_renderer || m->has_supersampling || m->has_antialiasing || m->has_texture_filter || m->has_screen_kind || + m->has_scanlines || m->has_fmv_filter || m->has_frame_interp || m->has_skip_fmv || m->has_geometry_precision || @@ -3322,6 +3323,30 @@ void draw_display_controls(LauncherModel* m, const LauncherTheme& th) { launcher_model_cycle_screen_kind(m); } + // Scanlines: darken every other display line for a CRT look. Independent of + // the Screen model colour filter above — the two compose. The strength row + // only appears once it is on. + if (m->has_scanlines) { + row_label("Scanlines", th); + bool sl = m->s.scanlines != 0; + if (ImGui::Checkbox("##scanlines", &sl)) + launcher_model_toggle_scanlines(m); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) + ImGui::SetTooltip("Darkens the gaps between display lines for a CRT " + "look.\nApplied at the console's own scanline " + "pitch; cleanest when the\nwindow is at least twice " + "the console's line count tall."); + if (m->s.scanlines) { + row_label("Scanline strength", th); + int p = launcher_model_scanline_strength_pct(m); + ImGui::PushID("scanline_strength"); + ImGui::SetNextItemWidth(px(150)); + if (ImGui::SliderInt("##scanline_strength", &p, 1, 100, "%d%%")) + launcher_model_set_scanline_strength_pct(m, p); + ImGui::PopID(); + } + } + // Frame interpolation is only meaningful under OpenGL (Software has no // interpolation pass); Presentation target only matters once frame // interpolation is actually on. @@ -4108,6 +4133,70 @@ void settings_pad_label(int binding, char* out, size_t capacity) { snprintf(out, capacity, "%s", text); } +/* PSX's runtime matches a host shortcut bound to ONE button or ONE trigger + * direction only while Select is also held (hotkey_pad_binding_down): the + * implicit chord keeps a lone face button from firing Rewind mid-game. A + * two-button capture is stored as an explicit chord and replaces that + * implicit Select. Show the requirement in the label so a player who binds + * "righttrigger+" is not left pressing R2 alone and seeing nothing. */ +static bool assist_pad_bind_implies_select(const LauncherModel* m, int binding) { + const SystemProfile* prof = m ? (const SystemProfile*)m->profile : nullptr; + const bool psx = prof && prof->id && std::strcmp(prof->id, "psx") == 0; + return psx && (RECOMP_LAUNCHER_PAD_IS_BUTTON(binding) || + RECOMP_LAUNCHER_PAD_IS_AXIS(binding)); +} + +static void assist_pad_label(const LauncherModel* m, int binding, + char* out, size_t capacity) { + char text[96]; + settings_pad_label(binding, text, sizeof text); + if (assist_pad_bind_implies_select(m, binding)) + snprintf(out, capacity, "select + %s", text); + else + snprintf(out, capacity, "%s", text); +} + +/* The PSX runtime evaluates controller host shortcuts against PLAYER 1's + * opened pad handle only, so with Player 1 on Keyboard / None, or its pad + * unplugged, every controller shortcut is dead no matter what is bound. + * Hide the column in that state instead of offering bindings that cannot + * fire. Other profiles keep the column unconditionally. */ +static bool assist_pad_column_available(const LauncherModel* m) { + const SystemProfile* prof = m ? (const SystemProfile*)m->profile : nullptr; + const bool psx = prof && prof->id && std::strcmp(prof->id, "psx") == 0; + if (!psx) return true; + if (m->s.player_src[0] != 2) return false; + const char* guid = m->s.player_gamepad_guid[0]; + for (int j = 0; j < g_pad_count; ++j) { + if (g_pads[j].id && g_pads[j].id == m->player_pad_id[0]) return true; + if (guid[0] && g_pads[j].guid[0] && std::strcmp(g_pads[j].guid, guid) == 0) + return true; + } + return false; +} + +static void draw_assist_pad_unavailable_hint(const LauncherModel* m, + const LauncherTheme& th) { + if (!m) return; + ImGui::TextColored(col(th.text_muted), + m->s.player_src[0] == 2 + ? "Controller shortcuts hidden: Player 1's gamepad is not connected." + : "Controller shortcuts hidden: set Player 1's input source to a gamepad."); +} + +static void draw_assist_pad_chord_hint(const LauncherModel* m, + const LauncherTheme& th) { + if (!m) return; + for (int action = 0; action < m->assist_binding_count; ++action) { + if (assist_pad_bind_implies_select(m, m->s.assist_pad_bind[action])) { + ImGui::TextColored(col(th.text_muted), + "Single-button shortcuts fire with Select held; capture two" + " buttons together for a chord without Select."); + return; + } + } +} + void draw_assist_binding_editor(LauncherModel* m, const LauncherTheme& th, const char* table_id, int action_limit, bool show_reset) { @@ -4131,14 +4220,17 @@ void draw_assist_binding_editor(LauncherModel* m, const LauncherTheme& th, m->has_assist_tools ? "Global controls; they only operate while Assist Tools is enabled." : "Press a controller button or chord."); - if (ImGui::BeginTable(table_id, 3, ImGuiTableFlags_SizingFixedFit | - ImGuiTableFlags_RowBg)) { + const bool pad_col = assist_pad_column_available(m); + if (ImGui::BeginTable(table_id, pad_col ? 3 : 2, + ImGuiTableFlags_SizingFixedFit | + ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed, px(150)); ImGui::TableSetupColumn("Keyboard", ImGuiTableColumnFlags_WidthFixed, px(180)); - ImGui::TableSetupColumn("Controller", ImGuiTableColumnFlags_WidthFixed, - px(180)); + if (pad_col) + ImGui::TableSetupColumn("Controller", ImGuiTableColumnFlags_WidthFixed, + px(180)); ImGui::TableHeadersRow(); int count = m->assist_binding_count; if (action_limit > 0 && count > action_limit) count = action_limit; @@ -4151,24 +4243,35 @@ void draw_assist_binding_editor(LauncherModel* m, const LauncherTheme& th, ImGui::TableSetColumnIndex(1); bool capture_key = m->capturing && m->capture_assist && !m->capture_pad && m->capture_btn == action; - if (ImGui::Button( - capture_key ? "[ press a key... ]" : - settings_key_label(m->s.assist_key_bind[action]), - ImVec2(px(170), 0))) + /* "##key" / "##pad": both columns can read "(unbound)" at once + * (a freshly added shortcut), which would give the two buttons + * the same ImGui ID inside this row's PushID scope. */ + char key_lbl[96]; + snprintf(key_lbl, sizeof key_lbl, "%s##key", + capture_key ? "[ press a key... ]" + : settings_key_label(m->s.assist_key_bind[action])); + if (ImGui::Button(key_lbl, ImVec2(px(170), 0))) launcher_model_begin_assist_capture(m, action, false); - ImGui::TableSetColumnIndex(2); - bool capture_pad = m->capturing && m->capture_assist && - m->capture_pad && m->capture_btn == action; - char pad[48]; - settings_pad_label(m->s.assist_pad_bind[action], pad, sizeof pad); - if (ImGui::Button( - capture_pad ? "[ press a button... ]" : pad, - ImVec2(px(170), 0))) - launcher_model_begin_assist_capture(m, action, true); + if (pad_col) { + ImGui::TableSetColumnIndex(2); + bool capture_pad = m->capturing && m->capture_assist && + m->capture_pad && m->capture_btn == action; + char pad[112]; + assist_pad_label(m, m->s.assist_pad_bind[action], pad, sizeof pad); + char pad_lbl[128]; + snprintf(pad_lbl, sizeof pad_lbl, "%s##pad", + capture_pad ? "[ press a button... ]" : pad); + if (ImGui::Button(pad_lbl, ImVec2(px(170), 0))) + launcher_model_begin_assist_capture(m, action, true); + } ImGui::PopID(); } ImGui::EndTable(); } + if (pad_col) draw_assist_pad_chord_hint(m, th); + else draw_assist_pad_unavailable_hint(m, th); + if (m->capturing && m->capture_assist) + ImGui::TextColored(col(th.text_muted), "Backspace clears the binding."); if (show_reset && ImGui::Button(m->has_assist_tools ? "Reset Assist Controls" : "Reset Host Shortcuts")) @@ -4187,15 +4290,17 @@ void draw_controller_assist_shortcuts(LauncherModel* m, m->has_assist_tools ? "(global; requires Assist Tools)" : "(keyboard and controller)"); - if (ImGui::BeginTable("controller_assist_binds", 3, + const bool pad_col = assist_pad_column_available(m); + if (ImGui::BeginTable("controller_assist_binds", pad_col ? 3 : 2, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthStretch, 1.1f); ImGui::TableSetupColumn("Keyboard", ImGuiTableColumnFlags_WidthStretch, 1.0f); - ImGui::TableSetupColumn("Controller", ImGuiTableColumnFlags_WidthStretch, - 1.0f); + if (pad_col) + ImGui::TableSetupColumn("Controller", ImGuiTableColumnFlags_WidthStretch, + 1.0f); ImGui::TableHeadersRow(); for (int action = 0; action < m->assist_binding_count; ++action) { ImGui::PushID(action); @@ -4206,25 +4311,34 @@ void draw_controller_assist_shortcuts(LauncherModel* m, ImGui::TableSetColumnIndex(1); bool capture_key = m->capturing && m->capture_assist && !m->capture_pad && m->capture_btn == action; - if (ImGui::Button( - capture_key ? "[ key... ]" : - settings_key_label(m->s.assist_key_bind[action]), - ImVec2(-FLT_MIN, 0))) + /* "##key" / "##pad": see draw_assist_binding_editor. */ + char key_lbl[96]; + snprintf(key_lbl, sizeof key_lbl, "%s##key", + capture_key ? "[ key... ]" + : settings_key_label(m->s.assist_key_bind[action])); + if (ImGui::Button(key_lbl, ImVec2(-FLT_MIN, 0))) launcher_model_begin_assist_capture(m, action, false); - ImGui::TableSetColumnIndex(2); - bool capture_pad = m->capturing && m->capture_assist && - m->capture_pad && m->capture_btn == action; - char pad[48]; - settings_pad_label(m->s.assist_pad_bind[action], pad, sizeof pad); - if (ImGui::Button(capture_pad ? "[ button... ]" : pad, - ImVec2(-FLT_MIN, 0))) - launcher_model_begin_assist_capture(m, action, true); + if (pad_col) { + ImGui::TableSetColumnIndex(2); + bool capture_pad = m->capturing && m->capture_assist && + m->capture_pad && m->capture_btn == action; + char pad[112]; + assist_pad_label(m, m->s.assist_pad_bind[action], pad, sizeof pad); + char pad_lbl[128]; + snprintf(pad_lbl, sizeof pad_lbl, "%s##pad", + capture_pad ? "[ button... ]" : pad); + if (ImGui::Button(pad_lbl, ImVec2(-FLT_MIN, 0))) + launcher_model_begin_assist_capture(m, action, true); + } ImGui::PopID(); } ImGui::EndTable(); } + if (pad_col) draw_assist_pad_chord_hint(m, th); + else draw_assist_pad_unavailable_hint(m, th); if (m->capturing && m->capture_assist) - ImGui::TextColored(col(th.warn), "Listening... (Esc cancels)"); + ImGui::TextColored(col(th.warn), + "Listening... (Esc cancels, Backspace clears)"); } void draw_assist_tools(LauncherModel* m, const LauncherTheme& th) { @@ -4771,6 +4885,7 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { col(th.text_muted), label_col_w, px(6.0f)); const bool cap = m->capturing && !m->capture_pad && + !m->capture_assist && m->capture_btn == b; const bool cap_alt = cap && m->capture_slot == 1; const char* lbl = m->binds[p][b]; @@ -4844,7 +4959,7 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { s_kb_profile_saved_until = ImGui::GetTime() + 2.5; } } - if (m->capturing && !m->capture_pad) { + if (m->capturing && !m->capture_pad && !m->capture_assist) { const char* label = (m->capture_btn >= 0 && m->capture_btn < LNG_PSX_PAD_BUTTON_COUNT) @@ -4881,6 +4996,7 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { col(th.text_muted), label_col_w, px(6.0f)); const bool cap = m->capturing && m->capture_pad && + !m->capture_assist && m->capture_btn == b; const bool wait_rel = cap && m->map_all_wait_release; const char* pl = m->pad_binds[p][b][0] @@ -4928,7 +5044,7 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { s_profile_saved_until = ImGui::GetTime() + 2.5; } } - if (m->capturing && m->capture_pad) { + if (m->capturing && m->capture_pad && !m->capture_assist) { const char* label = (m->capture_btn >= 0 && m->capture_btn < LNG_PSX_PAD_BUTTON_COUNT) @@ -5076,7 +5192,8 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { for (int slot = 0; slot < bpi; ++slot) { if (slot) ImGui::SameLine(0, chip_gap); ImGui::PushID(slot); - const bool cap = m->capturing && m->capture_btn == b + const bool cap = m->capturing && !m->capture_assist && + m->capture_btn == b && m->capture_slot == slot; const char* txt = cap ? "[ press a key... ]" @@ -5091,7 +5208,8 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { // GAMEPAD chip only: the player's source is a pad, so a key // bind on this row would map something nothing reads. ImGui::PushID("pad"); - const bool cap_pad = m->capturing && m->capture_pad && m->capture_btn == b; + const bool cap_pad = m->capturing && m->capture_pad && + !m->capture_assist && m->capture_btn == b; char settings_pad[48]; settings_pad_label(m->s.player_pad_bind[p][b], settings_pad, sizeof settings_pad); @@ -5107,7 +5225,8 @@ void draw_controller_config_view(LauncherModel* m, const LauncherTheme& th) { } else { // KEY chip only: keyboard is the source (or the console has // no pad binds at all). - const bool cap_key = m->capturing && !m->capture_pad && m->capture_btn == b; + const bool cap_key = m->capturing && !m->capture_pad && + !m->capture_assist && m->capture_btn == b; if (cap_key) ImGui::PushStyleColor(ImGuiCol_Button, col(th.accent)); const char* key_text = settings_player_binds ? settings_key_label(m->s.player_key_bind[p][b]) @@ -11236,7 +11355,14 @@ bool try_capture(LauncherModel* m, const SDL_Event& ev) { // axis push (past a dead threshold) commits. PSX only accepts events from // the player's selected Input source device. if (m->capturing && m->capture_pad) { - if (m->capture_assist) { + if (m->settings_bindings && m->capture_assist) { + // Backspace while listening clears the shortcut (0 = unbound); + // there is no other way to remove a host shortcut from the UI. + if (ev.type == SDL_EVENT_KEY_DOWN && LNG_EVKEY(ev) == SDLK_BACKSPACE) { + launcher_model_set_captured_pad(m, 0); + launcher_model_cancel_capture(m); + return true; + } if (ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { const int button = (int)LNG_EVGBTN(ev); uint32_t mask = launcher_input_gamepad_button_mask( @@ -11447,8 +11573,9 @@ bool try_capture(LauncherModel* m, const SDL_Event& ev) { // Single-bind stores (SNES/PSX/GBA) use the legacy scancode setter // (capture_slot is always 0 for them). const SystemProfile* prof = (const SystemProfile*)m->profile; - if (m->capture_assist) - launcher_model_set_captured_key(m, (int)LNG_EVSCAN(ev)); + if (m->settings_bindings && m->capture_assist) + launcher_model_set_captured_key( + m, LNG_EVKEY(ev) == SDLK_BACKSPACE ? 0 : (int)LNG_EVSCAN(ev)); else if (prof && prof->controller.binds_per_input >= 2 && prof->id && !strcmp(prof->id, "psx")) launcher_binds_set_button_slot(m, m->cfg_player + 1, m->capture_btn, m->capture_slot, (int)LNG_EVSCAN(ev)); @@ -11463,7 +11590,12 @@ bool try_capture(LauncherModel* m, const SDL_Event& ev) { } // hotkey capture: wait past a bare modifier press for the real key if (is_modifier_scancode((SDL_Scancode)LNG_EVSCAN(ev))) return true; - launcher_binds_set_hotkey(m, m->capture_hk, (int)LNG_EVKEY(ev), (int)LNG_EVMOD(ev)); + // Backspace unbinds: launcher_binds_set_hotkey writes "None", which the + // PSX runtime honours as an explicit unbind (no default fallback). + if (LNG_EVKEY(ev) == SDLK_BACKSPACE) + launcher_binds_set_hotkey(m, m->capture_hk, 0, 0); + else + launcher_binds_set_hotkey(m, m->capture_hk, (int)LNG_EVKEY(ev), (int)LNG_EVMOD(ev)); launcher_model_cancel_hk_capture(m); return true; } diff --git a/src/common/launcher_binds.c b/src/common/launcher_binds.c index 5f2b615..1fd39ce 100644 --- a/src/common/launcher_binds.c +++ b/src/common/launcher_binds.c @@ -202,7 +202,7 @@ static const char* kHotkeyKey[LNG_HK_COUNT] = { "WindowBigger", "WindowSmaller", "VolumeUp", "VolumeDown", "DisplayPerf", "ToggleRenderer", "SolarBrighter", "SolarDimmer", "SolarLive", - "Rewind", "SaveStateMenu" + "Rewind", "SaveStateMenu", "TurboToggle" }; // Built-in defaults (shown when config.ini has no line; "" = unbound). static const char* kHotkeyDef[LNG_HK_COUNT] = { @@ -211,7 +211,7 @@ static const char* kHotkeyDef[LNG_HK_COUNT] = { * (psxrecomp host_keymap reads these from [KeyMap]). */ "", "", "Keypad +", "Keypad -", "F", "R", "", "", "", - "F8", "F7" + "F8", "F7", "F9" }; static void copy_str(char* d, size_t cap, const char* s) { @@ -410,7 +410,12 @@ static void reload_hotkey_display(LauncherModel* m) { char* hash = strchr(v, '#'); if (hash) *hash = 0; size_t vl = strlen(v); while (vl && (v[vl-1] == ' ' || v[vl-1] == '\t')) v[--vl] = 0; for (int h = 0; h < LNG_HK_COUNT; ++h) - if (ieq(p, klen, kHotkeyKey[h])) { copy_str(m->hotkeys[h], sizeof(m->hotkeys[h]), v); break; } + if (ieq(p, klen, kHotkeyKey[h])) { + const int unbound = !v[0] || ieq(v, strlen(v), "None") || + ieq(v, strlen(v), "(unbound)"); + copy_str(m->hotkeys[h], sizeof(m->hotkeys[h]), unbound ? "(unbound)" : v); + break; + } } free(text); } @@ -1450,6 +1455,10 @@ void launcher_binds_set_hotkey(LauncherModel* m, LngHotkey h, int keycode, int k if (h < 0 || h >= LNG_HK_COUNT) return; char val[64]; format_hotkey(keycode, kmod, val, sizeof(val)); - keymap_write(kHotkeyKey[h], val); + // keycode 0 = explicit unbind. Write the literal "None" rather than an + // empty value: the PSX runtime treats a present-but-empty/None line as + // "the user cleared this" and skips its built-in default, whereas a + // missing line keeps the default. + keymap_write(kHotkeyKey[h], val[0] ? val : "None"); copy_str(m->hotkeys[h], sizeof(m->hotkeys[h]), val[0] ? val : "(unbound)"); } diff --git a/src/common/launcher_model.c b/src/common/launcher_model.c index 56e9c75..2014e5f 100644 --- a/src/common/launcher_model.c +++ b/src/common/launcher_model.c @@ -52,7 +52,7 @@ static const char* kHotkeyNames[LNG_HK_COUNT] = { "Window bigger", "Window smaller", "Volume up", "Volume down", "FPS readout", "Toggle renderer", "Solar level up", "Solar level down", "Resume live solar", - "Rewind", "Save states menu" + "Rewind", "Save states menu", "Fast-forward toggle" }; static const char* kViewNames[8] = { "Dashboard", "Settings", "Controller", "Netplay", "Mods", @@ -276,6 +276,7 @@ void launcher_model_init(LauncherModel* m, m->has_texture_filter = game->has_texture_filter != 0; m->has_fmv_filter = game->has_fmv_filter != 0; m->has_screen_kind = game->has_screen_kind != 0; + m->has_scanlines = game->has_scanlines != 0; m->has_frame_interp = game->has_frame_interp != 0; m->has_spu_hq = game->has_spu_hq != 0; m->has_rewind_depth = game->has_rewind_depth != 0; @@ -1668,6 +1669,26 @@ const char* launcher_model_screen_kind_label(const LauncherModel* m) { return names[clampi(m->s.screen_kind, 0, n - 1)]; } +void launcher_model_toggle_scanlines(LauncherModel* m) { + if (!m || !m->has_scanlines) return; + m->s.scanlines = !m->s.scanlines; + /* First time on with no persisted strength: seed the default so the slider + * has a value to show (and the effect is visible). */ + if (m->s.scanlines && m->s.scanline_strength_pct == 0) + m->s.scanline_strength_pct = 50; +} + +void launcher_model_set_scanline_strength_pct(LauncherModel* m, int pct) { + if (!m || !m->has_scanlines) return; + m->s.scanline_strength_pct = clampi(pct, 1, 100); +} + +int launcher_model_scanline_strength_pct(const LauncherModel* m) { + if (!m) return 50; + int p = m->s.scanline_strength_pct; + return (p >= 1 && p <= 100) ? p : 50; /* 0 = unset -> default 50 */ +} + void launcher_model_toggle_frame_interp(LauncherModel* m) { m->s.frame_interp = !m->s.frame_interp; } diff --git a/src/common/launcher_model.h b/src/common/launcher_model.h index b8e10e3..92d0c02 100644 --- a/src/common/launcher_model.h +++ b/src/common/launcher_model.h @@ -103,6 +103,7 @@ typedef enum { LNG_HK_SOLAR_BRIGHTER, LNG_HK_SOLAR_DIMMER, LNG_HK_SOLAR_LIVE, LNG_HK_REWIND, /* PSX local rewind filmstrip → [KeyMap] Rewind */ LNG_HK_SAVE_STATE_MENU, /* PSX save-state slot menu → [KeyMap] SaveStateMenu */ + LNG_HK_TURBO_TOGGLE, /* PSX press-to-latch fast-forward → [KeyMap] TurboToggle */ LNG_HK_COUNT } LngHotkey; @@ -378,6 +379,7 @@ typedef struct { bool has_texture_filter; bool has_fmv_filter; bool has_screen_kind; + bool has_scanlines; // present-time scanline post-process (PSX) bool has_frame_interp; bool has_spu_hq; bool has_rewind_depth; @@ -759,6 +761,9 @@ void launcher_model_toggle_perspective_texturing(LauncherModel* m); bool launcher_model_geometry_correction_inert(const LauncherModel* m); void launcher_model_cycle_screen_kind(LauncherModel* m); // Raw/CRT/Composite/Trinitron const char* launcher_model_screen_kind_label(const LauncherModel* m); +void launcher_model_toggle_scanlines(LauncherModel* m); +void launcher_model_set_scanline_strength_pct(LauncherModel* m, int pct); // clamps 1..100 +int launcher_model_scanline_strength_pct(const LauncherModel* m); // effective (seeds 50) void launcher_model_toggle_frame_interp(LauncherModel* m); void launcher_model_cycle_interp_fps(LauncherModel* m); // {0,90,120,144,165,240} wrap const char* launcher_model_interp_fps_label(const LauncherModel* m); // "Display refresh"/"90 fps" diff --git a/src/consoles/psx/psx_profile.h b/src/consoles/psx/psx_profile.h index 405d756..af64cd5 100644 --- a/src/consoles/psx/psx_profile.h +++ b/src/consoles/psx/psx_profile.h @@ -119,7 +119,8 @@ static const SystemProfile kSystemProfilePsx = { (1u << LNG_HK_VOLUME_DOWN) | (1u << LNG_HK_DISPLAY_PERF) | RUI_PSX_REWIND_HOTKEY_MASK | - (1u << LNG_HK_SAVE_STATE_MENU)), + (1u << LNG_HK_SAVE_STATE_MENU) | + (1u << LNG_HK_TURBO_TOGGLE)), /* panels_dashboard */ kPanelsDashboardPsx, /* panels_settings */ kPanelsSettingsPsx, /* panels_controller */ kPanelsControllerCommon, @@ -161,6 +162,7 @@ static inline void launcher_profile_apply_psx(RecompLauncherCGameInfo* gi) { gi->has_fmv_filter = 1; /* MDEC decodes video at native res; how it is * scaled to the window is a player choice. */ gi->has_frame_interp = 0; gi->has_spu_hq = 1; gi->has_skip_fmv = 0; + gi->has_scanlines = 1; /* present-time CRT scanline post-process */ gi->has_turbo_loads = 1; gi->has_bios = 1; gi->has_deadzone_pct = 1; gi->has_rewind_depth = RECOMP_UI_PSX_HAS_REWIND ? 1 : 0; diff --git a/src/recomp_launcher.h b/src/recomp_launcher.h index e8f9e5e..94b4df6 100644 --- a/src/recomp_launcher.h +++ b/src/recomp_launcher.h @@ -1021,6 +1021,15 @@ struct RecompLauncherCSettings { * against the roster, falling back to disc 1. Appended additively. */ int disc_index; + /* Scanline post-process on/off (GameInfo.has_scanlines consoles). 0 = off + * (also the unset default, like rewind_enabled — the host default is off, so + * a zero-initialized host predating this field gets the right answer), 1 = + * on. Appended additively. */ + int scanlines; + /* Scanline dark-gap depth as a percent. 0 = unset -> the model seeds 50; the + * effective range is 1..100. Stored as a percent (not 0..1) so the whole + * settings struct stays plain-int. Appended additively. */ + int scanline_strength_pct; /* NDS virtual stylus overlay. 0 is unset and means the launcher default * for the console, which is enabled; 1 is explicitly enabled, -1 is * explicitly disabled. Bindings live in assist_key_bind/assist_pad_bind so @@ -1054,6 +1063,10 @@ struct RecompLauncherCSettings { /* Hosts can #ifdef on this to stay source-compatible with older recomp-ui. */ #define RECOMP_LAUNCHER_HAS_FRAME_BLEND 1 +/* Scanline post-process (Settings.scanlines / scanline_strength_pct, + * GameInfo.has_scanlines). Hosts #ifdef on this to stay source-compatible with + * older recomp-ui that lacks the fields. */ +#define RECOMP_LAUNCHER_HAS_SCANLINES 1 // ---- host verification/inspection results (filled by the callbacks below) ---- // Plain-C structs so a host can implement the callbacks with zero launcher @@ -1624,6 +1637,11 @@ typedef struct RecompLauncherCGameInfo { int (*persist_setup_discs)(void* ctx, const char* const* disc_paths, int disc_count, const char* bios_path); + /* Display row for Settings.scanlines / scanline_strength_pct: a present-time + * scanline post-process (a checkbox plus a strength slider). Only meaningful + * for a console whose runtime implements it (PSX); everything else leaves + * this 0 and the rows are absent. Appended for ABI stability. */ + int has_scanlines; /* NDS input convenience setting. When set, Settings shows a Virtual Stylus * opt-out and the Controller page may expose host-owned bindings for it. */ int has_virtual_stylus;