pause/resume (and should they have hotkeys)

This commit is contained in:
N3xoX1 2026-04-14 09:17:42 +09:30
parent 224d34b6e4
commit cf1172b256
14 changed files with 208 additions and 23 deletions

View File

@ -1,4 +1,11 @@
:root {
--font-ui: 'Segoe UI', Arial, sans-serif;
--color-ui-text-primary: #eff0f1;
--color-ui-text-muted: #b0b6bc;
--color-statusbar-background: #31363b;
--color-statusbar-border: #434242;
--color-statusbar-hover-border: #76797c;
--color-pause-panel-background: #25282c;
--color-main-background: #2d2d2d;
--color-titlebar-background: #181818;
--color-titlebar-background-inactive: #2d2d2d;
@ -52,7 +59,7 @@ html > header > window-caption
line-height: window-caption-height;
width: *;
padding: 0 1em;
font-family: 'Segoe UI', Arial, sans-serif;
font-family: var(--font-ui);
font-size: 0.9em;
font-weight: 100;
}
@ -138,8 +145,8 @@ ul#menu-bar
margin: 0;
padding: 0 0.5em 0.3em 0.5em;
background: var(--color-menubar-background);
color: #eff0f1;
font-family: 'Segoe UI', Arial, sans-serif;
color: var(--color-ui-text-primary);
font-family: var(--font-ui);
font-size: 0.9em;
font-weight: 100;
behavior:menu-bar;

View File

@ -6,12 +6,12 @@
#StatusBar
{
background: #31363b;
background: var(--color-statusbar-background);
flow: horizontal;
color: #eff0f1;
color: var(--color-ui-text-primary);
padding: 0.2em 1em;
border-top: 1px solid #434242;
font-family: 'Segoe UI', Arial, sans-serif;
border-top: 1px solid var(--color-statusbar-border);
font-family: var(--font-ui);
font-size: 0.8em;
font-weight: 100;
height: 1.6rem;
@ -37,24 +37,60 @@
#StatusBar #volume:hover,
#StatusBar #renderer:hover
{
border: 1px solid #76797c;
border: 1px solid var(--color-statusbar-hover-border);
}
#VolumePopup
{
background: #31363b;
border: 1px solid #434242;
background: var(--color-statusbar-background);
border: 1px solid var(--color-statusbar-border);
padding: 1em;
}
#RomBrowserPanel,
#LoadingPanel
.main-contents-panel
{
width: 100%%;
height: 100%%;
display: none;
}
#PausePanel
{
background: var(--color-pause-panel-background);
color: var(--color-ui-text-primary);
overflow: hidden;
}
#PausePanel .pause-panel-container
{
width: 100%%;
height: 100%%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 2rem;
box-sizing: border-box;
}
#PausePanel .pause-panel-title
{
font-family: var(--font-ui);
font-size: 2rem;
font-weight: 600;
margin: 0 0 0.5rem;
}
#PausePanel .pause-panel-subtitle
{
font-family: var(--font-ui);
font-size: 1rem;
font-weight: 300;
color: var(--color-ui-text-muted);
margin: 0;
}
rombrowser
{
display: block;
@ -83,7 +119,7 @@ rombrowser .config-button svg
rombrowser .config-button .outline
{
fill: #eff0f1;
fill: var(--color-ui-text-primary);
}
rombrowser .config-button:hover .outline
@ -104,10 +140,10 @@ rombrowser .config-button:hover
rombrowser .games-title h1
{
font-family: 'Segoe UI', Arial, sans-serif;
font-family: var(--font-ui);
font-size: 1.5rem;
font-weight: 400;
color: #eff0f1;
color: var(--color-ui-text-primary);
padding: 0 0 0.6rem;
margin: 0;
width:*

View File

@ -14,10 +14,10 @@
</header>
<mainmenu id="MainMenu" />
<div id="MainContents">
<div id="RomBrowserPanel">
<div id="RomBrowserPanel" class="main-contents-panel">
<rombrowser />
</div>
<div id="LoadingPanel">
<div id="LoadingPanel" class="main-contents-panel">
<div class="loading-container">
<div class="loading-text"><span class="text">Loading</span>
<div class="dot-pulse-container">
@ -28,6 +28,11 @@
</div>
</div>
</div>
<div id="PausePanel" class="main-contents-panel">
<div class="pause-panel-container">
<div class="pause-panel-title">Paused</div>
</div>
</div>
</div>
<div id="StatusBar" ><span id="renderer" /><span id="volume" /><div id="StatusText" /></div>
</html>

View File

@ -29,7 +29,7 @@ enum
MODULE_LOADER_SPECS_VERSION = 0x0119,
MODULE_VIDEO_SPECS_VERSION = 0x0116,
MODULE_CPU_SPECS_VERSION = 0x010D,
MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0111,
MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0112,
};
enum MODULE_TYPE : uint16_t

View File

@ -431,6 +431,8 @@ nxinterface IOperatingSystem
virtual IParamPackage * GetNextInput() const = 0;
virtual void PumpInputEvents() const = 0;
virtual PerfStatsResults GetAndResetPerfStats() = 0;
virtual void SetEmulationPaused(bool paused) = 0;
virtual bool IsEmulationPaused() const = 0;
};
EXPORT IOperatingSystem * CALL CreateOperatingSystem(ISystemModules & modules);

View File

@ -35,12 +35,29 @@ public:
while (!stop_token.stop_requested())
{
std::unique_lock lk{m_should_run_mutex};
if (m_should_run)
if (m_should_run.load(std::memory_order_relaxed))
{
m_system.Run();
m_stopped.Reset();
Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return !m_should_run; });
Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] {
return !m_should_run.load(std::memory_order_relaxed);
});
}
else
{
m_system.Pause();
m_stopped.Set();
g_settings->SetInt(NXCoreSetting::EmulationState, (int32_t)EmulationState::Paused);
Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] {
return m_should_run.load(std::memory_order_relaxed);
});
if (!stop_token.stop_requested())
{
g_settings->SetInt(NXCoreSetting::EmulationState, (int32_t)EmulationState::Running);
}
}
}
@ -63,7 +80,7 @@ public:
std::mutex m_should_run_mutex;
std::condition_variable_any m_should_run_cv;
Common::Event m_stopped;
bool m_should_run;
std::atomic<bool> m_should_run;
Core::System & m_system;
Kernel::KProcess *& m_process;
};
@ -102,7 +119,7 @@ void EmuThread::SetRunning(bool should_run)
{
// Notify the running thread to change state.
std::unique_lock run_lk{impl->m_should_run_mutex};
impl->m_should_run = should_run;
impl->m_should_run.store(should_run, std::memory_order_release);
impl->m_should_run_cv.notify_one();
}
@ -111,4 +128,9 @@ void EmuThread::SetRunning(bool should_run)
{
impl->m_stopped.Wait();
}
}
}
bool EmuThread::IsRunning() const
{
return impl->m_should_run.load(std::memory_order_acquire);
}

View File

@ -15,6 +15,7 @@ public:
void Start();
void Stop();
void SetRunning(bool should_run);
bool IsRunning() const;
private:
struct Impl;

View File

@ -356,3 +356,21 @@ PerfStatsResults OSManager::GetAndResetPerfStats()
{
return m_coreSystem.GetAndResetPerfStats();
}
void OSManager::SetEmulationPaused(bool paused)
{
if (!m_emuThread)
{
return;
}
m_emuThread->SetRunning(!paused);
}
bool OSManager::IsEmulationPaused() const
{
if (!m_emuThread)
{
return false;
}
return !m_emuThread->IsRunning();
}

View File

@ -45,6 +45,8 @@ public:
IParamPackage * GetNextInput() const override;
void PumpInputEvents() const override;
PerfStatsResults GetAndResetPerfStats() override;
void SetEmulationPaused(bool paused) override;
bool IsEmulationPaused() const override;
private:
OSManager() = delete;

View File

@ -16,6 +16,7 @@ namespace
{Hotkey::Exit, {.key = (uint32_t)SCITER_KEY_Q, .ctrl = true}},
{Hotkey::Fullscreen, {.key = (uint32_t)SCITER_KEY_F11, .ctrl = false, .alt = false, .shift = false}},
{Hotkey::ExitFullscreen, {.key = (uint32_t)SCITER_KEY_ESCAPE, .ctrl = false, .alt = false, .shift = false}},
{Hotkey::PauseContinue, {.key = (uint32_t)SCITER_KEY_F4}},
};
void merge_default_hotkeys(HotkeyMap & map)

View File

@ -11,6 +11,7 @@ constexpr const char LoadFile[] = "LoadFile";
constexpr const char Exit[] = "Exit";
constexpr const char Fullscreen[] = "Fullscreen";
constexpr const char ExitFullscreen[] = "ExitFullscreen";
constexpr const char PauseContinue[] = "PauseContinue";
}
using HotkeyMap = std::map<std::string, MenuBarAccelerator>;

View File

@ -167,6 +167,16 @@ void SciterMainWindow::ResetMenu()
MenuBarItemList systemMenu;
if (m_emulationRunning)
{
bool paused = false;
if (m_modules.IsValid())
{
paused = m_modules.Modules().OperatingSystem().IsEmulationPaused();
}
systemMenu.push_back(MenuBarItem(
ID_SYSTEM_PAUSE_CONTINUE,
paused ? "Continue" : "Pause",
nullptr,
HotkeyAccelerator(Hotkey::PauseContinue)));
systemMenu.push_back(MenuBarItem(ID_SYSTEM_STOP, "&Stop"));
mainTitleMenu.push_back(MenuBarItem(MenuBarItem::SUB_MENU, "&System", &systemMenu));
}
@ -335,6 +345,8 @@ void SciterMainWindow::CreateRenderWindow()
IVideo & video = m_modules.Modules().Video();
video.UpdateFramebufferLayout(rect.right - rect.left, rect.bottom - rect.top);
}
UpdatePausePanel();
}
void SciterMainWindow::SetCaption(const std::string & caption)
@ -385,11 +397,17 @@ void SciterMainWindow::EmulationStateChanged(const char * /*setting*/, void * us
else if (state == EmulationState::Running)
{
impl->m_rootElement.PostEvent(EVENT_EMULATION_RUNNING);
impl->ResetMenu();
}
else if (state == EmulationState::Paused)
{
impl->ResetMenu();
}
else if (state == EmulationState::Stopped)
{
impl->m_rootElement.PostEvent(EVENT_EMULATION_STOPPED);
}
impl->UpdatePausePanel();
}
void SciterMainWindow::GameFileChanged(const char * /*setting*/, void * userData)
@ -493,6 +511,26 @@ void SciterMainWindow::OnStopGame()
settings.SetBool(NXCoreSetting::EmulationRunning, false);
}
void SciterMainWindow::OnPauseContinueGame()
{
if (!m_emulationRunning || !m_modules.IsValid())
{
return;
}
IOperatingSystem & os = m_modules.Modules().OperatingSystem();
if (os.IsEmulationPaused())
{
PreventOSSleep();
os.SetEmulationPaused(false);
}
else
{
os.SetEmulationPaused(true);
AllowOSSleep();
}
ResetMenu();
}
void SciterMainWindow::OnSystemConfig()
{
ShowConfig(nullptr);
@ -526,10 +564,19 @@ void SciterMainWindow::UpdateStatusBar()
IVideo & video = m_modules.Modules().Video();
std::string status;
if (operatingSystem.IsEmulationPaused())
{
status += "Paused";
}
const int shaders_building = video.ShadersBuilding();
if (shaders_building > 0)
{
if (!status.empty())
{
status += " | ";
}
status += stdstr_f("Building: %d shader(s)", shaders_building);
}
@ -603,6 +650,11 @@ bool SciterMainWindow::ProcessMenuBarAccelerator(const char * hotkeyId)
OnFileExit();
return true;
}
if (strcmp(hotkeyId, Hotkey::PauseContinue) == 0)
{
OnPauseContinueGame();
return true;
}
return false;
}
@ -626,6 +678,7 @@ void SciterMainWindow::OnMenuItem(int32_t id, SCITER_ELEMENT /*item*/)
{
case ID_FILE_LOAD_FILE: OnOpenFile(); break;
case ID_FILE_EXIT: OnFileExit(); break;
case ID_SYSTEM_PAUSE_CONTINUE: OnPauseContinueGame(); break;
case ID_SYSTEM_STOP: OnStopGame(); break;
case ID_EMULATION_CONTROLLERS: OnInputConfig(); break;
case ID_EMULATION_CONFIGURE: OnSystemConfig(); break;
@ -753,6 +806,37 @@ void SciterMainWindow::LayoutRenderWindow()
IVideo & video = m_modules.Modules().Video();
video.UpdateFramebufferLayout(width, height);
}
UpdatePausePanel();
}
void SciterMainWindow::UpdatePausePanel()
{
SciterElement panel(m_rootElement.GetElementByID("PausePanel"));
if (!panel.IsValid())
{
return;
}
const bool paused =
m_emulationRunning && m_modules.IsValid() && m_modules.Modules().OperatingSystem().IsEmulationPaused();
panel.SetStyleAttribute("display", paused ? "block" : "none");
if (m_renderWindow == nullptr || !m_emulationRunning)
{
return;
}
if (paused)
{
ShowWindow((HWND)m_renderWindow, SW_HIDE);
}
else
{
SettingsStore & settings = SettingsStore::GetInstance();
const bool showRender = settings.GetBool(NXCoreSetting::DisplayedFrames);
ShowWindow((HWND)m_renderWindow, showRender ? SW_SHOW : SW_HIDE);
}
}
void SciterMainWindow::ToggleFullscreen()
@ -1064,6 +1148,7 @@ bool SciterMainWindow::OnEvent(SCITER_ELEMENT /*element*/, SCITER_ELEMENT /*sour
{
romBrowserPanel.SetStyleAttribute("display", "block");
}
UpdatePausePanel();
}
else if (event_code == EVENT_EMULATION_FIRST_FRAME)
{
@ -1077,6 +1162,7 @@ bool SciterMainWindow::OnEvent(SCITER_ELEMENT /*element*/, SCITER_ELEMENT /*sour
LoadingPanel.SetStyleAttribute("display", "none");
}
}
UpdatePausePanel();
}
return false;
}

View File

@ -35,6 +35,7 @@ class SciterMainWindow :
ID_FILE_EXIT,
// System Menu
ID_SYSTEM_PAUSE_CONTINUE,
ID_SYSTEM_STOP,
// Emulation Menu
@ -94,6 +95,7 @@ private:
void OnOpenFile();
void OnFileExit();
void OnStopGame();
void OnPauseContinueGame();
void OnSystemConfig();
void OnInputConfig();
void OnRecetGame(uint32_t fileIndex);
@ -112,6 +114,7 @@ private:
void ResetWindowSize1080();
#endif
void LayoutRenderWindow();
void UpdatePausePanel();
// IWindowDestroySink
void OnWindowDestroy(HWINDOW hWnd) override;

View File

@ -48,6 +48,7 @@ static const HotkeyLabelEntry kHotkeyLabels[] = {
{Hotkey::Exit, "Exit"},
{Hotkey::Fullscreen, "Fullscreen"},
{Hotkey::ExitFullscreen, "Exit Fullscreen"},
{Hotkey::PauseContinue, "Pause / Continue"},
};
std::string HotkeyBindingElementId(const std::string & hotkeyId)