Compare commits

...

6 Commits

Author SHA1 Message Date
bicarus 5065a92d55 launcher: don't warn about hook option conflicts (#771)
DLL hook options (-k and -z) allow multiple flags being specified in
both spicecfg and via command line; they never conflict as they are
additive. Remove the warning about option conflicts.
2026-06-20 15:46:45 -07:00
bicarus 5483e8e2c0 ponp: make Force Sub Redraw an option (#770)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Similar to how it is on SDVX, turning this on can cause graphical
glitches depending on the GPU, so it needs to be an option that users
can toggle.

## Testing
2026-06-19 22:30:08 -07:00
bicarus e8949a2612 overlay: fix highlights, alignment in presets table (#769)
* Fix table row highlight hover detection in Options tab when row has
two lines of text
* Fix text alignment in controller presets table
2026-06-19 00:40:54 -07:00
bicarus db71a0b24d cfg: center window on launch (#768) 2026-06-18 23:02:23 -07:00
bicarus d3d5422768 cfg: enforce minimum window size (#767) 2026-06-18 21:10:42 -07:00
bicarus-dev 5185f753e5 fix truncated text 2026-06-18 17:32:59 -07:00
9 changed files with 103 additions and 40 deletions
+38 -1
View File
@@ -21,6 +21,8 @@ static const char *CLASS_NAME = "ConfiguratorWindow";
static std::string WINDOW_TITLE;
static int WINDOW_SIZE_X = 800;
static int WINDOW_SIZE_Y = 600;
static const int WINDOW_MIN_SIZE_X = 540;
static const int WINDOW_MIN_SIZE_Y = 300;
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
static const UINT_PTR RENDER_TIMER_ID = 1;
@@ -85,6 +87,23 @@ static cfg::ConfiguratorWindow *get_state(HWND hWnd) {
GetWindowLongPtrW(hWnd, GWLP_USERDATA));
}
// computes the top-left position that centers a window of the given size on
// the primary monitor's work area (the desktop minus the taskbar); falls back
// to (0, 0) if the monitor info can't be queried.
static POINT center_on_primary_monitor(int width, int height) {
POINT pos = { 0, 0 };
HMONITOR mon = MonitorFromPoint(pos, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO mi {};
mi.cbSize = sizeof(mi);
if (GetMonitorInfo(mon, &mi)) {
const int work_w = mi.rcWork.right - mi.rcWork.left;
const int work_h = mi.rcWork.bottom - mi.rcWork.top;
pos.x = mi.rcWork.left + (work_w - width) / 2;
pos.y = mi.rcWork.top + (work_h - height) / 2;
}
return pos;
}
// Returns the refresh rate (Hz) of the monitor the window currently lives on,
// clamped to a sane range. Falls back to 60 if detection fails or the value
// looks invalid (DEVMODE may report 0 or 1 to mean "use hardware default").
@@ -243,8 +262,10 @@ cfg::ConfiguratorWindow::~ConfiguratorWindow() {
void cfg::ConfiguratorWindow::run() {
const POINT pos = center_on_primary_monitor(WINDOW_SIZE_X, WINDOW_SIZE_Y);
SetWindowPos(this->hWnd, HWND_TOP, pos.x, pos.y, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
// show window
SetWindowPos(this->hWnd, HWND_TOP, 0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
ShowWindow(this->hWnd, SW_SHOWNORMAL);
UpdateWindow(this->hWnd);
@@ -290,6 +311,22 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
}
break;
}
case WM_GETMINMAXINFO: {
// enforce a minimum window size so the UI can't be shrunk to a
// point where the tabs/controls become unusable. The minimum is
// expressed in client-area pixels and converted to a full window
// size that accounts for the current frame/title-bar style.
RECT rc = { 0, 0, WINDOW_MIN_SIZE_X, WINDOW_MIN_SIZE_Y };
DWORD style = static_cast<DWORD>(GetWindowLongPtrW(hWnd, GWL_STYLE));
DWORD ex_style = static_cast<DWORD>(GetWindowLongPtrW(hWnd, GWL_EXSTYLE));
if (AdjustWindowRectEx(&rc, style, FALSE, ex_style)) {
auto *mmi = reinterpret_cast<MINMAXINFO *>(lParam);
mmi->ptMinTrackSize.x = rc.right - rc.left;
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
}
break;
}
case WM_CREATE: {
// set user data of window to class pointer
@@ -1273,9 +1273,11 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
wintouchemu::update();
// newer versions of exceed gear needs SUBSCREEN_FORCE_REDRAW
// (when enabled on older versions of EG, you end up with graphical glitches on the subscreen
// early versions of popn HC needs this as well, otherwise the subscreen doesn't update at all
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW || games::popn::is_pikapika_model()) {
// (when enabled on older versions of EG, you end up with graphical glitches on the subscreen)
//
// early versions of popn HC needs this as well, but not on by default as it can cause
// graphical glitches on some GPUs
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) {
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
}
}
+16 -2
View File
@@ -643,6 +643,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnNativeTouch].value_bool()) {
games::popn::NATIVE_TOUCH = true;
}
if (options[launcher::Options::PopnSubRedraw].value_bool()) {
SUBSCREEN_FORCE_REDRAW = true;
}
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true;
}
@@ -1546,8 +1549,19 @@ int main_implementation(int argc, char *argv[]) {
// print out conflicts
size_t conflicts = 0;
for (const auto &option : options) {
if (option.conflicting && option.get_definition().type != OptionType::Bool) {
for (size_t i = 0; i < options.size(); i++) {
// InjectHook / EarlyInjectHook accept multiple values, so command line and
// spicecfg entries are merged rather than conflicting; don't warn about them
if (i == (size_t) launcher::Options::InjectHook ||
i == (size_t) launcher::Options::EarlyInjectHook) {
continue;
}
const auto &option = options[i];
// ignore Boolean values
if (option.get_definition().type == OptionType::Bool) {
continue;
}
if (option.conflicting) {
conflicts += 1;
const auto& value = option.get_definition().sensitive ? "*****" : option.value;
if (launcher::USE_CMD_OVERRIDE) {
+11
View File
@@ -1138,6 +1138,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Pop'n Music",
.category = "Advanced Game Options",
},
{
// PopnSubRedraw
.title = "Pop'n Music PikaPika Subscreen Force Redraw",
.name = "popnsubredraw",
.desc = "Check if submonitor in fullscreen mode appears stuck; "
"this option forces subscreen to redraw every frame.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Advanced Game Options",
.quick_setting_category = "Game",
},
{
.title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm",
+1
View File
@@ -108,6 +108,7 @@ namespace launcher {
PopnNoSub,
PopnSubMonitorOverride,
PopnNativeTouch,
PopnSubRedraw,
LoadHelloPopnMusicModule,
LoadGitaDoraModule,
GitaDoraTwoChannelAudio,
+12 -11
View File
@@ -2,6 +2,7 @@
#include <cmath>
#include "external/imgui/imgui.h"
#include "external/imgui/imgui_internal.h"
#include "overlay/overlay.h"
@@ -209,17 +210,17 @@ namespace ImGui {
return open;
}
void InvisibleTableRowSelectable() {
ImGui::TableSetColumnIndex(0);
ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::PushTabStop(false); // prevent tab navigation
ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopTabStop();
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
void HighlightTableRowOnHover() {
// hit-test the row rect directly so the row layout and height are untouched
ImGuiTable *table = ImGui::GetCurrentTable();
if (table == nullptr) {
return;
}
if (ImGui::IsWindowHovered() &&
ImGui::IsMouseHoveringRect(
ImVec2(table->WorkRect.Min.x, table->RowPosY1),
ImVec2(table->WorkRect.Max.x, table->RowPosY2),
false)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ namespace ImGui {
void TextTruncated(const std::string& p_text, float p_truncated_width);
bool DeleteButton(const std::string& tooltip);
bool ClearButton(const std::string& tooltip);
void InvisibleTableRowSelectable();
void HighlightTableRowOnHover();
// Config tab bar with extra label padding and uniform, centered tab widths.
// Wrap items in BeginPaddedTabItem between BeginPaddedTabBar/EndTabBar.
+18 -20
View File
@@ -928,18 +928,14 @@ namespace overlay::windows {
// primary
build_button(name, primary_button, &primary_button, button_it, button_it_max, 0);
ImGui::PushID(&primary_button);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
ImGui::HighlightTableRowOnHover();
// alternatives
int alt_index = 1; // 0 is primary
for (auto &alt : primary_button.getAlternatives()) {
if (alt.isValid()) {
build_button(name, primary_button, &alt, button_it, button_it_max, alt_index);
ImGui::PushID(&alt);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
ImGui::HighlightTableRowOnHover();
}
alt_index++;
}
@@ -2519,8 +2515,7 @@ namespace overlay::windows {
edit_analog_popup(analog, title);
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
ImGui::HighlightTableRowOnHover();
// clean up
ImGui::PopID();
@@ -3174,16 +3169,12 @@ namespace overlay::windows {
}
build_light(light, &light, i, 0);
ImGui::PushID(&light);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
ImGui::HighlightTableRowOnHover();
int alt_index = 1;
for (auto &alt : light.getAlternatives()) {
if (alt.isValid()) {
build_light(light, &alt, i, alt_index);
ImGui::PushID(&alt);
ImGui::InvisibleTableRowSelectable();
ImGui::PopID();
ImGui::HighlightTableRowOnHover();
}
alt_index++;
}
@@ -4209,8 +4200,10 @@ namespace overlay::windows {
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card overrides");
ImGui::Spacing();
ImGui::TextUnformatted(
"Specify hardcoded card numbers here. This will always take priority when Insert Card is pressed.");
ImGui::TextWrapped(
"%s",
"Specify hardcoded card numbers here.\n\n"
"Overrides will always take priority when Insert Card is pressed.");
ImGui::Spacing();
// read in values from options
@@ -4387,7 +4380,8 @@ namespace overlay::windows {
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card from text files");
ImGui::Spacing();
ImGui::TextUnformatted(
ImGui::TextWrapped(
"%s",
"Use text files on disk; its content will be read when Insert Card is pressed.");
ImGui::Spacing();
@@ -5293,8 +5287,7 @@ namespace overlay::windows {
}
}
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
ImGui::HighlightTableRowOnHover();
// next item
ImGui::PopID();
@@ -5787,23 +5780,28 @@ namespace overlay::windows {
// name
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::TextTruncated(
t.name, ImGui::GetContentRegionAvail().x - overlay::apply_scaling(20));
// type
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(t.is_builtin ? "Built-in" : "User");
// buttons
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("%d", (int)t.buttons.size());
// analogs
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("%d", (int)t.analogs.size());
// lights
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("%d", (int)t.lights.size());
// actions
@@ -5831,7 +5829,7 @@ namespace overlay::windows {
}
}
ImGui::InvisibleTableRowSelectable();
ImGui::HighlightTableRowOnHover();
ImGui::PopID();
}
@@ -876,8 +876,7 @@ namespace overlay::windows {
ImGui::EndDisabled();
}
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
ImGui::HighlightTableRowOnHover();
ImGui::PopID();
}