mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 23:00:42 -07:00
overlay: refactor overlay layering (#739)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Create three distinct layers for the overlay: 1. Bottommost persistent layer - non-interactable layer that is always on. This was only for notifications, but now the FPS widget lives here as well. 2. Overlay windows layer - most interactable windows go here. 3. Topmost main menu - this is reserved for the main menu (escape key) and this is a modal dialog that occludes the layers below. Why? - `toggle overlay` behavior with FPS widget *also* toggling on/off was a bit confusing (now they're two separate keys) - FPS widget is popular, but it caused the entire overlay to be active, which affects how input is handled - the main menu being a standalone window was a little awkward (now it's a modal) ## Testing
This commit is contained in:
@@ -324,20 +324,12 @@ namespace overlay::windows {
|
||||
}
|
||||
if (ImGui::BeginTabItem("Overlay")) {
|
||||
tab_selected_new = ConfigTab::CONFIG_TAB_OVERLAY;
|
||||
|
||||
const auto offset = cfg::CONFIGURATOR_STANDALONE ? page_offset : page_offset2;
|
||||
|
||||
ImGui::BeginChild("Overlay", ImVec2(
|
||||
0, ImGui::GetWindowContentRegionMax().y - offset), false);
|
||||
0, ImGui::GetWindowContentRegionMax().y - page_offset2), false);
|
||||
|
||||
// overlay buttons
|
||||
this->build_buttons("Overlay", games::get_buttons_overlay(this->games_selected_name));
|
||||
ImGui::EndChild();
|
||||
|
||||
// standalone configurator extras
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
ImGui::Checkbox("Enable Overlay in Config", &OVERLAY->hotkeys_enable);
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Lights")) {
|
||||
@@ -4316,7 +4308,7 @@ namespace overlay::windows {
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "NFC card reader status");
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "NFC / API card reader status");
|
||||
ImGui::Spacing();
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
|
||||
@@ -4379,14 +4371,16 @@ namespace overlay::windows {
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "More tips");
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card Manager");
|
||||
ImGui::Spacing();
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::TextWrapped("To debug card reader issues, run spice.exe -cfg in command line and check the log.");
|
||||
ImGui::TextWrapped(
|
||||
"If you have multiple players, try opening Card Manager window in the game. "
|
||||
"Check the key bind in Overlay tab for Toggle Card Manager.");
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::Button("Open Card Manager")) {
|
||||
if (this->overlay->window_cards != nullptr) {
|
||||
this->overlay->window_cards->set_active(true);
|
||||
this->overlay->window_cards->bring_to_front();
|
||||
}
|
||||
}
|
||||
ImGui::TextUnformatted("");
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
|
||||
bool Config::validate_ea_card(char card_number[16]) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "exitprompt.h"
|
||||
#include "avs/game.h"
|
||||
#include "build/defs.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
@@ -9,7 +10,7 @@
|
||||
namespace overlay::windows {
|
||||
|
||||
ExitPrompt::ExitPrompt(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "spice2x";
|
||||
this->title = "spice2x (" + to_string(VERSION_STRING_CFG) + ")";
|
||||
this->init_size = ImVec2(
|
||||
(ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2),
|
||||
overlay::apply_scaling(120));
|
||||
@@ -20,22 +21,35 @@ namespace overlay::windows {
|
||||
this->flags = ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_AlwaysAutoResize
|
||||
| ImGuiWindowFlags_NoDocking;
|
||||
| ImGuiWindowFlags_NoDocking
|
||||
| ImGuiWindowFlags_NoMove;
|
||||
|
||||
// the menu renders itself as a popup, not a normal overlay window
|
||||
this->draws_window = false;
|
||||
}
|
||||
|
||||
void ExitPrompt::update() {
|
||||
Window::update();
|
||||
|
||||
// allow the popup to be reopened the next time the menu is shown
|
||||
if (!this->active) {
|
||||
this->popup_opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ExitPrompt::build_button(
|
||||
Window *window, std::string label, const ImVec2 &size, NextItem next, bool is_toggle) {
|
||||
Window *window, std::string label, const ImVec2 &size, NextItem next) {
|
||||
|
||||
if (window == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Button(label.c_str(), size)) {
|
||||
if (is_toggle) {
|
||||
window->toggle_active();
|
||||
} else {
|
||||
window->set_active(true);
|
||||
this->set_active(false);
|
||||
window->toggle_active();
|
||||
|
||||
// raise to the top when the user toggled it visible from the menu
|
||||
if (window->get_active()) {
|
||||
window->bring_to_front();
|
||||
}
|
||||
}
|
||||
if (next == NextItem::NEW_LINE) {
|
||||
@@ -46,6 +60,31 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
void ExitPrompt::build_content() {
|
||||
// use ### so the visible label is the full title (with version) while the
|
||||
// popup keeps a stable identifier regardless of the title text
|
||||
const std::string popup_id_str = this->title + "###mainmenu";
|
||||
const char *popup_id = popup_id_str.c_str();
|
||||
|
||||
// open the popup once when the menu becomes active; popup_opened is reset
|
||||
// in update() while inactive so it reopens the next time it is shown
|
||||
if (!this->popup_opened) {
|
||||
ImGui::OpenPopup(popup_id);
|
||||
this->popup_opened = true;
|
||||
}
|
||||
|
||||
// position at the top center of the screen
|
||||
const ImVec2 menu_pos(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
overlay::apply_scaling(10));
|
||||
ImGui::SetNextWindowPos(menu_pos, ImGuiCond_Appearing);
|
||||
|
||||
bool open = true;
|
||||
if (!ImGui::BeginPopupModal(popup_id, &open, this->flags)) {
|
||||
// dismissed via escape - close the menu
|
||||
this->set_active(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 size(ImGui::GetFontSize() * 14, ImGui::GetFontSize() * 1.9f);
|
||||
const ImVec2 size_half(
|
||||
(size.x - ImGui::GetStyle().ItemSpacing.x) / 2,
|
||||
@@ -54,11 +93,7 @@ namespace overlay::windows {
|
||||
(size.x - (ImGui::GetStyle().ItemSpacing.x * 2)) / 3,
|
||||
ImGui::GetFontSize() * 2.5f);
|
||||
|
||||
if (ImGui::Button("Hide overlay", size)) {
|
||||
overlay::OVERLAY->set_active(false);
|
||||
}
|
||||
ImGui::Spacing();
|
||||
build_button(this->overlay->window_config, "Show Config", size, NextItem::NEW_LINE, false);
|
||||
build_button(this->overlay->window_config, "Options", size, NextItem::NEW_LINE);
|
||||
|
||||
std::string sub = "Show Subscreen";
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
@@ -77,11 +112,12 @@ namespace overlay::windows {
|
||||
sub = "Show Pop'n Subscreen";
|
||||
}
|
||||
|
||||
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
|
||||
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE);
|
||||
|
||||
ImGui::TextDisabled("Graphics");
|
||||
build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE);
|
||||
build_button(this->overlay->window_fps, "FPS", size_half, NextItem::SAME_LINE);
|
||||
|
||||
build_button(this->overlay->window_fps.get(), "FPS", size_half, NextItem::SAME_LINE);
|
||||
build_button(this->overlay->window_resize, "Resize", size_half, NextItem::NEW_LINE);
|
||||
|
||||
ImGui::TextDisabled("I/O");
|
||||
@@ -157,5 +193,12 @@ namespace overlay::windows {
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::EndPopup();
|
||||
|
||||
// title-bar X was clicked - close the menu
|
||||
if (!open) {
|
||||
this->set_active(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,13 @@ namespace overlay::windows {
|
||||
public:
|
||||
ExitPrompt(SpiceOverlay *overlay);
|
||||
void build_content() override;
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
void build_button(Window *window, std::string label, const ImVec2 &size, NextItem next, bool is_toggle=true);
|
||||
void build_button(Window *window, std::string label, const ImVec2 &size, NextItem next);
|
||||
|
||||
// latch so the popup is opened once per activation; reset in update()
|
||||
// whenever the menu is inactive
|
||||
bool popup_opened = false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,58 +1,124 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include "external/fmt/include/fmt/chrono.h"
|
||||
#include "fps.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
// tighter internal cell padding than the imgui default (4, 2)
|
||||
static const ImVec2 FPS_CELL_PADDING(4.0f, 1.0f);
|
||||
|
||||
// tighter window padding than the imgui default (8, 8)
|
||||
static const ImVec2 FPS_WINDOW_PADDING(6.0f, 4.0f);
|
||||
|
||||
FPS::FPS(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Stats";
|
||||
this->flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_AlwaysAutoResize
|
||||
| ImGuiWindowFlags_NoFocusOnAppearing
|
||||
| ImGuiWindowFlags_NoBringToFrontOnFocus
|
||||
| ImGuiWindowFlags_NoNavFocus
|
||||
| ImGuiWindowFlags_NoNavInputs
|
||||
| ImGuiWindowFlags_NoNav
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoInputs
|
||||
| ImGuiWindowFlags_NoDocking;
|
||||
this->bg_alpha = 0.5f;
|
||||
this->window_padding = FPS_WINDOW_PADDING;
|
||||
this->start_time =
|
||||
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
void FPS::calculate_initial_window() {
|
||||
// width is 114x82 px with window decoration, 98x47 for the content
|
||||
int pos_x =
|
||||
overlay::FPS_SHOULD_FLIP ?
|
||||
overlay::apply_scaling(8) :
|
||||
ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(122);
|
||||
this->init_pos = ImVec2(pos_x, overlay::apply_scaling(8));
|
||||
// size the window explicitly (no AlwaysAutoResize) so the corner anchoring
|
||||
// below is exact; the footprint mirrors the fixed-fit table in build_content()
|
||||
const float line_h = ImGui::GetTextLineHeight();
|
||||
const int rows = 3;
|
||||
|
||||
// widest label and widest value drive the two fixed-fit columns
|
||||
const float label_w = (std::max)(
|
||||
ImGui::CalcTextSize("Time").x,
|
||||
ImGui::CalcTextSize("Game").x);
|
||||
const float value_w = ImGui::CalcTextSize("00:00:00").x;
|
||||
|
||||
const float win_w = label_w + value_w
|
||||
+ FPS_CELL_PADDING.x * 2
|
||||
+ FPS_WINDOW_PADDING.x * 2;
|
||||
const float win_h = (line_h + FPS_CELL_PADDING.y * 2) * rows
|
||||
+ FPS_WINDOW_PADDING.y * 2;
|
||||
this->init_size = ImVec2(win_w, win_h);
|
||||
|
||||
// bottom-anchored windows use a larger edge margin (matching notification
|
||||
// toasts) since they overlap the same on-screen UI; other edges hug closer
|
||||
const float edge_margin = overlay::apply_scaling(4);
|
||||
const float bottom_margin = overlay::apply_scaling(20);
|
||||
const ImVec2 &display = ImGui::GetIO().DisplaySize;
|
||||
|
||||
const bool right =
|
||||
overlay::FPS_LOCATION == overlay::FpsLocation::TopRight ||
|
||||
overlay::FPS_LOCATION == overlay::FpsLocation::BottomRight;
|
||||
const bool bottom =
|
||||
overlay::FPS_LOCATION == overlay::FpsLocation::BottomLeft ||
|
||||
overlay::FPS_LOCATION == overlay::FpsLocation::BottomRight;
|
||||
|
||||
const float pos_x = right ? display.x - win_w - edge_margin : edge_margin;
|
||||
const float pos_y = bottom ? display.y - win_h - bottom_margin : edge_margin;
|
||||
this->init_pos = ImVec2(pos_x, pos_y);
|
||||
}
|
||||
|
||||
void FPS::build_content() {
|
||||
|
||||
// frame timers
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
ImGui::Text("FPS: %.1f", io.Framerate);
|
||||
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
|
||||
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto now_s = std::chrono::floor<std::chrono::seconds>(now);
|
||||
|
||||
// current time
|
||||
{
|
||||
const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
|
||||
std::tm local_tm{};
|
||||
localtime_s(&local_tm, &tt);
|
||||
ImGui::TextUnformatted(fmt::format("Time: {:%H:%M:%S}", local_tm).c_str());
|
||||
}
|
||||
const std::time_t tt = std::chrono::system_clock::to_time_t(now_s);
|
||||
std::tm local_tm{};
|
||||
localtime_s(&local_tm, &tt);
|
||||
|
||||
// elapsed time
|
||||
{
|
||||
const auto uptime = now_s - this->start_time;
|
||||
const auto uptime = now_s - this->start_time;
|
||||
|
||||
// right-align a label within the current cell so the label column reads
|
||||
// flush against the value column instead of looking ragged. the label is
|
||||
// only slightly dimmer than normal text (not the much darker "disabled" tone)
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
ImVec4 label_col = style.Colors[ImGuiCol_Text];
|
||||
label_col.w *= 0.7f;
|
||||
const auto label = [&label_col](const char *text) {
|
||||
const float avail = ImGui::GetContentRegionAvail().x;
|
||||
const float text_w = ImGui::CalcTextSize(text).x;
|
||||
if (avail > text_w) {
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail - text_w));
|
||||
}
|
||||
ImGui::TextColored(label_col, "%s", text);
|
||||
};
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, FPS_CELL_PADDING);
|
||||
if (ImGui::BeginTable("##fps_stats", 2, ImGuiTableFlags_SizingFixedFit)) {
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
label("FPS");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("%.2f", io.Framerate);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
label("Time");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted(fmt::format("{:%H:%M:%S}", local_tm).c_str());
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
label("Game");
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted(
|
||||
fmt::format("Up: {:%H:%M:%S}",
|
||||
fmt::format("{:%H:%M:%S}",
|
||||
std::chrono::floor<std::chrono::seconds>(uptime)).c_str());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,5 @@ namespace overlay::windows {
|
||||
|
||||
void calculate_initial_window() override;
|
||||
void build_content() override;
|
||||
|
||||
bool should_flip = false;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user