Panels used to Begin at cascade positions, so they overlapped and clipped. A new apply_panel_layout() in app_chrome positions/sizes each panel from the main viewport work area (ImGuiCond_FirstUseEver, still movable): Injection left column full height (room for hook diagnostics), Controllers/Video/Audio stacked in the center column, Log right edge full height (max room for the log stream). Added a "View -> Reset layout" menu item (request_layout_reset / apply_layout_end_frame re-apply the defaults once via ImGuiCond_Always). Each panel now calls apply_panel_layout(Panel::X) instead of its own ad-hoc SetNextWindowPos/Size. Verified live: captured the host overlay -- Injection (left, full height), Controllers/Video/Audio (center stack), Log (right, full height), no overlap among the panels. x64 build + ctest green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
162 lines
4.3 KiB
C++
162 lines
4.3 KiB
C++
// Top-level overlay chrome shared across panels: which panels are visible, the
|
|
// global "show debug details" switch, rolling frame-timing stats, and the main
|
|
// menu bar that drives them. Keeping this in one place lets the individual panels
|
|
// stay focused on their own pipeline while presenting a consistent shell.
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
|
|
namespace coop
|
|
{
|
|
|
|
// Visibility + verbosity shared by all panels. Panels read `debug_details` to
|
|
// gate verbose diagnostics; the main loop reads the per-panel flags to decide
|
|
// what to draw.
|
|
struct UiState
|
|
{
|
|
bool show_controllers = true;
|
|
bool show_injection = true;
|
|
bool show_video = true;
|
|
bool show_audio = true;
|
|
bool show_log = true;
|
|
bool debug_details = false; // off = general status; on = full diagnostics
|
|
};
|
|
|
|
// The overlay panels, for the shared default layout below.
|
|
enum class Panel
|
|
{
|
|
Injection, // left column, full height (room for hook diagnostics)
|
|
Controllers, // center column, top
|
|
Video, // center column, below Controllers
|
|
Audio, // center column, below Video
|
|
Log, // right column, full height (max room for the log stream)
|
|
};
|
|
|
|
// Position + size the next ImGui window per the default 3-column layout, so panels
|
|
// open without overlapping or needing a manual resize. First-use only, unless a
|
|
// layout reset was just requested (then it re-applies once). Call right before the
|
|
// panel's ImGui::Begin.
|
|
void apply_panel_layout(Panel panel);
|
|
|
|
// "View -> Reset layout" sets this; it forces apply_panel_layout to re-place every
|
|
// panel on the next frame. apply_layout_end_frame() clears it after the panels draw.
|
|
void request_layout_reset();
|
|
void apply_layout_end_frame();
|
|
|
|
// 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
|
|
{
|
|
public:
|
|
// Number of frame samples kept for the graphs (~2 s at 120 FPS).
|
|
static constexpr int kHistory = 240;
|
|
|
|
void tick(float dt_ms)
|
|
{
|
|
if (dt_ms < cur_min_)
|
|
{
|
|
cur_min_ = dt_ms;
|
|
}
|
|
if (dt_ms > cur_max_)
|
|
{
|
|
cur_max_ = dt_ms;
|
|
}
|
|
accum_ms_ += dt_ms;
|
|
++frames_;
|
|
if (accum_ms_ >= 1000.0f && frames_ > 0)
|
|
{
|
|
avg_ms_ = accum_ms_ / static_cast<float>(frames_);
|
|
min_ms_ = cur_min_;
|
|
max_ms_ = cur_max_;
|
|
cur_min_ = 1.0e9f;
|
|
cur_max_ = 0.0f;
|
|
accum_ms_ = 0.0f;
|
|
frames_ = 0;
|
|
}
|
|
|
|
history_[hist_pos_] = dt_ms;
|
|
hist_pos_ = (hist_pos_ + 1) % kHistory;
|
|
if (hist_count_ < kHistory)
|
|
{
|
|
++hist_count_;
|
|
}
|
|
}
|
|
|
|
// --- 1 s windowed aggregates (stable readout for the menu bar) ---------
|
|
[[nodiscard]] float avg_ms() const
|
|
{
|
|
return avg_ms_;
|
|
}
|
|
[[nodiscard]] float min_ms() const
|
|
{
|
|
return min_ms_;
|
|
}
|
|
[[nodiscard]] float max_ms() const
|
|
{
|
|
return max_ms_;
|
|
}
|
|
[[nodiscard]] float fps() const
|
|
{
|
|
return avg_ms_ > 0.0f ? 1000.0f / avg_ms_ : 0.0f;
|
|
}
|
|
|
|
// --- Sample history (for graphs) ---------------------------------------
|
|
[[nodiscard]] int history_size() const
|
|
{
|
|
return hist_count_;
|
|
}
|
|
|
|
// Copy the frame-time samples (ms) into `out` oldest-to-newest; `out` must
|
|
// hold at least kHistory floats. Returns the number written.
|
|
int copy_frame_ms(float* out) const
|
|
{
|
|
const int start = (hist_pos_ - hist_count_ + kHistory * 2) % kHistory;
|
|
for (int i = 0; i < hist_count_; ++i)
|
|
{
|
|
out[i] = history_[(start + i) % kHistory];
|
|
}
|
|
return hist_count_;
|
|
}
|
|
|
|
// min / max / mean over the whole retained history (order-independent).
|
|
void history_stats(float& min_ms, float& max_ms, float& avg_ms) const
|
|
{
|
|
if (hist_count_ == 0)
|
|
{
|
|
min_ms = max_ms = avg_ms = 0.0f;
|
|
return;
|
|
}
|
|
float mn = 1.0e9f, mx = 0.0f, sum = 0.0f;
|
|
for (int i = 0; i < hist_count_; ++i)
|
|
{
|
|
const float v = history_[i];
|
|
mn = std::min(mn, v);
|
|
mx = std::max(mx, v);
|
|
sum += v;
|
|
}
|
|
min_ms = mn;
|
|
max_ms = mx;
|
|
avg_ms = sum / static_cast<float>(hist_count_);
|
|
}
|
|
|
|
private:
|
|
float accum_ms_ = 0.0f;
|
|
int frames_ = 0;
|
|
float cur_min_ = 1.0e9f;
|
|
float cur_max_ = 0.0f;
|
|
float avg_ms_ = 0.0f;
|
|
float min_ms_ = 0.0f;
|
|
float max_ms_ = 0.0f;
|
|
|
|
float history_[kHistory] = {};
|
|
int hist_pos_ = 0;
|
|
int hist_count_ = 0;
|
|
};
|
|
|
|
// Draw the main menu bar (app name, View menu of panel toggles + debug switch,
|
|
// and a right-aligned FPS readout). Returns the menu bar height so the caller can
|
|
// keep panels clear of it on first layout.
|
|
float draw_main_menu_bar(UiState& ui, const FrameStats& stats);
|
|
|
|
} // namespace coop
|