Add System config screen

This commit is contained in:
N3xoX1 2025-06-23 17:16:48 +09:30
parent b0f36ca6b5
commit c5a5215261
15 changed files with 390 additions and 90 deletions

2
external/sciterui vendored

@ -1 +1 @@
Subproject commit d1620cd8727048b2ce2b9e2041283504b2318bf2
Subproject commit 4caaf3d3bbfe54f832b3f95fc01e15996fbbf002

View File

@ -110,10 +110,6 @@ ul#menu-bar
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 0.9em;
font-weight: 100;
}
ul#menu-bar
{
behavior:menu-bar;
flow: horizontal;
padding: 0;
@ -122,7 +118,7 @@ ul#menu-bar
ul#menu-bar > li
{
padding: 0.5em 0.5em 0.1em 0.5em;
padding: 0.25em 0.5em 0.1em 0.5em;
border: 1px solid transparent;
margin:0;
white-space:nowrap;
@ -147,10 +143,11 @@ menu
flow: vertical;
display:none;
margin:0;
padding:0;
padding: 0.2em;
max-width:max-content;
background: #31363b;
color: #eff0f1;
border: 1px solid #434242;
}
@media (menu-animation-supported) {
@ -172,7 +169,7 @@ menu li
vertical-align:middle;
foreground-repeat: no-repeat;
foreground-position: left 2px top 50%;
padding: 0.7em 1.8em 0.7em 1.3em;
padding: 0.2em 1.8em 0.2em 1.3em;
margin: 0;
}
@ -200,6 +197,12 @@ menu li:has-child-of-type(menu):rtl
foreground-position: top 50% left 2px;
}
hr
{
padding: 0;
margin: 0.2em 0;
}
#MainContents
{
width: 100%%;

View File

@ -0,0 +1,176 @@
<html window-frame="solid-with-shadow" window-icon="window-icon.png">
<head>
<link href="main_window.css" rel="stylesheet" type="text/css" />
<link href="input_config.css" rel="stylesheet" type="text/css" />
<title>NxEmu Configuration</title>
<style>
#SettingsPage
{
flow: horizontal;
width: 100%%;
height: 100%%;
}
#MainTabNav {
width: 9em;
height: 100%%;
border: 1px solid #54575b;
border-radius: 0.5em;
background-color: #232629;
margin: 0 0.4em 0 0;
color: #ffffff;
font-size: 0.9em;
}
#MainTabNav > Page:first-child
{
border-radius: 0.5em 0.5em 0 0;
}
#MainTabNav > Page
{
padding: 0.2em 0.5em;
}
#MainTabNav > Page:visited
{
background: #287399;
}
#ConfigContents {
width: 100%%;
height: 100%%;
color: #ffffff;
}
.checkbox-row,
.slider-row
{
margin: 0 0 1em 0;
}
.form-row .left,
.form-row .right
{
width: 50%%;
display: inline-block;
}
@set styled-select < std-select-dropdown {
:root {
color: #FFF;
padding: 0.1em;
margin: 0 0 0.3em 0;
width: 100%%;
border: 1px solid #54575b;
border-radius: .2em;
background: #2F2F31;
}
:root:focus,
:root:active {
border-color: #3daee9;
}
:root:hover {
background: #3C3C3C;
}
:root > popup.list {
background-color: #2B2B2B;
border-color: #1F1F1F;
color: #FFF;
}
:root option {
vertical-align: middle;
padding: 0.3em;
}
:root > button {
fill: #FFF;
}
}
select:not([type]) {
style-set: styled-select;
}
slider {
display: inline-block;
style-set: std-hslider;
}
@set std-hslider < std-hslider-base {
:root {
background: #565a5e;
padding: 7dip;
background-clip: content-box;
background-image: url(stock:block);
background-repeat: no-repeat;
background-position: 0 0;
fill: color(accent);
width: 8em;
height: 0.3em;
background-size: var(slider-position, 0px) 0.3em; // "value" part
}
:root > knob {
size: 16dip;
box-shadow: #aaa 1dip 1dip 1dip;
background: color(accent);
foreground: none;
border: none;
border-radius: 8dip;
padding: 0;
}
:root > knob::marker {
content: parent-attr(value);
size: 32dip 36dip;
line-height: 32dip;
text-align: center;
background: no-repeat
url(path:M8 2.1c1.1 0 2.2 0.5 3 1.3 0.8 0.9 1.3 1.9 1.3 3.1s-0.5 2.5-1.3 3.3l-3 3.1-3-3.1c-0.8-0.8-1.3-2-1.3-3.3 0-1.2 0.4-2.2 1.3-3.1 0.8-0.8 1.9-1.3 3-1.3z);
background-size: 32dip 36dip;
fill: color(lighten4);
stroke: none;
transform: translate(-8dip, -100%) scale(0);
transition: transform 160ms;
transform-origin: 50% 100%;
filter: drop-shadow(1dip, 1dip, 1dip, #999);
}
:root:focus > knob::marker,
:root:hover > knob::marker {
transform: translate(-8dip, -100%) scale(1);
}
}
input[type="checkbox"] {
appearance:none;
height: 1em;
width: 1em;
border: 1px solid #818183;
background: none;
border-radius: 0.3em;
}
input[type="checkbox"]:checked {
background: no-repeat 50% 50%;
background-image: url(stock:checkmark);
background-size: 0.7em;
}
</style>
</head>
<header>
<window-caption role=window-caption><img src="window-icon-32.png" /><span>NxEmu Configuration</span></window-caption>
<window-buttons>
<window-button role="window-close"></window-button>
</window-buttons>
</header>
<body>
<div id="SettingsPage">
<PageNav target="ConfigContents" id="MainTabNav">
<Page contents="system_config_audio.html" label="Audio" Name="Audio" />
</PageNav>
<div id="ConfigContents" />
</div>
<div class="footer">
<button role="window-close">OK</button>
<button role="window-close">Cancel</button>
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
<div class="input-group">
<div class="group-title">Audio</div>
<div class="form-row">
<div class="left"><label for="audioOutputEngine">Output Engine:</label></div>
<div class="right"><select id="audioOutputEngine"></select></div>
</div>
<div class="form-row">
<div class="left"><label for="audioOutputDevice">Output Device:</label></div>
<div class="right"><select id="audioOutputDevice"></select></div>
</div>
<div class="form-row">
<div class="left"><label for="inputDevice">Input Device:</label></div>
<div class="right"><select id="inputDevice"></select></div>
</div>
<div class="form-row">
<div class="left"><label for="soundMode">Sound Output Mode:</label></div>
<div class="right"><select id="soundMode"></select></div>
</div>
<div class="form-row slider-row">
<div class="left"><label for="volume">Volume:</label></div>
<div class="right"><input type="hslider" id="Volume" min="0" max="100" value="200" style="width: 20em" /><span id="VolumeDisplay">100%</span></div>
</div>
<div class="form-row checkbox-row">
<div class="checkbox"><input type="checkbox" id="muteAudio"><label for="muteAudio">Mute audio</label></div>
</div>
<div class="form-row checkbox-row">
<div class="checkbox"><input type="checkbox" id="muteBackground"><label for="muteBackground">Mute audio when in background</label></div>
</div>
</div>

View File

@ -7,11 +7,13 @@
#include <nxemu-core/version.h>
#include <sciter_ui.h>
#include <widgets/menubar.h>
#include <widgets/page_nav.h>
#include <windows.h>
void RegisterWidgets(ISciterUI & sciterUI)
{
WidgetMenuBar::Register(sciterUI);
Register_WidgetMenuBar(sciterUI);
Register_WidgetPageNav(sciterUI);
}
int WINAPI WinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ LPSTR /*lpszArgs*/, _In_ int /*nWinMode*/)

View File

@ -40,8 +40,9 @@
<ClCompile Include="user_interface\menu_item.cpp" />
<ClCompile Include="user_interface\notification.cpp" />
<ClCompile Include="user_interface\sciter_main_window.cpp" />
<ClCompile Include="user_interface\settings\config_setting_section.cpp" />
<ClCompile Include="user_interface\settings\system_config.cpp" />
<ClCompile Include="user_interface\settings\input_config.cpp" />
<ClCompile Include="user_interface\settings\system_config_audio.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="settings\ui_settings.h" />
@ -49,8 +50,9 @@
<ClInclude Include="user_interface\notification.h" />
<ClInclude Include="user_interface\resource.h" />
<ClInclude Include="user_interface\sciter_main_window.h" />
<ClInclude Include="user_interface\settings\config_setting_section.h" />
<ClInclude Include="user_interface\settings\system_config.h" />
<ClInclude Include="user_interface\settings\input_config.h" />
<ClInclude Include="user_interface\settings\system_config_audio.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\external\sciterui\src\sciterui\sciterui.vcxproj">

View File

@ -45,15 +45,18 @@
<ClCompile Include="user_interface\notification.cpp">
<Filter>Source Files\user_interface</Filter>
</ClCompile>
<ClCompile Include="user_interface\settings\config_setting_section.cpp">
<Filter>Source Files\user_interface\settings</Filter>
</ClCompile>
<ClCompile Include="user_interface\sciter_main_window.cpp">
<Filter>Source Files\user_interface</Filter>
</ClCompile>
<ClCompile Include="user_interface\settings\input_config.cpp">
<Filter>Source Files\user_interface\settings</Filter>
</ClCompile>
<ClCompile Include="user_interface\settings\system_config.cpp">
<Filter>Source Files\user_interface\settings</Filter>
</ClCompile>
<ClCompile Include="user_interface\settings\system_config_audio.cpp">
<Filter>Source Files\user_interface\settings</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="user_interface\resource.h">
@ -68,15 +71,18 @@
<ClInclude Include="user_interface\notification.h">
<Filter>Header Files\user_interface</Filter>
</ClInclude>
<ClInclude Include="user_interface\settings\config_setting_section.h">
<Filter>Header Files\user_interface\settings</Filter>
</ClInclude>
<ClInclude Include="user_interface\sciter_main_window.h">
<Filter>Header Files\user_interface</Filter>
</ClInclude>
<ClInclude Include="user_interface\settings\input_config.h">
<Filter>Header Files\user_interface\settings</Filter>
</ClInclude>
<ClInclude Include="user_interface\settings\system_config.h">
<Filter>Header Files\user_interface\settings</Filter>
</ClInclude>
<ClInclude Include="user_interface\settings\system_config_audio.h">
<Filter>Header Files\user_interface\settings</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="user_interface\nxemu.rc">

View File

@ -1,5 +1,6 @@
#include "sciter_main_window.h"
#include "settings/input_config.h"
#include "settings/system_config.h"
#include "settings/ui_settings.h"
#include <Windows.h>
#include <common/std_string.h>
@ -11,11 +12,12 @@
#include <widgets/menubar.h>
#include <yuzu_common/settings_input.h>
SciterMainWindow::SciterMainWindow(ISciterUI & SciterUI, const char * windowTitle) :
m_sciterUI(SciterUI),
SciterMainWindow::SciterMainWindow(ISciterUI & sciterUI, const char * windowTitle) :
m_sciterUI(sciterUI),
m_window(nullptr),
m_renderWindow(nullptr),
m_windowTitle(windowTitle)
m_windowTitle(windowTitle),
m_systemConfig(sciterUI)
{
Settings & settings = Settings::GetInstance();
settings.RegisterCallback(NXCoreSetting::GameFile, std::bind(&SciterMainWindow::GameFileChanged, this));
@ -31,7 +33,8 @@ void SciterMainWindow::ResetMenu()
return;
}
MenuBarItemList fileMenu;
fileMenu.push_back(MenuBarItem(ID_FILE_OPEN_GAME, "Open Game..."));
fileMenu.push_back(MenuBarItem(ID_FILE_LOAD_FILE, "Load File..."));
fileMenu.push_back(MenuBarItem(MenuBarItem::SPLITER));
Stringlist & recentFiles = uiSettings.recentFiles;
MenuBarItemList RecentGameMenu;
@ -49,12 +52,13 @@ void SciterMainWindow::ResetMenu()
fileMenu.push_back(MenuBarItem(MenuBarItem::SPLITER));
fileMenu.push_back(MenuBarItem(ID_FILE_EXIT, "Exit"));
MenuBarItemList OptionsMenu;
OptionsMenu.push_back(MenuBarItem(ID_OPTIONS_INPUT, "Configure Input..."));
MenuBarItemList emulationMenu;
emulationMenu.push_back(MenuBarItem(ID_EMULATION_CONTROLLERS, "Controllers..."));
emulationMenu.push_back(MenuBarItem(ID_EMULATION_CONFIGURE, "Configure..."));
MenuBarItemList mainTitleMenu;
mainTitleMenu.push_back(MenuBarItem(MenuBarItem::SUB_MENU, "File", &fileMenu));
mainTitleMenu.push_back(MenuBarItem(MenuBarItem::SUB_MENU, "Options", &OptionsMenu));
mainTitleMenu.push_back(MenuBarItem(MenuBarItem::SUB_MENU, "Emulation", &emulationMenu));
m_menuBar->AddSink(this);
m_menuBar->SetMenuContent(mainTitleMenu);
@ -351,7 +355,7 @@ void SciterMainWindow::DisplayedFramesChanged(void)
ShowWindow((HWND)m_renderWindow, SW_SHOW);
}
void SciterMainWindow::OnOpenGame(void)
void SciterMainWindow::OnOpenFile(void)
{
SwitchSystem * system = SwitchSystem::GetInstance();
system->Systemloader().SelectAndLoad((void *)m_window->GetHandle());
@ -362,6 +366,11 @@ void SciterMainWindow::OnFileExit(void)
m_sciterUI.Stop();
}
void SciterMainWindow::OnSystemConfig(void)
{
m_systemConfig.Display((void*)m_window->GetHandle());
}
void SciterMainWindow::OnInputConfig(void)
{
InputConfig(m_sciterUI).Display((void *)m_window->GetHandle());
@ -386,9 +395,10 @@ void SciterMainWindow::OnMenuItem(int32_t id, SCITER_ELEMENT /*item*/)
{
switch (id)
{
case ID_FILE_OPEN_GAME: OnOpenGame(); break;
case ID_FILE_LOAD_FILE: OnOpenFile(); break;
case ID_FILE_EXIT: OnFileExit(); break;
case ID_OPTIONS_INPUT: OnInputConfig(); break;
case ID_EMULATION_CONTROLLERS: OnInputConfig(); break;
case ID_EMULATION_CONFIGURE: OnSystemConfig(); break;
default:
if (id >= ID_RECENT_FILE_START && id <= ID_RECENT_FILE_END)
{

View File

@ -3,6 +3,7 @@
#include <nxemu-core/modules/module_base.h>
#include <sciter_ui.h>
#include <widgets/menubar.h>
#include "user_interface/settings/system_config.h"
class SciterMainWindow :
public IWindowDestroySink,
@ -14,11 +15,12 @@ class SciterMainWindow :
enum MainMenuID
{
// File Menu
ID_FILE_OPEN_GAME = 1,
ID_FILE_LOAD_FILE = 1,
ID_FILE_EXIT,
// Options Menu
ID_OPTIONS_INPUT,
// Emulation Menu
ID_EMULATION_CONTROLLERS,
ID_EMULATION_CONFIGURE,
// Recent files
ID_RECENT_FILE_START,
@ -26,7 +28,7 @@ class SciterMainWindow :
};
public:
SciterMainWindow(ISciterUI & SciterUI, const char * windowTitle);
SciterMainWindow(ISciterUI & sciterUI, const char * windowTitle);
void ResetMenu();
bool Show(void);
@ -45,8 +47,9 @@ private:
int32_t SciterKeyToSwitchKey(SciterKeys key);
int32_t SciterKeyToVKCode(SciterKeys vkcode);
void OnOpenGame(void);
void OnOpenFile(void);
void OnFileExit(void);
void OnSystemConfig(void);
void OnInputConfig(void);
void OnRecetGame(uint32_t fileIndex);
@ -73,4 +76,5 @@ private:
std::shared_ptr<IMenuBar> m_menuBar;
void * m_renderWindow;
std::string m_windowTitle;
SystemConfig m_systemConfig;
};

View File

@ -1,30 +0,0 @@
#include "config_setting_section.h"
ConfigSettingSection::ConfigSettingSection(const wchar_t * pageTitle) :
m_pageTitle(pageTitle)
{
}
void ConfigSettingSection::AddPage(std::shared_ptr<SettingsPage> page)
{
m_pages.push_back(page);
}
SettingsPage * ConfigSettingSection::GetPage(uint32_t pageNo)
{
if (pageNo < 0 || pageNo >= (uint32_t)m_pages.size())
{
return NULL;
}
return m_pages[pageNo].get();
}
const wchar_t * ConfigSettingSection::GetPageTitle(void) const
{
return m_pageTitle.c_str();
}
size_t ConfigSettingSection::GetPageCount(void) const
{
return m_pages.size();
}

View File

@ -1,28 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class SettingsPage;
class ConfigSettingSection
{
public:
ConfigSettingSection(const wchar_t * pageTitle);
void AddPage(std::shared_ptr<SettingsPage> page);
SettingsPage * GetPage(uint32_t pageNo);
const wchar_t * GetPageTitle(void) const;
size_t GetPageCount(void) const;
private:
ConfigSettingSection() = delete;
ConfigSettingSection(const ConfigSettingSection &) = delete;
ConfigSettingSection & operator=(const ConfigSettingSection &) = delete;
typedef std::vector<std::shared_ptr<SettingsPage>> SETTING_PAGES;
SETTING_PAGES m_pages;
std::wstring m_pageTitle;
};

View File

@ -0,0 +1,66 @@
#include "system_config.h"
#include "system_config_audio.h"
#include <sciter_ui.h>
#include <sciter_element.h>
#include <widgets/page_nav.h>
SystemConfig::SystemConfig(ISciterUI & SciterUI) :
m_sciterUI(SciterUI),
m_window(nullptr)
{
}
SystemConfig::~SystemConfig()
{
}
void SystemConfig::Display(void * parentWindow)
{
enum
{
WINDOW_HEIGHT = 300,
WINDOW_WIDTH = 300,
};
if (!m_sciterUI.WindowCreate(parentWindow, "system_config.html", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SUIW_CHILD, m_window))
{
return;
}
SciterElement root(m_window->GetRootElement());
if (root.IsValid())
{
SciterElement pageNav = root.GetElementByID("MainTabNav");
if (pageNav.IsValid())
{
void * interfacePtr = nullptr;
if (m_sciterUI.GetElementInterface(pageNav, IID_IPAGENAV, &interfacePtr))
{
m_pageNav = std::shared_ptr<IPageNav>(reinterpret_cast<IPageNav*>(interfacePtr), [](IPageNav*) {});
m_pageNav->AddSink(this);
}
}
}
}
bool SystemConfig::PageNavChangeFrom(const std::string& PageName, SCITER_ELEMENT PageNav)
{
return true;
}
bool SystemConfig::PageNavChangeTo(const std::string& PageName, SCITER_ELEMENT PageNav)
{
return true;
}
void SystemConfig::PageNavCreatedPage(const std::string& PageName, SCITER_ELEMENT Page)
{
if (PageName == "Audio")
{
m_systemConfigAudio.reset(new SystemConfigAudio(m_sciterUI, m_window->GetHandle(), Page));
}
}
void SystemConfig::PageNavPageChanged(const std::string& PageName, SCITER_ELEMENT PageNav)
{
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <widgets/page_nav.h>
__interface ISciterUI;
__interface ISciterWindow;
class SystemConfigAudio;
class SystemConfig :
public IPagesSink
{
public:
SystemConfig(ISciterUI& SciterUI);
~SystemConfig();
void Display(void* parentWindow);
// IPagesSink
bool PageNavChangeFrom(const std::string& PageName, SCITER_ELEMENT PageNav) override;
bool PageNavChangeTo(const std::string& PageName, SCITER_ELEMENT PageNav) override;
void PageNavCreatedPage(const std::string& PageName, SCITER_ELEMENT Page) override;
void PageNavPageChanged(const std::string& PageName, SCITER_ELEMENT PageNav) override;
private:
SystemConfig() = delete;
SystemConfig(const SystemConfig &) = delete;
SystemConfig & operator=(const SystemConfig &) = delete;
ISciterUI & m_sciterUI;
ISciterWindow * m_window;
std::shared_ptr<IPageNav> m_pageNav;
std::unique_ptr<SystemConfigAudio> m_systemConfigAudio;
};

View File

@ -0,0 +1,8 @@
#include "system_config_audio.h"
SystemConfigAudio::SystemConfigAudio(ISciterUI & sciterUI, HWINDOW parent, SciterElement page) :
m_sciterUI(sciterUI),
m_parent(parent),
m_page(page)
{
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <sciter_ui.h>
#include <sciter_element.h>
class SystemConfigAudio
{
public:
SystemConfigAudio(ISciterUI & sciterUI, HWINDOW parent, SciterElement page);
~SystemConfigAudio() = default;
private:
SystemConfigAudio() = delete;
SystemConfigAudio(const SystemConfigAudio &) = delete;
SystemConfigAudio & operator=(const SystemConfigAudio &) = delete;
ISciterUI & m_sciterUI;
HWINDOW m_parent;
SciterElement m_page;
};