M1: UI-fit instrumentation + headless ui_fit_test + first overflow fixes
Add panel overflow instrumentation (record_panel_fit reading ImGui ScrollMax), a forced layout-reference + debug-aware center split, and host harness commands (uisize/uifit). New headless ui_fit_test drives the real Controllers + Audio panels at reference resolutions with Debug details on and asserts no panel overflows its assigned size; the Audio panel gains a demo mode so its richest content renders without a live mirror. Fixes from the measured overflow: widen the center column (was too narrow -> horizontal overflow), merge the Controllers poll/round-trip tables and fold the trigger line into the slot line, and make the center height split Debug-details-aware (Video's height is mirroring-driven, not debug-driven, so a static split can't serve both modes). Controllers + Audio now fit at 1920x1080 with max info. Video/Injection/Log fit is finalized at the end via the live uifit harness (M1 stays open until then). 15/15 ctest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -206,11 +206,24 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
|
||||
manage_overrides(status, pid); // auto-apply a saved override / auto-save a caught format
|
||||
|
||||
if (mirror_.running())
|
||||
// Live mirror state -- or synthetic worst-case values in the UI-fit demo (no live
|
||||
// AudioMirror), so the headless fit test renders the richest content path.
|
||||
const bool running = demo_ ? true : mirror_.running();
|
||||
const AudioMirror::Source src = demo_ ? AudioMirror::Source::Loopback : mirror_.source();
|
||||
const unsigned m_rate = demo_ ? 44100u : mirror_.sample_rate();
|
||||
const unsigned m_ch = demo_ ? 6u : mirror_.channels();
|
||||
const unsigned m_buffered = demo_ ? 9999u : mirror_.buffered_ms();
|
||||
const std::string mirror_status =
|
||||
demo_ ? std::string("Loopback (echo): re-rendering the game's audio on the default endpoint.")
|
||||
: mirror_.status();
|
||||
const std::string reason =
|
||||
demo_ ? std::string("render-hook did not publish a format in time; using WASAPI process loopback.")
|
||||
: mirror_.fallback_reason();
|
||||
|
||||
if (running)
|
||||
{
|
||||
const AudioMirror::Source src = mirror_.source();
|
||||
const bool hooked = src == AudioMirror::Source::Hooked;
|
||||
ImGui::TextColored(kGreen, "Mirroring %u Hz, %u ch", mirror_.sample_rate(), mirror_.channels());
|
||||
ImGui::TextColored(kGreen, "Mirroring %u Hz, %u ch", m_rate, m_ch);
|
||||
ImGui::Text("Source:");
|
||||
ImGui::SameLine();
|
||||
if (hooked)
|
||||
@@ -225,7 +238,7 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextColored(kAmber, "%s", mirror_.source_name());
|
||||
ImGui::TextColored(kAmber, "%s", demo_ ? "Loopback (echo)" : mirror_.source_name());
|
||||
}
|
||||
|
||||
// Where the rendered format came from -- so it's clear the playback pitch is right.
|
||||
@@ -242,9 +255,8 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
{
|
||||
ImGui::TextColored(kGreen, "device endpoint (known, post-mix)");
|
||||
}
|
||||
ImGui::Text("Buffered: %4u ms", mirror_.buffered_ms());
|
||||
ImGui::Text("Buffered: %4u ms", m_buffered);
|
||||
}
|
||||
const std::string mirror_status = mirror_.status();
|
||||
if (!mirror_status.empty())
|
||||
{
|
||||
ImGui::TextWrapped("%s", mirror_status.c_str());
|
||||
@@ -252,7 +264,6 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
|
||||
// Why we're on loopback instead of the no-echo hooked path (empty when hooked). Amber
|
||||
// because it's a degraded-but-working state that auto-resolves when the hook catches up.
|
||||
const std::string reason = mirror_.fallback_reason();
|
||||
if (!reason.empty())
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, kAmber);
|
||||
@@ -262,7 +273,7 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
|
||||
// Only the loopback path leaves the game audible locally (the echo); the
|
||||
// hooked path silences it, so don't warn there.
|
||||
if (mirror_.source() == AudioMirror::Source::Loopback)
|
||||
if (src == AudioMirror::Source::Loopback)
|
||||
{
|
||||
bool audio_hook_on = false;
|
||||
const std::uint32_t hn =
|
||||
@@ -299,6 +310,7 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
|
||||
if (!debug_details)
|
||||
{
|
||||
record_panel_fit("Audio");
|
||||
ImGui::End();
|
||||
return; // the per-stream table below is diagnostic detail
|
||||
}
|
||||
@@ -372,7 +384,7 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
// --- Operator controls: re-measure / override the primary stream's format -----
|
||||
// For when detection is wrong (re-measure) or unrecoverable (override the channels/
|
||||
// bit-depth the hook had to assume). Only meaningful while mirroring is active.
|
||||
if (mirror_.running())
|
||||
if (running)
|
||||
{
|
||||
ImGui::SeparatorText("Fix the primary stream (debug)");
|
||||
if (ImGui::Button("Re-measure rate"))
|
||||
@@ -414,6 +426,7 @@ void AudioPanel::draw_ui(const HookStatusView& status, bool debug_details)
|
||||
}
|
||||
}
|
||||
|
||||
record_panel_fit("Audio");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,15 @@ public:
|
||||
// `debug_details` on, the per-stream table is shown.
|
||||
void draw_ui(const HookStatusView& status, bool debug_details);
|
||||
|
||||
// UI-fit check (headless test) only: render the richest content path -- as if a
|
||||
// loopback mirror were running with long status/reason strings -- without a live
|
||||
// AudioMirror, so the fit test can measure the panel's worst-case size. Never set in
|
||||
// the shipping host (the render path is identical, just fed synthetic values).
|
||||
void dev_set_demo(bool on)
|
||||
{
|
||||
demo_ = on;
|
||||
}
|
||||
|
||||
#ifdef COOP_TEST_HARNESS
|
||||
// Test-harness hooks (debug builds only): drive the real audio code paths and read
|
||||
// state back, incl. targeting a windowless process by pid (coop_tone has no window).
|
||||
@@ -89,6 +98,7 @@ private:
|
||||
HWND target_ = nullptr;
|
||||
DWORD dev_pid_ = 0; // test harness only: force a (windowless) target pid; 0 in production
|
||||
bool enabled_ = false;
|
||||
bool demo_ = false; // UI-fit test only: render the richest content with synthetic values
|
||||
AudioMirror mirror_;
|
||||
|
||||
// Per-game persisted overrides + per-target session bookkeeping.
|
||||
|
||||
@@ -186,6 +186,7 @@ void CapturePanel::draw_ui(const FrameStats& stats)
|
||||
draw_pipeline_metrics(stats);
|
||||
draw_perf_graphs(stats);
|
||||
|
||||
record_panel_fit("Video");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ void draw_pad(int index, const PadInfo& pad, bool debug_details)
|
||||
}
|
||||
|
||||
ImGui::TextColored(kGreen, "Slot %d [%s]", index, pad.source.c_str());
|
||||
if (debug_details)
|
||||
{
|
||||
// Triggers on the slot line (saves a row); thumbsticks below.
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("LT %3u RT %3u", pad.state.left_trigger, pad.state.right_trigger);
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
ImGui::TextUnformatted("Buttons: ");
|
||||
@@ -57,7 +63,6 @@ void draw_pad(int index, const PadInfo& pad, bool debug_details)
|
||||
|
||||
if (debug_details)
|
||||
{
|
||||
ImGui::Text("LT %3u RT %3u", pad.state.left_trigger, pad.state.right_trigger);
|
||||
ImGui::Text("L (%6d, %6d) R (%6d, %6d)", pad.state.thumb_lx, pad.state.thumb_ly,
|
||||
pad.state.thumb_rx, pad.state.thumb_ry);
|
||||
}
|
||||
@@ -117,6 +122,7 @@ void ControllersPanel::draw(const InputSnapshot& input, const HookStatusView& st
|
||||
if (!status.attached)
|
||||
{
|
||||
ImGui::TextDisabled("Not injected (no XInput hook).");
|
||||
record_panel_fit("Controllers");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
@@ -150,16 +156,24 @@ void ControllersPanel::draw(const InputSnapshot& input, const HookStatusView& st
|
||||
ImGui::TextColored(kGrey, "Game reading controller: idle");
|
||||
}
|
||||
|
||||
// One per-slot table covers both the hook's poll counters and the input round-trip
|
||||
// (what we forwarded vs what the game read back through the hook). A round-trip mismatch
|
||||
// isolates a tool->game forwarding problem from an input->tool one. Merged into a single
|
||||
// table so the (debug) controller view stays inside its panel even with every slot busy.
|
||||
if (debug_details &&
|
||||
ImGui::BeginTable("slots", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp))
|
||||
ImGui::BeginTable("slots", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp))
|
||||
{
|
||||
ImGui::TableSetupColumn("Slot");
|
||||
ImGui::TableSetupColumn("GetState/s");
|
||||
ImGui::TableSetupColumn("GetState total");
|
||||
ImGui::TableSetupColumn("GetCaps total");
|
||||
ImGui::TableSetupColumn("Poll/s");
|
||||
ImGui::TableSetupColumn("Polls");
|
||||
ImGui::TableSetupColumn("Forwarded btn/LX,LY");
|
||||
ImGui::TableSetupColumn("Game read btn/LX,LY");
|
||||
ImGui::TableHeadersRow();
|
||||
const auto& fwd = input.pads;
|
||||
for (int i = 0; i < static_cast<int>(kMaxPads); ++i)
|
||||
{
|
||||
const CoopPadState& f = fwd[i].state;
|
||||
const CoopPadState& r = status.read_state[i];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", i);
|
||||
@@ -175,42 +189,15 @@ void ControllersPanel::draw(const InputSnapshot& input, const HookStatusView& st
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%llu", static_cast<unsigned long long>(status.get_state[i]));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%llu", static_cast<unsigned long long>(status.get_caps[i]));
|
||||
ImGui::Text("0x%04X %6d,%6d", f.buttons, f.thumb_lx, f.thumb_ly);
|
||||
ImGui::TableNextColumn();
|
||||
const bool match = f.buttons == r.buttons && f.thumb_lx == r.thumb_lx && f.thumb_ly == r.thumb_ly;
|
||||
ImGui::TextColored(match ? kGreen : kGrey, "0x%04X %6d,%6d", r.buttons, r.thumb_lx, r.thumb_ly);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// Round-trip view: what we forwarded (the active backend's pad, tagged per slot in
|
||||
// the Incoming section above) vs what the game actually read back through the hook.
|
||||
// A mismatch isolates a tool->game forwarding problem from an input->tool one.
|
||||
if (debug_details)
|
||||
{
|
||||
ImGui::SeparatorText("Round-trip (forwarded vs game read)");
|
||||
if (ImGui::BeginTable("roundtrip", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp))
|
||||
{
|
||||
ImGui::TableSetupColumn("Slot");
|
||||
ImGui::TableSetupColumn("Forwarded btn / LX,LY");
|
||||
ImGui::TableSetupColumn("Game read btn / LX,LY");
|
||||
ImGui::TableHeadersRow();
|
||||
const auto& fwd = input.pads;
|
||||
for (int i = 0; i < static_cast<int>(kMaxPads); ++i)
|
||||
{
|
||||
const CoopPadState& f = fwd[i].state;
|
||||
const CoopPadState& r = status.read_state[i];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", i);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%04X %6d,%6d", f.buttons, f.thumb_lx, f.thumb_ly);
|
||||
ImGui::TableNextColumn();
|
||||
const bool match = f.buttons == r.buttons && f.thumb_lx == r.thumb_lx && f.thumb_ly == r.thumb_ly;
|
||||
ImGui::TextColored(match ? kGreen : kGrey, "0x%04X %6d,%6d", r.buttons, r.thumb_lx, r.thumb_ly);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::TextDisabled("Slot tags in 'Incoming' above show which backend (XInput / Steam) fed each slot.");
|
||||
}
|
||||
|
||||
record_panel_fit("Controllers");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
@@ -657,6 +657,7 @@ void InjectionPanel::draw(bool debug_details)
|
||||
|
||||
draw_hook_status(debug_details);
|
||||
|
||||
record_panel_fit("Injection");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,19 @@ std::string apply_test_command(const std::string& cmd, coop::UiState& ui, coop::
|
||||
window.request_screenshot(p);
|
||||
return "ok";
|
||||
}
|
||||
if (v == "uisize")
|
||||
{
|
||||
// Force a reference layout size so the UI-fit check is monitor-independent.
|
||||
coop::set_layout_reference(static_cast<float>(num(1)), static_cast<float>(num(2)));
|
||||
return "ok";
|
||||
}
|
||||
if (v == "uifit")
|
||||
{
|
||||
// Report any panel whose content overflowed its assigned size last frame.
|
||||
char buf[256];
|
||||
coop::panel_fit_report(buf, sizeof(buf));
|
||||
return buf;
|
||||
}
|
||||
if (v == "quit")
|
||||
{
|
||||
ui.request_quit = true;
|
||||
@@ -387,6 +400,8 @@ int run()
|
||||
window.request_screenshot(screenshot_path()); // captured at Present, overlay included
|
||||
}
|
||||
|
||||
coop::reset_panel_fit(); // panels record their overflow as they draw (UI-fit check)
|
||||
coop::set_layout_debug(ui.debug_details); // center split adapts to the debug verbosity
|
||||
if (show_overlay)
|
||||
{
|
||||
coop::draw_main_menu_bar(ui, stats);
|
||||
|
||||
@@ -57,6 +57,23 @@ int g_startup_force = 4;
|
||||
// True when ImGui loaded a saved layout (.ini) at startup; then the startup force is
|
||||
// suppressed so the restored window positions survive (Reset layout still re-applies).
|
||||
bool g_had_persisted_layout = false;
|
||||
|
||||
// Forced reference WorkSize for the UI-fit check (0 = use the live viewport).
|
||||
float g_ref_w = 0.0f;
|
||||
float g_ref_h = 0.0f;
|
||||
|
||||
// Whether Debug details is on, so the center column splits its height accordingly.
|
||||
bool g_layout_debug = false;
|
||||
|
||||
// Per-frame panel-overflow registry (UI-fit instrumentation).
|
||||
struct PanelFit
|
||||
{
|
||||
char name[24];
|
||||
float over_x;
|
||||
float over_y;
|
||||
};
|
||||
PanelFit g_fits[8];
|
||||
int g_fit_count = 0;
|
||||
} // namespace
|
||||
|
||||
void register_ui_settings(UiState& ui)
|
||||
@@ -95,20 +112,100 @@ void apply_layout_end_frame()
|
||||
}
|
||||
}
|
||||
|
||||
void set_layout_reference(float w, float h)
|
||||
{
|
||||
g_ref_w = w > 0.0f ? w : 0.0f;
|
||||
g_ref_h = h > 0.0f ? h : 0.0f;
|
||||
}
|
||||
|
||||
void set_layout_debug(bool on)
|
||||
{
|
||||
g_layout_debug = on;
|
||||
}
|
||||
|
||||
void reset_panel_fit()
|
||||
{
|
||||
g_fit_count = 0;
|
||||
}
|
||||
|
||||
void record_panel_fit(const char* name)
|
||||
{
|
||||
// Called while the panel's window is current (before End()). ScrollMax is the content
|
||||
// overflow beyond the visible region (set every frame whether or not a scrollbar shows),
|
||||
// so > 0 on either axis means content is cut off at the assigned size.
|
||||
const float ox = ImGui::GetScrollMaxX();
|
||||
const float oy = ImGui::GetScrollMaxY();
|
||||
if (g_fit_count >= static_cast<int>(sizeof(g_fits) / sizeof(g_fits[0])))
|
||||
{
|
||||
return;
|
||||
}
|
||||
PanelFit& f = g_fits[g_fit_count++];
|
||||
std::snprintf(f.name, sizeof(f.name), "%s", name);
|
||||
f.over_x = ox;
|
||||
f.over_y = oy;
|
||||
}
|
||||
|
||||
bool panel_fit_overflow(float* worst_x, float* worst_y)
|
||||
{
|
||||
float mx = 0.0f, my = 0.0f;
|
||||
for (int i = 0; i < g_fit_count; ++i)
|
||||
{
|
||||
mx = std::max(mx, g_fits[i].over_x);
|
||||
my = std::max(my, g_fits[i].over_y);
|
||||
}
|
||||
if (worst_x != nullptr)
|
||||
{
|
||||
*worst_x = mx;
|
||||
}
|
||||
if (worst_y != nullptr)
|
||||
{
|
||||
*worst_y = my;
|
||||
}
|
||||
return mx > 0.5f || my > 0.5f;
|
||||
}
|
||||
|
||||
void panel_fit_report(char* buf, int cap)
|
||||
{
|
||||
if (buf == nullptr || cap <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int n = 0;
|
||||
bool any = false;
|
||||
for (int i = 0; i < g_fit_count && n < cap - 1; ++i)
|
||||
{
|
||||
if (g_fits[i].over_x <= 0.5f && g_fits[i].over_y <= 0.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
n += std::snprintf(buf + n, static_cast<size_t>(cap - n), "%s%s:%.0f,%.0f", n > 0 ? " " : "",
|
||||
g_fits[i].name, g_fits[i].over_x, g_fits[i].over_y);
|
||||
}
|
||||
if (!any)
|
||||
{
|
||||
std::snprintf(buf, static_cast<size_t>(cap), "fit");
|
||||
}
|
||||
}
|
||||
|
||||
void apply_panel_layout(Panel panel)
|
||||
{
|
||||
const ImGuiViewport* vp = ImGui::GetMainViewport();
|
||||
const ImVec2 o = vp->WorkPos; // below the main menu bar
|
||||
const ImVec2 s = vp->WorkSize;
|
||||
const ImVec2 o = vp->WorkPos; // below the main menu bar
|
||||
// Use the forced reference size when the UI-fit check set one, so panel sizes are
|
||||
// deterministic regardless of the live monitor; otherwise the real work area.
|
||||
const ImVec2 s = (g_ref_w > 0.0f) ? ImVec2(g_ref_w, g_ref_h) : vp->WorkSize;
|
||||
const float m = 12.0f; // outer margin
|
||||
const float gap = 10.0f; // between panels
|
||||
|
||||
// Three columns: Injection (left) and Log (right) are the wide ones (window list
|
||||
// titles / log lines); the center control-panel column is the narrow one.
|
||||
// Three columns. The center control-panel column carries the debug-heavy tables
|
||||
// (controller poll / round-trip, audio streams, override editor), so it gets the most
|
||||
// width so that content fits without horizontal scrolling at max info; Injection (left)
|
||||
// and Log (right) still have ample room for window-list titles / log lines.
|
||||
const float usable_w = std::max(600.0f, s.x - 2.0f * m - 2.0f * gap);
|
||||
const float left_w = usable_w * 0.36f;
|
||||
const float center_w = usable_w * 0.28f;
|
||||
const float right_w = usable_w * 0.36f;
|
||||
const float left_w = usable_w * 0.30f;
|
||||
const float center_w = usable_w * 0.40f;
|
||||
const float right_w = usable_w * 0.30f;
|
||||
const float left_x = o.x + m;
|
||||
const float center_x = left_x + left_w + gap;
|
||||
const float right_x = center_x + center_w + gap;
|
||||
@@ -116,13 +213,18 @@ void apply_panel_layout(Panel panel)
|
||||
const float top = o.y + m;
|
||||
const float full_h = std::max(200.0f, s.y - 2.0f * m);
|
||||
|
||||
// Center column stacks Controllers / Video / Audio. Controllers has the most
|
||||
// content (incoming pads + poll + round-trip tables) so it gets the most height;
|
||||
// Audio the least (proportions measured from the operator's preferred layout).
|
||||
// Center column stacks Controllers / Video / Audio. The split depends on Debug details:
|
||||
// with it on, Controllers (incoming pads + the merged poll/round-trip table) and Audio
|
||||
// (status + stream table + override editor) carry tall content and get the larger shares;
|
||||
// with it off, those collapse to a few lines and Video's perf graphs are the tall content.
|
||||
// Tuned so each panel fits its content at max info at >=1080p.
|
||||
const float stack_avail = std::max(150.0f, full_h - 2.0f * gap);
|
||||
const float ctrl_h = stack_avail * 0.45f;
|
||||
const float vid_h = stack_avail * 0.32f;
|
||||
const float audio_h = stack_avail * 0.23f;
|
||||
const float ctrl_frac = g_layout_debug ? 0.44f : 0.40f;
|
||||
const float vid_frac = g_layout_debug ? 0.19f : 0.37f;
|
||||
const float audio_frac = g_layout_debug ? 0.37f : 0.23f;
|
||||
const float ctrl_h = stack_avail * ctrl_frac;
|
||||
const float vid_h = stack_avail * vid_frac;
|
||||
const float audio_h = stack_avail * audio_frac;
|
||||
|
||||
ImVec2 pos, size;
|
||||
switch (panel)
|
||||
@@ -151,8 +253,10 @@ void apply_panel_layout(Panel panel)
|
||||
|
||||
// Force the computed layout only when the user asks (Reset layout) or on a fresh
|
||||
// install with no saved layout to restore. Otherwise FirstUseEver lets ImGui's
|
||||
// restored .ini positions stand (and still seeds any brand-new panel).
|
||||
const bool force = g_layout_reset || (!g_had_persisted_layout && g_startup_force > 0);
|
||||
// restored .ini positions stand (and still seeds any brand-new panel). A forced
|
||||
// reference size (UI-fit check) also forces, so the assigned sizes are exact.
|
||||
const bool force =
|
||||
g_layout_reset || g_ref_w > 0.0f || (!g_had_persisted_layout && g_startup_force > 0);
|
||||
const ImGuiCond cond = force ? ImGuiCond_Always : ImGuiCond_FirstUseEver;
|
||||
ImGui::SetNextWindowPos(pos, cond);
|
||||
ImGui::SetNextWindowSize(size, cond);
|
||||
|
||||
@@ -56,6 +56,37 @@ void apply_layout_end_frame();
|
||||
// clobber the user's saved window positions); only an explicit Reset layout re-applies.
|
||||
void set_layout_persisted(bool had_persisted_layout);
|
||||
|
||||
// --- UI-fit instrumentation (M1) -------------------------------------------
|
||||
// The panels open at fixed sizes that scale with the monitor; with Debug details on
|
||||
// (maximum information) a panel's content can exceed its assigned size and get scrolled
|
||||
// out of view. These hooks let a test (and the debug harness) drive the overlay to its
|
||||
// fullest and assert nothing overflows.
|
||||
//
|
||||
// Force a reference WorkSize for apply_panel_layout so the per-panel sizes are
|
||||
// deterministic regardless of the real monitor (w<=0 restores the live viewport). When
|
||||
// set, the computed layout is force-applied every frame.
|
||||
void set_layout_reference(float w, float h);
|
||||
|
||||
// Tell the layout whether Debug details is on, so the center column can split its height
|
||||
// to match: with debug on, Controllers and Audio carry tall tables/editors and get more;
|
||||
// with it off, Video's perf graphs are the tall content and get more. Call once per frame
|
||||
// before the panels draw (a layout reset / startup-force re-applies it).
|
||||
void set_layout_debug(bool on);
|
||||
|
||||
// Per-frame overflow tracking. reset_panel_fit() at the top of the frame; each panel
|
||||
// calls record_panel_fit(name) while its window is current (just before End()) to capture
|
||||
// how far its content overflows the assigned size (ScrollMax x/y, 0 = fits).
|
||||
void reset_panel_fit();
|
||||
void record_panel_fit(const char* name);
|
||||
|
||||
// Worst overflow (px) seen since the last reset across all recorded panels. Returns true
|
||||
// if anything overflowed; out params (optional) receive the worst horizontal/vertical.
|
||||
bool panel_fit_overflow(float* worst_x, float* worst_y);
|
||||
|
||||
// One-line "Panel:x,y ..." report of every panel that overflowed since the last reset
|
||||
// (or "fit" if all fit). Written into buf (NUL-terminated, clamped to cap).
|
||||
void panel_fit_report(char* buf, int cap);
|
||||
|
||||
// Rolling frame-timing over a ~1 s window, recomputed each window so the status
|
||||
// bar can show a stable FPS plus the min/max frame time (jitter) underneath it.
|
||||
class FrameStats
|
||||
|
||||
Reference in New Issue
Block a user