Files
2026-10-18 03:11:11 +00:00

191 lines
9.2 KiB
C++

// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING).
//
// The runtime's own screens, drawn with Dear ImGui over the game's image:
// the installer (before the game starts), the settings menu (F1, Back +
// Start on a controller, or a three-finger tap on a touchscreen) and the
// performance overlay.
//
// - The presenter owns the frame: it calls BeginFrame() before recording,
// and Draw() inside the dynamic-rendering pass that writes the swapchain
// image, after the game's image.
// - While a menu is open the UI owns the keyboard, mouse and controller;
// the game gets neutral input (CapturingInput()).
// - Touch reaches ImGui as the mouse SDL makes of the first finger: a tap
// is a click, and on iOS a drag scrolls a list. While the settings menu
// is open a finger's press waits until it can't be the start of a
// three-finger tap (a quarter of a second at most); under a modal (the
// installer), where the tap does nothing, it doesn't wait.
// - ImGui is main-thread only. The Vulkan backend submits to the queue the
// renderer shares (font uploads), so Draw() and Initialize() hold the
// presenter's queue mutex around it.
#pragma once
#include <cstdint>
#include <functional>
#include <mutex>
#include <string>
#include <string_view>
#include <vector>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_video.h>
#include <vulkan/vulkan.h>
namespace ui
{
struct VulkanTarget
{
VkInstance instance;
VkPhysicalDevice physical;
VkDevice device;
uint32_t queueFamily;
VkQueue queue;
std::mutex* queueMutex; // held around every use of the queue
uint32_t apiVersion; // the instance's
VkFormat format; // the swapchain's
};
// Main thread, once the swapchain exists. False: no UI (the game still runs).
bool Initialize(SDL_Window* window, const VulkanTarget& target);
// The swapchain was recreated in another format.
void SetFormat(VkFormat format);
// The swapchain's size (each time it is built). The UI's framebuffer
// scale follows it (iOS's present scale: Screen Resolution or
// NFSMW_PRESENT_SCALE makes it smaller than the window's pixels), and
// its layout too should the window's size disagree with it in shape
// (video/swapchain_policy.h: FitUiDisplay). 0, 0: the window's.
void SetFramebufferSize(uint32_t width, uint32_t height);
// Main thread, with the device idle.
void Shutdown();
// Main thread, every frame just before polling events: scripted test
// keys (NFSMW_UI_KEYS), and the time of the poll, by which BeginFrame
// judges a touch press held back for a three-finger tap. On iOS it also
// keeps the system's three-finger editing gestures off.
void Tick();
// Main thread: NFSMW_UI_KEYS asked for a capture of the next presented
// frame ("Shot"); true once per request.
bool TakeShot();
// Main thread: the same request from elsewhere (NFSMW_VIRTUAL_PAD's SHOT).
void RequestShot();
// Main thread: every SDL event, before the game's input sees it. Opens
// and closes the settings menu (keys, controller chord, three-finger
// tap), and feeds the UI while it has input.
void ProcessEvent(const SDL_Event& event);
// Any thread: a menu is open and owns the input.
bool CapturingInput();
// The UI needs frames even while the game presents none (a menu or the
// installer is showing).
bool WantsFrames();
// Main thread, once per presented frame, after the frame's events and
// before recording it: lays out this frame. False: nothing to draw
// (don't call Draw).
bool BeginFrame();
// Records the frame laid out by BeginFrame(), inside a dynamic-rendering
// pass on the swapchain image (any viewport; it sets its own).
void Draw(VkCommandBuffer cmd);
// The presenter showed a new game frame (the performance overlay counts them).
void NoteGameFrame();
// Any thread: a one-line notice at the bottom of the screen for a few
// seconds (a settings file that didn't load, a save that failed, where a
// bug report went). Several take turns.
void Toast(std::string text, double seconds = 8.0);
// Any thread: takes down the toast with this text, showing or waiting
// its turn, once it no longer holds (a controller connected). None:
// nothing.
void WithdrawToast(std::string text);
// A full-screen page drawn every frame instead of the game until it is
// cleared (the installer). Call from the main thread; `draw` runs inside
// BeginFrame().
void SetModal(std::function<void()> draw);
bool HasModal();
// Initialize() succeeded: the UI draws (a modal set without it would
// hold the game's input with nothing on screen).
bool Ready();
// This version's CHANGELOG.md entry, compiled in ("" without one).
std::string_view WhatsNewEntry();
// Main thread: the one-time "What's new" note ("Updated to v<version>"
// and WhatsNewEntry()) as a modal panel over the game until the player
// closes it (A, B, Start, Esc or OK). Nothing without an entry.
void ShowWhatsNew(std::string_view version);
// Main thread: a one-time notice (`title`, a paragraph of `text`, OK) as a
// panel over the game, which pauses under it, until the player closes it
// (A, B, Start, Esc or OK); then `closed` runs with how. False (nothing
// shown) without a UI, or while another panel or the settings menu is up.
bool ShowNotice(std::string title, std::string text, std::function<void(const char*)> closed);
// Main thread, before the game starts (main.cpp): a page saying why the
// game can't start (memory_status.h's StartupFailure), drawn until it is
// closed (Quit, B, Start, Esc; on iOS only by closing the app) or the
// window closes. `runFrame` is video::RunFrame. Nothing without a UI.
void RunStartupError(const std::string& title, const std::vector<std::string>& paragraphs, const std::string& details,
const std::function<bool()>& runFrame);
void OpenSettings();
void CloseSettings(); // saves any change
bool SettingsOpen();
// Screens (settings_menu.cpp, perf_overlay.cpp, installer_screen.cpp).
void DrawSettings();
void ResetSettingsMenu(); // on opening: focus the first row, no dialog left open
// Main thread, from ProcessEvent on a key-down or key-up while the menu
// is open, after the F1 toggle: true when a Controls row was waiting
// for a key and took this one, refused it (the wait goes on), or Esc
// cancelled it, or this is a repeat or the release of a key it took or
// refused, so ImGui must not see it.
bool SettingsMenuTakesKey(const SDL_KeyboardEvent& key);
void DrawPerfOverlay();
// Settings > Advanced > Check for Updates (update_dialog.cpp, the check
// itself is update/updater.h): the row's value and help lines, and its
// dialog, drawn inside the settings window.
const char* UpdateRowValue(); // "Check", "Checking...", "Up to date", "v0.1.2 is out", "Failed"
void UpdateRowHelp();
// Starts a check (unless one is running) and opens the dialog next frame.
void OpenUpdateDialog();
// Every frame inside the settings window, at its top level (not in a
// child or a table: the popup's ID). True while the dialog is open: B
// closes it, not the menu.
bool DrawUpdateDialog();
bool UpdateDialogOpen(); // at the same level
// The menu closed or reopened: the dialog is gone (a check still running
// then ends in a toast).
void ForgetUpdateDialog();
// The menu closed: Quit Game's prompt (settings_menu.cpp, quit_game.h),
// if it was asking, is cancelled.
void ForgetQuitPrompt();
// Shared look (ui.cpp).
SDL_Window* GetWindow();
float Scale(); // 1 at 720 points of window height
void PushAccentText(); // ImGui::PushStyleColor(ImGuiCol_Text, accent)
void PopAccentText();
// Large accent heading; `hint`, if any, right-aligned on its baseline.
void TitleText(const char* text, const char* hint = nullptr);
// The top of a line of text in the current font whose capitals centre
// on `y`, to the pixel (a label in a row's highlight).
float CentredTextTop(float y);
// Puts the next row of buttons on the window's bottom edge, so it stays
// put whatever the text above it runs to.
void ButtonRowAtBottom();
// Spacing: one scale for our own screens, in points at 720 points of
// window height (times Scale()). Headings, text, frames, highlights and
// separators start at a window's content edge; text inside a frame or a
// highlight keeps kSpaceL from its edge (a row's label, the arrow at a
// row's end, a list entry, a button's or a tab's label: FramePadding.x),
// and text under a list of rows lines up with their labels.
constexpr float kSpaceXS = 4.0f; // between the lines of one entry
constexpr float kSpaceS = 8.0f; // between related things; a compact box's padding
constexpr float kSpaceM = 12.0f; // between groups; the room either side of a value
constexpr float kSpaceL = 16.0f; // text inside a frame or highlight; a floating box
// from the screen's edge
}