mirror of
https://github.com/RetroPie/EmulationStation.git
synced 2025-04-02 10:41:48 -04:00
Compare commits
122 commits
Author | SHA1 | Date | |
---|---|---|---|
|
88fe9771f0 | ||
|
17c96ed83d | ||
|
51b6f4162d | ||
|
803bad626e | ||
|
6b4281ac9f | ||
|
894543cea6 | ||
|
d46f04a25b | ||
|
a7a9dec637 | ||
|
81c62c97ee | ||
|
75c01f73ab | ||
|
e8fb5a0869 | ||
|
3c41f15374 | ||
|
a9ee7e48c4 | ||
|
6de6151b09 | ||
|
4a064a2130 | ||
|
485b995196 | ||
|
95ba158235 | ||
|
0aa10ae9a1 | ||
|
fc66fd78ac | ||
|
022e621156 | ||
|
4094f8af03 | ||
|
3ed0ac6e83 | ||
|
d544c73d81 | ||
|
7b45bf7144 | ||
|
bebf1e5f8e | ||
|
60a116708e | ||
|
bb6d8e9e47 | ||
|
049d378c5c | ||
|
3e23bcac75 | ||
|
b912c62580 | ||
|
07026edba7 | ||
|
a5cc5cea9c | ||
|
a4768e7022 | ||
|
aebbf19b3e | ||
|
a113b22ca7 | ||
|
6c7dc88ffe | ||
|
16cb012a91 | ||
|
6e47f19510 | ||
|
4245966a0b | ||
|
c8bcfa4420 | ||
|
97eea573eb | ||
|
771e457a1f | ||
|
fd85ed6a3f | ||
|
9b0d64fbac | ||
|
0ee1aba658 | ||
|
eb4fbab399 | ||
|
aa88f6b206 | ||
|
0a29d3b1ac | ||
|
45259de2c7 | ||
|
e782d53637 | ||
|
5d2221e207 | ||
|
19fd7d9ae9 | ||
|
4f5d50ae5d | ||
|
3cb9468891 | ||
|
822eb758cc | ||
|
6edd9669aa | ||
|
f604dcf2fa | ||
|
809ffe308e | ||
|
5baa8f04fa | ||
|
beb193ce0a | ||
|
0b4818b827 | ||
|
1fa7f4cd9e | ||
|
0905c6dba4 | ||
|
e02ca4a18e | ||
|
9d3afd020b | ||
|
587f757ca5 | ||
|
858bcf4a23 | ||
|
01de7618d0 | ||
|
0702bf4d1b | ||
|
eec2cbfc2a | ||
|
fe0dc785a3 | ||
|
610105ec65 | ||
|
5d6278449d | ||
|
b8800f02f4 | ||
|
5985498cd6 | ||
|
6d10f1c327 | ||
|
c8f176529b | ||
|
0880bf5469 | ||
|
fc0f18b50a | ||
|
2fd3a78e52 | ||
|
8bad0f0d25 | ||
|
421874fbc4 | ||
|
992820c76b | ||
|
87a05a138b | ||
|
928391103b | ||
|
58750da873 | ||
|
6df518e171 | ||
|
362b675ec8 | ||
|
b9c0a5c4b2 | ||
|
127e85fe13 | ||
|
04829a6c73 | ||
|
bf03ad4846 | ||
|
ce9464767b | ||
|
7d922c0b0d | ||
|
54ae1e623a | ||
|
44df564dcf | ||
|
17b5b0b884 | ||
|
f9542838c2 | ||
|
f4c3815b44 | ||
|
b348bbd1ab | ||
|
dec977ce00 | ||
|
b2d41e84fd | ||
|
b2203777a1 | ||
|
236bb03fe7 | ||
|
2846859508 | ||
|
30cbdeb7cd | ||
|
08d74d3345 | ||
|
9afa234f3c | ||
|
955fe06086 | ||
|
cfc514bc8e | ||
|
70f737b5f0 | ||
|
e44e0b7c3d | ||
|
e7558c8191 | ||
|
4389185cfb | ||
|
0a054f0ba1 | ||
|
1c80f0ca5a | ||
|
797d2929aa | ||
|
966b513c9c | ||
|
5a3b9074e1 | ||
|
00577453d2 | ||
|
33fdbb2326 | ||
|
9473f19c70 |
89 changed files with 16025 additions and 5929 deletions
3
.github/workflows/ccpp.yml
vendored
3
.github/workflows/ccpp.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: C/C++ CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
141
.github/workflows/win32.yml
vendored
Normal file
141
.github/workflows/win32.yml
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
name: Build ES for Win32
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on:
|
||||
# https://github.com/actions/runner-images/blob/main/images/win/Windows2022-Readme.md
|
||||
windows-2022
|
||||
|
||||
env:
|
||||
# Build parameters for CMake
|
||||
BUILD_TYPE: Release
|
||||
Platform: Win32
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: cmd
|
||||
|
||||
steps:
|
||||
# Create directories for build (used by CMake) and nuget
|
||||
- name: Set up directories
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: mkdir build nuget
|
||||
|
||||
# Check-out repository under $GITHUB_WORKSPACE
|
||||
# https://github.com/actions/checkout
|
||||
- name: Check-out repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
# Discover location of MSBuild tool and to PATH environment variables
|
||||
# https://github.com/microsoft/setup-msbuild
|
||||
- name: Locate MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
|
||||
# Use NuGet to download the latest libVLC.
|
||||
- name: Download libVLC
|
||||
working-directory: ${{runner.workspace}}/nuget
|
||||
run: nuget install -ExcludeVersion VideoLAN.LibVLC.Windows
|
||||
|
||||
# Use vcpkg to download and build the latest cURL
|
||||
- name: Build cURL static library
|
||||
run: vcpkg install curl:x86-windows-static-md
|
||||
|
||||
# Use vcpkg to download and build the latest FreeImage
|
||||
- name: Build FreeImage static library
|
||||
run: vcpkg install freeimage:x86-windows-static-md
|
||||
|
||||
# Use vcpkg to download and build the latest FreeType2
|
||||
- name: Build FreeType2 static library
|
||||
run: vcpkg install freetype:x86-windows-static-md
|
||||
|
||||
# Use vcpkg to download and build the latest SDL2
|
||||
- name: Build SDL2 static library
|
||||
run: vcpkg install sdl2:x86-windows-static-md
|
||||
|
||||
# Use vcpkg to download and build the latest RapidJSON
|
||||
- name: Build RapidJSON static library
|
||||
run: vcpkg install rapidjson:x86-windows-static-md
|
||||
|
||||
# Setup environment variables for subsequent steps
|
||||
# Note: Forward slashes are used for CMake compatibility
|
||||
- name: Set up environment
|
||||
run: |
|
||||
set VCPKG=%VCPKG_INSTALLATION_ROOT%/installed/x86-windows-static-md
|
||||
set "VCPKG=%VCPKG:\=/%"
|
||||
set NUGET=${{runner.workspace}}/nuget
|
||||
set "NUGET=%NUGET:\=/%"
|
||||
set VLC_HOME=%NUGET%/VideoLAN.LibVLC.Windows/build/x86
|
||||
echo VCPKG=%VCPKG%>> %GITHUB_ENV%
|
||||
echo NUGET=%NUGET%>> %GITHUB_ENV%
|
||||
echo FREETYPE_DIR=%VCPKG%>> %GITHUB_ENV%
|
||||
echo FREEIMAGE_HOME=%VCPKG%>> %GITHUB_ENV%
|
||||
echo VLC_HOME=%VLC_HOME%>> %GITHUB_ENV%
|
||||
echo RAPIDJSON_INCLUDE_DIRS=%VCPKG%/include>> %GITHUB_ENV%
|
||||
echo CURL_INCLUDE_DIR=%VCPKG%/include>> %GITHUB_ENV%
|
||||
echo SDL2_INCLUDE_DIR=%VCPKG%/include/SDL2>> %GITHUB_ENV%
|
||||
echo VLC_INCLUDE_DIR=%VLC_HOME%/include>> %GITHUB_ENV%
|
||||
echo CURL_LIBRARY=%VCPKG%/lib/*.lib>> %GITHUB_ENV%
|
||||
echo SDL2_LIBRARY=%VCPKG%/lib/manual-link/SDL2main.lib>> %GITHUB_ENV%
|
||||
echo VLC_LIBRARIES=%VLC_HOME%/libvlc*.lib>> %GITHUB_ENV%
|
||||
echo VLC_VERSION=3.0.11>> %GITHUB_ENV%
|
||||
|
||||
# Use CMake to create Visual Studio project in build folder
|
||||
- name: Create Visual Studio project
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: cmake ${{github.workspace}}
|
||||
-B build
|
||||
-A %Platform%
|
||||
-DRAPIDJSON_INCLUDE_DIRS=%RAPIDJSON_INCLUDE_DIRS%
|
||||
-DCURL_INCLUDE_DIR=%CURL_INCLUDE_DIR%
|
||||
-DSDL2_INCLUDE_DIR=%SDL2_INCLUDE_DIR%
|
||||
-DVLC_INCLUDE_DIR=%VLC_INCLUDE_DIR%
|
||||
-DCURL_LIBRARY=%CURL_LIBRARY%
|
||||
-DSDL2_LIBRARY=%SDL2_LIBRARY%
|
||||
-DVLC_LIBRARIES=%VLC_LIBRARIES%
|
||||
-DVLC_VERSION=%VLC_VERSION%
|
||||
-DCMAKE_EXE_LINKER_FLAGS=/SAFESEH:NO
|
||||
|
||||
# Use CMake to build project
|
||||
- name: Build EmulationStation
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: cmake --build build --config %BUILD_TYPE%
|
||||
|
||||
# Copy all other dependencies into Release folder
|
||||
# Note: Forward slashes are replaced with back slashes for this step
|
||||
- name: Collect dependencies
|
||||
working-directory: ${{github.workspace}}/Release
|
||||
run: |
|
||||
set "VLC_ROOT=%VLC_HOME:/=\%"
|
||||
mkdir .emulationstation
|
||||
xcopy ..\resources .\resources /h /i /c /k /e /r /y
|
||||
copy %VLC_ROOT%\*.dll .
|
||||
xcopy %VLC_ROOT%\plugins .\plugins /h /i /c /k /e /r /y
|
||||
|
||||
# Create systems configuration file
|
||||
- name: Create systems configuration file
|
||||
working-directory: ${{github.workspace}}/Release/.emulationstation
|
||||
run: |
|
||||
echo ^<!-- This is the EmulationStation Systems configuration file.> es_systems.cfg
|
||||
echo All systems must be contained within the ^<systemList^> tag.--^>>> es_systems.cfg
|
||||
echo:>> es_systems.cfg
|
||||
echo ^<systemList^>>> es_systems.cfg
|
||||
echo:>> es_systems.cfg
|
||||
echo ^</systemList^>>> es_systems.cfg
|
||||
|
||||
# Uploads artifacts from workflow
|
||||
# https://github.com/actions/upload-artifact
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: EmulationStation
|
||||
path: |
|
||||
${{github.workspace}}\Release\*.exe
|
||||
${{github.workspace}}\Release\*.dll
|
||||
${{github.workspace}}\Release\resources\
|
||||
${{github.workspace}}\Release\plugins\
|
||||
${{github.workspace}}\Release\.emulationstation\
|
|
@ -61,11 +61,11 @@ mark_as_advanced(RAPIDJSON_INCLUDE_DIRS)
|
|||
# handle the QUIETLY and REQUIRED arguments and set RAPIDJSON_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
include("FindPackageHandleStandardArgs")
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Rapidjson
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(RapidJSON
|
||||
REQUIRED_VARS RAPIDJSON_INCLUDE_DIRS
|
||||
FOUND_VAR Rapidjson_FOUND
|
||||
FOUND_VAR RapidJSON_FOUND
|
||||
)
|
||||
|
||||
if(Rapidjson_FOUND)
|
||||
set(RAPIDJSON_FOUND ${Rapidjson_FOUND})
|
||||
if(RapidJSON_FOUND)
|
||||
set(RAPIDJSON_FOUND ${RapidJSON_FOUND})
|
||||
endif()
|
||||
|
|
|
@ -23,14 +23,14 @@ endif(VLC_INCLUDE_DIR AND VLC_LIBRARIES)
|
|||
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||
if(NOT WIN32)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(VLC libvlc>=1.0.0)
|
||||
pkg_check_modules(VLC libvlc>=3.0.0)
|
||||
set(VLC_DEFINITIONS ${VLC_CFLAGS})
|
||||
set(VLC_LIBRARIES ${VLC_LDFLAGS})
|
||||
endif(NOT WIN32)
|
||||
|
||||
# TODO add argument support to pass version on find_package
|
||||
include(MacroEnsureVersion)
|
||||
macro_ensure_version(1.0.0 ${VLC_VERSION} VLC_VERSION_OK)
|
||||
macro_ensure_version(3.0.0 ${VLC_VERSION} VLC_VERSION_OK)
|
||||
if(VLC_VERSION_OK)
|
||||
set(VLC_FOUND TRUE)
|
||||
message(STATUS "VLC library found")
|
||||
|
|
|
@ -70,7 +70,9 @@ endmacro(clear_if_changed)
|
|||
|
||||
# Try to get some hints from pkg-config, if available
|
||||
macro(use_pkgconfig PREFIX PKGNAME)
|
||||
find_package(PkgConfig)
|
||||
if(NOT WIN32)
|
||||
find_package(PkgConfig)
|
||||
endif(NOT WIN32)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(${PREFIX} ${PKGNAME})
|
||||
endif ()
|
||||
|
@ -96,7 +98,7 @@ macro(get_debug_names PREFIX)
|
|||
endforeach(i)
|
||||
endmacro(get_debug_names)
|
||||
|
||||
# Add the parent dir from DIR to VAR
|
||||
# Add the parent dir from DIR to VAR
|
||||
macro(add_parent_dir VAR DIR)
|
||||
get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE)
|
||||
set(${VAR} ${${VAR}} ${${DIR}_TEMP})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
# Note: Visual Studio 2022 generator requires CMake 3.21 or greater.
|
||||
|
||||
option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES})
|
||||
option(GL "Set to ON if targeting Desktop OpenGL" ${GL})
|
||||
|
@ -137,8 +138,16 @@ if(MSVC)
|
|||
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
|
||||
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
|
||||
add_definitions(-DNOMINMAX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") #multi-processor compilation
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") #multi-processor compilation
|
||||
|
||||
# multi-processor compilation
|
||||
# disable warning c4018 - signed/unsigned mismatch
|
||||
# disable warning c4244 - conversion, possible loss of data
|
||||
# disable warning c4996 - use of deprecated function/member/variable/typedef
|
||||
# Use extended ASCII character set
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4018 /wd4244 /wd4996 /source-charset:437")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4018 /wd4244 /wd4996 /source-charset:437")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "/SAFESEH:NO")
|
||||
|
||||
# Set the start-up project to in VS to 'emulationstation'
|
||||
set(VS_STARTUP_PROJECT "emulationstation")
|
||||
|
@ -238,7 +247,12 @@ endif()
|
|||
|
||||
if(MSVC)
|
||||
LIST(APPEND COMMON_LIBRARIES
|
||||
Crypt32
|
||||
Imm32
|
||||
Setupapi
|
||||
Version
|
||||
winmm
|
||||
Wldap32
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
122
README.md
122
README.md
|
@ -7,34 +7,37 @@ EmulationStation is a cross-platform graphical front-end for emulators with cont
|
|||
Building
|
||||
========
|
||||
|
||||
**Building on Linux**
|
||||
Building on Linux
|
||||
-----------------
|
||||
|
||||
EmulationStation uses some C++11 code, which means you'll need to use at least g++-4.7 on Linux, or VS2010 on Windows, to compile.
|
||||
|
||||
EmulationStation has a few dependencies. For building, you'll need CMake, SDL2, FreeImage, FreeType, cURL and RapidJSON. You also should probably install the `fonts-droid` package which contains fallback fonts for Chinese/Japanese/Korean characters, but ES will still work fine without it (this package is only used at run-time).
|
||||
EmulationStation has a few dependencies. For building, you'll need CMake, SDL2, FreeImage, FreeType, LibVLC (ver. 3 or later), cURL and RapidJSON. You also should probably install the `fonts-droid` package which contains fallback fonts for Chinese/Japanese/Korean characters, but ES will still work fine without it (this package is only used at run-time).
|
||||
|
||||
**On Debian/Ubuntu:**
|
||||
### On Debian/Ubuntu:
|
||||
All of this be easily installed with `apt-get`:
|
||||
```bash
|
||||
sudo apt-get install libsdl2-dev libfreeimage-dev libfreetype6-dev libcurl4-openssl-dev rapidjson-dev \
|
||||
libasound2-dev libgles2-mesa-dev build-essential cmake fonts-droid-fallback libvlc-dev \
|
||||
libvlccore-dev vlc-bin
|
||||
```
|
||||
**On Fedora:**
|
||||
### On Fedora:
|
||||
All of this be easily installed with `dnf` (with rpmfusion activated) :
|
||||
```bash
|
||||
sudo dnf install SDL2-devel freeimage-devel freetype-devel curl-devel \
|
||||
alsa-lib-devel mesa-libGL-devel cmake \
|
||||
vlc-devel rapidjson-devel
|
||||
vlc-devel rapidjson-devel
|
||||
```
|
||||
|
||||
Optionaly, `pugixml` can be installed and used (Debian package: `libpugixml-dev`, Fedora/SuSE package: `pugixml-devel`), but EmulationStation can use its own included copy if not found.
|
||||
|
||||
**Note**: this repository uses a git submodule - to checkout the source and all submodules, use
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/RetroPie/EmulationStation.git
|
||||
```
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
```bash
|
||||
git clone https://github.com/RetroPie/EmulationStation.git
|
||||
|
@ -54,7 +57,7 @@ NOTE: to generate a `Debug` build on Unix/Linux, run the Makefile generation ste
|
|||
cmake -DCMAKE_BUILD_TYPE=Debug .
|
||||
```
|
||||
|
||||
**On the Raspberry Pi**
|
||||
### On the Raspberry Pi:
|
||||
|
||||
* Choosing a GLES implementation.
|
||||
|
||||
|
@ -71,23 +74,110 @@ cmake -DCMAKE_BUILD_TYPE=Debug .
|
|||
|
||||
If your system doesn't have a working GLESv2 implementation, the GLESv1 legacy renderer can be compiled in by adding `-DUSE_GLES1=On` to the build options.
|
||||
|
||||
**Building on Windows**
|
||||
Building on Windows
|
||||
-------------------
|
||||
|
||||
[FreeImage](http://downloads.sourceforge.net/freeimage/FreeImage3154Win32.zip)
|
||||
* Install [Visual Studio 2022](https://visualstudio.microsoft.com/vs/community/). At a minimum, install the "Desktop development with C++" workload with the default list of optional items.
|
||||
|
||||
[FreeType2](http://download.savannah.gnu.org/releases/freetype/freetype-2.4.9.tar.bz2) (you'll need to compile)
|
||||
* Install the latest version of [CMake](https://cmake.org/download/) (e.g., [cmake-3.27.1-windows-x86_64.msi](https://github.com/Kitware/CMake/releases/download/v3.27.1/cmake-3.27.1-windows-x86_64.msi)). CMake is used for generating the Visual Studio project.
|
||||
|
||||
[SDL2](http://www.libsdl.org/release/SDL2-devel-2.0.8-VC.zip)
|
||||
* Use git to clone [vcpkg](https://vcpkg.io/en/), then run the bootstrap script as shown below to build vcpkg . This is a C/C++ dependency manager from Microsoft.
|
||||
|
||||
[cURL](http://curl.haxx.se/download.html) (you'll need to compile or get the pre-compiled DLL version)
|
||||
```batchfile
|
||||
C:\src>git clone https://github.com/Microsoft/vcpkg.git
|
||||
C:\src>.\vcpkg\bootstrap-vcpkg.bat
|
||||
```
|
||||
|
||||
[RapisJSON](https://github.com/tencent/rapidjson) (you'll need the `include/rapidsjon` added to the include path)
|
||||
* Download the latest [nuget.exe](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) to a folder that is in your Windows PATH. NuGet is a .NET package manager.
|
||||
|
||||
(Remember to copy necessary .DLLs into the same folder as the executable: probably FreeImage.dll, freetype6.dll, SDL2.dll, libcurl.dll, and zlib1.dll. Exact list depends on if you built your libraries in "static" mode or not.)
|
||||
* Use NuGet to download the latest [libVLC](https://www.videolan.org/vlc/libvlc.html). This library is used to play video snaps.
|
||||
|
||||
[CMake](http://www.cmake.org/cmake/resources/software.html) (this is used for generating the Visual Studio project)
|
||||
```batchfile
|
||||
C:\src\EmulationStation>mkdir nuget
|
||||
C:\src\EmulationStation>cd nuget
|
||||
C:\src\EmulationStation\nuget>nuget install -ExcludeVersion VideoLAN.LibVLC.Windows
|
||||
```
|
||||
|
||||
(If you don't know how to use CMake, here are some hints: run cmake-gui and point it at your EmulationStation folder. Point the "build" directory somewhere - I use EmulationStation/build. Click configure, choose "Visual Studio [year] Project", fill in red fields as they appear and keep clicking Configure (you may need to check "Advanced"), then click Generate.)
|
||||
* Use vcpkg to download the latest pre-compiled [cURL](http://curl.haxx.se/download.html). This is a library for transferring data with URLs.
|
||||
|
||||
```batchfile
|
||||
c:\src>.\vcpkg\vcpkg install curl:x86-windows-static-md
|
||||
```
|
||||
|
||||
* Use vcpkg to download the latest [FreeImage](https://freeimage.sourceforge.io/index.html). This library supports popular graphics image formats.
|
||||
|
||||
```batchfile
|
||||
c:\src\>.\vcpkg\vcpkg install freeimage:x86-windows-static-md
|
||||
```
|
||||
|
||||
* Use vcpkg to download the latest pre-compiled [FreeType2](https://freetype.org/). This library is used to render fonts.
|
||||
|
||||
```batchfile
|
||||
c:\src>.\vcpkg\vcpkg install freetype:x86-windows-static-md
|
||||
```
|
||||
|
||||
* Use vcpkg to download the latest [SDL2](http://www.libsdl.org/). Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.
|
||||
|
||||
```batchfile
|
||||
c:\src>.\vcpkg\vcpkg install sdl2:x86-windows-static-md
|
||||
```
|
||||
|
||||
* Use vcpkg to download the latest [RapidJSON](http://rapidjson.org/). This library provides a fast JSON parser/generator for C++ with both SAX/DOM style API.
|
||||
|
||||
```batchfile
|
||||
c:\src>.\vcpkg\vcpkg install rapidjson:x86-windows-static-md
|
||||
```
|
||||
|
||||
* Using the example shown below, configure environment variables to point to the libraries that were installed in the above steps. Please note that the below example intentionally uses forward slashes for compatibility with CMake.
|
||||
|
||||
```batchfile
|
||||
C:\src\EmulationStation>set VCPKG=C:/src/vcpkg/installed/x86-windows-static-md
|
||||
C:\src\EmulationStation>set NUGET=C:/src/EmulationStation/nuget
|
||||
C:\src\EmulationStation>set FREETYPE_DIR=%VCPKG%
|
||||
C:\src\EmulationStation>set FREEIMAGE_HOME=%VCPKG%
|
||||
C:\src\EmulationStation>set VLC_HOME=%NUGET%/VideoLAN.LibVLC.Windows/build/x86
|
||||
C:\src\EmulationStation>set RAPIDJSON_INCLUDE_DIRS=%VCPKG%/include
|
||||
C:\src\EmulationStation>set CURL_INCLUDE_DIR=%VCPKG%/include
|
||||
C:\src\EmulationStation>set SDL2_INCLUDE_DIR=%VCPKG%/include/SDL2
|
||||
C:\src\EmulationStation>set VLC_INCLUDE_DIR=%VLC_HOME%/include
|
||||
C:\src\EmulationStation>set CURL_LIBRARY=%VCPKG%/lib/*.lib
|
||||
C:\src\EmulationStation>set SDL2_LIBRARY=%VCPKG%/lib/manual-link/SDL2main.lib
|
||||
C:\src\EmulationStation>set VLC_LIBRARIES=%VLC_HOME%/libvlc*.lib
|
||||
C:\src\EmulationStation>set VLC_VERSION=3.0.11
|
||||
```
|
||||
|
||||
* Use CMake to generate the Visual Studio project.
|
||||
|
||||
```batchfile
|
||||
C:\src\EmulationStation>mkdir build
|
||||
C:\src\EmulationStation>cmake . -B build -A Win32 ^
|
||||
-DRAPIDJSON_INCLUDE_DIRS=%RAPIDJSON_INCLUDE_DIRS% ^
|
||||
-DCURL_INCLUDE_DIR=%CURL_INCLUDE_DIR% ^
|
||||
-DSDL2_INCLUDE_DIR=%SDL2_INCLUDE_DIR% ^
|
||||
-DVLC_INCLUDE_DIR=%VLC_INCLUDE_DIR% ^
|
||||
-DCURL_LIBRARY=%CURL_LIBRARY% ^
|
||||
-DSDL2_LIBRARY=%SDL2_LIBRARY% ^
|
||||
-DVLC_LIBRARIES=%VLC_LIBRARIES% ^
|
||||
-DVLC_VERSION=%VLC_VERSION% ^
|
||||
-DCMAKE_EXE_LINKER_FLAGS=/SAFESEH:NO
|
||||
```
|
||||
|
||||
* Use CMake to build the Visual Studio project.
|
||||
|
||||
```batchfile
|
||||
C:\src\EmulationStation>cmake --build build --config Release
|
||||
```
|
||||
|
||||
* Using the example shown below, copy the newly built binaries and other needed files to destination folder.
|
||||
|
||||
```batchfile
|
||||
C:\src\EmulationStation>mkdir -p C:\apps\EmulationStation\.emulationstation
|
||||
C:\src\EmulationStation>xcopy C:\src\EmulationStation\resources C:\apps\EmulationStation\resources /h /i /c /k /e /r /y
|
||||
C:\src\EmulationStation>copy C:\src\EmulationStation\Release\*.exe C:\apps\EmulationStation /Y
|
||||
C:\src\EmulationStation>copy C:\src\EmulationStation\nuget\VideoLAN.LibVLC.Windows\build\x86\*.dll C:\apps\EmulationStation /Y
|
||||
C:\src\EmulationStation>xcopy C:\src\EmulationStation\nuget\VideoLAN.LibVLC.Windows\build\x86\plugins C:\apps\EmulationStation\plugins /h /i /c /k /e /r /y
|
||||
|
||||
```
|
||||
|
||||
|
||||
Configuring
|
||||
|
|
|
@ -35,6 +35,7 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiRandomCollectionOptions.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h
|
||||
|
||||
# Scrapers
|
||||
|
@ -93,6 +94,7 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiRandomCollectionOptions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp
|
||||
|
||||
# Scrapers
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "CollectionSystemManager.h"
|
||||
|
||||
#include "components/TextListComponent.h"
|
||||
#include "guis/GuiInfoPopup.h"
|
||||
#include "utils/FileSystemUtil.h"
|
||||
#include "utils/StringUtil.h"
|
||||
|
@ -12,12 +13,9 @@
|
|||
#include "Settings.h"
|
||||
#include "SystemData.h"
|
||||
#include "ThemeData.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <fstream>
|
||||
|
||||
std::string myCollectionsName = "collections";
|
||||
|
||||
#define LAST_PLAYED_MAX 50
|
||||
#include <cstring>
|
||||
|
||||
/* Handling the getting, initialization, deinitialization, saving and deletion of
|
||||
* a CollectionSystemManager Instance */
|
||||
|
@ -26,11 +24,12 @@ CollectionSystemManager* CollectionSystemManager::sInstance = NULL;
|
|||
CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(window)
|
||||
{
|
||||
CollectionSystemDecl systemDecls[] = {
|
||||
//type name long name //default sort // theme folder // isCustom
|
||||
{ AUTO_ALL_GAMES, "all", "all games", "filename, ascending", "auto-allgames", false },
|
||||
{ AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false },
|
||||
{ AUTO_FAVORITES, "favorites", "favorites", "filename, ascending", "auto-favorites", false },
|
||||
{ CUSTOM_COLLECTION, myCollectionsName, "collections", "filename, ascending", "custom-collections", true }
|
||||
//type name long name (display) default sort (key, order) theme folder isCustom
|
||||
{ AUTO_ALL_GAMES, "all", "all games", "name, ascending", "auto-allgames", false },
|
||||
{ AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false },
|
||||
{ AUTO_FAVORITES, "favorites", "favorites", "name, ascending", "auto-favorites", false },
|
||||
{ AUTO_RANDOM, RANDOM_COLL_ID, "random", "name, ascending", "auto-random", false },
|
||||
{ CUSTOM_COLLECTION, CUSTOM_COLL_ID, "collections", "name, ascending", "custom-collections", true }
|
||||
};
|
||||
|
||||
// create a map
|
||||
|
@ -59,6 +58,7 @@ CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(windo
|
|||
mEditingCollection = "Favorites";
|
||||
mEditingCollectionSystemData = NULL;
|
||||
mCustomCollectionsBundle = NULL;
|
||||
mRandomCollection = NULL;
|
||||
}
|
||||
|
||||
CollectionSystemManager::~CollectionSystemManager()
|
||||
|
@ -98,29 +98,37 @@ void CollectionSystemManager::deinit()
|
|||
}
|
||||
}
|
||||
|
||||
void CollectionSystemManager::saveCustomCollection(SystemData* sys)
|
||||
bool CollectionSystemManager::saveCustomCollection(SystemData* sys)
|
||||
{
|
||||
std::string name = sys->getName();
|
||||
std::unordered_map<std::string, FileData*> games = sys->getRootFolder()->getChildrenByFilename();
|
||||
bool found = mCustomCollectionSystemsData.find(name) != mCustomCollectionSystemsData.cend();
|
||||
if (found) {
|
||||
CollectionSystemData sysData = mCustomCollectionSystemsData.at(name);
|
||||
if (sysData.needsSave)
|
||||
{
|
||||
std::ofstream configFile;
|
||||
configFile.open(getCustomCollectionConfigPath(name));
|
||||
for(std::unordered_map<std::string, FileData*>::const_iterator iter = games.cbegin(); iter != games.cend(); ++iter)
|
||||
{
|
||||
std::string path = iter->first;
|
||||
configFile << path << std::endl;
|
||||
}
|
||||
configFile.close();
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!found)
|
||||
{
|
||||
LOG(LogError) << "Couldn't find collection to save! " << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectionSystemData sysData = mCustomCollectionSystemsData.at(name);
|
||||
if (sysData.needsSave)
|
||||
{
|
||||
std::string absCollectionFn = getCustomCollectionConfigPath(name);
|
||||
std::ofstream configFile;
|
||||
configFile.open(absCollectionFn);
|
||||
if (!configFile.good())
|
||||
{
|
||||
auto const errNo = errno;
|
||||
LOG(LogError) << "Failed to create file, collection not created: " << absCollectionFn << ": " << std::strerror(errNo) << " (" << errNo << ")";
|
||||
return false;
|
||||
}
|
||||
for(std::unordered_map<std::string, FileData*>::const_iterator iter = games.cbegin(); iter != games.cend(); ++iter)
|
||||
{
|
||||
std::string path = iter->first;
|
||||
configFile << path << std::endl;
|
||||
}
|
||||
configFile.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Methods to load all Collections into memory, and handle enabling the active ones */
|
||||
|
@ -128,8 +136,8 @@ void CollectionSystemManager::saveCustomCollection(SystemData* sys)
|
|||
void CollectionSystemManager::loadCollectionSystems(bool async)
|
||||
{
|
||||
initAutoCollectionSystems();
|
||||
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[myCollectionsName];
|
||||
mCustomCollectionsBundle = createNewCollectionEntry(decl.name, decl, false);
|
||||
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[CUSTOM_COLL_ID];
|
||||
mCustomCollectionsBundle = createNewCollectionEntry(decl.name, decl, CollectionFlags::NONE);
|
||||
// we will also load custom systems here
|
||||
initCustomCollectionSystems();
|
||||
if(Settings::getInstance()->getString("CollectionSystemsAuto") != "" || Settings::getInstance()->getString("CollectionSystemsCustom") != "")
|
||||
|
@ -171,7 +179,7 @@ void CollectionSystemManager::updateSystemsList()
|
|||
// remove all Collection Systems
|
||||
removeCollectionsFromDisplayedSystems();
|
||||
// add custom enabled ones
|
||||
addEnabledCollectionsToDisplayedSystems(&mCustomCollectionSystemsData);
|
||||
addEnabledCollectionsToDisplayedSystems(&mCustomCollectionSystemsData, false);
|
||||
|
||||
if(Settings::getInstance()->getBool("SortAllSystems"))
|
||||
{
|
||||
|
@ -197,20 +205,20 @@ void CollectionSystemManager::updateSystemsList()
|
|||
|
||||
if(mCustomCollectionsBundle->getRootFolder()->getChildren().size() > 0)
|
||||
{
|
||||
mCustomCollectionsBundle->getRootFolder()->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[myCollectionsName].defaultSort));
|
||||
mCustomCollectionsBundle->getRootFolder()->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[CUSTOM_COLL_ID].defaultSort));
|
||||
SystemData::sSystemVector.push_back(mCustomCollectionsBundle);
|
||||
}
|
||||
|
||||
// add auto enabled ones
|
||||
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData);
|
||||
// add auto enabled ones except random
|
||||
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData, false);
|
||||
// finally, add random
|
||||
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData, true);
|
||||
|
||||
// create views for collections, before reload
|
||||
for(auto sysIt = SystemData::sSystemVector.cbegin(); sysIt != SystemData::sSystemVector.cend(); sysIt++)
|
||||
{
|
||||
if ((*sysIt)->isCollection())
|
||||
{
|
||||
ViewController::get()->getGameListView((*sysIt));
|
||||
}
|
||||
}
|
||||
|
||||
// if we were editing a custom collection, and it's no longer enabled, exit edit mode
|
||||
|
@ -284,20 +292,26 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS
|
|||
rootFolder->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[name].defaultSort));
|
||||
if (name == "recent")
|
||||
{
|
||||
trimCollectionCount(rootFolder, LAST_PLAYED_MAX);
|
||||
trimCollectionCount(rootFolder, LAST_PLAYED_MAX, false);
|
||||
ViewController::get()->onFileChanged(rootFolder, FILE_METADATA_CHANGED);
|
||||
// Force re-calculation of cursor position
|
||||
ViewController::get()->getGameListView(curSys)->setViewportTop(TextListComponent<FileData>::REFRESH_LIST_CURSOR_POS);
|
||||
}
|
||||
else
|
||||
ViewController::get()->onFileChanged(rootFolder, FILE_SORTED);
|
||||
}
|
||||
}
|
||||
|
||||
void CollectionSystemManager::trimCollectionCount(FileData* rootFolder, int limit)
|
||||
void CollectionSystemManager::trimCollectionCount(FileData* rootFolder, int limit, bool shuffle)
|
||||
{
|
||||
SystemData* curSys = rootFolder->getSystem();
|
||||
while ((int)rootFolder->getChildrenListToDisplay().size() > limit)
|
||||
{
|
||||
CollectionFileData* gameToRemove = (CollectionFileData*)rootFolder->getChildrenListToDisplay().back();
|
||||
std::vector<FileData*> games = rootFolder->getFilesRecursive(GAME, true);
|
||||
if (shuffle)
|
||||
std::shuffle(games.begin(), games.end(), SystemData::sURNG);
|
||||
|
||||
CollectionFileData* gameToRemove = (CollectionFileData*)games.back();
|
||||
ViewController::get()->getGameListView(curSys).get()->remove(gameToRemove, false, false);
|
||||
}
|
||||
ViewController::get()->onFileChanged(rootFolder, FILE_REMOVED);
|
||||
|
@ -370,6 +384,7 @@ bool CollectionSystemManager::isThemeCustomCollectionCompatible(std::vector<std:
|
|||
std::string CollectionSystemManager::getValidNewCollectionName(std::string inName, int index)
|
||||
{
|
||||
std::string name = inName;
|
||||
const std::string infix = " (" + std::to_string(index) + ")";
|
||||
|
||||
if(index == 0)
|
||||
{
|
||||
|
@ -383,7 +398,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
|
|||
}
|
||||
else
|
||||
{
|
||||
name += " (" + std::to_string(index) + ")";
|
||||
name += infix;
|
||||
}
|
||||
|
||||
if(name == "")
|
||||
|
@ -393,7 +408,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
|
|||
|
||||
if(name != inName)
|
||||
{
|
||||
LOG(LogInfo) << "Had to change name, from: " << inName << " to: " << name;
|
||||
LOG(LogInfo) << "Name collision, had to change name from: " << inName << " to: " << name;
|
||||
}
|
||||
|
||||
// get used systems in es_systems.cfg
|
||||
|
@ -413,7 +428,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
|
|||
if (*sysIt == name)
|
||||
{
|
||||
if(index > 0) {
|
||||
name = name.substr(0, name.size()-4);
|
||||
name = name.substr(0, name.size() - infix.size());
|
||||
}
|
||||
return getValidNewCollectionName(name, index+1);
|
||||
}
|
||||
|
@ -424,7 +439,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
|
|||
return name;
|
||||
}
|
||||
|
||||
void CollectionSystemManager::setEditMode(std::string collectionName)
|
||||
void CollectionSystemManager::setEditMode(std::string collectionName, bool quiet)
|
||||
{
|
||||
if (mCustomCollectionSystemsData.find(collectionName) == mCustomCollectionSystemsData.cend())
|
||||
{
|
||||
|
@ -442,22 +457,38 @@ void CollectionSystemManager::setEditMode(std::string collectionName)
|
|||
// if it's bundled, this needs to be the bundle system
|
||||
mEditingCollectionSystemData = sysData;
|
||||
|
||||
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + Utils::String::toUpper(collectionName) + "' Collection. Add/remove games with Y.", 10000);
|
||||
mWindow->setInfoPopup(s);
|
||||
if (!quiet) {
|
||||
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + Utils::String::toUpper(collectionName) + "' Collection. Add/remove games with Y.", 8000);
|
||||
mWindow->setInfoPopup(s);
|
||||
}
|
||||
}
|
||||
|
||||
void CollectionSystemManager::exitEditMode()
|
||||
void CollectionSystemManager::exitEditMode(bool quiet)
|
||||
{
|
||||
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + mEditingCollection + "' Collection.", 4000);
|
||||
mWindow->setInfoPopup(s);
|
||||
mIsEditingCustom = false;
|
||||
mEditingCollection = "Favorites";
|
||||
if (!quiet) {
|
||||
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + Utils::String::toUpper(mEditingCollection) + "' Collection.", 4000);
|
||||
mWindow->setInfoPopup(s);
|
||||
}
|
||||
if (mIsEditingCustom) {
|
||||
mIsEditingCustom = false;
|
||||
mEditingCollection = "Favorites";
|
||||
mEditingCollectionSystemData->system->onMetaDataSavePoint();
|
||||
saveCustomCollection(mEditingCollectionSystemData->system);
|
||||
}
|
||||
}
|
||||
|
||||
mEditingCollectionSystemData->system->onMetaDataSavePoint();
|
||||
int CollectionSystemManager::getPressCountInDuration() {
|
||||
Uint32 now = SDL_GetTicks();
|
||||
if (now - mFirstPressMs < DOUBLE_PRESS_DETECTION_DURATION) {
|
||||
return 2;
|
||||
} else {
|
||||
mFirstPressMs = now;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// adds or removes a game from a specific collection
|
||||
bool CollectionSystemManager::toggleGameInCollection(FileData* file, int presscount)
|
||||
bool CollectionSystemManager::toggleGameInCollection(FileData* file)
|
||||
{
|
||||
if (file->getType() == GAME)
|
||||
{
|
||||
|
@ -483,7 +514,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
|
|||
SystemData* systemViewToUpdate = getSystemToView(sysData);
|
||||
|
||||
if (found) {
|
||||
if (needDoublePress(presscount)) {
|
||||
if (needDoublePress(getPressCountInDuration())) {
|
||||
return true;
|
||||
}
|
||||
adding = false;
|
||||
|
@ -504,7 +535,11 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
|
|||
CollectionFileData* newGame = new CollectionFileData(file, sysData);
|
||||
rootFolder->addChild(newGame);
|
||||
fileIndex->addToIndex(newGame);
|
||||
ViewController::get()->getGameListView(systemViewToUpdate)->onFileChanged(newGame, FILE_METADATA_CHANGED);
|
||||
// this is the biggest performance bottleneck for this process.
|
||||
// this code has been here for 7 years, since this feature was added.
|
||||
// I might have been playing it safe back then, but it feels unnecessary, especially given following onFileChanged to sort
|
||||
// Commenting this out for now.
|
||||
//ViewController::get()->getGameListView(systemViewToUpdate)->onFileChanged(newGame, FILE_METADATA_CHANGED);
|
||||
rootFolder->sort(getSortTypeFromString(mEditingCollectionSystemData->decl.defaultSort));
|
||||
ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), FILE_SORTED);
|
||||
// add to bundle index as well, if needed
|
||||
|
@ -513,6 +548,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
|
|||
systemViewToUpdate->getIndex()->addToIndex(newGame);
|
||||
}
|
||||
}
|
||||
sysData->setShuffledCacheDirty();
|
||||
updateCollectionFolderMetadata(sysData);
|
||||
}
|
||||
else
|
||||
|
@ -526,7 +562,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
|
|||
}
|
||||
else
|
||||
{
|
||||
if (needDoublePress(presscount)) {
|
||||
if (needDoublePress(getPressCountInDuration())) {
|
||||
return true;
|
||||
}
|
||||
adding = false;
|
||||
|
@ -546,6 +582,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
|
|||
{
|
||||
s = new GuiInfoPopup(mWindow, "Removed '" + Utils::String::removeParenthesis(name) + "' from '" + Utils::String::toUpper(sysName) + "'", 4000);
|
||||
}
|
||||
|
||||
mWindow->setInfoPopup(s);
|
||||
return true;
|
||||
}
|
||||
|
@ -571,8 +608,57 @@ SystemData* CollectionSystemManager::getSystemToView(SystemData* sys)
|
|||
return systemToView;
|
||||
}
|
||||
|
||||
void CollectionSystemManager::recreateCollection(SystemData* sysData)
|
||||
{
|
||||
CollectionSystemData* colSysData;
|
||||
if (mAutoCollectionSystemsData.find(sysData->getName()) != mAutoCollectionSystemsData.end())
|
||||
{
|
||||
// it's an auto collection
|
||||
colSysData = &mAutoCollectionSystemsData[sysData->getName()];
|
||||
}
|
||||
else if (mCustomCollectionSystemsData.find(sysData->getName()) != mCustomCollectionSystemsData.end())
|
||||
{
|
||||
// it's a custom collection
|
||||
colSysData = &mCustomCollectionSystemsData[sysData->getName()];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LogDebug) << "Couldn't find collection to recreate in either custom or auto collections: " << sysData->getName();
|
||||
return;
|
||||
}
|
||||
|
||||
CollectionSystemDecl sysDecl = colSysData->decl;
|
||||
FileData* rootFolder = sysData->getRootFolder();
|
||||
FileFilterIndex* index = sysData->getIndex();
|
||||
const std::unordered_map<std::string, FileData*>& children = rootFolder->getChildrenByFilename();
|
||||
|
||||
sysData->getIndex()->resetIndex();
|
||||
std::string name = sysData->getName();
|
||||
|
||||
SystemData* systemViewToUpdate = getSystemToView(sysData);
|
||||
|
||||
// while there are games there, remove them from the view and system
|
||||
while(rootFolder->getChildrenByFilename().size() > 0)
|
||||
ViewController::get()->getGameListView(systemViewToUpdate).get()->remove(rootFolder->getChildrenByFilename().begin()->second, false, false);
|
||||
|
||||
colSysData->isPopulated = false;
|
||||
if (sysDecl.isCustom)
|
||||
populateCustomCollection(colSysData);
|
||||
else
|
||||
populateAutoCollection(colSysData);
|
||||
|
||||
rootFolder->sort(getSortTypeFromString(colSysData->decl.defaultSort));
|
||||
ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), FILE_SORTED);
|
||||
|
||||
// Workaround to force video to play
|
||||
FileData* cursor = ViewController::get()->getGameListView(systemViewToUpdate)->getCursor();
|
||||
ViewController::get()->getGameListView(systemViewToUpdate)->setCursor(cursor, true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Handles loading a collection system, creating an empty one, and populating on demand */
|
||||
// loads Automatic Collection systems (All, Favorites, Last Played)
|
||||
// loads Automatic Collection systems (All, Favorites, Last Played, Random)
|
||||
void CollectionSystemManager::initAutoCollectionSystems()
|
||||
{
|
||||
for(std::map<std::string, CollectionSystemDecl>::const_iterator it = mCollectionSystemDeclsIndex.cbegin() ; it != mCollectionSystemDeclsIndex.cend() ; it++ )
|
||||
|
@ -580,7 +666,9 @@ void CollectionSystemManager::initAutoCollectionSystems()
|
|||
CollectionSystemDecl sysDecl = it->second;
|
||||
if (!sysDecl.isCustom)
|
||||
{
|
||||
createNewCollectionEntry(sysDecl.name, sysDecl);
|
||||
SystemData* newCol = createNewCollectionEntry(sysDecl.name, sysDecl, CollectionFlags::HOLD_IN_MAP);
|
||||
if (sysDecl.type == AUTO_RANDOM)
|
||||
mRandomCollection = newCol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -678,17 +766,20 @@ SystemData* CollectionSystemManager::getAllGamesCollection()
|
|||
return allSysData->system;
|
||||
}
|
||||
|
||||
SystemData* CollectionSystemManager::addNewCustomCollection(std::string name)
|
||||
SystemData* CollectionSystemManager::addNewCustomCollection(std::string name, bool needsSave)
|
||||
{
|
||||
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[myCollectionsName];
|
||||
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[CUSTOM_COLL_ID];
|
||||
decl.themeFolder = name;
|
||||
decl.name = name;
|
||||
decl.longName = name;
|
||||
return createNewCollectionEntry(name, decl);
|
||||
CollectionFlags flags = CollectionFlags::HOLD_IN_MAP;
|
||||
if (needsSave)
|
||||
flags = flags | CollectionFlags::NEEDS_SAVE;
|
||||
return createNewCollectionEntry(name, decl, flags);
|
||||
}
|
||||
|
||||
// creates a new, empty Collection system, based on the name and declaration
|
||||
SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index)
|
||||
SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, const CollectionFlags flags)
|
||||
{
|
||||
SystemData* newSys = new SystemData(name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true);
|
||||
|
||||
|
@ -697,9 +788,9 @@ SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name,
|
|||
newCollectionData.decl = sysDecl;
|
||||
newCollectionData.isEnabled = false;
|
||||
newCollectionData.isPopulated = false;
|
||||
newCollectionData.needsSave = false;
|
||||
newCollectionData.needsSave = (flags & CollectionFlags::NEEDS_SAVE) == CollectionFlags::NEEDS_SAVE ? true : false;
|
||||
|
||||
if (index)
|
||||
if ((flags & CollectionFlags::HOLD_IN_MAP) == CollectionFlags::HOLD_IN_MAP)
|
||||
{
|
||||
if (!sysDecl.isCustom)
|
||||
{
|
||||
|
@ -714,6 +805,118 @@ SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name,
|
|||
return newSys;
|
||||
}
|
||||
|
||||
void CollectionSystemManager::addRandomGames(SystemData* newSys, SystemData* sourceSystem, FileData* rootFolder,
|
||||
FileFilterIndex* index, std::map<std::string, std::map<std::string, int>> mapsForRandomColl, int defaultValue)
|
||||
{
|
||||
|
||||
int gamesForSourceSystem = defaultValue;
|
||||
for (auto& m : mapsForRandomColl)
|
||||
{
|
||||
// m.first unused
|
||||
std::map<std::string, int> collMap = m.second;
|
||||
if (collMap.find(sourceSystem->getFullName()) != collMap.end())
|
||||
{
|
||||
int maxForSys = collMap[sourceSystem->getFullName()];
|
||||
// we won't add more than the max and less than 0
|
||||
gamesForSourceSystem = Math::max(Math::min(RANDOM_SYSTEM_MAX, maxForSys), 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// load exclusion collection
|
||||
std::unordered_map<std::string,FileData*> exclusionMap;
|
||||
std::string exclusionCollection = Settings::getInstance()->getString("RandomCollectionExclusionCollection");
|
||||
auto sysDataIt = mCustomCollectionSystemsData.find(exclusionCollection);
|
||||
|
||||
if (!exclusionCollection.empty() && sysDataIt != mCustomCollectionSystemsData.end()) {
|
||||
if (!sysDataIt->second.isPopulated)
|
||||
{
|
||||
populateCustomCollection(&(sysDataIt->second));
|
||||
}
|
||||
|
||||
exclusionMap = mCustomCollectionSystemsData[exclusionCollection].system->getRootFolder()->getChildrenByFilename();
|
||||
|
||||
}
|
||||
|
||||
// we do this to avoid trying to add more games than there are in the system
|
||||
gamesForSourceSystem = Math::min(gamesForSourceSystem, sourceSystem->getRootFolder()->getFilesRecursive(GAME).size());
|
||||
|
||||
int startCount = rootFolder->getFilesRecursive(GAME).size();
|
||||
int endCount = startCount + gamesForSourceSystem;
|
||||
int retryCount = 10;
|
||||
|
||||
for (int iterCount = startCount; iterCount < endCount;)
|
||||
{
|
||||
FileData* randomGame = sourceSystem->getRandomGame()->getSourceFileData();
|
||||
CollectionFileData* newGame = NULL;
|
||||
|
||||
if(exclusionMap.find(randomGame->getFullPath()) == exclusionMap.end())
|
||||
{
|
||||
// Not in the exclusion collection
|
||||
newGame = new CollectionFileData(randomGame, newSys);
|
||||
rootFolder->addChild(newGame);
|
||||
index->addToIndex(newGame);
|
||||
}
|
||||
|
||||
if (rootFolder->getFilesRecursive(GAME).size() > iterCount)
|
||||
{
|
||||
// added game, proceed
|
||||
iterCount++;
|
||||
retryCount = 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the game already exists in the collection, let's try again
|
||||
LOG(LogDebug) << "Clash: " << randomGame->getName() << " already exists or in exclusion list. Deleting and trying again";
|
||||
delete newGame;
|
||||
retryCount--;
|
||||
if (retryCount == 0)
|
||||
{
|
||||
// we give up. Either we were very unlucky, or all the games in this system are already there.
|
||||
LOG(LogDebug) << "Giving up retrying: cannot add this game. Deleting and moving on.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollectionSystemManager::populateRandomCollectionFromCollections(std::map<std::string, std::map<std::string, int>> mapsForRandomColl)
|
||||
{
|
||||
CollectionSystemData* sysData = &mAutoCollectionSystemsData[RANDOM_COLL_ID];
|
||||
SystemData* newSys = sysData->system;
|
||||
CollectionSystemDecl sysDecl = sysData->decl;
|
||||
FileData* rootFolder = newSys->getRootFolder();
|
||||
FileFilterIndex* index = newSys->getIndex();
|
||||
|
||||
// iterate the auto collections map
|
||||
for(auto &c : mAutoCollectionSystemsData)
|
||||
{
|
||||
CollectionSystemData csd = c.second;
|
||||
// we can't add games from the random collection to the random collection
|
||||
if (csd.decl.type != AUTO_RANDOM)
|
||||
{
|
||||
// collections might not be populated
|
||||
if (!csd.isPopulated)
|
||||
populateAutoCollection(&csd);
|
||||
|
||||
if (csd.isPopulated)
|
||||
addRandomGames(newSys, csd.system, rootFolder, index, mapsForRandomColl, DEFAULT_RANDOM_COLLECTIONS_GAMES);
|
||||
}
|
||||
}
|
||||
|
||||
// iterate the custom collections map
|
||||
for(auto &c : mCustomCollectionSystemsData)
|
||||
{
|
||||
CollectionSystemData csd = c.second;
|
||||
// collections might not be populated
|
||||
if (!csd.isPopulated)
|
||||
populateCustomCollection(&csd);
|
||||
|
||||
if (csd.isPopulated)
|
||||
addRandomGames(newSys, csd.system, rootFolder, index, mapsForRandomColl, DEFAULT_RANDOM_COLLECTIONS_GAMES);
|
||||
}
|
||||
}
|
||||
|
||||
// populates an Automatic Collection System
|
||||
void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysData)
|
||||
{
|
||||
|
@ -721,35 +924,80 @@ void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysDa
|
|||
CollectionSystemDecl sysDecl = sysData->decl;
|
||||
FileData* rootFolder = newSys->getRootFolder();
|
||||
FileFilterIndex* index = newSys->getIndex();
|
||||
|
||||
std::map<std::string, std::map<std::string, int>> mapsForRandomColl;
|
||||
if (sysDecl.type == AUTO_RANDOM)
|
||||
{
|
||||
// user may have defined a custom collection with the same name as a system name, thus keeping maps in another map
|
||||
std::map<std::string, int> randomSystems = Settings::getInstance()->getMap("RandomCollectionSystems");
|
||||
mapsForRandomColl["RandomCollectionSystems"] = randomSystems;
|
||||
std::map<std::string, int> randomAutoColl = Settings::getInstance()->getMap("RandomCollectionSystemsAuto");
|
||||
mapsForRandomColl["RandomCollectionSystemsAuto"] = randomAutoColl;
|
||||
std::map<std::string, int> randomCustColl = Settings::getInstance()->getMap("RandomCollectionSystemsCustom");
|
||||
mapsForRandomColl["RandomCollectionSystemsCustom"] = randomCustColl;
|
||||
}
|
||||
// Only iterate through game systems, not collections yet
|
||||
for(auto sysIt = SystemData::sSystemVector.cbegin(); sysIt != SystemData::sSystemVector.cend(); sysIt++)
|
||||
{
|
||||
// we won't iterate all collections
|
||||
if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection()) {
|
||||
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME);
|
||||
for(auto gameIt = files.cbegin(); gameIt != files.cend(); gameIt++)
|
||||
if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection())
|
||||
{
|
||||
if (sysDecl.type == AUTO_RANDOM)
|
||||
{
|
||||
bool include = includeFileInAutoCollections((*gameIt));
|
||||
switch(sysDecl.type) {
|
||||
case AUTO_LAST_PLAYED:
|
||||
include = include && (*gameIt)->metadata.get("playcount") > "0";
|
||||
break;
|
||||
case AUTO_FAVORITES:
|
||||
// we may still want to add files we don't want in auto collections in "favorites"
|
||||
include = (*gameIt)->metadata.get("favorite") == "true";
|
||||
break;
|
||||
}
|
||||
addRandomGames(newSys, *sysIt, rootFolder, index, mapsForRandomColl, DEFAULT_RANDOM_SYSTEM_GAMES);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME);
|
||||
|
||||
if (include) {
|
||||
CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys);
|
||||
rootFolder->addChild(newGame);
|
||||
index->addToIndex(newGame);
|
||||
for(auto gameIt = files.cbegin(); gameIt != files.cend(); gameIt++)
|
||||
{
|
||||
bool include = includeFileInAutoCollections(*gameIt);
|
||||
switch(sysDecl.type) {
|
||||
case AUTO_LAST_PLAYED:
|
||||
include = include && (*gameIt)->metadata.get("playcount") > "0";
|
||||
break;
|
||||
case AUTO_FAVORITES:
|
||||
// we may still want to add files we don't want in auto collections in "favorites"
|
||||
include = (*gameIt)->metadata.get("favorite") == "true";
|
||||
break;
|
||||
case AUTO_ALL_GAMES:
|
||||
break;
|
||||
default:
|
||||
// No-op to prevent compiler warnings
|
||||
// Getting here means that the file is not part of a pre-defined collection.
|
||||
include = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (include)
|
||||
{
|
||||
CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys);
|
||||
rootFolder->addChild(newGame);
|
||||
index->addToIndex(newGame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort));
|
||||
if (sysDecl.type == AUTO_LAST_PLAYED)
|
||||
trimCollectionCount(rootFolder, LAST_PLAYED_MAX);
|
||||
|
||||
// here we finish populating the Random collection based on other Collections
|
||||
if (sysDecl.type == AUTO_RANDOM)
|
||||
populateRandomCollectionFromCollections(mapsForRandomColl);
|
||||
|
||||
// sort before optional trimming, if collection is displayed
|
||||
if (sysData->isEnabled)
|
||||
rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort));
|
||||
|
||||
if (sysData->isEnabled && (sysDecl.type == AUTO_LAST_PLAYED || sysDecl.type == AUTO_RANDOM))
|
||||
{
|
||||
int trimValue = LAST_PLAYED_MAX;
|
||||
if (sysDecl.type == AUTO_RANDOM)
|
||||
trimValue = Settings::getInstance()->getInt("RandomCollectionMaxGames");
|
||||
if (trimValue > 0)
|
||||
trimCollectionCount(rootFolder, trimValue, sysDecl.type == AUTO_RANDOM);
|
||||
}
|
||||
|
||||
sysData->isPopulated = true;
|
||||
}
|
||||
|
||||
|
@ -757,7 +1005,6 @@ void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysDa
|
|||
void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sysData)
|
||||
{
|
||||
SystemData* newSys = sysData->system;
|
||||
sysData->isPopulated = true;
|
||||
CollectionSystemDecl sysDecl = sysData->decl;
|
||||
std::string path = getCustomCollectionConfigPath(newSys->getName());
|
||||
|
||||
|
@ -778,11 +1025,11 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys
|
|||
std::unordered_map<std::string,FileData*> allFilesMap = getAllGamesCollection()->getRootFolder()->getChildrenByFilename();
|
||||
|
||||
// iterate list of files in config file
|
||||
|
||||
for(std::string gameKey; getline(input, gameKey); )
|
||||
{
|
||||
std::unordered_map<std::string,FileData*>::const_iterator it = allFilesMap.find(gameKey);
|
||||
if (it != allFilesMap.cend()) {
|
||||
if (it != allFilesMap.cend())
|
||||
{
|
||||
CollectionFileData* newGame = new CollectionFileData(it->second, newSys);
|
||||
rootFolder->addChild(newGame);
|
||||
index->addToIndex(newGame);
|
||||
|
@ -794,6 +1041,7 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys
|
|||
}
|
||||
rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort));
|
||||
updateCollectionFolderMetadata(newSys);
|
||||
sysData->isPopulated = true;
|
||||
}
|
||||
|
||||
/* Handle System View removal and insertion of Collections */
|
||||
|
@ -829,38 +1077,40 @@ void CollectionSystemManager::removeCollectionsFromDisplayedSystems()
|
|||
}
|
||||
}
|
||||
|
||||
void CollectionSystemManager::addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData)
|
||||
// The "random" collection relies on all other collections to have been initialized, so we defer its processing
|
||||
void CollectionSystemManager::addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData, bool processRandom)
|
||||
{
|
||||
// add auto enabled ones
|
||||
for(std::map<std::string, CollectionSystemData>::iterator it = colSystemData->begin() ; it != colSystemData->end() ; it++ )
|
||||
{
|
||||
if(it->second.isEnabled)
|
||||
|
||||
if ((!processRandom && it->second.decl.type != AUTO_RANDOM) || (processRandom && it->second.decl.type == AUTO_RANDOM))
|
||||
{
|
||||
// check if populated, otherwise populate
|
||||
if (!it->second.isPopulated)
|
||||
if(it->second.isEnabled)
|
||||
{
|
||||
if(it->second.decl.isCustom)
|
||||
// check if populated, otherwise populate
|
||||
if (!it->second.isPopulated)
|
||||
{
|
||||
populateCustomCollection(&(it->second));
|
||||
if(it->second.decl.isCustom)
|
||||
populateCustomCollection(&(it->second));
|
||||
else
|
||||
populateAutoCollection(&(it->second));
|
||||
}
|
||||
|
||||
// check if it has its own view
|
||||
if(!it->second.decl.isCustom || themeFolderExists(it->first) || !Settings::getInstance()->getBool("UseCustomCollectionsSystem"))
|
||||
{
|
||||
// exists theme folder, or we chose not to bundle it under the custom-collections system
|
||||
// so we need to create a view
|
||||
SystemData::sSystemVector.push_back(it->second.system);
|
||||
}
|
||||
else
|
||||
{
|
||||
populateAutoCollection(&(it->second));
|
||||
FileData* newSysRootFolder = it->second.system->getRootFolder();
|
||||
mCustomCollectionsBundle->getRootFolder()->addChild(newSysRootFolder);
|
||||
mCustomCollectionsBundle->getIndex()->importIndex(it->second.system->getIndex());
|
||||
}
|
||||
}
|
||||
// check if it has its own view
|
||||
if(!it->second.decl.isCustom || themeFolderExists(it->first) || !Settings::getInstance()->getBool("UseCustomCollectionsSystem"))
|
||||
{
|
||||
// exists theme folder, or we chose not to bundle it under the custom-collections system
|
||||
// so we need to create a view
|
||||
SystemData::sSystemVector.push_back(it->second.system);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileData* newSysRootFolder = it->second.system->getRootFolder();
|
||||
mCustomCollectionsBundle->getRootFolder()->addChild(newSysRootFolder);
|
||||
mCustomCollectionsBundle->getIndex()->importIndex(it->second.system->getIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1054,9 +1304,10 @@ bool CollectionSystemManager::includeFileInAutoCollections(FileData* file)
|
|||
|
||||
|
||||
bool CollectionSystemManager::needDoublePress(int presscount) {
|
||||
if (Settings::getInstance()->getBool("DoublePressRemovesFromFavs") && presscount < 2) {
|
||||
if (Settings::getInstance()->getBool("DoublePressRemovesFromFavs") && presscount < 2)
|
||||
{
|
||||
GuiInfoPopup* toast = new GuiInfoPopup(mWindow, "Press again to remove from '" + Utils::String::toUpper(mEditingCollection)
|
||||
+ "'", ISimpleGameListView::DOUBLE_PRESS_DETECTION_DURATION, 100, 200);
|
||||
+ "'", DOUBLE_PRESS_DETECTION_DURATION, 100, 200);
|
||||
mWindow->setInfoPopup(toast);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#define ES_APP_COLLECTION_SYSTEM_MANAGER_H
|
||||
|
||||
#include <map>
|
||||
#include <SDL_timer.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -10,15 +11,43 @@ class FileData;
|
|||
class SystemData;
|
||||
class Window;
|
||||
struct SystemEnvironmentData;
|
||||
class FileFilterIndex;
|
||||
|
||||
static const std::string CUSTOM_COLL_ID = "collections";
|
||||
static const std::string RANDOM_COLL_ID = "random";
|
||||
constexpr int LAST_PLAYED_MAX = 50;
|
||||
|
||||
constexpr int RANDOM_SYSTEM_MAX = 5;
|
||||
constexpr int DEFAULT_RANDOM_SYSTEM_GAMES = 1;
|
||||
constexpr int DEFAULT_RANDOM_COLLECTIONS_GAMES = 0;
|
||||
|
||||
enum CollectionSystemType
|
||||
{
|
||||
AUTO_ALL_GAMES,
|
||||
AUTO_LAST_PLAYED,
|
||||
AUTO_FAVORITES,
|
||||
AUTO_RANDOM,
|
||||
CUSTOM_COLLECTION
|
||||
};
|
||||
|
||||
// Flags when loading or creating a collection
|
||||
enum class CollectionFlags : uint8_t
|
||||
{
|
||||
NONE, // create only
|
||||
HOLD_IN_MAP, // create and keep in mAutoCollectionSystemsData or mCustomCollectionSystemsData
|
||||
NEEDS_SAVE // force save of newly added collection
|
||||
};
|
||||
|
||||
constexpr CollectionFlags operator|(CollectionFlags a,CollectionFlags b)
|
||||
{
|
||||
return static_cast<CollectionFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
}
|
||||
|
||||
constexpr CollectionFlags operator&(CollectionFlags a, CollectionFlags b)
|
||||
{
|
||||
return static_cast<CollectionFlags>(static_cast<uint8_t>(a) & static_cast<uint8_t>(b));
|
||||
}
|
||||
|
||||
struct CollectionSystemDecl
|
||||
{
|
||||
CollectionSystemType type; // type of system
|
||||
|
@ -47,7 +76,7 @@ public:
|
|||
static CollectionSystemManager* get();
|
||||
static void init(Window* window);
|
||||
static void deinit();
|
||||
void saveCustomCollection(SystemData* sys);
|
||||
bool saveCustomCollection(SystemData* sys);
|
||||
|
||||
void loadCollectionSystems(bool async=false);
|
||||
void loadEnabledListFromSettings();
|
||||
|
@ -56,28 +85,32 @@ public:
|
|||
void refreshCollectionSystems(FileData* file);
|
||||
void updateCollectionSystem(FileData* file, CollectionSystemData sysData);
|
||||
void deleteCollectionFiles(FileData* file);
|
||||
void recreateCollection(SystemData* sysData);
|
||||
|
||||
inline std::map<std::string, CollectionSystemData> getAutoCollectionSystems() { return mAutoCollectionSystemsData; };
|
||||
inline std::map<std::string, CollectionSystemData> getCustomCollectionSystems() { return mCustomCollectionSystemsData; };
|
||||
inline SystemData* getCustomCollectionsBundle() { return mCustomCollectionsBundle; };
|
||||
inline SystemData* getRandomCollection() { return mRandomCollection; };
|
||||
std::vector<std::string> getUnusedSystemsFromTheme();
|
||||
SystemData* addNewCustomCollection(std::string name);
|
||||
SystemData* addNewCustomCollection(std::string name, bool needsSave = false);
|
||||
|
||||
bool isThemeGenericCollectionCompatible(bool genericCustomCollections);
|
||||
bool isThemeCustomCollectionCompatible(std::vector<std::string> stringVector);
|
||||
std::string getValidNewCollectionName(std::string name, int index = 0);
|
||||
|
||||
void setEditMode(std::string collectionName);
|
||||
void exitEditMode();
|
||||
void setEditMode(std::string collectionName, bool quiet = false);
|
||||
void exitEditMode(bool quiet = false);
|
||||
inline bool isEditing() { return mIsEditingCustom; };
|
||||
inline std::string getEditingCollection() { return mEditingCollection; };
|
||||
bool toggleGameInCollection(FileData* file, int presscount);
|
||||
bool toggleGameInCollection(FileData* file);
|
||||
|
||||
SystemData* getSystemToView(SystemData* sys);
|
||||
void updateCollectionFolderMetadata(SystemData* sys);
|
||||
|
||||
SystemData* getAllGamesCollection();
|
||||
|
||||
void trimCollectionCount(FileData* rootFolder, int limit, bool shuffle);
|
||||
|
||||
private:
|
||||
static CollectionSystemManager* sInstance;
|
||||
SystemEnvironmentData* mCollectionEnvData;
|
||||
|
@ -88,15 +121,19 @@ private:
|
|||
bool mIsEditingCustom;
|
||||
std::string mEditingCollection;
|
||||
CollectionSystemData* mEditingCollectionSystemData;
|
||||
Uint32 mFirstPressMs = 0;
|
||||
|
||||
void initAutoCollectionSystems();
|
||||
void initCustomCollectionSystems();
|
||||
SystemData* createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index = true);
|
||||
SystemData* createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, const CollectionFlags flags);
|
||||
void populateAutoCollection(CollectionSystemData* sysData);
|
||||
void populateCustomCollection(CollectionSystemData* sysData);
|
||||
void addRandomGames(SystemData* newSys, SystemData* sourceSystem, FileData* rootFolder, FileFilterIndex* index,
|
||||
std::map<std::string, std::map<std::string, int>> mapsForRandomColl, int defaultValue);
|
||||
void populateRandomCollectionFromCollections(std::map<std::string, std::map<std::string, int>> mapsForRandomColl);
|
||||
|
||||
void removeCollectionsFromDisplayedSystems();
|
||||
void addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData);
|
||||
void addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData, bool processRandom);
|
||||
|
||||
std::vector<std::string> getSystemsFromConfig();
|
||||
std::vector<std::string> getSystemsFromTheme();
|
||||
|
@ -104,15 +141,17 @@ private:
|
|||
std::vector<std::string> getCollectionThemeFolders(bool custom);
|
||||
std::vector<std::string> getUserCollectionThemeFolders();
|
||||
|
||||
void trimCollectionCount(FileData* rootFolder, int limit);
|
||||
|
||||
bool themeFolderExists(std::string folder);
|
||||
|
||||
bool includeFileInAutoCollections(FileData* file);
|
||||
|
||||
bool needDoublePress(int presscount);
|
||||
int getPressCountInDuration();
|
||||
|
||||
SystemData* mCustomCollectionsBundle;
|
||||
SystemData* mRandomCollection;
|
||||
|
||||
static const int DOUBLE_PRESS_DETECTION_DURATION = 1500; // millis
|
||||
};
|
||||
|
||||
std::string getCustomCollectionConfigPath(std::string collectionName);
|
||||
|
|
|
@ -250,21 +250,30 @@ void FileData::removeChild(FileData* file)
|
|||
|
||||
void FileData::sort(ComparisonFunction& comparator, bool ascending)
|
||||
{
|
||||
std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
|
||||
|
||||
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
|
||||
if (ascending)
|
||||
{
|
||||
if((*it)->getChildren().size() > 0)
|
||||
(*it)->sort(comparator, ascending);
|
||||
std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
|
||||
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
|
||||
{
|
||||
if((*it)->getChildren().size() > 0)
|
||||
(*it)->sort(comparator, ascending);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stable_sort(mChildren.rbegin(), mChildren.rend(), comparator);
|
||||
for(auto it = mChildren.rbegin(); it != mChildren.rend(); it++)
|
||||
{
|
||||
if((*it)->getChildren().size() > 0)
|
||||
(*it)->sort(comparator, ascending);
|
||||
}
|
||||
}
|
||||
|
||||
if(!ascending)
|
||||
std::reverse(mChildren.begin(), mChildren.end());
|
||||
}
|
||||
|
||||
void FileData::sort(const SortType& type)
|
||||
{
|
||||
sort(*type.comparisonFunction, type.ascending);
|
||||
mSortDesc = type.description;
|
||||
}
|
||||
|
||||
void FileData::launchGame(Window* window)
|
||||
|
@ -377,6 +386,6 @@ FileData::SortType getSortTypeFromString(std::string desc) {
|
|||
return sort;
|
||||
}
|
||||
}
|
||||
// if not found default to name, ascending
|
||||
// if not found default to "name, ascending"
|
||||
return FileSorts::SortTypes.at(0);
|
||||
}
|
||||
|
|
|
@ -86,8 +86,8 @@ public:
|
|||
: comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {}
|
||||
};
|
||||
|
||||
void sort(ComparisonFunction& comparator, bool ascending = true);
|
||||
void sort(const SortType& type);
|
||||
std::string getSortDescription() { return mSortDesc; }
|
||||
MetaDataList metadata;
|
||||
|
||||
protected:
|
||||
|
@ -96,6 +96,7 @@ protected:
|
|||
std::string mSystemName;
|
||||
|
||||
private:
|
||||
void sort(ComparisonFunction& comparator, bool ascending = true);
|
||||
FileType mType;
|
||||
std::string mPath;
|
||||
SystemEnvironmentData* mEnvData;
|
||||
|
@ -103,6 +104,7 @@ private:
|
|||
std::unordered_map<std::string,FileData*> mChildrenByFilename;
|
||||
std::vector<FileData*> mChildren;
|
||||
std::vector<FileData*> mFilteredChildren;
|
||||
std::string mSortDesc;
|
||||
};
|
||||
|
||||
class CollectionFileData : public FileData
|
||||
|
|
|
@ -171,6 +171,9 @@ std::string FileFilterIndex::getIndexableKey(FileData* game, FilterIndexType typ
|
|||
key = Utils::String::toUpper(game->metadata.get("kidgame"));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(LogWarning) << "Unknown Filter type:" << type;
|
||||
break;
|
||||
}
|
||||
key = Utils::String::trim(key);
|
||||
if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) {
|
||||
|
@ -534,4 +537,4 @@ void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index, std::s
|
|||
void FileFilterIndex::clearIndex(std::map<std::string, int> indexMap)
|
||||
{
|
||||
indexMap.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ namespace FileSorts
|
|||
{
|
||||
|
||||
const FileData::SortType typesArr[] = {
|
||||
FileData::SortType(&compareName, true, "filename, ascending"),
|
||||
FileData::SortType(&compareName, false, "filename, descending"),
|
||||
FileData::SortType(&compareName, true, "name, ascending"),
|
||||
FileData::SortType(&compareName, false, "name, descending"),
|
||||
|
||||
FileData::SortType(&compareRating, true, "rating, ascending"),
|
||||
FileData::SortType(&compareRating, false, "rating, descending"),
|
||||
|
|
|
@ -8,15 +8,16 @@
|
|||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemData.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
|
||||
{
|
||||
// first, verify that path is within the system's root folder
|
||||
FileData* root = system->getRootFolder();
|
||||
bool contains = false;
|
||||
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains, true);
|
||||
const std::string systemPath = root->getPath();
|
||||
|
||||
// first, verify that path is within the system's root folder
|
||||
std::string relative = Utils::FileSystem::removeCommonPath(path, systemPath, contains, true);
|
||||
if(!contains)
|
||||
{
|
||||
LOG(LogError) << "File path \"" << path << "\" is outside system path \"" << system->getStartPath() << "\"";
|
||||
|
@ -24,17 +25,21 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
|
|||
}
|
||||
|
||||
Utils::FileSystem::stringList pathList = Utils::FileSystem::getPathList(relative);
|
||||
|
||||
auto path_it = pathList.begin();
|
||||
FileData* treeNode = root;
|
||||
bool found = false;
|
||||
|
||||
// iterate over all subpaths below the provided path
|
||||
while(path_it != pathList.end())
|
||||
{
|
||||
const std::unordered_map<std::string, FileData*>& children = treeNode->getChildrenByFilename();
|
||||
|
||||
std::string key = *path_it;
|
||||
found = children.find(key) != children.cend();
|
||||
std::string pathSegment = *path_it;
|
||||
auto candidate = children.find(pathSegment);
|
||||
found = candidate != children.cend();
|
||||
if (found) {
|
||||
treeNode = children.at(key);
|
||||
treeNode = candidate->second;
|
||||
}
|
||||
|
||||
// this is the end
|
||||
|
@ -51,8 +56,12 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
|
|||
|
||||
FileData* file = new FileData(type, path, system->getSystemEnvData(), system);
|
||||
|
||||
// skipping arcade assets from gamelist
|
||||
if(!file->isArcadeAsset())
|
||||
// skipping arcade assets from gamelist and add only to filesystem
|
||||
// (fs) folders, i.e. entriess in gamelist with <folder/> and not to
|
||||
// fs-folders which are marked as <game/> in gamelist. NB:
|
||||
// treeNode's type (=parent) is determined by the element in the
|
||||
// gamelist and not by the fs-type.
|
||||
if(!file->isArcadeAsset() && treeNode->getType() == FOLDER)
|
||||
{
|
||||
treeNode->addChild(file);
|
||||
}
|
||||
|
@ -65,12 +74,23 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
|
|||
// if type is a folder it's gonna be empty, so don't bother
|
||||
if(type == FOLDER)
|
||||
{
|
||||
LOG(LogWarning) << "gameList: folder doesn't already exist, won't create";
|
||||
std::string absFolder = Utils::FileSystem::getAbsolutePath(pathSegment, systemPath);
|
||||
LOG(LogWarning) << "gameList: folder " << absFolder << " absent on fs, no FileData object created. Do remove leftover in gamelist.xml to remediate this warning.";
|
||||
return NULL;
|
||||
}
|
||||
// discard constellations like scummvm/game.svm/game.svm as
|
||||
// scummvm/game.svm/ is a GAME and not a FOLDER
|
||||
if (treeNode->getType() == GAME)
|
||||
{
|
||||
std::string absFolder = Utils::FileSystem::getAbsolutePath(pathSegment, systemPath);
|
||||
LOG(LogWarning) << "gameList: trying to add game '" << absFolder << "' to a parent <game/> entry is invalid, no FileData object created. Do remove nested <game/> in gamelist.xml to remediate this warning.";
|
||||
return NULL;
|
||||
}
|
||||
// create folder filedata object
|
||||
std::string absPath = Utils::FileSystem::resolveRelativePath(treeNode->getPath() + "/" + pathSegment, systemPath, false, true);
|
||||
FileData* folder = new FileData(FOLDER, absPath, system->getSystemEnvData(), system);
|
||||
LOG(LogDebug) << "folder not found as FileData, adding: " << folder->getPath();
|
||||
|
||||
// create missing folder
|
||||
FileData* folder = new FileData(FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it, system->getSystemEnvData(), system);
|
||||
treeNode->addChild(folder);
|
||||
treeNode = folder;
|
||||
}
|
||||
|
@ -85,6 +105,7 @@ void parseGamelist(SystemData* system)
|
|||
{
|
||||
bool trustGamelist = Settings::getInstance()->getBool("ParseGamelistOnly");
|
||||
std::string xmlpath = system->getGamelistPath(false);
|
||||
const std::vector<std::string> allowedExtensions = system->getExtensions();
|
||||
|
||||
if(!Utils::FileSystem::exists(xmlpath))
|
||||
return;
|
||||
|
@ -117,7 +138,8 @@ void parseGamelist(SystemData* system)
|
|||
FileType type = typeList[i];
|
||||
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
|
||||
{
|
||||
const std::string path = Utils::FileSystem::resolveRelativePath(fileNode.child("path").text().get(), relativeTo, false, true);
|
||||
std::string path = fileNode.child("path").text().get();
|
||||
path = Utils::FileSystem::resolveRelativePath(path, relativeTo, false, true);
|
||||
|
||||
if(!trustGamelist && !Utils::FileSystem::exists(path))
|
||||
{
|
||||
|
@ -125,6 +147,13 @@ void parseGamelist(SystemData* system)
|
|||
continue;
|
||||
}
|
||||
|
||||
// Check whether the file's extension is allowed in the system
|
||||
if (i == 0 /*game*/ && std::find(allowedExtensions.cbegin(), allowedExtensions.cend(), Utils::FileSystem::getExtension(path)) == allowedExtensions.cend())
|
||||
{
|
||||
LOG(LogDebug) << "file " << path << " found in gamelist, but has unregistered extension";
|
||||
continue;
|
||||
}
|
||||
|
||||
FileData* file = findOrCreateFile(system, path, type);
|
||||
if(!file)
|
||||
{
|
||||
|
@ -134,7 +163,7 @@ void parseGamelist(SystemData* system)
|
|||
else if(!file->isArcadeAsset())
|
||||
{
|
||||
std::string defaultName = file->metadata.get("name");
|
||||
file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo);
|
||||
file->metadata = MetaDataList::createFromXML(file->getType() == GAME ? GAME_METADATA : FOLDER_METADATA, fileNode, relativeTo);
|
||||
|
||||
//make sure name gets set if one didn't exist
|
||||
if(file->metadata.get("name").empty())
|
||||
|
@ -165,7 +194,8 @@ void addFileDataNode(pugi::xml_node& parent, const FileData* file, const char* t
|
|||
//there's something useful in there so we'll keep the node, add the path
|
||||
|
||||
// try and make the path relative if we can so things still work if we change the rom folder location in the future
|
||||
newNode.prepend_child("path").text().set(Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false, true).c_str());
|
||||
std::string relPath = Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false, true);
|
||||
newNode.prepend_child("path").text().set(relPath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,9 +246,8 @@ void updateGamelist(SystemData* system)
|
|||
{
|
||||
int numUpdated = 0;
|
||||
|
||||
//get only files, no folders
|
||||
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
|
||||
|
||||
|
||||
// Stage 1: iterate through all files in memory, checking for changes
|
||||
for(std::vector<FileData*>::const_iterator fit = files.cbegin(); fit != files.cend(); ++fit)
|
||||
{
|
||||
|
@ -226,13 +255,13 @@ void updateGamelist(SystemData* system)
|
|||
// do not touch if it wasn't changed anyway
|
||||
if (!(*fit)->metadata.wasChanged())
|
||||
continue;
|
||||
|
||||
|
||||
// adding item to changed list
|
||||
if ((*fit)->getType() == GAME)
|
||||
if ((*fit)->getType() == GAME)
|
||||
{
|
||||
changedGames.push_back((*fit));
|
||||
changedGames.push_back((*fit));
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
changedFolders.push_back((*fit));
|
||||
}
|
||||
|
@ -243,13 +272,13 @@ void updateGamelist(SystemData* system)
|
|||
const char* tagList[2] = { "game", "folder" };
|
||||
FileType typeList[2] = { GAME, FOLDER };
|
||||
std::vector<FileData*> changedList[2] = { changedGames, changedFolders };
|
||||
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
const char* tag = tagList[i];
|
||||
std::vector<FileData*> changes = changedList[i];
|
||||
|
||||
// if changed items of this type
|
||||
// check for changed items of this type
|
||||
if (changes.size() > 0) {
|
||||
// check if the item already exists in the XML
|
||||
// if it does, remove all corresponding items before adding
|
||||
|
@ -266,9 +295,10 @@ void updateGamelist(SystemData* system)
|
|||
continue;
|
||||
}
|
||||
|
||||
std::string xmlpath = pathNode.text().get();
|
||||
// apply the same transformation as in Gamelist::parseGamelist
|
||||
std::string xmlpath = Utils::FileSystem::resolveRelativePath(pathNode.text().get(), relativeTo, false, true);
|
||||
|
||||
xmlpath = Utils::FileSystem::resolveRelativePath(xmlpath, relativeTo, false, true);
|
||||
|
||||
for(std::vector<FileData*>::const_iterator cfit = changes.cbegin(); cfit != changes.cend(); ++cfit)
|
||||
{
|
||||
if(xmlpath == (*cfit)->getPath())
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#include "MetaData.h"
|
||||
|
||||
#include "utils/FileSystemUtil.h"
|
||||
#include "utils/TimeUtil.h"
|
||||
#include "Log.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
MetaDataDecl gameDecls[] = {
|
||||
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
|
||||
|
@ -13,7 +14,7 @@ MetaDataDecl gameDecls[] = {
|
|||
{"video", MD_PATH , "", false, "video", "enter path to video"},
|
||||
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
|
||||
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
|
||||
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
|
||||
{"rating", MD_RATING, "0", false, "rating", "enter rating"},
|
||||
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
||||
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
||||
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
|
||||
|
@ -27,6 +28,12 @@ MetaDataDecl gameDecls[] = {
|
|||
};
|
||||
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0]));
|
||||
|
||||
const inline std::string blankDate() {
|
||||
// blank date (1970-01-02) is used to render "" (see DateTimeComponent.cpp) for
|
||||
// folder metadata when no date is provided (=default case)
|
||||
return Utils::Time::timeToString(Utils::Time::BLANK_DATE, "%Y%m%d");
|
||||
}
|
||||
|
||||
MetaDataDecl folderDecls[] = {
|
||||
{"name", MD_STRING, "", false, "name", "enter game name"},
|
||||
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
|
||||
|
@ -35,12 +42,12 @@ MetaDataDecl folderDecls[] = {
|
|||
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
|
||||
{"video", MD_PATH, "", false, "video", "enter path to video"},
|
||||
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
|
||||
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
|
||||
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
|
||||
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
|
||||
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
|
||||
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
|
||||
{"players", MD_INT, "1", false, "players", "enter number of players"}
|
||||
{"rating", MD_RATING, "0", false, "rating", "enter rating"},
|
||||
{"releasedate", MD_DATE, blankDate(), true, "release date", "enter release date"},
|
||||
{"developer", MD_STRING, "", false, "developer", "enter game developer"},
|
||||
{"publisher", MD_STRING, "", false, "publisher", "enter game publisher"},
|
||||
{"genre", MD_STRING, "", false, "genre", "enter game genre"},
|
||||
{"players", MD_INT, "", false, "players", "enter number of players"}
|
||||
};
|
||||
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ struct MetaDataDecl
|
|||
std::string key;
|
||||
MetaDataType type;
|
||||
std::string defaultValue;
|
||||
bool isStatistic; //if true, ignore scraper values for this metadata
|
||||
bool isStatistic; // if true: ignore in scraping and hide in metadata edits for this key
|
||||
std::string displayName; // displayed as this in editors
|
||||
std::string displayPrompt; // phrase displayed in editors when prompted to enter value (currently only for strings)
|
||||
};
|
||||
|
|
|
@ -92,6 +92,7 @@ namespace PlatformIds
|
|||
"ti99",
|
||||
"dragon32",
|
||||
"zmachine",
|
||||
"fmtowns",
|
||||
|
||||
"ignore", // do not allow scraping for this system
|
||||
"invalid"
|
||||
|
|
|
@ -93,6 +93,7 @@ namespace PlatformIds
|
|||
TI_99,
|
||||
DRAGON32,
|
||||
ZMACHINE,
|
||||
FMTOWNS,
|
||||
|
||||
PLATFORM_IGNORE, // do not allow scraping for this system
|
||||
PLATFORM_COUNT
|
||||
|
|
|
@ -105,7 +105,7 @@ void SystemData::populateFolder(FileData* folder)
|
|||
|
||||
//this is a little complicated because we allow a list of extensions to be defined (delimited with a space)
|
||||
//we first get the extension of the file itself:
|
||||
extension = Utils::String::toLower(Utils::FileSystem::getExtension(filePath));
|
||||
extension = Utils::FileSystem::getExtension(filePath);
|
||||
|
||||
//fyi, folders *can* also match the extension and be added as games - this is mostly just to support higan
|
||||
//see issue #75: https://github.com/Aloshi/EmulationStation/issues/75
|
||||
|
@ -148,6 +148,9 @@ void SystemData::indexAllGameFilters(const FileData* folder)
|
|||
{
|
||||
case GAME: { mFilterIndex->addToIndex(*it); } break;
|
||||
case FOLDER: { indexAllGameFilters(*it); } break;
|
||||
default:
|
||||
LOG(LogInfo) << "Unknown type: " << (*it)->getType();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +187,7 @@ SystemData* SystemData::loadSystem(pugi::xml_node system)
|
|||
|
||||
for (auto extension = list.cbegin(); extension != list.cend(); extension++)
|
||||
{
|
||||
std::string xt = Utils::String::toLower(*extension);
|
||||
std::string xt = std::string(*extension);
|
||||
if (std::find(extensions.begin(), extensions.end(), xt) == extensions.end())
|
||||
extensions.push_back(xt);
|
||||
}
|
||||
|
@ -210,7 +213,9 @@ SystemData* SystemData::loadSystem(pugi::xml_node system)
|
|||
|
||||
// if there appears to be an actual platform ID supplied but it didn't match the list, warn
|
||||
if (str != NULL && str[0] != '\0' && platformId == PlatformIds::PLATFORM_UNKNOWN)
|
||||
{
|
||||
LOG(LogWarning) << " Unknown platform for system \"" << name << "\" (platform \"" << str << "\" from list \"" << platformList << "\")";
|
||||
}
|
||||
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
|
||||
platformIds.push_back(platformId);
|
||||
}
|
||||
|
@ -552,6 +557,11 @@ SystemData* SystemData::getRandomSystem()
|
|||
return random_system;
|
||||
}
|
||||
|
||||
void SystemData::setShuffledCacheDirty()
|
||||
{
|
||||
mGamesShuffled.clear();
|
||||
}
|
||||
|
||||
FileData* SystemData::getRandomGame()
|
||||
{
|
||||
if (mGamesShuffled.empty())
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
class FileData;
|
||||
class FileFilterIndex;
|
||||
|
@ -76,6 +76,7 @@ public:
|
|||
|
||||
FileFilterIndex* getIndex() { return mFilterIndex; };
|
||||
void onMetaDataSavePoint();
|
||||
void setShuffledCacheDirty();
|
||||
|
||||
private:
|
||||
static SystemData* loadSystem(pugi::xml_node system);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "SystemScreenSaver.h"
|
||||
#include "components/TextListComponent.h"
|
||||
|
||||
#ifdef _OMX_
|
||||
#include "components/VideoPlayerComponent.h"
|
||||
|
@ -7,6 +8,7 @@
|
|||
#include "CollectionSystemManager.h"
|
||||
#include "utils/FileSystemUtil.h"
|
||||
#include "views/gamelist/IGameListView.h"
|
||||
#include "views/UIModeController.h"
|
||||
#include "views/ViewController.h"
|
||||
#include "FileData.h"
|
||||
#include "FileFilterIndex.h"
|
||||
|
@ -33,8 +35,11 @@ SystemScreenSaver::SystemScreenSaver(Window* window) :
|
|||
mOpacity(0.0f),
|
||||
mTimer(0),
|
||||
mCurrentGame(NULL),
|
||||
mStopBackgroundAudio(true)
|
||||
mPreviousGame(NULL),
|
||||
mStopBackgroundAudio(true),
|
||||
mSystem(NULL)
|
||||
{
|
||||
remove(getTitlePath().c_str());
|
||||
mWindow->setScreenSaver(this);
|
||||
std::string path = getTitleFolder();
|
||||
if(!Utils::FileSystem::exists(path))
|
||||
|
@ -62,6 +67,58 @@ bool SystemScreenSaver::isScreenSaverActive()
|
|||
return (mState != STATE_INACTIVE);
|
||||
}
|
||||
|
||||
bool SystemScreenSaver::inputDuringScreensaver(InputConfig* config, Input input)
|
||||
{
|
||||
bool input_consumed = false;
|
||||
std::string screensaver_type = Settings::getInstance()->getString("ScreenSaverBehavior");
|
||||
bool is_media_screensaver = screensaver_type == "random video" || screensaver_type == "slideshow";
|
||||
|
||||
if (!mWindow->isSleeping() && is_media_screensaver)
|
||||
{
|
||||
// catch any valid screensaver or invalid inputs here to prevent screensaver from stopping
|
||||
input_consumed = input_consumed || (config->getMappedTo(input).size() == 0);
|
||||
|
||||
bool is_next_input = config->isMappedLike("right", input) || config->isMappedTo("select", input);
|
||||
bool is_previous_input = config->isMappedLike("left", input);
|
||||
bool is_favorite_input = config->isMappedLike("y", input);
|
||||
bool is_start_input = config->isMappedTo("start", input);
|
||||
bool is_select_game_input = config->isMappedTo("a", input);
|
||||
bool use_gamelistmedia = screensaver_type == "random video" || !Settings::getInstance()->getBool("SlideshowScreenSaverCustomMediaSource");
|
||||
|
||||
// catch any valid screensaver or invalid inputs here to prevent screensaver from stopping
|
||||
input_consumed = input_consumed || (config->getMappedTo(input).size() == 0);
|
||||
|
||||
if (input.value != 0)
|
||||
{
|
||||
if (is_next_input)
|
||||
{
|
||||
changeMediaItem();
|
||||
input_consumed = true;
|
||||
}
|
||||
else if (use_gamelistmedia)
|
||||
{
|
||||
if (is_previous_input && mPreviousGame)
|
||||
{
|
||||
changeMediaItem(false);
|
||||
input_consumed = true;
|
||||
}
|
||||
else if (is_start_input || is_select_game_input)
|
||||
{
|
||||
selectGame(is_start_input);
|
||||
input_consumed = false;
|
||||
}
|
||||
else if (is_favorite_input && !UIModeController::getInstance()->isUIModeKid())
|
||||
{
|
||||
assert(mCurrentGame != NULL);
|
||||
CollectionSystemManager::get()->toggleGameInCollection(mCurrentGame);
|
||||
input_consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return input_consumed;
|
||||
}
|
||||
|
||||
void SystemScreenSaver::setVideoScreensaver(std::string& path)
|
||||
{
|
||||
#ifdef _OMX_
|
||||
|
@ -89,9 +146,10 @@ void SystemScreenSaver::setVideoScreensaver(std::string& path)
|
|||
mVideoScreensaver->setVideo(path);
|
||||
mVideoScreensaver->setScreensaverMode(true);
|
||||
mVideoScreensaver->onShow();
|
||||
|
||||
handleScreenSaverEditingCollection();
|
||||
PowerSaver::runningScreenSaver(true);
|
||||
mTimer = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
void SystemScreenSaver::setImageScreensaver(std::string& path)
|
||||
|
@ -101,8 +159,6 @@ void SystemScreenSaver::setImageScreensaver(std::string& path)
|
|||
mImageScreensaver = new ImageComponent(mWindow, false, false);
|
||||
}
|
||||
|
||||
mTimer = 0;
|
||||
|
||||
mImageScreensaver->setImage(path);
|
||||
mImageScreensaver->setOrigin(0.5f, 0.5f);
|
||||
mImageScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f);
|
||||
|
@ -115,6 +171,10 @@ void SystemScreenSaver::setImageScreensaver(std::string& path)
|
|||
{
|
||||
mImageScreensaver->setMaxSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
|
||||
}
|
||||
|
||||
handleScreenSaverEditingCollection();
|
||||
PowerSaver::runningScreenSaver(true);
|
||||
mTimer = 0;
|
||||
}
|
||||
|
||||
bool SystemScreenSaver::isFileVideo(std::string& path)
|
||||
|
@ -127,8 +187,34 @@ bool SystemScreenSaver::isFileVideo(std::string& path)
|
|||
return pathFilter.find(pathExtension) != std::string::npos;
|
||||
}
|
||||
|
||||
void SystemScreenSaver::startScreenSaver()
|
||||
void SystemScreenSaver::handleScreenSaverEditingCollection()
|
||||
{
|
||||
std::string screensaverCollection = Settings::getInstance()->getString("DefaultScreenSaverCollection");
|
||||
std::string currentEditingCollection = CollectionSystemManager::get()->getEditingCollection();
|
||||
|
||||
// check if we need to change the screensaver collection
|
||||
if (screensaverCollection != "") {
|
||||
// check if we're starting the screensaver
|
||||
if (isScreenSaverActive())
|
||||
{
|
||||
// we're entering the screensaver, backup the currently actively editing collection
|
||||
mRegularEditingCollection = CollectionSystemManager::get()->getEditingCollection();
|
||||
CollectionSystemManager::get()->setEditMode(screensaverCollection, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're exiting the screensaver, restore the currently actively editing collection
|
||||
CollectionSystemManager::get()->exitEditMode(true);
|
||||
if (mRegularEditingCollection != "Favorites" && mRegularEditingCollection != "")
|
||||
CollectionSystemManager::get()->setEditMode(mRegularEditingCollection, true);
|
||||
mRegularEditingCollection = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SystemScreenSaver::startScreenSaver(SystemData* system)
|
||||
{
|
||||
mSystem = system;
|
||||
// if set to index files in background, start thread
|
||||
if (Settings::getInstance()->getBool("BackgroundIndexing"))
|
||||
{
|
||||
|
@ -148,7 +234,7 @@ void SystemScreenSaver::startScreenSaver()
|
|||
|
||||
// Load a random video
|
||||
std::string path = "";
|
||||
pickRandomVideo(path);
|
||||
pickRandomVideo(path, mCurrentGame != NULL);
|
||||
|
||||
int retry = 200;
|
||||
while(retry > 0 && ((path.empty() || !Utils::FileSystem::exists(path)) || mCurrentGame == NULL))
|
||||
|
@ -160,7 +246,7 @@ void SystemScreenSaver::startScreenSaver()
|
|||
if (!path.empty() && Utils::FileSystem::exists(path))
|
||||
{
|
||||
setVideoScreensaver(path);
|
||||
if (mCurrentGame != NULL)
|
||||
if (mCurrentGame != NULL)
|
||||
{
|
||||
Scripting::fireEvent("screensaver-game-select", mCurrentGame->getSystem()->getName(), mCurrentGame->getPath(), mCurrentGame->getName(), "randomvideo");
|
||||
}
|
||||
|
@ -186,33 +272,24 @@ void SystemScreenSaver::startScreenSaver()
|
|||
}
|
||||
else
|
||||
{
|
||||
pickRandomGameListImage(path);
|
||||
pickRandomGameListImage(path, mCurrentGame != NULL);
|
||||
}
|
||||
|
||||
if (isFileVideo(path))
|
||||
{
|
||||
setVideoScreensaver(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
setImageScreensaver(path);
|
||||
}
|
||||
|
||||
std::string bg_audio_file = Settings::getInstance()->getString("SlideshowScreenSaverBackgroundAudioFile");
|
||||
if ((!mBackgroundAudio) && (bg_audio_file != ""))
|
||||
if (!mBackgroundAudio && bg_audio_file != "" && Utils::FileSystem::exists(bg_audio_file))
|
||||
{
|
||||
if (Utils::FileSystem::exists(bg_audio_file))
|
||||
{
|
||||
// paused PS so that the background audio keeps playing
|
||||
PowerSaver::pause();
|
||||
mBackgroundAudio = Sound::get(bg_audio_file);
|
||||
mBackgroundAudio->play();
|
||||
}
|
||||
// paused PS so that the background audio keeps playing
|
||||
PowerSaver::pause();
|
||||
mBackgroundAudio = Sound::get(bg_audio_file);
|
||||
mBackgroundAudio->play();
|
||||
}
|
||||
|
||||
PowerSaver::runningScreenSaver(true);
|
||||
mTimer = 0;
|
||||
if (mCurrentGame != NULL)
|
||||
if (mCurrentGame != NULL)
|
||||
{
|
||||
Scripting::fireEvent("screensaver-game-select", mCurrentGame->getSystem()->getName(), mCurrentGame->getFileName(), mCurrentGame->getName(), "slideshow");
|
||||
}
|
||||
|
@ -223,8 +300,9 @@ void SystemScreenSaver::startScreenSaver()
|
|||
mCurrentGame = NULL;
|
||||
}
|
||||
|
||||
void SystemScreenSaver::stopScreenSaver()
|
||||
void SystemScreenSaver::stopScreenSaver(bool toResume)
|
||||
{
|
||||
remove(getTitlePath().c_str());
|
||||
if ((mBackgroundAudio) && (mStopBackgroundAudio))
|
||||
{
|
||||
mBackgroundAudio->stop();
|
||||
|
@ -241,27 +319,36 @@ void SystemScreenSaver::stopScreenSaver()
|
|||
delete mImageScreensaver;
|
||||
mImageScreensaver = NULL;
|
||||
|
||||
// Exit the indexing thread
|
||||
if (Settings::getInstance()->getBool("BackgroundIndexing"))
|
||||
if (!toResume) {
|
||||
// if we're not changing videos or images, let's delete the random list
|
||||
// and all the scrensaver session-related variables
|
||||
mCurrentGame = NULL;
|
||||
mPreviousGame = NULL;
|
||||
mAllFiles.clear();
|
||||
mSystem = NULL;
|
||||
}
|
||||
|
||||
// Exit the indexing thread in case it's running. Check if thread still exists.
|
||||
if (Settings::getInstance()->getBool("BackgroundIndexing") && mThread)
|
||||
{
|
||||
mExit = true;
|
||||
mThread->join();
|
||||
delete mThread;
|
||||
mThread = NULL;
|
||||
}
|
||||
|
||||
// we need this to loop through different videos
|
||||
mState = STATE_INACTIVE;
|
||||
handleScreenSaverEditingCollection();
|
||||
PowerSaver::runningScreenSaver(false);
|
||||
}
|
||||
|
||||
void SystemScreenSaver::renderScreenSaver()
|
||||
{
|
||||
std::string screensaver_behavior = Settings::getInstance()->getString("ScreenSaverBehavior");
|
||||
if (mVideoScreensaver && screensaver_behavior == "random video")
|
||||
if (mVideoScreensaver && (screensaver_behavior == "random video" || screensaver_behavior == "slideshow"))
|
||||
{
|
||||
// Render black background
|
||||
Renderer::setMatrix(Transform4x4f::Identity());
|
||||
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
|
||||
setBackground();
|
||||
|
||||
// Only render the video if the state requires it
|
||||
if ((int)mState >= STATE_FADE_IN_VIDEO)
|
||||
|
@ -269,32 +356,31 @@ void SystemScreenSaver::renderScreenSaver()
|
|||
Transform4x4f transform = Transform4x4f::Identity();
|
||||
mVideoScreensaver->render(transform);
|
||||
}
|
||||
|
||||
// Check if slideshow then loop background music
|
||||
if (screensaver_behavior == "slideshow" && mBackgroundAudio && !mBackgroundAudio->isPlaying())
|
||||
{
|
||||
mBackgroundAudio->play();
|
||||
}
|
||||
|
||||
}
|
||||
else if (mImageScreensaver && screensaver_behavior == "slideshow")
|
||||
{
|
||||
// Render black background
|
||||
Renderer::setMatrix(Transform4x4f::Identity());
|
||||
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
|
||||
setBackground();
|
||||
|
||||
// Only render the image if the state requires it
|
||||
if ((int)mState >= STATE_FADE_IN_VIDEO)
|
||||
if ((int)mState >= STATE_FADE_IN_VIDEO && mImageScreensaver->hasImage())
|
||||
{
|
||||
if (mImageScreensaver->hasImage())
|
||||
{
|
||||
mImageScreensaver->setOpacity(255- (unsigned char) (mOpacity * 255));
|
||||
mImageScreensaver->setOpacity(255- (unsigned char) (mOpacity * 255));
|
||||
|
||||
Transform4x4f transform = Transform4x4f::Identity();
|
||||
mImageScreensaver->render(transform);
|
||||
}
|
||||
Transform4x4f transform = Transform4x4f::Identity();
|
||||
mImageScreensaver->render(transform);
|
||||
}
|
||||
|
||||
// Check if we need to restart the background audio
|
||||
if ((mBackgroundAudio) && (Settings::getInstance()->getString("SlideshowScreenSaverBackgroundAudioFile") != ""))
|
||||
if (mBackgroundAudio && !mBackgroundAudio->isPlaying())
|
||||
{
|
||||
if (!mBackgroundAudio->isPlaying())
|
||||
{
|
||||
mBackgroundAudio->play();
|
||||
}
|
||||
mBackgroundAudio->play();
|
||||
}
|
||||
}
|
||||
else if (mState != STATE_INACTIVE)
|
||||
|
@ -314,7 +400,7 @@ void SystemScreenSaver::backgroundIndexing()
|
|||
std::vector<FileData*> files = all->getRootFolder()->getFilesRecursive(GAME);
|
||||
|
||||
const auto startTs = std::chrono::system_clock::now();
|
||||
for (lastIndex; lastIndex < files.size(); lastIndex++)
|
||||
for ( ; lastIndex < files.size(); lastIndex++)
|
||||
{
|
||||
if(mExit)
|
||||
break;
|
||||
|
@ -327,10 +413,15 @@ void SystemScreenSaver::backgroundIndexing()
|
|||
LOG(LogDebug) << "Indexed a total of " << lastIndex << " entries in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms. Stopping.";
|
||||
}
|
||||
|
||||
void SystemScreenSaver::getAllGamelistNodesForSystem(SystemData* system) {
|
||||
std::vector<FileData*> subsysFiles {};
|
||||
FileData* rootFileData = system->getRootFolder();
|
||||
subsysFiles = rootFileData->getFilesRecursive(FileType::GAME, true);
|
||||
mAllFiles.insert(mAllFiles.end(), subsysFiles.begin(), subsysFiles.end());
|
||||
}
|
||||
|
||||
std::vector<FileData*> SystemScreenSaver::getAllGamelistNodes()
|
||||
void SystemScreenSaver::getAllGamelistNodes()
|
||||
{
|
||||
std::vector<FileData*> allFiles {};
|
||||
std::vector<FileData*> subsysFiles {};
|
||||
for (std::vector<SystemData*>::const_iterator it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); ++it)
|
||||
{
|
||||
|
@ -338,65 +429,75 @@ std::vector<FileData*> SystemScreenSaver::getAllGamelistNodes()
|
|||
if (!(*it)->isGameSystem() || (*it)->isCollection())
|
||||
continue;
|
||||
|
||||
FileData* rootFileData = (*it)->getRootFolder();
|
||||
subsysFiles = rootFileData->getFilesRecursive(FileType::GAME, true);
|
||||
allFiles.insert(allFiles.end(), subsysFiles.begin(), subsysFiles.end());
|
||||
getAllGamelistNodesForSystem(*it);
|
||||
}
|
||||
|
||||
return allFiles;
|
||||
}
|
||||
|
||||
|
||||
void SystemScreenSaver::pickGameListNode(const char *nodeName, std::string& path)
|
||||
void SystemScreenSaver::pickGameListNode(const char *nodeName)
|
||||
{
|
||||
FileData *itf = nullptr;
|
||||
bool found = false;
|
||||
int missCtr = 0;
|
||||
while (!found) {
|
||||
if (mAllFiles.empty()) {
|
||||
mAllFiles = getAllGamelistNodes();
|
||||
while (!found)
|
||||
{
|
||||
if (mAllFiles.empty())
|
||||
{
|
||||
if (mSystem)
|
||||
getAllGamelistNodesForSystem(mSystem);
|
||||
else
|
||||
getAllGamelistNodes();
|
||||
|
||||
if (mAllFiles.empty()) { return; } // no games at all
|
||||
mAllFilesSize = mAllFiles.size();
|
||||
std::shuffle(std::begin(mAllFiles), std::end(mAllFiles), SystemData::sURNG);
|
||||
}
|
||||
|
||||
itf = mAllFiles.back();
|
||||
mAllFiles.pop_back();
|
||||
if ((strcmp(nodeName, "video") == 0 && itf->getVideoPath() != "") ||
|
||||
(strcmp(nodeName, "image") == 0 && itf->getImagePath() != ""))
|
||||
{
|
||||
found = true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
missCtr++;
|
||||
if (missCtr == mAllFilesSize) {
|
||||
if (missCtr == mAllFilesSize)
|
||||
// avoid looping forever when no candidate exist
|
||||
// with image/video path set
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
path = (strcmp(nodeName, "video") == 0) ? itf->getVideoPath() : itf->getImagePath();
|
||||
mCurrentGame = itf;
|
||||
}
|
||||
|
||||
if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never")
|
||||
{
|
||||
auto systemName = mCurrentGame->getSystem()->getFullName();
|
||||
writeSubtitle(mCurrentGame->getName().c_str(), systemName.c_str(),
|
||||
(Settings::getInstance()->getString("ScreenSaverGameInfo") == "always"));
|
||||
void SystemScreenSaver::prepareScreenSaverMedia(const char *nodeName, std::string& path)
|
||||
{
|
||||
if (mCurrentGame) {
|
||||
path = (strcmp(nodeName, "video") == 0) ? mCurrentGame->getVideoPath() : mCurrentGame->getImagePath();
|
||||
if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never")
|
||||
{
|
||||
auto systemName = mCurrentGame->getSourceFileData()->getSystem()->getFullName();
|
||||
writeSubtitle(mCurrentGame->getSourceFileData()->getName().c_str(), systemName.c_str(),
|
||||
(Settings::getInstance()->getString("ScreenSaverGameInfo") == "always"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SystemScreenSaver::pickRandomVideo(std::string& path)
|
||||
|
||||
void SystemScreenSaver::pickRandomVideo(std::string& path, bool keepSame)
|
||||
{
|
||||
mCurrentGame = NULL;
|
||||
pickGameListNode("video", path);
|
||||
if (!keepSame)
|
||||
pickGameListNode("video");
|
||||
prepareScreenSaverMedia("video", path);
|
||||
}
|
||||
|
||||
void SystemScreenSaver::pickRandomGameListImage(std::string& path)
|
||||
void SystemScreenSaver::pickRandomGameListImage(std::string& path, bool keepSame)
|
||||
{
|
||||
mCurrentGame = NULL;
|
||||
pickGameListNode("image", path);
|
||||
if (!keepSame)
|
||||
pickGameListNode("image");
|
||||
prepareScreenSaverMedia("image", path);
|
||||
}
|
||||
|
||||
void SystemScreenSaver::pickRandomCustomMedia(std::string& path)
|
||||
|
@ -484,7 +585,7 @@ void SystemScreenSaver::update(int deltaTime)
|
|||
mTimer += deltaTime;
|
||||
if (mTimer > mSwapTimeout)
|
||||
{
|
||||
nextMediaItem();
|
||||
changeMediaItem();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -495,10 +596,21 @@ void SystemScreenSaver::update(int deltaTime)
|
|||
mImageScreensaver->update(deltaTime);
|
||||
}
|
||||
|
||||
void SystemScreenSaver::nextMediaItem() {
|
||||
void SystemScreenSaver::changeMediaItem(bool next) {
|
||||
if (!next) {
|
||||
// swap entries
|
||||
FileData* tmpGame = mCurrentGame;
|
||||
mCurrentGame = mPreviousGame;
|
||||
mPreviousGame = tmpGame;
|
||||
}
|
||||
else
|
||||
{
|
||||
mPreviousGame = mCurrentGame;
|
||||
mCurrentGame = NULL;
|
||||
}
|
||||
mStopBackgroundAudio = false;
|
||||
stopScreenSaver();
|
||||
startScreenSaver();
|
||||
stopScreenSaver(true);
|
||||
startScreenSaver(mSystem);
|
||||
mState = STATE_SCREENSAVER_ACTIVE;
|
||||
}
|
||||
|
||||
|
@ -507,14 +619,33 @@ FileData* SystemScreenSaver::getCurrentGame()
|
|||
return mCurrentGame;
|
||||
}
|
||||
|
||||
void SystemScreenSaver::launchGame()
|
||||
void SystemScreenSaver::selectGame(bool launch)
|
||||
{
|
||||
if (mCurrentGame != NULL)
|
||||
{
|
||||
// launching Game
|
||||
ViewController::get()->goToGameList(mCurrentGame->getSystem());
|
||||
IGameListView* view = ViewController::get()->getGameListView(mCurrentGame->getSystem()).get();
|
||||
view->setCursor(mCurrentGame);
|
||||
view->launch(mCurrentGame);
|
||||
//Stop screensaver
|
||||
mStopBackgroundAudio = true;
|
||||
FileData* gameToSelect = mCurrentGame;
|
||||
stopScreenSaver();
|
||||
|
||||
ViewController::get()->goToGameList(gameToSelect->getSystem());
|
||||
IGameListView* view = ViewController::get()->getGameListView(gameToSelect->getSystem()).get();
|
||||
if (launch)
|
||||
view->launch(gameToSelect);
|
||||
else
|
||||
// Flag true is set to re-calculate the cursor position on the visible gamelist section on screen.
|
||||
// This flag is only to be set when there is no previous navigation state,
|
||||
// i.e. when jumping to a game from the screensaver or launching a game.
|
||||
// The latter case is covered in view->launch() ==> ViewController::launch().
|
||||
// The former case must be flagged as below.
|
||||
// see also: TextListComponent.REFRESH_LIST_CURSOR_POS and its usage for the 'true' flag
|
||||
view->setCursor(gameToSelect, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemScreenSaver::setBackground()
|
||||
{
|
||||
// Render black background
|
||||
Renderer::setMatrix(Transform4x4f::Identity());
|
||||
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
|
||||
}
|
||||
|
|
|
@ -16,29 +16,33 @@ public:
|
|||
SystemScreenSaver(Window* window);
|
||||
virtual ~SystemScreenSaver();
|
||||
|
||||
virtual void startScreenSaver();
|
||||
virtual void stopScreenSaver();
|
||||
virtual void nextMediaItem();
|
||||
virtual void startScreenSaver(SystemData* system=NULL);
|
||||
virtual void stopScreenSaver(bool toResume=false);
|
||||
virtual void renderScreenSaver();
|
||||
virtual bool allowSleep();
|
||||
virtual void update(int deltaTime);
|
||||
virtual bool isScreenSaverActive();
|
||||
|
||||
virtual FileData* getCurrentGame();
|
||||
virtual void launchGame();
|
||||
virtual void selectGame(bool launch);
|
||||
virtual bool inputDuringScreensaver(InputConfig* config, Input input);
|
||||
|
||||
private:
|
||||
void pickGameListNode(const char *nodeName, std::string& path);
|
||||
void pickRandomVideo(std::string& path);
|
||||
void pickRandomGameListImage(std::string& path);
|
||||
void changeMediaItem(bool next = true);
|
||||
void pickGameListNode(const char *nodeName);
|
||||
void prepareScreenSaverMedia(const char *nodeName, std::string& path);
|
||||
void pickRandomVideo(std::string& path, bool keepSame = false);
|
||||
void pickRandomGameListImage(std::string& path, bool keepSame = false);
|
||||
void pickRandomCustomMedia(std::string& path);
|
||||
void setVideoScreensaver(std::string& path);
|
||||
void setImageScreensaver(std::string& path);
|
||||
bool isFileVideo(std::string& path);
|
||||
std::vector<std::string> getCustomMediaFiles(const std::string &mediaDir);
|
||||
std::vector<FileData*> getAllGamelistNodes();
|
||||
void getAllGamelistNodes();
|
||||
void getAllGamelistNodesForSystem(SystemData* system);
|
||||
void backgroundIndexing();
|
||||
|
||||
void setBackground();
|
||||
void handleScreenSaverEditingCollection();
|
||||
void input(InputConfig* config, Input input);
|
||||
|
||||
enum STATE {
|
||||
|
@ -52,18 +56,21 @@ private:
|
|||
VideoComponent* mVideoScreensaver;
|
||||
ImageComponent* mImageScreensaver;
|
||||
Window* mWindow;
|
||||
SystemData* mSystem;
|
||||
STATE mState;
|
||||
float mOpacity;
|
||||
int mTimer;
|
||||
FileData* mCurrentGame;
|
||||
int mSwapTimeout;
|
||||
FileData* mPreviousGame;
|
||||
int mSwapTimeout;
|
||||
std::shared_ptr<Sound> mBackgroundAudio;
|
||||
bool mStopBackgroundAudio;
|
||||
std::vector<FileData*> mAllFiles;
|
||||
std::vector<std::string> mCustomMediaFiles;
|
||||
int mAllFilesSize;
|
||||
std::thread* mThread;
|
||||
bool mExit;
|
||||
std::vector<FileData*> mAllFiles;
|
||||
std::vector<std::string> mCustomMediaFiles;
|
||||
int mAllFilesSize;
|
||||
std::thread* mThread;
|
||||
bool mExit;
|
||||
std::string mRegularEditingCollection;
|
||||
};
|
||||
|
||||
#endif // ES_APP_SYSTEM_SCREEN_SAVER_H
|
||||
|
|
|
@ -7,22 +7,21 @@
|
|||
class MoveCameraAnimation : public Animation
|
||||
{
|
||||
public:
|
||||
MoveCameraAnimation(Transform4x4f& camera, const Vector3f& target) : mCameraStart(camera), mTarget(target), cameraOut(camera) {}
|
||||
MoveCameraAnimation(Transform4x4f& camera, const Vector3f& target) : mCameraStart(camera), mTarget(target), mCameraOut(camera) { }
|
||||
|
||||
int getDuration() const override { return 400; }
|
||||
|
||||
void apply(float t) override
|
||||
{
|
||||
// cubic ease out
|
||||
t -= 1;
|
||||
cameraOut.translation() = -Vector3f().lerp(-mCameraStart.translation(), mTarget, t*t*t + 1);
|
||||
mCameraOut.translation() = -Vector3f().lerp(-mCameraStart.translation(), mTarget, t*t*t + 1 /*cubic ease out*/);
|
||||
}
|
||||
|
||||
private:
|
||||
Transform4x4f mCameraStart;
|
||||
Vector3f mTarget;
|
||||
|
||||
Transform4x4f& cameraOut;
|
||||
Transform4x4f& mCameraOut;
|
||||
};
|
||||
|
||||
#endif // ES_APP_ANIMATIONS_MOVE_CAMERA_ANIMATION_H
|
||||
|
|
|
@ -110,12 +110,12 @@ void RatingComponent::render(const Transform4x4f& parentTrans)
|
|||
Transform4x4f trans = parentTrans * getTransform();
|
||||
Renderer::setMatrix(trans);
|
||||
|
||||
mFilledTexture->bind();
|
||||
Renderer::drawTriangleStrips(&mVertices[0], 4);
|
||||
|
||||
mUnfilledTexture->bind();
|
||||
Renderer::drawTriangleStrips(&mVertices[4], 4);
|
||||
|
||||
mFilledTexture->bind();
|
||||
Renderer::drawTriangleStrips(&mVertices[0], 4);
|
||||
|
||||
renderChildren(trans);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
void setValue(const std::string& value) override; // Should be a normalized float (in the range [0..1]) - if it's not, it will be clamped.
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void render(const Transform4x4f& parentTrans);
|
||||
void render(const Transform4x4f& parentTrans) override;
|
||||
|
||||
void onSizeChanged() override;
|
||||
|
||||
|
|
|
@ -30,13 +30,17 @@ protected:
|
|||
using IList<TextListData, T>::getTransform;
|
||||
using IList<TextListData, T>::mSize;
|
||||
using IList<TextListData, T>::mCursor;
|
||||
using IList<TextListData, T>::Entry;
|
||||
using IList<TextListData, T>::mViewportTop;
|
||||
using IList<TextListData, T>::mEntry;
|
||||
|
||||
public:
|
||||
using IList<TextListData, T>::size;
|
||||
using IList<TextListData, T>::isScrolling;
|
||||
using IList<TextListData, T>::stopScrolling;
|
||||
|
||||
// flag to re-evaluate list cursor position in visible list section
|
||||
static constexpr int REFRESH_LIST_CURSOR_POS = -1;
|
||||
|
||||
TextListComponent(Window* window);
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
|
@ -81,8 +85,8 @@ public:
|
|||
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
|
||||
|
||||
protected:
|
||||
virtual void onScroll(int /*amt*/) { if(!mScrollSound.empty()) Sound::get(mScrollSound)->play(); }
|
||||
virtual void onCursorChanged(const CursorState& state);
|
||||
virtual void onScroll(int /*amt*/) override { if(!mScrollSound.empty()) Sound::get(mScrollSound)->play(); }
|
||||
virtual void onCursorChanged(const CursorState& state) override;
|
||||
|
||||
private:
|
||||
int mMarqueeOffset;
|
||||
|
@ -92,7 +96,7 @@ private:
|
|||
Alignment mAlignment;
|
||||
float mHorizontalMargin;
|
||||
|
||||
int getFirstVisibleEntry();
|
||||
int viewportTop();
|
||||
std::function<void(CursorState state)> mCursorChangedCallback;
|
||||
|
||||
std::shared_ptr<Font> mFont;
|
||||
|
@ -107,10 +111,8 @@ private:
|
|||
std::string mScrollSound;
|
||||
static const unsigned int COLOR_ID_COUNT = 2;
|
||||
unsigned int mColors[COLOR_ID_COUNT];
|
||||
unsigned int mScreenCount;
|
||||
int mStartEntry = 0;
|
||||
unsigned int mCursorPrev = -1;
|
||||
bool mOneEntryUpDn = true;
|
||||
int mViewportHeight;
|
||||
int mCursorPrev = -1;
|
||||
|
||||
ImageComponent mSelectorImage;
|
||||
};
|
||||
|
@ -151,32 +153,34 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
|
|||
|
||||
const float entrySize = Math::max(font->getHeight(1.0), (float)font->getSize()) * mLineSpacing;
|
||||
|
||||
// number of entries that can fit on the screen simultaniously
|
||||
mScreenCount = (int)(mSize.y() / entrySize);
|
||||
// number of listentries that can fit on the screen
|
||||
mViewportHeight = (int)(mSize.y() / entrySize);
|
||||
|
||||
if(mViewportTop == REFRESH_LIST_CURSOR_POS)
|
||||
{
|
||||
// returning from screen saver activated game launch or screensaver press 'A'
|
||||
mViewportTop = mCursor - mViewportHeight/2;
|
||||
mCursorPrev = -1; // reset to pristine to calc viewportTop() right when jumping to game with 'A' pressed
|
||||
}
|
||||
if(mCursor != mCursorPrev)
|
||||
{
|
||||
mStartEntry = (size() > mScreenCount) ? getFirstVisibleEntry() : 0;
|
||||
mViewportTop = (size() > mViewportHeight) ? viewportTop() : 0;
|
||||
mCursorPrev = mCursor;
|
||||
}
|
||||
|
||||
unsigned int listCutoff = mStartEntry + mScreenCount;
|
||||
unsigned int listCutoff = mViewportTop + mViewportHeight;
|
||||
if(listCutoff > size())
|
||||
listCutoff = size();
|
||||
|
||||
float y = (mSize.y() - (mScreenCount * entrySize)) * 0.5f;
|
||||
float y = (mSize.y() - (mViewportHeight * entrySize)) * 0.5f;
|
||||
|
||||
// draw selector bar
|
||||
if(mStartEntry < listCutoff)
|
||||
{
|
||||
if (mSelectorImage.hasImage()) {
|
||||
mSelectorImage.setPosition(0.f, y + (mCursor - mStartEntry)*entrySize + mSelectorOffsetY, 0.f);
|
||||
mSelectorImage.render(trans);
|
||||
} else {
|
||||
Renderer::setMatrix(trans);
|
||||
Renderer::drawRect(0.0f, y + (mCursor - mStartEntry)*entrySize + mSelectorOffsetY, mSize.x(),
|
||||
mSelectorHeight, mSelectorColor, mSelectorColorEnd, mSelectorColorGradientHorizontal);
|
||||
}
|
||||
if (mSelectorImage.hasImage()) {
|
||||
mSelectorImage.setPosition(0.f, y + (mCursor - mViewportTop)*entrySize + mSelectorOffsetY, 0.f);
|
||||
mSelectorImage.render(trans);
|
||||
} else {
|
||||
Renderer::setMatrix(trans);
|
||||
Renderer::drawRect(0.0f, y + (mCursor - mViewportTop)*entrySize + mSelectorOffsetY, mSize.x(),
|
||||
mSelectorHeight, mSelectorColor, mSelectorColorEnd, mSelectorColorGradientHorizontal);
|
||||
}
|
||||
|
||||
// clip to inside margins
|
||||
|
@ -185,7 +189,7 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
|
|||
Renderer::pushClipRect(Vector2i((int)(trans.translation().x() + mHorizontalMargin), (int)trans.translation().y()),
|
||||
Vector2i((int)(dim.x() - mHorizontalMargin*2), (int)dim.y()));
|
||||
|
||||
for(int i = mStartEntry; i < listCutoff; i++)
|
||||
for(int i = mViewportTop; i < listCutoff; i++)
|
||||
{
|
||||
typename IList<TextListData, T>::Entry& entry = mEntries.at((unsigned int)i);
|
||||
|
||||
|
@ -254,91 +258,65 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
|
|||
|
||||
|
||||
template <typename T>
|
||||
int TextListComponent<T>::getFirstVisibleEntry()
|
||||
int TextListComponent<T>::viewportTop()
|
||||
{
|
||||
if (mCursorPrev == -1)
|
||||
{
|
||||
// init or returned from emulator
|
||||
mCursorPrev = mCursor;
|
||||
int quot = div(mCursor, mScreenCount).quot;
|
||||
mStartEntry = quot * mScreenCount;
|
||||
}
|
||||
int screenRelCursor = mCursorPrev - mStartEntry;
|
||||
bool cursorCentered = screenRelCursor == mScreenCount/2;
|
||||
int visibleEntryMax = size() - mScreenCount;
|
||||
int firstVisibleEntry = 0;
|
||||
int viewportTopMax = size() - mViewportHeight;
|
||||
int topNew = mViewportTop;
|
||||
|
||||
if(Settings::getInstance()->getBool("UseFullscreenPaging") && !cursorCentered)
|
||||
if (mCursorPrev == -1)
|
||||
mCursorPrev = mCursor;
|
||||
|
||||
int delta = mCursor - mCursorPrev;
|
||||
|
||||
if(Settings::getInstance()->getBool("UseFullscreenPaging"))
|
||||
{
|
||||
// keep visible cursor constant but move visible list (default)
|
||||
firstVisibleEntry = mCursor - screenRelCursor;
|
||||
if(mOneEntryUpDn)
|
||||
// delta may be greater/less than +/-mViewportHeight on re-sorting of list
|
||||
if (delta <= -mViewportHeight || delta >= mViewportHeight
|
||||
// keep cursor sticky at position unless the user navigates
|
||||
// to the middle of the viewport
|
||||
|| delta < 0 && mCursor - mViewportTop < mViewportHeight/2
|
||||
|| delta > 0 && mCursor - mViewportTop > mViewportHeight/2)
|
||||
{
|
||||
int delta = mCursor - mCursorPrev;
|
||||
// detect rollover (== delta is more than one item)
|
||||
if(delta < -3)
|
||||
firstVisibleEntry = 0;
|
||||
else if(delta > 3)
|
||||
firstVisibleEntry = visibleEntryMax;
|
||||
else if(screenRelCursor < mScreenCount/2 && delta > 0 /*down pressed*/
|
||||
|| screenRelCursor > mScreenCount/2 && delta < 0 /*up pressed*/)
|
||||
// cases for list begin / list end
|
||||
// move visible cursor and keep visible list section constant
|
||||
firstVisibleEntry = firstVisibleEntry - delta;
|
||||
topNew += delta;
|
||||
}
|
||||
} else {
|
||||
// cursor always in middle of visible list
|
||||
firstVisibleEntry = mCursor - mScreenCount/2;
|
||||
// no match above will place the cursor more towards the middle
|
||||
}
|
||||
// bounds check
|
||||
if(firstVisibleEntry < 0)
|
||||
firstVisibleEntry = 0;
|
||||
else if(firstVisibleEntry > visibleEntryMax)
|
||||
firstVisibleEntry = visibleEntryMax;
|
||||
return firstVisibleEntry;
|
||||
else
|
||||
// put cursor in middle of visible list
|
||||
topNew = mCursor - mViewportHeight/2;
|
||||
|
||||
if (mCursor <= mViewportHeight/2)
|
||||
topNew = 0;
|
||||
else if (mCursor >= viewportTopMax + mViewportHeight/2)
|
||||
topNew = viewportTopMax;
|
||||
|
||||
return topNew;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool TextListComponent<T>::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(size() > 0)
|
||||
bool isSingleStep = config->isMappedLike("down", input) || config->isMappedLike("up", input);
|
||||
bool isPageStep = config->isMappedLike("rightshoulder", input) || config->isMappedLike("leftshoulder", input);
|
||||
|
||||
if(size() > 0 && (isSingleStep || isPageStep))
|
||||
{
|
||||
if(input.value != 0)
|
||||
{
|
||||
if(config->isMappedLike("down", input))
|
||||
int delta;
|
||||
mCursorPrev = mCursor;
|
||||
if(isSingleStep)
|
||||
delta = config->isMappedLike("down", input) ? 1 : -1;
|
||||
else
|
||||
{
|
||||
listInput(1);
|
||||
mOneEntryUpDn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedLike("up", input))
|
||||
{
|
||||
listInput(-1);
|
||||
mOneEntryUpDn = true;
|
||||
return true;
|
||||
}
|
||||
if(config->isMappedLike("rightshoulder", input))
|
||||
{
|
||||
int delta = Settings::getInstance()->getBool("UseFullscreenPaging") ? mScreenCount : 10;
|
||||
listInput(delta);
|
||||
mOneEntryUpDn = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedLike("leftshoulder", input))
|
||||
{
|
||||
int delta = Settings::getInstance()->getBool("UseFullscreenPaging") ? mScreenCount : 10;
|
||||
listInput(-delta);
|
||||
mOneEntryUpDn = false;
|
||||
return true;
|
||||
delta = Settings::getInstance()->getBool("UseFullscreenPaging") ? mViewportHeight : 10;
|
||||
if (config->isMappedLike("leftshoulder", input))
|
||||
delta = -delta;
|
||||
}
|
||||
listInput(delta);
|
||||
return true;
|
||||
}else{
|
||||
if(config->isMappedLike("down", input) || config->isMappedLike("up", input) ||
|
||||
config->isMappedLike("rightshoulder", input) || config->isMappedLike("leftshoulder", input))
|
||||
{
|
||||
stopScrolling();
|
||||
}
|
||||
stopScrolling();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,6 +392,11 @@ void TextListComponent<T>::onCursorChanged(const CursorState& state)
|
|||
template <typename T>
|
||||
void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
|
||||
{
|
||||
if(Settings::getInstance()->getBool("UseFullscreenPaging"))
|
||||
{
|
||||
mViewportTop = REFRESH_LIST_CURSOR_POS;
|
||||
}
|
||||
|
||||
GuiComponent::applyTheme(theme, view, element, properties);
|
||||
|
||||
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "textlist");
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#include <fstream>
|
||||
|
||||
#include "guis/GuiCollectionSystemsOptions.h"
|
||||
|
||||
#include "components/OptionListComponent.h"
|
||||
#include "components/SwitchComponent.h"
|
||||
#include "guis/GuiInfoPopup.h"
|
||||
#include "guis/GuiRandomCollectionOptions.h"
|
||||
#include "guis/GuiSettings.h"
|
||||
#include "guis/GuiTextEditPopup.h"
|
||||
#include "utils/StringUtil.h"
|
||||
|
@ -19,53 +23,63 @@ void GuiCollectionSystemsOptions::initializeMenu()
|
|||
addChild(&mMenu);
|
||||
|
||||
// get collections
|
||||
|
||||
addSystemsToMenu();
|
||||
|
||||
// add "Create New Custom Collection from Theme"
|
||||
// manage random collection
|
||||
addEntry("RANDOM GAME COLLECTION SETTINGS", 0x777777FF, true, [this] { openRandomCollectionSettings(); });
|
||||
|
||||
std::vector<std::string> unusedFolders = CollectionSystemManager::get()->getUnusedSystemsFromTheme();
|
||||
if (unusedFolders.size() > 0)
|
||||
{
|
||||
addEntry("CREATE NEW CUSTOM COLLECTION FROM THEME", 0x777777FF, true,
|
||||
[this, unusedFolders] {
|
||||
auto s = new GuiSettings(mWindow, "SELECT THEME FOLDER");
|
||||
std::shared_ptr< OptionListComponent<std::string> > folderThemes = std::make_shared< OptionListComponent<std::string> >(mWindow, "SELECT THEME FOLDER", true);
|
||||
|
||||
// add Custom Systems
|
||||
for(auto it = unusedFolders.cbegin() ; it != unusedFolders.cend() ; it++ )
|
||||
{
|
||||
ComponentListRow row;
|
||||
std::string name = *it;
|
||||
|
||||
std::function<void()> createCollectionCall = [name, this, s] {
|
||||
createCollection(name);
|
||||
};
|
||||
row.makeAcceptInputHandler(createCollectionCall);
|
||||
|
||||
auto themeFolder = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||
row.addElement(themeFolder, true);
|
||||
s->addRow(row);
|
||||
}
|
||||
mWindow->pushGui(s);
|
||||
});
|
||||
}
|
||||
|
||||
ComponentListRow row;
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
auto createCustomCollection = [this](const std::string& newVal) {
|
||||
std::string name = newVal;
|
||||
// we need to store the first Gui and remove it, as it'll be deleted by the actual Gui
|
||||
Window* window = mWindow;
|
||||
GuiComponent* topGui = window->peekGui();
|
||||
window->removeGui(topGui);
|
||||
createCollection(name);
|
||||
};
|
||||
row.makeAcceptInputHandler([this, createCustomCollection] {
|
||||
mWindow->pushGui(new GuiTextEditPopup(mWindow, "New Collection Name", "", createCustomCollection, false));
|
||||
});
|
||||
if(CollectionSystemManager::get()->isEditing())
|
||||
{
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "FINISH EDITING '" + Utils::String::toUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.makeAcceptInputHandler(std::bind(&GuiCollectionSystemsOptions::exitEditMode, this));
|
||||
mMenu.addRow(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add "Create New Custom Collection from Theme"
|
||||
std::vector<std::string> unusedFolders = CollectionSystemManager::get()->getUnusedSystemsFromTheme();
|
||||
if (unusedFolders.size() > 0)
|
||||
{
|
||||
addEntry("CREATE NEW CUSTOM COLLECTION FROM THEME", 0x777777FF, true,
|
||||
[this, unusedFolders] {
|
||||
auto s = new GuiSettings(mWindow, "SELECT THEME FOLDER");
|
||||
std::shared_ptr< OptionListComponent<std::string> > folderThemes = std::make_shared< OptionListComponent<std::string> >(mWindow, "SELECT THEME FOLDER", true);
|
||||
|
||||
mMenu.addRow(row);
|
||||
// add Custom Systems
|
||||
for(auto it = unusedFolders.cbegin() ; it != unusedFolders.cend() ; it++ )
|
||||
{
|
||||
ComponentListRow row;
|
||||
std::string name = *it;
|
||||
|
||||
std::function<void()> createCollectionCall = [name, this, s] {
|
||||
createCollection(name);
|
||||
};
|
||||
row.makeAcceptInputHandler(createCollectionCall);
|
||||
|
||||
auto themeFolder = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||
row.addElement(themeFolder, true);
|
||||
s->addRow(row);
|
||||
}
|
||||
mWindow->pushGui(s);
|
||||
});
|
||||
}
|
||||
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
auto createCustomCollection = [this](const std::string& newVal) {
|
||||
std::string name = newVal;
|
||||
// we need to store the first Gui and remove it, as it'll be deleted by the actual Gui
|
||||
Window* window = mWindow;
|
||||
GuiComponent* topGui = window->peekGui();
|
||||
window->removeGui(topGui);
|
||||
createCollection(name);
|
||||
};
|
||||
row.makeAcceptInputHandler([this, createCustomCollection] {
|
||||
mWindow->pushGui(new GuiTextEditPopup(mWindow, "New Collection Name", "", createCustomCollection, false));
|
||||
});
|
||||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
bundleCustomCollections = std::make_shared<SwitchComponent>(mWindow);
|
||||
bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem"));
|
||||
|
@ -84,14 +98,23 @@ void GuiCollectionSystemsOptions::initializeMenu()
|
|||
doublePressToRemoveFavs->setState(Settings::getInstance()->getBool("DoublePressRemovesFromFavs"));
|
||||
mMenu.addWithLabel("PRESS (Y) TWICE TO REMOVE FROM FAVS./COLL.", doublePressToRemoveFavs);
|
||||
|
||||
if(CollectionSystemManager::get()->isEditing())
|
||||
|
||||
// Add option to select default collection for screensaver
|
||||
defaultScreenSaverCollection = std::make_shared< OptionListComponent<std::string> >(mWindow, "ADD/REMOVE GAMES WHILE SCREENSAVER TO", false);
|
||||
// Add default option
|
||||
std::string defaultScreenSaverCollectionName = Settings::getInstance()->getString("DefaultScreenSaverCollection");
|
||||
defaultScreenSaverCollection->add("<DEFAULT>", "", defaultScreenSaverCollectionName == "");
|
||||
|
||||
std::map<std::string, CollectionSystemData> customSystems = CollectionSystemManager::get()->getCustomCollectionSystems();
|
||||
// add all enabled Custom Systems
|
||||
for(std::map<std::string, CollectionSystemData>::const_iterator it = customSystems.cbegin() ; it != customSystems.cend() ; it++ )
|
||||
{
|
||||
row.elements.clear();
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "FINISH EDITING '" + Utils::String::toUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.makeAcceptInputHandler(std::bind(&GuiCollectionSystemsOptions::exitEditMode, this));
|
||||
mMenu.addRow(row);
|
||||
if (it->second.isEnabled)
|
||||
defaultScreenSaverCollection->add(it->second.decl.longName, it->second.decl.name, defaultScreenSaverCollectionName == it->second.decl.name);
|
||||
}
|
||||
|
||||
mMenu.addWithLabel("ADD/REMOVE GAMES WHILE SCREENSAVER TO", defaultScreenSaverCollection);
|
||||
|
||||
mMenu.addButton("BACK", "back", std::bind(&GuiCollectionSystemsOptions::applySettings, this));
|
||||
|
||||
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
|
||||
|
@ -116,9 +139,17 @@ void GuiCollectionSystemsOptions::addEntry(const char* name, unsigned int color,
|
|||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
void GuiCollectionSystemsOptions::createCollection(std::string inName) {
|
||||
std::string name = CollectionSystemManager::get()->getValidNewCollectionName(inName);
|
||||
SystemData* newSys = CollectionSystemManager::get()->addNewCustomCollection(name);
|
||||
void GuiCollectionSystemsOptions::createCollection(std::string inName)
|
||||
{
|
||||
CollectionSystemManager* collSysMgr = CollectionSystemManager::get();
|
||||
std::string name = collSysMgr->getValidNewCollectionName(inName);
|
||||
|
||||
SystemData* newSys = collSysMgr->addNewCustomCollection(name, true);
|
||||
if (!collSysMgr->saveCustomCollection(newSys)) {
|
||||
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Failed creating '" + Utils::String::toUpper(name) + "' Collection. See log for details.", 8000);
|
||||
mWindow->setInfoPopup(s);
|
||||
return;
|
||||
}
|
||||
customOptionList->add(name, name, true);
|
||||
std::string outAuto = Utils::String::vectorToDelimitedString(autoOptionList->getSelectedObjects(), ",");
|
||||
std::string outCustom = Utils::String::vectorToDelimitedString(customOptionList->getSelectedObjects(), ",");
|
||||
|
@ -126,10 +157,14 @@ void GuiCollectionSystemsOptions::createCollection(std::string inName) {
|
|||
ViewController::get()->goToSystemView(newSys);
|
||||
|
||||
Window* window = mWindow;
|
||||
CollectionSystemManager::get()->setEditMode(name);
|
||||
collSysMgr->setEditMode(name);
|
||||
while(window->peekGui() && window->peekGui() != ViewController::get())
|
||||
delete window->peekGui();
|
||||
return;
|
||||
}
|
||||
|
||||
void GuiCollectionSystemsOptions::openRandomCollectionSettings()
|
||||
{
|
||||
mWindow->pushGui(new GuiRandomCollectionOptions(mWindow));
|
||||
}
|
||||
|
||||
void GuiCollectionSystemsOptions::exitEditMode()
|
||||
|
@ -181,15 +216,42 @@ void GuiCollectionSystemsOptions::applySettings()
|
|||
bool prevBundle = Settings::getInstance()->getBool("UseCustomCollectionsSystem");
|
||||
bool prevShow = Settings::getInstance()->getBool("CollectionShowSystemInfo");
|
||||
bool outShow = toggleSystemNameInCollections->getState();
|
||||
bool prevDblPressRmFavs = Settings::getInstance()->getBool("DoublePressRemovesFromFavs");
|
||||
bool outDblPressRmFavs = doublePressToRemoveFavs->getState();
|
||||
bool needUpdateSettings = prevAuto != outAuto || prevCustom != outCustom || outSort != prevSort || outBundle != prevBundle
|
||||
|| prevShow != outShow || prevDblPressRmFavs != outDblPressRmFavs;
|
||||
if (needUpdateSettings)
|
||||
|
||||
// check if custom collection is still enabled for 'collection during screensaver'.
|
||||
// if not set 'collection during screensaver' to "" which renders as <DEFAULT>
|
||||
std::string enabledCollectionName = "";
|
||||
std::vector<std::string> selection = defaultScreenSaverCollection->getSelectedObjects();
|
||||
if (selection.size() > 0)
|
||||
{
|
||||
std::string selectedCollection = selection.at(0);
|
||||
if (selectedCollection != "")
|
||||
{
|
||||
std::vector<std::string> enabledCollections = customOptionList->getSelectedObjects();
|
||||
for(auto nameIt = enabledCollections.begin(); nameIt != enabledCollections.end(); nameIt++)
|
||||
{
|
||||
if(*nameIt == selectedCollection)
|
||||
{
|
||||
enabledCollectionName = selectedCollection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Settings::getInstance()->setString("DefaultScreenSaverCollection", enabledCollectionName);
|
||||
Settings::getInstance()->setBool("DoublePressRemovesFromFavs", doublePressToRemoveFavs->getState());
|
||||
|
||||
bool needRefreshCollectionSettings = prevAuto != outAuto || prevCustom != outCustom || outSort != prevSort || outBundle != prevBundle
|
||||
|| prevShow != outShow;
|
||||
|
||||
if (needRefreshCollectionSettings)
|
||||
{
|
||||
updateSettings(outAuto, outCustom);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Settings::getInstance()->saveFile();
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
|
@ -200,8 +262,9 @@ void GuiCollectionSystemsOptions::updateSettings(std::string newAutoSettings, st
|
|||
Settings::getInstance()->setBool("SortAllSystems", sortAllSystemsSwitch->getState());
|
||||
Settings::getInstance()->setBool("UseCustomCollectionsSystem", bundleCustomCollections->getState());
|
||||
Settings::getInstance()->setBool("CollectionShowSystemInfo", toggleSystemNameInCollections->getState());
|
||||
Settings::getInstance()->setBool("DoublePressRemovesFromFavs", doublePressToRemoveFavs->getState());
|
||||
|
||||
Settings::getInstance()->saveFile();
|
||||
|
||||
CollectionSystemManager::get()->loadEnabledListFromSettings();
|
||||
CollectionSystemManager::get()->updateSystemsList();
|
||||
ViewController::get()->goToStart();
|
||||
|
|
|
@ -26,8 +26,10 @@ private:
|
|||
void updateSettings(std::string newAutoSettings, std::string newCustomSettings);
|
||||
void createCollection(std::string inName);
|
||||
void exitEditMode();
|
||||
void openRandomCollectionSettings();
|
||||
std::shared_ptr< OptionListComponent<std::string> > autoOptionList;
|
||||
std::shared_ptr< OptionListComponent<std::string> > customOptionList;
|
||||
std::shared_ptr< OptionListComponent<std::string> > defaultScreenSaverCollection;
|
||||
std::shared_ptr<SwitchComponent> sortAllSystemsSwitch;
|
||||
std::shared_ptr<SwitchComponent> bundleCustomCollections;
|
||||
std::shared_ptr<SwitchComponent> toggleSystemNameInCollections;
|
||||
|
|
|
@ -14,7 +14,7 @@ public:
|
|||
void onSizeChanged() override;
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime);
|
||||
void update(int deltaTime) override;
|
||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -10,73 +10,98 @@
|
|||
#include "FileSorts.h"
|
||||
#include "GuiMetaDataEd.h"
|
||||
#include "SystemData.h"
|
||||
#include "components/TextListComponent.h"
|
||||
|
||||
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window),
|
||||
mSystem(system), mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false)
|
||||
mSystem(system), mMenu(window, "OPTIONS"), mFromPlaceholder(false), mFiltersChanged(false),
|
||||
mJumpToSelected(false), mMetadataChanged(false)
|
||||
{
|
||||
addChild(&mMenu);
|
||||
|
||||
// check it's not a placeholder folder - if it is, only show "Filter Options"
|
||||
FileData* file = getGamelist()->getCursor();
|
||||
fromPlaceholder = file->isPlaceHolder();
|
||||
mFromPlaceholder = file->isPlaceHolder();
|
||||
ComponentListRow row;
|
||||
|
||||
if (!fromPlaceholder) {
|
||||
// jump to letter
|
||||
if (!mFromPlaceholder) {
|
||||
row.elements.clear();
|
||||
|
||||
// define supported character range
|
||||
// this range includes all numbers, capital letters, and most reasonable symbols
|
||||
char startChar = '!';
|
||||
char endChar = '_';
|
||||
std::string currentSort = mSystem->getRootFolder()->getSortDescription();
|
||||
std::string reqSort = FileSorts::SortTypes.at(0).description;
|
||||
|
||||
char curChar = (char)toupper(getGamelist()->getCursor()->getSortName()[0]);
|
||||
if(curChar < startChar || curChar > endChar)
|
||||
curChar = startChar;
|
||||
// "jump to letter" menuitem only available (and correct jumping) on sort order "name, asc"
|
||||
if (currentSort == reqSort) {
|
||||
bool outOfRange = false;
|
||||
char curChar = (char)toupper(getGamelist()->getCursor()->getSortName()[0]);
|
||||
// define supported character range
|
||||
// this range includes all numbers, capital letters, and most reasonable symbols
|
||||
char startChar = '!';
|
||||
char endChar = '_';
|
||||
if (curChar < startChar || curChar > endChar) {
|
||||
// most likely 8 bit ASCII or Unicode (Prefix: 0xc2 or 0xe2) value
|
||||
curChar = startChar;
|
||||
outOfRange = true;
|
||||
}
|
||||
|
||||
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO...", false);
|
||||
for (char c = startChar; c <= endChar; c++)
|
||||
{
|
||||
// check if c is a valid first letter in current list
|
||||
const std::vector<FileData*>& files = getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
|
||||
for (auto file : files)
|
||||
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO ...", false);
|
||||
for (char c = startChar; c <= endChar; c++)
|
||||
{
|
||||
char candidate = (char)toupper(file->getSortName()[0]);
|
||||
if (c == candidate)
|
||||
// check if c is a valid first letter in current list
|
||||
const std::vector<FileData*>& files = getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
|
||||
for (auto file : files)
|
||||
{
|
||||
mJumpToLetterList->add(std::string(1, c), c, c == curChar);
|
||||
break;
|
||||
char candidate = (char)toupper(file->getSortName()[0]);
|
||||
if (c == candidate)
|
||||
{
|
||||
mJumpToLetterList->add(std::string(1, c), c, (c == curChar) || outOfRange);
|
||||
outOfRange = false; // only override selection on very first c == candidate match
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "JUMP TO ...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.addElement(mJumpToLetterList, false);
|
||||
row.input_handler = [&](InputConfig* config, Input input) {
|
||||
if(config->isMappedTo("a", input) && input.value)
|
||||
{
|
||||
jumpToLetter();
|
||||
return true;
|
||||
}
|
||||
else if(mJumpToLetterList->input(config, input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "JUMP TO...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.addElement(mJumpToLetterList, false);
|
||||
row.input_handler = [&](InputConfig* config, Input input) {
|
||||
if(config->isMappedTo("a", input) && input.value)
|
||||
{
|
||||
jumpToLetter();
|
||||
return true;
|
||||
}
|
||||
else if(mJumpToLetterList->input(config, input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
mMenu.addRow(row);
|
||||
// add launch system screensaver
|
||||
std::string screensaver_behavior = Settings::getInstance()->getString("ScreenSaverBehavior");
|
||||
bool useGamelistMedia = screensaver_behavior == "random video" || (screensaver_behavior == "slideshow" && !Settings::getInstance()->getBool("SlideshowScreenSaverCustomMediaSource"));
|
||||
bool rpConfigSelected = "retropie" == mSystem->getName();
|
||||
bool collectionsSelected = mSystem->getName() == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName();
|
||||
|
||||
// sort list by
|
||||
if (!rpConfigSelected && useGamelistMedia && (!collectionsSelected || collectionsSelected && file->getType() == GAME)) {
|
||||
row.elements.clear();
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "LAUNCH SYSTEM SCREENSAVER", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::launchSystemScreenSaver, this));
|
||||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
// "sort list by" menuitem
|
||||
mListSort = std::make_shared<SortList>(mWindow, "SORT GAMES BY", false);
|
||||
for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++)
|
||||
{
|
||||
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
|
||||
mListSort->add(sort.description, &sort, i == 0); // TODO - actually make the sort type persistent
|
||||
mListSort->add(sort.description, &sort, sort.description == currentSort);
|
||||
}
|
||||
|
||||
mMenu.addWithLabel("SORT GAMES BY", mListSort);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// show filtered menu
|
||||
if(!Settings::getInstance()->getBool("ForceDisableFilters"))
|
||||
{
|
||||
|
@ -107,10 +132,21 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
|
|||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder && !(mSystem->isCollection() && file->getType() == FOLDER))
|
||||
if(UIModeController::getInstance()->isUIModeFull() && system == CollectionSystemManager::get()->getRandomCollection())
|
||||
{
|
||||
row.elements.clear();
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, "GET NEW RANDOM GAMES", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::recreateCollection, this));
|
||||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && !(mSystem->isCollection() && file->getType() == FOLDER))
|
||||
{
|
||||
row.elements.clear();
|
||||
std::string lblTxt = std::string("EDIT THIS ");
|
||||
lblTxt += std::string((file->getType() == FOLDER ? "FOLDER" : "GAME"));
|
||||
lblTxt += std::string("'S METADATA");
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, lblTxt, Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.addElement(makeArrow(mWindow), false);
|
||||
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
|
||||
mMenu.addRow(row);
|
||||
|
@ -123,23 +159,48 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
|
|||
|
||||
GuiGamelistOptions::~GuiGamelistOptions()
|
||||
{
|
||||
FileData* root = mSystem->getRootFolder();
|
||||
// apply sort
|
||||
if (!fromPlaceholder) {
|
||||
FileData* root = mSystem->getRootFolder();
|
||||
root->sort(*mListSort->getSelected()); // will also recursively sort children
|
||||
if (!mFromPlaceholder) {
|
||||
const FileData::SortType selectedSort = mJumpToSelected ? FileSorts::SortTypes.at(0) /* force "name, asc" */ : *mListSort->getSelected();
|
||||
if (root->getSortDescription() != selectedSort.description) {
|
||||
root->sort(selectedSort); // will also recursively sort children
|
||||
// notify that the root folder was sorted
|
||||
getGamelist()->onFileChanged(root, FILE_SORTED);
|
||||
}
|
||||
}
|
||||
|
||||
// notify that the root folder was sorted
|
||||
getGamelist()->onFileChanged(root, FILE_SORTED);
|
||||
}
|
||||
if (mFiltersChanged)
|
||||
if (mFiltersChanged || mMetadataChanged)
|
||||
{
|
||||
// only reload full view if we came from a placeholder
|
||||
// as we need to re-display the remaining elements for whatever new
|
||||
// game is selected
|
||||
// force refresh of cursor list position
|
||||
ViewController::get()->getGameListView(mSystem)->setViewportTop(TextListComponent<FileData>::REFRESH_LIST_CURSOR_POS);
|
||||
// re-display the elements for whatever new or renamed game is selected
|
||||
ViewController::get()->reloadGameListView(mSystem);
|
||||
if (mFiltersChanged) {
|
||||
// trigger repaint of cursor and list detail
|
||||
getGamelist()->onFileChanged(root, FILE_SORTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GuiGamelistOptions::launchSystemScreenSaver()
|
||||
{
|
||||
SystemData* system = mSystem;
|
||||
std::string systemName = system->getName();
|
||||
// need to check if we're in a folder inside the collections bundle, to launch from there
|
||||
if(systemName == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName())
|
||||
{
|
||||
FileData* file = getGamelist()->getCursor(); // is GAME otherwise menuentry would have been hidden
|
||||
// we are inside a specific collection. We want to launch for that one.
|
||||
system = file->getSystem();
|
||||
}
|
||||
mWindow->startScreenSaver(system);
|
||||
mWindow->renderScreenSaver();
|
||||
|
||||
delete this;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GuiGamelistOptions::openGamelistFilter()
|
||||
{
|
||||
mFiltersChanged = true;
|
||||
|
@ -147,6 +208,12 @@ void GuiGamelistOptions::openGamelistFilter()
|
|||
mWindow->pushGui(ggf);
|
||||
}
|
||||
|
||||
void GuiGamelistOptions::recreateCollection()
|
||||
{
|
||||
CollectionSystemManager::get()->recreateCollection(mSystem);
|
||||
delete this;
|
||||
}
|
||||
|
||||
void GuiGamelistOptions::startEditMode()
|
||||
{
|
||||
std::string editingSystem = mSystem->getName();
|
||||
|
@ -184,8 +251,14 @@ void GuiGamelistOptions::openMetaDataEd()
|
|||
p.game = file;
|
||||
p.system = file->getSystem();
|
||||
|
||||
std::function<void()> deleteBtnFunc;
|
||||
std::function<void()> saveBtnFunc;
|
||||
saveBtnFunc = [this, file] {
|
||||
ViewController::get()->getGameListView(mSystem)->setCursor(file, true);
|
||||
mMetadataChanged = true;
|
||||
ViewController::get()->getGameListView(file->getSystem())->onFileChanged(file, FILE_METADATA_CHANGED);
|
||||
};
|
||||
|
||||
std::function<void()> deleteBtnFunc;
|
||||
if (file->getType() == FOLDER)
|
||||
{
|
||||
deleteBtnFunc = NULL;
|
||||
|
@ -198,8 +271,7 @@ void GuiGamelistOptions::openMetaDataEd()
|
|||
};
|
||||
}
|
||||
|
||||
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, Utils::FileSystem::getFileName(file->getPath()),
|
||||
std::bind(&IGameListView::onFileChanged, ViewController::get()->getGameListView(file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
|
||||
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, Utils::FileSystem::getFileName(file->getPath()), saveBtnFunc, deleteBtnFunc));
|
||||
}
|
||||
|
||||
void GuiGamelistOptions::jumpToLetter()
|
||||
|
@ -234,6 +306,9 @@ void GuiGamelistOptions::jumpToLetter()
|
|||
|
||||
gamelist->setCursor(files.at(mid));
|
||||
|
||||
// flag to force default sort order "name, asc", if user changed the sortorder in the options dialog
|
||||
mJumpToSelected = true;
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,10 @@ public:
|
|||
|
||||
private:
|
||||
void openGamelistFilter();
|
||||
bool launchSystemScreenSaver();
|
||||
void openMetaDataEd();
|
||||
void startEditMode();
|
||||
void recreateCollection();
|
||||
void exitEditMode();
|
||||
void jumpToLetter();
|
||||
|
||||
|
@ -37,8 +39,10 @@ private:
|
|||
|
||||
SystemData* mSystem;
|
||||
IGameListView* getGamelist();
|
||||
bool fromPlaceholder;
|
||||
bool mFromPlaceholder;
|
||||
bool mFiltersChanged;
|
||||
bool mJumpToSelected;
|
||||
bool mMetadataChanged;
|
||||
};
|
||||
|
||||
#endif // ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
|
||||
|
|
|
@ -14,7 +14,7 @@ public:
|
|||
GuiInfoPopup(Window* window, std::string message, int duration, int fadein = 500, int fadeout = 500);
|
||||
~GuiInfoPopup();
|
||||
void render(const Transform4x4f& parentTrans) override;
|
||||
inline void stop() { running = false; };
|
||||
inline void stop() override { running = false; };
|
||||
private:
|
||||
std::string mMessage;
|
||||
int mDuration;
|
||||
|
|
|
@ -301,8 +301,7 @@ void GuiMenu::openUISettings()
|
|||
{
|
||||
Scripting::fireEvent("theme-changed", theme_set->getSelected(), oldTheme);
|
||||
CollectionSystemManager::get()->updateSystemsList();
|
||||
ViewController::get()->goToStart();
|
||||
ViewController::get()->reloadAll(); // TODO - replace this with some sort of signal-based implementation
|
||||
ViewController::get()->reloadAll(true); // TODO - replace this with some sort of signal-based implementation
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#include "guis/GuiMetaDataEd.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "components/ButtonComponent.h"
|
||||
#include "components/ComponentList.h"
|
||||
#include "components/DateTimeComponent.h"
|
||||
#include "components/DateTimeEditComponent.h"
|
||||
#include "components/MenuComponent.h"
|
||||
#include "components/RatingComponent.h"
|
||||
|
@ -18,6 +20,7 @@
|
|||
#include "FileFilterIndex.h"
|
||||
#include "SystemData.h"
|
||||
#include "Window.h"
|
||||
#include "Log.h"
|
||||
|
||||
GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector<MetaDataDecl>& mdd, ScraperSearchParams scraperParams,
|
||||
const std::string& /*header*/, std::function<void()> saveCallback, std::function<void()> deleteFunc) : GuiComponent(window),
|
||||
|
@ -28,7 +31,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
|||
|
||||
mMetaDataDecl(mdd),
|
||||
mMetaData(md),
|
||||
mSavedCallback(saveCallback), mDeleteFunc(deleteFunc)
|
||||
mSavedCallback(saveCallback),
|
||||
mDeleteFunc(deleteFunc)
|
||||
{
|
||||
addChild(&mBackground);
|
||||
addChild(&mGrid);
|
||||
|
@ -36,8 +40,9 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
|||
mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5));
|
||||
|
||||
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
|
||||
mSubtitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath())),
|
||||
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
|
||||
std::string tgt = md->getType() == GAME_METADATA ? "GAME" : "FOLDER";
|
||||
std::string subt = tgt + ": " + Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath()));
|
||||
mSubtitle = std::make_shared<TextComponent>(mWindow, subt, Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
|
||||
mHeaderGrid->setEntry(mTitle, Vector2i(0, 1), false, true);
|
||||
mHeaderGrid->setEntry(mSubtitle, Vector2i(0, 3), false, true);
|
||||
|
||||
|
@ -49,16 +54,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
|||
// populate list
|
||||
for(auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
|
||||
{
|
||||
std::shared_ptr<GuiComponent> ed;
|
||||
|
||||
// don't add statistics
|
||||
if(iter->isStatistic)
|
||||
continue;
|
||||
|
||||
std::shared_ptr<GuiComponent> ed;
|
||||
|
||||
// create ed and add it (and any related components) to mMenu
|
||||
// ed's value will be set below
|
||||
ComponentListRow row;
|
||||
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||
auto lblTxt = Utils::String::toUpper(iter->displayName);
|
||||
if (iter->type == MD_DATE) {
|
||||
lblTxt += " (" + DateTimeComponent::getDateformatTip() + ")";
|
||||
}
|
||||
auto lbl = std::make_shared<TextComponent>(mWindow, lblTxt, Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||
row.addElement(lbl, true); // label
|
||||
|
||||
switch(iter->type)
|
||||
|
@ -110,6 +119,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
|||
{
|
||||
// MD_STRING
|
||||
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
||||
const float height = lbl->getSize().y() * 0.71f;
|
||||
ed->setSize(0, height);
|
||||
row.addElement(ed, true);
|
||||
|
||||
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
||||
|
@ -139,7 +150,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
|
|||
|
||||
std::vector< std::shared_ptr<ButtonComponent> > buttons;
|
||||
|
||||
if(!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
||||
if(md->getType() == GAME_METADATA && !scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));
|
||||
|
||||
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save", [&] { save(); delete this; }));
|
||||
|
@ -184,11 +195,17 @@ void GuiMetaDataEd::save()
|
|||
// remove game from index
|
||||
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
|
||||
|
||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
||||
assert(mMetaDataDecl.size() >= mEditors.size());
|
||||
// there may be less editfields than metadata entries as
|
||||
// statistic md fields are not shown to the user.
|
||||
// md statistic fields are not necessarily at the end of the md list
|
||||
int edIdx = 0;
|
||||
for(auto &mdd : mMetaDataDecl)
|
||||
{
|
||||
if(mMetaDataDecl.at(i).isStatistic)
|
||||
continue;
|
||||
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
|
||||
if(!mdd.isStatistic) {
|
||||
mMetaData->set(mdd.key, mEditors.at(edIdx)->getValue());
|
||||
edIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// enter game in index
|
||||
|
@ -211,29 +228,21 @@ void GuiMetaDataEd::fetch()
|
|||
|
||||
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
||||
{
|
||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
||||
assert(mMetaDataDecl.size() >= mEditors.size());
|
||||
int edIdx = 0;
|
||||
for(auto &mdd : mMetaDataDecl)
|
||||
{
|
||||
if(mMetaDataDecl.at(i).isStatistic)
|
||||
if(mdd.isStatistic)
|
||||
continue;
|
||||
|
||||
const std::string& key = mMetaDataDecl.at(i).key;
|
||||
mEditors.at(i)->setValue(result.mdl.get(key));
|
||||
mEditors.at(edIdx)->setValue(result.mdl.get(mdd.key));
|
||||
edIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
void GuiMetaDataEd::close(bool closeAllWindows)
|
||||
{
|
||||
// find out if the user made any changes
|
||||
bool dirty = false;
|
||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
||||
{
|
||||
const std::string& key = mMetaDataDecl.at(i).key;
|
||||
if(mMetaData->get(key) != mEditors.at(i)->getValue())
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool dirty = hasChanges();
|
||||
|
||||
std::function<void()> closeFunc;
|
||||
if(!closeAllWindows)
|
||||
|
@ -247,7 +256,6 @@ void GuiMetaDataEd::close(bool closeAllWindows)
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
if(dirty)
|
||||
{
|
||||
// changes were made, ask if the user wants to save them
|
||||
|
@ -261,6 +269,49 @@ void GuiMetaDataEd::close(bool closeAllWindows)
|
|||
}
|
||||
}
|
||||
|
||||
bool GuiMetaDataEd::hasChanges()
|
||||
{
|
||||
assert(mMetaDataDecl.size() >= mEditors.size());
|
||||
// find out if the user made any changes
|
||||
int edIdx = 0;
|
||||
for(auto &mdd : mMetaDataDecl)
|
||||
{
|
||||
if(!mdd.isStatistic)
|
||||
{
|
||||
std::string gamelistVal = mMetaData->get(mdd.key);
|
||||
std::string editorVal = mEditors.at(edIdx++)->getValue();
|
||||
if (mdd.key == "rating")
|
||||
{
|
||||
// needed to catch "0", "0.0" or ".<d>" (and "1.0") from gamelist string rating
|
||||
// getValue() of RatingComponent returns "0" for floats 0, 0.0; "0.<d>" for .<d>
|
||||
// and "1" for float 1.0
|
||||
// convert to float and compare to avoid false "Save Changes" prompt
|
||||
bool ok;
|
||||
if (to_float(gamelistVal, ok) != to_float(editorVal, ok))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// string compare
|
||||
if (gamelistVal != editorVal)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float GuiMetaDataEd::to_float(const std::string& str, bool& ok)
|
||||
{
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
float f = std::strtof(str.c_str(), &end);
|
||||
ok = !str.empty() && !*end && errno == 0;
|
||||
if (!ok)
|
||||
LOG(LogWarning) << "Conversion of input string '" << str << "' to float failed or is incomplete. Return value: " << f;
|
||||
return f;
|
||||
}
|
||||
|
||||
bool GuiMetaDataEd::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(GuiComponent::input(config, input))
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "GuiComponent.h"
|
||||
#include "MetaData.h"
|
||||
|
||||
|
||||
class ComponentList;
|
||||
class TextComponent;
|
||||
|
||||
|
@ -26,6 +27,8 @@ private:
|
|||
void fetch();
|
||||
void fetchDone(const ScraperSearchResult& result);
|
||||
void close(bool closeAllWindows);
|
||||
bool hasChanges();
|
||||
float to_float(const std::string& str, bool& ok);
|
||||
|
||||
NinePatchComponent mBackground;
|
||||
ComponentGrid mGrid;
|
||||
|
|
228
es-app/src/guis/GuiRandomCollectionOptions.cpp
Normal file
228
es-app/src/guis/GuiRandomCollectionOptions.cpp
Normal file
|
@ -0,0 +1,228 @@
|
|||
#include "guis/GuiRandomCollectionOptions.h"
|
||||
|
||||
#include "GuiRandomCollectionOptions.h"
|
||||
#include "components/OptionListComponent.h"
|
||||
#include "components/SwitchComponent.h"
|
||||
#include "guis/GuiSettings.h"
|
||||
#include "guis/GuiTextEditPopup.h"
|
||||
#include "utils/StringUtil.h"
|
||||
#include "views/ViewController.h"
|
||||
#include "CollectionSystemManager.h"
|
||||
#include "SystemData.h"
|
||||
#include "Window.h"
|
||||
|
||||
GuiRandomCollectionOptions::GuiRandomCollectionOptions(Window* window) : GuiComponent(window), mMenu(window, "RANDOM COLLECTION")
|
||||
{
|
||||
customCollectionLists.clear();
|
||||
autoCollectionLists.clear();
|
||||
systemLists.clear();
|
||||
mNeedsCollectionRefresh = false;
|
||||
|
||||
initializeMenu();
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::initializeMenu()
|
||||
{
|
||||
// get collections
|
||||
addEntry("INCLUDE SYSTEMS", 0x777777FF, true, [this] { selectSystems(); });
|
||||
addEntry("INCLUDE AUTO COLLECTIONS", 0x777777FF, true, [this] { selectAutoCollections(); });
|
||||
addEntry("INCLUDE CUSTOM COLLECTIONS", 0x777777FF, true, [this] { selectCustomCollections(); });
|
||||
|
||||
// Add option to exclude games from a collection
|
||||
exclusionCollection = std::make_shared< OptionListComponent<std::string> >(mWindow, "EXCLUDE GAMES FROM", false);
|
||||
|
||||
// Add default option
|
||||
exclusionCollection->add("<NONE>", "", Settings::getInstance()->getString("RandomCollectionExclusionCollection") == "");
|
||||
|
||||
std::map<std::string, CollectionSystemData> customSystems = CollectionSystemManager::get()->getCustomCollectionSystems();
|
||||
// add all enabled Custom Systems
|
||||
for(std::map<std::string, CollectionSystemData>::const_iterator it = customSystems.cbegin() ; it != customSystems.cend() ; it++ )
|
||||
{
|
||||
exclusionCollection->add(it->second.decl.longName, it->second.decl.name, Settings::getInstance()->getString("RandomCollectionExclusionCollection") == it->second.decl.name);
|
||||
}
|
||||
|
||||
mMenu.addWithLabel("EXCLUDE GAMES FROM", exclusionCollection);
|
||||
|
||||
// Add option to trim random collection items
|
||||
trimRandom = std::make_shared<NumberList>(mWindow, "MAX GAMES", false);
|
||||
|
||||
// Add default entry
|
||||
int maxGames = Settings::getInstance()->getInt("RandomCollectionMaxGames");
|
||||
trimRandom->add("ALL", 0, maxGames == 0);
|
||||
|
||||
// add limit values for size of random collection
|
||||
for(int i = 5; i <= 50; i = i+5)
|
||||
{
|
||||
trimRandom->add(std::to_string(i), i, maxGames == i);
|
||||
}
|
||||
|
||||
mMenu.addWithLabel("MAX GAMES", trimRandom);
|
||||
|
||||
addChild(&mMenu);
|
||||
|
||||
mMenu.addButton("OK", "ok", std::bind(&GuiRandomCollectionOptions::saveSettings, this));
|
||||
mMenu.addButton("CANCEL", "cancel", [&] { delete this; });
|
||||
|
||||
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func)
|
||||
{
|
||||
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);
|
||||
|
||||
// populate the list
|
||||
ComponentListRow row;
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, name, font, color), true);
|
||||
|
||||
if(add_arrow)
|
||||
{
|
||||
std::shared_ptr<ImageComponent> bracket = makeArrow(mWindow);
|
||||
row.addElement(bracket, false);
|
||||
}
|
||||
|
||||
row.makeAcceptInputHandler(func);
|
||||
|
||||
mMenu.addRow(row);
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::selectSystems()
|
||||
{
|
||||
std::map<std::string, CollectionSystemData> systems;
|
||||
for(auto &sys : SystemData::sSystemVector)
|
||||
{
|
||||
// we won't iterate all collections
|
||||
if (sys->isGameSystem() && !sys->isCollection())
|
||||
{
|
||||
CollectionSystemDecl sysDecl;
|
||||
sysDecl.name = sys->getName();
|
||||
sysDecl.longName = sys->getFullName();
|
||||
|
||||
CollectionSystemData newCollectionData;
|
||||
newCollectionData.system = sys;
|
||||
newCollectionData.decl = sysDecl;
|
||||
newCollectionData.isEnabled = true;
|
||||
|
||||
systems[sysDecl.name] = newCollectionData;
|
||||
}
|
||||
}
|
||||
selectEntries(systems, "RandomCollectionSystems", DEFAULT_RANDOM_SYSTEM_GAMES, &systemLists);
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::selectAutoCollections()
|
||||
{
|
||||
selectEntries(CollectionSystemManager::get()->getAutoCollectionSystems(), "RandomCollectionSystemsAuto", DEFAULT_RANDOM_COLLECTIONS_GAMES, &autoCollectionLists);
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::selectCustomCollections()
|
||||
{
|
||||
selectEntries(CollectionSystemManager::get()->getCustomCollectionSystems(), "RandomCollectionSystemsCustom", DEFAULT_RANDOM_COLLECTIONS_GAMES, &customCollectionLists);
|
||||
}
|
||||
|
||||
GuiRandomCollectionOptions::~GuiRandomCollectionOptions()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::selectEntries(std::map<std::string, CollectionSystemData> collection, std::string settingsLabel, int defaultValue, std::vector< SystemGames>* results) {
|
||||
auto s = new GuiSettings(mWindow, "INCLUDE GAMES FROM");
|
||||
|
||||
std::map<std::string, int> initValues = Settings::getInstance()->getMap(settingsLabel);
|
||||
|
||||
results->clear();
|
||||
|
||||
for(auto &c : collection)
|
||||
{
|
||||
CollectionSystemData csd = c.second;
|
||||
if (csd.system != CollectionSystemManager::get()->getRandomCollection())
|
||||
{
|
||||
ComponentListRow row;
|
||||
|
||||
std::string label = csd.decl.longName;
|
||||
int selectedValue = defaultValue;
|
||||
|
||||
if (initValues.find(label) != initValues.end())
|
||||
{
|
||||
int maxForSys = initValues[label];
|
||||
// we won't add more than the max and less than 0
|
||||
selectedValue = Math::max(Math::min(RANDOM_SYSTEM_MAX, maxForSys), 0);
|
||||
mNeedsCollectionRefresh |= selectedValue != maxForSys; // force overwrite of outlier in settings
|
||||
}
|
||||
|
||||
initValues[label] = selectedValue;
|
||||
|
||||
std::shared_ptr<NumberList> colItems = std::make_shared<NumberList>(mWindow, label, false);
|
||||
for (int i = 0; i <= RANDOM_SYSTEM_MAX; i++)
|
||||
{
|
||||
colItems->add(std::to_string(i), i, i == selectedValue);
|
||||
}
|
||||
row.addElement(std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(label), Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||
row.addElement(colItems, false);
|
||||
|
||||
s->addRow(row);
|
||||
SystemGames sys;
|
||||
sys.name = label;
|
||||
sys.gamesSelection = colItems;
|
||||
results->push_back(sys);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
|
||||
s->setPosition((mSize.x() - s->getSize().x()) / 2, (mSize.y() - s->getSize().y()) / 2);
|
||||
s->addSaveFunc([this, settingsLabel, initValues, results] { applyGroupSettings(settingsLabel, initValues, results); });
|
||||
mWindow->pushGui(s);
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::applyGroupSettings(std::string settingsLabel, const std::map<std::string, int> &initialValues, std::vector<SystemGames> *results)
|
||||
{
|
||||
std::map<std::string, int> currentValues;
|
||||
for (auto it = results->begin(); it != results->end(); ++it)
|
||||
{
|
||||
currentValues[(*it).name] = (*it).gamesSelection->getSelected();
|
||||
}
|
||||
if (currentValues != initialValues)
|
||||
{
|
||||
mNeedsCollectionRefresh = true;
|
||||
Settings::getInstance()->setMap(settingsLabel, currentValues);
|
||||
}
|
||||
}
|
||||
|
||||
void GuiRandomCollectionOptions::saveSettings()
|
||||
{
|
||||
int curTrim = trimRandom->getSelected();
|
||||
int prevTrim = Settings::getInstance()->getInt("RandomCollectionMaxGames");
|
||||
Settings::getInstance()->setInt("RandomCollectionMaxGames", curTrim);
|
||||
|
||||
std::string curExclusion = exclusionCollection->getSelected();
|
||||
std::string prevExclusion = Settings::getInstance()->getString("RandomCollectionExclusionCollection");
|
||||
Settings::getInstance()->setString("RandomCollectionExclusionCollection", curExclusion);
|
||||
|
||||
mNeedsCollectionRefresh |= (curTrim != prevTrim || curExclusion != prevExclusion);
|
||||
|
||||
if (mNeedsCollectionRefresh)
|
||||
{
|
||||
Settings::getInstance()->saveFile();
|
||||
CollectionSystemManager::get()->recreateCollection(CollectionSystemManager::get()->getRandomCollection());
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool GuiRandomCollectionOptions::input(InputConfig* config, Input input)
|
||||
{
|
||||
bool consumed = GuiComponent::input(config, input);
|
||||
if(consumed)
|
||||
return true;
|
||||
|
||||
if(config->isMappedTo("b", input) && input.value != 0)
|
||||
saveSettings();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<HelpPrompt> GuiRandomCollectionOptions::getHelpPrompts()
|
||||
{
|
||||
std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
|
||||
prompts.push_back(HelpPrompt("b", "back"));
|
||||
return prompts;
|
||||
}
|
54
es-app/src/guis/GuiRandomCollectionOptions.h
Normal file
54
es-app/src/guis/GuiRandomCollectionOptions.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
#ifndef ES_APP_GUIS_GUI_RANDOM_COLLECTION_OPTIONS_H
|
||||
#define ES_APP_GUIS_GUI_RANDOM_COLLECTION_OPTIONS_H
|
||||
|
||||
#include "components/MenuComponent.h"
|
||||
|
||||
|
||||
template<typename T>
|
||||
class OptionListComponent;
|
||||
class SwitchComponent;
|
||||
class SystemData;
|
||||
class GuiSettings;
|
||||
struct CollectionSystemData;
|
||||
|
||||
typedef OptionListComponent<int> NumberList;
|
||||
struct SystemGames
|
||||
{
|
||||
std::string name;
|
||||
std::shared_ptr<NumberList> gamesSelection;
|
||||
};
|
||||
|
||||
class GuiRandomCollectionOptions : public GuiComponent
|
||||
{
|
||||
public:
|
||||
GuiRandomCollectionOptions(Window* window);
|
||||
~GuiRandomCollectionOptions();
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
|
||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
private:
|
||||
void initializeMenu();
|
||||
void saveSettings();
|
||||
void applyGroupSettings(std::string settingsLabel, const std::map<std::string, int> &initialValues, std::vector<SystemGames>* results);
|
||||
void addSystemsToMenu();
|
||||
void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func);
|
||||
void selectEntries(std::map<std::string, CollectionSystemData> collection, std::string settingsLabel, int defaultValue, std::vector< SystemGames>* results);
|
||||
|
||||
void selectSystems();
|
||||
void selectAutoCollections();
|
||||
void selectCustomCollections();
|
||||
|
||||
bool mNeedsCollectionRefresh;
|
||||
|
||||
std::vector< SystemGames> customCollectionLists;
|
||||
std::vector< SystemGames> autoCollectionLists;
|
||||
std::vector< SystemGames> systemLists;
|
||||
std::shared_ptr< NumberList> trimRandom;
|
||||
std::shared_ptr< OptionListComponent<std::string> > exclusionCollection;
|
||||
MenuComponent mMenu;
|
||||
SystemData* mSystem;
|
||||
};
|
||||
|
||||
#endif // ES_APP_GUIS_GUI_RANDOM_COLLECTION_OPTIONS_H
|
|
@ -66,6 +66,8 @@ GuiSlideshowScreensaverOptions::GuiSlideshowScreensaverOptions(Window* window, c
|
|||
|
||||
// custom video filter
|
||||
auto sss_video_filter = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
||||
// set y-size >0 on last TextComponent in menu to assure proper fit into available row height
|
||||
sss_video_filter->setSize(Vector2f(0, Font::get(FONT_SIZE_SMALL)->getLetterHeight()));
|
||||
addEditableTextComponent(row, "CUSTOM VIDEO FILTER", sss_video_filter, Settings::getInstance()->getString("SlideshowScreenSaverVideoFilter"));
|
||||
addSaveFunc([sss_video_filter] {
|
||||
Settings::getInstance()->setString("SlideshowScreenSaverVideoFilter", sss_video_filter->getValue());
|
||||
|
|
|
@ -52,7 +52,18 @@ bool parseArgs(int argc, char* argv[])
|
|||
|
||||
for(int i = 1; i < argc; i++)
|
||||
{
|
||||
if(strcmp(argv[i], "--resolution") == 0)
|
||||
if(strcmp(argv[i], "--monitor") == 0)
|
||||
{
|
||||
if (i >= argc - 1)
|
||||
{
|
||||
std::cerr << "Invalid monitor supplied.";
|
||||
return false;
|
||||
}
|
||||
|
||||
int monitor = atoi(argv[i + 1]);
|
||||
i++; // skip the argument value
|
||||
Settings::getInstance()->setInt("MonitorID", monitor);
|
||||
}else if(strcmp(argv[i], "--resolution") == 0)
|
||||
{
|
||||
if(i >= argc - 2)
|
||||
{
|
||||
|
@ -180,7 +191,9 @@ bool parseArgs(int argc, char* argv[])
|
|||
"--screensize WIDTH HEIGHT for a canvas smaller than the full resolution,\n"
|
||||
" or if rotating into portrait mode\n"
|
||||
"--screenoffset X Y move the canvas by x,y pixels\n"
|
||||
"--fullscreen-borderless borderless fullscreen window\n"
|
||||
"--windowed not fullscreen, should be used with --resolution\n"
|
||||
"--monitor N monitor index (0-)\n"
|
||||
"\nGame and settings visibility in ES and behaviour of ES:\n"
|
||||
"--force-disable-filters force the UI to ignore applied filters on\n"
|
||||
" gamelist (p)\n"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "Settings.h"
|
||||
#include "SystemData.h"
|
||||
#include "utils/TimeUtil.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
/* When raspbian will get an up to date version of rapidjson we'll be
|
||||
able to have it throw in case of error with the following:
|
||||
|
@ -109,6 +109,7 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
|
|||
{ TRS80_COLOR_COMPUTER, "4941" },
|
||||
{ TI_99, "4953" },
|
||||
{ TANDY, "4941" },
|
||||
{ FMTOWNS, "4932" },
|
||||
};
|
||||
|
||||
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "PlatformId.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemData.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <cstring>
|
||||
|
||||
using namespace PlatformIds;
|
||||
|
@ -100,7 +100,8 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
|
|||
{ TANDY, 144 },
|
||||
{ TI_99, 205 },
|
||||
{ DRAGON32, 91 },
|
||||
{ ZMACHINE, 21 }
|
||||
{ ZMACHINE, 21 },
|
||||
{ FMTOWNS, 253 }
|
||||
};
|
||||
|
||||
|
||||
|
@ -184,7 +185,7 @@ void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req, std::vec
|
|||
assert(req->status() == HttpReq::REQ_SUCCESS);
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result parseResult = doc.load(req->getContent().c_str());
|
||||
pugi::xml_parse_result parseResult = doc.load_string(req->getContent().c_str());
|
||||
|
||||
if (!parseResult)
|
||||
{
|
||||
|
|
|
@ -54,7 +54,7 @@ void ViewController::goToStart()
|
|||
if ((*it)->getName() == requestedSystem)
|
||||
{
|
||||
goToGameList(*it);
|
||||
Scripting::fireEvent("system-select", requestedSystem, "requestedsystem");
|
||||
Scripting::fireEvent("system-select", requestedSystem, "requestedsystem");
|
||||
FileData* cursor = getGameListView(*it)->getCursor();
|
||||
if (cursor != NULL)
|
||||
{
|
||||
|
@ -193,11 +193,50 @@ void ViewController::playViewTransition()
|
|||
}else{
|
||||
advanceAnimation(0, (int)(mFadeOpacity * FADE_DURATION));
|
||||
}
|
||||
} else if (transition_style == "slide"){
|
||||
}
|
||||
else if (transition_style == "slide")
|
||||
{
|
||||
// slide or simple slide
|
||||
setAnimation(new MoveCameraAnimation(mCamera, target));
|
||||
bool inGamelistNav = -mCamera.translation().y() == target.y() // not in/out gamelist nav
|
||||
&& -mCamera.translation().x() - target.x(); // left/right movement
|
||||
cancelAnimation(0);
|
||||
Vector3f tgt = Vector3f(target);
|
||||
Vector3f positionOrig;
|
||||
if (inGamelistNav) {
|
||||
const float screenWidth = (float)Renderer::getScreenWidth();
|
||||
if (-mCamera.translation().x() - tgt.x() >= 2 * screenWidth)
|
||||
{
|
||||
// right rollover
|
||||
mLockInput = true;
|
||||
tgt.x() = screenWidth * mGameListViews.size();
|
||||
}
|
||||
else if (-mCamera.translation().x() - tgt.x() <= 2 * -screenWidth)
|
||||
{
|
||||
// left rollover
|
||||
mLockInput = true;
|
||||
tgt.x() = -screenWidth;
|
||||
}
|
||||
// deny any further input on rollover as mCurrentView would be
|
||||
// different on subsequent animations, resulting in restoring
|
||||
// a unrelated mCurrentView/mCamera with the original position
|
||||
if (mLockInput)
|
||||
{
|
||||
positionOrig = Vector3f(mCurrentView->getPosition());
|
||||
mCurrentView->setPosition(tgt.x(), tgt.y());
|
||||
}
|
||||
}
|
||||
|
||||
setAnimation(new MoveCameraAnimation(mCamera, tgt), 0, [this, positionOrig] {
|
||||
if (mLockInput) {
|
||||
mCurrentView->setPosition(positionOrig);
|
||||
mCamera.translation() = -positionOrig;
|
||||
}
|
||||
mLockInput = false;
|
||||
});
|
||||
updateHelpPrompts(); // update help prompts immediately
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// instant
|
||||
setAnimation(new LambdaAnimation(
|
||||
[this, target](float /*t*/)
|
||||
|
@ -245,10 +284,12 @@ void ViewController::launch(FileData* game, Vector3f center)
|
|||
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc]
|
||||
{
|
||||
game->launchGame(mWindow);
|
||||
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this] { mLockInput = false; }, true);
|
||||
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game] { mLockInput = false; }, true);
|
||||
this->onFileChanged(game, FILE_METADATA_CHANGED);
|
||||
if (mCurrentView)
|
||||
if (mCurrentView) {
|
||||
this->getGameListView(game->getSystem())->setCursor(game, true);
|
||||
mCurrentView->onShow();
|
||||
}
|
||||
});
|
||||
} else if (transition_style == "slide"){
|
||||
// move camera to zoom in on center + fade out, launch game, come back in
|
||||
|
@ -256,20 +297,24 @@ void ViewController::launch(FileData* game, Vector3f center)
|
|||
{
|
||||
game->launchGame(mWindow);
|
||||
mCamera = origCamera;
|
||||
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, [this] { mLockInput = false; }, true);
|
||||
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, [this, game] { mLockInput = false; }, true);
|
||||
this->onFileChanged(game, FILE_METADATA_CHANGED);
|
||||
if (mCurrentView)
|
||||
if (mCurrentView) {
|
||||
this->getGameListView(game->getSystem())->setCursor(game, true);
|
||||
mCurrentView->onShow();
|
||||
}
|
||||
});
|
||||
} else { // instant
|
||||
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, origCamera, center, game]
|
||||
{
|
||||
game->launchGame(mWindow);
|
||||
mCamera = origCamera;
|
||||
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this] { mLockInput = false; }, true);
|
||||
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, game] { mLockInput = false; }, true);
|
||||
this->onFileChanged(game, FILE_METADATA_CHANGED);
|
||||
if (mCurrentView)
|
||||
if (mCurrentView) {
|
||||
this->getGameListView(game->getSystem())->setCursor(game, true);
|
||||
mCurrentView->onShow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -285,6 +330,24 @@ void ViewController::removeGameListView(SystemData* system)
|
|||
}
|
||||
}
|
||||
|
||||
ViewController::GameListViewType ViewController::getGameListViewType()
|
||||
{
|
||||
//decide type
|
||||
GameListViewType selectedViewType = AUTOMATIC;
|
||||
|
||||
std::string viewPreference = Settings::getInstance()->getString("GamelistViewStyle");
|
||||
if (viewPreference.compare("basic") == 0)
|
||||
selectedViewType = BASIC;
|
||||
if (viewPreference.compare("detailed") == 0)
|
||||
selectedViewType = DETAILED;
|
||||
if (viewPreference.compare("grid") == 0)
|
||||
selectedViewType = GRID;
|
||||
if (viewPreference.compare("video") == 0)
|
||||
selectedViewType = VIDEO;
|
||||
|
||||
return selectedViewType;
|
||||
}
|
||||
|
||||
std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* system)
|
||||
{
|
||||
//if we already made one, return that one
|
||||
|
@ -299,17 +362,7 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
|
|||
bool themeHasVideoView = system->getTheme()->hasView("video");
|
||||
|
||||
//decide type
|
||||
GameListViewType selectedViewType = AUTOMATIC;
|
||||
|
||||
std::string viewPreference = Settings::getInstance()->getString("GamelistViewStyle");
|
||||
if (viewPreference.compare("basic") == 0)
|
||||
selectedViewType = BASIC;
|
||||
if (viewPreference.compare("detailed") == 0)
|
||||
selectedViewType = DETAILED;
|
||||
if (viewPreference.compare("grid") == 0)
|
||||
selectedViewType = GRID;
|
||||
if (viewPreference.compare("video") == 0)
|
||||
selectedViewType = VIDEO;
|
||||
GameListViewType selectedViewType = getGameListViewType();
|
||||
|
||||
if (selectedViewType == AUTOMATIC)
|
||||
{
|
||||
|
@ -477,6 +530,7 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
|||
bool isCurrent = (mCurrentView == it->second);
|
||||
SystemData* system = it->first;
|
||||
FileData* cursor = view->getCursor();
|
||||
int viewportTop = view->getViewportTop();
|
||||
mGameListViews.erase(it);
|
||||
|
||||
if(reloadTheme)
|
||||
|
@ -487,6 +541,7 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
|||
// to counter having come from a placeholder
|
||||
if (!cursor->isPlaceHolder()) {
|
||||
newView->setCursor(cursor);
|
||||
newView->setViewportTop(viewportTop);
|
||||
}
|
||||
if(isCurrent)
|
||||
mCurrentView = newView;
|
||||
|
@ -500,17 +555,18 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
|||
|
||||
}
|
||||
|
||||
void ViewController::reloadAll()
|
||||
void ViewController::reloadAll(bool themeChanged)
|
||||
{
|
||||
// clear all gamelistviews
|
||||
std::map<SystemData*, FileData*> cursorMap;
|
||||
std::map<SystemData*, int> viewportTopMap;
|
||||
for(auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++)
|
||||
{
|
||||
cursorMap[it->first] = it->second->getCursor();
|
||||
viewportTopMap[it->first] = it->second->getViewportTop();
|
||||
}
|
||||
mGameListViews.clear();
|
||||
|
||||
|
||||
// load themes, create gamelistviews and reset filters
|
||||
for(auto it = cursorMap.cbegin(); it != cursorMap.cend(); it++)
|
||||
{
|
||||
|
@ -519,6 +575,15 @@ void ViewController::reloadAll()
|
|||
getGameListView(it->first)->setCursor(it->second);
|
||||
}
|
||||
|
||||
if(!themeChanged || !Settings::getInstance()->getBool("UseFullscreenPaging"))
|
||||
{
|
||||
// restore index of first list item on display
|
||||
for(auto it = viewportTopMap.cbegin(); it != viewportTopMap.cend(); it++)
|
||||
{
|
||||
getGameListView(it->first)->setViewportTop(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild SystemListView
|
||||
mSystemListView.reset();
|
||||
getSystemListView();
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
// the current gamelist view (as it may change to be detailed).
|
||||
void reloadGameListView(IGameListView* gamelist, bool reloadTheme = false);
|
||||
inline void reloadGameListView(SystemData* system, bool reloadTheme = false) { reloadGameListView(getGameListView(system).get(), reloadTheme); }
|
||||
void reloadAll(); // Reload everything with a theme. Used when the "ThemeSet" setting changes.
|
||||
void reloadAll(bool themeChanged = false); // Reload everything with a theme. When the "ThemeSet" setting changes, themeChanged is true.
|
||||
|
||||
// Navigation.
|
||||
void goToNextGameList();
|
||||
|
@ -65,6 +65,8 @@ public:
|
|||
VIDEO
|
||||
};
|
||||
|
||||
ViewController::GameListViewType getGameListViewType();
|
||||
|
||||
struct State
|
||||
{
|
||||
ViewMode viewing;
|
||||
|
|
|
@ -61,12 +61,19 @@ FileData* BasicGameListView::getCursor()
|
|||
return mList.getSelected();
|
||||
}
|
||||
|
||||
void BasicGameListView::setCursor(FileData* cursor)
|
||||
void BasicGameListView::setCursor(FileData* cursor, bool refreshListCursorPos)
|
||||
{
|
||||
if(!mList.setCursor(cursor) && (!cursor->isPlaceHolder()))
|
||||
if (refreshListCursorPos)
|
||||
setViewportTop(mList.REFRESH_LIST_CURSOR_POS);
|
||||
|
||||
bool notInList = !mList.setCursor(cursor);
|
||||
if(!refreshListCursorPos && notInList && !cursor->isPlaceHolder())
|
||||
{
|
||||
populateList(cursor->getParent()->getChildrenListToDisplay());
|
||||
mList.setCursor(cursor);
|
||||
// this extra call is needed iff a system has games organized in folders
|
||||
// and the cursor is focusing a game in a folder
|
||||
if (cursor->getParent()->getType() == FOLDER)
|
||||
mList.setCursor(cursor);
|
||||
|
||||
// update our cursor stack in case our cursor just got set to some folder we weren't in before
|
||||
if(mCursorStack.empty() || mCursorStack.top() != cursor->getParent())
|
||||
|
@ -90,6 +97,17 @@ void BasicGameListView::setCursor(FileData* cursor)
|
|||
}
|
||||
}
|
||||
|
||||
void BasicGameListView::setViewportTop(int index)
|
||||
{
|
||||
mList.setViewportTop(index);
|
||||
}
|
||||
|
||||
|
||||
int BasicGameListView::getViewportTop()
|
||||
{
|
||||
return mList.getViewportTop();
|
||||
}
|
||||
|
||||
void BasicGameListView::addPlaceholder()
|
||||
{
|
||||
// empty list - add a placeholder
|
||||
|
|
|
@ -11,12 +11,14 @@ public:
|
|||
BasicGameListView(Window* window, FileData* root);
|
||||
|
||||
// Called when a FileData* is added, has its metadata changed, or is removed
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change);
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change) override;
|
||||
|
||||
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
|
||||
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
|
||||
|
||||
virtual FileData* getCursor() override;
|
||||
virtual void setCursor(FileData* file) override;
|
||||
virtual void setCursor(FileData* file, bool refreshListCursorPos = false) override;
|
||||
virtual int getViewportTop() override;
|
||||
virtual void setViewportTop(int index) override;
|
||||
|
||||
virtual const char* getName() const override { return "basic"; }
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ FileData* GridGameListView::getCursor()
|
|||
return mGrid.getSelected();
|
||||
}
|
||||
|
||||
void GridGameListView::setCursor(FileData* file)
|
||||
void GridGameListView::setCursor(FileData* file, bool refreshListCursorPos)
|
||||
{
|
||||
if(!mGrid.setCursor(file) && (!file->isPlaceHolder()))
|
||||
{
|
||||
|
|
|
@ -20,7 +20,9 @@ public:
|
|||
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
|
||||
|
||||
virtual FileData* getCursor() override;
|
||||
virtual void setCursor(FileData*) override;
|
||||
virtual void setCursor(FileData*, bool refreshListCursorPos = false) override;
|
||||
virtual void setViewportTop(int index) override { ; }
|
||||
virtual int getViewportTop() override { return -1; }
|
||||
|
||||
virtual bool input(InputConfig* config, Input input) override;
|
||||
|
||||
|
|
|
@ -30,7 +30,12 @@ public:
|
|||
inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; }
|
||||
|
||||
virtual FileData* getCursor() = 0;
|
||||
virtual void setCursor(FileData*) = 0;
|
||||
// if flag is true then the cursor position on the visible gamelist section on screen is recalculated
|
||||
// used only in list based views and only to be set true when there is no previous navigation to a game
|
||||
// see also: TextListComponent.REFRESH_LIST_CURSOR_POS for the use 'true' flag
|
||||
virtual void setCursor(FileData*, bool refreshListCursorPos = false) = 0;
|
||||
virtual int getViewportTop() = 0;
|
||||
virtual void setViewportTop(int index) = 0;
|
||||
|
||||
virtual bool input(InputConfig* config, Input input) override;
|
||||
virtual void remove(FileData* game, bool deleteFile, bool refreshView=true) = 0;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "Settings.h"
|
||||
#include "Sound.h"
|
||||
#include "SystemData.h"
|
||||
#include <SDL_timer.h>
|
||||
|
||||
ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGameListView(window, root),
|
||||
mHeaderText(window), mHeaderImage(window), mBackground(window)
|
||||
|
@ -152,8 +151,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
|||
{
|
||||
if(mRoot->getSystem()->isGameSystem())
|
||||
{
|
||||
int presscount = getPressCountInDuration();
|
||||
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor(), presscount))
|
||||
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -171,15 +169,4 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
|||
Scripting::fireEvent("game-select", "NULL", "NULL", "NULL", "input");
|
||||
}
|
||||
return IGameListView::input(config, input);
|
||||
}
|
||||
|
||||
|
||||
int ISimpleGameListView::getPressCountInDuration() {
|
||||
Uint32 now = SDL_GetTicks();
|
||||
if (now - firstPressMs < DOUBLE_PRESS_DETECTION_DURATION) {
|
||||
return 2;
|
||||
} else {
|
||||
firstPressMs = now;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,18 +17,18 @@ public:
|
|||
// Called when a new file is added, a file is removed, a file's metadata changes, or a file's children are sorted.
|
||||
// NOTE: FILE_SORTED is only reported for the topmost FileData, where the sort started.
|
||||
// Since sorts are recursive, that FileData's children probably changed too.
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change);
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change) override;
|
||||
|
||||
// Called whenever the theme changes.
|
||||
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
|
||||
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
|
||||
|
||||
virtual FileData* getCursor() = 0;
|
||||
virtual void setCursor(FileData*) = 0;
|
||||
virtual FileData* getCursor() override = 0;
|
||||
virtual void setCursor(FileData*, bool refreshListCursorPos = false) override = 0;
|
||||
virtual int getViewportTop() override = 0;
|
||||
virtual void setViewportTop(int index) override = 0;
|
||||
|
||||
virtual bool input(InputConfig* config, Input input) override;
|
||||
virtual void launch(FileData* game) = 0;
|
||||
|
||||
static const int DOUBLE_PRESS_DETECTION_DURATION = 1500; // millis
|
||||
virtual void launch(FileData* game) override = 0;
|
||||
|
||||
protected:
|
||||
static const int DESCRIPTION_SCROLL_DELAY = 5 * 1000; // five secs
|
||||
|
@ -47,7 +47,6 @@ protected:
|
|||
|
||||
private:
|
||||
int getPressCountInDuration();
|
||||
Uint32 firstPressMs = 0;
|
||||
};
|
||||
|
||||
#endif // ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H
|
||||
|
|
|
@ -77,7 +77,13 @@ HttpReq::HttpReq(const std::string& url)
|
|||
}
|
||||
|
||||
//set curl restrict redirect protocols
|
||||
//starting with 7.85.0, CURLOPT_REDIR_PROTOCOLS is deprecated
|
||||
// and CURLOPT_REDIR_PROTOCOLS_STR should be used instead
|
||||
#if CURL_AT_LEAST_VERSION(7,85,0)
|
||||
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
|
||||
#else
|
||||
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
#endif
|
||||
if(err != CURLE_OK)
|
||||
{
|
||||
mStatus = REQ_IO_ERROR;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#include "InputConfig.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include "utils/StringUtil.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
//some util functions
|
||||
std::string inputTypeToString(InputType type)
|
||||
|
@ -39,19 +40,10 @@ InputType stringToInputType(const std::string& type)
|
|||
}
|
||||
|
||||
|
||||
std::string toLower(std::string str)
|
||||
{
|
||||
for(unsigned int i = 0; i < str.length(); i++)
|
||||
{
|
||||
str[i] = (char)tolower(str[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
//end util functions
|
||||
|
||||
InputConfig::InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID) : mDeviceId(deviceId), mDeviceName(deviceName), mDeviceGUID(deviceGUID)
|
||||
{
|
||||
mVendorId = 0;
|
||||
mProductId = 0;
|
||||
}
|
||||
|
||||
void InputConfig::clear()
|
||||
|
@ -66,19 +58,19 @@ bool InputConfig::isConfigured()
|
|||
|
||||
void InputConfig::mapInput(const std::string& name, Input input)
|
||||
{
|
||||
mNameMap[toLower(name)] = input;
|
||||
mNameMap[Utils::String::toLower(name)] = input;
|
||||
}
|
||||
|
||||
void InputConfig::unmapInput(const std::string& name)
|
||||
{
|
||||
auto it = mNameMap.find(toLower(name));
|
||||
auto it = mNameMap.find(Utils::String::toLower(name));
|
||||
if(it != mNameMap.cend())
|
||||
mNameMap.erase(it);
|
||||
}
|
||||
|
||||
bool InputConfig::getInputByName(const std::string& name, Input* result)
|
||||
{
|
||||
auto it = mNameMap.find(toLower(name));
|
||||
auto it = mNameMap.find(Utils::String::toLower(name));
|
||||
if(it != mNameMap.cend())
|
||||
{
|
||||
*result = it->second;
|
||||
|
@ -188,7 +180,7 @@ void InputConfig::loadFromXML(pugi::xml_node& node)
|
|||
if(value == 0)
|
||||
LOG(LogWarning) << "WARNING: InputConfig value is 0 for " << type << " " << id << "!\n";
|
||||
|
||||
mNameMap[toLower(name)] = Input(mDeviceId, typeEnum, id, value, true);
|
||||
mNameMap[Utils::String::toLower(name)] = Input(mDeviceId, typeEnum, id, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,6 +202,11 @@ void InputConfig::writeToXML(pugi::xml_node& parent)
|
|||
{
|
||||
cfg.append_attribute("type") = "joystick";
|
||||
cfg.append_attribute("deviceName") = mDeviceName.c_str();
|
||||
if(mVendorId && mProductId)
|
||||
{
|
||||
cfg.append_attribute("vendorId") = mVendorId;
|
||||
cfg.append_attribute("productId") = mProductId;
|
||||
}
|
||||
}
|
||||
|
||||
cfg.append_attribute("deviceGUID") = mDeviceGUID.c_str();
|
||||
|
|
|
@ -102,9 +102,14 @@ public:
|
|||
void mapInput(const std::string& name, Input input);
|
||||
void unmapInput(const std::string& name); // unmap all Inputs mapped to this name
|
||||
|
||||
inline int getDeviceId() const { return mDeviceId; };
|
||||
inline int getDeviceId() const { return mDeviceId; }
|
||||
inline const std::string& getDeviceName() { return mDeviceName; }
|
||||
inline const std::string& getDeviceGUIDString() { return mDeviceGUID; }
|
||||
inline const unsigned short getVendorId() { return mVendorId; }
|
||||
inline const unsigned short getProductId() { return mProductId; }
|
||||
|
||||
inline void setVendorId(unsigned short vendorID) { mVendorId = vendorID; }
|
||||
inline void setProductId(unsigned short productID) { mProductId = productID; }
|
||||
|
||||
//Returns true if Input is mapped to this name, false otherwise.
|
||||
bool isMappedTo(const std::string& name, Input input);
|
||||
|
@ -127,6 +132,9 @@ private:
|
|||
const int mDeviceId;
|
||||
const std::string mDeviceName;
|
||||
const std::string mDeviceGUID;
|
||||
|
||||
unsigned short mVendorId;
|
||||
unsigned short mProductId;
|
||||
};
|
||||
|
||||
#endif // ES_CORE_INPUT_CONFIG_H
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "platform.h"
|
||||
#include "Scripting.h"
|
||||
#include "Window.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <SDL.h>
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
|
@ -100,6 +100,11 @@ void InputManager::addJoystickByDeviceIndex(int id)
|
|||
|
||||
// create the InputConfig
|
||||
mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid);
|
||||
|
||||
// add Vendor and Product IDs
|
||||
mInputConfigs[joyId]->setVendorId(SDL_JoystickGetVendor(joy));
|
||||
mInputConfigs[joyId]->setProductId(SDL_JoystickGetProduct(joy));
|
||||
|
||||
if(!loadInputConfig(mInputConfigs[joyId]))
|
||||
{
|
||||
LOG(LogInfo) << "Added unconfigured joystick '" << SDL_JoystickName(joy) << "' (GUID: " << guid << ", instance ID: " << joyId << ", device index: " << id << ").";
|
||||
|
|
|
@ -53,6 +53,7 @@ void Log::flush()
|
|||
|
||||
void Log::close()
|
||||
{
|
||||
if(file == NULL) return;
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
#include <sstream>
|
||||
|
||||
#define LOG(level) \
|
||||
if(level > Log::getReportingLevel()) ; \
|
||||
else Log().get(level)
|
||||
if(level <= Log::getReportingLevel()) \
|
||||
Log().get(level)
|
||||
|
||||
enum LogLevel { LogError, LogWarning, LogInfo, LogDebug };
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "resources/ResourceManager.h"
|
||||
#include "utils/FileSystemUtil.h"
|
||||
#include "Log.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <string.h>
|
||||
|
||||
MameNames* MameNames::sInstance = nullptr;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "Log.h"
|
||||
#include "Scripting.h"
|
||||
#include "platform.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
|
@ -13,27 +13,28 @@ Settings* Settings::sInstance = NULL;
|
|||
// these values are NOT saved to es_settings.xml
|
||||
// since they're set through command-line arguments, and not the in-program settings menu
|
||||
std::vector<const char*> settings_dont_save {
|
||||
{ "Debug" },
|
||||
{ "DebugGrid" },
|
||||
{ "DebugText" },
|
||||
{ "DebugImage" },
|
||||
{ "ForceKid" },
|
||||
{ "ForceKiosk" },
|
||||
{ "IgnoreGamelist" },
|
||||
{ "HideConsole" },
|
||||
{ "ShowExit" },
|
||||
{ "ConfirmQuit" },
|
||||
{ "SplashScreen" },
|
||||
{ "VSync" },
|
||||
{ "FullscreenBorderless" },
|
||||
{ "Windowed" },
|
||||
{ "WindowWidth" },
|
||||
{ "WindowHeight" },
|
||||
{ "ScreenWidth" },
|
||||
{ "ScreenHeight" },
|
||||
{ "ScreenOffsetX" },
|
||||
{ "ScreenOffsetY" },
|
||||
{ "ScreenRotate" }
|
||||
"Debug",
|
||||
"DebugGrid",
|
||||
"DebugText",
|
||||
"DebugImage",
|
||||
"ForceKid",
|
||||
"ForceKiosk",
|
||||
"IgnoreGamelist",
|
||||
"HideConsole",
|
||||
"ShowExit",
|
||||
"ConfirmQuit",
|
||||
"SplashScreen",
|
||||
"VSync",
|
||||
"FullscreenBorderless",
|
||||
"Windowed",
|
||||
"WindowWidth",
|
||||
"WindowHeight",
|
||||
"ScreenWidth",
|
||||
"ScreenHeight",
|
||||
"ScreenOffsetX",
|
||||
"ScreenOffsetY",
|
||||
"ScreenRotate",
|
||||
"MonitorID"
|
||||
};
|
||||
|
||||
Settings::Settings()
|
||||
|
@ -139,8 +140,17 @@ void Settings::setDefaults()
|
|||
mStringMap["VlcScreenSaverResolution"] = "original";
|
||||
// Audio out device for Video playback using OMX player.
|
||||
mStringMap["OMXAudioDev"] = "both";
|
||||
mIntMap["RandomCollectionMaxGames"] = 0; // 0 == no limit
|
||||
std::map<std::string, int> m1;
|
||||
mMapIntMap["RandomCollectionSystemsAuto"] = m1;
|
||||
std::map<std::string, int> m2;
|
||||
mMapIntMap["RandomCollectionSystemsCustom"] = m2;
|
||||
std::map<std::string, int> m3;
|
||||
mMapIntMap["RandomCollectionSystems"] = m3;
|
||||
mStringMap["RandomCollectionExclusionCollection"] = "";
|
||||
mStringMap["CollectionSystemsAuto"] = "";
|
||||
mStringMap["CollectionSystemsCustom"] = "";
|
||||
mStringMap["DefaultScreenSaverCollection"] = "";
|
||||
mBoolMap["CollectionShowSystemInfo"] = true;
|
||||
mBoolMap["SortAllSystems"] = false;
|
||||
mBoolMap["UseCustomCollectionsSystem"] = true;
|
||||
|
@ -169,6 +179,7 @@ void Settings::setDefaults()
|
|||
mIntMap["ScreenOffsetX"] = 0;
|
||||
mIntMap["ScreenOffsetY"] = 0;
|
||||
mIntMap["ScreenRotate"] = 0;
|
||||
mIntMap["MonitorID"] = 0;
|
||||
|
||||
mBoolMap["UseFullscreenPaging"] = false;
|
||||
|
||||
|
@ -212,6 +223,20 @@ void Settings::saveFile()
|
|||
node.append_attribute("value").set_value(iter->second.c_str());
|
||||
}
|
||||
|
||||
for(auto &m : mMapIntMap)
|
||||
{
|
||||
pugi::xml_node node = doc.append_child("map");
|
||||
node.append_attribute("name").set_value(m.first.c_str());
|
||||
std::string datatype = "int";
|
||||
node.append_attribute("type").set_value(datatype.c_str());
|
||||
for(auto &intMap : m.second) // intMap is a <string, int> map
|
||||
{
|
||||
pugi::xml_node entry = node.append_child(datatype.c_str());
|
||||
entry.append_attribute("name").set_value(intMap.first.c_str());
|
||||
entry.append_attribute("value").set_value(intMap.second);
|
||||
}
|
||||
}
|
||||
|
||||
doc.save_file(path.c_str());
|
||||
|
||||
Scripting::fireEvent("config-changed");
|
||||
|
@ -242,9 +267,45 @@ void Settings::loadFile()
|
|||
for(pugi::xml_node node = doc.child("string"); node; node = node.next_sibling("string"))
|
||||
setString(node.attribute("name").as_string(), node.attribute("value").as_string());
|
||||
|
||||
for(pugi::xml_node node = doc.child("map"); node; node = node.next_sibling("map"))
|
||||
{
|
||||
std::string mapName = node.attribute("name").as_string();
|
||||
std::string mapType = node.attribute("type").as_string();
|
||||
if (mapType == "int") {
|
||||
// only supporting int value maps currently
|
||||
std::map<std::string, int> _map;
|
||||
for(pugi::xml_node entry : node.children(mapType.c_str()))
|
||||
{
|
||||
_map[entry.attribute("name").as_string()] = entry.attribute("value").as_int();
|
||||
}
|
||||
setMap(mapName, _map);
|
||||
} else {
|
||||
LOG(LogWarning) << "Map: '" << mapName << "'. Unsupported data type '"<< mapType <<"'. Value ignored!";
|
||||
}
|
||||
}
|
||||
|
||||
processBackwardCompatibility();
|
||||
}
|
||||
|
||||
|
||||
void Settings::setMap(const std::string& key, const std::map<std::string, int>& map)
|
||||
{
|
||||
mMapIntMap[key] = map;
|
||||
}
|
||||
|
||||
const std::map<std::string, int> Settings::getMap(const std::string& key)
|
||||
{
|
||||
if(mMapIntMap.find(key) == mMapIntMap.cend())
|
||||
{
|
||||
LOG(LogError) << "Tried to use undefined setting " << key << "!";
|
||||
std::map<std::string, int> empty;
|
||||
return empty;
|
||||
|
||||
}
|
||||
return mMapIntMap[key];
|
||||
}
|
||||
|
||||
|
||||
template<typename Map>
|
||||
void Settings::renameSetting(Map& map, std::string&& oldName, std::string&& newName)
|
||||
{
|
||||
|
|
|
@ -20,11 +20,13 @@ public:
|
|||
int getInt(const std::string& name);
|
||||
float getFloat(const std::string& name);
|
||||
const std::string& getString(const std::string& name);
|
||||
const std::map<std::string, int> getMap(const std::string& name);
|
||||
|
||||
void setBool(const std::string& name, bool value);
|
||||
void setInt(const std::string& name, int value);
|
||||
void setFloat(const std::string& name, float value);
|
||||
void setString(const std::string& name, const std::string& value);
|
||||
void setMap(const std::string& name, const std::map<std::string, int>& map);
|
||||
|
||||
private:
|
||||
static Settings* sInstance;
|
||||
|
@ -40,6 +42,7 @@ private:
|
|||
std::map<std::string, int> mIntMap;
|
||||
std::map<std::string, float> mFloatMap;
|
||||
std::map<std::string, std::string> mStringMap;
|
||||
std::map<std::string, std::map<std::string, int>> mMapIntMap;
|
||||
};
|
||||
|
||||
#endif // ES_CORE_SETTINGS_H
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "Log.h"
|
||||
#include "platform.h"
|
||||
#include "Settings.h"
|
||||
#include <pugixml/src/pugixml.hpp>
|
||||
#include <pugixml.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
std::vector<std::string> ThemeData::sSupportedViews { { "system" }, { "basic" }, { "detailed" }, { "grid" }, { "video" } };
|
||||
|
|
|
@ -117,8 +117,9 @@ void Window::textInput(const char* text)
|
|||
void Window::input(InputConfig* config, Input input)
|
||||
{
|
||||
if (mScreenSaver && mScreenSaver->isScreenSaverActive() && Settings::getInstance()->getBool("ScreenSaverControls")
|
||||
&& inputDuringScreensaver(config, input))
|
||||
&& mScreenSaver->inputDuringScreensaver(config, input))
|
||||
{
|
||||
mTimeSinceLastInput = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -131,7 +132,7 @@ void Window::input(InputConfig* config, Input input)
|
|||
}
|
||||
|
||||
mTimeSinceLastInput = 0;
|
||||
if (cancelScreenSaver())
|
||||
if (input.value != 0 && cancelScreenSaver())
|
||||
return;
|
||||
|
||||
bool dbg_keyboard_key_press = Settings::getInstance()->getBool("Debug") && config->getDeviceId() == DEVICE_KEYBOARD && input.value;
|
||||
|
@ -156,37 +157,6 @@ void Window::input(InputConfig* config, Input input)
|
|||
}
|
||||
}
|
||||
|
||||
bool Window::inputDuringScreensaver(InputConfig* config, Input input)
|
||||
{
|
||||
bool input_consumed = false;
|
||||
std::string screensaver_type = Settings::getInstance()->getString("ScreenSaverBehavior");
|
||||
|
||||
if (screensaver_type == "random video" || screensaver_type == "slideshow")
|
||||
{
|
||||
bool is_select_input = config->isMappedLike("right", input) || config->isMappedTo("select", input);
|
||||
bool is_start_input = config->isMappedTo("start", input);
|
||||
|
||||
if (is_select_input)
|
||||
{
|
||||
if (input.value) {
|
||||
mScreenSaver->nextMediaItem();
|
||||
// user input resets sleep time counter
|
||||
mTimeSinceLastInput = 0;
|
||||
}
|
||||
input_consumed = true;
|
||||
}
|
||||
else if (is_start_input)
|
||||
{
|
||||
bool slideshow_custom_images = Settings::getInstance()->getBool("SlideshowScreenSaverCustomMediaSource");
|
||||
if (screensaver_type == "random video" || !slideshow_custom_images)
|
||||
{
|
||||
mScreenSaver->launchGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
return input_consumed;
|
||||
}
|
||||
|
||||
void Window::update(int deltaTime)
|
||||
{
|
||||
if(mNormalizeNextUpdate)
|
||||
|
@ -271,7 +241,7 @@ void Window::render()
|
|||
// or not because it may perform a fade on transition
|
||||
renderScreenSaver();
|
||||
|
||||
if(!mRenderScreenSaver && mInfoPopup)
|
||||
if(mInfoPopup)
|
||||
{
|
||||
mInfoPopup->render(transform);
|
||||
}
|
||||
|
@ -448,7 +418,7 @@ bool Window::isProcessing()
|
|||
return count_if(mGuiStack.cbegin(), mGuiStack.cend(), [](GuiComponent* c) { return c->isProcessing(); }) > 0;
|
||||
}
|
||||
|
||||
void Window::startScreenSaver()
|
||||
void Window::startScreenSaver(SystemData* system)
|
||||
{
|
||||
if (mScreenSaver && !mRenderScreenSaver)
|
||||
{
|
||||
|
@ -457,7 +427,7 @@ void Window::startScreenSaver()
|
|||
for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
|
||||
(*i)->onScreenSaverActivate();
|
||||
|
||||
mScreenSaver->startScreenSaver();
|
||||
mScreenSaver->startScreenSaver(system);
|
||||
mRenderScreenSaver = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
class SystemData;
|
||||
class FileData;
|
||||
class Font;
|
||||
class GuiComponent;
|
||||
|
@ -23,15 +24,15 @@ class Window
|
|||
public:
|
||||
class ScreenSaver {
|
||||
public:
|
||||
virtual void startScreenSaver() = 0;
|
||||
virtual void stopScreenSaver() = 0;
|
||||
virtual void nextMediaItem() = 0;
|
||||
virtual void startScreenSaver(SystemData* system=NULL) = 0;
|
||||
virtual void stopScreenSaver(bool toResume=false) = 0;
|
||||
virtual void renderScreenSaver() = 0;
|
||||
virtual bool allowSleep() = 0;
|
||||
virtual void update(int deltaTime) = 0;
|
||||
virtual bool isScreenSaverActive() = 0;
|
||||
virtual FileData* getCurrentGame() = 0;
|
||||
virtual void launchGame() = 0;
|
||||
virtual void selectGame(bool launch) = 0;
|
||||
virtual bool inputDuringScreensaver(InputConfig* config, Input input) = 0;
|
||||
};
|
||||
|
||||
class InfoPopup {
|
||||
|
@ -72,14 +73,13 @@ public:
|
|||
void setInfoPopup(InfoPopup* infoPopup) { delete mInfoPopup; mInfoPopup = infoPopup; }
|
||||
inline void stopInfoPopup() { if (mInfoPopup) mInfoPopup->stop(); };
|
||||
|
||||
void startScreenSaver();
|
||||
void startScreenSaver(SystemData* system=NULL);
|
||||
bool cancelScreenSaver();
|
||||
void renderScreenSaver();
|
||||
|
||||
private:
|
||||
void onSleep();
|
||||
void onWake();
|
||||
bool inputDuringScreensaver(InputConfig* config, Input input);
|
||||
|
||||
// Returns true if at least one component on the stack is processing
|
||||
bool isProcessing();
|
||||
|
|
|
@ -7,6 +7,7 @@ class Animation
|
|||
public:
|
||||
virtual int getDuration() const = 0;
|
||||
virtual void apply(float t) = 0;
|
||||
virtual ~Animation() = default;
|
||||
};
|
||||
|
||||
#endif // ES_CORE_ANIMATIONS_ANIMATION_H
|
||||
|
|
|
@ -4,15 +4,17 @@
|
|||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
DateTimeComponent::DateTimeComponent(Window* window) : TextComponent(window), mDisplayRelative(false)
|
||||
{
|
||||
setFormat("%m/%d/%Y");
|
||||
setFormat(getDateformat());
|
||||
}
|
||||
|
||||
DateTimeComponent::DateTimeComponent(Window* window, const std::string& text, const std::shared_ptr<Font>& font, unsigned int color, Alignment align,
|
||||
Vector3f pos, Vector2f size, unsigned int bgcolor) : TextComponent(window, text, font, color, align, pos, size, bgcolor), mDisplayRelative(false)
|
||||
{
|
||||
setFormat("%m/%d/%Y");
|
||||
setFormat(getDateformat());
|
||||
}
|
||||
|
||||
void DateTimeComponent::setValue(const std::string& val)
|
||||
|
@ -47,9 +49,13 @@ void DateTimeComponent::onTextChanged()
|
|||
|
||||
std::string DateTimeComponent::getDisplayString() const
|
||||
{
|
||||
if(std::difftime(mTime.getTime(), Utils::Time::BLANK_DATE) == 0.0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (mDisplayRelative) {
|
||||
//relative time
|
||||
if(mTime.getTime() == 0)
|
||||
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
|
||||
return "never";
|
||||
|
||||
Utils::Time::DateTime now(Utils::Time::now());
|
||||
|
@ -69,7 +75,7 @@ std::string DateTimeComponent::getDisplayString() const
|
|||
return std::string(buf);
|
||||
}
|
||||
|
||||
if(mTime.getTime() == 0)
|
||||
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
|
||||
return "unknown";
|
||||
|
||||
return Utils::Time::timeToString(mTime.getTime(), mFormat);
|
||||
|
|
|
@ -25,6 +25,9 @@ public:
|
|||
|
||||
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
|
||||
|
||||
static std::string getDateformat() { return "%m/%d/%Y"; }
|
||||
static std::string getDateformatTip() { return "MM/DD/YYYY"; }
|
||||
|
||||
protected:
|
||||
void onTextChanged() override;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "components/DateTimeEditComponent.h"
|
||||
|
||||
#include "DateTimeComponent.h"
|
||||
#include "resources/Font.h"
|
||||
#include "utils/StringUtil.h"
|
||||
|
||||
|
@ -196,17 +197,17 @@ std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const
|
|||
switch(mode)
|
||||
{
|
||||
case DISP_DATE:
|
||||
fmt = "%m/%d/%Y";
|
||||
fmt = DateTimeComponent::getDateformat();
|
||||
break;
|
||||
case DISP_DATE_TIME:
|
||||
if(mTime.getTime() == 0)
|
||||
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
|
||||
return "unknown";
|
||||
fmt = "%m/%d/%Y %H:%M:%S";
|
||||
fmt = DateTimeComponent::getDateformat() + " %H:%M:%S";
|
||||
break;
|
||||
case DISP_RELATIVE_TO_NOW:
|
||||
{
|
||||
//relative time
|
||||
if(mTime.getTime() == 0)
|
||||
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
|
||||
return "never";
|
||||
|
||||
Utils::Time::DateTime now(Utils::Time::now());
|
||||
|
|
|
@ -22,7 +22,7 @@ public:
|
|||
GridTileComponent(Window* window);
|
||||
|
||||
void render(const Transform4x4f& parentTrans) override;
|
||||
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties);
|
||||
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
|
||||
|
||||
// Made this a static function because the ImageGridComponent need to know the default tile max size
|
||||
// to calculate the grid dimension before it instantiate the GridTileComponents
|
||||
|
@ -41,7 +41,7 @@ public:
|
|||
|
||||
Vector3f getBackgroundPosition();
|
||||
|
||||
virtual void update(int deltaTime);
|
||||
virtual void update(int deltaTime) override;
|
||||
|
||||
std::shared_ptr<TextureResource> getTexture();
|
||||
|
||||
|
|
|
@ -58,7 +58,10 @@ public:
|
|||
};
|
||||
|
||||
protected:
|
||||
struct Entry mEntry;
|
||||
|
||||
int mCursor;
|
||||
int mViewportTop;
|
||||
|
||||
int mScrollTier;
|
||||
int mScrollVelocity;
|
||||
|
@ -81,6 +84,7 @@ public:
|
|||
mGradient(window), mTierList(tierList), mLoopType(loopType)
|
||||
{
|
||||
mCursor = 0;
|
||||
mViewportTop = 0;
|
||||
mScrollTier = 0;
|
||||
mScrollVelocity = 0;
|
||||
mScrollTierAccumulator = 0;
|
||||
|
@ -158,6 +162,16 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
void setViewportTop(int index)
|
||||
{
|
||||
mViewportTop = index;
|
||||
}
|
||||
|
||||
int getViewportTop()
|
||||
{
|
||||
return mViewportTop;
|
||||
}
|
||||
|
||||
// entry management
|
||||
void add(const Entry& e)
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ protected:
|
|||
using IList<ImageGridData, T>::getTransform;
|
||||
using IList<ImageGridData, T>::mSize;
|
||||
using IList<ImageGridData, T>::mCursor;
|
||||
using IList<ImageGridData, T>::Entry;
|
||||
using IList<ImageGridData, T>::mEntry;
|
||||
using IList<ImageGridData, T>::mWindow;
|
||||
|
||||
public:
|
||||
|
@ -305,7 +305,9 @@ void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|||
std::string path = elem->get<std::string>("gameImage");
|
||||
|
||||
if (!ResourceManager::getInstance()->fileExists(path))
|
||||
{
|
||||
LOG(LogWarning) << "Could not replace default game image, check path: " << path;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string oldDefaultGameTexture = mDefaultGameTexture;
|
||||
|
@ -326,7 +328,9 @@ void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|||
std::string path = elem->get<std::string>("folderImage");
|
||||
|
||||
if (!ResourceManager::getInstance()->fileExists(path))
|
||||
{
|
||||
LOG(LogWarning) << "Could not replace default folder image, check path: " << path;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string oldDefaultFolderTexture = mDefaultFolderTexture;
|
||||
|
|
|
@ -18,7 +18,7 @@ public:
|
|||
|
||||
bool getState() const;
|
||||
void setState(bool state);
|
||||
std::string getValue() const;
|
||||
std::string getValue() const override;
|
||||
void setValue(const std::string& statestring) override;
|
||||
|
||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
|
|
@ -120,6 +120,9 @@ void TextComponent::render(const Transform4x4f& parentTrans)
|
|||
case ALIGN_CENTER:
|
||||
yOff = (getSize().y() - textSize.y()) / 2.0f;
|
||||
break;
|
||||
default:
|
||||
LOG(LogError) << "Unknown vertical alignment: " << mVerticalAlignment;
|
||||
break;
|
||||
}
|
||||
Vector3f off(0, yOff, 0);
|
||||
|
||||
|
@ -147,68 +150,73 @@ void TextComponent::render(const Transform4x4f& parentTrans)
|
|||
case ALIGN_RIGHT:
|
||||
Renderer::drawRect(mSize.x() - mTextCache->metrics.size.x(), 0.0f, mTextCache->metrics.size.x(), mTextCache->metrics.size.y(), 0x00000033, 0x00000033);
|
||||
break;
|
||||
default:
|
||||
LOG(LogError) << "Unknown horizontal alignment: " << mHorizontalAlignment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mFont->renderTextCache(mTextCache.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TextComponent::calculateExtent()
|
||||
std::string TextComponent::calculateExtent(bool allow_wrapping)
|
||||
{
|
||||
std::string text = mUppercase ? Utils::String::toUpper(mText) : mText;
|
||||
if(mAutoCalcExtent.x())
|
||||
{
|
||||
mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing);
|
||||
}else{
|
||||
if(mAutoCalcExtent.y())
|
||||
{
|
||||
mSize[1] = mFont->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText) : mText, getSize().x(), mLineSpacing).y();
|
||||
mSize = mFont->sizeText(text, mLineSpacing);
|
||||
}else if(mAutoCalcExtent.y() || allow_wrapping)
|
||||
// usually a textcomponent wraps only when x > 0 and y == 0 in size (see TextComponent.h).
|
||||
// The extra flag allow_wrapping does wrapping if an textcomponent has x > 0 and y > height of
|
||||
// one line (calculated by fontsize and line spacing).
|
||||
// Some themes rely on this wrap functionality while having an fixed y (y>0) in <size/>.
|
||||
{
|
||||
text = mFont->wrapText(text, getSize().x());
|
||||
if (mAutoCalcExtent.y()) {
|
||||
// only resize when y was 0 before
|
||||
// otherwise leave y value as defined before (i.e. theme value)
|
||||
mSize.y() = mFont->sizeText(text, mLineSpacing).y();
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void TextComponent::onTextChanged()
|
||||
{
|
||||
calculateExtent();
|
||||
|
||||
if(!mFont || mText.empty())
|
||||
{
|
||||
mTextCache.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string text = mUppercase ? Utils::String::toUpper(mText) : mText;
|
||||
|
||||
std::shared_ptr<Font> f = mFont;
|
||||
const bool isMultiline = (mSize.y() == 0 || mSize.y() > f->getHeight()*1.2f);
|
||||
std::string text = calculateExtent(mSize.y() > f->getHeight(mLineSpacing));
|
||||
const bool oneLiner = mSize.y() > 0 && mSize.y() <= f->getHeight(mLineSpacing);
|
||||
|
||||
bool addAbbrev = false;
|
||||
if(!isMultiline)
|
||||
if(oneLiner)
|
||||
{
|
||||
bool addAbbrev = false;
|
||||
size_t newline = text.find('\n');
|
||||
text = text.substr(0, newline); // single line of text - stop at the first newline since it'll mess everything up
|
||||
addAbbrev = newline != std::string::npos;
|
||||
}
|
||||
Vector2f size = f->sizeText(text);
|
||||
addAbbrev = newline != std::string::npos || size.x() > mSize.x();
|
||||
|
||||
Vector2f size = f->sizeText(text);
|
||||
if(!isMultiline && mSize.x() && text.size() && (size.x() > mSize.x() || addAbbrev))
|
||||
{
|
||||
// abbreviate text
|
||||
const std::string abbrev = "...";
|
||||
Vector2f abbrevSize = f->sizeText(abbrev);
|
||||
|
||||
while(text.size() && size.x() + abbrevSize.x() > mSize.x())
|
||||
if(addAbbrev)
|
||||
{
|
||||
size_t newSize = Utils::String::prevCursor(text, text.size());
|
||||
text.erase(newSize, text.size() - newSize);
|
||||
size = f->sizeText(text);
|
||||
// abbreviate text
|
||||
const std::string abbrev = "...";
|
||||
Vector2f abbrevSize = f->sizeText(abbrev);
|
||||
|
||||
while(text.size() && size.x() + abbrevSize.x() > mSize.x())
|
||||
{
|
||||
size_t newSize = Utils::String::prevCursor(text, text.size());
|
||||
text.erase(newSize, text.size() - newSize);
|
||||
size = f->sizeText(text);
|
||||
}
|
||||
text.append(abbrev);
|
||||
}
|
||||
|
||||
text.append(abbrev);
|
||||
|
||||
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(text, Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
|
||||
}else{
|
||||
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(f->wrapText(text, mSize.x()), Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
|
||||
}
|
||||
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(text, Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
|
||||
}
|
||||
|
||||
void TextComponent::onColorChanged()
|
||||
|
|
|
@ -49,7 +49,7 @@ protected:
|
|||
std::shared_ptr<Font> mFont;
|
||||
|
||||
private:
|
||||
void calculateExtent();
|
||||
std::string calculateExtent(bool allow_wrapping);
|
||||
|
||||
void onColorChanged();
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
|
||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
virtual void update(int deltaTime);
|
||||
virtual void update(int deltaTime) override;
|
||||
|
||||
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain aspect ratio.
|
||||
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing.
|
||||
|
|
|
@ -5,13 +5,16 @@
|
|||
#include "utils/StringUtil.h"
|
||||
#include "PowerSaver.h"
|
||||
#include "Settings.h"
|
||||
#ifdef WIN32
|
||||
#include <basetsd.h>
|
||||
#include <codecvt>
|
||||
typedef SSIZE_T ssize_t;
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <vlc/vlc.h>
|
||||
#include <SDL_mutex.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <codecvt>
|
||||
#endif
|
||||
|
||||
libvlc_instance_t* VideoVlcComponent::mVLC = NULL;
|
||||
|
||||
// VLC prepares to render a video frame.
|
||||
|
@ -220,11 +223,7 @@ void VideoVlcComponent::handleLooping()
|
|||
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
|
||||
if (state == libvlc_Ended)
|
||||
{
|
||||
if (!Settings::getInstance()->getBool("VideoAudio") ||
|
||||
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
|
||||
{
|
||||
libvlc_audio_set_mute(mMediaPlayer, 1);
|
||||
}
|
||||
setMuteMode();
|
||||
//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
|
||||
libvlc_media_player_set_media(mMediaPlayer, mMedia);
|
||||
libvlc_media_player_play(mMediaPlayer);
|
||||
|
@ -255,7 +254,9 @@ void VideoVlcComponent::startVideo()
|
|||
{
|
||||
unsigned track_count;
|
||||
// Get the media metadata so we can find the aspect ratio
|
||||
libvlc_media_parse(mMedia);
|
||||
libvlc_media_parse_with_options(mMedia, libvlc_media_fetch_local, -1);
|
||||
while (libvlc_media_get_parsed_status(mMedia) == 0)
|
||||
;
|
||||
libvlc_media_track_t** tracks;
|
||||
track_count = libvlc_media_tracks_get(mMedia, &tracks);
|
||||
for (unsigned track = 0; track < track_count; ++track)
|
||||
|
@ -276,7 +277,7 @@ void VideoVlcComponent::startVideo()
|
|||
{
|
||||
std::string resolution = Settings::getInstance()->getString("VlcScreenSaverResolution");
|
||||
if(resolution != "original") {
|
||||
float scale = 1;
|
||||
float scale = 1;
|
||||
if (resolution == "low")
|
||||
// 25% of screen resolution
|
||||
scale = 0.25;
|
||||
|
@ -299,17 +300,17 @@ void VideoVlcComponent::startVideo()
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remove(getTitlePath().c_str());
|
||||
}
|
||||
PowerSaver::pause();
|
||||
setupContext();
|
||||
|
||||
// Setup the media player
|
||||
mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
|
||||
|
||||
if (!Settings::getInstance()->getBool("VideoAudio") ||
|
||||
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
|
||||
{
|
||||
libvlc_audio_set_mute(mMediaPlayer, 1);
|
||||
}
|
||||
setMuteMode();
|
||||
|
||||
libvlc_media_player_play(mMediaPlayer);
|
||||
libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display, (void*)&mContext);
|
||||
|
@ -339,3 +340,11 @@ void VideoVlcComponent::stopVideo()
|
|||
PowerSaver::resume();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoVlcComponent::setMuteMode()
|
||||
{
|
||||
Settings *cfg = Settings::getInstance();
|
||||
if (!cfg->getBool("VideoAudio") || (cfg->getBool("ScreenSaverVideoMute") && mScreensaverMode)) {
|
||||
libvlc_media_add_option(mMedia, ":no-audio");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,24 +40,25 @@ public:
|
|||
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing.
|
||||
// Can be set before or after a video is loaded.
|
||||
// setMaxSize() and setResize() are mutually exclusive.
|
||||
void setResize(float width, float height);
|
||||
void setResize(float width, float height) override;
|
||||
|
||||
// Resize the video to be as large as possible but fit within a box of this size.
|
||||
// Can be set before or after a video is loaded.
|
||||
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
|
||||
void setMaxSize(float width, float height);
|
||||
void setMaxSize(float width, float height) override;
|
||||
|
||||
private:
|
||||
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
|
||||
// Used internally whenever the resizing parameters or texture change.
|
||||
void resize();
|
||||
// Start the video Immediately
|
||||
virtual void startVideo();
|
||||
virtual void startVideo() override;
|
||||
// Stop the video
|
||||
virtual void stopVideo();
|
||||
virtual void stopVideo() override;
|
||||
// Handle looping the video. Must be called periodically
|
||||
virtual void handleLooping();
|
||||
virtual void handleLooping() override;
|
||||
|
||||
void setMuteMode();
|
||||
void setupContext();
|
||||
void freeContext();
|
||||
|
||||
|
|
|
@ -342,27 +342,39 @@ bool GuiInputConfig::filterTrigger(Input input, InputConfig* config, int inputId
|
|||
#if defined(__linux__)
|
||||
// on Linux, some gamepads return both an analog axis and a digital button for the trigger;
|
||||
// we want the analog axis only, so this function removes the button press event
|
||||
bool isPlaystation = (
|
||||
// match PlayStation joystick with 6 axes only
|
||||
strstr(config->getDeviceName().c_str(), "PLAYSTATION") != NULL
|
||||
|| strstr(config->getDeviceName().c_str(), "Sony Interactive") != NULL // Official dualshock 4
|
||||
|| strstr(config->getDeviceName().c_str(), "PS3 Ga") != NULL
|
||||
|| strstr(config->getDeviceName().c_str(), "PS(R) Ga") != NULL
|
||||
// BigBen kid's PS3 gamepad 146b:0902, matched on SDL GUID because its name "Bigben Interactive Bigben Game Pad" may be too generic
|
||||
|| strcmp(config->getDeviceGUIDString().c_str(), "030000006b1400000209000011010000") == 0
|
||||
);
|
||||
bool isAnbernic = (
|
||||
strcmp(config->getDeviceGUIDString().c_str(), "03004ab1020500000913000010010000") == 0 // Anbernic RG P01 has same issue
|
||||
);
|
||||
|
||||
if((
|
||||
// match PlayStation joystick with 6 axes only
|
||||
strstr(config->getDeviceName().c_str(), "PLAYSTATION") != NULL
|
||||
|| strstr(config->getDeviceName().c_str(), "PS3 Ga") != NULL
|
||||
|| strstr(config->getDeviceName().c_str(), "PS(R) Ga") != NULL
|
||||
// BigBen kid's PS3 gamepad 146b:0902, matched on SDL GUID because its name "Bigben Interactive Bigben Game Pad" may be too generic
|
||||
|| strcmp(config->getDeviceGUIDString().c_str(), "030000006b1400000209000011010000") == 0
|
||||
) && InputManager::getInstance()->getAxisCountByDevice(config->getDeviceId()) == 6)
|
||||
if((isPlaystation || isAnbernic)
|
||||
&& InputManager::getInstance()->getAxisCountByDevice(config->getDeviceId()) == 6)
|
||||
{
|
||||
// digital triggers are unwanted
|
||||
if(input.type == TYPE_BUTTON && (input.id == 6 || input.id == 7))
|
||||
if((
|
||||
(isPlaystation && (input.id == 6 || input.id == 7))
|
||||
|| (isAnbernic && (input.id == 8 || input.id == 9))
|
||||
) && input.type == TYPE_BUTTON)
|
||||
{
|
||||
mHoldingInput = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore negative pole for axes 2/5 only when triggers are being configured
|
||||
if(input.type == TYPE_AXIS && (input.id == 2 || input.id == 5))
|
||||
bool genericTrigger = !isAnbernic && (input.id == 2 || input.id == 5);
|
||||
bool anbernicTrigger = isAnbernic && (input.id == 4 || input.id == 5);
|
||||
// ignore negative pole for axes only when triggers are being configured
|
||||
if(input.type == TYPE_AXIS && (genericTrigger || anbernicTrigger))
|
||||
{
|
||||
|
||||
if(strstr(GUI_INPUT_CONFIG_LIST[inputId].name, "Trigger") != NULL)
|
||||
{
|
||||
if(input.value == 1)
|
||||
|
|
|
@ -15,8 +15,8 @@ public:
|
|||
GuiTextEditPopup(Window* window, const std::string& title, const std::string& initValue,
|
||||
const std::function<void(const std::string&)>& okCallback, bool multiLine, const char* acceptBtnText = "OK");
|
||||
|
||||
bool input(InputConfig* config, Input input);
|
||||
void onSizeChanged();
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void onSizeChanged() override;
|
||||
std::vector<HelpPrompt> getHelpPrompts() override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -85,5 +85,10 @@ void processQuitMode()
|
|||
touch("/tmp/es-shutdown");
|
||||
runShutdownCommand();
|
||||
break;
|
||||
default:
|
||||
// No-op to prevent compiler warnings
|
||||
// If we reach here, it is not a RESTART, REBOOT,
|
||||
// or SHUTDOWN. Basically a normal exit.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,8 +75,14 @@ namespace Renderer
|
|||
|
||||
initialCursorState = (SDL_ShowCursor(0) != 0);
|
||||
|
||||
int displayIndex = Settings::getInstance()->getInt("MonitorID");
|
||||
|
||||
if(displayIndex < 0 || displayIndex >= SDL_GetNumVideoDisplays()){
|
||||
displayIndex = 0;
|
||||
}
|
||||
|
||||
SDL_DisplayMode dispMode;
|
||||
SDL_GetDesktopDisplayMode(0, &dispMode);
|
||||
SDL_GetDesktopDisplayMode(displayIndex, &dispMode);
|
||||
windowWidth = Settings::getInstance()->getInt("WindowWidth") ? Settings::getInstance()->getInt("WindowWidth") : dispMode.w;
|
||||
windowHeight = Settings::getInstance()->getInt("WindowHeight") ? Settings::getInstance()->getInt("WindowHeight") : dispMode.h;
|
||||
screenWidth = Settings::getInstance()->getInt("ScreenWidth") ? Settings::getInstance()->getInt("ScreenWidth") : windowWidth;
|
||||
|
@ -89,7 +95,7 @@ namespace Renderer
|
|||
|
||||
const unsigned int windowFlags = (Settings::getInstance()->getBool("Windowed") ? 0 : (Settings::getInstance()->getBool("FullscreenBorderless") ? SDL_WINDOW_BORDERLESS : SDL_WINDOW_FULLSCREEN)) | getWindowFlags();
|
||||
|
||||
if((sdlWindow = SDL_CreateWindow("EmulationStation", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, windowWidth, windowHeight, windowFlags)) == nullptr)
|
||||
if((sdlWindow = SDL_CreateWindow("EmulationStation", SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), windowWidth, windowHeight, windowFlags)) == nullptr)
|
||||
{
|
||||
LOG(LogError) << "Error creating SDL window!\n\t" << SDL_GetError();
|
||||
return false;
|
||||
|
|
|
@ -526,7 +526,11 @@ std::string Font::wrapText(std::string text, float maxWidth)
|
|||
}
|
||||
}
|
||||
|
||||
if(cursor == text.length()) // arrived at end of text.
|
||||
if(cursor == text.length() && lineWidth <= maxWidth)
|
||||
// arrived at end of text while being in bounds of textbox
|
||||
// second clause is mandatory for short descriptions which coincidentially
|
||||
// ending with cursor at text end but slightly overrunning the bounding box
|
||||
// to be wrapped on the next line, thus have to hit the else branch
|
||||
{
|
||||
out += text;
|
||||
text.erase();
|
||||
|
@ -600,12 +604,12 @@ float Font::getNewlineStartOffset(const std::string& text, const unsigned int& c
|
|||
return 0;
|
||||
case ALIGN_CENTER:
|
||||
{
|
||||
unsigned int endChar = (unsigned int)text.find('\n', charStart);
|
||||
size_t endChar = text.find('\n', charStart);
|
||||
return (xLen - sizeText(text.substr(charStart, endChar != std::string::npos ? endChar - charStart : endChar)).x()) / 2.0f;
|
||||
}
|
||||
case ALIGN_RIGHT:
|
||||
{
|
||||
unsigned int endChar = (unsigned int)text.find('\n', charStart);
|
||||
size_t endChar = text.find('\n', charStart);
|
||||
return xLen - (sizeText(text.substr(charStart, endChar != std::string::npos ? endChar - charStart : endChar)).x());
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -22,23 +22,23 @@ namespace Utils
|
|||
else if((c & 0xE0) == 0xC0) // 110xxxxx, two byte character
|
||||
{
|
||||
// 110xxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x1F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
result = (_string[_cursor++] & 0x1F) << 6;
|
||||
result |= (_string[_cursor++] & 0x3F);
|
||||
}
|
||||
else if((c & 0xF0) == 0xE0) // 1110xxxx, three byte character
|
||||
{
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x0F) << 12) |
|
||||
((_string[_cursor++] & 0x3F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
result = (_string[_cursor++] & 0x0F) << 12;
|
||||
result |= (_string[_cursor++] & 0x3F) << 6;
|
||||
result |= (_string[_cursor++] & 0x3F);
|
||||
}
|
||||
else if((c & 0xF8) == 0xF0) // 11110xxx, four byte character
|
||||
{
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x07) << 18) |
|
||||
((_string[_cursor++] & 0x3F) << 12) |
|
||||
((_string[_cursor++] & 0x3F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
result = (_string[_cursor++] & 0x07) << 18;
|
||||
result |= (_string[_cursor++] & 0x3F) << 12;
|
||||
result |= (_string[_cursor++] & 0x3F) << 6;
|
||||
result |= (_string[_cursor++] & 0x3F);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -9,7 +9,13 @@ namespace Utils
|
|||
{
|
||||
namespace Time
|
||||
{
|
||||
static inline time_t blankDate() {
|
||||
// 1970-01-02
|
||||
tm timeStruct = { 0, 0, 0, 2, 0, 70, 0, 0, -1 };
|
||||
return mktime(&timeStruct);
|
||||
}
|
||||
static int NOT_A_DATE_TIME = 0;
|
||||
static time_t BLANK_DATE = blankDate();
|
||||
|
||||
class DateTime
|
||||
{
|
||||
|
|
7
external/CMakeLists.txt
vendored
7
external/CMakeLists.txt
vendored
|
@ -2,4 +2,9 @@
|
|||
# package managers are included with the project (in the 'external' folder)
|
||||
|
||||
add_subdirectory("nanosvg")
|
||||
add_subdirectory("pugixml")
|
||||
|
||||
find_package(pugixml)
|
||||
|
||||
if(NOT pugixml_FOUND)
|
||||
add_subdirectory("pugixml")
|
||||
endif()
|
||||
|
|
2
external/pugixml
vendored
2
external/pugixml
vendored
|
@ -1 +1 @@
|
|||
Subproject commit d2deb420bc70369faa12785df2b5dd4d390e523d
|
||||
Subproject commit a0e064336317c9347a91224112af9933598714e9
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Generated on 2022-02-11, from MAME 0.240 (Arcade).dat, fbneo.dat, mame2003-plus.xml -->
|
||||
<!-- Generated on 2025-03-10, from MAME 0.275 (arcade).dat, fbneo.dat, mame2003-plus.xml -->
|
||||
<bios>3dobios</bios>
|
||||
<bios>acpsx</bios>
|
||||
<bios>airlbios</bios>
|
||||
|
@ -9,6 +9,7 @@
|
|||
<bios>ar_bios</bios>
|
||||
<bios>aristmk5</bios>
|
||||
<bios>aristmk6</bios>
|
||||
<bios>aristmk7</bios>
|
||||
<bios>atarisy1</bios>
|
||||
<bios>awbios</bios>
|
||||
<bios>bubsys</bios>
|
||||
|
@ -23,6 +24,7 @@
|
|||
<bios>coh1001l</bios>
|
||||
<bios>coh1002e</bios>
|
||||
<bios>coh1002m</bios>
|
||||
<bios>coh1002t</bios>
|
||||
<bios>coh1002v</bios>
|
||||
<bios>coh3002c</bios>
|
||||
<bios>coh3002t</bios>
|
||||
|
@ -47,6 +49,7 @@
|
|||
<bios>konamigx</bios>
|
||||
<bios>konendev</bios>
|
||||
<bios>kpython</bios>
|
||||
<bios>kpython2</bios>
|
||||
<bios>kviper</bios>
|
||||
<bios>lindbios</bios>
|
||||
<bios>mac2bios</bios>
|
||||
|
@ -67,6 +70,7 @@
|
|||
<bios>nss</bios>
|
||||
<bios>pgm</bios>
|
||||
<bios>playch10</bios>
|
||||
<bios>pumpitup</bios>
|
||||
<bios>recel</bios>
|
||||
<bios>sammymdl</bios>
|
||||
<bios>segasp</bios>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<!-- Generated on 2022-02-11, from MAME 0.240.dat -->
|
||||
<!-- Generated on 2025-03-10, from MAME 0.275.dat -->
|
||||
<device>22vp931</device>
|
||||
<device>3c505</device>
|
||||
<device>3xtwin</device>
|
||||
<device>4dparprn</device>
|
||||
<device>a1000kbd_de</device>
|
||||
<device>a1000kbd_dk</device>
|
||||
<device>a1000kbd_fr</device>
|
||||
|
@ -36,8 +37,8 @@
|
|||
<device>a2cffa02</device>
|
||||
<device>a2cffa2</device>
|
||||
<device>a2corvus</device>
|
||||
<device>a2diskii</device>
|
||||
<device>a2diskiing</device>
|
||||
<device>a2excel9</device>
|
||||
<device>a2focdrv</device>
|
||||
<device>a2grappler</device>
|
||||
<device>a2grapplerplus</device>
|
||||
|
@ -52,11 +53,14 @@
|
|||
<device>a2q68</device>
|
||||
<device>a2q68plus</device>
|
||||
<device>a2ramfac</device>
|
||||
<device>a2romfp</device>
|
||||
<device>a2romint</device>
|
||||
<device>a2scsi</device>
|
||||
<device>a2sd</device>
|
||||
<device>a2sider1</device>
|
||||
<device>a2sider2</device>
|
||||
<device>a2ssc</device>
|
||||
<device>a2superdrive</device>
|
||||
<device>a2suprterm</device>
|
||||
<device>a2surance</device>
|
||||
<device>a2swyft</device>
|
||||
|
@ -67,6 +71,7 @@
|
|||
<device>a2ulttrm</device>
|
||||
<device>a2uniprint</device>
|
||||
<device>a2vidtrm</device>
|
||||
<device>a2vistaa800</device>
|
||||
<device>a2vtc1</device>
|
||||
<device>a2vulcan</device>
|
||||
<device>a2vulgld</device>
|
||||
|
@ -109,7 +114,6 @@
|
|||
<device>abc_db4107</device>
|
||||
<device>abc_db4112</device>
|
||||
<device>abc_fd2</device>
|
||||
<device>abc_memcard</device>
|
||||
<device>abc_slutprov</device>
|
||||
<device>abc_ssa</device>
|
||||
<device>abc_uni800</device>
|
||||
|
@ -126,6 +130,7 @@
|
|||
<device>adam_kb</device>
|
||||
<device>adam_prn</device>
|
||||
<device>adam_spi</device>
|
||||
<device>adbmodem</device>
|
||||
<device>agat7_flop</device>
|
||||
<device>agat840k_hle</device>
|
||||
<device>agat9_flop</device>
|
||||
|
@ -137,8 +142,18 @@
|
|||
<device>aha1542cp</device>
|
||||
<device>aha1740</device>
|
||||
<device>aha1742a</device>
|
||||
<device>alice_mcx128</device>
|
||||
<device>aha2940au</device>
|
||||
<device>alto2_cpu</device>
|
||||
<device>altos2_kbd</device>
|
||||
<device>amiga_a2091</device>
|
||||
<device>amiga_a570</device>
|
||||
<device>amiga_a590</device>
|
||||
<device>amiga_ar1</device>
|
||||
<device>amiga_ar2</device>
|
||||
<device>amiga_ar3</device>
|
||||
<device>amiga_buddha</device>
|
||||
<device>amiga_oktagon2008</device>
|
||||
<device>amiga_ripple</device>
|
||||
<device>ampex230_kbd</device>
|
||||
<device>ap2000</device>
|
||||
<device>aplcd150</device>
|
||||
|
@ -153,26 +168,42 @@
|
|||
<device>arc_bbcio_we</device>
|
||||
<device>arc_eaglem2</device>
|
||||
<device>arc_ether1_aka25</device>
|
||||
<device>arc_ether2_aeh50</device>
|
||||
<device>arc_ether3_aeh54</device>
|
||||
<device>arc_ethera</device>
|
||||
<device>arc_etherd</device>
|
||||
<device>arc_etherr</device>
|
||||
<device>arc_faxpack</device>
|
||||
<device>arc_greyhawk</device>
|
||||
<device>arc_hdisc_akd52</device>
|
||||
<device>arc_hdisc_cw</device>
|
||||
<device>arc_hdisc_morley</device>
|
||||
<device>arc_hdisc_we</device>
|
||||
<device>arc_ide_be</device>
|
||||
<device>arc_ide_rdev</device>
|
||||
<device>arc_iomidi_aka15</device>
|
||||
<device>arc_lark</device>
|
||||
<device>arc_lbp4</device>
|
||||
<device>arc_midi2</device>
|
||||
<device>arc_midi_aka16</device>
|
||||
<device>arc_midimax</device>
|
||||
<device>arc_midimax2</device>
|
||||
<device>arc_nexus_a500</device>
|
||||
<device>arc_rom_aka05</device>
|
||||
<device>arc_rom_r225</device>
|
||||
<device>arc_rs423</device>
|
||||
<device>arc_scan256</device>
|
||||
<device>arc_scanjunior</device>
|
||||
<device>arc_scanjunior3</device>
|
||||
<device>arc_scanlight</device>
|
||||
<device>arc_scanvideo</device>
|
||||
<device>arc_scsi_a500</device>
|
||||
<device>arc_scsi_aka31</device>
|
||||
<device>arc_scsi_aka32</device>
|
||||
<device>arc_scsi_cumana</device>
|
||||
<device>arc_scsi_ling</device>
|
||||
<device>arc_scsi_morley</device>
|
||||
<device>arc_scsi_oak</device>
|
||||
<device>arc_scsi_vti</device>
|
||||
<device>arc_serial</device>
|
||||
<device>arc_spectra</device>
|
||||
|
@ -188,7 +219,11 @@
|
|||
<device>atari810</device>
|
||||
<device>atom_discpack</device>
|
||||
<device>ax208</device>
|
||||
<device>ax208p</device>
|
||||
<device>banshee_x86</device>
|
||||
<device>basf7100_kbd</device>
|
||||
<device>bbc_24bbc</device>
|
||||
<device>bbc_2ndserial</device>
|
||||
<device>bbc_acorn1770</device>
|
||||
<device>bbc_acorn8271</device>
|
||||
<device>bbc_ams3</device>
|
||||
|
@ -200,6 +235,7 @@
|
|||
<device>bbc_bitstik2</device>
|
||||
<device>bbc_cc500</device>
|
||||
<device>bbc_chameleon</device>
|
||||
<device>bbc_cisco</device>
|
||||
<device>bbc_cumana1</device>
|
||||
<device>bbc_cumana2</device>
|
||||
<device>bbc_cumana68k</device>
|
||||
|
@ -208,6 +244,7 @@
|
|||
<device>bbc_ieee488</device>
|
||||
<device>bbc_integrab</device>
|
||||
<device>bbc_kenda</device>
|
||||
<device>bbc_memexb20</device>
|
||||
<device>bbc_mertec</device>
|
||||
<device>bbc_morleyaa</device>
|
||||
<device>bbc_multiform</device>
|
||||
|
@ -218,6 +255,7 @@
|
|||
<device>bbc_opus8272</device>
|
||||
<device>bbc_opusa</device>
|
||||
<device>bbc_pdram</device>
|
||||
<device>bbc_pms64k</device>
|
||||
<device>bbc_ramdisc</device>
|
||||
<device>bbc_raven20</device>
|
||||
<device>bbc_stl1770_1</device>
|
||||
|
@ -233,14 +271,18 @@
|
|||
<device>bbc_tube_32016</device>
|
||||
<device>bbc_tube_32016l</device>
|
||||
<device>bbc_tube_6502</device>
|
||||
<device>bbc_tube_6502e</device>
|
||||
<device>bbc_tube_6502p</device>
|
||||
<device>bbc_tube_65c102</device>
|
||||
<device>bbc_tube_80186</device>
|
||||
<device>bbc_tube_80286</device>
|
||||
<device>bbc_tube_a500</device>
|
||||
<device>bbc_tube_a500d</device>
|
||||
<device>bbc_tube_arm</device>
|
||||
<device>bbc_tube_arm7</device>
|
||||
<device>bbc_tube_casper</device>
|
||||
<device>bbc_tube_cms6809</device>
|
||||
<device>bbc_tube_matchbox</device>
|
||||
<device>bbc_tube_pcplus</device>
|
||||
<device>bbc_tube_rc6502</device>
|
||||
<device>bbc_tube_rc65816</device>
|
||||
|
@ -258,6 +300,7 @@
|
|||
<device>bbc_weddb3</device>
|
||||
<device>betadisk</device>
|
||||
<device>bingoct</device>
|
||||
<device>bk_kmd</device>
|
||||
<device>bluechip</device>
|
||||
<device>bml3kanji</device>
|
||||
<device>bml3mp1802</device>
|
||||
|
@ -295,6 +338,7 @@
|
|||
<device>c64_nl10</device>
|
||||
<device>c64_supercpu</device>
|
||||
<device>c64_xl80</device>
|
||||
<device>c64_z80videopak</device>
|
||||
<device>c8050</device>
|
||||
<device>c8050fdc</device>
|
||||
<device>c8250</device>
|
||||
|
@ -319,6 +363,9 @@
|
|||
<device>cdu415</device>
|
||||
<device>cdu561_25</device>
|
||||
<device>cdu75s</device>
|
||||
<device>centennial_sl01m_15_11194</device>
|
||||
<device>centennial_sl02m_15_11194</device>
|
||||
<device>centennial_sl04m_15_11194</device>
|
||||
<device>cffa1</device>
|
||||
<device>cfp1080s</device>
|
||||
<device>cga</device>
|
||||
|
@ -335,20 +382,20 @@
|
|||
<device>cit101e_kbd</device>
|
||||
<device>cit220p_kbd</device>
|
||||
<device>clgd542x</device>
|
||||
<device>clgd5446_pci</device>
|
||||
<device>clgd5465_laguna</device>
|
||||
<device>cmdhd</device>
|
||||
<device>cmdrc2</device>
|
||||
<device>cmi_ankbd</device>
|
||||
<device>cmi_mkbd</device>
|
||||
<device>cms_4080term</device>
|
||||
<device>cmsscsi</device>
|
||||
<device>coco2_hdb1</device>
|
||||
<device>coco3_hdb1</device>
|
||||
<device>coco_dcmodem</device>
|
||||
<device>coco_fdc</device>
|
||||
<device>coco_fdc_v11</device>
|
||||
<device>coco_orch90</device>
|
||||
<device>coco_psg</device>
|
||||
<device>coco_rs232</device>
|
||||
<device>coco_scii</device>
|
||||
<device>coco_ssc</device>
|
||||
<device>coco_t4426</device>
|
||||
<device>coco_wpk</device>
|
||||
|
@ -362,6 +409,8 @@
|
|||
<device>comx_pl80</device>
|
||||
<device>comx_prn</device>
|
||||
<device>comx_thm</device>
|
||||
<device>concept_kbd</device>
|
||||
<device>cp2024</device>
|
||||
<device>cp31</device>
|
||||
<device>cp450_fdc</device>
|
||||
<device>cpc_brunword4</device>
|
||||
|
@ -374,6 +423,7 @@
|
|||
<device>cpc_smartwatch</device>
|
||||
<device>cpc_ssa1</device>
|
||||
<device>cpc_transtape</device>
|
||||
<device>cpuap</device>
|
||||
<device>cq90_028</device>
|
||||
<device>crd254sh</device>
|
||||
<device>crt9028_000</device>
|
||||
|
@ -381,24 +431,32 @@
|
|||
<device>crvfdc02</device>
|
||||
<device>csd1</device>
|
||||
<device>cuda</device>
|
||||
<device>cuda302</device>
|
||||
<device>cudalite</device>
|
||||
<device>cv8lc</device>
|
||||
<device>cw7501</device>
|
||||
<device>d2fdc</device>
|
||||
<device>d9060</device>
|
||||
<device>d9090</device>
|
||||
<device>db50xg</device>
|
||||
<device>db60xg</device>
|
||||
<device>dc320b</device>
|
||||
<device>dc320e</device>
|
||||
<device>dc820</device>
|
||||
<device>dc820b</device>
|
||||
<device>dectalk_isa</device>
|
||||
<device>dg640</device>
|
||||
<device>digilog_kbd</device>
|
||||
<device>dim68k_kbd</device>
|
||||
<device>dio98543</device>
|
||||
<device>dio98544</device>
|
||||
<device>dio98550</device>
|
||||
<device>dio98603a</device>
|
||||
<device>dio98603b</device>
|
||||
<device>dio98628</device>
|
||||
<device>dio98629</device>
|
||||
<device>diskii13</device>
|
||||
<device>dm_clgd5430</device>
|
||||
<device>dms3d2kp</device>
|
||||
<device>dmv_k220</device>
|
||||
<device>dmv_k230</device>
|
||||
<device>dmv_k231</device>
|
||||
|
@ -427,6 +485,8 @@
|
|||
<device>eispc_kb</device>
|
||||
<device>electron_ap1</device>
|
||||
<device>electron_ap6</device>
|
||||
<device>electron_elksd128</device>
|
||||
<device>electron_elksd64</device>
|
||||
<device>electron_m2105</device>
|
||||
<device>electron_mc68k</device>
|
||||
<device>electron_mode7</device>
|
||||
|
@ -435,58 +495,79 @@
|
|||
<device>electron_pwrjoy</device>
|
||||
<device>electron_romboxp</device>
|
||||
<device>electron_sidewndr</device>
|
||||
<device>electron_voxbox</device>
|
||||
<device>enh2000</device>
|
||||
<device>enp10</device>
|
||||
<device>ep64_exdos</device>
|
||||
<device>epson_pf10</device>
|
||||
<device>epson_qx_option_multifont</device>
|
||||
<device>epson_rx80</device>
|
||||
<device>epson_tf20</device>
|
||||
<device>ergoline_kbd</device>
|
||||
<device>et4000</device>
|
||||
<device>et4000_kasan16</device>
|
||||
<device>et4kw32i</device>
|
||||
<device>europc_kbd</device>
|
||||
<device>ev346</device>
|
||||
<device>ex1280</device>
|
||||
<device>ex800</device>
|
||||
<device>executive10_102_kbd</device>
|
||||
<device>exorterm155_device</device>
|
||||
<device>f4431_kbd</device>
|
||||
<device>fc_disksys</device>
|
||||
<device>fccpu20</device>
|
||||
<device>fccpu21</device>
|
||||
<device>fccpu21a</device>
|
||||
<device>fccpu21b</device>
|
||||
<device>fccpu21s</device>
|
||||
<device>fccpu21ya</device>
|
||||
<device>fccpu21yb</device>
|
||||
<device>fcisio1</device>
|
||||
<device>fcscsi1</device>
|
||||
<device>fd148</device>
|
||||
<device>fd2000</device>
|
||||
<device>fd4000</device>
|
||||
<device>fdc344</device>
|
||||
<device>fdcmag</device>
|
||||
<device>filetto_cga</device>
|
||||
<device>freedom220_kbd</device>
|
||||
<device>fsd1</device>
|
||||
<device>fsd2</device>
|
||||
<device>g80_1500</device>
|
||||
<device>geforce256</device>
|
||||
<device>geforce256ddr</device>
|
||||
<device>gfxultra</device>
|
||||
<device>gfxultrap</device>
|
||||
<device>gic</device>
|
||||
<device>gunsense</device>
|
||||
<device>h89bus</device>
|
||||
<device>hardbox</device>
|
||||
<device>hcpu30</device>
|
||||
<device>hd44780_a00</device>
|
||||
<device>hd44780</device>
|
||||
<device>hd44780u</device>
|
||||
<device>hd61830</device>
|
||||
<device>hd63484</device>
|
||||
<device>he191_3425</device>
|
||||
<device>heath_gp19_tlb</device>
|
||||
<device>heath_igc_super19_tlb_device</device>
|
||||
<device>heath_igc_tlb_device</device>
|
||||
<device>heath_igc_ultra_tlb_device</device>
|
||||
<device>heath_igc_watz_tlb_device</device>
|
||||
<device>heath_imaginator_tlb</device>
|
||||
<device>heath_super19_tlb</device>
|
||||
<device>heath_superset_tlb</device>
|
||||
<device>heath_tlb</device>
|
||||
<device>heath_ultra_tlb</device>
|
||||
<device>heath_watz_tlb</device>
|
||||
<device>hk68v10</device>
|
||||
<device>hp82900</device>
|
||||
<device>hp82937</device>
|
||||
<device>hp82939</device>
|
||||
<device>hp9122c</device>
|
||||
<device>hp9133</device>
|
||||
<device>hp98034</device>
|
||||
<device>hp98035</device>
|
||||
<device>hp98046</device>
|
||||
<device>hp9845_prt</device>
|
||||
<device>hp9895</device>
|
||||
<device>hpblp</device>
|
||||
<device>human_interface</device>
|
||||
<device>i8244</device>
|
||||
<device>i8245</device>
|
||||
<device>ibm_mfc</device>
|
||||
<device>ibm_vga</device>
|
||||
<device>idpart_video</device>
|
||||
<device>ie15_device</device>
|
||||
<device>ie15kbd</device>
|
||||
<device>imds2ioc</device>
|
||||
|
@ -508,16 +589,19 @@
|
|||
<device>isa_hercules</device>
|
||||
<device>isa_ibm_mda</device>
|
||||
<device>isa_ibm_pgc</device>
|
||||
<device>isa_ibm_speech</device>
|
||||
<device>isa_pcmidi</device>
|
||||
<device>isa_prose4001</device>
|
||||
<device>isbc202</device>
|
||||
<device>isbc8024</device>
|
||||
<device>isbc_215g</device>
|
||||
<device>jaleco_vj_pc</device>
|
||||
<device>jc1310</device>
|
||||
<device>jvs13551</device>
|
||||
<device>k573_dio</device>
|
||||
<device>k573kara</device>
|
||||
<device>k573mcr</device>
|
||||
<device>k573msu</device>
|
||||
<device>k573npu</device>
|
||||
<device>k7659_keyboard</device>
|
||||
<device>kaypro10kbd</device>
|
||||
<device>kb_3270pc</device>
|
||||
|
@ -531,10 +615,15 @@
|
|||
<device>kbd_lle_en_us</device>
|
||||
<device>kc_d004</device>
|
||||
<device>kc_d004_gide</device>
|
||||
<device>keytronic_l2207</device>
|
||||
<device>keytronic_pc3270</device>
|
||||
<device>keytronic_pc3270_at</device>
|
||||
<device>km035</device>
|
||||
<device>ks0066_f05</device>
|
||||
<device>ks0066</device>
|
||||
<device>labtam_3232</device>
|
||||
<device>labtam_8086cpu</device>
|
||||
<device>labtam_vducom</device>
|
||||
<device>labtam_z80sbc</device>
|
||||
<device>lba_enhancer</device>
|
||||
<device>lc7985</device>
|
||||
<device>ldp1000</device>
|
||||
|
@ -557,6 +646,7 @@
|
|||
<device>m68705p5</device>
|
||||
<device>m68705r3</device>
|
||||
<device>m68705u3</device>
|
||||
<device>m68hc05pge</device>
|
||||
<device>m68hc705c4a</device>
|
||||
<device>m68hc705c8a</device>
|
||||
<device>mach64isa</device>
|
||||
|
@ -566,21 +656,26 @@
|
|||
<device>mackbd_m0110a_j</device>
|
||||
<device>mackbd_m0110b</device>
|
||||
<device>mackbd_m0110f</device>
|
||||
<device>mackbd_m0110j</device>
|
||||
<device>mackbd_m0110t</device>
|
||||
<device>mackbd_m0120</device>
|
||||
<device>mackbd_m0120p</device>
|
||||
<device>mb90082</device>
|
||||
<device>mbc55x_kbd</device>
|
||||
<device>mc10_mcx128</device>
|
||||
<device>mc1502_rom</device>
|
||||
<device>mcx128</device>
|
||||
<device>md_kbd</device>
|
||||
<device>md_rom_svp</device>
|
||||
<device>mg1_kbd_device</device>
|
||||
<device>mga2064w</device>
|
||||
<device>microtan_kbd_mt009</device>
|
||||
<device>microtouch</device>
|
||||
<device>midcsd</device>
|
||||
<device>midssio</device>
|
||||
<device>mie</device>
|
||||
<device>mindset_sound_module</device>
|
||||
<device>minichif</device>
|
||||
<device>mks3</device>
|
||||
<device>mm1kb</device>
|
||||
<device>mm5740</device>
|
||||
<device>mockingboardd</device>
|
||||
|
@ -595,6 +690,17 @@
|
|||
<device>mpcba79</device>
|
||||
<device>mpcbb68</device>
|
||||
<device>mpcbb92</device>
|
||||
<device>mpf1_epb</device>
|
||||
<device>mpf1_epb_ibp</device>
|
||||
<device>mpf1_iom_ip</device>
|
||||
<device>mpf1_prt</device>
|
||||
<device>mpf1_prt_ip</device>
|
||||
<device>mpf1_sgb</device>
|
||||
<device>mpf1_ssb</device>
|
||||
<device>mpf1_tva_ip</device>
|
||||
<device>mpf1_vid</device>
|
||||
<device>mps1200</device>
|
||||
<device>mps1250</device>
|
||||
<device>mpu401</device>
|
||||
<device>ms7004</device>
|
||||
<device>msdsd1</device>
|
||||
|
@ -609,15 +715,27 @@
|
|||
<device>msx_cart_easispeech</device>
|
||||
<device>msx_cart_sfg01</device>
|
||||
<device>msx_cart_sfg05</device>
|
||||
<device>msx_cart_skw01</device>
|
||||
<device>msx_cart_sunrise_ataide</device>
|
||||
<device>msx_moonsound</device>
|
||||
<device>mtx_cfx</device>
|
||||
<device>mtx_sdxbas</device>
|
||||
<device>mtx_sdxcpm</device>
|
||||
<device>mu5lcd</device>
|
||||
<device>mulcd</device>
|
||||
<device>mvme120</device>
|
||||
<device>mvme121</device>
|
||||
<device>mvme122</device>
|
||||
<device>mvme123</device>
|
||||
<device>mvme147</device>
|
||||
<device>mvme180</device>
|
||||
<device>mvme181</device>
|
||||
<device>mvme187</device>
|
||||
<device>mvme327a</device>
|
||||
<device>mvme328</device>
|
||||
<device>mvme350</device>
|
||||
<device>mzr8105</device>
|
||||
<device>nabupc_keyboard</device>
|
||||
<device>namco50</device>
|
||||
<device>namco51</device>
|
||||
<device>namco52</device>
|
||||
|
@ -627,18 +745,32 @@
|
|||
<device>namco58</device>
|
||||
<device>namco59</device>
|
||||
<device>namco62</device>
|
||||
<device>namco_amc</device>
|
||||
<device>namco_asca1</device>
|
||||
<device>namco_asca3</device>
|
||||
<device>namco_asca3a</device>
|
||||
<device>namco_asca5</device>
|
||||
<device>namco_csz1</device>
|
||||
<device>namco_cyberlead</device>
|
||||
<device>namco_cyberlead_led</device>
|
||||
<device>namco_cyberleada</device>
|
||||
<device>namco_emio102</device>
|
||||
<device>namco_fca10</device>
|
||||
<device>namco_fca11</device>
|
||||
<device>namco_fcb</device>
|
||||
<device>namco_tssio</device>
|
||||
<device>namco_xmiu1</device>
|
||||
<device>namcoc65</device>
|
||||
<device>namcoc67</device>
|
||||
<device>namcoc68</device>
|
||||
<device>namcoc69</device>
|
||||
<device>namcoc70</device>
|
||||
<device>namcoc71</device>
|
||||
<device>namcoc74</device>
|
||||
<device>namcoc75</device>
|
||||
<device>namcoc76</device>
|
||||
<device>nanoreseau_mo</device>
|
||||
<device>nanoreseau_to</device>
|
||||
<device>nb_48gc</device>
|
||||
<device>nb_824gc</device>
|
||||
<device>nb_aenet</device>
|
||||
<device>nb_amc3b</device>
|
||||
<device>nb_btbug</device>
|
||||
|
@ -647,17 +779,26 @@
|
|||
<device>nb_laserview</device>
|
||||
<device>nb_m2hr</device>
|
||||
<device>nb_m2vc</device>
|
||||
<device>nb_mdc48</device>
|
||||
<device>nb_mdc824</device>
|
||||
<device>nb_qdlink</device>
|
||||
<device>nb_rtpd</device>
|
||||
<device>nb_sp8s3</device>
|
||||
<device>nb_spdq</device>
|
||||
<device>nb_thungx</device>
|
||||
<device>nb_vikbw</device>
|
||||
<device>nb_wspt</device>
|
||||
<device>ncr53c825_pci</device>
|
||||
<device>neon250</device>
|
||||
<device>nes_rob</device>
|
||||
<device>newbrain_eim</device>
|
||||
<device>newbrain_fdc</device>
|
||||
<device>nlq401</device>
|
||||
<device>nmk004</device>
|
||||
<device>novell_dcb</device>
|
||||
<device>np600a3</device>
|
||||
<device>nss_tvinterface</device>
|
||||
<device>omniwave</device>
|
||||
<device>omti8621isa</device>
|
||||
<device>oric_jasmin</device>
|
||||
<device>oric_microdisc</device>
|
||||
|
@ -665,15 +806,18 @@
|
|||
<device>osa_maestro</device>
|
||||
<device>osa_maestroa</device>
|
||||
<device>osa_sparc</device>
|
||||
<device>oti64111_pci</device>
|
||||
<device>p1_fdc</device>
|
||||
<device>p1_hdc</device>
|
||||
<device>p1_rom</device>
|
||||
<device>p72</device>
|
||||
<device>pa7246</device>
|
||||
<device>partner_gdp</device>
|
||||
<device>pc1512kb</device>
|
||||
<device>pc1640_iga</device>
|
||||
<device>pc80s31</device>
|
||||
<device>pc80s31k</device>
|
||||
<device>pc88va2_fd_if</device>
|
||||
<device>pc9801_118</device>
|
||||
<device>pc9801_26</device>
|
||||
<device>pc9801_55l</device>
|
||||
|
@ -689,19 +833,35 @@
|
|||
<device>pd3_lviw</device>
|
||||
<device>pd3_mclr</device>
|
||||
<device>pd3_pc16</device>
|
||||
<device>pdc20262</device>
|
||||
<device>pds30_emac</device>
|
||||
<device>pds_hyper</device>
|
||||
<device>pds_sefp</device>
|
||||
<device>pdslc_macconlc</device>
|
||||
<device>pet_softbox</device>
|
||||
<device>pet_superpet</device>
|
||||
<device>pg200</device>
|
||||
<device>plg100_vl</device>
|
||||
<device>plg150_ap</device>
|
||||
<device>plus4_sid</device>
|
||||
<device>polyvti</device>
|
||||
<device>pr8210</device>
|
||||
<device>premier_fdc</device>
|
||||
<device>profighterq</device>
|
||||
<device>profighterqa</device>
|
||||
<device>profighterqb</device>
|
||||
<device>profighterx</device>
|
||||
<device>promotion3210</device>
|
||||
<device>ps2_keybc</device>
|
||||
<device>psion_3fax</device>
|
||||
<device>psion_3link_ser</device>
|
||||
<device>psion_parallel</device>
|
||||
<device>psx_cd</device>
|
||||
<device>psxgboost</device>
|
||||
<device>pvga1a</device>
|
||||
<device>pvga1a_jk</device>
|
||||
<device>px320a</device>
|
||||
<device>qg640</device>
|
||||
<device>ql_cumanafdi</device>
|
||||
<device>ql_gold</device>
|
||||
<device>ql_kdi</device>
|
||||
|
@ -725,7 +885,24 @@
|
|||
<device>qsound</device>
|
||||
<device>qsound_hle</device>
|
||||
<device>qts1</device>
|
||||
<device>qx10_keyboard</device>
|
||||
<device>quadro</device>
|
||||
<device>qx10_keyboard_ascii</device>
|
||||
<device>qx10_keyboard_hasci</device>
|
||||
<device>rageiidvd</device>
|
||||
<device>rc2014_micro</device>
|
||||
<device>rc2014_mini_cpm</device>
|
||||
<device>rc2014_pagable_rom</device>
|
||||
<device>rc2014_rom_ram_512k</device>
|
||||
<device>rc2014_switchable_rom</device>
|
||||
<device>riva128</device>
|
||||
<device>riva128zx</device>
|
||||
<device>rivatnt</device>
|
||||
<device>rivatnt2</device>
|
||||
<device>rivatnt2_m64</device>
|
||||
<device>rivatnt2_ultra</device>
|
||||
<device>rm_mq2</device>
|
||||
<device>robotron_k7070</device>
|
||||
<device>robotron_k7071</device>
|
||||
<device>rolm_pdc</device>
|
||||
<device>rolm_smioc</device>
|
||||
<device>rtpc_kbd</device>
|
||||
|
@ -738,8 +915,6 @@
|
|||
<device>s100_sj2db</device>
|
||||
<device>s1410</device>
|
||||
<device>s3_764</device>
|
||||
<device>s3virge</device>
|
||||
<device>s3virgedx</device>
|
||||
<device>s97269pb</device>
|
||||
<device>s_smp</device>
|
||||
<device>sa1403d</device>
|
||||
|
@ -760,22 +935,36 @@
|
|||
<device>sb300p</device>
|
||||
<device>sbus_hme</device>
|
||||
<device>sbus_sunpc</device>
|
||||
<device>sc119</device>
|
||||
<device>scorpion_ic</device>
|
||||
<device>sdtandy_fdc</device>
|
||||
<device>sed1200da</device>
|
||||
<device>sed1200db</device>
|
||||
<device>sed1200fa</device>
|
||||
<device>sed1200fb</device>
|
||||
<device>sed1278_0b</device>
|
||||
<device>sed1278</device>
|
||||
<device>sed1330</device>
|
||||
<device>sega837_14438</device>
|
||||
<device>segaai_soundbox</device>
|
||||
<device>segabill</device>
|
||||
<device>segadimm</device>
|
||||
<device>seganetw</device>
|
||||
<device>sente6vb</device>
|
||||
<device>sfd10001</device>
|
||||
<device>serad</device>
|
||||
<device>sfd1001</device>
|
||||
<device>sgi_gm1</device>
|
||||
<device>sgi_ip4</device>
|
||||
<device>sgi_kbd</device>
|
||||
<device>side116</device>
|
||||
<device>simutrek</device>
|
||||
<device>sis630_gui</device>
|
||||
<device>sis6326_agp</device>
|
||||
<device>sis900_eth</device>
|
||||
<device>smoc501</device>
|
||||
<device>sms_diypaddle</device>
|
||||
<device>smvme2000</device>
|
||||
<device>sn74s262</device>
|
||||
<device>sn74s263</device>
|
||||
<device>sns_dsp1bleg</device>
|
||||
<device>sns_dsp1leg</device>
|
||||
<device>sns_dsp1leg_hi</device>
|
||||
|
@ -808,8 +997,13 @@
|
|||
<device>spectrum_lprint3</device>
|
||||
<device>spectrum_mface1</device>
|
||||
<device>spectrum_mface128</device>
|
||||
<device>spectrum_mface128v1</device>
|
||||
<device>spectrum_mface1v1</device>
|
||||
<device>spectrum_mface1v2</device>
|
||||
<device>spectrum_mface1v3</device>
|
||||
<device>spectrum_mface3</device>
|
||||
<device>spectrum_mikroplus</device>
|
||||
<device>spectrum_mpoker</device>
|
||||
<device>spectrum_mprint</device>
|
||||
<device>spectrum_opus</device>
|
||||
<device>spectrum_plus2test</device>
|
||||
|
@ -818,25 +1012,52 @@
|
|||
<device>spectrum_sdi</device>
|
||||
<device>spectrum_spdos</device>
|
||||
<device>spectrum_speccydos</device>
|
||||
<device>spectrum_specmate</device>
|
||||
<device>spectrum_swiftdisc</device>
|
||||
<device>spectrum_swiftdisc2</device>
|
||||
<device>spectrum_usource</device>
|
||||
<device>spectrum_uspeech</device>
|
||||
<device>spectrum_vtx5000</device>
|
||||
<device>spectrum_wafa</device>
|
||||
<device>st_kbd</device>
|
||||
<device>stereo_fx</device>
|
||||
<device>stic</device>
|
||||
<device>sun1cpu</device>
|
||||
<device>sv603</device>
|
||||
<device>sv806</device>
|
||||
<device>sw1000xg</device>
|
||||
<device>swtpc8212_device</device>
|
||||
<device>sx1541</device>
|
||||
<device>sys68k_cpu1</device>
|
||||
<device>sys68k_cpu20</device>
|
||||
<device>sys68k_cpu21</device>
|
||||
<device>sys68k_cpu21a</device>
|
||||
<device>sys68k_cpu21b</device>
|
||||
<device>sys68k_cpu21s</device>
|
||||
<device>sys68k_cpu21ya</device>
|
||||
<device>sys68k_cpu21yb</device>
|
||||
<device>sys68k_cpu30</device>
|
||||
<device>sys68k_cpu30be</device>
|
||||
<device>sys68k_cpu30lite</device>
|
||||
<device>sys68k_cpu30x</device>
|
||||
<device>sys68k_cpu30xa</device>
|
||||
<device>sys68k_cpu30za</device>
|
||||
<device>sys68k_cpu33</device>
|
||||
<device>sys68k_iscsi1</device>
|
||||
<device>sys68k_isio1</device>
|
||||
<device>t5182</device>
|
||||
<device>tanbus_ra32k</device>
|
||||
<device>tanbus_ra32krom</device>
|
||||
<device>tanbus_tandos</device>
|
||||
<device>tanbus_tanex</device>
|
||||
<device>tanbus_vid8082</device>
|
||||
<device>tandberg_tdv2100_disp_logic</device>
|
||||
<device>tandberg_tdv2100_keyboard</device>
|
||||
<device>tandy2kb</device>
|
||||
<device>tdl12</device>
|
||||
<device>technica</device>
|
||||
<device>tek410x_kbd</device>
|
||||
<device>tek_msu_fdc</device>
|
||||
<device>teletex800</device>
|
||||
<device>tetriskr_cga</device>
|
||||
<device>tgui9680</device>
|
||||
<device>ti99_bwg</device>
|
||||
|
@ -851,30 +1072,43 @@
|
|||
<device>ti99_pcode</device>
|
||||
<device>ti99_rs232</device>
|
||||
<device>ti99_speech</device>
|
||||
<device>ti99_tipi</device>
|
||||
<device>ti99_whtscsi</device>
|
||||
<device>ti_hx5102</device>
|
||||
<device>tiki100_8088</device>
|
||||
<device>tim011_kbd</device>
|
||||
<device>tk02</device>
|
||||
<device>tms32030</device>
|
||||
<device>tms32031</device>
|
||||
<device>tms32032</device>
|
||||
<device>tms32033</device>
|
||||
<device>to8_kbd</device>
|
||||
<device>to9_kbd</device>
|
||||
<device>to9p_kbd</device>
|
||||
<device>tp880v</device>
|
||||
<device>tp881v</device>
|
||||
<device>trs80m2kb</device>
|
||||
<device>turbogx</device>
|
||||
<device>turbogxp</device>
|
||||
<device>tv950kb</device>
|
||||
<device>tv955kb</device>
|
||||
<device>tvc_hbf</device>
|
||||
<device>tvga9000</device>
|
||||
<device>ultra12f</device>
|
||||
<device>ultra12f32</device>
|
||||
<device>ultra14f</device>
|
||||
<device>ultra24f</device>
|
||||
<device>umc6650</device>
|
||||
<device>upd7220</device>
|
||||
<device>upd7220a</device>
|
||||
<device>uts_400kbd</device>
|
||||
<device>uts_extw</device>
|
||||
<device>v102_kbd</device>
|
||||
<device>v1050kb</device>
|
||||
<device>v50_kbd</device>
|
||||
<device>v550_kbd</device>
|
||||
<device>vanta</device>
|
||||
<device>vector4_kbd</device>
|
||||
<device>vic1515</device>
|
||||
<device>vic1520</device>
|
||||
<device>vic20_fe3</device>
|
||||
|
@ -883,8 +1117,12 @@
|
|||
<device>victor9k_kb</device>
|
||||
<device>virge_pci</device>
|
||||
<device>virgedx_pci</device>
|
||||
<device>vision864</device>
|
||||
<device>vision964</device>
|
||||
<device>vision968</device>
|
||||
<device>voicebox</device>
|
||||
<device>votrax</device>
|
||||
<device>votrsc01</device>
|
||||
<device>votrsc01a</device>
|
||||
<device>vp700</device>
|
||||
<device>vtech_fdc</device>
|
||||
<device>vtech_rs232</device>
|
||||
|
@ -898,7 +1136,17 @@
|
|||
<device>wangpckb</device>
|
||||
<device>wd1002a_wx1</device>
|
||||
<device>wd1007a</device>
|
||||
<device>wd90c00_jk</device>
|
||||
<device>wd90c11_lr</device>
|
||||
<device>wd90c30_lr</device>
|
||||
<device>wd90c31_lr</device>
|
||||
<device>wd90c31a_lr</device>
|
||||
<device>wd90c31a_zs</device>
|
||||
<device>wd90c33_zz</device>
|
||||
<device>wd90c90_jk</device>
|
||||
<device>wd9710_pci</device>
|
||||
<device>wdxt_gen</device>
|
||||
<device>wg130</device>
|
||||
<device>wyse700</device>
|
||||
<device>x68k_cz6bs1</device>
|
||||
<device>x820kb</device>
|
||||
|
@ -910,10 +1158,6 @@
|
|||
<device>ymf281</device>
|
||||
<device>z8671</device>
|
||||
<device>z8682</device>
|
||||
<device>zip100_ide</device>
|
||||
<device>zorba_kbd</device>
|
||||
<device>zorro_a2091</device>
|
||||
<device>zorro_a590</device>
|
||||
<device>zorro_ar1</device>
|
||||
<device>zorro_ar2</device>
|
||||
<device>zorro_ar3</device>
|
||||
<device>zorro_buddha</device>
|
||||
<device>zxbus_neogs</device>
|
||||
|
|
18636
resources/mamenames.xml
18636
resources/mamenames.xml
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ Format notes:
|
|||
|
||||
"""
|
||||
from xml.sax.saxutils import escape
|
||||
from datetime import datetime
|
||||
from datetime import datetime,timezone
|
||||
import xml.etree.ElementTree as et
|
||||
import sys
|
||||
import os
|
||||
|
@ -77,7 +77,7 @@ for dat in sys.argv[1:]:
|
|||
games[name] = desc
|
||||
|
||||
print(f"Found {len(games)} games, {len(sorted(set(bioses)))} BIOSes and {len(sorted(set(devices)))} devices")
|
||||
ident_info = f"<!-- Generated on {datetime.utcnow().strftime('%F')}, from {', '.join(files)} -->"
|
||||
ident_info = f"<!-- Generated on {datetime.now(timezone.utc).strftime('%F')}, from {', '.join(files)} -->"
|
||||
|
||||
if len(games) > 0:
|
||||
with open('mamenames.xml', 'w') as f:
|
||||
|
|
Loading…
Add table
Reference in a new issue