Add initial work on Qt frontend

This commit is contained in:
Connor McLaughlin 2019-12-31 16:17:17 +10:00
parent 98214a9327
commit f3e9c3ec8c
64 changed files with 3490 additions and 14 deletions

View File

@ -27,7 +27,7 @@ jobs:
run: |
mkdir build-debug
cd build-debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_QT_FRONTEND=OFF ..
make
- name: Compile release build
@ -35,6 +35,6 @@ jobs:
run: |
mkdir build-release
cd build-release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_QT_FRONTEND=OFF ..
make

View File

@ -4,6 +4,11 @@ project(duckstation C CXX)
# Pull in modules.
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules/")
if(NOT ANDROID)
option(BUILD_SDL_FRONTEND "Build the SDL frontend" ON)
option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
endif()
# Common include/library directories on Windows.
if(WIN32)
@ -30,8 +35,15 @@ endif()
# Required libraries.
if(NOT ANDROID)
if(BUILD_SDL_FRONTEND)
find_package(SDL2 REQUIRED)
else()
endif()
if(BUILD_QT_FRONTEND)
find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)
endif()
endif()
if(ANDROID)
find_package(EGL REQUIRED)
endif()

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="UserMacros">
<QTDIRDefault>$(SolutionDir)dep\msvc\qt5-x64\</QTDIRDefault>
<QTDIR Condition="Exists('$(QTDIRDefault)') And ('$(QTDIR)'=='' Or !Exists('$(QTDIR)'))">$(QTDIRDefault)</QTDIR>
<QTDIR Condition="Exists('$(QTDIR)') And !HasTrailingSlash('$(QTDIR)')">$(QTDIR)\</QTDIR>
<QtDirValid>false</QtDirValid>
<QtDirValid Condition="Exists('$(QTDIR)')">true</QtDirValid>
<QtIncludeDir>$(QTDIR)include\</QtIncludeDir>
<QtLibDir>$(QTDIR)lib\</QtLibDir>
<QtBinDir>$(QTDIR)bin\</QtBinDir>
<QtPluginsDir>$(QTDIR)plugins\</QtPluginsDir>
<QtToolOutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</QtToolOutDir>
<QtMocOutPrefix>$(QtToolOutDir)moc_</QtMocOutPrefix>
<QtDebugSuffix>d</QtDebugSuffix>
<QtLibSuffix Condition="'$(Configuration)'=='Debug'">$(QtDebugSuffix)</QtLibSuffix>
<QtPluginFolder>QtPlugins</QtPluginFolder>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions Condition="'$(Configuration)'=='Release'">QT_NO_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtToolOutDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtIncludeDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(QtLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>qtmain$(QtLibSuffix).lib;Qt5Core$(QtLibSuffix).lib;Qt5Gui$(QtLibSuffix).lib;Qt5Widgets$(QtLibSuffix).lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<!--Passes all .qrc files to rcc and puts output in the build directory-->
<ItemGroup>
<ResFiles Include="$(MSBuildProjectDirectory)\**\*.qrc" />
</ItemGroup>
<Target Name="QtResource"
BeforeTargets="ClCompile"
Inputs="@(ResFiles)"
Condition="'@(QtResource)'!=''"
Outputs="@(ResFiles->'$(QtToolOutDir)qrc_%(Filename).cpp')">
<Message Text="rcc %(ResFiles.Filename)" Importance="High" />
<Error Condition="!$(QtDirValid)" Text="QTDIR not set or non-existent (pull the submodule?)" />
<MakeDir Directories="$(QtToolOutDir)" />
<Exec Command="&quot;$(QtBinDir)rcc.exe&quot; &quot;%(ResFiles.FullPath)&quot; -o &quot;$(QtToolOutDir)qrc_%(ResFiles.Filename).cpp&quot;" />
</Target>
<Target Name="QtResourceClean">
<Delete Files="@(ResFiles->'$(QtToolOutDir)qrc_%(Filename).cpp')" />
</Target>
<!--Passes all .ui files to uic and puts output in the build directory-->
<ItemGroup>
<UiFiles Include="$(MSBuildProjectDirectory)\**\*.ui" />
</ItemGroup>
<Target Name="QtUi"
BeforeTargets="ClCompile"
Inputs="@(UiFiles)"
Condition="'@(QtUi)'!=''"
Outputs="@(UiFiles->'$(QtToolOutDir)ui_%(Filename).h')">
<Message Text="uic %(UiFiles.Filename)" Importance="High" />
<Error Condition="!$(QtDirValid)" Text="QTDIR not set or non-existent (pull the submodule?)" />
<MakeDir Directories="$(QtToolOutDir)" />
<Exec Command="&quot;$(QtBinDir)uic.exe&quot; &quot;%(UiFiles.FullPath)&quot; -o &quot;$(QtToolOutDir)ui_%(UiFiles.Filename).h&quot;" />
</Target>
<Target Name="QtUiClean">
<Delete Files="@(UiFiles->'$(QtToolOutDir)ui_%(Filename).h')" />
</Target>
<!--Compile files needed to MOC and output in the build directory-->
<!--TODO find a way to autocreate from ClCompile settings-->
<PropertyGroup>
<MocDefines></MocDefines>
<MocDefines Condition="'$(Configuration)'=='Release'">-DQT_NO_DEBUG -DNDEBUG $(MocDefines)</MocDefines>
<!-- !!!HOLY UGLY BATMAN!!!
Be very careful here when adding include directories. Each path must have the whole arg surrounded by doublequotes - HOWEVER,
the ending doublequote cannot be directly preceeded by a directory seperator. In other words, you must use:
"-I$(SomeDir) "
instead of
"-I$(SomeDir)"
in order to prevent the trailing slash from escaping the doublequote after value replacement.
-->
<MocIncludes>"-I$(QtIncludeDir)" "-I$(SolutionDir)src" -I.</MocIncludes>
</PropertyGroup>
<Target Name="QtMoc"
BeforeTargets="ClCompile"
Condition="'@(QtMoc)'!=''"
Inputs="%(QtMoc.Identity);%(QtMoc.AdditionalDependencies);$(MSBuildProjectFile)"
Outputs="$(QtToolOutDir)moc_%(QtMoc.Filename).cpp">
<Message Text="moc %(QtMoc.Filename) $(QtToolOutDir)moc_%(QtMoc.Filename).cpp" Importance="High" />
<Error Condition="!$(QtDirValid)" Text="QTDIR not set or non-existent (pull the submodule?)" />
<MakeDir Directories="$(QtToolOutDir)" />
<Exec Command="&quot;$(QtBinDir)moc.exe&quot; &quot;%(QtMoc.FullPath)&quot; -o &quot;$(QtToolOutDir)moc_%(QtMoc.Filename).cpp&quot; -f%(QtMoc.Filename)%(QtMoc.Extension) $(MocDefines) $(MocIncludes)" />
</Target>
<ItemGroup>
<MocOutputs Include="$(QtToolOutDir)moc_*.cpp" />
</ItemGroup>
<Target Name="QtMocClean">
<Delete Files="@(MocOutputs)" />
</Target>
<!--Expose the new targets to VS-->
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
<AvailableItemName Include="QtResource">
<Targets>QtResource</Targets>
</AvailableItemName>
<AvailableItemName Include="QtUi">
<Targets>QtUi</Targets>
</AvailableItemName>
<AvailableItemName Include="QtMoc">
<Targets>QtMoc</Targets>
</AvailableItemName>
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CleanDependsOn>QtResourceClean;QtUiClean;QtMocClean;$(CleanDependsOn)</CleanDependsOn>
<!--
<ComputeLinkInputsTargets>$(ComputeLinkInputsTargets);QtComputeMocOutput;</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>$(ComputeLibInputsTargets);QtComputeMocOutput;</ComputeLibInputsTargets>
-->
</PropertyGroup>
</Project>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
<ItemType Name="QtResource" DisplayName="Qt Resource File" />
<ItemType Name="QtUi" DisplayName="Qt User Interface File" />
<ItemType Name="QtMoc" DisplayName="Qt Meta Object File" />
<ContentType Name="QtUi" DisplayName="Qt User Interface Compiler" />
<ContentType Name="QtResource" DisplayName="Qt Resource Compiler" />
<ContentType Name="QtMoc" DisplayName="Qt Meta Object Compiler" />
<FileExtension Name="*.qrc" ContentType="QtResource" />
<FileExtension Name="*.ui" ContentType="QtUi" />
</ProjectSchemaDefinitions>

View File

@ -27,6 +27,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simpleini", "dep\simpleini\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinyxml2", "dep\tinyxml2\tinyxml2.vcxproj", "{933118A9-68C5-47B4-B151-B03C93961623}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "duckstation-qt", "src\duckstation-qt\duckstation-qt.vcxproj", "{28F14272-0EC4-41BB-849F-182ADB81AF70}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -215,6 +217,18 @@ Global
{933118A9-68C5-47B4-B151-B03C93961623}.ReleaseLTCG|x64.Build.0 = Release|x64
{933118A9-68C5-47B4-B151-B03C93961623}.ReleaseLTCG|x86.ActiveCfg = Release|Win32
{933118A9-68C5-47B4-B151-B03C93961623}.ReleaseLTCG|x86.Build.0 = Release|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x64.ActiveCfg = Debug|x64
{28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x86.ActiveCfg = Debug|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.Debug|x86.Build.0 = Debug|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x64.ActiveCfg = DebugFast|x64
{28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x86.ActiveCfg = DebugFast|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.DebugFast|x86.Build.0 = DebugFast|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x64.ActiveCfg = Release|x64
{28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x86.ActiveCfg = Release|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.Release|x86.Build.0 = Release|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64
{28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32
{28F14272-0EC4-41BB-849F-182ADB81AF70}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,6 +1,11 @@
add_subdirectory(common)
add_subdirectory(core)
if(NOT ANDROID)
if(BUILD_SDL_FRONTEND)
add_subdirectory(duckstation)
endif()
if(BUILD_QT_FRONTEND)
add_subdirectory(duckstation-qt)
endif()

View File

@ -35,7 +35,7 @@ static std::string GetPathDirectory(const char* path)
const char* backslash_ptr = std::strrchr(path, '\\');
const char* slash_ptr;
if (forwardslash_ptr && backslash_ptr)
slash_ptr = std::min(forwardslash_ptr, backslash_ptr);
slash_ptr = std::max(forwardslash_ptr, backslash_ptr);
else if (backslash_ptr)
slash_ptr = backslash_ptr;
else if (forwardslash_ptr)

View File

@ -4,6 +4,7 @@
#include "bios.h"
#include "common/cd_image.h"
#include "common/iso_reader.h"
#include "settings.h"
#include <algorithm>
#include <array>
#include <cctype>
@ -194,11 +195,6 @@ std::optional<ConsoleRegion> GameList::GetRegionForPath(const char* image_path)
return GetRegionForImage(cdi.get());
}
void GameList::AddDirectory(const char* path, bool recursive)
{
ScanDirectory(path, recursive);
}
bool GameList::IsExeFileName(const char* path)
{
const char* extension = std::strrchr(path, '.');
@ -404,6 +400,40 @@ private:
GameList::DatabaseMap& m_database;
};
void GameList::AddDirectory(std::string path, bool recursive)
{
auto iter = std::find_if(m_search_directories.begin(), m_search_directories.end(),
[&path](const DirectoryEntry& de) { return de.path == path; });
if (iter != m_search_directories.end())
{
iter->recursive = recursive;
return;
}
m_search_directories.push_back({path, recursive});
}
void GameList::SetDirectoriesFromSettings(SettingsInterface& si)
{
m_search_directories.clear();
std::vector<std::string> dirs = si.GetStringList("GameList", "Paths");
for (std::string& dir : dirs)
m_search_directories.push_back({std::move(dir), false});
dirs = si.GetStringList("GameList", "RecursivePaths");
for (std::string& dir : dirs)
m_search_directories.push_back({std::move(dir), true});
}
void GameList::RescanAllDirectories()
{
m_entries.clear();
for (const DirectoryEntry& de : m_search_directories)
ScanDirectory(de.path.c_str(), de.recursive);
}
bool GameList::ParseRedumpDatabase(const char* redump_dat_path)
{
tinyxml2::XMLDocument doc;
@ -427,3 +457,8 @@ bool GameList::ParseRedumpDatabase(const char* redump_dat_path)
Log_InfoPrintf("Loaded %zu entries from Redump.org database '%s'", m_database.size(), redump_dat_path);
return true;
}
void GameList::ClearDatabase()
{
m_database.clear();
}

View File

@ -8,6 +8,8 @@
class CDImage;
class SettingsInterface;
class GameList
{
public:
@ -54,18 +56,28 @@ public:
const EntryList& GetEntries() const { return m_entries; }
const u32 GetEntryCount() const { return static_cast<u32>(m_entries.size()); }
void AddDirectory(const char* path, bool recursive);
void AddDirectory(std::string path, bool recursive);
void SetDirectoriesFromSettings(SettingsInterface& si);
void RescanAllDirectories();
bool ParseRedumpDatabase(const char* redump_dat_path);
void ClearDatabase();
private:
struct DirectoryEntry
{
std::string path;
bool recursive;
};
static bool IsExeFileName(const char* path);
static bool GetExeListEntry(const char* path, GameListEntry* entry);
bool GetGameListEntry(const char* path, GameListEntry* entry);
void ScanDirectory(const char* path, bool recursive);
DatabaseMap m_database;
EntryList m_entries;
std::vector<DirectoryEntry> m_search_directories;
};

View File

@ -0,0 +1,32 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
add_executable(duckstation-qt
resources/icons.qrc
consolesettingswidget.cpp
consolesettingswidget.h
consolesettingswidget.ui
gamelistsettingswidget.cpp
gamelistsettingswidget.h
gamelistsettingswidget.ui
gamelistwidget.cpp
gamelistwidget.h
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
opengldisplaywindow.cpp
opengldisplaywindow.h
qthostinterface.cpp
qthostinterface.h
qtsettingsinterface.cpp
qtsettingsinterface.h
qtutils.cpp
qtutils.h
settingsdialog.cpp
settingsdialog.h
settingsdialog.ui
)
target_link_libraries(duckstation-qt PRIVATE YBaseLib core common imgui glad Qt5::Core Qt5::Gui Qt5::Widgets)

View File

@ -0,0 +1,8 @@
#include "consolesettingswidget.h"
ConsoleSettingsWidget::ConsoleSettingsWidget(QWidget* parent /*= nullptr*/) : QWidget(parent)
{
m_ui.setupUi(this);
}
ConsoleSettingsWidget::~ConsoleSettingsWidget() = default;

View File

@ -0,0 +1,17 @@
#pragma once
#include <QtWidgets/QWidget>
#include "ui_consolesettingswidget.h"
class ConsoleSettingsWidget : public QWidget
{
Q_OBJECT
public:
explicit ConsoleSettingsWidget(QWidget* parent = nullptr);
~ConsoleSettingsWidget();
private:
Ui::ConsoleSettingsWidget m_ui;
};

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConsoleSettingsWidget</class>
<widget class="QWidget" name="ConsoleSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>502</width>
<height>308</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="region">
<item>
<property name="text">
<string>Auto-Detect</string>
</property>
</item>
<item>
<property name="text">
<string>NTSC-U (US)</string>
</property>
</item>
<item>
<property name="text">
<string>NTSC-J (Japan)</string>
</property>
</item>
<item>
<property name="text">
<string>PAL (Europe, Australia)</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>BIOS Path:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="biosPath"/>
</item>
<item>
<widget class="QPushButton" name="biosPathBrowse">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="enableTTYOutput">
<property name="text">
<string>Enable TTY Output</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="fastBoot">
<property name="text">
<string>Fast Boot</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="enableSpeedLimiter">
<property name="text">
<string>Enable Speed Limiter</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="pauseOnStart">
<property name="text">
<string>Pause On Start</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,426 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="DebugFast|Win32">
<Configuration>DebugFast</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="DebugFast|x64">
<Configuration>DebugFast</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseLTCG|Win32">
<Configuration>ReleaseLTCG</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseLTCG|x64">
<Configuration>ReleaseLTCG</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="consolesettingswidget.cpp" />
<ClCompile Include="gamelistsettingswidget.cpp" />
<ClCompile Include="gamelistwidget.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="mainwindow.cpp" />
<ClCompile Include="opengldisplaywindow.cpp" />
<ClCompile Include="qthostinterface.cpp" />
<ClCompile Include="qtsettingsinterface.cpp" />
<ClCompile Include="qtutils.cpp" />
<ClCompile Include="settingsdialog.cpp" />
</ItemGroup>
<ItemGroup>
<QtMoc Include="consolesettingswidget.h" />
<QtMoc Include="gamelistsettingswidget.h" />
<QtMoc Include="gamelistwidget.h" />
<QtMoc Include="mainwindow.h" />
<QtMoc Include="opengldisplaywindow.h" />
<QtMoc Include="qthostinterface.h" />
<ClInclude Include="qtsettingsinterface.h" />
<ClInclude Include="qtutils.h" />
<QtMoc Include="settingsdialog.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\dep\glad\glad.vcxproj">
<Project>{43540154-9e1e-409c-834f-b84be5621388}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\YBaseLib\Source\YBaseLib.vcxproj">
<Project>{b56ce698-7300-4fa5-9609-942f1d05c5a2}</Project>
</ProjectReference>
<ProjectReference Include="..\common\common.vcxproj">
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
</ProjectReference>
<ProjectReference Include="..\core\core.vcxproj">
<Project>{868b98c8-65a1-494b-8346-250a73a48c0a}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<QtUi Include="consolesettingswidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="gamelistsettingswidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="mainwindow.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="settingsdialog.ui">
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<ItemGroup>
<QtResource Include="resources\icons.qrc">
<FileType>Document</FileType>
</QtResource>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
<ClCompile Include="$(IntDir)moc_opengldisplaywindow.cpp" />
<ClCompile Include="$(IntDir)moc_qthostinterface.cpp" />
<ClCompile Include="$(IntDir)moc_settingsdialog.cpp" />
<ClCompile Include="$(IntDir)qrc_icons.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{28F14272-0EC4-41BB-849F-182ADB81AF70}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>duckstation-qt</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="..\..\dep\msvc\vsprops\QtCompile.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64-debug;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64-debug;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
<OmitFramePointers>true</OmitFramePointers>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\Include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
<OmitFramePointers>true</OmitFramePointers>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\dep\msvc\vsprops\QtCompile.targets" />
</ImportGroup>
</Project>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="mainwindow.cpp" />
<ClCompile Include="gamelistwidget.cpp" />
<ClCompile Include="settingsdialog.cpp" />
<ClCompile Include="consolesettingswidget.cpp" />
<ClCompile Include="opengldisplaywindow.cpp" />
<ClCompile Include="qthostinterface.cpp" />
<ClCompile Include="gamelistsettingswidget.cpp" />
<ClCompile Include="qtsettingsinterface.cpp" />
<ClCompile Include="qtutils.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="mainwindow.h" />
<ClInclude Include="gamelistwidget.h" />
<ClInclude Include="settingsdialog.h" />
<ClInclude Include="consolesettingswidget.h" />
<ClInclude Include="opengldisplaywindow.h" />
<ClInclude Include="qthostinterface.h" />
<ClInclude Include="gamelistsettingswidget.h" />
<ClInclude Include="qtsettingsinterface.h" />
<ClInclude Include="qtutils.h" />
</ItemGroup>
<ItemGroup>
<None Include="consolesettingswidget.ui" />
<None Include="gamelistsettingswidget.ui" />
<None Include="mainwindow.ui" />
<None Include="settingsdialog.ui" />
<None Include="resources\icons.qrc">
<Filter>resources</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Filter Include="resources">
<UniqueIdentifier>{3b2587ae-ce3b-4eb5-ada2-237e853620cf}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@ -0,0 +1,230 @@
#include "gamelistsettingswidget.h"
#include "qthostinterface.h"
#include "qtutils.h"
#include <QtCore/QAbstractTableModel>
#include <QtCore/QSettings>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMessageBox>
#include <algorithm>
class GameListSearchDirectoriesModel : public QAbstractTableModel
{
public:
GameListSearchDirectoriesModel(QSettings& settings) : m_settings(settings) {}
~GameListSearchDirectoriesModel() = default;
int columnCount(const QModelIndex& parent) const override
{
if (parent.isValid())
return 0;
return 2;
}
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
return {};
if (section == 0)
return tr("Path");
else
return tr("Recursive");
}
int rowCount(const QModelIndex& parent) const override
{
if (parent.isValid())
return 0;
return static_cast<int>(m_entries.size());
}
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return {};
const int row = index.row();
const int column = index.column();
if (row < 0 || row >= static_cast<int>(m_entries.size()))
return {};
const Entry& entry = m_entries[row];
if (role == Qt::CheckStateRole)
{
if (column == 1)
return entry.recursive ? Qt::Checked : Qt::Unchecked;
}
else if (role == Qt::DisplayRole)
{
if (column == 0)
return entry.path;
}
return {};
}
void addEntry(const QString& path, bool recursive)
{
if (std::find_if(m_entries.begin(), m_entries.end(), [path](const Entry& e) { return e.path == path; }) !=
m_entries.end())
{
return;
}
beginInsertRows(QModelIndex(), static_cast<int>(m_entries.size()), static_cast<int>(m_entries.size() + 1));
m_entries.push_back({path, recursive});
endInsertRows();
}
void removeEntry(int row)
{
if (row < 0 || row >= static_cast<int>(m_entries.size()))
return;
beginRemoveRows(QModelIndex(), row, row);
m_entries.erase(m_entries.begin() + row);
endRemoveRows();
}
void loadFromSettings()
{
QStringList path_list = m_settings.value(QStringLiteral("GameList/Paths")).toStringList();
for (QString& entry : path_list)
m_entries.push_back({std::move(entry), false});
path_list = m_settings.value(QStringLiteral("GameList/RecursivePaths")).toStringList();
for (QString& entry : path_list)
m_entries.push_back({std::move(entry), true});
}
void saveToSettings()
{
QStringList paths;
QStringList recursive_paths;
for (const Entry& entry : m_entries)
{
if (entry.recursive)
recursive_paths.push_back(entry.path);
else
paths.push_back(entry.path);
}
if (paths.empty())
m_settings.remove(QStringLiteral("GameList/Paths"));
else
m_settings.setValue(QStringLiteral("GameList/Paths"), paths);
if (recursive_paths.empty())
m_settings.remove(QStringLiteral("GameList/RecursivePaths"));
else
m_settings.setValue(QStringLiteral("GameList/RecursivePaths"), recursive_paths);
}
private:
struct Entry
{
QString path;
bool recursive;
};
QSettings& m_settings;
std::vector<Entry> m_entries;
};
GameListSettingsWidget::GameListSettingsWidget(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
: QWidget(parent), m_host_interface(host_interface)
{
m_ui.setupUi(this);
QSettings& qsettings = host_interface->getQSettings();
m_search_directories_model = new GameListSearchDirectoriesModel(qsettings);
m_search_directories_model->loadFromSettings();
m_ui.redumpDatabasePath->setText(qsettings.value("GameList/RedumpDatabasePath").toString());
m_ui.searchDirectoryList->setModel(m_search_directories_model);
m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection);
m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows);
m_ui.searchDirectoryList->setAlternatingRowColors(true);
m_ui.searchDirectoryList->setShowGrid(false);
m_ui.searchDirectoryList->verticalHeader()->hide();
m_ui.searchDirectoryList->setCurrentIndex({});
connect(m_ui.addSearchDirectoryButton, &QToolButton::pressed, this,
&GameListSettingsWidget::onAddSearchDirectoryButtonPressed);
connect(m_ui.removeSearchDirectoryButton, &QToolButton::pressed, this,
&GameListSettingsWidget::onRemoveSearchDirectoryButtonPressed);
connect(m_ui.refreshGameListButton, &QToolButton::pressed, this,
&GameListSettingsWidget::onRefreshGameListButtonPressed);
connect(m_ui.browseRedumpPath, &QToolButton::pressed, this, &GameListSettingsWidget::onBrowseRedumpPathButtonPressed);
connect(m_ui.downloadRedumpDatabase, &QToolButton::pressed, this,
&GameListSettingsWidget::onDownloadRedumpDatabaseButtonPressed);
}
GameListSettingsWidget::~GameListSettingsWidget() = default;
void GameListSettingsWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QtUtils::ResizeColumnsForTableView(m_ui.searchDirectoryList, {-1, 100});
}
void GameListSettingsWidget::onAddSearchDirectoryButtonPressed()
{
QString dir = QFileDialog::getExistingDirectory(this, tr("Select Search Directory"));
if (dir.isEmpty())
return;
QMessageBox::StandardButton selection =
QMessageBox::question(this, tr("Scan Recursively?"),
tr("Would you like to scan the directory \"%1\" recursively?\n\nScanning recursively takes "
"more time, but will identify files in subdirectories.")
.arg(dir),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (selection == QMessageBox::Cancel)
return;
const bool recursive = (selection == QMessageBox::Yes);
m_search_directories_model->addEntry(dir, recursive);
m_search_directories_model->saveToSettings();
m_host_interface->refreshGameList(false);
}
void GameListSettingsWidget::onRemoveSearchDirectoryButtonPressed()
{
QModelIndexList selection = m_ui.searchDirectoryList->selectionModel()->selectedIndexes();
if (selection.size() < 1)
return;
const int row = selection[0].row();
m_search_directories_model->removeEntry(row);
m_search_directories_model->saveToSettings();
m_host_interface->refreshGameList(false);
}
void GameListSettingsWidget::onRefreshGameListButtonPressed()
{
m_host_interface->refreshGameList(true);
}
void GameListSettingsWidget::onBrowseRedumpPathButtonPressed()
{
QString filename = QFileDialog::getOpenFileName(this, tr("Select Redump Database File"), QString(),
tr("Redump Database Files (*.dat)"));
if (filename.isEmpty())
return;
m_ui.redumpDatabasePath->setText(filename);
m_host_interface->getQSettings().setValue("GameList/RedumpDatabasePath", filename);
m_host_interface->updateGameListDatabase(true);
}
void GameListSettingsWidget::onDownloadRedumpDatabaseButtonPressed()
{
QMessageBox::information(this, tr("TODO"), tr("TODO"));
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <QtWidgets/QWidget>
#include "ui_gamelistsettingswidget.h"
class QtHostInterface;
class GameListSearchDirectoriesModel;
class GameListSettingsWidget : public QWidget
{
Q_OBJECT
public:
GameListSettingsWidget(QtHostInterface* host_interface, QWidget* parent = nullptr);
~GameListSettingsWidget();
private Q_SLOTS:
void onAddSearchDirectoryButtonPressed();
void onRemoveSearchDirectoryButtonPressed();
void onRefreshGameListButtonPressed();
void onBrowseRedumpPathButtonPressed();
void onDownloadRedumpDatabaseButtonPressed();
protected:
void resizeEvent(QResizeEvent* event);
private:
QtHostInterface* m_host_interface;
Ui::GameListSettingsWidget m_ui;
GameListSearchDirectoriesModel* m_search_directories_model = nullptr;
};

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GameListSettingsWidget</class>
<widget class="QWidget" name="GameListSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>527</width>
<height>376</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Search Directories</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="searchDirectoryList"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="addSearchDirectoryButton">
<property name="text">
<string>Add</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/list-add.png</normaloff>:/icons/list-add.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="removeSearchDirectoryButton">
<property name="text">
<string>Remove</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/list-remove.png</normaloff>:/icons/list-remove.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="refreshGameListButton">
<property name="text">
<string>Refresh</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/view-refresh.png</normaloff>:/icons/view-refresh.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Redump Database Path</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="redumpDatabasePath"/>
</item>
<item>
<widget class="QToolButton" name="browseRedumpPath">
<property name="text">
<string>Browse...</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/system-search.png</normaloff>:/icons/system-search.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="downloadRedumpDatabase">
<property name="text">
<string>Download...</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/applications-internet.png</normaloff>:/icons/applications-internet.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,164 @@
#include "gamelistwidget.h"
#include "core/settings.h"
#include "qthostinterface.h"
#include "qtutils.h"
#include <QtWidgets/QHeaderView>
class GameListModel : public QAbstractTableModel
{
public:
enum Column : int
{
// Column_Icon,
Column_Code,
Column_Title,
Column_Region,
Column_Size,
Column_Count
};
GameListModel(GameList* game_list, QObject* parent = nullptr)
: QAbstractTableModel(parent), m_game_list(game_list), m_size(static_cast<int>(m_game_list->GetEntryCount()))
{
}
~GameListModel() = default;
int rowCount(const QModelIndex& parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return static_cast<int>(m_game_list->GetEntryCount());
}
int columnCount(const QModelIndex& parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return Column_Count;
}
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return {};
if (role != Qt::DisplayRole)
return {};
const int row = index.row();
if (row < 0 || row >= static_cast<int>(m_game_list->GetEntryCount()))
return {};
const GameList::GameListEntry& ge = m_game_list->GetEntries()[row];
switch (index.column())
{
case Column_Code:
return QString::fromStdString(ge.code);
case Column_Title:
return QString::fromStdString(ge.title);
case Column_Region:
return QString(Settings::GetConsoleRegionName(ge.region));
case Column_Size:
return QString("%1 MB").arg(static_cast<double>(ge.total_size) / 1048576.0, 0, 'f', 2);
default:
return {};
}
}
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
return {};
switch (section)
{
case Column_Code:
return "Code";
case Column_Title:
return "Title";
case Column_Region:
return "Region";
case Column_Size:
return "Size";
default:
return {};
}
}
void refresh()
{
if (m_size > 0)
{
beginRemoveRows(QModelIndex(), 0, m_size - 1);
endRemoveRows();
}
m_size = static_cast<int>(m_game_list->GetEntryCount());
beginInsertRows(QModelIndex(), 0, m_size);
endInsertRows();
}
private:
GameList* m_game_list;
int m_size;
};
GameListWidget::GameListWidget(QWidget* parent /* = nullptr */) : QStackedWidget(parent) {}
GameListWidget::~GameListWidget() = default;
void GameListWidget::initialize(QtHostInterface* host_interface)
{
m_host_interface = host_interface;
m_game_list = host_interface->getGameList();
connect(m_host_interface, &QtHostInterface::gameListRefreshed, this, &GameListWidget::onGameListRefreshed);
m_table_model = new GameListModel(m_game_list, this);
m_table_view = new QTableView(this);
m_table_view->setModel(m_table_model);
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table_view->setAlternatingRowColors(true);
m_table_view->setShowGrid(false);
m_table_view->setCurrentIndex({});
m_table_view->verticalHeader()->hide();
m_table_view->resizeColumnsToContents();
connect(m_table_view, &QTableView::doubleClicked, this, &GameListWidget::onTableViewItemDoubleClicked);
insertWidget(0, m_table_view);
setCurrentIndex(0);
}
void GameListWidget::onGameListRefreshed()
{
m_table_model->refresh();
}
void GameListWidget::onTableViewItemDoubleClicked(const QModelIndex& index)
{
if (!index.isValid() || index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
return;
const GameList::GameListEntry& entry = m_game_list->GetEntries().at(index.row());
emit bootEntryRequested(entry);
}
void GameListWidget::resizeEvent(QResizeEvent* event)
{
QStackedWidget::resizeEvent(event);
QtUtils::ResizeColumnsForTableView(m_table_view, {100, -1, 100, 100});
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "core/game_list.h"
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QTableView>
class GameListModel;
class QtHostInterface;
class GameListWidget : public QStackedWidget
{
Q_OBJECT
public:
GameListWidget(QWidget* parent = nullptr);
~GameListWidget();
void initialize(QtHostInterface* host_interface);
Q_SIGNALS:
void bootEntryRequested(const GameList::GameListEntry& entry);
private Q_SLOTS:
void onGameListRefreshed();
void onTableViewItemDoubleClicked(const QModelIndex& index);
protected:
void resizeEvent(QResizeEvent* event);
private:
QtHostInterface* m_host_interface = nullptr;
GameList* m_game_list = nullptr;
GameListModel* m_table_model = nullptr;
QTableView* m_table_view = nullptr;
};

View File

@ -0,0 +1,28 @@
#include "YBaseLib/Log.h"
#include "mainwindow.h"
#include "qthostinterface.h"
#include <QtWidgets/QApplication>
#include <memory>
static void InitLogging()
{
// set log flags
// g_pLog->SetConsoleOutputParams(true);
g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_PROFILE);
g_pLog->SetFilterLevel(LOGLEVEL_PROFILE);
// g_pLog->SetDebugOutputParams(true);
}
int main(int argc, char* argv[])
{
InitLogging();
QApplication app(argc, argv);
std::unique_ptr<QtHostInterface> host_interface = std::make_unique<QtHostInterface>();
std::unique_ptr<MainWindow> window = std::make_unique<MainWindow>(host_interface.get());
window->show();
return app.exec();
}

View File

@ -0,0 +1,207 @@
#include "mainwindow.h"
#include "core/game_list.h"
#include "core/settings.h"
#include "gamelistwidget.h"
#include "qthostinterface.h"
#include "qtsettingsinterface.h"
#include "settingsdialog.h"
#include <QtWidgets/QFileDialog>
static constexpr char DISC_IMAGE_FILTER[] =
"All File Types (*.bin *.img *.cue *.exe *.psexe);;Single-Track Raw Images (*.bin *.img);;Cue Sheets "
"(*.cue);;PlayStation Executables (*.exe *.psexe)";
MainWindow::MainWindow(QtHostInterface* host_interface) : QMainWindow(nullptr), m_host_interface(host_interface)
{
m_ui.setupUi(this);
setupAdditionalUi();
connectSignals();
// force a scan of the game list
m_host_interface->refreshGameList();
resize(750, 690);
}
MainWindow::~MainWindow()
{
m_host_interface->destroyDisplayWidget();
}
void MainWindow::onEmulationStarting()
{
switchToEmulationView();
updateEmulationActions(true, false);
// we need the surface visible..
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
void MainWindow::onEmulationStarted()
{
updateEmulationActions(false, true);
m_emulation_running = true;
}
void MainWindow::onEmulationStopped()
{
updateEmulationActions(false, false);
switchToGameListView();
m_emulation_running = false;
}
void MainWindow::onEmulationPaused(bool paused)
{
m_ui.actionPause->setChecked(paused);
}
void MainWindow::onStartDiscActionTriggered()
{
QString filename =
QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr);
if (filename.isEmpty())
return;
m_host_interface->bootSystem(std::move(filename), QString());
}
void MainWindow::onChangeDiscActionTriggered()
{
QMenu menu(tr("Change Disc..."), this);
QAction* from_file = menu.addAction(tr("From File..."));
QAction* from_game_list = menu.addAction(tr("From Game List"));
QAction* selected = menu.exec(QCursor::pos());
if (selected == from_file)
{
QString filename =
QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr);
if (filename.isEmpty())
return;
m_host_interface->changeDisc(filename);
}
else if (selected == from_game_list)
{
m_host_interface->pauseSystem(true);
switchToGameListView();
}
}
void MainWindow::onStartBiosActionTriggered()
{
m_host_interface->bootSystem(QString(), QString());
}
void MainWindow::onOpenDirectoryActionTriggered() {}
void MainWindow::onExitActionTriggered() {}
void MainWindow::onFullscreenActionToggled(bool fullscreen) {}
void MainWindow::onGitHubRepositoryActionTriggered() {}
void MainWindow::onIssueTrackerActionTriggered() {}
void MainWindow::onAboutActionTriggered() {}
void MainWindow::setupAdditionalUi()
{
m_game_list_widget = new GameListWidget(m_ui.mainContainer);
m_game_list_widget->initialize(m_host_interface);
m_ui.mainContainer->insertWidget(0, m_game_list_widget);
QWidget* display_widget = m_host_interface->createDisplayWidget(m_ui.mainContainer);
m_ui.mainContainer->insertWidget(1, display_widget);
m_ui.mainContainer->setCurrentIndex(0);
}
void MainWindow::updateEmulationActions(bool starting, bool running)
{
m_ui.actionStartDisc->setDisabled(starting || running);
m_ui.actionStartBios->setDisabled(starting || running);
m_ui.actionOpenDirectory->setDisabled(starting || running);
m_ui.actionPowerOff->setDisabled(starting || running);
m_ui.actionPowerOff->setDisabled(starting || !running);
m_ui.actionReset->setDisabled(starting || !running);
m_ui.actionPause->setDisabled(starting || !running);
m_ui.actionChangeDisc->setDisabled(starting || !running);
m_ui.actionLoadState->setDisabled(starting);
m_ui.actionSaveState->setDisabled(starting);
m_ui.actionFullscreen->setDisabled(starting || !running);
}
void MainWindow::switchToGameListView()
{
m_ui.mainContainer->setCurrentIndex(0);
}
void MainWindow::switchToEmulationView()
{
m_ui.mainContainer->setCurrentIndex(1);
}
void MainWindow::connectSignals()
{
updateEmulationActions(false, false);
onEmulationPaused(false);
connect(m_ui.actionStartDisc, &QAction::triggered, this, &MainWindow::onStartDiscActionTriggered);
connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBiosActionTriggered);
connect(m_ui.actionChangeDisc, &QAction::triggered, this, &MainWindow::onChangeDiscActionTriggered);
connect(m_ui.actionOpenDirectory, &QAction::triggered, this, &MainWindow::onOpenDirectoryActionTriggered);
connect(m_ui.actionPowerOff, &QAction::triggered, m_host_interface, &QtHostInterface::powerOffSystem);
connect(m_ui.actionReset, &QAction::triggered, m_host_interface, &QtHostInterface::resetSystem);
connect(m_ui.actionPause, &QAction::toggled, m_host_interface, &QtHostInterface::pauseSystem);
connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::onExitActionTriggered);
connect(m_ui.actionFullscreen, &QAction::toggled, this, &MainWindow::onFullscreenActionToggled);
connect(m_ui.actionSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::Count); });
connect(m_ui.actionGameListSettings, &QAction::triggered,
[this]() { doSettings(SettingsDialog::Category::GameListSettings); });
connect(m_ui.actionCPUSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::CPUSettings); });
connect(m_ui.actionGPUSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::GPUSettings); });
connect(m_ui.actionAudioSettings, &QAction::triggered,
[this]() { doSettings(SettingsDialog::Category::AudioSettings); });
connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered);
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
connect(m_host_interface, &QtHostInterface::emulationStarting, this, &MainWindow::onEmulationStarting);
connect(m_host_interface, &QtHostInterface::emulationStarted, this, &MainWindow::onEmulationStarted);
connect(m_host_interface, &QtHostInterface::emulationStopped, this, &MainWindow::onEmulationStopped);
connect(m_host_interface, &QtHostInterface::emulationPaused, this, &MainWindow::onEmulationPaused);
connect(m_game_list_widget, &GameListWidget::bootEntryRequested, [this](const GameList::GameListEntry& entry) {
// if we're not running, boot the system, otherwise swap discs
QString path = QString::fromStdString(entry.path);
if (!m_emulation_running)
{
m_host_interface->bootSystem(path, QString());
}
else
{
m_host_interface->changeDisc(path);
m_host_interface->pauseSystem(false);
switchToEmulationView();
}
});
}
void MainWindow::doSettings(SettingsDialog::Category category)
{
if (!m_settings_dialog)
m_settings_dialog = new SettingsDialog(m_host_interface, this);
if (!m_settings_dialog->isVisible())
{
m_settings_dialog->setModal(false);
m_settings_dialog->show();
}
if (category != SettingsDialog::Category::Count)
m_settings_dialog->setCategory(category);
}

View File

@ -0,0 +1,57 @@
#pragma once
#include <QtCore/QThread>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <memory>
#include "settingsdialog.h"
#include "ui_mainwindow.h"
class GameList;
class GameListWidget;
class QtHostInterface;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QtHostInterface* host_interface);
~MainWindow();
public Q_SLOTS:
void onEmulationStarting();
void onEmulationStarted();
void onEmulationStopped();
void onEmulationPaused(bool paused);
void onStartDiscActionTriggered();
void onChangeDiscActionTriggered();
void onStartBiosActionTriggered();
void onOpenDirectoryActionTriggered();
void onExitActionTriggered();
void onFullscreenActionToggled(bool fullscreen);
void onGitHubRepositoryActionTriggered();
void onIssueTrackerActionTriggered();
void onAboutActionTriggered();
private:
void createGameList();
void setupAdditionalUi();
void connectSignals();
void updateEmulationActions(bool starting, bool running);
void switchToGameListView();
void switchToEmulationView();
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
Ui::MainWindow m_ui;
QtHostInterface* m_host_interface = nullptr;
std::unique_ptr<GameList> m_game_list;
GameListWidget* m_game_list_widget = nullptr;
SettingsDialog* m_settings_dialog = nullptr;
bool m_emulation_running = false;
};

View File

@ -0,0 +1,368 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>754</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>DuckStation</string>
</property>
<property name="windowIcon">
<iconset resource="icons.qrc">
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
</property>
<widget class="QStackedWidget" name="mainContainer">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_2"/>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>754</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuSystem">
<property name="title">
<string>System</string>
</property>
<addaction name="actionStartDisc"/>
<addaction name="actionStartBios"/>
<addaction name="separator"/>
<addaction name="actionPowerOff"/>
<addaction name="actionReset"/>
<addaction name="actionPause"/>
<addaction name="actionChangeDisc"/>
<addaction name="separator"/>
<addaction name="actionLoadState"/>
<addaction name="actionSaveState"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuSettings">
<property name="title">
<string>S&amp;ettings</string>
</property>
<widget class="QMenu" name="menuRenderer">
<property name="title">
<string>Renderer</string>
</property>
<addaction name="actionRendererD3D11"/>
<addaction name="actionRendererOpenGL"/>
<addaction name="actionRendererSoftware"/>
</widget>
<widget class="QMenu" name="actionCPUExecutionMode">
<property name="title">
<string>CPU Execution Mode</string>
</property>
<addaction name="actionCPUExecutionModeInterpreter"/>
<addaction name="actionCPUExecutionModeCachedInterpreter"/>
<addaction name="actionCPUExecutionModeRecompiler"/>
</widget>
<addaction name="actionFullscreen"/>
<addaction name="separator"/>
<addaction name="actionConsoleSettings"/>
<addaction name="actionGameListSettings"/>
<addaction name="actionPortSettings"/>
<addaction name="actionCPUSettings"/>
<addaction name="actionGPUSettings"/>
<addaction name="actionAudioSettings"/>
<addaction name="separator"/>
<addaction name="actionOpenDirectory"/>
<addaction name="separator"/>
<addaction name="actionCPUExecutionMode"/>
<addaction name="menuRenderer"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="actionGitHubRepository"/>
<addaction name="actionIssueTracker"/>
<addaction name="separator"/>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuSystem"/>
<addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionStartDisc"/>
<addaction name="actionStartBios"/>
<addaction name="actionOpenDirectory"/>
<addaction name="separator"/>
<addaction name="actionPowerOff"/>
<addaction name="actionReset"/>
<addaction name="actionPause"/>
<addaction name="actionChangeDisc"/>
<addaction name="separator"/>
<addaction name="actionLoadState"/>
<addaction name="actionSaveState"/>
<addaction name="separator"/>
<addaction name="actionFullscreen"/>
<addaction name="actionSettings"/>
</widget>
<action name="actionStartDisc">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/drive-optical.png</normaloff>:/icons/drive-optical.png</iconset>
</property>
<property name="text">
<string>Start &amp;Disc...</string>
</property>
</action>
<action name="actionStartBios">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/drive-removable-media.png</normaloff>:/icons/drive-removable-media.png</iconset>
</property>
<property name="text">
<string>Start &amp;BIOS</string>
</property>
</action>
<action name="actionPowerOff">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/system-shutdown.png</normaloff>:/icons/system-shutdown.png</iconset>
</property>
<property name="text">
<string>Power &amp;Off</string>
</property>
</action>
<action name="actionReset">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/view-refresh.png</normaloff>:/icons/view-refresh.png</iconset>
</property>
<property name="text">
<string>&amp;Reset</string>
</property>
</action>
<action name="actionPause">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/media-playback-pause.png</normaloff>:/icons/media-playback-pause.png</iconset>
</property>
<property name="text">
<string>&amp;Pause</string>
</property>
</action>
<action name="actionLoadState">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/document-open.png</normaloff>:/icons/document-open.png</iconset>
</property>
<property name="text">
<string>&amp;Load State</string>
</property>
</action>
<action name="actionSaveState">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/document-save.png</normaloff>:/icons/document-save.png</iconset>
</property>
<property name="text">
<string>&amp;Save State</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>E&amp;xit</string>
</property>
</action>
<action name="actionConsoleSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/utilities-system-monitor.png</normaloff>:/icons/utilities-system-monitor.png</iconset>
</property>
<property name="text">
<string>&amp;Console Settings...</string>
</property>
</action>
<action name="actionPortSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/input-gaming.png</normaloff>:/icons/input-gaming.png</iconset>
</property>
<property name="text">
<string>&amp;Port Settings...</string>
</property>
</action>
<action name="actionCPUSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/applications-other.png</normaloff>:/icons/applications-other.png</iconset>
</property>
<property name="text">
<string>&amp;CPU Settings...</string>
</property>
</action>
<action name="actionGPUSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/video-display.png</normaloff>:/icons/video-display.png</iconset>
</property>
<property name="text">
<string>&amp;GPU Settings...</string>
</property>
</action>
<action name="actionFullscreen">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/view-fullscreen.png</normaloff>:/icons/view-fullscreen.png</iconset>
</property>
<property name="text">
<string>Fullscreen</string>
</property>
</action>
<action name="actionRendererD3D11">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Hardware (D3D11)</string>
</property>
</action>
<action name="actionRendererOpenGL">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Hardware (OpenGL)</string>
</property>
</action>
<action name="actionRendererSoftware">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Software</string>
</property>
</action>
<action name="actionCPUExecutionModeInterpreter">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Interpreter (Slowest)</string>
</property>
</action>
<action name="actionCPUExecutionModeCachedInterpreter">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Cached Interpreter (Slower)</string>
</property>
</action>
<action name="actionCPUExecutionModeRecompiler">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Recompiler (Fastest)</string>
</property>
</action>
<action name="actionResolution_Scale">
<property name="text">
<string>Resolution Scale</string>
</property>
</action>
<action name="actionGitHubRepository">
<property name="text">
<string>&amp;GitHub Repository...</string>
</property>
</action>
<action name="actionIssueTracker">
<property name="text">
<string>&amp;Issue Tracker...</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>&amp;About...</string>
</property>
</action>
<action name="actionChangeDisc">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/media-optical.png</normaloff>:/icons/media-optical.png</iconset>
</property>
<property name="text">
<string>Change Disc...</string>
</property>
</action>
<action name="actionAudioSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/audio-card.png</normaloff>:/icons/audio-card.png</iconset>
</property>
<property name="text">
<string>Audio Settings...</string>
</property>
</action>
<action name="actionGameListSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/folder-open.png</normaloff>:/icons/folder-open.png</iconset>
</property>
<property name="text">
<string>Game List Settings...</string>
</property>
</action>
<action name="actionOpenDirectory">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/edit-find.png</normaloff>:/icons/edit-find.png</iconset>
</property>
<property name="text">
<string>Open Directory...</string>
</property>
</action>
<action name="actionSettings">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/applications-system.png</normaloff>:/icons/applications-system.png</iconset>
</property>
<property name="text">
<string>&amp;Settings...</string>
</property>
</action>
</widget>
<resources>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,430 @@
#include "opengldisplaywindow.h"
#include "YBaseLib/Assert.h"
#include "YBaseLib/Log.h"
#include <array>
#include <tuple>
Log_SetChannel(OpenGLDisplayWindow);
static thread_local QOpenGLContext* s_thread_gl_context;
static void* GetProcAddressCallback(const char* name)
{
QOpenGLContext* ctx = s_thread_gl_context;
if (!ctx)
return nullptr;
return (void*)ctx->getProcAddress(name);
}
class OpenGLHostDisplayTexture : public HostDisplayTexture
{
public:
OpenGLHostDisplayTexture(GLuint id, u32 width, u32 height) : m_id(id), m_width(width), m_height(height) {}
~OpenGLHostDisplayTexture() override { glDeleteTextures(1, &m_id); }
void* GetHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(m_id)); }
u32 GetWidth() const override { return m_width; }
u32 GetHeight() const override { return m_height; }
GLuint GetGLID() const { return m_id; }
static std::unique_ptr<OpenGLHostDisplayTexture> Create(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride)
{
GLuint id;
glGenTextures(1, &id);
GLint old_texture_binding = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding);
// TODO: Set pack width
Assert(!initial_data || initial_data_stride == (width * sizeof(u32)));
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, initial_data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, id);
return std::make_unique<OpenGLHostDisplayTexture>(id, width, height);
}
private:
GLuint m_id;
u32 m_width;
u32 m_height;
};
OpenGLDisplayWindow::OpenGLDisplayWindow(QWindow* parent) : QWindow(parent)
{
setSurfaceType(QWindow::OpenGLSurface);
}
OpenGLDisplayWindow::~OpenGLDisplayWindow() = default;
HostDisplay::RenderAPI OpenGLDisplayWindow::GetRenderAPI() const
{
return HostDisplay::RenderAPI::OpenGL;
}
void* OpenGLDisplayWindow::GetRenderDevice() const
{
return nullptr;
}
void* OpenGLDisplayWindow::GetRenderContext() const
{
return m_gl_context;
}
void* OpenGLDisplayWindow::GetRenderWindow() const
{
return const_cast<QWindow*>(static_cast<const QWindow*>(this));
}
void OpenGLDisplayWindow::ChangeRenderWindow(void* new_window)
{
Panic("Not implemented");
}
std::unique_ptr<HostDisplayTexture> OpenGLDisplayWindow::CreateTexture(u32 width, u32 height, const void* data,
u32 data_stride, bool dynamic)
{
return OpenGLHostDisplayTexture::Create(width, height, data, data_stride);
}
void OpenGLDisplayWindow::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height,
const void* data, u32 data_stride)
{
OpenGLHostDisplayTexture* tex = static_cast<OpenGLHostDisplayTexture*>(texture);
Assert(data_stride == (width * sizeof(u32)));
GLint old_texture_binding = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding);
glBindTexture(GL_TEXTURE_2D, tex->GetGLID());
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, old_texture_binding);
}
void OpenGLDisplayWindow::SetDisplayTexture(void* texture, s32 offset_x, s32 offset_y, s32 width, s32 height,
u32 texture_width, u32 texture_height, float aspect_ratio)
{
m_display_texture_id = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture));
m_display_offset_x = offset_x;
m_display_offset_y = offset_y;
m_display_width = width;
m_display_height = height;
m_display_texture_width = texture_width;
m_display_texture_height = texture_height;
m_display_aspect_ratio = aspect_ratio;
m_display_texture_changed = true;
}
void OpenGLDisplayWindow::SetDisplayLinearFiltering(bool enabled)
{
m_display_linear_filtering = enabled;
}
void OpenGLDisplayWindow::SetDisplayTopMargin(int height)
{
m_display_top_margin = height;
}
void OpenGLDisplayWindow::SetVSync(bool enabled)
{
// Window framebuffer has to be bound to call SetSwapInterval.
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// SDL_GL_SetSwapInterval(enabled ? 1 : 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
}
std::tuple<u32, u32> OpenGLDisplayWindow::GetWindowSize() const
{
const QSize s = size();
return std::make_tuple(static_cast<u32>(s.width()), static_cast<u32>(s.height()));
}
void OpenGLDisplayWindow::WindowResized() {}
const char* OpenGLDisplayWindow::GetGLSLVersionString() const
{
return m_is_gles ? "#version 300 es" : "#version 130\n";
}
std::string OpenGLDisplayWindow::GetGLSLVersionHeader() const
{
std::string header = GetGLSLVersionString();
header += "\n\n";
if (m_is_gles)
{
header += "precision highp float;\n";
header += "precision highp int;\n\n";
}
return header;
}
static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar* message, const void* userParam)
{
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH_KHR:
Log_ErrorPrintf(message);
break;
case GL_DEBUG_SEVERITY_MEDIUM_KHR:
Log_WarningPrint(message);
break;
case GL_DEBUG_SEVERITY_LOW_KHR:
Log_InfoPrintf(message);
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
// Log_DebugPrint(message);
break;
}
}
bool OpenGLDisplayWindow::createGLContext(QThread* worker_thread)
{
m_gl_context = new QOpenGLContext();
// Prefer a desktop OpenGL context where possible. If we can't get this, try OpenGL ES.
static constexpr std::array<std::tuple<int, int>, 11> desktop_versions_to_try = {
{{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}, {3, 1}, {3, 0}}};
static constexpr std::array<std::tuple<int, int>, 4> es_versions_to_try = {{{3, 2}, {3, 1}, {3, 0}}};
QSurfaceFormat surface_format = requestedFormat();
surface_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
surface_format.setSwapInterval(0);
surface_format.setRenderableType(QSurfaceFormat::OpenGL);
surface_format.setProfile(QSurfaceFormat::CoreProfile);
#ifdef _DEBUG
surface_format.setOption(QSurfaceFormat::DebugContext);
#endif
for (const auto [major, minor] : desktop_versions_to_try)
{
surface_format.setVersion(major, minor);
m_gl_context->setFormat(surface_format);
if (m_gl_context->create())
{
Log_InfoPrintf("Got a desktop OpenGL %d.%d context", major, minor);
break;
}
}
if (!m_gl_context)
{
// try es
surface_format.setRenderableType(QSurfaceFormat::OpenGLES);
surface_format.setProfile(QSurfaceFormat::NoProfile);
#ifdef _DEBUG
surface_format.setOption(QSurfaceFormat::DebugContext, false);
#endif
for (const auto [major, minor] : es_versions_to_try)
{
surface_format.setVersion(major, minor);
m_gl_context->setFormat(surface_format);
if (m_gl_context->create())
{
Log_InfoPrintf("Got a OpenGL ES %d.%d context", major, minor);
m_is_gles = true;
break;
}
}
}
if (!m_gl_context->isValid())
{
Log_ErrorPrintf("Failed to create any GL context");
delete m_gl_context;
m_gl_context = nullptr;
return false;
}
if (!m_gl_context->makeCurrent(this))
{
Log_ErrorPrintf("Failed to make GL context current on UI thread");
delete m_gl_context;
m_gl_context = nullptr;
return false;
}
m_gl_context->doneCurrent();
m_gl_context->moveToThread(worker_thread);
return true;
}
bool OpenGLDisplayWindow::initializeGLContext()
{
if (!m_gl_context->makeCurrent(this))
return false;
s_thread_gl_context = m_gl_context;
// Load GLAD.
const auto load_result =
m_is_gles ? gladLoadGLES2Loader(GetProcAddressCallback) : gladLoadGLLoader(GetProcAddressCallback);
if (!load_result)
{
Log_ErrorPrintf("Failed to load GL functions");
return false;
}
#if 1
if (GLAD_GL_KHR_debug)
{
glad_glDebugMessageCallbackKHR(GLDebugCallback, nullptr);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
#endif
if (!CreateImGuiContext() || !CreateGLResources())
{
s_thread_gl_context = nullptr;
m_gl_context->doneCurrent();
return false;
}
return true;
}
void OpenGLDisplayWindow::destroyGLContext()
{
Assert(m_gl_context && s_thread_gl_context == m_gl_context);
s_thread_gl_context = nullptr;
if (m_display_vao != 0)
glDeleteVertexArrays(1, &m_display_vao);
if (m_display_linear_sampler != 0)
glDeleteSamplers(1, &m_display_linear_sampler);
if (m_display_nearest_sampler != 0)
glDeleteSamplers(1, &m_display_nearest_sampler);
m_display_program.Destroy();
m_gl_context->doneCurrent();
delete m_gl_context;
m_gl_context = nullptr;
}
bool OpenGLDisplayWindow::CreateImGuiContext()
{
return true;
}
bool OpenGLDisplayWindow::CreateGLResources()
{
static constexpr char fullscreen_quad_vertex_shader[] = R"(
uniform vec4 u_src_rect;
out vec2 v_tex0;
void main()
{
vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
v_tex0 = u_src_rect.xy + pos * u_src_rect.zw;
gl_Position = vec4(pos * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f);
}
)";
static constexpr char display_fragment_shader[] = R"(
uniform sampler2D samp0;
in vec2 v_tex0;
out vec4 o_col0;
void main()
{
o_col0 = texture(samp0, v_tex0);
}
)";
if (!m_display_program.Compile(GetGLSLVersionHeader() + fullscreen_quad_vertex_shader,
GetGLSLVersionHeader() + display_fragment_shader))
{
Log_ErrorPrintf("Failed to compile display shaders");
return false;
}
if (!m_is_gles)
m_display_program.BindFragData(0, "o_col0");
if (!m_display_program.Link())
{
Log_ErrorPrintf("Failed to link display program");
return false;
}
m_display_program.Bind();
m_display_program.RegisterUniform("u_src_rect");
m_display_program.RegisterUniform("samp0");
m_display_program.Uniform1i(1, 0);
glGenVertexArrays(1, &m_display_vao);
// samplers
glGenSamplers(1, &m_display_nearest_sampler);
glSamplerParameteri(m_display_nearest_sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(m_display_nearest_sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glGenSamplers(1, &m_display_linear_sampler);
glSamplerParameteri(m_display_linear_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(m_display_linear_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return true;
}
void OpenGLDisplayWindow::Render()
{
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
RenderDisplay();
// ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
m_gl_context->makeCurrent(this);
m_gl_context->swapBuffers(this);
// ImGui_ImplSDL2_NewFrame(m_window);
// ImGui_ImplOpenGL3_NewFrame();
GL::Program::ResetLastProgram();
}
void OpenGLDisplayWindow::RenderDisplay()
{
if (!m_display_texture_id)
return;
// - 20 for main menu padding
const QSize window_size = size();
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(
window_size.width(), std::max(window_size.height() - m_display_top_margin, 1), m_display_aspect_ratio);
glViewport(vp_left, window_size.height() - (m_display_top_margin + vp_top) - vp_height, vp_width, vp_height);
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_SCISSOR_TEST);
glDepthMask(GL_FALSE);
m_display_program.Bind();
m_display_program.Uniform4f(0, static_cast<float>(m_display_offset_x) / static_cast<float>(m_display_texture_width),
static_cast<float>(m_display_offset_y) / static_cast<float>(m_display_texture_height),
static_cast<float>(m_display_width) / static_cast<float>(m_display_texture_width),
static_cast<float>(m_display_height) / static_cast<float>(m_display_texture_height));
glBindTexture(GL_TEXTURE_2D, m_display_texture_id);
glBindSampler(0, m_display_linear_filtering ? m_display_linear_sampler : m_display_nearest_sampler);
glBindVertexArray(m_display_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindSampler(0, 0);
}

View File

@ -0,0 +1,75 @@
#pragma once
#include <glad.h>
#include <QtGui/QWindow>
#include <QtGui/QOpenGLContext>
#include "common/gl/program.h"
#include "common/gl/texture.h"
#include "core/host_display.h"
#include <string>
#include <memory>
class OpenGLDisplayWindow final : public QWindow, public HostDisplay
{
Q_OBJECT
public:
explicit OpenGLDisplayWindow(QWindow* parent);
~OpenGLDisplayWindow();
bool createGLContext(QThread* worker_thread);
bool initializeGLContext();
void destroyGLContext();
RenderAPI GetRenderAPI() const override;
void* GetRenderDevice() const override;
void* GetRenderContext() const override;
void* GetRenderWindow() const override;
void ChangeRenderWindow(void* new_window) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride,
bool dynamic) override;
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data,
u32 data_stride) override;
void SetDisplayTexture(void* texture, s32 offset_x, s32 offset_y, s32 width, s32 height, u32 texture_width,
u32 texture_height, float aspect_ratio) override;
void SetDisplayLinearFiltering(bool enabled) override;
void SetDisplayTopMargin(int height) override;
void SetVSync(bool enabled) override;
std::tuple<u32, u32> GetWindowSize() const override;
void WindowResized() override;
private:
const char* GetGLSLVersionString() const;
std::string GetGLSLVersionHeader() const;
bool CreateImGuiContext();
bool CreateGLResources();
void Render();
void RenderDisplay();
QOpenGLContext* m_gl_context = nullptr;
GL::Program m_display_program;
GLuint m_display_vao = 0;
GLuint m_display_texture_id = 0;
s32 m_display_offset_x = 0;
s32 m_display_offset_y = 0;
s32 m_display_width = 0;
s32 m_display_height = 0;
u32 m_display_texture_width = 0;
u32 m_display_texture_height = 0;
int m_display_top_margin = 0;
float m_display_aspect_ratio = 1.0f;
GLuint m_display_nearest_sampler = 0;
GLuint m_display_linear_sampler = 0;
bool m_is_gles = false;
bool m_display_texture_changed = false;
bool m_display_linear_filtering = false;
};

View File

@ -0,0 +1,282 @@
#include "qthostinterface.h"
#include "YBaseLib/Log.h"
#include "common/null_audio_stream.h"
#include "core/game_list.h"
#include "core/gpu.h"
#include "core/system.h"
#include "qtsettingsinterface.h"
#include <QtCore/QCoreApplication>
#include <QtWidgets/QMessageBox>
#include <memory>
Log_SetChannel(QtHostInterface);
QtHostInterface::QtHostInterface(QObject* parent)
: QObject(parent), m_qsettings("duckstation-qt.ini", QSettings::IniFormat)
{
checkSettings();
createGameList();
createThread();
}
QtHostInterface::~QtHostInterface()
{
Assert(!m_opengl_display_window);
stopThread();
}
void QtHostInterface::ReportError(const char* message)
{
// QMessageBox::critical(nullptr, tr("DuckStation Error"), message, QMessageBox::Ok);
}
void QtHostInterface::ReportMessage(const char* message)
{
// QMessageBox::information(nullptr, tr("DuckStation Information"), message, QMessageBox::Ok);
}
void QtHostInterface::setDefaultSettings()
{
QtSettingsInterface si(m_qsettings);
m_settings.SetDefaults();
m_settings.Save(si);
m_qsettings.sync();
}
void QtHostInterface::applySettings()
{
QtSettingsInterface si(m_qsettings);
m_settings.Load(si);
}
void QtHostInterface::checkSettings()
{
const QSettings::Status settings_status = m_qsettings.status();
if (settings_status != QSettings::NoError)
m_qsettings.clear();
const QString settings_version_key = QStringLiteral("General/SettingsVersion");
const int expected_version = 1;
const QVariant settings_version_var = m_qsettings.value(settings_version_key);
bool settings_version_okay;
int settings_version = settings_version_var.toInt(&settings_version_okay);
if (!settings_version_okay)
settings_version = 0;
if (settings_version != expected_version)
{
Log_WarningPrintf("Settings version %d does not match expected version %d, resetting", settings_version,
expected_version);
m_qsettings.clear();
m_qsettings.setValue(settings_version_key, expected_version);
setDefaultSettings();
}
}
void QtHostInterface::createGameList()
{
m_game_list = std::make_unique<GameList>();
updateGameListDatabase(false);
refreshGameList(false);
}
void QtHostInterface::updateGameListDatabase(bool refresh_list /*= true*/)
{
m_game_list->ClearDatabase();
const QString redump_dat_path = m_qsettings.value("GameList/RedumpDatabasePath").toString();
if (!redump_dat_path.isEmpty())
m_game_list->ParseRedumpDatabase(redump_dat_path.toStdString().c_str());
if (refresh_list)
refreshGameList(true);
}
void QtHostInterface::refreshGameList(bool invalidate_cache /*= false*/)
{
QtSettingsInterface si(m_qsettings);
m_game_list->SetDirectoriesFromSettings(si);
m_game_list->RescanAllDirectories();
emit gameListRefreshed();
}
QWidget* QtHostInterface::createDisplayWidget(QWidget* parent)
{
m_opengl_display_window = new OpenGLDisplayWindow(nullptr);
m_display.release();
m_display = std::unique_ptr<HostDisplay>(static_cast<HostDisplay*>(m_opengl_display_window));
return QWidget::createWindowContainer(m_opengl_display_window, parent);
}
void QtHostInterface::destroyDisplayWidget()
{
m_display.release();
delete m_opengl_display_window;
m_opengl_display_window = nullptr;
}
void QtHostInterface::bootSystem(QString initial_filename, QString initial_save_state_filename)
{
emit emulationStarting();
if (!m_opengl_display_window->createGLContext(m_worker_thread))
{
emit emulationStopped();
return;
}
QMetaObject::invokeMethod(this, "doBootSystem", Qt::QueuedConnection, Q_ARG(QString, initial_filename),
Q_ARG(QString, initial_save_state_filename));
}
void QtHostInterface::powerOffSystem()
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "doPowerOffSystem", Qt::QueuedConnection);
return;
}
if (!m_system)
{
Log_ErrorPrintf("powerOffSystem() called without system");
return;
}
m_system.reset();
m_opengl_display_window->destroyGLContext();
emit emulationStopped();
}
void QtHostInterface::resetSystem()
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "resetSystem", Qt::QueuedConnection);
return;
}
if (!m_system)
{
Log_ErrorPrintf("resetSystem() called without system");
return;
}
HostInterface::ResetSystem();
}
void QtHostInterface::pauseSystem(bool paused)
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "pauseSystem", Qt::QueuedConnection, Q_ARG(bool, paused));
return;
}
m_paused = paused;
emit emulationPaused(paused);
}
void QtHostInterface::changeDisc(QString new_disc_filename) {}
void QtHostInterface::doBootSystem(QString initial_filename, QString initial_save_state_filename)
{
if (!m_opengl_display_window->initializeGLContext())
{
emit emulationStopped();
return;
}
m_audio_stream = NullAudioStream::Create();
m_audio_stream->Reconfigure();
std::string initial_filename_str = initial_filename.toStdString();
std::string initial_save_state_filename_str = initial_save_state_filename.toStdString();
if (!CreateSystem() ||
!BootSystem(initial_filename_str.empty() ? nullptr : initial_filename_str.c_str(),
initial_save_state_filename_str.empty() ? nullptr : initial_save_state_filename_str.c_str()))
{
m_opengl_display_window->destroyGLContext();
emit emulationStopped();
return;
}
emit emulationStarted();
}
void QtHostInterface::createThread()
{
m_original_thread = QThread::currentThread();
m_worker_thread = new Thread(this);
m_worker_thread->start();
moveToThread(m_worker_thread);
}
void QtHostInterface::stopThread()
{
Assert(!isOnWorkerThread());
QMetaObject::invokeMethod(this, "doStopThread", Qt::QueuedConnection);
m_worker_thread->wait();
}
void QtHostInterface::doStopThread()
{
m_shutdown_flag.store(true);
}
void QtHostInterface::threadEntryPoint()
{
while (!m_shutdown_flag.load())
{
if (!m_system)
{
// wait until we have a system before running
QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
continue;
}
// execute the system, polling events inbetween frames
// simulate the system if not paused
if (m_system && !m_paused)
m_system->RunFrame();
// rendering
{
// DrawImGui();
if (m_system)
m_system->GetGPU()->ResetGraphicsAPIState();
// ImGui::Render();
m_display->Render();
// ImGui::NewFrame();
if (m_system)
{
m_system->GetGPU()->RestoreGraphicsAPIState();
if (m_speed_limiter_enabled)
Throttle();
}
UpdatePerformanceCounters();
}
QCoreApplication::processEvents(QEventLoop::AllEvents, m_paused ? 16 : 0);
}
m_system.reset();
// move back to UI thread
moveToThread(m_original_thread);
}
QtHostInterface::Thread::Thread(QtHostInterface* parent) : QThread(parent), m_parent(parent) {}
QtHostInterface::Thread::~Thread() = default;
void QtHostInterface::Thread::run()
{
m_parent->threadEntryPoint();
}

View File

@ -0,0 +1,89 @@
#pragma once
#include <atomic>
#include <memory>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QSettings>
#include "core/host_interface.h"
#include "opengldisplaywindow.h"
class QWidget;
class GameList;
class QtHostInterface : public QObject, private HostInterface
{
Q_OBJECT
public:
explicit QtHostInterface(QObject* parent = nullptr);
~QtHostInterface();
void ReportError(const char* message) override;
void ReportMessage(const char* message) override;
const QSettings& getQSettings() const { return m_qsettings; }
QSettings& getQSettings() { return m_qsettings; }
void setDefaultSettings();
void applySettings();
const GameList* getGameList() const { return m_game_list.get(); }
GameList* getGameList() { return m_game_list.get(); }
void updateGameListDatabase(bool refresh_list = true);
void refreshGameList(bool invalidate_cache = false);
bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
QWidget* createDisplayWidget(QWidget* parent);
void destroyDisplayWidget();
void bootSystem(QString initial_filename, QString initial_save_state_filename);
Q_SIGNALS:
void emulationStarting();
void emulationStarted();
void emulationStopped();
void emulationPaused(bool paused);
void gameListRefreshed();
public Q_SLOTS:
void powerOffSystem();
void resetSystem();
void pauseSystem(bool paused);
void changeDisc(QString new_disc_filename);
private Q_SLOTS:
void doBootSystem(QString initial_filename, QString initial_save_state_filename);
void doStopThread();
private:
class Thread : public QThread
{
public:
Thread(QtHostInterface* parent);
~Thread();
protected:
void run() override;
private:
QtHostInterface* m_parent;
};
void checkSettings();
void createGameList();
void createThread();
void stopThread();
void threadEntryPoint();
QSettings m_qsettings;
std::unique_ptr<GameList> m_game_list;
OpenGLDisplayWindow* m_opengl_display_window = nullptr;
QThread* m_original_thread = nullptr;
Thread* m_worker_thread = nullptr;
std::atomic_bool m_shutdown_flag{ false };
};

View File

@ -0,0 +1,142 @@
#include "qtsettingsinterface.h"
#include <QtCore/QSettings>
#include <algorithm>
static QString GetFullKey(const char* section, const char* key)
{
return QStringLiteral("%1/%2").arg(section, key);
}
QtSettingsInterface::QtSettingsInterface(QSettings& settings) : m_settings(settings) {}
QtSettingsInterface::~QtSettingsInterface() = default;
int QtSettingsInterface::GetIntValue(const char* section, const char* key, int default_value /*= 0*/)
{
QVariant value = m_settings.value(GetFullKey(section, key));
if (!value.isValid())
return default_value;
bool converted_value_okay;
int converted_value = value.toInt(&converted_value_okay);
if (!converted_value_okay)
return default_value;
else
return converted_value;
}
float QtSettingsInterface::GetFloatValue(const char* section, const char* key, float default_value /*= 0.0f*/)
{
QVariant value = m_settings.value(GetFullKey(section, key));
if (!value.isValid())
return default_value;
bool converted_value_okay;
float converted_value = value.toFloat(&converted_value_okay);
if (!converted_value_okay)
return default_value;
else
return converted_value;
}
bool QtSettingsInterface::GetBoolValue(const char* section, const char* key, bool default_value /*= false*/)
{
QVariant value = m_settings.value(GetFullKey(section, key));
return value.isValid() ? value.toBool() : default_value;
}
std::string QtSettingsInterface::GetStringValue(const char* section, const char* key,
const char* default_value /*= ""*/)
{
QVariant value = m_settings.value(GetFullKey(section, key));
return value.isValid() ? value.toString().toStdString() : std::string(default_value);
}
void QtSettingsInterface::SetIntValue(const char* section, const char* key, int value)
{
m_settings.setValue(GetFullKey(section, key), QVariant(value));
}
void QtSettingsInterface::SetFloatValue(const char* section, const char* key, float value)
{
m_settings.setValue(GetFullKey(section, key), QVariant(value));
}
void QtSettingsInterface::SetBoolValue(const char* section, const char* key, bool value)
{
m_settings.setValue(GetFullKey(section, key), QVariant(value));
}
void QtSettingsInterface::SetStringValue(const char* section, const char* key, const char* value)
{
m_settings.setValue(GetFullKey(section, key), QVariant(value));
}
std::vector<std::string> QtSettingsInterface::GetStringList(const char* section, const char* key)
{
QVariant value = m_settings.value(GetFullKey(section, key));
if (value.type() == QVariant::String)
return { value.toString().toStdString() };
else if (value.type() != QVariant::StringList)
return {};
QStringList value_sl = value.toStringList();
std::vector<std::string> results;
results.reserve(static_cast<unsigned>(value_sl.size()));
std::transform(value_sl.begin(), value_sl.end(), std::back_inserter(results),
[](const QString& str) { return str.toStdString(); });
return results;
}
void QtSettingsInterface::SetStringList(const char* section, const char* key,
const std::vector<std::string_view>& items)
{
QString full_key = GetFullKey(section, key);
if (items.empty())
{
m_settings.remove(full_key);
return;
}
QStringList sl;
sl.reserve(static_cast<int>(items.size()));
std::transform(items.begin(), items.end(), std::back_inserter(sl), [](const std::string_view& sv) {
return QString::fromLocal8Bit(sv.data(), static_cast<int>(sv.size()));
});
m_settings.setValue(full_key, sl);
}
bool QtSettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item)
{
QString full_key = GetFullKey(section, key);
QVariant var = m_settings.value(full_key);
QStringList sl = var.toStringList();
if (sl.removeAll(item) == 0)
return false;
if (sl.isEmpty())
m_settings.remove(full_key);
else
m_settings.setValue(full_key, sl);
return true;
}
bool QtSettingsInterface::AddToStringList(const char* section, const char* key, const char* item)
{
QString full_key = GetFullKey(section, key);
QVariant var = m_settings.value(full_key);
QStringList sl = (var.type() == QVariant::StringList) ? var.toStringList() : QStringList();
QString qitem(item);
if (sl.contains(qitem))
return false;
sl.push_back(qitem);
m_settings.setValue(full_key, sl);
return true;
}
void QtSettingsInterface::DeleteValue(const char* section, const char* key)
{
m_settings.remove(GetFullKey(section, key));
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "core/settings.h"
class QSettings;
class QtSettingsInterface : public SettingsInterface
{
public:
QtSettingsInterface(QSettings& settings);
~QtSettingsInterface();
int GetIntValue(const char* section, const char* key, int default_value = 0) override;
float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) override;
bool GetBoolValue(const char* section, const char* key, bool default_value = false) override;
std::string GetStringValue(const char* section, const char* key, const char* default_value = "") override;
void SetIntValue(const char* section, const char* key, int value) override;
void SetFloatValue(const char* section, const char* key, float value) override;
void SetBoolValue(const char* section, const char* key, bool value) override;
void SetStringValue(const char* section, const char* key, const char* value) override;
std::vector<std::string> GetStringList(const char* section, const char* key) override;
void SetStringList(const char* section, const char* key, const std::vector<std::string_view>& items) override;
bool RemoveFromStringList(const char* section, const char* key, const char* item) override;
bool AddToStringList(const char* section, const char* key, const char* item) override;
void DeleteValue(const char* section, const char* key) override;
private:
QSettings& m_settings;
};

View File

@ -0,0 +1,23 @@
#include "qtutils.h"
#include <QtWidgets/QTableView>
#include <algorithm>
namespace QtUtils {
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths)
{
const int total_width =
std::accumulate(widths.begin(), widths.end(), 0, [](int a, int b) { return a + std::max(b, 0); });
const int flex_width = std::max(view->width() - total_width - 2, 1);
int column_index = 0;
for (const int spec_width : widths)
{
const int width = spec_width < 0 ? flex_width : spec_width;
view->setColumnWidth(column_index, width);
column_index++;
}
}
} // namespace QtUtils

View File

@ -0,0 +1,12 @@
#pragma once
#include <initializer_list>
class QTableView;
namespace QtUtils {
/// Resizes columns of the table view to at the specified widths. A width of -1 will stretch the column to use the
/// remaining space.
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths);
} // namespace QtUtils

View File

@ -0,0 +1,29 @@
<RCC>
<qresource>
<file>icons/applications-internet.png</file>
<file>icons/system-search.png</file>
<file>icons/list-add.png</file>
<file>icons/list-remove.png</file>
<file>icons/duck.png</file>
<file>icons/edit-find.png</file>
<file>icons/folder-open.png</file>
<file>icons/applications-development.png</file>
<file>icons/applications-other.png</file>
<file>icons/applications-system.png</file>
<file>icons/audio-card.png</file>
<file>icons/document-open.png</file>
<file>icons/document-save.png</file>
<file>icons/drive-optical.png</file>
<file>icons/drive-removable-media.png</file>
<file>icons/input-gaming.png</file>
<file>icons/media-flash.png</file>
<file>icons/media-optical.png</file>
<file>icons/media-playback-pause.png</file>
<file>icons/media-playback-start.png</file>
<file>icons/system-shutdown.png</file>
<file>icons/utilities-system-monitor.png</file>
<file>icons/video-display.png</file>
<file>icons/view-fullscreen.png</file>
<file>icons/view-refresh.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,42 @@
#include "settingsdialog.h"
#include "consolesettingswidget.h"
#include "gamelistsettingswidget.h"
#include "qthostinterface.h"
#include <QtWidgets/QTextEdit>
SettingsDialog::SettingsDialog(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
: QDialog(parent), m_host_interface(host_interface)
{
m_ui.setupUi(this);
m_console_settings = new ConsoleSettingsWidget(m_ui.settingsContainer);
m_game_list_settings = new GameListSettingsWidget(host_interface, m_ui.settingsContainer);
m_cpu_settings = new QWidget(m_ui.settingsContainer);
m_gpu_settings = new QWidget(m_ui.settingsContainer);
m_audio_settings = new QWidget(m_ui.settingsContainer);
m_ui.settingsContainer->insertWidget(0, m_console_settings);
m_ui.settingsContainer->insertWidget(1, m_game_list_settings);
m_ui.settingsContainer->insertWidget(2, m_cpu_settings);
m_ui.settingsContainer->insertWidget(3, m_gpu_settings);
m_ui.settingsContainer->insertWidget(4, m_audio_settings);
m_ui.settingsCategory->setCurrentRow(0);
m_ui.settingsContainer->setCurrentIndex(0);
connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &SettingsDialog::onCategoryCurrentRowChanged);
}
SettingsDialog::~SettingsDialog() = default;
void SettingsDialog::setCategory(Category category)
{
if (category >= Category::Count)
return;
m_ui.settingsCategory->setCurrentRow(static_cast<int>(category));
}
void SettingsDialog::onCategoryCurrentRowChanged(int row)
{
m_ui.settingsContainer->setCurrentIndex(row);
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <QtWidgets/QDialog>
#include "ui_settingsdialog.h"
class QtHostInterface;
class ConsoleSettingsWidget;
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
enum class Category
{
ConsoleSettings,
GameListSettings,
CPUSettings,
GPUSettings,
AudioSettings,
Count
};
explicit SettingsDialog(QtHostInterface* host_interface, QWidget* parent = nullptr);
~SettingsDialog();
public Q_SLOTS:
void setCategory(Category category);
private Q_SLOTS:
void onCategoryCurrentRowChanged(int row);
private:
Ui::SettingsDialog m_ui;
QtHostInterface* m_host_interface;
ConsoleSettingsWidget* m_console_settings = nullptr;
QWidget* m_game_list_settings = nullptr;
QWidget* m_cpu_settings = nullptr;
QWidget* m_gpu_settings = nullptr;
QWidget* m_audio_settings = nullptr;
};

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>637</width>
<height>405</height>
</rect>
</property>
<property name="windowTitle">
<string>DuckStation Settings</string>
</property>
<property name="windowIcon">
<iconset resource="icons.qrc">
<normaloff>:/icons/applications-system.png</normaloff>:/icons/applications-system.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<property name="horizontalSpacing">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QListWidget" name="settingsCategory">
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<item>
<property name="text">
<string>Console Settings</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/utilities-system-monitor.png</normaloff>:/icons/utilities-system-monitor.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Game List Settings</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/folder-open.png</normaloff>:/icons/folder-open.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>CPU Settings</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/applications-other.png</normaloff>:/icons/applications-other.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>GPU Settings</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/video-display.png</normaloff>:/icons/video-display.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Audio Settings</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/audio-card.png</normaloff>:/icons/audio-card.png</iconset>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="QStackedWidget" name="settingsContainer">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_2"/>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>