update from master

This commit is contained in:
SophiaHadash 2021-07-16 10:10:55 +02:00
commit 75bf7781e3
218 changed files with 10117 additions and 9860 deletions

144
.clang-format Normal file
View file

@ -0,0 +1,144 @@
# SPDX-License-Identifier: MIT
#
# EmulationStation Desktop Edition
# .clang-format
#
# Style configuration file for automatic C++ code formatting using clang-format.
# This file requires at least clang-format version 10.0.
#
---
Language: Cpp
# BasedOnStyle: WebKit
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
# The following option requires version >= 11.0.
#####AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
- Regex: '.*'
Priority: 1
SortPriority: 0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: c++14
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
...

3
.gitignore vendored
View file

@ -28,6 +28,9 @@ emulationstation.core
# Profiling data # Profiling data
gmon.out gmon.out
# Patch/diff files
*.patch
# Build directories # Build directories
build build
Debug Debug

View file

@ -12,9 +12,9 @@ The 1.1 release brings many large changes including a fullscreen media viewer, a
A much better mechanism to find emulators and emulator cores has been implemented as well, which among other things removes the need to manually modify the Path variable on Windows to find RetroArch. It also eliminates the requirement for a separate Flatpak-specific es_systems.xml file on Linux. A much better mechanism to find emulators and emulator cores has been implemented as well, which among other things removes the need to manually modify the Path variable on Windows to find RetroArch. It also eliminates the requirement for a separate Flatpak-specific es_systems.xml file on Linux.
The User guide contains additional in-depth explanations of the new functionality. There are also several changes under the hood, such as the addition of the CImg image processing library, automatic code formatting of the entire codebase using clang-format, change of language standard from C++11 to C++14 and lots of general code refactoring.
Apart from this, many small improvements and bug fixes are part of the release, as listed below. Apart from this, numerous small improvements and bug fixes are part of the release, as detailed below.
### Detailed list of changes ### Detailed list of changes
@ -25,14 +25,16 @@ Apart from this, many small improvements and bug fixes are part of the release,
* Added a new video player based on FFmpeg * Added a new video player based on FFmpeg
* Added a 60 FPS frame rate upscaler option to the video player which results in slightly smoother playback for low frame rate videos (e.g. 24 and 30 FPS) * Added a 60 FPS frame rate upscaler option to the video player which results in slightly smoother playback for low frame rate videos (e.g. 24 and 30 FPS)
* Implemented a new mechanism for locating emulators and cores, with configurable find rules (this eliminates some hacks such as the separate Flatpak es_systems.cfg file) * Implemented a new mechanism for locating emulators and cores, with configurable find rules (this eliminates some hacks such as the separate Flatpak es_systems.cfg file)
* Added a Windows-specific find rule that searches the Registry for the App Paths keys, which should eliminate the need to modify the Path manually to find RetroArch * Added a Windows-specific find rule that searches the Registry for the App Paths keys, which eliminates the need to modify the Path manually to find RetroArch
* Removed the deprecated %COREPATH% setting and corresponding menu entry * Removed the deprecated %COREPATH% setting and corresponding menu entry
* The "Run in background (while game is launched)" option can now be enabled on all operating systems instead of only on Windows * The "Run in background (while game is launched)" option can now be enabled on all operating systems instead of only on Windows
* Added a workaround for a game launch issue on Windows when using AMD and Intel GPUs * Added a workaround for a game launch issue on Windows when using AMD and Intel GPUs
* Moved to the SDL GameController API which gives numerous improvements to the controller handling * Moved to the SDL GameController API which gives numerous improvements to the controller handling
* Default controller configuration is now automatically applied, input configuration should rarely if ever be required any longer except for deliberate button customization * Default controller configuration is now automatically applied, input configuration should rarely if ever be required any longer except for deliberate button customization
* Added support for selecting the controller type (Xbox, Xbox 360, PS4, PS5 and SNES), which changes the help icons, help text and the input configuration tool icons and text * Added support for selecting the controller type (Xbox, Xbox 360, PS4, PS5 and SNES), which changes the help icons, help text and the input configuration tool icons and text
* Added an option to limit the input in ES-DE to only the first controller (does not affect the emulators) * Added an option to limit the input in ES-DE to only the first controller (this does not affect the emulators)
* Switched the order of the "Back" and "Start" buttons (or equivalents) in the input configurator to align with the other button entries which go from left to right
* Added separate controller deadzone values for the triggers and thumbsticks
* Removed the startup notification regarding default keyboard mappings being in use, instead default mappings are now considered the recommended input configuration * Removed the startup notification regarding default keyboard mappings being in use, instead default mappings are now considered the recommended input configuration
* The controller input configuration is not automatically started any longer if there is no es_input.cfg file or if there are no applicable configuration entries in the file * The controller input configuration is not automatically started any longer if there is no es_input.cfg file or if there are no applicable configuration entries in the file
* Increased the max allowed size for images when scraping, which should now only downscale files which really need it * Increased the max allowed size for images when scraping, which should now only downscale files which really need it
@ -42,7 +44,7 @@ Apart from this, many small improvements and bug fixes are part of the release,
* The help text for the "A" button now shows "Enter" instead of "Launch" in the grouped custom collections view * The help text for the "A" button now shows "Enter" instead of "Launch" in the grouped custom collections view
* Added navigation sounds for some actions where it was missing, such as when attempting to add folders, placeholders or systems to custom collections * Added navigation sounds for some actions where it was missing, such as when attempting to add folders, placeholders or systems to custom collections
* Changed the custom collection "Jump to" navigation sound to the select sound instead of the scroll sound * Changed the custom collection "Jump to" navigation sound to the select sound instead of the scroll sound
* A notification is now displayed in the grouped custom collections view if a filter is applied to the collection * A notification is now displayed in the grouped custom collections view if a filter is applied to the selected collection
* Changed the default screensaver type from "dim" to "video" and made the fallback screensaver "dim" instead of "black" * Changed the default screensaver type from "dim" to "video" and made the fallback screensaver "dim" instead of "black"
* Moved the video screensaver audio setting to the sound settings menu * Moved the video screensaver audio setting to the sound settings menu
* Added support for the Nintendo Switch game system (using the Yuzu emulator) * Added support for the Nintendo Switch game system (using the Yuzu emulator)
@ -53,6 +55,7 @@ Apart from this, many small improvements and bug fixes are part of the release,
* The quit menu is now disabled by default, instead showing the "Quit EmulationStation" entry unless configured otherwise * The quit menu is now disabled by default, instead showing the "Quit EmulationStation" entry unless configured otherwise
* Removed the "Display game media from ROM directories" setting as it doesn't make sense to support this legacy functionality any longer * Removed the "Display game media from ROM directories" setting as it doesn't make sense to support this legacy functionality any longer
* Added support for using the %ESPATH% and %ROMPATH% variables in the slideshow screensaver custom image directory setting * Added support for using the %ESPATH% and %ROMPATH% variables in the slideshow screensaver custom image directory setting
* Improved scaling relative to the screen aspect ratio for various GUI components which enhances the layout on 4:3 displays and ultrawide monitors
* Removed the menu fade-in effect as it looked terrible * Removed the menu fade-in effect as it looked terrible
* Enabled the menu scale-up effect for the OpenGL ES renderer * Enabled the menu scale-up effect for the OpenGL ES renderer
* Renamed es_systems.cfg, es_settings.cfg and es_input.cfg to es_systems.xml, es_settings.xml and es_input.xml * Renamed es_systems.cfg, es_settings.cfg and es_input.cfg to es_systems.xml, es_settings.xml and es_input.xml
@ -69,17 +72,28 @@ Apart from this, many small improvements and bug fixes are part of the release,
* Added a DebugSkipInputLogging option which is intended primarily for development and needs to be manually set in es_settings.xml * Added a DebugSkipInputLogging option which is intended primarily for development and needs to be manually set in es_settings.xml
* Added the CImg library as a Git subtree and created some utility functions for it (used by the miximage generator and the game launch screen) * Added the CImg library as a Git subtree and created some utility functions for it (used by the miximage generator and the game launch screen)
* Added a function to ImageComponent to crop fully transparent areas around an image * Added a function to ImageComponent to crop fully transparent areas around an image
* Added and clarified startup log warnings for missing or invalid es_systems.xml platform tags
* Added a CMake option to control whether the VLC video player should be built, and set this to off by default * Added a CMake option to control whether the VLC video player should be built, and set this to off by default
* Made it possible to build on the Raspberry Pi 4 (tested on Raspberry Pi OS)
* Removed the deprecated VideoOmxComponent
* Removed the pointless APPLE_SKIP_INSTALL_LIBS CMake option * Removed the pointless APPLE_SKIP_INSTALL_LIBS CMake option
* Added a clang-format style configuration file to use for automatic code formatting
* Formatted the entire codebase using clang-format
* Integrated clang-tidy with CMake and made it possible to enable it via a flag
* Added the NanoSVG library as a proper Git subtree * Added the NanoSVG library as a proper Git subtree
* Changed the language standard from C++11 to C++14 * Changed the language standard from C++11 to C++14
### Bug fixes ### Bug fixes
* Marking all games as favorites for a system or folder or removing all favorite markings would sometimes crash the application * Marking all games as favorites for a system or folder or removing all favorite markings would sometimes crash the application
* Scraping new game media using the single-game scraper followed by a re-scrape that was aborted could crash the application
* The scraper search could be refined or skipped after the result was accepted which sometimes crashed the application
* Attempting to load a non-existent font file defined by the theme crashed the application instead of using the bundled font as fallback
* Refining a search before it was completed and then cancelling the dialog would lead to an empty scraper screen
* Games that were filtered out were included in the random game selection for the grouped custom collections view * Games that were filtered out were included in the random game selection for the grouped custom collections view
* After switching theme sets with only a single system available, diagonal slide transitions would sometimes play when moving to the system view * After switching theme sets with only a single system available, diagonal slide transitions would sometimes play when moving to the system view
* Ongoing slide transition animations would continue to play after switching theme sets * Ongoing slide transition animations would continue to play after switching theme sets
* When using the Video view style, the static image would not get rendered during the first Slide transition when coming from the System view
* Long game names that were horizontally scrolling in the gamelist view would sometimes flicker when returning to the start position * Long game names that were horizontally scrolling in the gamelist view would sometimes flicker when returning to the start position
* On Windows, images with Unicode characters in the game name that were resized when scraping would not get saved with valid filenames * On Windows, images with Unicode characters in the game name that were resized when scraping would not get saved with valid filenames
* The glitches when configuring trigger buttons in GuiInputConfig have been fixed * The glitches when configuring trigger buttons in GuiInputConfig have been fixed
@ -87,7 +101,10 @@ Apart from this, many small improvements and bug fixes are part of the release,
* GuiInputConfig didn't correctly inform which buttons could be skipped for some rows * GuiInputConfig didn't correctly inform which buttons could be skipped for some rows
* The scraper would sometimes consider very small images to be invalid * The scraper would sometimes consider very small images to be invalid
* The Quick System Select help prompt was shown even when there was only a single game system present * The Quick System Select help prompt was shown even when there was only a single game system present
* The "Back (cancel)" help prompt was missing for the single-game scraper
* The "Y" button help prompt wasn't displayed correctly when using the Grid view style * The "Y" button help prompt wasn't displayed correctly when using the Grid view style
* Fractional game rating values would always get rounded up
* Encountering a corrupt image file would lead to a continuous loop of attempts to load the image while filling the log file with error messages
* Cropping in ImageComponent didn't work correctly * Cropping in ImageComponent didn't work correctly
* The debug logging for the analog controller inputs had some inconsistent signs * The debug logging for the analog controller inputs had some inconsistent signs

View file

@ -14,8 +14,6 @@ set(CMAKE_VERBOSE_MAKEFILE OFF CACHE BOOL "Show verbose compiler output" FORCE)
# Package type to use for CPack on Linux. # Package type to use for CPack on Linux.
set(LINUX_CPACK_GENERATOR "DEB" CACHE STRING "CPack generator, DEB or RPM") set(LINUX_CPACK_GENERATOR "DEB" CACHE STRING "CPack generator, DEB or RPM")
set(VLC_PLAYER OFF CACHE BOOL "Whether to build with the VLC video player")
# Add local find modules to the CMake path. # Add local find modules to the CMake path.
list(APPEND CMAKE_MODULE_PATH list(APPEND CMAKE_MODULE_PATH
${CMAKE_CURRENT_SOURCE_DIR}/CMake/Utils ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Utils
@ -26,38 +24,37 @@ option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES})
option(GL "Set to ON if targeting Desktop OpenGL" ${GL}) option(GL "Set to ON if targeting Desktop OpenGL" ${GL})
option(RPI "Set to ON to enable Raspberry Pi specific build" ${RPI}) option(RPI "Set to ON to enable Raspberry Pi specific build" ${RPI})
option(CEC "Set to ON to enable CEC" ${CEC}) option(CEC "Set to ON to enable CEC" ${CEC})
option(VLC_PLAYER "Set to ON to build the VLC-based video player" ${VLC_PLAYER})
option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" ${CLANG_TIDY})
if (CLANG_TIDY)
find_program(CLANG_TIDY_BINARY NAMES clang-tidy)
if("${CLANG_TIDY_BINARY}" STREQUAL "CLANG_TIDY_BINARY-NOTFOUND")
message("-- CLANG_TIDY was set but the clang-tidy binary was not found")
else()
message("-- Building with the clang-tidy static analyzer")
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,-fuchsia-*,-hicpp-*,-llvm-*, \
-readability-braces-*,-google-readability-braces-*, \
-readability-uppercase-literal-suffix,-modernize-use-trailing-return-type, \
-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers")
endif()
endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# OpenGL setup. # OpenGL setup.
if(GLES)
set(GLSystem "Embedded OpenGL" CACHE STRING "The OpenGL system to be used")
elseif(GL)
set(GLSystem "Desktop OpenGL" CACHE STRING "The OpenGL system to be used")
# Check if we're running on a Raspberry Pi. # Check if we're running on a Raspberry Pi.
elseif(EXISTS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/bcm_host.h") if(EXISTS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/bcm_host.h")
MESSAGE("bcm_host.h found") message("-- Building on a Raspberry Pi (bcm_host.h found)")
set(BCMHOST found) # Setting BCMHOST seems to break OpenGL ES on the RPi 4 so set RPI instead.
#set(BCMHOST found)
set(RPI ON)
set(GLSystem "Embedded OpenGL" CACHE STRING "The OpenGL system to be used") set(GLSystem "Embedded OpenGL" CACHE STRING "The OpenGL system to be used")
elseif(GLES OR RPI)
# Check if we're running on OSMC Vero4K.
elseif(EXISTS "${CMAKE_FIND_ROOT_PATH}/opt/vero3/lib/libMali.so")
MESSAGE("libMali.so found")
set(VERO4K found)
set(GLSystem "Embedded OpenGL" CACHE STRING "The OpenGL system to be used")
# Check if we're running on olinuxino / odroid / etc.
elseif(EXISTS "${CMAKE_FIND_ROOT_PATH}/usr/lib/libMali.so" OR
EXISTS "${CMAKE_FIND_ROOT_PATH}/usr/lib/arm-linux-gnueabihf/libMali.so" OR
EXISTS "${CMAKE_FIND_ROOT_PATH}/usr/lib/aarch64-linux-gnu/libMali.so" OR
EXISTS "${CMAKE_FIND_ROOT_PATH}/usr/lib/arm-linux-gnueabihf/mali-egl/libmali.so" OR
EXISTS "${CMAKE_FIND_ROOT_PATH}/usr/lib/arm-linux-gnueabihf/libmali.so")
MESSAGE("libMali.so found")
set(GLSystem "Embedded OpenGL" CACHE STRING "The OpenGL system to be used") set(GLSystem "Embedded OpenGL" CACHE STRING "The OpenGL system to be used")
else() else()
set(GLSystem "Desktop OpenGL" CACHE STRING "The OpenGL system to be used") set(GLSystem "Desktop OpenGL" CACHE STRING "The OpenGL system to be used")
endif(GLES) endif()
set_property(CACHE GLSystem PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL") set_property(CACHE GLSystem PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL")
@ -80,7 +77,7 @@ if(NOT WIN32)
find_package(Pugixml REQUIRED) find_package(Pugixml REQUIRED)
find_package(RapidJSON REQUIRED) find_package(RapidJSON REQUIRED)
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
if(VLC_PLAYER OR RPI) if(VLC_PLAYER)
find_package(VLC REQUIRED) find_package(VLC REQUIRED)
endif() endif()
endif() endif()
@ -107,8 +104,8 @@ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
message("-- Compiler is GNU/GCC") message("-- Compiler is GNU/GCC")
execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpfullversion OUTPUT_VARIABLE G++_VERSION) execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpfullversion OUTPUT_VARIABLE G++_VERSION)
if(G++_VERSION VERSION_LESS 4.8) if(G++_VERSION VERSION_LESS 5.4)
message(SEND_ERROR "You need at least GCC 4.8 to compile EmulationStation-DE") message(SEND_ERROR "You need at least GCC 5.4 to compile EmulationStation-DE")
endif() endif()
if(WIN32) if(WIN32)
set(CMAKE_CXX_FLAGS "-mwindows ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "-mwindows ${CMAKE_CXX_FLAGS}")
@ -193,11 +190,6 @@ endif()
if(DEFINED BCMHOST OR RPI) if(DEFINED BCMHOST OR RPI)
add_definitions(-D_RPI_) add_definitions(-D_RPI_)
add_definitions(-DBUILD_VLC_PLAYER)
endif()
if(DEFINED VERO4K)
add_definitions(-D_VERO4K_)
endif() endif()
if(DEFINED libCEC_FOUND) if(DEFINED libCEC_FOUND)
@ -240,7 +232,7 @@ set(COMMON_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/external/CImg ${CMAKE_CURRENT_SOURCE_DIR}/external/CImg
${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src ${CMAKE_CURRENT_SOURCE_DIR}/external/nanosvg/src
${CMAKE_CURRENT_SOURCE_DIR}/es-core/src) ${CMAKE_CURRENT_SOURCE_DIR}/es-core/src)
if(VLC_PLAYER OR RPI) if(VLC_PLAYER)
set(COMMON_INCLUDE_DIRS ${COMMON_INCLUDE_DIRS} ${VLC_INCLUDE_DIR}) set(COMMON_INCLUDE_DIRS ${COMMON_INCLUDE_DIRS} ${VLC_INCLUDE_DIR})
endif() endif()
@ -250,7 +242,6 @@ if(WIN32)
if(NOT EXISTS ${WIN32_INCLUDE_DIR}) if(NOT EXISTS ${WIN32_INCLUDE_DIR})
message(SEND_ERROR "Can't find WIN32 include directory: ${WIN32_INCLUDE_DIR}") message(SEND_ERROR "Can't find WIN32 include directory: ${WIN32_INCLUDE_DIR}")
endif() endif()
#include_directories(${WIN32_INCLUDE_DIR})
endif() endif()
# Temporary solution until the VLC find module has been updated to work properly on macOS. # Temporary solution until the VLC find module has been updated to work properly on macOS.
@ -272,23 +263,12 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
list(APPEND COMMON_INCLUDE_DIRS ${ALSA_INCLUDE_DIRS}) list(APPEND COMMON_INCLUDE_DIRS ${ALSA_INCLUDE_DIRS})
endif() endif()
if(DEFINED BCMHOST) if(DEFINED BCMHOST OR RPI)
list(APPEND COMMON_INCLUDE_DIRS list(APPEND COMMON_INCLUDE_DIRS
"${CMAKE_FIND_ROOT_PATH}/opt/vc/include" "${CMAKE_FIND_ROOT_PATH}/opt/vc/include"
"${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos" "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos"
"${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux" "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux"
"${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos/pthreads") "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos/pthreads")
# Add Vero4k include directory.
elseif(DEFINED VERO4K)
list(APPEND COMMON_INCLUDE_DIRS "${CMAKE_FIND_ROOT_PATH}/opt/vero3/include")
else()
# Add OpenGL include directory.
if(${GLSystem} MATCHES "Desktop OpenGL")
list(APPEND COMMON_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR})
else()
# Add OpenGL ES include directory.
list(APPEND COMMON_INCLUDE_DIRS ${OPENGLES_INCLUDE_DIR})
endif()
endif() endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
@ -302,7 +282,7 @@ if(NOT WIN32)
${FREETYPE_LIBRARIES} ${FREETYPE_LIBRARIES}
${PUGIXML_LIBRARIES} ${PUGIXML_LIBRARIES}
${SDL2_LIBRARY}) ${SDL2_LIBRARY})
if(VLC_PLAYER OR RPI) if(VLC_PLAYER)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${VLC_LIBRARIES}) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${VLC_LIBRARIES})
endif() endif()
elseif(WIN32) elseif(WIN32)
@ -327,12 +307,12 @@ elseif(WIN32)
endif() endif()
else() else()
set(COMMON_LIBRARIES set(COMMON_LIBRARIES
"${PROJECT_SOURCE_DIR}/avcodec-59.dll" "${PROJECT_SOURCE_DIR}/avcodec-58.dll"
"${PROJECT_SOURCE_DIR}/avfilter-8.dll" "${PROJECT_SOURCE_DIR}/avfilter-7.dll"
"${PROJECT_SOURCE_DIR}/avformat-59.dll" "${PROJECT_SOURCE_DIR}/avformat-58.dll"
"${PROJECT_SOURCE_DIR}/avutil-57.dll" "${PROJECT_SOURCE_DIR}/avutil-56.dll"
"${PROJECT_SOURCE_DIR}/swresample-4.dll" "${PROJECT_SOURCE_DIR}/swresample-3.dll"
"${PROJECT_SOURCE_DIR}/swscale-6.dll" "${PROJECT_SOURCE_DIR}/swscale-5.dll"
"${PROJECT_SOURCE_DIR}/FreeImage.dll" "${PROJECT_SOURCE_DIR}/FreeImage.dll"
"${PROJECT_SOURCE_DIR}/glew32.dll" "${PROJECT_SOURCE_DIR}/glew32.dll"
"${PROJECT_SOURCE_DIR}/libcurl-x64.dll" "${PROJECT_SOURCE_DIR}/libcurl-x64.dll"
@ -368,8 +348,8 @@ endif()
# Add libCEC libraries. # Add libCEC libraries.
if(DEFINED libCEC_FOUND) if(DEFINED libCEC_FOUND)
if(DEFINED BCMHOST) if(DEFINED BCMHOST OR RPI)
list(APPEND COMMON_LIBRARIES vchiq_arm) list(APPEND COMMON_LIBRARIES bcm_host vchiq_arm)
endif() endif()
list(APPEND COMMON_LIBRARIES dl ${libCEC_LIBRARIES}) list(APPEND COMMON_LIBRARIES dl ${libCEC_LIBRARIES})
endif() endif()
@ -382,16 +362,16 @@ endif()
if(DEFINED BCMHOST) if(DEFINED BCMHOST)
link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib") link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib")
list(APPEND COMMON_LIBRARIES bcm_host brcmEGL ${OPENGLES_LIBRARIES}) list(APPEND COMMON_LIBRARIES bcm_host brcmEGL ${OPENGLES_LIBRARIES})
elseif(DEFINED VERO4K) elseif(RPI)
link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vero3/lib") link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib")
list(APPEND COMMON_LIBRARIES EGL ${OPENGLES_LIBRARIES}) list(APPEND COMMON_LIBRARIES ${OPENGLES_LIBRARIES})
else() endif()
if(${GLSystem} MATCHES "Desktop OpenGL") if(${GLSystem} MATCHES "Desktop OpenGL")
list(APPEND COMMON_LIBRARIES ${OPENGL_LIBRARIES}) list(APPEND COMMON_LIBRARIES ${OPENGL_LIBRARIES})
else() else()
list(APPEND COMMON_LIBRARIES EGL ${OPENGLES_LIBRARIES}) list(APPEND COMMON_LIBRARIES EGL ${OPENGLES_LIBRARIES})
endif() endif()
endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# Build directories. # Build directories.

View file

@ -43,22 +43,22 @@ This plan is under constant review so expect it to change from time to time. Sti
* Badges highlighting things like favorite games, completed games etc. (will require theme support) * Badges highlighting things like favorite games, completed games etc. (will require theme support)
* On-screen keyboard * On-screen keyboard
* Web proxy support for the scraper * Web proxy support for the scraper
* Add GLM library dependency for matrix and vector operations, decommissioning the built-in functions * Add GLM library dependency for matrix and vector operations, starting to decommission the built-in functions
* Flatpak and Snap releases on Linux
#### v1.3 #### v1.3
* Localization/multi-language support * Localization/multi-language support
* Overhaul of the theme handling, adding capabilities and improving compatibility with Recalbox and Batocera themes
* Scrapping the Grid view style and adding a general grid/wall component instead
* Checksum support for the scraper for exact searches and for determining when to overwrite files * Checksum support for the scraper for exact searches and for determining when to overwrite files
* Complete overhaul of the grid view style * Improved text and font functions, e.g. faster and cleaner line wrapping and more exact sizing
* A nice and useful grid view implementation in rbsimple-DE
* Improved text and font functions, e.g. faster and cleaner line wrapping
* Flatpak and Snap support on Linux
#### v1.4 #### v1.4
* Authoring tools to clean up orphaned gamelist entries, media files etc. * Authoring tools to clean up orphaned gamelist entries, media files etc.
* Scrollbars for menus and gamelists
* Support for pre-defined alternative emulators and cores (configured in es_systems.xml) * Support for pre-defined alternative emulators and cores (configured in es_systems.xml)
* Simple file browsing component
* Add 'time played' counter per game, similar to how it works in Steam * Add 'time played' counter per game, similar to how it works in Steam
* Preload all built-in resources and never clear them from the cache * Preload all built-in resources and never clear them from the cache
* Improved multi-threading * Improved multi-threading
@ -66,8 +66,7 @@ This plan is under constant review so expect it to change from time to time. Sti
#### v1.5 #### v1.5
* Bulk metadata editor * Bulk metadata editor
* Overhaul of the GUI element scaling and placement logic to make ES-DE look more consistent across different resolutions * Simple file browsing component
* Scrollbars for menus and gamelists
* Improve the performance of the GLSL shader code * Improve the performance of the GLSL shader code
* Animated menu elements like switches, tick boxes, smooth scrolling etc. * Animated menu elements like switches, tick boxes, smooth scrolling etc.
* Support for additional scraper services (if feasible?) * Support for additional scraper services (if feasible?)
@ -88,37 +87,21 @@ To see which features have been implemented in previous versions, please refer t
### Coding style ### Coding style
The coding style for ES-DE is mostly a combination of the Linux kernel style (although that's C it's close enough to C++ as far as I'm concerned) and Google's C++ guidelines. The visual appearance aspect of the coding style is automatically applied using clang-format, so to understand the exact formatting rules, refer to the .clang-format file in the root of the repository.
Please refer to these documents here: Due to this approach, all code changes must be auto-formatted before they are committed. You can read in [INSTALL-DEV.md](INSTALL-DEV.md#using-clang-format-for-automatic-code-formatting) how clang-format is installed and used.
https://www.kernel.org/doc/html/v4.10/process/coding-style.html \ But as clang-format won't change the actual code content or fix all code style choices, here are some additional key points:
https://google.github.io/styleguide/cppguide.html
**Some key points:** * Always write comments in C++ style, i.e. `//` instead of `/* */`
* Column width (line length) is 100 characters
* Indentation is 4 spaces, don't use tabs as they can be interpreted differently
* Line break is Unix-style (line feed only, no carriage return)
* Do not leave trailing whitespaces at the end of the lines (a good source code editor should have a setting to automatically trim these for you)
* When breaking up long lines into multiple lines, consider what could be useful data to grep for so you don't break in the middle of such a string
* Comments always in C++ style, i.e. `//` instead of `/* */`
* Comments should be proper sentences, starting with a capital letter and ending with a dot * Comments should be proper sentences, starting with a capital letter and ending with a dot
* Use K&R placements of braces, read the Linux kernel coding style document for clarifications * As a general rule, use C++ syntax instead of C syntax, for example `static_cast<int>(someFloatVariable)` instead of `(int)someFloatVariable`
* Always use spaces between keywords and opening brackets, i.e. `if ()`, `for ()`, `while ()` etc.
* Indentation of switch/case statements is optional, but it's usually easier to read the code with indentations in place
* Use `std::string` or `std::vector<char>` instead of `char *` or `char []` unless there is a specific reason requiring the latter
* Actually, try to use C++ syntax in general instead of C syntax, another example would be `static_cast<int>(someFloatVariable)` instead of `(int)someFloatVariable`
* If the arguments (and initializer list) for a function or class exceeds 4 items, arrange them vertically to make the code easier to read
* Always declare one variable per line, never combine multiple declarations of the same type * Always declare one variable per line, never combine multiple declarations of the same type
* Name local variables with the first word in small letters and the proceeding words starting with capital letters, e.g. `myExampleVariable` * Name member variables starting with an `m` such as `mMyMemberVariable` and name static variables starting with an `s` such as `sMyStaticVariable`
* Name member variables starting with an `m` such as `mMyMemberVariable` and name static variables with an `s` such as `sMyStaticVariable` * Short function definitions can be placed in either the .h or .cpp file depending on the situation
* Use the same naming convention for functions as for local variables, e.g. `someFunction()`
* Single-line function definitions are fine to put in the header files, but if it's more than one line, place it in the corresponding .cpp file
* Inline functions makes perfect sense to use, but don't overdo it by using them for functions that won't be called very frequently
* Don't put more than one statement on a single line (there are some exceptions though like lambda expressions and possibly switch statements)
* Avoid overoptimizations, especially if it sacrifices readability, makes the code hard to expand on or is error prone * Avoid overoptimizations, especially if it sacrifices readability, makes the code hard to expand on or is error prone
* For the rest, check the code and have fun! :) * Try to be coherent with the existing codebase regarding names, structure etc., it should not be obvious what person wrote which parts
* For the rest, check the code and have fun :)
### Building and configuring ### Building and configuring

View file

@ -2,7 +2,7 @@
# Programming # Programming
Alec "Aloshi" Lofquist (original version) \ Alec Lofquist (original version) \
http://www.aloshi.com http://www.aloshi.com
RetroPie Community (RetroPie fork) \ RetroPie Community (RetroPie fork) \
@ -110,6 +110,6 @@ https://freesound.org/people/farpro/sounds/264762
https://freesound.org/people/farpro/sounds/264763/ https://freesound.org/people/farpro/sounds/264763/
https://freesound.org/people/newlocknew/sounds/515827 \ https://freesound.org/people/newlocknew/sounds/515827 \
(Sample cut slightly.) (Sample cut slightly)
https://freesound.org/people/ertfelda/sounds/243701/ https://freesound.org/people/ertfelda/sounds/243701/

View file

@ -14,20 +14,20 @@ ES-DE is developed and compiled using Clang/LLVM and GCC on Unix, Clang/LLVM on
CMake is the build system for all the supported operating systems, used in conjunction with `make` on Unix and macOS and `nmake` and `make` on Windows. Xcode on macOS or Visual Studio on Windows are not required for building ES-DE and they have not been used during the development. The only exception is notarization of codesigned macOS packages which require the `altool` and `stapler` binaries that come bundled with Xcode. CMake is the build system for all the supported operating systems, used in conjunction with `make` on Unix and macOS and `nmake` and `make` on Windows. Xcode on macOS or Visual Studio on Windows are not required for building ES-DE and they have not been used during the development. The only exception is notarization of codesigned macOS packages which require the `altool` and `stapler` binaries that come bundled with Xcode.
For automatic code formatting [clang-format](https://clang.llvm.org/docs/ClangFormat.html) is used.
Any code editor can be used of course, but I recommend [VSCode](https://code.visualstudio.com). Any code editor can be used of course, but I recommend [VSCode](https://code.visualstudio.com).
## Building on Unix ## Building on Unix
The code has some dependencies. For building, you'll need CMake and development packages for cURL, FFmpeg, FreeImage, FreeType, pugixml, SDL2 and RapidJSON (and optionally VLC). There are some dependencies that need to be fulfilled in order to build ES-DE. These are detailed per operating system below.
**Installing dependencies:** **Debian/Ubuntu**
**On Debian/Ubuntu**
All of the required packages can be installed with apt-get: All of the required packages can be installed with apt-get:
``` ```
sudo apt-get install build-essential git cmake libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libcurl4-openssl-dev libpugixml-dev rapidjson-dev libasound2-dev libgl1-mesa-dev sudo apt-get install build-essential clang-format git cmake libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libfreetype6-dev libcurl4-openssl-dev libpugixml-dev rapidjson-dev libasound2-dev libgl1-mesa-dev
``` ```
If building with the optional VLC video player, the following packages are also needed: If building with the optional VLC video player, the following packages are also needed:
@ -35,28 +35,28 @@ If building with the optional VLC video player, the following packages are also
sudo apt-get install vlc libvlc-dev sudo apt-get install vlc libvlc-dev
``` ```
**On Fedora** **Fedora**
Use dnf to install all the required packages: Use dnf to install all the required packages:
``` ```
sudo dnf install gcc-c++ cmake SDL2-devel ffmpeg-devel freeimage-devel freetype-devel curl-devel pugixml-devel rapidjson-devel alsa-lib-devel mesa-libGL-devel sudo dnf install gcc-c++ clang-tools-extra cmake SDL2-devel ffmpeg-devel freeimage-devel freetype-devel curl-devel pugixml-devel rapidjson-devel alsa-lib-devel mesa-libGL-devel
``` ```
If building with the VLC video player, add the RPM Fusion repository. If building with the VLC video player, add the RPM Fusion repository.
Go to [https://rpmfusion.org/Configuration](https://rpmfusion.org/Configuration) and download the .rpm package for the free repository, then install it as well as VLC: Go to [https://rpmfusion.org/Configuration](https://rpmfusion.org/Configuration) and download the .rpm package for the free repository. Then install it, followed by VLC:
``` ```
sudo dnf install ./rpmfusion-free-release-33.noarch.rpm sudo dnf install ./rpmfusion-free-release-33.noarch.rpm
sudo dnf install vlc vlc-devel sudo dnf install vlc vlc-devel
``` ```
**On Manjaro** **Manjaro**
Use pacman to install all the required packages: Use pacman to install all the required packages:
``` ```
sudo pacman -S gcc make cmake pkgconf sdl2 ffmpeg freeimage freetype2 pugixml rapidjson sudo pacman -S gcc clang make cmake pkgconf sdl2 ffmpeg freeimage freetype2 pugixml rapidjson
``` ```
If building with the optional VLC video player, the following package is also needed: If building with the optional VLC video player, the following package is also needed:
@ -64,7 +64,26 @@ If building with the optional VLC video player, the following package is also ne
sudo pacman -S vlc sudo pacman -S vlc
``` ```
**On FreeBSD** **Raspberry Pi OS (Raspian)**
Note: The Raspberry Pi 4 is the minimum recommended model to use with ES-DE. As this type of device is quite weak and because the FFmpeg video player does not support hardware decoding on this platform, it's strongly adviced to build with the VLC player, which is hardware accelerated.
All of the required packages can be installed with apt-get:
```
sudo apt-get install clang-format cmake libsdl2-dev libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libfreeimage-dev libcurl4-gnutls-dev libpugixml-dev rapidjson-dev
```
If building with the optional VLC video player, the following packages are also needed:
```
sudo apt-get install vlc libvlc-dev
```
To build with CEC support you also need to install these packages:
```
sudo apt-get install libcec-dev libp8-platform-dev
```
**FreeBSD**
Use pkg to install the dependencies: Use pkg to install the dependencies:
``` ```
@ -76,13 +95,13 @@ If building with the optional VLC video player, the following package is also ne
pkg install vlc pkg install vlc
``` ```
Clang/LLVM and cURL should already be installed in the base OS image. Clang/LLVM and cURL should already be included in the base OS installation.
**On NetBSD** **NetBSD**
Use pkgin to install the dependencies: Use pkgin to install the dependencies:
``` ```
pkgin install git cmake pkgconf SDL2 ffmpeg4 freeimage pugixml rapidjson pkgin install clang git cmake pkgconf SDL2 ffmpeg4 freeimage pugixml rapidjson
``` ```
If building with the optional VLC video player, the following package is also needed: If building with the optional VLC video player, the following package is also needed:
@ -90,13 +109,13 @@ If building with the optional VLC video player, the following package is also ne
pkgin vlc pkgin vlc
``` ```
NetBSD ships with GCC by default, and although you should be able to install and use Clang/LLVM, it's probably easier to just stick to the default compiler environment. NetBSD ships with GCC by default, and although you should be able to use Clang/LLVM, it's probably easier to just stick to the default compiler environment. The reason why the clang package needs to be installed is to get clang-format onto the system.
**On OpenBSD** **OpenBSD**
Use pkg_add to install the dependencies: Use pkg_add to install the dependencies:
``` ```
pkg_add cmake pkgconf sdl2 ffmpeg freeimage pkg_add clang-tools-extra cmake pkgconf sdl2 ffmpeg freeimage
``` ```
If building with the optional VLC video player, the following package is also needed: If building with the optional VLC video player, the following package is also needed:
@ -143,7 +162,7 @@ cmake .
make make
``` ```
By default the master branch will be used, which is where the development takes place. To instead build the latest stable release, switch to the `stable` branch: By default the master branch will be used, which is where development takes place. To instead build the latest stable release, switch to the `stable` branch:
``` ```
cd emulationstation-de cd emulationstation-de
@ -207,22 +226,29 @@ scan-build cmake -DCMAKE_BUILD_TYPE=Debug .
scan-build make -j6 scan-build make -j6
``` ```
You open the report with the `scan-view` command which lets you browse it using your web browser. Note that the compilation time is much higher when using the static analyzer compared to a normal compilation. As well this tool generates a lot of extra files and folders in the build tree, so it may make sense to run it in a separate copy of the source folder to avoid having to clean up all this extra data when the analysis has been completed. You open the report with the `scan-view` command which lets you read it using your web browser. Note that the compilation time is much longer when using the static analyzer compared to a normal build. As well this tool generates a lot of extra files and folders in the build tree, so it may make sense to run it in a separate copy of the source folder to avoid having to clean up all this extra data when the analysis has been completed.
An even more advanced static analyzer is `clang-tidy`, to use it first make sure it's installed on your system and then run the following:
```
cmake -DCLANG_TIDY=on .
```
Even though many irrelevant checks are filtered out via the configuration, this will still likely produce a quite huge report (at least until most of the recommendations have been implemented). In the same manner as for scan-view, the compilation time is much longer when using this static analyzer compared to a normal build.
To build ES-DE with CEC support, enable the corresponding option, for example: To build ES-DE with CEC support, enable the corresponding option, for example:
``` ```
cmake -DCMAKE_BUILD_TYPE=Debug -DCEC=on . cmake -DCEC=on .
make make
``` ```
I have however not been able to test the CEC support and I'm not entirely sure how it's supposed to work. You will most likely need to install additional packages to get this to build. On Debian-based systems these are _libcec-dev_ and _libp8-platform-dev_. Note that the CEC support is currently untested.
To build with the GLES renderer, run the following: To build with the GLES renderer, run the following:
``` ```
cmake -DCMAKE_BUILD_TYPE=Debug -DGLES=on . cmake -DGLES=on .
make make
``` ```
Note that the GLES renderer is quite limited as there is no shader support for it, so ES-DE will definitely not look as pretty as when using the default OpenGL renderer. The GLES renderer is quite limited as there is no shader support for it, so ES-DE will definitely not look as pretty as when using the default OpenGL renderer. When building on a Raspberry Pi, the GLES renderer will be automatically selected.
Running multiple compile jobs in parallel is a good thing as it speeds up the build time a lot (scaling almost linearly). Here's an example telling make to run 6 parallel jobs: Running multiple compile jobs in parallel is a good thing as it speeds up the build time a lot (scaling almost linearly). Here's an example telling make to run 6 parallel jobs:
@ -238,7 +264,7 @@ The following example will build the application for installtion under /opt:
cmake -DCMAKE_INSTALL_PREFIX=/opt . cmake -DCMAKE_INSTALL_PREFIX=/opt .
``` ```
It's important to know that this is not only the directory used by the install script, the CMAKE_INSTALL_PREFIX variable also modifies code inside ES-DE used to locate the required program resources. So it's necessary that the install prefix corresponds to the location where the application will actually be installed. It's important to understand that this is not only the directory used by the install script, the CMAKE_INSTALL_PREFIX variable also modifies code inside ES-DE used to locate the required program resources. So it's necessary that the install prefix corresponds to the location where the application will actually be installed.
On Linux, if you're not building a package and instead intend to install using `make install` it's recommended to set the installation prefix to /usr/local instead of /usr. On Linux, if you're not building a package and instead intend to install using `make install` it's recommended to set the installation prefix to /usr/local instead of /usr.
@ -246,26 +272,24 @@ On Linux, if you're not building a package and instead intend to install using `
Both Clang/LLVM and GCC work fine for building ES-DE. Both Clang/LLVM and GCC work fine for building ES-DE.
I did some small benchmarks comparing Clang to GCC with the ES-DE codebase (as of writing it's year 2020) and it's pretty interesting. I did some small benchmarks comparing Clang 10.0 to GCC 9.3.0 with the ES-DE v1.1 codebase on an Intel Xeon W-2245 @ 3.90GHz running Kubuntu 20.04.2 LTS and it's pretty interesting.
Advantages with Clang (vs GCC): Advantages with Clang (vs GCC):
* 10% smaller binary size for a release build * 8% smaller binary size for a release build
* 17% smaller binary size for a debug build * 31% smaller binary size for a debug build
* 2% faster compile time for a release build * 16% faster compile time for a release build
* 16% faster compile time for a debug build * 25% faster compile time for a debug build
* 13% faster application startup time for a release build
* 4% faster application startup time for a debug build * 4% faster application startup time for a debug build
Advantage with GCC (vs Clang):
* 1% faster application startup time for a release build
*Release build: Optimizations enabled, debug info disabled, binary stripped.* \ *Release build: Optimizations enabled, debug info disabled, binary stripped.* \
*Debug build: Optimizations disabled, debug info enabled, binary not stripped.* *Debug build: Optimizations disabled, debug info enabled, binary not stripped.*
This Clang debug build is LLVM "native", i.e. intended to be debugged using the LLVM project debugger LLDB. The problem is that this is still not well integrated with VSCode that I use for development so I need to keep using GDB. But this is problematic as the libstd++ data required by GDB is missing in the binary, making it impossible to see the values of for instance std::string variables. This Clang debug build is LLVM "native", i.e. intended to be debugged using the LLVM project debugger LLDB. The problem is that this is still not well integrated with VSCode that I use for development so I need to keep using GDB. But this is problematic as the libstd++ data required by GDB is missing in the binary, making it impossible to see the values of for instance std::string variables.
It's possible to activate the additional debug info needed by GDB by using the flag `-D_GLIBCXX_DEBUG`. I've added this to CMakeLists.txt when using Clang, but this bloats the binary and makes the code much slower. Actually, instead of a 4% faster application startup, it's now 36% slower! The same goes for the binary size, instead of 17% smaller it's now 17% larger. It's possible to activate the additional debug info needed by GDB by using the flag `-D_GLIBCXX_DEBUG`. I've added this to CMakeLists.txt when using Clang, but this bloats the binary and makes the code much slower. Actually, instead of a 4% faster application startup, it's now 25% slower. The same goes for the binary size, instead of 31% smaller it's now 5% larger. The compilation time is still less than GCC but only by 10% instead of 25%.
I'm expecting this to be resolved in the near future though, and as I think Clang is an interesting compiler, I use it as the default when working on the project (I sometimes test with GCC to make sure that it still builds the software correctly). But I'm expecting this issue to be resolved in the future so the workaround can be removed.
It's by the way very easy to switch between LLVM and GCC using Ubuntu, just use the `update-alternatives` command: It's by the way very easy to switch between LLVM and GCC using Ubuntu, just use the `update-alternatives` command:
@ -311,7 +335,7 @@ However, when installing manually instead of building a package, it's recommende
Be aware that if using the GNOME desktop environment, /usr/share/pixmaps/emulationstation.svg must exist in order for the ES-DE icon to be shown in the Dash and task switcher. Be aware that if using the GNOME desktop environment, /usr/share/pixmaps/emulationstation.svg must exist in order for the ES-DE icon to be shown in the Dash and task switcher.
ES-DE will look in the following locations for the resources, in the listed order: ES-DE will look in the following locations for application resources, in the listed order:
* \<home\>/.emulationstation/resources/ * \<home\>/.emulationstation/resources/
* \<install prefix\>/share/emulationstation/resources/ * \<install prefix\>/share/emulationstation/resources/
@ -319,7 +343,7 @@ ES-DE will look in the following locations for the resources, in the listed orde
The resources directory is critical, without it the application won't start. The resources directory is critical, without it the application won't start.
And it will look in the following locations for the themes, also in the listed order: As well the following locations will be searched for themes, also in the listed order:
* \<home\>/.emulationstation/themes/ * \<home\>/.emulationstation/themes/
* \<install prefix\>/share/emulationstation/themes/ * \<install prefix\>/share/emulationstation/themes/
@ -327,7 +351,7 @@ And it will look in the following locations for the themes, also in the listed o
A theme is not mandatory to start the application, but ES-DE will be basically useless without it. A theme is not mandatory to start the application, but ES-DE will be basically useless without it.
The home directory will always take precedence, and any resources or themes located there will override the ones in the installation path or in the path of the ES-DE executable. As indicated above, the home directory will always take precedence and any resources or themes located there will override the ones in the installation path, or in the path of the ES-DE executable.
**Creating .deb and .rpm packages** **Creating .deb and .rpm packages**
@ -344,7 +368,7 @@ CPackDeb: - Generating dependency list
CPack: - package: /home/myusername/emulationstation-de/emulationstation-de-1.1.0-x64.deb generated. CPack: - package: /home/myusername/emulationstation-de/emulationstation-de-1.1.0-x64.deb generated.
``` ```
You may like to check that the dependencies look fine, as they're automatically generated by CMake: You may want to check that the dependencies look fine, as they're (mostly) automatically generated by CMake:
``` ```
dpkg -I ./emulationstation-de-1.1.0-x64.deb dpkg -I ./emulationstation-de-1.1.0-x64.deb
@ -375,17 +399,17 @@ CPackRPM: Will use GENERATED spec file: /home/myusername/emulationstation-de/_CP
CPack: - package: /home/myusername/emulationstation-de/emulationstation-de-1.1.0-x64.rpm generated. CPack: - package: /home/myusername/emulationstation-de/emulationstation-de-1.1.0-x64.rpm generated.
``` ```
On Fedora, you need to install rpmbuild before this command can be run though: On Fedora, you need to install rpmbuild before this command can be run:
``` ```
sudo dnf install rpm-build sudo dnf install rpm-build
``` ```
After the package generation you can check that the metadata looks fine using this command: After the package generation you can check that the metadata looks fine using the `rpm` command:
``` ```
rpm -qi ./emulationstation-de-1.1.0-x64.rpm rpm -qi ./emulationstation-de-1.1.0-x64.rpm
``` ```
And to see the automatically generated dependency requirements, run this: To see the automatically generated dependencies, run this:
``` ```
rpm -q --requires ./emulationstation-de-1.1.0-x64.rpm rpm -q --requires ./emulationstation-de-1.1.0-x64.rpm
``` ```
@ -405,7 +429,7 @@ As for code editing, I use [VSCode](https://code.visualstudio.com). I suppose Xc
Install the Command Line Tools which include Clang/LLVM, Git, make etc. Simply open a terminal and enter the command `clang`. This will open a dialog that will let you download and install the tools. Install the Command Line Tools which include Clang/LLVM, Git, make etc. Simply open a terminal and enter the command `clang`. This will open a dialog that will let you download and install the tools.
Following this, install the Homebrew package manager which will simplify the rest of the installation greatly. Install it by runing the following in a terminal window: Following this, install the Homebrew package manager which will greatly simplify the rest of the installation. Install it by runing the following in a terminal window:
``` ```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
``` ```
@ -416,7 +440,7 @@ Be aware that Homebrew can be really slow, especially when it compiles packages
Install the required tools and dependencies: Install the required tools and dependencies:
``` ```
brew install cmake pkg-config nasm fdk-aac libvpx sdl2 freeimage freetype pugixml rapidjson brew install clang-format cmake pkg-config nasm fdk-aac libvpx sdl2 freeimage freetype pugixml rapidjson
``` ```
If building with the optional VLC video player, then run this as well: If building with the optional VLC video player, then run this as well:
@ -427,7 +451,7 @@ brew install --cask vlc
**Compiling FFmpeg:** **Compiling FFmpeg:**
The FFmpeg build distributed via Homebrew has a lot of dependencies we don't need, and which would make it very difficult to package it in an installer. Instead we will build this software with only some limited options: The FFmpeg build distributed via Homebrew has a lot of dependencies we don't need, and which would make it very difficult to package the application. Instead we will build this software with only some limited options:
``` ```
git clone https://github.com/FFmpeg/FFmpeg.git git clone https://github.com/FFmpeg/FFmpeg.git
@ -444,7 +468,7 @@ Enable developer mode to avoid annoying password requests when attaching the deb
``` ```
sudo /usr/sbin/DevToolsSecurity --enable sudo /usr/sbin/DevToolsSecurity --enable
``` ```
It makes me wonder who designed this functionality and what was their thinking, but a simple command is enough to not having to ponder this any further. It makes me wonder who designed this functionality and what was their thinking, I've never seen anything like this on any of the other systems I've been developing on.
If required, define SDKROOT. This is only needed if during compilation you get error messages regarding missing include files. Running the following will properly setup the development environment paths: If required, define SDKROOT. This is only needed if during compilation you get error messages regarding missing include files. Running the following will properly setup the development environment paths:
``` ```
@ -478,36 +502,35 @@ cmake .
make make
``` ```
To build ES-DE with the VLC video player in addition to the default FFmpeg player, enable the VLC_PLAYER option, for example: To build ES-DE with the VLC video player in addition to the default FFmpeg player, enable the VLC_PLAYER option:
``` ```
cmake -DVLC_PLAYER=on . cmake -DVLC_PLAYER=on .
make make
``` ```
To generate a debug build, run this instead: To generate a debug build, run this:
``` ```
cmake -DCMAKE_BUILD_TYPE=Debug . cmake -DCMAKE_BUILD_TYPE=Debug .
make make
``` ```
Keep in mind though that a debug version will be much slower due to all compiler optimizations being disabled. Keep in mind that the debug version will be much slower due to all compiler optimizations being disabled.
Running `make -j6` (or whatever number of parallel jobs you prefer) speeds up the compilation time if you have cores to spare. Running `make -j6` (or whatever number of parallel jobs you prefer) speeds up the compilation time if you have cores to spare.
After building ES-DE and trying to execute the application, there could be issues with finding the dynamic link libraries for VLC (assuming VLC is actually enabled for the build) as these are not installed into a standard location but rather into the /Applications folder. As such, you may need to set the DYLD_LIBRARY_PATH environmental variable to find the VLC libraries. Note that this is not intended or required for the release build that will be shipped in a DMG installer or if you manually install ES-DE using 'make install'. It's only needed to be able to run the binary from the build directory. The following will of course only be active during your session, and you need to set the variable for each terminal window that you want to start ES-DE from, unless you add it to your shell profile file: After building ES-DE and trying to execute the application, there could be issues with finding the dynamic link libraries for VLC (assuming VLC was enabled for the build) as these are not installed into a standard location but rather into the /Applications folder. As such, you may need to set the DYLD_LIBRARY_PATH environmental variable to find the VLC libraries. Note that this is not intended or required for the release build that will be shipped in a DMG installer or if you manually install ES-DE using _make install_. It's only needed to be able to run the binary from the build directory. You should add this to your shell profile file to avoid having to set it each time you open a new terminal window:
``` ```
export DYLD_LIBRARY_PATH=/Applications/VLC.app/Contents/MacOS/lib export DYLD_LIBRARY_PATH=/Applications/VLC.app/Contents/MacOS/lib
``` ```
**Note:** Running ES-DE from the build directory may be a bit flaky as there is no Info.plist file available which is required for setting the proper window mode and such. It's therefore recommended to run the application from the installation directory for any more in-depth testing. But normal debugging can of course be done from the build directory. Running ES-DE from the build directory may be a bit flaky as there is no Info.plist file available which is required for setting the proper window mode and such. It's therefore recommended to run the application from the installation directory for any more in-depth testing. But normal debugging can of course be done from the build directory.
Be aware that the approach taken for macOS has the limitation that you can't build for previous operating system versions. You can certainly set CMAKE_OSX_DEPLOYMENT_TARGET to whatever version you like, but the problem is that the Homebrew libraries will most likely not work on earlier macOS versions. In theory this can be worked around by building all these libraries yourself with a lower deployment target, but it's hardly worth the effort. It's better to build on the lowest OS version that should be supported and rely on forward compatibility. Be aware that the approach taken for macOS has the limitation that you can't build for previous operating system versions. You can certainly set CMAKE_OSX_DEPLOYMENT_TARGET to whatever version you like, but the problem is that the Homebrew libraries will most likely not work on earlier macOS versions. In theory this can be worked around by building all these libraries yourself with a lower deployment target, but it's hardly worth the effort. It's better to build on the lowest OS version that should be supported and rely on forward compatibility.
**Code signing:** **Code signing:**
Due to the Apple notarization requirement implemented as of macOS 10.14.5 a build with simple code signing is needed for versions up to 10.13 and another build with both code signing and notarization will be required for versions 10.14 and higher. Due to the Apple notarization requirement implemented as of macOS 10.14.5 a build with simple code signing is needed for versions up to 10.13 and another build with both code signing and notarization is required for version 10.14 and higher.
macOS code signing is beyond the scope of this document, but the option MACOS_CODESIGN_IDENTITY is used to to specify the code signing certificate identity, for example: macOS code signing is beyond the scope of this document, but the option MACOS_CODESIGN_IDENTITY is used to specify the code signing certificate identity, for example:
``` ```
cmake -DMACOS_CODESIGN_IDENTITY="My Name" . cmake -DMACOS_CODESIGN_IDENTITY="My Name" .
``` ```
@ -516,12 +539,14 @@ Assuming the code signing ceritificate is properly setup in Keychain Access, the
**Legacy build:** **Legacy build:**
Normally ES-DE is meant to be built for macOS 10.14 and higher, but a legacy build for earlier operating system versions can be enabled. This has been tested with a minimum version of 10.11 and it's unclear if it works with even older macOS versions. Normally ES-DE is meant to be built for macOS 10.14 and higher, but a legacy build for earlier operating system versions can be enabled. This has been tested with a minimum version of 10.11. It's unclear if it works with even older macOS versions.
To enable a legacy build, change the CMAKE_OSX_DEPLOYMENT_TARGET variable in CMakeLists.txt from 10.14 to whatever version you would like to build for. This will disable Hardened Runtime if signing is enabled and it will add 'legacy' to the DMG installer file name when running CPack. To enable a legacy build, change the CMAKE_OSX_DEPLOYMENT_TARGET variable in CMakeLists.txt from 10.14 to whatever version you would like to build for. This will disable Hardened Runtime if signing is enabled and it will add 'legacy' to the DMG installer file name when running CPack.
You also need to modify es-app/assets/EmulationStation-DE_Info.plist and set the key SMinimumSystemVersion to the version you're building for. You also need to modify es-app/assets/EmulationStation-DE_Info.plist and set the key SMinimumSystemVersion to the version you're building for.
Due to issues with getting FFmpeg to compile on some older macOS versions, ES-DE v1.0.1 is the last version where a legacy build has been tested.
**Installing:** **Installing:**
As macOS does not have any package manager which would have handled the library dependencies, we need to bundle the required shared libraries with the application. Copy the following .dylib files from their respective installation directories to the emulationstation-de build directory: As macOS does not have any package manager which would have handled the library dependencies, we need to bundle the required shared libraries with the application. Copy the following .dylib files from their respective installation directories to the emulationstation-de build directory:
@ -547,12 +572,12 @@ All except the VLC libraries should be located in /usr/local/lib. The VLC librar
Note that the filenames could be slightly different depending on what versions you have installed on your system. Note that the filenames could be slightly different depending on what versions you have installed on your system.
After copying the libraries to the build directory, set the permissions to writable: After copying the libraries to the build directory, set their permissions like this:
``` ```
chmod 755 ./*.dylib chmod 755 ./*.dylib
``` ```
There are some secondary internal dependencies between some of these library files, and these are baked into the files as absolut paths. As such we need to rewrite these to rpaths (relative paths) which is done using the install_name_tool command. There are some secondary internal dependencies between some of these library files, and these are baked into the files as absolute paths. As such we need to rewrite these to rpaths (relative paths) which is done using the install_name_tool command.
A script is available to automate this: `tools/macOS_change_dylib_rpaths.sh` A script is available to automate this: `tools/macOS_change_dylib_rpaths.sh`
@ -636,23 +661,23 @@ This will be the directory structure for the installation (the VLC-related files
/Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/* /Applications/EmulationStation Desktop Edition.app/Contents/Resources/themes/*
``` ```
ES will look in the following locations for the resources, in the listed order: ES-DE will look in the following locations for application resources, in the listed order:
* \<home\>/.emulationstation/resources/ * \<home\>/.emulationstation/resources/
* \<ES-DE executable directory\>/../Resources/resources/ * \<ES-DE executable directory\>/../Resources/resources/
* \<ES-DE executable directory\>/resources/ * \<ES-DE executable directory\>/resources/
**Note:** The resources directory is critical, without it the application won't start. The resources directory is critical, without it the application won't start.
And it will look in the following locations for the themes, also in the listed order: As well the following locations will be searched for themes, also in the listed order:
* \<HOME\>/.emulationstation/themes/ * \<HOME\>/.emulationstation/themes/
* \<ES-DE executable directory\>/../Resources/themes/ * \<ES-DE executable directory\>/../Resources/themes/
* \<ES-DE executable directory\>/themes/ * \<ES-DE executable directory\>/themes/
A theme is not mandatory to start the application, but ES will be basically useless without it. A theme is not mandatory to start the application, but ES-DE will be basically useless without it.
The home directory will always take precedence, and any resources or themes located there will override the ones in the installation path or in the path of the ES-DE executable. As indicated above, the home directory will always take precedence and any resources or themes located there will override the ones in the path of the ES-DE executable.
**Creating a .dmg installer:** **Creating a .dmg installer:**
@ -668,7 +693,7 @@ CPack: Create package
CPack: - package: /Users/myusername/emulationstation-de/EmulationStation-DE-1.1.0-x64.dmg generated. CPack: - package: /Users/myusername/emulationstation-de/EmulationStation-DE-1.1.0-x64.dmg generated.
``` ```
Generating .dmg installers on older version of macOS seems to make them forward compatible to a pretty good extent, for instance building on El Capitan seems to generate an application that is usable on Catalina and Big Sur. The other way around does however not seem to be true, which is quite unsurprising. Generating .dmg installers on older version of macOS seems to make them forward compatible to a pretty good extent, for instance building on El Capitan seems to generate an application that is usable on Catalina and Big Sur. The other way around is not true due to the use of dependencies from the Homebrew repository.
**Special considerations regarding run-paths:** **Special considerations regarding run-paths:**
@ -701,29 +726,12 @@ This is what an incorrect line would look like:
`/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib (compatibility version 13.0.0, current version 13.0.0)` `/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib (compatibility version 13.0.0, current version 13.0.0)`
This is the section in es-app/CMakeLists.txt that would need to be modified:
```
add_custom_command(TARGET EmulationStation POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL}
-change /usr/local/lib/libavcodec.58.dylib @rpath/libavcodec.58.dylib
-change /usr/local/lib/libavfilter.7.dylib @rpath/libavfilter.7.dylib
-change /usr/local/lib/libavformat.58.dylib @rpath/libavformat.58.dylib
-change /usr/local/lib/libavutil.56.dylib @rpath/libavutil.56.dylib
-change /usr/local/lib/libswresample.3.dylib @rpath/libswresample.3.dylib
-change /usr/local/lib/libswscale.5.dylib @rpath/libswscale.5.dylib
-change /usr/local/opt/freeimage/lib/libfreeimage.dylib @rpath/libfreeimage.dylib
-change /usr/local/opt/freetype/lib/libfreetype.6.dylib @rpath/libfreetype.6.dylib
-change /usr/local/opt/libpng/lib/libpng16.16.dylib @rpath/libpng16.16.dylib
-change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib
$<TARGET_FILE:EmulationStation>)
```
## Building on Windows ## Building on Windows
Both MSVC and MinGW (GCC) work fine for building ES-DE on Windows. Both MSVC and MinGW (GCC) work fine for building ES-DE on Windows.
Although I would prefer to exclude support for MSVC, this proprietary compiler simply works much better when developing as it's much faster than MinGW when linking debug builds and when actually debugging. For release builds though MinGW is very fast and it seems as if ES-DE starts around 18% faster when built with MinGW so this compiler probably generates more efficient code overall. As well MSVC requires a lot more junk DLL files to be distributed with the application so it's not a good candidate for the final release build. Although I would prefer to exclude support for MSVC, this compiler simply works much better when developing as it's much faster than MinGW when linking debug builds and when actually debugging. But for release builds MinGW is very fast and ES-DE starts around 18% faster when built with MinGW meaning this compiler probably generates more efficient code overall. As well MSVC requires a lot more junk DLL files to be distributed with the application so it's not a good candidate for the final release build.
For this reason I think MSVC makes sense for development and MinGW for the releases. For this reason I think MSVC makes sense for development and MinGW for the releases.
@ -746,7 +754,7 @@ Just-In-Time debugger
C++ CMake tools for Windows C++ CMake tools for Windows
``` ```
If you intend to use both MinGW and MSVC on the machine, it's probably better to exclude CMake and install it manually as described in the MinGW setup instructions below. If you intend to use both MinGW and MSVC on the same machine, it's probably better to exclude CMake and install it manually as described in the MinGW setup instructions below.
The way the MSVC environment works is that a specific developer shell is provided where the build environment is properly configured. You open this from the Start menu via `Visual Studio 2019` -> `Visual Studio tools` -> `VC` -> `x64 Native Tools Command Prompt for VS 2019`. The way the MSVC environment works is that a specific developer shell is provided where the build environment is properly configured. You open this from the Start menu via `Visual Studio 2019` -> `Visual Studio tools` -> `VC` -> `x64 Native Tools Command Prompt for VS 2019`.
@ -762,30 +770,35 @@ Download the following packages and install them:
[https://jmeubank.github.io/tdm-gcc](https://jmeubank.github.io/tdm-gcc) [https://jmeubank.github.io/tdm-gcc](https://jmeubank.github.io/tdm-gcc)
After installation, make a copy of `TDM-GCC-64/bin/mingw32-make` to `make` just for convenience. After installation, make a copy of `TDM-GCC-64\bin\mingw32-make` to `make` just for convenience.
Note that most GDB builds for Windows have broken Python support so that pretty printing won't work. The recommended MinGW installation should work fine though. Note that most GDB builds for Windows have broken Python support so that pretty printing won't work. The recommended MinGW distribution should work fine though.
**Other preparations:** **Other preparations:**
In order to get clang-format onto the system you need to download and install Clang: \
[https://llvm.org/builds](https://llvm.org/builds)
Just run the installer and make sure to select the option _Add LLVM to the system PATH for current user_.
Install your editor of choice, I use [VSCode](https://code.visualstudio.com). Install your editor of choice, I use [VSCode](https://code.visualstudio.com).
Configure Git. I won't get into the details on how it's done, but there are many resources available online to support with this. The `Git Bash` shell shipped with Git for Windows is very useful though as it's somewhat reproducing a Unix environment using MinGW/MSYS2. Configure Git. I won't get into the details on how this is done, but there are many resources available online to support with this. The `Git Bash` shell shipped with Git for Windows is very useful though as it's somewhat reproducing a Unix environment using MinGW/MSYS2.
It's strongly recommended to set line breaks to Unix-style (line feed only) directly in the editor, although it can also be configured in Git for conversion during commits. The source code for ES-DE only uses Unix-style line breaks. It's strongly recommended to set line breaks to Unix-style (line feed only) directly in the editor. But if not done, lines breaks will anyway be converted when running clang-format on the code, as explained [here](INSTALL-DEV.md#using-clang-format-for-automatic-code-formatting).
In the description below it's assumed that all build steps for MinGW/GCC will be done in the Git Bash shell, and all the build steps for MSVC will be done in the MSVC developer console (x64 Native Tools Command Prompt for VS). In the descriptions below it's assumed that all build steps for MinGW/GCC will be done in the Git Bash shell, and all the build steps for MSVC will be done in the MSVC developer console (x64 Native Tools Command Prompt for VS).
**Download the dependency packages:** **Download the dependency packages:**
FFmpeg (choose a package with win64-gpl-shared in the filename, the latest snapshot build should generally be fine to use) \ FFmpeg (choose the n4.4 package with win64-gpl-shared in the filename, the snapshot version will not work) \
[https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases) [https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases)
FreeImage (binary distribution) \ FreeImage (binary distribution) \
[https://sourceforge.net/projects/freeimage](https://sourceforge.net/projects/freeimage) [https://sourceforge.net/projects/freeimage](https://sourceforge.net/projects/freeimage)
cURL (Windows 64 bit binary, the MinGW version even if using MSVC) \ cURL (Windows 64 bit binary, select the MinGW version even if using MSVC) \
[https://curl.haxx.se/download.html](https://curl.haxx.se/download.html) [https://curl.haxx.se/download.html](https://curl.haxx.se/download.html)
SDL2 (development libraries, MinGW or VC/MSVC) \ SDL2 (development libraries, MinGW or VC/MSVC) \
@ -859,7 +872,7 @@ make
[RapidJSON](http://rapidjson.org) [RapidJSON](http://rapidjson.org)
For RapidJSON, you don't need to compile it, you just need the include files: For RapidJSON you don't need to compile, you just need the include files:
``` ```
git clone git://github.com/Tencent/rapidjson.git git clone git://github.com/Tencent/rapidjson.git
@ -877,7 +890,7 @@ This works the same as on Unix or macOS, just run the following:
git clone https://gitlab.com/leonstyhre/emulationstation-de.git git clone https://gitlab.com/leonstyhre/emulationstation-de.git
``` ```
By default the master branch will be used, which is where the development takes place. To instead build the latest stable release, switch to the `stable` branch: By default the master branch will be used, which is where development takes place. To instead build the latest stable release, switch to the `stable` branch:
``` ```
cd emulationstation-de cd emulationstation-de
@ -896,7 +909,7 @@ You may need to create the SDL2 directory manually and copy the header files the
For FFmpeg, copy the directories libavcodec, libavfilter, libavformat and libavutil. For FFmpeg, copy the directories libavcodec, libavfilter, libavformat and libavutil.
It should then look something like this: It should look something like this:
``` ```
$ ls -1 include/ $ ls -1 include/
@ -924,18 +937,18 @@ Copy the files to the `emulationstation-de` build directory. Most of them will c
**Required files for MSVC:** **Required files for MSVC:**
``` ```
avcodec-59.dll avcodec-58.dll
avcodec.lib avcodec.lib
avfilter.lib avfilter.lib
avfilter-8.dll avfilter-7.dll
avformat-59.dll avformat-58.dll
avformat.lib avformat.lib
avutil-57.dll avutil-56.dll
avutil.lib avutil.lib
postproc-56.dll postproc-55.dll
swresample-4.dll swresample-3.dll
swresample.lib swresample.lib
swscale-6.dll swscale-5.dll
swscale.lib swscale.lib
FreeImage.dll FreeImage.dll
FreeImage.lib FreeImage.lib
@ -982,13 +995,13 @@ lib /def:libvlc.def /out:libvlc.lib /machine:x64
**Required files for MinGW:** **Required files for MinGW:**
``` ```
avcodec-59.dll avcodec-58.dll
avfilter-8.dll avfilter-7.dll
avformat-59.dll avformat-58.dll
avutil-57.dll avutil-56.dll
postproc-56.dll postproc-55.dll
swresample-4.dll swresample-3.dll
swscale-6.dll swscale-5.dll
FreeImage.dll FreeImage.dll
glew32.dll glew32.dll
libcrypto-1_1-x64.dll (from the OpenSSL package, located in Git MinGW/MSYS2 under \mingw64\bin) libcrypto-1_1-x64.dll (from the OpenSSL package, located in Git MinGW/MSYS2 under \mingw64\bin)
@ -1005,11 +1018,9 @@ vcomp140.dll (From Visual C++ Redistributable for Visual Studio 201
**Additional files for both MSVC and MinGW if building with the VLC video player:** **Additional files for both MSVC and MinGW if building with the VLC video player:**
In addition to the files above, you need to copy some libraries from the VLC `plugins` folder to be able to play video files. There is a subdirectory structure under the plugins folder but there is no requirement to retain this as libVLC apparently looks recursively for the .dll files. In addition to the files above, you need to copy some libraries from the VLC `plugins` folder. This contains a subdirectory structure but there is no requirement to retain this as libVLC apparently looks recursively for the .dll files.
It's a bit tricky to know which libraries are really needed. But as the plugins directory is around 120 MB (as of VLC version 3.0.11), we definitely only want to copy the files we need. The following libraries seem to be required to play most video and audio formats:
The following files seem to be required to play most video and audio formats (place them in `emulationstation-de\plugins`):
``` ```
access\libfilesystem_plugin.dll access\libfilesystem_plugin.dll
@ -1029,7 +1040,9 @@ video_chroma\libswscale_plugin.dll
video_output\libvmem_plugin.dll video_output\libvmem_plugin.dll
``` ```
The combined size of these files is around 22 MB which is more reasonable. The reason to not simply copy all plugins is that the combined size of these is around 120 MB (as of VLC version 3.0.11) and the size of the selected files listed above is around 22 MB, which is more reasonable.
Place the files in the `emulationstation-de\plugins\` directory.
**Building ES-DE using MSVC:** **Building ES-DE using MSVC:**
@ -1097,7 +1110,7 @@ Note that compilation time is much longer than on Unix or macOS, and linking tim
If you are running Windows in a virtualized environment such as QEMU-KVM that does not support HW accelerated OpenGL, you can install the Mesa3D for Windows library, which can be downloaded at [https://fdossena.com/?p=mesa/index.frag](https://fdossena.com/?p=mesa/index.frag). If you are running Windows in a virtualized environment such as QEMU-KVM that does not support HW accelerated OpenGL, you can install the Mesa3D for Windows library, which can be downloaded at [https://fdossena.com/?p=mesa/index.frag](https://fdossena.com/?p=mesa/index.frag).
You simply extract the opengl32.dll file into the ES-DE directory and this will enable the llvmpipe renderer. The performance will be terrible of course, but everything should work and it should be good enough for test building on Windows without having to reboot your computer to a native Windows installation. (Note that you may need to copy opengl32.dll to your RetroArch installation directory as well to get the emulators to work correctly.) You simply extract the opengl32.dll file into the ES-DE directory and this will enable the llvmpipe renderer. The performance will be terrible of course, but everything should work and it should be good enough for test building on Windows without having to reboot your computer to a native Windows installation. (Note that you may need to copy opengl32.dll to your RetroArch installation directory as well to get the emulators to work somehow correctly.)
Obviously this library is only intended for development and will not be shipped with ES-DE. Obviously this library is only intended for development and will not be shipped with ES-DE.
@ -1123,23 +1136,98 @@ CPack: - package: C:/Programming/emulationstation-de/EmulationStation-DE-1.1.0-x
The default installation directory suggested by the installer is `C:\Program Files\EmulationStation-DE` but this can of course be changed by the user. The default installation directory suggested by the installer is `C:\Program Files\EmulationStation-DE` but this can of course be changed by the user.
ES will look in the following locations for the resources, in the listed order: ES-DE will look in the following locations for application resources, in the listed order:
* \<home\>\\.emulationstation\resources\ * \<home\>\\.emulationstation\resources\
* \<ES-DE executable directory\>\resources\ * \<ES-DE executable directory\>\resources\
The resources directory is critical, without it the application won't start. The resources directory is critical, without it the application won't start.
And it will look in the following locations for the themes, also in the listed order: As well the following locations will be searched for themes, also in the listed order:
* \<home\>\\.emulationstation\themes\ * \<home\>\\.emulationstation\themes\
* \<ES-DE executable directory\>\themes\ * \<ES-DE executable directory\>\themes\
The theme is not mandatory to start the application, but ES-DE will be basically useless without it. A theme is not mandatory to start the application, but ES-DE will be basically useless without it.
So the home directory will always take precedence, and any resources or themes located there will override the ones in the path of the ES-DE executable. As indicated above, the home directory will always take precedence and any resources or themes located there will override the ones in the path of the ES-DE executable.
## Using clang-format for automatic code formatting
The entire ES-DE codebase is formatted using clang-format and all new code must be formatted using this tool before being committed.
There is a style configuration file named .clang-format located directly at the root of the repository which contains the formatting rules. How to install clang-format is detailed per operating system earlier in this document.
There are two ways to run the tool, from the command line or from inside an editor such as VSCode.
To format a file from the command line, simply run:
```clang-format -i <source file>```
The -i flag will make an inplace edit of the file.
But the recommended approach is to run clang-format from within the editor. If using VSCode, there is an extension available named Clang-Format. After installing this, simply open a source file, right click and choose `Format Document` or use the applicable keyboard shortcut. The first time you do this, you will have to make a choice to perform the formatting using clang-format. The rest should be completely automatic.
In some instances you may want to avoid getting code formatted, and you can accomplish this by simply enclosing the lines with the two comments "clang-format off" and "clang-format on", such as this:
```c++
// clang-format off
CollectionSystemDecl systemDecls[] = {
// Type Name Long name Theme folder isCustom
{ AUTO_ALL_GAMES, "all", "all games", "auto-allgames", false },
{ AUTO_LAST_PLAYED, "recent", "last played", "auto-lastplayed", false },
{ AUTO_FAVORITES, "favorites", "favorites", "auto-favorites", false },
{ CUSTOM_COLLECTION, myCollectionsName, "collections", "custom-collections", true }
};
// clang-format on
```
Adding a comment on its own line will also prevent some formatting such as turning short functions and lambda expressions into single lines. For this function such a comment has been added:
```c++
const std::string FileData::get3DBoxPath() const
{
// Return path to the 3D box image.
return getMediafilePath("3dboxes", "3dbox");
}
```
If the comment was omitted, the function would get formatted like this:
```c++
const std::string FileData::get3DBoxPath() const { return getMediafilePath("3dboxes", "3dbox"); }
```
Adding comments (even empty ones) can also force line breaks to avoid ugly formatting such as this:
```c++
for (auto it = mCursorStackHistory.begin(); it != mCursorStackHistory.end();
it++) {
if (std::find(listEntries.begin(), listEntries.end(), *it) !=
listEntries.end()) {
newCursor = *it;
mCursorStackHistory.erase(it);
break;
}
}
```
A comment at the right place produces this much nicer formatting:
```c++
for (auto it = mCursorStackHistory.begin(); // Line break.
it != mCursorStackHistory.end(); it++) {
if (std::find(listEntries.begin(), listEntries.end(), *it) !=
listEntries.end()) {
newCursor = *it;
mCursorStackHistory.erase(it);
break;
}
}
```
Of course you would like to get the code formatted according to the clang-format rules in most instances, these workaround are only meant for rare exceptions. Some compromises are necessary when auto-formatting code, at least with clang-format in its current state.
## CA certificates and MAME ROM information ## CA certificates and MAME ROM information
**CA certificates:** **CA certificates:**
@ -1158,9 +1246,9 @@ emulationstation-de/resources/certificates/curl-ca-bundle.crt
**MAME ROM info:** **MAME ROM info:**
This is a bit tricky as the data needs to be converted to an internal format used by ES-DE. The original file is huge and most of the information is not required. ES-DE automatically identifies and excludes MAME BIOS and device files, as well as translating the short MAME ROM names to their full game names. This is done using information from the MAME driver file shipped with the official MAME distribution. The file needs to be converted to an internal format used by ES-DE as the original file is huge and most of the information is not required.
Go to [https://www.mamedev.org/release.php](https://www.mamedev.org/release.php) and select the Windows version, but only download the driver information in XML format and not MAME itself. This file will be named something like `mame0226lx.zip` and unzipping it will give you a file name such as `mame0226.xml`. To get hold of the driver file, go to [https://www.mamedev.org/release.php](https://www.mamedev.org/release.php) and select the Windows version, but only download the driver information in XML format and not MAME itself. This file will be named something like `mame0226lx.zip` and unzipping it will give you a file name such as `mame0226.xml`.
Move the XML driver file to the resources/MAME directory and then convert it to the ES-DE internal files: Move the XML driver file to the resources/MAME directory and then convert it to the ES-DE internal files:
@ -1197,14 +1285,14 @@ git diff mamebioses
git diff mamedevices git diff mamedevices
``` ```
The reason to not simply replace the BIOS and devices files with the new version is that we want to retain entries from all older MAME versions as otherwise older ROM sets used on older MAME versions would have missing information. This is so as the MAME project sometimes removes older entries when they're reorganizing the ROM sets. By merging the files we retain backwards compatibility but still support the latest MAME version. To clarify, this of course does not affect the emulation itself, but rather the filtering of BIOS and device files inside ES-DE. The mamenames.xml file containing the translation of MAME ROM names to the full game names does not suffer from this problem as it's cumulative, which is why it is simply overwritten. The reason to not simply replace the BIOS and devices files with the new version is that we want to retain entries from all older MAME versions as otherwise older ROM sets used on older MAME versions would have missing information. This is so as the MAME project sometimes removes older entries when they're reorganizing the ROM sets. By merging the files we retain backward compatibility but still support the latest MAME version. To clarify, this of course does not affect the emulation itself, but rather the filtering of BIOS and device files inside ES-DE. The mamenames.xml file containing the translation of MAME ROM names to the full game names does not suffer from this problem as it's cumulative, which is why it is simply overwritten.
## Configuration ## Configuration
**~/.emulationstation/es_settings.xml:** **~/.emulationstation/es_settings.xml:**
When ES-DE is first run, a configuration file will be created as `~/.emulationstation/es_settings.xml`. When ES-DE is first started, a configuration file will be created as `~/.emulationstation/es_settings.xml`
This file will contain all supported settings at their default values. Normally you shouldn't need to modify this file manually, instead you should be able to use the menu inside ES-DE to update all the necessary settings. This file will contain all supported settings at their default values. Normally you shouldn't need to modify this file manually, instead you should be able to use the menu inside ES-DE to update all the necessary settings.
@ -1238,9 +1326,11 @@ There is also support to add the variable %ESPATH% to the ROM directory setting,
**~/.emulationstation/es_input.xml:** **~/.emulationstation/es_input.xml:**
You normally don't need to modify this file manually as it's created by the built-in input configuration step. This procedure is detailed in the [User guide](USERGUIDE.md#input-device-configuration). As ES-DE auto-configures the keyboard and controllers, neither the input configuration step or manual adjustments to the es_input.xml file should normally be needed. Actually, unless the button layout has been customized using the input configurator, the es_input.xml file will not even exist.
If your controller and keyboard stop working, you can delete the `~/.emulationstation/es_input.xml` file to make the input configuration screen re-appear on the next startup, or you can start ES-DE with the `--force-input-config` command line option. But if you have customized your button layout and your controller or keyboard stop working, you can delete the `~/.emulationstation/es_input.xml` file to remove the customizations, or you can start ES-DE with the `--force-input-config` command line option to make the input configurator appear.
The input configuration is described in the [User guide](USERGUIDE-DEV.md#input-device-configuration).
## Command line options ## Command line options
@ -1315,15 +1405,17 @@ ES-DE ships with a comprehensive `es_systems.xml` configuration file and normall
To make a customized version of the systems configuration file, it first needs to be copied to `~/.emulationstation/custom_systems/es_systems.xml`. (The tilde symbol `~` translates to `$HOME` on Unix and macOS, and to `%HOMEPATH%` on Windows unless overridden using the --home command line option.) To make a customized version of the systems configuration file, it first needs to be copied to `~/.emulationstation/custom_systems/es_systems.xml`. (The tilde symbol `~` translates to `$HOME` on Unix and macOS, and to `%HOMEPATH%` on Windows unless overridden using the --home command line option.)
The bundled es_systems.xml file is located in the resources directory that is part of the application installation. For example this could be `/usr/share/emulationstation/resources/systems/unix/es_systems.xml` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/resources/systems/macos/es_systems.xml` on macOS and `C:\Program Files\EmulationStation-DE\resources\systems\windows\es_systems.xml` on Windows. The actual location may differ from these examples of course, depending on where ES-DE has been installed. The bundled es_systems.xml file is located in the resources directory that is part of the application installation. For example this could be `/usr/share/emulationstation/resources/systems/unix/es_systems.xml` on Unix, `/Applications/EmulationStation Desktop Edition.app/Contents/Resources/resources/systems/macos/es_systems.xml` on macOS or `C:\Program Files\EmulationStation-DE\resources\systems\windows\es_systems.xml` on Windows. The actual location may differ from these examples of course, depending on where ES-DE has been installed.
Note that when copying the bundled es_systems.xml file to ~/.emulationstation/custom_systems/, it will completely replace the default file. So when upgrading to future ES-DE versions, any modifications such as additional game systems will not be enabled until the customized configuration file has been manually updated. Note that when copying the bundled es_systems.xml file to ~/.emulationstation/custom_systems/, it will completely replace the default file processing. So when upgrading to future ES-DE versions, any modifications such as additional game systems will not be enabled until the customized configuration file has been manually updated.
It doesn't matter in which order you define the systems as they will be sorted by the full system name inside the application, but it's still probably a good idea to add them in alphabetical order to make the file easier to maintain. It doesn't matter in which order you define the systems as they will be sorted by the full system name inside the application, but it's still probably a good idea to add them in alphabetical order to make the file easier to maintain.
Keep in mind that you have to set up your emulators separately from ES-DE as the es_systems.xml file assumes that your emulator environment is properly configured. Keep in mind that you have to set up your emulators separately from ES-DE as the es_systems.xml file assumes that your emulator environment is properly configured.
Below is an overview of the file layout with various examples. For a real system entry there can of course not be multiple entries for the same tag such as the multiple \<command\> entries listed here. Below is an overview of the file layout with various examples. For the command tag, the newer es_find_rules.xml logic described later in this document removes the need for most of the legacy options, but they are still supported for special configurations and for backward compatibility with old configuration files.
For a real system entry there can of course not be multiple entries for the same tag such as the multiple \<command\> entries listed here.
```xml ```xml
<?xml version="1.0"?> <?xml version="1.0"?>
@ -1336,7 +1428,7 @@ Below is an overview of the file layout with various examples. For a real system
<name>snes</name> <name>snes</name>
<!-- The full system name, used for sorting the systems, for selecting the systems to multi-scrape etc. --> <!-- The full system name, used for sorting the systems, for selecting the systems to multi-scrape etc. -->
<fullname>Super Nintendo Entertainment System</fullname> <fullname>Nintendo SNES (Super Nintendo)</fullname>
<!-- The path to look for ROMs in. '~' will be expanded to $HOME or %HOMEPATH%, depending on the operating system. <!-- The path to look for ROMs in. '~' will be expanded to $HOME or %HOMEPATH%, depending on the operating system.
The optional %ROMPATH% variable will expand to the path defined in the setting ROMDirectory in es_settings.xml. The optional %ROMPATH% variable will expand to the path defined in the setting ROMDirectory in es_settings.xml.
@ -1361,7 +1453,7 @@ Below is an overview of the file layout with various examples. For a real system
the find rules for the emulator cores. --> the find rules for the emulator cores. -->
<command>retroarch -L %CORE_RETROARCH%/snes9x_libretro.so %ROM%</command> <command>retroarch -L %CORE_RETROARCH%/snes9x_libretro.so %ROM%</command>
<!-- This is an example for macOS, which is very similar to the Unix example above except using an absolut path to the emulator. --> <!-- This is an example for macOS, which is very similar to the Unix example above except using an absolute path to the emulator. -->
<command>/Applications/RetroArch.app/Contents/MacOS/RetroArch -L %CORE_RETROARCH%/snes9x_libretro.dylib %ROM%</command> <command>/Applications/RetroArch.app/Contents/MacOS/RetroArch -L %CORE_RETROARCH%/snes9x_libretro.dylib %ROM%</command>
<!-- This is an example for Windows. The .exe extension is optional and both forward slashes and backslashes are allowed as <!-- This is an example for Windows. The .exe extension is optional and both forward slashes and backslashes are allowed as
@ -1381,14 +1473,14 @@ Below is an overview of the file layout with various examples. For a real system
<!-- The platform(s) to use when scraping. You can see the full list of supported platforms in es-app/src/PlatformId.cpp. <!-- The platform(s) to use when scraping. You can see the full list of supported platforms in es-app/src/PlatformId.cpp.
The entry is case insensitive as it will be converted to lower case during startup. The entry is case insensitive as it will be converted to lower case during startup.
This tag is optional but the system can't be scraped if it's left out. This tag is optional but scraper searches for the system will be inaccurate if it's left out.
You can use multiple platforms too, delimited with any of the whitespace characters (", \r\n\t"), e.g. "megadrive, genesis". --> You can use multiple platforms too, delimited with any of the whitespace characters (", \r\n\t"), e.g. "megadrive, genesis". -->
<platform>snes</platform> <platform>snes</platform>
<!-- The theme to load from the current theme set. See THEMES.md for more information. <!-- The theme to load from the current theme set. See THEMES.md for more information.
This tag is optional and if it doesn't exist, ES-DE will attempt to find a theme with the same name as the system name. This tag is optional and if it doesn't exist, ES-DE will attempt to find a theme with the same name as the system name.
If no such match is made, the system will be unthemed. If no such match is made, the system will be unthemed.
It's strongly recommended to include this tag even if it's just to clarify that the theme should correspond to the system name. --> It's recommended to include this tag even if it's just to clarify that the theme should correspond to the system name. -->
<theme>snes</theme> <theme>snes</theme>
</system> </system>
</systemList> </systemList>
@ -1400,7 +1492,7 @@ The following variable is expanded for the `path` tag:
The following variables are expanded for the `command` tag: The following variables are expanded for the `command` tag:
`%ROM%` - Replaced with the absolute path to the selected ROM, with most Bash special characters escaped with a backslash. `%ROM%` - Replaced with the absolute path to the selected ROM, with most special characters escaped with a backslash.
`%ROMRAW%` - Replaced with the unescaped, absolute path to the selected ROM. If your emulator is picky about paths, you might want to use this instead of %ROM%, but enclosed in quotes. `%ROMRAW%` - Replaced with the unescaped, absolute path to the selected ROM. If your emulator is picky about paths, you might want to use this instead of %ROM%, but enclosed in quotes.
@ -1529,12 +1621,12 @@ The `name` attribute must correspond to the command tags in es_systems.xml, take
Here %EMULATOR_ and %CORE_ are followed by the string RETROARCH which corresponds to the name attribute in es_find_rules.xml. The name is case sensitive but it's recommended to use uppercase names to make the variables feel consistent (%EMULATOR_retroarch% doesn't look so pretty). Here %EMULATOR_ and %CORE_ are followed by the string RETROARCH which corresponds to the name attribute in es_find_rules.xml. The name is case sensitive but it's recommended to use uppercase names to make the variables feel consistent (%EMULATOR_retroarch% doesn't look so pretty).
Of course this makes it possible to add any number of emulators to the configuration file if not only using RetroArch. Of course this makes it possible to add any number of emulators to the configuration file.
The `winregistrypath` rule searches the Windows Registry "App Paths" keys for the emulators defined in the `<entry>` tags. If for example this tag is set to `retroarch.exe`, the key `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\retroarch.exe` will be searched for. HKEY_CURRENT_USER is tried first, and if no key is found there, HKEY_LOCAL_MACHINE is tried as well. In addition to this, ES-DE will check that the binary defined in the key actually exists, and if not, processing will proceed with the next rule. Be aware that the App Paths keys are added by the emulators during their installation, and although RetroArch does add this key, not all emulators do. The `winregistrypath` rule searches the Windows Registry "App Paths" keys for the emulators defined in the `<entry>` tags. If for example this tag is set to `retroarch.exe`, the key `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\retroarch.exe` will be searched for. HKEY_CURRENT_USER is tried first, and if no key is found there, HKEY_LOCAL_MACHINE is tried as well. In addition to this, ES-DE will check that the binary defined in the key actually exists, and if not, it will proceed with the next rule. Be aware that the App Paths keys are added by the emulators during their installation, and although RetroArch does add this key, not all emulators do.
The other rules are probably self-explanatory with `systempath` searching the PATH environment variable for the binary names defined by the `<entry>` tags and `staticpath` defines absolute paths to the emulators. For staticpath, the actual emulator binary must also be included in the entry tag. The other rules are probably self-explanatory with `systempath` searching the PATH environment variable for the binary names defined by the `<entry>` tags and `staticpath` defines absolute paths to the emulators. For staticpath, the actual emulator binary must be included in the entry tag.
The winregistrypath rules are always processed first, followed by systempath and then staticpath. This is done regardless of which order they are defined in the es_find_rules.xml file. The winregistrypath rules are always processed first, followed by systempath and then staticpath. This is done regardless of which order they are defined in the es_find_rules.xml file.
@ -1542,7 +1634,7 @@ As for `corepath` this rule is simply a path to search for the emulator core.
Each rule supports multiple entry tags which are tried in the order that they are defined in the file. Each rule supports multiple entry tags which are tried in the order that they are defined in the file.
The %ESPATH% variable can be used inside the staticpath rules and both the %EMUPATH% and %ESPATH% variables can be used inside the corepath rules. The %ESPATH% and %ROMPATH% variables can be used inside the staticpath rules and the %ESPATH% and %EMUPATH% variables can be used inside the corepath rules.
The tilde symbol `~` is supported for the staticpath and corepath rules and will expand to the user home directory. Be aware that if ES-DE has been started with the --home command line option, the home directory is considered to be whatever path was passed as an argument to that option. The same is true if using a portable.txt file. The tilde symbol `~` is supported for the staticpath and corepath rules and will expand to the user home directory. Be aware that if ES-DE has been started with the --home command line option, the home directory is considered to be whatever path was passed as an argument to that option. The same is true if using a portable.txt file.
@ -1638,7 +1730,7 @@ There is also support to add the variable %ESPATH% to the media directory settin
The default media directory is `~/.emulationstation/downloaded_media` The default media directory is `~/.emulationstation/downloaded_media`
You can use ES-DE's scraping tools to populate the gamelist.xml files, or manually update individual entries using the metadata editor. All of this is explained in the [User guide](USERGUIDE.md). You can use ES-DE's scrapers to populate the gamelist.xml files, or manually update individual entries using the metadata editor. All of this is explained in the [User guide](USERGUIDE-DEV.md).
The gamelist.xml files are searched for in the ES-DE home directory, i.e. `~/.emulationstation/gamelists/<system name>/gamelist.xml` The gamelist.xml files are searched for in the ES-DE home directory, i.e. `~/.emulationstation/gamelists/<system name>/gamelist.xml`
@ -1680,10 +1772,10 @@ There are a few different data types for the metadata which the string values in
* `string` - just text * `string` - just text
* `float` - a floating-point decimal value (written as a string) * `float` - a floating-point decimal value (written as a string)
* `integer` - an integer value (written as a string) * `integer` - an integer value (written as a string)
* `datetime` - a date and, potentially, a time. These are encoded as an ISO string, in the following format: "%Y%m%dT%H%M%S%F%q". For example, the release date for Chrono Trigger is encoded as "19950311T000000" (no time specified) * `datetime` - a date and optionally a time encoded as an ISO string in the format "%Y%m%dT%H%M%S", for example "19950311T000000"
* `bool` - a true or false value * `bool` - a true or false value
Some metadata is also marked as "statistic" - these are kept track of by ES-DE and do not show up in the metadata editor. They are shown in certain views (for example, the detailed view and the video view both show `lastplayed`, although the label can be disabled by the theme). Some metadata is also marked as "statistic", these are kept track of by ES-DE and do not show up in the metadata editor. They are shown in certain views (for example, the detailed view and the video view both show `lastplayed`, although the label can be disabled by the theme).
There are two basic categories of metadata, `game` and `folders` and the metdata tags for these are described in detail below. There are two basic categories of metadata, `game` and `folders` and the metdata tags for these are described in detail below.
@ -1766,34 +1858,36 @@ This will draw a semi-transparent blue frame around all text elements.
This will reload either a single gamelist or all gamelists depending on where you're located when entering the key combination (go to the system view to make a complete reload). Very useful for theme development as any changes to the theme files will be activated without requiring an application restart. Note that the menu needs to be closed for this key combination to have any effect. This will reload either a single gamelist or all gamelists depending on where you're located when entering the key combination (go to the system view to make a complete reload). Very useful for theme development as any changes to the theme files will be activated without requiring an application restart. Note that the menu needs to be closed for this key combination to have any effect.
By default all controller input (keyboard and controller button presses) will be logged when the --debug flag has been passed. To disable the input logging, the setting DebugSkipInputLogging kan be set to false in the es_settings.xml file. There is no menu entry to change this as it's intended for developers and not for end users.
## Portable installation on Windows ## Portable installation on Windows
It's possible to easily create a portable installation of ES-DE for Windows, for example to place on a USB memory stick. It's possible to easily create a portable installation of ES-DE on Windows, for example to place on a USB memory stick.
For the sake of this example, let's assume that the removable media has the device name `f:\` For the sake of this example, let's assume that the removable media has the device name `F:\`
These are the steps to perform: These are the steps to perform:
* Copy the EmulationStation-DE installation directory to f:\ * Copy the EmulationStation-DE installation directory to F:\
* Copy your emulator directories to f:\EmulationStation-DE\ * Copy your emulator directories to F:\EmulationStation-DE\
* Copy your ROMs directory to f:\EmulationStation-DE\ * Copy your ROMs directory to F:\EmulationStation-DE\
* Create an empty file named portable.txt in f:\EmulationStation-DE\ * Create an empty file named portable.txt in F:\EmulationStation-DE\
You should end up with something like this: You should end up with something like this:
``` ```
f:\EmulationStation-DE\ F:\EmulationStation-DE\
f:\EmulationStation-DE\RetroArch-Win64\ F:\EmulationStation-DE\RetroArch-Win64\
f:\EmulationStation-DE\yuzu\ F:\EmulationStation-DE\yuzu\
f:\EmulationStation-DE\ROMs\ F:\EmulationStation-DE\ROMs\
f:\EmulationStation-DE\portable.txt F:\EmulationStation-DE\portable.txt
``` ```
(Yuzu is an optional Nintendo Switch emulator.) (Yuzu is an optional Nintendo Switch emulator.)
Of course there will be many more files and directories from the normal installation than those listed above. Of course there will be many more files and directories from the normal installation than those listed above.
How this works is that when ES-DE finds a file named portable.txt in its executable directory, it will locate the .emulationstation directory directly in this folder. It's however also possible to modify portable.txt with a path relative to the ES-DE executable directory. For instance if two dots `..` are placed inside the portable.txt file, then the .emulationstation directory will be located in the parent folder, which would be directly under f:\ in this example. How this works is that when ES-DE finds a file named portable.txt in its executable directory, it will by default locate the .emulationstation directory directly in this folder. It's also possible to modify portable.txt with a path relative to the ES-DE executable directory. For instance if two dots `..` are placed inside the portable.txt file, then the .emulationstation directory will be located in the parent folder, which would be directly under F:\ in this example.
If the --home command line parameter is passed when starting ES-DE, that will override the portable.txt file. If the --home command line parameter is passed when starting ES-DE, that will override the portable.txt file.
@ -1808,15 +1902,15 @@ yuzu\yuzu-windows-msvc\yuzu.exe
..\yuzu\yuzu-windows-msvc\yuzu.exe ..\yuzu\yuzu-windows-msvc\yuzu.exe
``` ```
If you want to place your emulators elsewhere, you need to create a customized es_find_rules.xml file, which is explained later in this document. If you want to place your emulators elsewhere, you need to create a customized es_find_rules.xml file, which is explained earlier in this document.
Start ES-DE from the f:\ device and check that everything works as expected. Start ES-DE from the F:\ device and check that everything works as expected.
Following this, optionally copy any existing gamelist.xml files, game media files etc. to the removable media. For example: Following this, optionally copy any existing gamelist.xml files, game media files etc. to the removable media. For example:
``` ```
f:\EmulationStation-DE\.emulationstation\gamelists\ F:\EmulationStation-DE\.emulationstation\gamelists\
f:\EmulationStation-DE\.emulationstation\downloaded_media\ F:\EmulationStation-DE\.emulationstation\downloaded_media\
``` ```
You now have a fully functional portable retro gaming installation! You now have a fully functional portable retro gaming installation!
@ -1831,10 +1925,10 @@ There are numerous locations throughout ES-DE where custom scripts will be execu
The approach is quite straightforward, ES-DE will look for any files inside a script directory that corresponds to the event that is triggered and will then execute all these files. The approach is quite straightforward, ES-DE will look for any files inside a script directory that corresponds to the event that is triggered and will then execute all these files.
We'll go through two examples: We'll go through two examples:
* Create a log file that will record the start and end time for each game we play, letting us see how much time we spend on retro-gaming * Creating a log file that will record the start and end time for each game we play, letting us see how much time we spend on retro-gaming
* Change the system resolution when launching and returning from a game in order to run the emulator at a lower resolution than ES-DE * Changing the system resolution when launching and returning from a game in order to run the emulator at a lower resolution than ES-DE
**Note:** The following examples are for Unix systems, but it works the same way in macOS (which is also Unix after all), and on Windows (although .bat batch files are then used instead of shell scripts and any spaces in the parameters are not escaped as is the case on Unix). **Note:** The following examples are for Unix systems, but it works the same way on macOS (which is also Unix after all), and on Windows (although .bat batch files are then used instead of shell scripts and any spaces in the parameters are not escaped as is the case on Unix).
The events executed when a game starts and ends are called `game-start` and `game-end` respectively. Finding these event names is easily achieved by starting ES-DE with the `--debug` flag. If this is done, all attempts to execute custom event scripts will be logged to es_log.txt, including the event names. The events executed when a game starts and ends are called `game-start` and `game-end` respectively. Finding these event names is easily achieved by starting ES-DE with the `--debug` flag. If this is done, all attempts to execute custom event scripts will be logged to es_log.txt, including the event names.
@ -1867,7 +1961,7 @@ After creating the two scripts, you should have something like this on the files
~/.emulationstation/scripts/game-end/game_end_logging.sh ~/.emulationstation/scripts/game-end/game_end_logging.sh
``` ```
Don't forget to make the scripts executable (e.g. 'chmod 755 ./game_start_logging.sh'). Don't forget to make the scripts executable (e.g. "chmod 755 ./game_start_logging.sh").
If we now start ES-DE with the debug flag and launch a game, something like the following should show up in es_log.txt: If we now start ES-DE with the debug flag and launch a game, something like the following should show up in es_log.txt:

View file

@ -31,7 +31,7 @@ The following operating systems have been tested with ES-DE (all for the x86 arc
**Note:** If using a Mac with an ARM CPU (e.g. M1) you need to install the x86 version of RetroArch and any other emulators, or you won't be able to launch any games. This will be fixed whenever a native macOS ARM build of ES-DE is released. **Note:** If using a Mac with an ARM CPU (e.g. M1) you need to install the x86 version of RetroArch and any other emulators, or you won't be able to launch any games. This will be fixed whenever a native macOS ARM build of ES-DE is released.
At the moment Raspberry Pi is not supported, but this is planned for version 1.2. It may still be possible to compile and run ES-DE on this type of device, but as of v1.1 it's not actively used during development and therefore not tested. The Raspberry Pi 4 has been tested as well (with Raspberry Pi OS) but it's a bit buggy at the moment and is missing some features like GLSL shaders. Official support will probably be added for this device as of ES-DE v1.2.
### Download ### Download
@ -45,14 +45,14 @@ The latest stable version is 1.0.1 (released 2021-05-01)
| macOS DMG installer | Legacy macOS 10.11 "El Capitan" to 10.13 "High Sierra" | x64 (x86) | [EmulationStation-DE-1.0.1-x64_legacy.dmg](https://es-de.org/releases/stable/macOS/EmulationStation-DE-1.0.1-x64_legacy.dmg)| | macOS DMG installer | Legacy macOS 10.11 "El Capitan" to 10.13 "High Sierra" | x64 (x86) | [EmulationStation-DE-1.0.1-x64_legacy.dmg](https://es-de.org/releases/stable/macOS/EmulationStation-DE-1.0.1-x64_legacy.dmg)|
| Windows installer | Windows 10 and 8.1 | x64 (x86) | [EmulationStation-DE-1.0.1-x64.exe](https://es-de.org/releases/stable/Windows/EmulationStation-DE-1.0.1-x64.exe)| | Windows installer | Windows 10 and 8.1 | x64 (x86) | [EmulationStation-DE-1.0.1-x64.exe](https://es-de.org/releases/stable/Windows/EmulationStation-DE-1.0.1-x64.exe)|
The latest pre-release version is 1.1.0-beta1 (released 2021-06-27) The latest pre-release version is 1.1.0-beta2 (released 2021-07-04)
| Package | Operating systems | Architecture | Download link | | Package | Operating systems | Architecture | Download link |
| :------------------ | :------------------------------------------------------ | :----------- | :------------- | | :------------------ | :------------------------------------------------------ | :----------- | :------------- |
| Debian DEB package | Ubuntu 20.04 to 21.04, Linux Mint 20, possibly others | x64 (x86) | [emulationstation-de-1.1.0-beta1-x64.deb](https://es-de.org/releases/beta/Linux/emulationstation-de-1.1.0-beta1-x64.deb)| | Debian DEB package | Ubuntu 20.04 to 21.04, Linux Mint 20, possibly others | x64 (x86) | [emulationstation-de-1.1.0-beta2-x64.deb](https://es-de.org/releases/beta/Linux/emulationstation-de-1.1.0-beta2-x64.deb)|
| Fedora RPM package | Fedora Workstation 33, possibly others | x64 (x86) | [emulationstation-de-1.1.0-beta1-x64.rpm](https://es-de.org/releases/beta/Linux/emulationstation-de-1.1.0-beta1-x64.rpm)| | Fedora RPM package | Fedora Workstation 33, possibly others | x64 (x86) | [emulationstation-de-1.1.0-beta2-x64.rpm](https://es-de.org/releases/beta/Linux/emulationstation-de-1.1.0-beta2-x64.rpm)|
| macOS DMG installer | macOS 10.14 "Mojave" to 11 "Big Sur" | x64 (x86) | [EmulationStation-DE-1.1.0-beta1-x64.dmg](https://es-de.org/releases/beta/macOS/EmulationStation-DE-1.1.0-beta1-x64.dmg)| | macOS DMG installer | macOS 10.14 "Mojave" to 11 "Big Sur" | x64 (x86) | [EmulationStation-DE-1.1.0-beta2-x64.dmg](https://es-de.org/releases/beta/macOS/EmulationStation-DE-1.1.0-beta2-x64.dmg)|
| Windows installer | Windows 10 and 8.1 | x64 (x86) | [EmulationStation-DE-1.1.0-beta1-x64.exe](https://es-de.org/releases/beta/Windows/EmulationStation-DE-1.1.0-beta1-x64.exe)| | Windows installer | Windows 10 and 8.1 | x64 (x86) | [EmulationStation-DE-1.1.0-beta2-x64.exe](https://es-de.org/releases/beta/Windows/EmulationStation-DE-1.1.0-beta2-x64.exe)|
Unfortunately due to technical reasons, v1.0.1 will be the last release for legacy macOS versions. Unfortunately due to technical reasons, v1.0.1 will be the last release for legacy macOS versions.

File diff suppressed because it is too large Load diff

View file

@ -127,8 +127,8 @@ endif()
if(WIN32) if(WIN32)
install(TARGETS EmulationStation RUNTIME DESTINATION .) install(TARGETS EmulationStation RUNTIME DESTINATION .)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
install(FILES ../avcodec-59.dll ../avfilter-8.dll ../avformat-59.dll ../avutil-57.dll install(FILES ../avcodec-58.dll ../avfilter-7.dll ../avformat-58.dll ../avutil-56.dll
../postproc-56.dll ../swresample-4.dll ../swscale-6.dll ../FreeImage.dll ../postproc-55.dll ../swresample-3.dll ../swscale-5.dll ../FreeImage.dll
../glew32.dll ../libcrypto-1_1-x64.dll ../libcurl-x64.dll ../freetype.dll ../glew32.dll ../libcrypto-1_1-x64.dll ../libcurl-x64.dll ../freetype.dll
../pugixml.dll ../libssl-1_1-x64.dll ../SDL2.dll ../MSVCP140.dll ../VCOMP140.DLL ../pugixml.dll ../libssl-1_1-x64.dll ../SDL2.dll ../MSVCP140.dll ../VCOMP140.DLL
../VCRUNTIME140.dll ../VCRUNTIME140_1.dll DESTINATION .) ../VCRUNTIME140.dll ../VCRUNTIME140_1.dll DESTINATION .)
@ -136,8 +136,8 @@ if(WIN32)
install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .)
endif() endif()
else() else()
install(FILES ../avcodec-59.dll ../avfilter-8.dll ../avformat-59.dll ../avutil-57.dll install(FILES ../avcodec-58.dll ../avfilter-7.dll ../avformat-58.dll ../avutil-56.dll
../postproc-56.dll ../swresample-4.dll ../swscale-6.dll ../FreeImage.dll ../postproc-55.dll ../swresample-3.dll ../swscale-5.dll ../FreeImage.dll
../glew32.dll ../libcrypto-1_1-x64.dll ../libcurl-x64.dll ../libfreetype.dll ../glew32.dll ../libcrypto-1_1-x64.dll ../libcurl-x64.dll ../libfreetype.dll
../libpugixml.dll ../libssl-1_1-x64.dll ../SDL2.dll ../vcomp140.dll DESTINATION .) ../libpugixml.dll ../libssl-1_1-x64.dll ../SDL2.dll ../vcomp140.dll DESTINATION .)
if(VLC_PLAYER) if(VLC_PLAYER)
@ -275,7 +275,7 @@ endif()
set(CPACK_PACKAGE_VENDOR "Leon Styhre") set(CPACK_PACKAGE_VENDOR "Leon Styhre")
# Update this when there has been a new release. # Update this when there has been a new release.
set(CPACK_PACKAGE_VERSION "1.1.0-beta2-dev") set(CPACK_PACKAGE_VERSION "1.1.0-rc")
# Use the shorter x64 descriptor if on the x86_64/AMD64 architecture. # Use the shorter x64 descriptor if on the x86_64/AMD64 architecture.
if(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL x86_64 OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL AMD64) if(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL x86_64 OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL AMD64)
@ -332,7 +332,7 @@ else()
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://es-de.org") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://es-de.org")
set(CPACK_DEBIAN_PACKAGE_SECTION "games") set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
if(VLC_PLAYER OR RPI) if(VLC_PLAYER)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "vlc") set(CPACK_DEBIAN_PACKAGE_DEPENDS "vlc")
endif() endif()
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)

View file

@ -19,7 +19,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.1.0-beta2-dev</string> <string>1.1.0-rc</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>10.14.0</string> <string>10.14.0</string>
<key>LSUIPresentationMode</key> <key>LSUIPresentationMode</key>

Binary file not shown.

View file

@ -1,5 +1,5 @@
[Desktop Entry] [Desktop Entry]
Version=1.1.0-beta2-dev Version=1.1.0-rc
Name=EmulationStation Desktop Edition Name=EmulationStation Desktop Edition
GenericName=Emulator Front-end GenericName=Emulator Front-end
Type=Application Type=Application

View file

@ -21,19 +21,19 @@
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "guis/GuiInfoPopup.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "views/gamelist/IGameListView.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "FileData.h" #include "FileData.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "ThemeData.h" #include "ThemeData.h"
#include "guis/GuiInfoPopup.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "views/gamelist/IGameListView.h"
#include <fstream> #include <fstream>
#include <pugixml.hpp> #include <pugixml.hpp>
@ -47,8 +47,10 @@ std::string myCollectionsName = "collections";
// saving and deletion of a CollectionSystemsManager instance. // saving and deletion of a CollectionSystemsManager instance.
CollectionSystemsManager* CollectionSystemsManager::sInstance = nullptr; CollectionSystemsManager* CollectionSystemsManager::sInstance = nullptr;
CollectionSystemsManager::CollectionSystemsManager(Window* window) : mWindow(window) CollectionSystemsManager::CollectionSystemsManager(Window* window)
: mWindow(window)
{ {
// clang-format off
CollectionSystemDecl systemDecls[] = { CollectionSystemDecl systemDecls[] = {
// Type Name Long name Theme folder isCustom // Type Name Long name Theme folder isCustom
{ AUTO_ALL_GAMES, "all", "all games", "auto-allgames", false }, { AUTO_ALL_GAMES, "all", "all games", "auto-allgames", false },
@ -56,10 +58,11 @@ CollectionSystemsManager::CollectionSystemsManager(Window* window) : mWindow(win
{ AUTO_FAVORITES, "favorites", "favorites", "auto-favorites", false }, { AUTO_FAVORITES, "favorites", "favorites", "auto-favorites", false },
{ CUSTOM_COLLECTION, myCollectionsName, "collections", "custom-collections", true } { CUSTOM_COLLECTION, myCollectionsName, "collections", "custom-collections", true }
}; };
// clang-format on
// Create a map of the collections. // Create a map of the collections.
std::vector<CollectionSystemDecl> tempSystemDecl = std::vector<CollectionSystemDecl> std::vector<CollectionSystemDecl> tempSystemDecl = std::vector<CollectionSystemDecl>(
(systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0])); systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0]));
for (std::vector<CollectionSystemDecl>::const_iterator it = tempSystemDecl.cbegin(); for (std::vector<CollectionSystemDecl>::const_iterator it = tempSystemDecl.cbegin();
it != tempSystemDecl.cend(); it++) it != tempSystemDecl.cend(); it++)
@ -95,8 +98,8 @@ CollectionSystemsManager::~CollectionSystemsManager()
removeCollectionsFromDisplayedSystems(); removeCollectionsFromDisplayedSystems();
// Delete all custom collections. // Delete all custom collections.
for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator it =
it = mCustomCollectionSystemsData.cbegin(); mCustomCollectionSystemsData.cbegin();
it != mCustomCollectionSystemsData.cend(); it++) it != mCustomCollectionSystemsData.cend(); it++)
delete it->second.system; delete it->second.system;
@ -105,10 +108,9 @@ CollectionSystemsManager::~CollectionSystemsManager()
delete mCustomCollectionsBundle; delete mCustomCollectionsBundle;
// Delete the auto collections systems. // Delete the auto collections systems.
for (auto it = mAutoCollectionSystemsData.cbegin(); for (auto it = mAutoCollectionSystemsData.cbegin(); // Line break.
it != mAutoCollectionSystemsData.cend(); it++) { it != mAutoCollectionSystemsData.cend(); it++)
delete (*it).second.system; delete (*it).second.system;
}
delete mCollectionEnvData; delete mCollectionEnvData;
sInstance = nullptr; sInstance = nullptr;
@ -136,8 +138,8 @@ void CollectionSystemsManager::saveCustomCollection(SystemData* sys)
{ {
const std::string rompath = FileData::getROMDirectory(); const std::string rompath = FileData::getROMDirectory();
std::string name = sys->getName(); std::string name = sys->getName();
std::unordered_map<std::string, FileData*> std::unordered_map<std::string, FileData*> games =
games = sys->getRootFolder()->getChildrenByFilename(); sys->getRootFolder()->getChildrenByFilename();
bool found = mCustomCollectionSystemsData.find(name) != mCustomCollectionSystemsData.cend(); bool found = mCustomCollectionSystemsData.find(name) != mCustomCollectionSystemsData.cend();
if (found) { if (found) {
@ -156,8 +158,8 @@ void CollectionSystemsManager::saveCustomCollection(SystemData* sys)
std::ofstream configFileOut; std::ofstream configFileOut;
#if defined(_WIN64) #if defined(_WIN64)
configFileIn.open(Utils::String:: configFileIn.open(
stringToWideString(getCustomCollectionConfigPath(name)).c_str()); Utils::String::stringToWideString(getCustomCollectionConfigPath(name)).c_str());
#else #else
configFileIn.open(getCustomCollectionConfigPath(name)); configFileIn.open(getCustomCollectionConfigPath(name));
#endif #endif
@ -172,8 +174,8 @@ void CollectionSystemsManager::saveCustomCollection(SystemData* sys)
} }
configFileIn.close(); configFileIn.close();
for (std::unordered_map<std::string, FileData*>::const_iterator for (std::unordered_map<std::string, FileData*>::const_iterator it = games.cbegin();
it = games.cbegin(); it != games.cend(); it++) { it != games.cend(); it++) {
std::string path = it->first; std::string path = it->first;
// If the ROM path of the game begins with the path from the setting // If the ROM path of the game begins with the path from the setting
// ROMDirectory (or the default ROM directory), then replace it with %ROMPATH%. // ROMDirectory (or the default ROM directory), then replace it with %ROMPATH%.
@ -190,8 +192,8 @@ void CollectionSystemsManager::saveCustomCollection(SystemData* sys)
fileGameEntries.erase(last, fileGameEntries.end()); fileGameEntries.erase(last, fileGameEntries.end());
#if defined(_WIN64) #if defined(_WIN64)
configFileOut.open(Utils::String:: configFileOut.open(
stringToWideString(getCustomCollectionConfigPath(name)).c_str()); Utils::String::stringToWideString(getCustomCollectionConfigPath(name)).c_str());
#else #else
configFileOut.open(getCustomCollectionConfigPath(name)); configFileOut.open(getCustomCollectionConfigPath(name));
#endif #endif
@ -231,12 +233,11 @@ void CollectionSystemsManager::loadEnabledListFromSettings()
Settings::getInstance()->getString("CollectionSystemsAuto"), ",", true); Settings::getInstance()->getString("CollectionSystemsAuto"), ",", true);
// Iterate the map. // Iterate the map.
for (std::map<std::string, CollectionSystemData, stringComparator>::iterator for (std::map<std::string, CollectionSystemData, stringComparator>::iterator it =
it = mAutoCollectionSystemsData.begin(); mAutoCollectionSystemsData.begin();
it != mAutoCollectionSystemsData.end(); it++) { it != mAutoCollectionSystemsData.end(); it++) {
it->second.isEnabled = (std::find(autoSelected.cbegin(), autoSelected.cend(), it->first) !=
it->second.isEnabled = (std::find(autoSelected.cbegin(), autoSelected.cend());
autoSelected.cend(), it->first) != autoSelected.cend());
} }
mHasEnabledCustomCollection = false; mHasEnabledCustomCollection = false;
@ -246,12 +247,11 @@ void CollectionSystemsManager::loadEnabledListFromSettings()
Settings::getInstance()->getString("CollectionSystemsCustom"), ",", true); Settings::getInstance()->getString("CollectionSystemsCustom"), ",", true);
// Iterate the map. // Iterate the map.
for (std::map<std::string, CollectionSystemData, stringComparator>::iterator for (std::map<std::string, CollectionSystemData, stringComparator>::iterator it =
it = mCustomCollectionSystemsData.begin(); mCustomCollectionSystemsData.begin();
it != mCustomCollectionSystemsData.end(); it++) { it != mCustomCollectionSystemsData.end(); it++) {
it->second.isEnabled = (std::find(customSelected.cbegin(), customSelected.cend(),
it->second.isEnabled = (std::find(customSelected.cbegin(), it->first) != customSelected.cend());
customSelected.cend(), it->first) != customSelected.cend());
if (it->second.isEnabled) if (it->second.isEnabled)
mHasEnabledCustomCollection = true; mHasEnabledCustomCollection = true;
} }
@ -269,8 +269,8 @@ void CollectionSystemsManager::updateSystemsList()
FileData* rootFolder = mCustomCollectionsBundle->getRootFolder(); FileData* rootFolder = mCustomCollectionsBundle->getRootFolder();
// Sort the bundled custom collections. // Sort the bundled custom collections.
if (rootFolder->getChildren().size() > 0) { if (rootFolder->getChildren().size() > 0) {
rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder-> rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
getSortTypeString()), Settings::getInstance()->getBool("FavFirstCustom")); Settings::getInstance()->getBool("FavFirstCustom"));
SystemData::sSystemVector.push_back(mCustomCollectionsBundle); SystemData::sSystemVector.push_back(mCustomCollectionsBundle);
} }
} }
@ -279,7 +279,7 @@ void CollectionSystemsManager::updateSystemsList()
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData); addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData);
// Create views for collections, before reload. // Create views for collections, before reload.
for (auto sysIt = SystemData::sSystemVector.cbegin(); for (auto sysIt = SystemData::sSystemVector.cbegin(); // Line break.
sysIt != SystemData::sSystemVector.cend(); sysIt++) { sysIt != SystemData::sSystemVector.cend(); sysIt++) {
if ((*sysIt)->isCollection()) if ((*sysIt)->isCollection())
ViewController::get()->getGameListView((*sysIt)); ViewController::get()->getGameListView((*sysIt));
@ -311,12 +311,11 @@ void CollectionSystemsManager::refreshCollectionSystems(FileData* file,
} }
std::map<std::string, CollectionSystemData> allCollections; std::map<std::string, CollectionSystemData> allCollections;
allCollections.insert(mAutoCollectionSystemsData.cbegin(), allCollections.insert(mAutoCollectionSystemsData.cbegin(), mAutoCollectionSystemsData.cend());
mAutoCollectionSystemsData.cend());
allCollections.insert(mCustomCollectionSystemsData.cbegin(), allCollections.insert(mCustomCollectionSystemsData.cbegin(),
mCustomCollectionSystemsData.cend()); mCustomCollectionSystemsData.cend());
for (auto sysDataIt = allCollections.cbegin(); for (auto sysDataIt = allCollections.cbegin(); // Line break.
sysDataIt != allCollections.cend(); sysDataIt++) { sysDataIt != allCollections.cend(); sysDataIt++) {
if (sysDataIt->second.isEnabled || (refreshDisabledAutoCollections && if (sysDataIt->second.isEnabled || (refreshDisabledAutoCollections &&
!sysDataIt->second.system->isGroupedCustomCollection())) !sysDataIt->second.system->isGroupedCustomCollection()))
@ -363,32 +362,35 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection
// Found it, and we are removing it. // Found it, and we are removing it.
if (name == "favorites" && file->metadata.get("favorite") == "false") { if (name == "favorites" && file->metadata.get("favorite") == "false") {
// Need to check if it is still marked as favorite, if not remove it. // Need to check if it is still marked as favorite, if not remove it.
ViewController::get()-> ViewController::get()->getGameListView(curSys).get()->remove(collectionEntry,
getGameListView(curSys).get()->remove(collectionEntry, false); false);
} }
else if (name == "recent" && file->metadata.get("lastplayed") == "0") { else if (name == "recent" && file->metadata.get("lastplayed") == "0") {
// If lastplayed is set to 0 it means the entry has been cleared, and the // If lastplayed is set to 0 it means the entry has been cleared, and the
// game should therefore be removed. // game should therefore be removed.
ViewController::get()-> ViewController::get()->getGameListView(curSys).get()->remove(collectionEntry,
getGameListView(curSys).get()->remove(collectionEntry, false); false);
ViewController::get()->onFileChanged(rootFolder, true); ViewController::get()->onFileChanged(rootFolder, true);
} }
else if (curSys->isCollection() && !file->getCountAsGame()) { else if (curSys->isCollection() && !file->getCountAsGame()) {
// If the countasgame flag has been set to false, then remove the game. // If the countasgame flag has been set to false, then remove the game.
if (curSys->isGroupedCustomCollection()) { if (curSys->isGroupedCustomCollection()) {
ViewController::get()->getGameListView(curSys->getRootFolder()->getParent()-> ViewController::get()
getSystem()).get()->remove(collectionEntry, false); ->getGameListView(curSys->getRootFolder()->getParent()->getSystem())
.get()
->remove(collectionEntry, false);
FileData* parentRootFolder = FileData* parentRootFolder =
rootFolder->getParent()->getSystem()->getRootFolder(); rootFolder->getParent()->getSystem()->getRootFolder();
parentRootFolder->sort(parentRootFolder->getSortTypeFromString( parentRootFolder->sort(parentRootFolder->getSortTypeFromString(
parentRootFolder->getSortTypeString()), mFavoritesSorting); parentRootFolder->getSortTypeString()),
mFavoritesSorting);
} }
else { else {
ViewController::get()-> ViewController::get()->getGameListView(curSys).get()->remove(collectionEntry,
getGameListView(curSys).get()->remove(collectionEntry, false); false);
} }
rootFolder->sort(rootFolder->getSortTypeFromString( rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
rootFolder->getSortTypeString()), mFavoritesSorting); mFavoritesSorting);
} }
else { else {
// Re-index with new metadata. // Re-index with new metadata.
@ -402,10 +404,12 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection
if ((name == "recent" && file->metadata.get("playcount") > "0" && if ((name == "recent" && file->metadata.get("playcount") > "0" &&
file->getCountAsGame() && includeFileInAutoCollections(file)) || file->getCountAsGame() && includeFileInAutoCollections(file)) ||
(name == "favorites" && file->metadata.get("favorite") == "true" && (name == "favorites" && file->metadata.get("favorite") == "true" &&
file->getCountAsGame())) file->getCountAsGame())) {
addGame = true; addGame = true;
else if (name == "all" && file->getCountAsGame()) }
else if (name == "all" && file->getCountAsGame()) {
addGame = true; addGame = true;
}
if (addGame) { if (addGame) {
CollectionFileData* newGame = new CollectionFileData(file, curSys); CollectionFileData* newGame = new CollectionFileData(file, curSys);
rootFolder->addChild(newGame); rootFolder->addChild(newGame);
@ -424,14 +428,13 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection
} }
// If the game doesn't exist in the current system and it's a custom // If the game doesn't exist in the current system and it's a custom
// collection, then skip the sorting. // collection, then skip the sorting.
else if (sysData.decl.isCustom && else if (sysData.decl.isCustom && children.find(file->getFullPath()) != children.cend()) {
children.find(file->getFullPath()) != children.cend()) {
// For custom collections, update either the actual system or its parent depending // For custom collections, update either the actual system or its parent depending
// on whether the collection is grouped or not. // on whether the collection is grouped or not.
if (rootFolder->getSystem()->isGroupedCustomCollection()) { if (rootFolder->getSystem()->isGroupedCustomCollection()) {
rootFolder->getParent()->sort(rootFolder->getParent()-> rootFolder->getParent()->sort(rootFolder->getParent()->getSortTypeFromString(
getSortTypeFromString(rootFolder->getParent()-> rootFolder->getParent()->getSortTypeString()),
getSortTypeString()), mFavoritesSorting); mFavoritesSorting);
} }
else { else {
rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()), rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
@ -453,11 +456,11 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection
// and therefore jump to the first line. The two seconds is incredibly generous // and therefore jump to the first line. The two seconds is incredibly generous
// as normally it would rather be some milliseconds, but who knows what special // as normally it would rather be some milliseconds, but who knows what special
// circumstances could cause a slight delay so let's keep a large margin. // circumstances could cause a slight delay so let's keep a large margin.
if (Utils::Time::now() - auto nTime = Utils::Time::now();
Utils::Time::stringToTime(file->metadata.get("lastplayed")) < 2) { if (nTime - Utils::Time::stringToTime(file->metadata.get("lastplayed")) < 2) {
// Select the first row of the gamelist (the game just played). // Select the first row of the gamelist (the game just played).
IGameListView* gameList = ViewController::get()-> IGameListView* gameList =
getGameListView(getSystemToView(sysData.system)).get(); ViewController::get()->getGameListView(getSystemToView(sysData.system)).get();
gameList->setCursor(gameList->getFirstEntry()); gameList->setCursor(gameList->getFirstEntry());
} }
} }
@ -482,8 +485,7 @@ void CollectionSystemsManager::deleteCollectionFiles(FileData* file)
// Find games in collection systems. // Find games in collection systems.
std::map<std::string, CollectionSystemData> allCollections; std::map<std::string, CollectionSystemData> allCollections;
allCollections.insert(mAutoCollectionSystemsData.cbegin(), allCollections.insert(mAutoCollectionSystemsData.cbegin(), mAutoCollectionSystemsData.cend());
mAutoCollectionSystemsData.cend());
allCollections.insert(mCustomCollectionSystemsData.cbegin(), allCollections.insert(mCustomCollectionSystemsData.cbegin(),
mCustomCollectionSystemsData.cend()); mCustomCollectionSystemsData.cend());
@ -496,8 +498,10 @@ void CollectionSystemsManager::deleteCollectionFiles(FileData* file)
if (found) { if (found) {
FileData* collectionEntry = children.at(key); FileData* collectionEntry = children.at(key);
SystemData* systemViewToUpdate = getSystemToView(sysDataIt->second.system); SystemData* systemViewToUpdate = getSystemToView(sysDataIt->second.system);
ViewController::get()->getGameListView(systemViewToUpdate).get()-> ViewController::get()
remove(collectionEntry, false); ->getGameListView(systemViewToUpdate)
.get()
->remove(collectionEntry, false);
if (sysDataIt->second.decl.isCustom) if (sysDataIt->second.decl.isCustom)
saveCustomCollection(sysDataIt->second.system); saveCustomCollection(sysDataIt->second.system);
} }
@ -543,20 +547,18 @@ std::string CollectionSystemsManager::getValidNewCollectionName(std::string inNa
if (index == 0) { if (index == 0) {
size_t remove = std::string::npos; size_t remove = std::string::npos;
// Get valid name. // Get valid name.
while ((remove = name.find_first_not_of( while ((remove = name.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-[]()' ")) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-[]()' ")) !=
!= std::string::npos) std::string::npos)
name.erase(remove, 1); name.erase(remove, 1);
} }
else { else {
name += " (" + std::to_string(index) + ")"; name += " (" + std::to_string(index) + ")";
} }
if (name == "") { if (name == "")
name = "new collection"; name = "new collection";
}
name = Utils::String::toLower(name); name = Utils::String::toLower(name);
@ -607,9 +609,10 @@ void CollectionSystemsManager::setEditMode(std::string collectionName, bool show
mEditingCollectionSystemData = sysData; mEditingCollectionSystemData = sysData;
if (showPopup) { if (showPopup) {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "EDITING THE '" + GuiInfoPopup* s = new GuiInfoPopup(mWindow,
Utils::String::toUpper(collectionName) + "EDITING '" + Utils::String::toUpper(collectionName) +
"' COLLECTION, ADD/REMOVE GAMES WITH 'Y'", 10000); "' COLLECTION, ADD/REMOVE GAMES WITH 'Y'",
10000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
@ -618,8 +621,10 @@ void CollectionSystemsManager::setEditMode(std::string collectionName, bool show
void CollectionSystemsManager::exitEditMode(bool showPopup) void CollectionSystemsManager::exitEditMode(bool showPopup)
{ {
if (showPopup) { if (showPopup) {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "FINISHED EDITING THE '" + GuiInfoPopup* s = new GuiInfoPopup(
Utils::String::toUpper(mEditingCollection) + "' COLLECTION", 4000); mWindow,
"FINISHED EDITING '" + Utils::String::toUpper(mEditingCollection) + "' COLLECTION",
4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
@ -628,8 +633,7 @@ void CollectionSystemsManager::exitEditMode(bool showPopup)
mEditingCollection = "Favorites"; mEditingCollection = "Favorites";
// Remove all tick marks from the games that are part of the collection. // Remove all tick marks from the games that are part of the collection.
for (auto it = SystemData::sSystemVector.begin(); for (auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) {
it != SystemData::sSystemVector.end(); it++) {
ViewController::get()->getGameListView((*it))->onFileChanged( ViewController::get()->getGameListView((*it))->onFileChanged(
ViewController::get()->getGameListView((*it))->getCursor(), false); ViewController::get()->getGameListView((*it))->getCursor(), false);
} }
@ -637,8 +641,8 @@ void CollectionSystemsManager::exitEditMode(bool showPopup)
mEditingCollectionSystemData->system->onMetaDataSavePoint(); mEditingCollectionSystemData->system->onMetaDataSavePoint();
} }
bool CollectionSystemsManager::inCustomCollection( bool CollectionSystemsManager::inCustomCollection(const std::string& collectionName,
const std::string& collectionName, FileData* gameFile) FileData* gameFile)
{ {
auto collectionEntry = mCustomCollectionSystemsData.find(collectionName); auto collectionEntry = mCustomCollectionSystemsData.find(collectionName);
@ -647,7 +651,6 @@ bool CollectionSystemsManager::inCustomCollection(
collectionEntry->second.system->getRootFolder()->getChildrenByFilename(); collectionEntry->second.system->getRootFolder()->getChildrenByFilename();
return children.find(gameFile->getFullPath()) != children.cend(); return children.find(gameFile->getFullPath()) != children.cend();
} }
return false; return false;
} }
@ -666,8 +669,8 @@ bool CollectionSystemsManager::toggleGameInCollection(FileData* file)
std::string key = file->getFullPath(); std::string key = file->getFullPath();
FileData* rootFolder = sysData->getRootFolder(); FileData* rootFolder = sysData->getRootFolder();
const std::unordered_map<std::string, FileData*>& const std::unordered_map<std::string, FileData*>& children =
children = rootFolder->getChildrenByFilename(); rootFolder->getChildrenByFilename();
bool found = children.find(key) != children.cend(); bool found = children.find(key) != children.cend();
FileFilterIndex* fileIndex = sysData->getIndex(); FileFilterIndex* fileIndex = sysData->getIndex();
std::string name = sysData->getName(); std::string name = sysData->getName();
@ -678,10 +681,12 @@ bool CollectionSystemsManager::toggleGameInCollection(FileData* file)
adding = false; adding = false;
// If we found it, we need to remove it. // If we found it, we need to remove it.
FileData* collectionEntry = children.at(key); FileData* collectionEntry = children.at(key);
ViewController::get()->getGameListView(systemViewToUpdate).get()-> ViewController::get()
remove(collectionEntry, false); ->getGameListView(systemViewToUpdate)
systemViewToUpdate->getRootFolder()->sort(rootFolder->getSortTypeFromString( .get()
rootFolder->getSortTypeString()), ->remove(collectionEntry, false);
systemViewToUpdate->getRootFolder()->sort(
rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
Settings::getInstance()->getBool("FavFirstCustom")); Settings::getInstance()->getBool("FavFirstCustom"));
ViewController::get()->reloadGameListView(systemViewToUpdate); ViewController::get()->reloadGameListView(systemViewToUpdate);
@ -692,8 +697,8 @@ bool CollectionSystemsManager::toggleGameInCollection(FileData* file)
CollectionFileData* newGame = new CollectionFileData(file, sysData); CollectionFileData* newGame = new CollectionFileData(file, sysData);
rootFolder->addChild(newGame); rootFolder->addChild(newGame);
systemViewToUpdate->getRootFolder()->sort(rootFolder->getSortTypeFromString( systemViewToUpdate->getRootFolder()->sort(
rootFolder->getSortTypeString()), rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
Settings::getInstance()->getBool("FavFirstCustom")); Settings::getInstance()->getBool("FavFirstCustom"));
ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), true); ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), true);
fileIndex->addToIndex(newGame); fileIndex->addToIndex(newGame);
@ -722,17 +727,23 @@ bool CollectionSystemsManager::toggleGameInCollection(FileData* file)
file->getSourceFileData()->getSystem()->onMetaDataSavePoint(); file->getSourceFileData()->getSystem()->onMetaDataSavePoint();
refreshCollectionSystems(file->getSourceFileData()); refreshCollectionSystems(file->getSourceFileData());
if (mAutoCollectionSystemsData["favorites"].isEnabled) if (mAutoCollectionSystemsData["favorites"].isEnabled)
ViewController::get()-> ViewController::get()->reloadGameListView(
reloadGameListView(mAutoCollectionSystemsData["favorites"].system); mAutoCollectionSystemsData["favorites"].system);
}
if (adding) {
s = new GuiInfoPopup(
mWindow,
"ADDED '" + Utils::String::toUpper(Utils::String::removeParenthesis(name)) +
"' TO '" + Utils::String::toUpper(sysName) + "'",
4000);
}
else {
s = new GuiInfoPopup(
mWindow,
"REMOVED '" + Utils::String::toUpper(Utils::String::removeParenthesis(name)) +
"' FROM '" + Utils::String::toUpper(sysName) + "'",
4000);
} }
if (adding)
s = new GuiInfoPopup(mWindow, "ADDED '" +
Utils::String::toUpper(Utils::String::removeParenthesis(name)) +
"' TO '" + Utils::String::toUpper(sysName) + "'", 4000);
else
s = new GuiInfoPopup(mWindow, "REMOVED '" +
Utils::String::toUpper(Utils::String::removeParenthesis(name)) +
"' FROM '" + Utils::String::toUpper(sysName) + "'", 4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
return true; return true;
} }
@ -745,8 +756,8 @@ SystemData* CollectionSystemsManager::getSystemToView(SystemData* sys)
FileData* rootFolder = sys->getRootFolder(); FileData* rootFolder = sys->getRootFolder();
FileData* bundleRootFolder = mCustomCollectionsBundle->getRootFolder(); FileData* bundleRootFolder = mCustomCollectionsBundle->getRootFolder();
const std::unordered_map<std::string, FileData*>& const std::unordered_map<std::string, FileData*>& bundleChildren =
bundleChildren = bundleRootFolder->getChildrenByFilename(); bundleRootFolder->getChildrenByFilename();
// Is the rootFolder bundled in the "My Collections" system? // Is the rootFolder bundled in the "My Collections" system?
bool sysFoundInBundle = bundleChildren.find(rootFolder->getKey()) != bundleChildren.cend(); bool sysFoundInBundle = bundleChildren.find(rootFolder->getKey()) != bundleChildren.cend();
@ -800,23 +811,25 @@ FileData* CollectionSystemsManager::updateCollectionFolderMetadata(SystemData* s
if (gameCount > 0) { if (gameCount > 0) {
if (Settings::getInstance()->getBool("CollectionShowSystemInfo")) { if (Settings::getInstance()->getBool("CollectionShowSystemInfo")) {
switch (gameCount) { switch (gameCount) {
case 1: case 1: {
desc = "This collection contains 1 game: '" + desc = "This collection contains 1 game: '" +
gamesList[0]->metadata.get("name") + " [" + gamesList[0]->metadata.get("name") + " [" +
gamesList[0]->getSourceFileData()->getSystem()->getName() + "]'"; gamesList[0]->getSourceFileData()->getSystem()->getName() + "]'";
break; break;
case 2: }
case 2: {
desc = "This collection contains 2 games: '" + desc = "This collection contains 2 games: '" +
gamesList[0]->metadata.get("name") + " [" + gamesList[0]->metadata.get("name") + " [" +
gamesList[0]->getSourceFileData()->getSystem()->getName() + gamesList[0]->getSourceFileData()->getSystem()->getName() + "]' and '" +
"]' and '" + gamesList[1]->metadata.get("name") + " [" + gamesList[1]->metadata.get("name") + " [" +
gamesList[1]->getSourceFileData()->getSystem()->getName() + "]'"; gamesList[1]->getSourceFileData()->getSystem()->getName() + "]'";
break; break;
default: }
desc = "This collection contains " + std::to_string(gameCount) + default: {
" games: '" + gamesList[0]->metadata.get("name") + desc = "This collection contains " + std::to_string(gameCount) + " games: '" +
" [" + gamesList[0]->getSourceFileData()->getSystem()->getName() + gamesList[0]->metadata.get("name") + " [" +
"]', '" + gamesList[1]->metadata.get("name") + " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() + "]', '" +
gamesList[1]->metadata.get("name") + " [" +
gamesList[1]->getSourceFileData()->getSystem()->getName() + "]' and '" + gamesList[1]->getSourceFileData()->getSystem()->getName() + "]' and '" +
gamesList[2]->metadata.get("name") + " [" + gamesList[2]->metadata.get("name") + " [" +
gamesList[2]->getSourceFileData()->getSystem()->getName() + "]'"; gamesList[2]->getSourceFileData()->getSystem()->getName() + "]'";
@ -824,27 +837,31 @@ FileData* CollectionSystemsManager::updateCollectionFolderMetadata(SystemData* s
break; break;
} }
} }
}
else { else {
switch (gameCount) { switch (gameCount) {
case 1: case 1: {
desc = "This collection contains 1 game: '" + desc = "This collection contains 1 game: '" +
gamesList[0]->metadata.get("name") + " '"; gamesList[0]->metadata.get("name") + " '";
break; break;
case 2: }
case 2: {
desc = "This collection contains 2 games: '" + desc = "This collection contains 2 games: '" +
gamesList[0]->metadata.get("name") + gamesList[0]->metadata.get("name") + "' and '" +
"' and '" + gamesList[1]->metadata.get("name") + "'"; gamesList[1]->metadata.get("name") + "'";
break; break;
default: }
desc = "This collection contains " + std::to_string(gameCount) + default: {
" games: '" + gamesList[0]->metadata.get("name") + desc = "This collection contains " + std::to_string(gameCount) + " games: '" +
"', '" + gamesList[1]->metadata.get("name") + "' and '" + gamesList[0]->metadata.get("name") + "', '" +
gamesList[1]->metadata.get("name") + "' and '" +
gamesList[2]->metadata.get("name") + "'"; gamesList[2]->metadata.get("name") + "'";
desc += (gameCount == 3 ? "" : ", among others"); desc += (gameCount == 3 ? "" : ", among others");
break; break;
} }
} }
} }
}
if (idx->isFiltered()) if (idx->isFiltered())
desc += "\n\n'" + rootFolder->getSystem()->getFullName() + desc += "\n\n'" + rootFolder->getSystem()->getFullName() +
@ -920,10 +937,11 @@ void CollectionSystemsManager::deleteCustomCollection(std::string collectionName
std::string configFile = getCustomCollectionConfigPath(collectionName); std::string configFile = getCustomCollectionConfigPath(collectionName);
Utils::FileSystem::removeFile(configFile); Utils::FileSystem::removeFile(configFile);
LOG(LogDebug) << "CollectionSystemsManager::deleteCustomCollection(): Deleted the " LOG(LogDebug) << "CollectionSystemsManager::deleteCustomCollection(): Deleted the "
"configuration file '" << configFile << "'."; "configuration file '"
<< configFile << "'.";
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "DELETED THE COLLECTION '" + GuiInfoPopup* s = new GuiInfoPopup(
Utils::String::toUpper(collectionName) + "'", 5000); mWindow, "DELETED COLLECTION '" + Utils::String::toUpper(collectionName) + "'", 5000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
else { else {
@ -941,8 +959,8 @@ void CollectionSystemsManager::reactivateCustomCollectionEntry(FileData* game)
// matching entries for the game passed as the parameter. If so, then enable it in each // matching entries for the game passed as the parameter. If so, then enable it in each
// of those collections. This is done also for disabled collections, as otherwise the // of those collections. This is done also for disabled collections, as otherwise the
// game would be missing if the collection was enabled during the program session. // game would be missing if the collection was enabled during the program session.
for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator it =
it = mCustomCollectionSystemsData.cbegin(); mCustomCollectionSystemsData.cbegin();
it != mCustomCollectionSystemsData.cend(); it++) { it != mCustomCollectionSystemsData.cend(); it++) {
std::string path = getCustomCollectionConfigPath(it->first); std::string path = getCustomCollectionConfigPath(it->first);
if (Utils::FileSystem::exists(path)) { if (Utils::FileSystem::exists(path)) {
@ -966,11 +984,12 @@ void CollectionSystemsManager::reactivateCustomCollectionEntry(FileData* game)
void CollectionSystemsManager::repopulateCollection(SystemData* sysData) void CollectionSystemsManager::repopulateCollection(SystemData* sysData)
{ {
for (auto it = mAutoCollectionSystemsData.cbegin(); for (auto it = mAutoCollectionSystemsData.cbegin(); // Line break.
it != mAutoCollectionSystemsData.cend(); it++) { it != mAutoCollectionSystemsData.cend(); it++) {
if ((*it).second.system == sysData) { if ((*it).second.system == sysData) {
LOG(LogDebug) << "CollectionSystemsManager::repopulateCollection(): " LOG(LogDebug) << "CollectionSystemsManager::repopulateCollection(): "
"Repopulating auto collection \"" << it->first << "\""; "Repopulating auto collection \""
<< it->first << "\"";
CollectionSystemData* autoSystem = &mAutoCollectionSystemsData[it->first]; CollectionSystemData* autoSystem = &mAutoCollectionSystemsData[it->first];
std::vector<FileData*> systemEntries = std::vector<FileData*> systemEntries =
@ -1005,18 +1024,19 @@ void CollectionSystemsManager::repopulateCollection(SystemData* sysData)
autoView->setCursor(autoView->getLastEntry()); autoView->setCursor(autoView->getLastEntry());
} }
else { else {
autoView->setCursor(autoSystem->system->getRootFolder()-> autoView->setCursor(
getChildrenRecursive().front()); autoSystem->system->getRootFolder()->getChildrenRecursive().front());
autoView->setCursor(autoView->getFirstEntry()); autoView->setCursor(autoView->getFirstEntry());
} }
} }
} }
for (auto it = mCustomCollectionSystemsData.cbegin(); for (auto it = mCustomCollectionSystemsData.cbegin(); // Line break.
it != mCustomCollectionSystemsData.cend(); it++) { it != mCustomCollectionSystemsData.cend(); it++) {
if ((*it).second.system == sysData) { if ((*it).second.system == sysData) {
LOG(LogDebug) << "CollectionSystemsManager::repopulateCollection(): " LOG(LogDebug) << "CollectionSystemsManager::repopulateCollection(): "
"Repopulating custom collection '" << it->first << "'."; "Repopulating custom collection '"
<< it->first << "'.";
CollectionSystemData* customSystem = &mCustomCollectionSystemsData[it->first]; CollectionSystemData* customSystem = &mCustomCollectionSystemsData[it->first];
std::vector<FileData*> systemEntries = std::vector<FileData*> systemEntries =
@ -1035,8 +1055,8 @@ void CollectionSystemsManager::repopulateCollection(SystemData* sysData)
populateCustomCollection(customSystem); populateCustomCollection(customSystem);
auto autoView = ViewController::get()->getGameListView(customSystem->system).get(); auto autoView = ViewController::get()->getGameListView(customSystem->system).get();
autoView->setCursor(customSystem->system->getRootFolder()-> autoView->setCursor(
getChildrenRecursive().front()); customSystem->system->getRootFolder()->getChildrenRecursive().front());
autoView->setCursor(autoView->getFirstEntry()); autoView->setCursor(autoView->getFirstEntry());
} }
} }
@ -1044,8 +1064,8 @@ void CollectionSystemsManager::repopulateCollection(SystemData* sysData)
void CollectionSystemsManager::initAutoCollectionSystems() void CollectionSystemsManager::initAutoCollectionSystems()
{ {
for (std::map<std::string, CollectionSystemDecl, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemDecl, stringComparator>::const_iterator it =
it = mCollectionSystemDeclsIndex.cbegin(); mCollectionSystemDeclsIndex.cbegin();
it != mCollectionSystemDeclsIndex.cend(); it++) { it != mCollectionSystemDeclsIndex.cend(); it++) {
CollectionSystemDecl sysDecl = it->second; CollectionSystemDecl sysDecl = it->second;
@ -1071,11 +1091,13 @@ SystemData* CollectionSystemsManager::getAllGamesCollection()
return allSysData->system; return allSysData->system;
} }
SystemData* CollectionSystemsManager::createNewCollectionEntry( SystemData* CollectionSystemsManager::createNewCollectionEntry(std::string name,
std::string name, CollectionSystemDecl sysDecl, bool index, bool custom) CollectionSystemDecl sysDecl,
bool index,
bool custom)
{ {
SystemData* newSys = new SystemData(name, sysDecl.longName, SystemData* newSys = new SystemData(name, sysDecl.longName, mCollectionEnvData,
mCollectionEnvData, sysDecl.themeFolder, true, custom); sysDecl.themeFolder, true, custom);
CollectionSystemData newCollectionData; CollectionSystemData newCollectionData;
newCollectionData.system = newSys; newCollectionData.system = newSys;
@ -1099,7 +1121,7 @@ void CollectionSystemsManager::populateAutoCollection(CollectionSystemData* sysD
CollectionSystemDecl sysDecl = sysData->decl; CollectionSystemDecl sysDecl = sysData->decl;
FileData* rootFolder = newSys->getRootFolder(); FileData* rootFolder = newSys->getRootFolder();
FileFilterIndex* index = newSys->getIndex(); FileFilterIndex* index = newSys->getIndex();
for (auto sysIt = SystemData::sSystemVector.cbegin(); for (auto sysIt = SystemData::sSystemVector.cbegin(); // Line break.
sysIt != SystemData::sSystemVector.cend(); sysIt++) { sysIt != SystemData::sSystemVector.cend(); sysIt++) {
// We won't iterate all collections. // We won't iterate all collections.
if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection()) { if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection()) {
@ -1108,17 +1130,20 @@ void CollectionSystemsManager::populateAutoCollection(CollectionSystemData* sysD
bool include = includeFileInAutoCollections((*gameIt)); bool include = includeFileInAutoCollections((*gameIt));
switch (sysDecl.type) { switch (sysDecl.type) {
case AUTO_LAST_PLAYED: case AUTO_LAST_PLAYED: {
include = include && (*gameIt)->metadata.get("playcount") > "0"; include = include && (*gameIt)->metadata.get("playcount") > "0";
break; break;
case AUTO_FAVORITES: }
case AUTO_FAVORITES: {
// We may still want to add files we don't want in auto collections // We may still want to add files we don't want in auto collections
// to "favorites". // to "favorites".
include = (*gameIt)->metadata.get("favorite") == "true"; include = (*gameIt)->metadata.get("favorite") == "true";
break; break;
default: }
default: {
break; break;
} }
}
if (include) { if (include) {
// Exclude files that are set not to be counted as games. // Exclude files that are set not to be counted as games.
@ -1132,6 +1157,7 @@ void CollectionSystemsManager::populateAutoCollection(CollectionSystemData* sysD
} }
} }
} }
if (rootFolder->getName() == "recent") if (rootFolder->getName() == "recent")
rootFolder->sort(rootFolder->getSortTypeFromString("last played, ascending")); rootFolder->sort(rootFolder->getSortTypeFromString("last played, ascending"));
else else
@ -1148,12 +1174,14 @@ void CollectionSystemsManager::populateAutoCollection(CollectionSystemData* sysD
// The following is needed to avoid a crash when repopulating the system as the previous // The following is needed to avoid a crash when repopulating the system as the previous
// cursor pointer may point to a random memory address. // cursor pointer may point to a random memory address.
auto recentGamelist = ViewController::get()->getGameListView(rootFolder->getSystem()).get(); auto recentGamelist = ViewController::get()->getGameListView(rootFolder->getSystem()).get();
recentGamelist->setCursor(rootFolder->getSystem()->getRootFolder()-> recentGamelist->setCursor(
getChildrenRecursive().front()); rootFolder->getSystem()->getRootFolder()->getChildrenRecursive().front());
recentGamelist->setCursor(recentGamelist->getFirstEntry()); recentGamelist->setCursor(recentGamelist->getFirstEntry());
if (rootFolder->getChildren().size() > 0) if (rootFolder->getChildren().size() > 0)
ViewController::get()->getGameListView(rootFolder->getSystem()).get()-> ViewController::get()
onFileChanged(rootFolder->getChildren().front(), false); ->getGameListView(rootFolder->getSystem())
.get()
->onFileChanged(rootFolder->getChildren().front(), false);
} }
sysData->isPopulated = true; sysData->isPopulated = true;
@ -1176,6 +1204,7 @@ void CollectionSystemsManager::populateCustomCollection(CollectionSystemData* sy
FileFilterIndex* index = newSys->getIndex(); FileFilterIndex* index = newSys->getIndex();
// Get configuration for this custom collection. // Get configuration for this custom collection.
#if defined(_WIN64) #if defined(_WIN64)
std::ifstream input(Utils::String::stringToWideString(path).c_str()); std::ifstream input(Utils::String::stringToWideString(path).c_str());
#else #else
@ -1183,8 +1212,8 @@ void CollectionSystemsManager::populateCustomCollection(CollectionSystemData* sy
#endif #endif
// Get all files map. // Get all files map.
std::unordered_map<std::string,FileData*> std::unordered_map<std::string, FileData*> allFilesMap =
allFilesMap = getAllGamesCollection()->getRootFolder()->getChildrenByFilename(); getAllGamesCollection()->getRootFolder()->getChildrenByFilename();
// Get the ROM directory, either as configured in es_settings.xml, or if no value // Get the ROM directory, either as configured in es_settings.xml, or if no value
// is set there, then use the default hardcoded path. // is set there, then use the default hardcoded path.
@ -1207,8 +1236,9 @@ void CollectionSystemsManager::populateCustomCollection(CollectionSystemData* sy
index->addToIndex(newGame); index->addToIndex(newGame);
} }
else { else {
LOG(LogWarning) << "File \"" << gameKey << LOG(LogWarning)
"\" does not exist, is hidden, or is not counted as a game, ignoring entry"; << "File \"" << gameKey
<< "\" does not exist, is hidden, or is not counted as a game, ignoring entry";
} }
} }
@ -1244,8 +1274,9 @@ void CollectionSystemsManager::addEnabledCollectionsToDisplayedSystems(
std::map<std::string, CollectionSystemData, stringComparator>* colSystemData) std::map<std::string, CollectionSystemData, stringComparator>* colSystemData)
{ {
// Add auto enabled collections. // Add auto enabled collections.
for (std::map<std::string, CollectionSystemData, stringComparator>::iterator for (std::map<std::string, CollectionSystemData, stringComparator>::iterator it =
it = colSystemData->begin(); it != colSystemData->end(); it++) { colSystemData->begin();
it != colSystemData->end(); it++) {
if (it->second.isEnabled) { if (it->second.isEnabled) {
// Check if populated, otherwise populate. // Check if populated, otherwise populate.
if (!it->second.isPopulated) { if (!it->second.isPopulated) {
@ -1263,12 +1294,12 @@ void CollectionSystemsManager::addEnabledCollectionsToDisplayedSystems(
// If this is a non-bundled custom collection, then sort it. // If this is a non-bundled custom collection, then sort it.
if (it->second.decl.isCustom == true) { if (it->second.decl.isCustom == true) {
FileData* rootFolder = it->second.system->getRootFolder(); FileData* rootFolder = it->second.system->getRootFolder();
rootFolder->sort(rootFolder->getSortTypeFromString( rootFolder->sort(
rootFolder->getSortTypeString()), rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
Settings::getInstance()->getBool("FavFirstCustom")); Settings::getInstance()->getBool("FavFirstCustom"));
// Jump to the first row of the game list, assuming it's not empty. // Jump to the first row of the game list, assuming it's not empty.
IGameListView* gameList = ViewController::get()-> IGameListView* gameList =
getGameListView((it->second.system)).get(); ViewController::get()->getGameListView((it->second.system)).get();
if (!gameList->getCursor()->isPlaceHolder()) { if (!gameList->getCursor()->isPlaceHolder()) {
gameList->setCursor(gameList->getFirstEntry()); gameList->setCursor(gameList->getFirstEntry());
} }
@ -1309,8 +1340,8 @@ std::vector<std::string> CollectionSystemsManager::getSystemsFromConfig()
if (!systemList) if (!systemList)
return systems; return systems;
for (pugi::xml_node system = systemList.child("system"); for (pugi::xml_node system = systemList.child("system"); system;
system; system = system.next_sibling("system")) { system = system.next_sibling("system")) {
// Theme folder. // Theme folder.
std::string themeFolder = system.child("theme").text().get(); std::string themeFolder = system.child("theme").text().get();
systems.push_back(themeFolder); systems.push_back(themeFolder);
@ -1328,8 +1359,8 @@ std::vector<std::string> CollectionSystemsManager::getSystemsFromTheme()
if (themeSets.empty()) if (themeSets.empty())
return systems; // No theme sets available. return systems; // No theme sets available.
std::map<std::string, ThemeSet>::const_iterator std::map<std::string, ThemeSet>::const_iterator set =
set = themeSets.find(Settings::getInstance()->getString("ThemeSet")); themeSets.find(Settings::getInstance()->getString("ThemeSet"));
if (set == themeSets.cend()) { if (set == themeSets.cend()) {
// Currently selected theme set is missing, so just pick the first available set. // Currently selected theme set is missing, so just pick the first available set.
set = themeSets.cbegin(); set = themeSets.cbegin();
@ -1341,8 +1372,8 @@ std::vector<std::string> CollectionSystemsManager::getSystemsFromTheme()
if (Utils::FileSystem::exists(themePath)) { if (Utils::FileSystem::exists(themePath)) {
Utils::FileSystem::stringList dirContent = Utils::FileSystem::getDirContent(themePath); Utils::FileSystem::stringList dirContent = Utils::FileSystem::getDirContent(themePath);
for (Utils::FileSystem::stringList::const_iterator for (Utils::FileSystem::stringList::const_iterator it = dirContent.cbegin();
it = dirContent.cbegin(); it != dirContent.cend(); it++) { it != dirContent.cend(); it++) {
if (Utils::FileSystem::isDirectory(*it)) { if (Utils::FileSystem::isDirectory(*it)) {
// ... here you have a directory. // ... here you have a directory.
std::string folder = *it; std::string folder = *it;
@ -1362,18 +1393,16 @@ std::vector<std::string> CollectionSystemsManager::getCollectionsFromConfigFolde
std::vector<std::string> systems; std::vector<std::string> systems;
std::string configPath = getCollectionsFolder(); std::string configPath = getCollectionsFolder();
if (Utils::FileSystem::exists(configPath)) if (Utils::FileSystem::exists(configPath)) {
{ Utils::FileSystem::stringList dirContent = Utils::FileSystem::getDirContent(configPath);
Utils::FileSystem::stringList dirContent = for (Utils::FileSystem::stringList::const_iterator it = dirContent.cbegin();
Utils::FileSystem::getDirContent(configPath); it != dirContent.cend(); it++) {
for (Utils::FileSystem::stringList::const_iterator
it = dirContent.cbegin(); it != dirContent.cend(); it++) {
if (Utils::FileSystem::isRegularFile(*it)) { if (Utils::FileSystem::isRegularFile(*it)) {
// It's a file. // It's a file.
std::string filename = Utils::FileSystem::getFileName(*it); std::string filename = Utils::FileSystem::getFileName(*it);
// Need to confirm filename matches config format. // Need to confirm filename matches config format.
if (filename != "custom-.cfg" && Utils::String::startsWith( if (filename != "custom-.cfg" && Utils::String::startsWith(filename, "custom-") &&
filename, "custom-") && Utils::String::endsWith(filename, ".cfg")) { Utils::String::endsWith(filename, ".cfg")) {
filename = filename.substr(7, filename.size() - 11); filename = filename.substr(7, filename.size() - 11);
systems.push_back(filename); systems.push_back(filename);
} }
@ -1390,8 +1419,8 @@ std::vector<std::string> CollectionSystemsManager::getCollectionsFromConfigFolde
std::vector<std::string> CollectionSystemsManager::getCollectionThemeFolders(bool custom) std::vector<std::string> CollectionSystemsManager::getCollectionThemeFolders(bool custom)
{ {
std::vector<std::string> systems; std::vector<std::string> systems;
for (std::map<std::string, CollectionSystemDecl, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemDecl, stringComparator>::const_iterator it =
it = mCollectionSystemDeclsIndex.cbegin(); mCollectionSystemDeclsIndex.cbegin();
it != mCollectionSystemDeclsIndex.cend(); it++) { it != mCollectionSystemDeclsIndex.cend(); it++) {
CollectionSystemDecl sysDecl = it->second; CollectionSystemDecl sysDecl = it->second;
if (sysDecl.isCustom == custom) if (sysDecl.isCustom == custom)
@ -1403,8 +1432,8 @@ std::vector<std::string> CollectionSystemsManager::getCollectionThemeFolders(boo
std::vector<std::string> CollectionSystemsManager::getUserCollectionThemeFolders() std::vector<std::string> CollectionSystemsManager::getUserCollectionThemeFolders()
{ {
std::vector<std::string> systems; std::vector<std::string> systems;
for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator it =
it = mCustomCollectionSystemsData.cbegin(); mCustomCollectionSystemsData.cbegin();
it != mCustomCollectionSystemsData.cend(); it++) it != mCustomCollectionSystemsData.cend(); it++)
systems.push_back(it->second.decl.themeFolder); systems.push_back(it->second.decl.themeFolder);
return systems; return systems;

View file

@ -34,7 +34,7 @@ class Window;
struct SystemEnvironmentData; struct SystemEnvironmentData;
enum CollectionSystemType { enum CollectionSystemType {
AUTO_ALL_GAMES, AUTO_ALL_GAMES, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
AUTO_LAST_PLAYED, AUTO_LAST_PLAYED,
AUTO_FAVORITES, AUTO_FAVORITES,
CUSTOM_COLLECTION CUSTOM_COLLECTION
@ -115,13 +115,17 @@ public:
// Repopulate the collection, which is basically a forced update of its complete content. // Repopulate the collection, which is basically a forced update of its complete content.
void repopulateCollection(SystemData* sysData); void repopulateCollection(SystemData* sysData);
inline std::map<std::string, CollectionSystemData, stringComparator> std::map<std::string, CollectionSystemData, stringComparator> getAutoCollectionSystems()
getAutoCollectionSystems() { return mAutoCollectionSystemsData; }; {
inline std::map<std::string, CollectionSystemData, stringComparator> return mAutoCollectionSystemsData;
getCustomCollectionSystems() { return mCustomCollectionSystemsData; }; }
inline SystemData* getCustomCollectionsBundle() { return mCustomCollectionsBundle; }; std::map<std::string, CollectionSystemData, stringComparator> getCustomCollectionSystems()
inline bool isEditing() { return mIsEditingCustom; }; {
inline std::string getEditingCollection() { return mEditingCollection; }; return mCustomCollectionSystemsData;
}
SystemData* getCustomCollectionsBundle() { return mCustomCollectionsBundle; }
bool isEditing() { return mIsEditingCustom; }
std::string getEditingCollection() { return mEditingCollection; }
private: private:
static CollectionSystemsManager* sInstance; static CollectionSystemsManager* sInstance;
@ -143,7 +147,9 @@ private:
SystemData* getAllGamesCollection(); SystemData* getAllGamesCollection();
// Create a new empty collection system based on the name and declaration. // Create a new empty collection system based on the name and declaration.
SystemData* createNewCollectionEntry(std::string name, SystemData* createNewCollectionEntry(std::string name,
CollectionSystemDecl sysDecl, bool index = true, bool custom = false); CollectionSystemDecl sysDecl,
bool index = true,
bool custom = false);
// Populate an automatic collection system. // Populate an automatic collection system.
void populateAutoCollection(CollectionSystemData* sysData); void populateAutoCollection(CollectionSystemData* sysData);
// Populate a custom collection system. // Populate a custom collection system.
@ -151,8 +157,8 @@ private:
// Functions to handle System View removal and insertion of collections: // Functions to handle System View removal and insertion of collections:
void removeCollectionsFromDisplayedSystems(); void removeCollectionsFromDisplayedSystems();
void addEnabledCollectionsToDisplayedSystems(std::map<std::string, void addEnabledCollectionsToDisplayedSystems(
CollectionSystemData, stringComparator>* colSystemData); std::map<std::string, CollectionSystemData, stringComparator>* colSystemData);
// Auxiliary functions: // Auxiliary functions:
std::vector<std::string> getSystemsFromConfig(); std::vector<std::string> getSystemsFromConfig();

View file

@ -10,10 +10,12 @@
// These numbers and strings need to be manually updated for a new version. // These numbers and strings need to be manually updated for a new version.
// Do this version number update as the very last commit for the new release version. // Do this version number update as the very last commit for the new release version.
// clang-format off
#define PROGRAM_VERSION_MAJOR 1 #define PROGRAM_VERSION_MAJOR 1
#define PROGRAM_VERSION_MINOR 0 #define PROGRAM_VERSION_MINOR 1
#define PROGRAM_VERSION_MAINTENANCE 0 #define PROGRAM_VERSION_MAINTENANCE 0
#define PROGRAM_VERSION_STRING "1.1.0-beta2-dev" // clang-format on
#define PROGRAM_VERSION_STRING "1.1.0-rc"
#define PROGRAM_BUILT_STRING __DATE__ " - " __TIME__ #define PROGRAM_BUILT_STRING __DATE__ " - " __TIME__

View file

@ -10,12 +10,6 @@
#include "FileData.h" #include "FileData.h"
#include "guis/GuiInfoPopup.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
@ -26,24 +20,29 @@
#include "Scripting.h" #include "Scripting.h"
#include "SystemData.h" #include "SystemData.h"
#include "Window.h" #include "Window.h"
#include "guis/GuiInfoPopup.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include <assert.h> #include <assert.h>
FileData::FileData( FileData::FileData(FileType type,
FileType type,
const std::string& path, const std::string& path,
SystemEnvironmentData* envData, SystemEnvironmentData* envData,
SystemData* system) SystemData* system)
: mType(type), : mType(type)
mPath(path), , mPath(path)
mSystem(system), , mSystem(system)
mEnvData(envData), , mEnvData(envData)
mSourceFileData(nullptr), , mSourceFileData(nullptr)
mParent(nullptr), , mParent(nullptr)
mOnlyFolders(false), , mOnlyFolders(false)
mDeletionFlag(false), , mDeletionFlag(false)
// Metadata is set in the constructor. // Metadata is set in the constructor.
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) , metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
{ {
// Metadata needs at least a name field (since that's what getName() will return). // Metadata needs at least a name field (since that's what getName() will return).
if (metadata.get("name").empty()) { if (metadata.get("name").empty()) {
@ -51,8 +50,7 @@ FileData::FileData(
system->hasPlatformId(PlatformIds::SNK_NEO_GEO)) && system->hasPlatformId(PlatformIds::SNK_NEO_GEO)) &&
metadata.getType() != FOLDER_METADATA) { metadata.getType() != FOLDER_METADATA) {
// If it's a MAME or Neo Geo game, expand the game name accordingly. // If it's a MAME or Neo Geo game, expand the game name accordingly.
metadata.set("name", metadata.set("name", MameNames::getInstance()->getCleanName(getCleanName()));
MameNames::getInstance()->getCleanName(getCleanName()));
} }
else { else {
if (metadata.getType() == FOLDER_METADATA && Utils::FileSystem::isHidden(mPath)) { if (metadata.getType() == FOLDER_METADATA && Utils::FileSystem::isHidden(mPath)) {
@ -89,6 +87,7 @@ std::string FileData::getCleanName() const
const std::string& FileData::getName() const std::string& FileData::getName()
{ {
// Return metadata name.
return metadata.get("name"); return metadata.get("name");
} }
@ -144,14 +143,13 @@ const std::vector<FileData*> FileData::getChildrenRecursive() const
{ {
std::vector<FileData*> childrenRecursive; std::vector<FileData*> childrenRecursive;
for (auto it = mChildrenByFilename.cbegin(); for (auto it = mChildrenByFilename.cbegin(); it != mChildrenByFilename.cend(); it++) {
it != mChildrenByFilename.cend(); it++) {
childrenRecursive.push_back((*it).second); childrenRecursive.push_back((*it).second);
// Recurse through any subdirectories. // Recurse through any subdirectories.
if ((*it).second->getType() == FOLDER) { if ((*it).second->getType() == FOLDER) {
std::vector<FileData*> childrenSubdirectory = (*it).second->getChildrenRecursive(); std::vector<FileData*> childrenSubdirectory = (*it).second->getChildrenRecursive();
childrenRecursive.insert(childrenRecursive.end(), childrenRecursive.insert(childrenRecursive.end(), childrenSubdirectory.begin(),
childrenSubdirectory.begin(), childrenSubdirectory.end()); childrenSubdirectory.end());
} }
} }
@ -202,8 +200,8 @@ const std::string FileData::getMediaDirectory()
// If %ESPATH% is used for the media directory configuration, then expand it to the // If %ESPATH% is used for the media directory configuration, then expand it to the
// binary directory of ES-DE. // binary directory of ES-DE.
mediaDirPath = Utils::String::replace( mediaDirPath =
mediaDirPath, "%ESPATH%", Utils::FileSystem::getExePath()); Utils::String::replace(mediaDirPath, "%ESPATH%", Utils::FileSystem::getExePath());
if (mediaDirPath.back() != '/') if (mediaDirPath.back() != '/')
mediaDirPath = mediaDirPath + "/"; mediaDirPath = mediaDirPath + "/";
@ -219,8 +217,8 @@ const std::string FileData::getMediafilePath(std::string subdirectory, std::stri
// Extract possible subfolders from the path. // Extract possible subfolders from the path.
if (mEnvData->mStartPath != "") if (mEnvData->mStartPath != "")
subFolders = Utils::String::replace( subFolders =
Utils::FileSystem::getParent(mPath), mEnvData->mStartPath, ""); Utils::String::replace(Utils::FileSystem::getParent(mPath), mEnvData->mStartPath, "");
const std::string tempPath = getMediaDirectory() + mSystemName + "/" + subdirectory + const std::string tempPath = getMediaDirectory() + mSystemName + "/" + subdirectory +
subFolders + "/" + getDisplayName(); subFolders + "/" + getDisplayName();
@ -253,31 +251,37 @@ const std::string FileData::getImagePath() const
const std::string FileData::get3DBoxPath() const const std::string FileData::get3DBoxPath() const
{ {
// Return path to the 3D box image.
return getMediafilePath("3dboxes", "3dbox"); return getMediafilePath("3dboxes", "3dbox");
} }
const std::string FileData::getCoverPath() const const std::string FileData::getCoverPath() const
{ {
// Return path to the cover image.
return getMediafilePath("covers", "cover"); return getMediafilePath("covers", "cover");
} }
const std::string FileData::getMarqueePath() const const std::string FileData::getMarqueePath() const
{ {
// Return path to the marquee image.
return getMediafilePath("marquees", "marquee"); return getMediafilePath("marquees", "marquee");
} }
const std::string FileData::getMiximagePath() const const std::string FileData::getMiximagePath() const
{ {
// Return path to the miximage.
return getMediafilePath("miximages", "miximage"); return getMediafilePath("miximages", "miximage");
} }
const std::string FileData::getScreenshotPath() const const std::string FileData::getScreenshotPath() const
{ {
// Return path to the screenshot image.
return getMediafilePath("screenshots", "screenshot"); return getMediafilePath("screenshots", "screenshot");
} }
const std::string FileData::getThumbnailPath() const const std::string FileData::getThumbnailPath() const
{ {
// Return path to the thumbnail image.
return getMediafilePath("thumbnails", "thumbnail"); return getMediafilePath("thumbnails", "thumbnail");
} }
@ -288,8 +292,8 @@ const std::string FileData::getVideoPath() const
// Extract possible subfolders from the path. // Extract possible subfolders from the path.
if (mEnvData->mStartPath != "") if (mEnvData->mStartPath != "")
subFolders = Utils::String::replace( subFolders =
Utils::FileSystem::getParent(mPath), mEnvData->mStartPath, ""); Utils::String::replace(Utils::FileSystem::getParent(mPath), mEnvData->mStartPath, "");
const std::string tempPath = const std::string tempPath =
getMediaDirectory() + mSystemName + "/videos" + subFolders + "/" + getDisplayName(); getMediaDirectory() + mSystemName + "/videos" + subFolders + "/" + getDisplayName();
@ -322,7 +326,8 @@ const std::vector<FileData*>& FileData::getChildrenListToDisplay()
} }
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask, std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask,
bool displayedOnly, bool countAllGames) const bool displayedOnly,
bool countAllGames) const
{ {
std::vector<FileData*> out; std::vector<FileData*> out;
FileFilterIndex* idx = mSystem->getIndex(); FileFilterIndex* idx = mSystem->getIndex();
@ -354,7 +359,8 @@ std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask,
} }
std::vector<FileData*> FileData::getScrapeFilesRecursive(bool includeFolders, std::vector<FileData*> FileData::getScrapeFilesRecursive(bool includeFolders,
bool excludeRecursively, bool respectExclusions) const bool excludeRecursively,
bool respectExclusions) const
{ {
std::vector<FileData*> out; std::vector<FileData*> out;
@ -383,17 +389,14 @@ std::vector<FileData*> FileData::getScrapeFilesRecursive(bool includeFolders,
return out; return out;
} }
std::string FileData::getKey() { std::string FileData::getKey() { return getFileName(); }
return getFileName();
}
const bool FileData::isArcadeAsset() const bool FileData::isArcadeAsset()
{ {
const std::string stem = Utils::FileSystem::getStem(mPath); const std::string stem = Utils::FileSystem::getStem(mPath);
return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) || return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) && mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) &&
(MameNames::getInstance()->isBios(stem) || (MameNames::getInstance()->isBios(stem) || MameNames::getInstance()->isDevice(stem)));
MameNames::getInstance()->isDevice(stem)));
} }
const bool FileData::isArcadeGame() const bool FileData::isArcadeGame()
@ -401,14 +404,10 @@ const bool FileData::isArcadeGame()
const std::string stem = Utils::FileSystem::getStem(mPath); const std::string stem = Utils::FileSystem::getStem(mPath);
return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) || return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) && mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) &&
(!MameNames::getInstance()->isBios(stem) && (!MameNames::getInstance()->isBios(stem) && !MameNames::getInstance()->isDevice(stem)));
!MameNames::getInstance()->isDevice(stem)));
} }
FileData* FileData::getSourceFileData() FileData* FileData::getSourceFileData() { return this; }
{
return this;
}
void FileData::addChild(FileData* file) void FileData::addChild(FileData* file)
{ {
@ -657,16 +656,16 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator,
// Sort favorite games and the other games separately. // Sort favorite games and the other games separately.
if (foldersOnTop && mOnlyFolders) { if (foldersOnTop && mOnlyFolders) {
std::stable_sort(mChildrenFavoritesFolders.begin(), std::stable_sort(mChildrenFavoritesFolders.begin(), mChildrenFavoritesFolders.end(),
mChildrenFavoritesFolders.end(), comparator); comparator);
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator); std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
} }
std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator); std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator);
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator); std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
// Iterate through any child favorite folders. // Iterate through any child favorite folders.
for (auto it = mChildrenFavoritesFolders.cbegin(); it != for (auto it = mChildrenFavoritesFolders.cbegin(); // Line break.
mChildrenFavoritesFolders.cend(); it++) { it != mChildrenFavoritesFolders.cend(); it++) {
if ((*it)->getChildren().size() > 0) if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, gameCount); (*it)->sortFavoritesOnTop(comparator, gameCount);
} }
@ -731,7 +730,8 @@ void FileData::countGames(std::pair<unsigned int, unsigned int>& gameCount)
mGameCount = gameCount; mGameCount = gameCount;
} }
FileData::SortType FileData::getSortTypeFromString(std::string desc) { FileData::SortType FileData::getSortTypeFromString(std::string desc)
{
std::vector<FileData::SortType> SortTypes = FileSorts::SortTypes; std::vector<FileData::SortType> SortTypes = FileSorts::SortTypes;
for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) { for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) {
@ -752,10 +752,12 @@ void FileData::launchGame(Window* window)
// Check if there is a launch command override for the game // Check if there is a launch command override for the game
// and the corresponding option to use it has been set. // and the corresponding option to use it has been set.
if (Settings::getInstance()->getBool("LaunchCommandOverride") && if (Settings::getInstance()->getBool("LaunchCommandOverride") &&
!metadata.get("launchcommand").empty()) !metadata.get("launchcommand").empty()) {
command = metadata.get("launchcommand"); command = metadata.get("launchcommand");
else }
else {
command = mEnvData->mLaunchCommand; command = mEnvData->mLaunchCommand;
}
std::string commandRaw = command; std::string commandRaw = command;
@ -794,14 +796,14 @@ void FileData::launchGame(Window* window)
// Hack to show an error message if there was no emulator entry in es_find_rules.xml. // Hack to show an error message if there was no emulator entry in es_find_rules.xml.
if (binaryPath.substr(0, 18) == "NO EMULATOR RULE: ") { if (binaryPath.substr(0, 18) == "NO EMULATOR RULE: ") {
std::string emulatorEntry = binaryPath.substr(18, binaryPath.size() - 18); std::string emulatorEntry = binaryPath.substr(18, binaryPath.size() - 18);
LOG(LogError) << "Couldn't launch game, either there is no emulator entry for \"" << LOG(LogError)
emulatorEntry << "\" in es_find_rules.xml or there are no systempath or staticpath " << "Couldn't launch game, either there is no emulator entry for \"" << emulatorEntry
"rules defined"; << "\" in es_find_rules.xml or there are no systempath or staticpath rules defined";
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: MISSING EMULATOR CONFIGURATION FOR '" + GuiInfoPopup* s = new GuiInfoPopup(
emulatorEntry + "'", 6000); window, "ERROR: MISSING EMULATOR CONFIGURATION FOR '" + emulatorEntry + "'", 6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
@ -810,19 +812,22 @@ void FileData::launchGame(Window* window)
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: COULDN'T FIND EMULATOR, HAS IT " \ GuiInfoPopup* s = new GuiInfoPopup(window,
"BEEN PROPERLY INSTALLED?", 6000); "ERROR: COULDN'T FIND EMULATOR, HAS IT "
"BEEN PROPERLY INSTALLED?",
6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
else { else {
#if defined(_WIN64) #if defined(_WIN64)
LOG(LogDebug) << "FileData::launchGame(): Found emulator binary \"" << LOG(LogDebug) << "FileData::launchGame(): Found emulator binary \""
Utils::String::replace(Utils::String::replace( << Utils::String::replace(
binaryPath, "%ESPATH%", esPath), "/", "\\") << "\""; Utils::String::replace(binaryPath, "%ESPATH%", esPath), "/", "\\")
<< "\"";
#else #else
LOG(LogDebug) << "FileData::launchGame(): Found emulator binary \"" << LOG(LogDebug) << "FileData::launchGame(): Found emulator binary \""
Utils::String::replace(binaryPath, "%ESPATH%", esPath) << "\""; << Utils::String::replace(binaryPath, "%ESPATH%", esPath) << "\"";
#endif #endif
} }
@ -834,8 +839,8 @@ void FileData::launchGame(Window* window)
unsigned int quotationMarkPos = 0; unsigned int quotationMarkPos = 0;
if (command.find("\"%EMUPATH%", emuPathPos - 1) != std::string::npos) { if (command.find("\"%EMUPATH%", emuPathPos - 1) != std::string::npos) {
hasQuotationMark = true; hasQuotationMark = true;
quotationMarkPos = static_cast<unsigned int>( quotationMarkPos =
command.find("\"", emuPathPos + 9) - emuPathPos); static_cast<unsigned int>(command.find("\"", emuPathPos + 9) - emuPathPos);
} }
size_t spacePos = command.find(" ", emuPathPos + quotationMarkPos); size_t spacePos = command.find(" ", emuPathPos + quotationMarkPos);
std::string coreRaw; std::string coreRaw;
@ -850,15 +855,16 @@ void FileData::launchGame(Window* window)
} }
if (!Utils::FileSystem::isRegularFile(coreFile) && if (!Utils::FileSystem::isRegularFile(coreFile) &&
!Utils::FileSystem::isSymlink(coreFile)) { !Utils::FileSystem::isSymlink(coreFile)) {
LOG(LogError) << "Couldn't launch game, emulator core file \"" << LOG(LogError) << "Couldn't launch game, emulator core file \""
Utils::FileSystem::getFileName(coreFile) << "\" not found"; << Utils::FileSystem::getFileName(coreFile) << "\" not found";
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
GuiInfoPopup* s = new GuiInfoPopup(window, GuiInfoPopup* s = new GuiInfoPopup(
window,
"ERROR: COULDN'T FIND EMULATOR CORE FILE '" + "ERROR: COULDN'T FIND EMULATOR CORE FILE '" +
Utils::String::toUpper(Utils::FileSystem::getFileName(coreFile)) + Utils::String::toUpper(Utils::FileSystem::getFileName(coreFile)) + "'",
"'", 6000); 6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
@ -877,8 +883,10 @@ void FileData::launchGame(Window* window)
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: INVALID ENTRY IN SYSTEMS " \ GuiInfoPopup* s = new GuiInfoPopup(window,
"CONFIGURATION FILE", 6000); "ERROR: INVALID ENTRY IN SYSTEMS "
"CONFIGURATION FILE",
6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
@ -886,13 +894,13 @@ void FileData::launchGame(Window* window)
// Error handling in case of no core find rule. // Error handling in case of no core find rule.
if (coreEntry != "" && emulatorCorePaths.empty()) { if (coreEntry != "" && emulatorCorePaths.empty()) {
LOG(LogError) << "Couldn't launch game, either there is no core entry for \"" << LOG(LogError) << "Couldn't launch game, either there is no core entry for \"" << coreEntry
coreEntry << "\" in es_find_rules.xml or there are no corepath rules defined"; << "\" in es_find_rules.xml or there are no corepath rules defined";
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: MISSING CORE CONFIGURATION FOR '" + GuiInfoPopup* s = new GuiInfoPopup(
coreEntry + "'", 6000); window, "ERROR: MISSING CORE CONFIGURATION FOR '" + coreEntry + "'", 6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
@ -925,8 +933,9 @@ void FileData::launchGame(Window* window)
size_t stringPos = coreFile.find("%EMUPATH%"); size_t stringPos = coreFile.find("%EMUPATH%");
if (stringPos != std::string::npos) { if (stringPos != std::string::npos) {
#if defined(_WIN64) #if defined(_WIN64)
coreFile = Utils::String::replace(coreFile.replace(stringPos, 9, coreFile = Utils::String::replace(
Utils::FileSystem::getParent(binaryPath)), "/", "\\"); coreFile.replace(stringPos, 9, Utils::FileSystem::getParent(binaryPath)), "/",
"\\");
#else #else
coreFile = coreFile.replace(stringPos, 9, Utils::FileSystem::getParent(binaryPath)); coreFile = coreFile.replace(stringPos, 9, Utils::FileSystem::getParent(binaryPath));
#endif #endif
@ -961,23 +970,28 @@ void FileData::launchGame(Window* window)
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: INVALID ENTRY IN SYSTEMS " \ GuiInfoPopup* s = new GuiInfoPopup(window,
"CONFIGURATION FILE", 6000); "ERROR: INVALID ENTRY IN SYSTEMS "
"CONFIGURATION FILE",
6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
} }
if (!foundCoreFile && coreName.size() > 0) { if (!foundCoreFile && coreName.size() > 0) {
LOG(LogError) << "Couldn't launch game, emulator core file \"" << LOG(LogError) << "Couldn't launch game, emulator core file \""
coreName.substr(0, coreName.size()) << "\" not found"; << coreName.substr(0, coreName.size()) << "\" not found";
LOG(LogError) << "Raw emulator launch command:"; LOG(LogError) << "Raw emulator launch command:";
LOG(LogError) << commandRaw; LOG(LogError) << commandRaw;
LOG(LogError) << LOG(LogError)
"Tried to find the core file using these paths as defined by es_find_rules.xml:"; << "Tried to find the core file using these paths as defined by es_find_rules.xml:";
LOG(LogError) << Utils::String::vectorToDelimitedString(emulatorCorePaths, ", "); LOG(LogError) << Utils::String::vectorToDelimitedString(emulatorCorePaths, ", ");
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR: COULDN'T FIND EMULATOR CORE FILE '" + GuiInfoPopup* s =
Utils::String::toUpper(coreName.substr(0, coreName.size()) + "'"), 6000); new GuiInfoPopup(window,
"ERROR: COULDN'T FIND EMULATOR CORE FILE '" +
Utils::String::toUpper(coreName.substr(0, coreName.size()) + "'"),
6000);
window->setInfoPopup(s); window->setInfoPopup(s);
return; return;
} }
@ -990,6 +1004,7 @@ void FileData::launchGame(Window* window)
// swapBuffers() is called here to turn the screen black to eliminate some potential // swapBuffers() is called here to turn the screen black to eliminate some potential
// flickering and to avoid showing the game launch message briefly when returning // flickering and to avoid showing the game launch message briefly when returning
// from the game. // from the game.
#if defined(_WIN64) #if defined(_WIN64)
if (!(Settings::getInstance()->getBool("LaunchWorkaround") || if (!(Settings::getInstance()->getBool("LaunchWorkaround") ||
ViewController::get()->runInBackground(mSystem))) ViewController::get()->runInBackground(mSystem)))
@ -1004,9 +1019,10 @@ void FileData::launchGame(Window* window)
LOG(LogDebug) << "Raw emulator launch command:"; LOG(LogDebug) << "Raw emulator launch command:";
LOG(LogDebug) << commandRaw; LOG(LogDebug) << commandRaw;
LOG(LogInfo) << "Expanded emulator launch command:"; LOG(LogInfo) << "Expanded emulator launch command:";
LOG(LogInfo) << command; LOG(LogInfo) << command;
// Possibly keep ES-DE running in the background while the game is launched. // Possibly keep ES-DE running in the background while the game is launched.
#if defined(_WIN64) #if defined(_WIN64)
returnValue = launchGameWindows(Utils::String::stringToWideString(command), returnValue = launchGameWindows(Utils::String::stringToWideString(command),
ViewController::get()->runInBackground(mSystem)); ViewController::get()->runInBackground(mSystem));
@ -1017,9 +1033,11 @@ void FileData::launchGame(Window* window)
if (returnValue != 0) { if (returnValue != 0) {
LOG(LogWarning) << "...launch terminated with nonzero return value " << returnValue; LOG(LogWarning) << "...launch terminated with nonzero return value " << returnValue;
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR LAUNCHING GAME '" + GuiInfoPopup* s = new GuiInfoPopup(
Utils::String::toUpper(metadata.get("name")) + "' (ERROR CODE " + window,
Utils::String::toUpper(std::to_string(returnValue) + ")"), 6000); "ERROR LAUNCHING GAME '" + Utils::String::toUpper(metadata.get("name")) +
"' (ERROR CODE " + Utils::String::toUpper(std::to_string(returnValue) + ")"),
6000);
window->setInfoPopup(s); window->setInfoPopup(s);
} }
else { else {
@ -1062,8 +1080,8 @@ void FileData::launchGame(Window* window)
// If the parent is a folder and it's not the root of the system, then update its lastplayed // If the parent is a folder and it's not the root of the system, then update its lastplayed
// timestamp to the same time as the game that was just launched. // timestamp to the same time as the game that was just launched.
if (gameToUpdate->getParent()->getType() == FOLDER && gameToUpdate->getParent()->getName() != if (gameToUpdate->getParent()->getType() == FOLDER &&
gameToUpdate->getSystem()->getFullName()) { gameToUpdate->getParent()->getName() != gameToUpdate->getSystem()->getFullName()) {
gameToUpdate->getParent()->metadata.set("lastplayed", gameToUpdate->getParent()->metadata.set("lastplayed",
gameToUpdate->metadata.get("lastplayed")); gameToUpdate->metadata.get("lastplayed"));
} }
@ -1126,33 +1144,19 @@ std::string FileData::findEmulatorPath(std::string& command)
DWORD pathSize = 1024; DWORD pathSize = 1024;
// First look in HKEY_CURRENT_USER. // First look in HKEY_CURRENT_USER.
keyStatus = RegOpenKeyEx( keyStatus = RegOpenKeyEx(HKEY_CURRENT_USER, registryKeyPath.c_str(), 0, KEY_QUERY_VALUE,
HKEY_CURRENT_USER,
registryKeyPath.c_str(),
0,
KEY_QUERY_VALUE,
&registryKey); &registryKey);
// If not found, then try in HKEY_LOCAL_MACHINE. // If not found, then try in HKEY_LOCAL_MACHINE.
if (keyStatus != ERROR_SUCCESS) { if (keyStatus != ERROR_SUCCESS) {
keyStatus = RegOpenKeyEx( keyStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registryKeyPath.c_str(), 0,
HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE, &registryKey);
registryKeyPath.c_str(),
0,
KEY_QUERY_VALUE,
&registryKey);
} }
// If the key exists, then try to retrieve the value. // If the key exists, then try to retrieve the value.
if (keyStatus == ERROR_SUCCESS) { if (keyStatus == ERROR_SUCCESS) {
pathStatus = RegGetValue( pathStatus = RegGetValue(registryKey, nullptr, nullptr, RRF_RT_REG_SZ, nullptr,
registryKey, &registryPath, &pathSize);
nullptr,
nullptr,
RRF_RT_REG_SZ,
nullptr,
&registryPath,
&pathSize);
} }
else { else {
RegCloseKey(registryKey); RegCloseKey(registryKey);
@ -1183,13 +1187,12 @@ std::string FileData::findEmulatorPath(std::string& command)
std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1); std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1);
wchar_t* fileName = nullptr; wchar_t* fileName = nullptr;
SearchPathW(nullptr, pathWide.c_str(), L".exe", size + 1 , SearchPathW(nullptr, pathWide.c_str(), L".exe", size + 1, pathBuffer.data(), &fileName);
pathBuffer.data(), &fileName);
std::wstring pathString = pathBuffer.data(); std::wstring pathString = pathBuffer.data();
if (pathString.length()) { if (pathString.length()) {
exePath = Utils::String::wideStringToString(pathString.substr(0, exePath = Utils::String::wideStringToString(
pathString.size() - std::wstring(fileName).size())); pathString.substr(0, pathString.size() - std::wstring(fileName).size()));
exePath.pop_back(); exePath.pop_back();
} }
} }
@ -1212,11 +1215,12 @@ std::string FileData::findEmulatorPath(std::string& command)
path = Utils::FileSystem::expandHomePath(path); path = Utils::FileSystem::expandHomePath(path);
// If %ESPATH% is used for the rule, then expand it to the binary directory of ES-DE. // If %ESPATH% is used for the rule, then expand it to the binary directory of ES-DE.
path = Utils::String::replace(path, "%ESPATH%", Utils::FileSystem::getExePath()); path = Utils::String::replace(path, "%ESPATH%", Utils::FileSystem::getExePath());
// Likewise for the %ROMPATH% variable which expands to the configured ROM directory.
path = Utils::String::replace(path, "%ROMPATH%", getROMDirectory());
#if defined(_WIN64) #if defined(_WIN64)
path = Utils::String::replace(path, "/", "\\"); path = Utils::String::replace(path, "/", "\\");
#endif #endif
if (Utils::FileSystem::isRegularFile(path) || if (Utils::FileSystem::isRegularFile(path) || Utils::FileSystem::isSymlink(path)) {
Utils::FileSystem::isSymlink(path)) {
command.replace(0, endPos + 1, path); command.replace(0, endPos + 1, path);
return path; return path;
} }
@ -1249,8 +1253,8 @@ std::string FileData::findEmulatorPath(std::string& command)
std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1); std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1);
wchar_t* fileName = nullptr; wchar_t* fileName = nullptr;
SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", size + 1 , SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", size + 1, pathBuffer.data(),
pathBuffer.data(), &fileName); &fileName);
exePath = Utils::String::wideStringToString(pathBuffer.data()); exePath = Utils::String::wideStringToString(pathBuffer.data());
} }
@ -1270,8 +1274,10 @@ std::string FileData::findEmulatorPath(std::string& command)
} }
CollectionFileData::CollectionFileData(FileData* file, SystemData* system) CollectionFileData::CollectionFileData(FileData* file, SystemData* system)
: FileData(file->getSourceFileData()->getType(), file->getSourceFileData()->getPath(), : FileData(file->getSourceFileData()->getType(),
file->getSourceFileData()->getSystemEnvData(), system) file->getSourceFileData()->getPath(),
file->getSourceFileData()->getSystemEnvData(),
system)
{ {
// We use this constructor to create a clone of the filedata, and change its system. // We use this constructor to create a clone of the filedata, and change its system.
mSourceFileData = file->getSourceFileData(); mSourceFileData = file->getSourceFileData();
@ -1289,15 +1295,6 @@ CollectionFileData::~CollectionFileData()
mParent = nullptr; mParent = nullptr;
} }
std::string CollectionFileData::getKey() {
return getFullPath();
}
FileData* CollectionFileData::getSourceFileData()
{
return mSourceFileData;
}
void CollectionFileData::refreshMetadata() void CollectionFileData::refreshMetadata()
{ {
metadata = mSourceFileData->metadata; metadata = mSourceFileData->metadata;

View file

@ -11,8 +11,8 @@
#ifndef ES_APP_FILE_DATA_H #ifndef ES_APP_FILE_DATA_H
#define ES_APP_FILE_DATA_H #define ES_APP_FILE_DATA_H
#include "utils/FileSystemUtil.h"
#include "MetaData.h" #include "MetaData.h"
#include "utils/FileSystemUtil.h"
#include <unordered_map> #include <unordered_map>
@ -30,8 +30,10 @@ enum FileType {
class FileData class FileData
{ {
public: public:
FileData(FileType type, const std::string& path, FileData(FileType type,
SystemEnvironmentData* envData, SystemData* system); const std::string& path,
SystemEnvironmentData* envData,
SystemData* system);
virtual ~FileData(); virtual ~FileData();
@ -41,17 +43,19 @@ public:
const bool getKidgame(); const bool getKidgame();
const bool getHidden(); const bool getHidden();
const bool getCountAsGame(); const bool getCountAsGame();
const std::pair<unsigned int, unsigned int> getGameCount() { return mGameCount; }; const std::pair<unsigned int, unsigned int> getGameCount() { return mGameCount; }
const bool getExcludeFromScraper(); const bool getExcludeFromScraper();
const std::vector<FileData*> getChildrenRecursive() const; const std::vector<FileData*> getChildrenRecursive() const;
inline FileType getType() const { return mType; } FileType getType() const { return mType; }
inline const std::string& getPath() const { return mPath; } const std::string& getPath() const { return mPath; }
inline FileData* getParent() const { return mParent; } FileData* getParent() const { return mParent; }
inline const std::unordered_map<std::string, FileData*>& getChildrenByFilename() const const std::unordered_map<std::string, FileData*>& getChildrenByFilename() const
{ return mChildrenByFilename; } {
inline const std::vector<FileData*>& getChildren() const { return mChildren; } return mChildrenByFilename;
inline SystemData* getSystem() const { return mSystem; } }
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } const std::vector<FileData*>& getChildren() const { return mChildren; }
SystemData* getSystem() const { return mSystem; }
SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
const bool getOnlyFoldersFlag() { return mOnlyFolders; } const bool getOnlyFoldersFlag() { return mOnlyFolders; }
const bool getHasFoldersFlag() { return mHasFolders; } const bool getHasFoldersFlag() { return mHasFolders; }
static const std::string getROMDirectory(); static const std::string getROMDirectory();
@ -66,29 +70,31 @@ public:
const std::string getThumbnailPath() const; const std::string getThumbnailPath() const;
const std::string getVideoPath() const; const std::string getVideoPath() const;
bool getDeletionFlag() { return mDeletionFlag; }; bool getDeletionFlag() { return mDeletionFlag; }
void setDeletionFlag(bool setting) { mDeletionFlag = setting; }; void setDeletionFlag(bool setting) { mDeletionFlag = setting; }
const std::vector<FileData*>& getChildrenListToDisplay(); const std::vector<FileData*>& getChildrenListToDisplay();
std::vector<FileData*> getFilesRecursive(unsigned int typeMask, std::vector<FileData*> getFilesRecursive(unsigned int typeMask,
bool displayedOnly = false, bool countAllGames = true) const; bool displayedOnly = false,
std::vector<FileData*> getScrapeFilesRecursive(bool includeFolders, bool excludeRecursively, bool countAllGames = true) const;
std::vector<FileData*> getScrapeFilesRecursive(bool includeFolders,
bool excludeRecursively,
bool respectExclusions) const; bool respectExclusions) const;
void addChild(FileData* file); // Error if mType != FOLDER void addChild(FileData* file); // Error if mType != FOLDER
void removeChild(FileData* file); // Error if mType != FOLDER void removeChild(FileData* file); // Error if mType != FOLDER
inline bool isPlaceHolder() { return mType == PLACEHOLDER; }; bool isPlaceHolder() { return mType == PLACEHOLDER; }
virtual inline void refreshMetadata() { return; }; virtual void refreshMetadata() { return; }
virtual std::string getKey(); virtual std::string getKey();
const bool isArcadeAsset(); const bool isArcadeAsset();
const bool isArcadeGame(); const bool isArcadeGame();
inline std::string getFullPath() { return getPath(); }; std::string getFullPath() { return getPath(); }
inline std::string getFileName() { return Utils::FileSystem::getFileName(getPath()); }; std::string getFileName() { return Utils::FileSystem::getFileName(getPath()); }
virtual FileData* getSourceFileData(); virtual FileData* getSourceFileData();
inline std::string getSystemName() const { return mSystemName; }; std::string getSystemName() const { return mSystemName; }
// Returns our best guess at the "real" name for this file. // Returns our best guess at the "real" name for this file.
std::string getDisplayName() const; std::string getDisplayName() const;
@ -104,7 +110,10 @@ public:
ComparisonFunction* comparisonFunction; ComparisonFunction* comparisonFunction;
std::string description; std::string description;
SortType(ComparisonFunction* sortFunction, const std::string& sortDescription) SortType(ComparisonFunction* sortFunction, const std::string& sortDescription)
: comparisonFunction(sortFunction), description(sortDescription) {} : comparisonFunction(sortFunction)
, description(sortDescription)
{
}
}; };
void sort(ComparisonFunction& comparator, std::pair<unsigned int, unsigned int>& gameCount); void sort(ComparisonFunction& comparator, std::pair<unsigned int, unsigned int>& gameCount);
@ -115,8 +124,8 @@ public:
// Only count the games, a cheaper alternative to a full sort when that is not required. // Only count the games, a cheaper alternative to a full sort when that is not required.
void countGames(std::pair<unsigned int, unsigned int>& gameCount); void countGames(std::pair<unsigned int, unsigned int>& gameCount);
inline void setSortTypeString(std::string typestring) { mSortTypeString = typestring; } void setSortTypeString(std::string typestring) { mSortTypeString = typestring; }
inline std::string getSortTypeString() { return mSortTypeString; } std::string getSortTypeString() { return mSortTypeString; }
FileData::SortType getSortTypeFromString(std::string desc); FileData::SortType getSortTypeFromString(std::string desc);
protected: protected:
@ -148,8 +157,8 @@ public:
~CollectionFileData(); ~CollectionFileData();
const std::string& getName(); const std::string& getName();
void refreshMetadata(); void refreshMetadata();
FileData* getSourceFileData(); FileData* getSourceFileData() { return mSourceFileData; }
std::string getKey(); std::string getKey() { return getFullPath(); }
private: private:
// Needs to be updated when metadata changes. // Needs to be updated when metadata changes.

View file

@ -8,12 +8,12 @@
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "math/Misc.h"
#include "utils/StringUtil.h"
#include "views/UIModeController.h"
#include "FileData.h" #include "FileData.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "math/Misc.h"
#include "utils/StringUtil.h"
#include "views/UIModeController.h"
#include <cmath> #include <cmath>
@ -21,19 +21,20 @@
#define INCLUDE_UNKNOWN false; #define INCLUDE_UNKNOWN false;
FileFilterIndex::FileFilterIndex() FileFilterIndex::FileFilterIndex()
: mFilterByText(false), : mFilterByText(false)
mFilterByFavorites(false), , mFilterByFavorites(false)
mFilterByGenre(false), , mFilterByGenre(false)
mFilterByPlayers(false), , mFilterByPlayers(false)
mFilterByPubDev(false), , mFilterByPubDev(false)
mFilterByRatings(false), , mFilterByRatings(false)
mFilterByKidGame(false), , mFilterByKidGame(false)
mFilterByCompleted(false), , mFilterByCompleted(false)
mFilterByBroken(false), , mFilterByBroken(false)
mFilterByHidden(false) , mFilterByHidden(false)
{ {
clearAllFilters(); clearAllFilters();
// clang-format off
FilterDataDecl filterDecls[] = { FilterDataDecl filterDecls[] = {
//type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel //type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel
{ FAVORITES_FILTER, &mFavoritesIndexAllKeys, &mFilterByFavorites, &mFavoritesIndexFilteredKeys, "favorite", false, "", "FAVORITES" }, { FAVORITES_FILTER, &mFavoritesIndexAllKeys, &mFilterByFavorites, &mFavoritesIndexFilteredKeys, "favorite", false, "", "FAVORITES" },
@ -46,21 +47,18 @@ FileFilterIndex::FileFilterIndex()
{ BROKEN_FILTER, &mBrokenIndexAllKeys, &mFilterByBroken, &mBrokenIndexFilteredKeys, "broken", false, "", "BROKEN" }, { BROKEN_FILTER, &mBrokenIndexAllKeys, &mFilterByBroken, &mBrokenIndexFilteredKeys, "broken", false, "", "BROKEN" },
{ HIDDEN_FILTER, &mHiddenIndexAllKeys, &mFilterByHidden, &mHiddenIndexFilteredKeys, "hidden", false, "", "HIDDEN" } { HIDDEN_FILTER, &mHiddenIndexAllKeys, &mFilterByHidden, &mHiddenIndexFilteredKeys, "hidden", false, "", "HIDDEN" }
}; };
// clang-format on
filterDataDecl = std::vector<FilterDataDecl>(filterDecls, filterDecls + filterDataDecl = std::vector<FilterDataDecl>(
sizeof(filterDecls) / sizeof(filterDecls[0])); filterDecls, filterDecls + sizeof(filterDecls) / sizeof(filterDecls[0]));
} }
FileFilterIndex::~FileFilterIndex() FileFilterIndex::~FileFilterIndex()
{ {
// Reset the index when destroyed.
resetIndex(); resetIndex();
} }
std::vector<FilterDataDecl>& FileFilterIndex::getFilterDataDecls()
{
return filterDataDecl;
}
void FileFilterIndex::importIndex(FileFilterIndex* indexToImport) void FileFilterIndex::importIndex(FileFilterIndex* indexToImport)
{ {
struct IndexImportStructure { struct IndexImportStructure {
@ -80,25 +78,26 @@ void FileFilterIndex::importIndex(FileFilterIndex* indexToImport)
{ &mHiddenIndexAllKeys, &(indexToImport->mHiddenIndexAllKeys) }, { &mHiddenIndexAllKeys, &(indexToImport->mHiddenIndexAllKeys) },
}; };
std::vector<IndexImportStructure> indexImportDecl = std::vector<IndexImportStructure> indexImportDecl = std::vector<IndexImportStructure>(
std::vector<IndexImportStructure>(indexStructDecls, indexStructDecls + indexStructDecls,
sizeof(indexStructDecls) / sizeof(indexStructDecls[0])); indexStructDecls + sizeof(indexStructDecls) / sizeof(indexStructDecls[0]));
for (std::vector<IndexImportStructure>::const_iterator indexesIt = for (std::vector<IndexImportStructure>::const_iterator indexesIt = indexImportDecl.cbegin();
indexImportDecl.cbegin(); indexesIt != indexImportDecl.cend(); indexesIt++) indexesIt != indexImportDecl.cend(); indexesIt++) {
{
for (std::map<std::string, int>::const_iterator sourceIt = for (std::map<std::string, int>::const_iterator sourceIt =
(*indexesIt).sourceIndex->cbegin(); sourceIt != (*indexesIt).sourceIndex->cbegin();
(*indexesIt).sourceIndex->cend(); sourceIt++) { sourceIt != (*indexesIt).sourceIndex->cend(); sourceIt++) {
if ((*indexesIt).destinationIndex->find((*sourceIt).first) == if ((*indexesIt).destinationIndex->find((*sourceIt).first) ==
(*indexesIt).destinationIndex->cend()) (*indexesIt).destinationIndex->cend()) {
// Entry doesn't exist. // Entry doesn't exist.
(*((*indexesIt).destinationIndex))[(*sourceIt).first] = (*sourceIt).second; (*((*indexesIt).destinationIndex))[(*sourceIt).first] = (*sourceIt).second;
else }
else {
(*((*indexesIt).destinationIndex))[(*sourceIt).first] += (*sourceIt).second; (*((*indexesIt).destinationIndex))[(*sourceIt).first] += (*sourceIt).second;
} }
} }
} }
}
void FileFilterIndex::resetIndex() void FileFilterIndex::resetIndex()
{ {
@ -115,7 +114,8 @@ void FileFilterIndex::resetIndex()
} }
std::string FileFilterIndex::getIndexableKey(FileData* game, std::string FileFilterIndex::getIndexableKey(FileData* game,
FilterIndexType type, bool getSecondary) FilterIndexType type,
bool getSecondary)
{ {
std::string key = ""; std::string key = "";
switch (type) { switch (type) {
@ -166,8 +166,8 @@ std::string FileFilterIndex::getIndexableKey(FileData* game,
// These values should only exist if a third party application has // These values should only exist if a third party application has
// been used for scraping the ratings, or if the gamelist.xml file // been used for scraping the ratings, or if the gamelist.xml file
// has been manually edited. // has been manually edited.
ratingNumber = static_cast<int>( ratingNumber =
(ceilf(stof(ratingString) / 0.1f) / 10) * 5); static_cast<int>((ceilf(stof(ratingString) / 0.1f) / 10.0f) * 5.0f);
if (ratingNumber < 0) if (ratingNumber < 0)
ratingNumber = 0; ratingNumber = 0;
@ -179,8 +179,8 @@ std::string FileFilterIndex::getIndexableKey(FileData* game,
std::to_string(ratingNumber) + ".5 STARS"; std::to_string(ratingNumber) + ".5 STARS";
} }
catch (int e) { catch (int e) {
LOG(LogError) << "Error parsing Rating (invalid value, exception nr.): " << LOG(LogError) << "Error parsing Rating (invalid value, exception nr.): "
ratingString << ", " << e; << ratingString << ", " << e;
} }
} }
} }
@ -259,8 +259,8 @@ void FileFilterIndex::setFilter(FilterIndexType type, std::vector<std::string>*
FilterDataDecl filterData = (*it); FilterDataDecl filterData = (*it);
*(filterData.filteredByRef) = values->size() > 0; *(filterData.filteredByRef) = values->size() > 0;
filterData.currentFilteredKeys->clear(); filterData.currentFilteredKeys->clear();
for (std::vector<std::string>::const_iterator vit = for (std::vector<std::string>::const_iterator vit = values->cbegin();
values->cbegin(); vit != values->cend(); vit++) { vit != values->cend(); vit++) {
// Check if it exists. // Check if it exists.
if (filterData.allIndexKeys->find(*vit) != filterData.allIndexKeys->cend()) { if (filterData.allIndexKeys->find(*vit) != filterData.allIndexKeys->cend()) {
filterData.currentFilteredKeys->push_back(std::string(*vit)); filterData.currentFilteredKeys->push_back(std::string(*vit));
@ -348,8 +348,8 @@ bool FileFilterIndex::showFile(FileData* game)
if (game->getType() == FOLDER) { if (game->getType() == FOLDER) {
std::vector<FileData*> children = game->getChildren(); std::vector<FileData*> children = game->getChildren();
// Iterate through all of the children, until there's a match. // Iterate through all of the children, until there's a match.
for (std::vector<FileData*>::const_iterator it = children.cbegin(); for (std::vector<FileData*>::const_iterator it = children.cbegin(); it != children.cend();
it != children.cend(); it++) { it++) {
if (showFile(*it)) if (showFile(*it))
return true; return true;
} }
@ -361,8 +361,8 @@ bool FileFilterIndex::showFile(FileData* game)
// Name filters take precedence over all other filters, so if there is no match for // Name filters take precedence over all other filters, so if there is no match for
// the game name, then always return false. // the game name, then always return false.
if (mTextFilter != "" && !(Utils::String::toUpper(game-> if (mTextFilter != "" &&
getName()).find(mTextFilter) != std::string::npos)) { !(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) {
return false; return false;
} }
else if (mTextFilter != "") { else if (mTextFilter != "") {
@ -419,13 +419,14 @@ bool FileFilterIndex::isFiltered()
bool FileFilterIndex::isKeyBeingFilteredBy(std::string key, FilterIndexType type) bool FileFilterIndex::isKeyBeingFilteredBy(std::string key, FilterIndexType type)
{ {
const FilterIndexType filterTypes[9] = { FAVORITES_FILTER, GENRE_FILTER, const FilterIndexType filterTypes[9] = { FAVORITES_FILTER, GENRE_FILTER, PLAYER_FILTER,
PLAYER_FILTER, PUBDEV_FILTER, RATINGS_FILTER, KIDGAME_FILTER, PUBDEV_FILTER, RATINGS_FILTER, KIDGAME_FILTER,
COMPLETED_FILTER, BROKEN_FILTER, HIDDEN_FILTER }; COMPLETED_FILTER, BROKEN_FILTER, HIDDEN_FILTER };
std::vector<std::string> filterKeysList[9] = { mFavoritesIndexFilteredKeys, std::vector<std::string> filterKeysList[9] = {
mGenreIndexFilteredKeys, mPlayersIndexFilteredKeys, mPubDevIndexFilteredKeys, mFavoritesIndexFilteredKeys, mGenreIndexFilteredKeys, mPlayersIndexFilteredKeys,
mRatingsIndexFilteredKeys, mKidGameIndexFilteredKeys, mCompletedIndexFilteredKeys, mPubDevIndexFilteredKeys, mRatingsIndexFilteredKeys, mKidGameIndexFilteredKeys,
mBrokenIndexFilteredKeys, mHiddenIndexFilteredKeys }; mCompletedIndexFilteredKeys, mBrokenIndexFilteredKeys, mHiddenIndexFilteredKeys
};
for (int i = 0; i < 9; i++) { for (int i = 0; i < 9; i++) {
if (filterTypes[i] == type) { if (filterTypes[i] == type) {
@ -589,7 +590,8 @@ void FileFilterIndex::manageHiddenEntryInIndex(FileData* game, bool remove)
} }
void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index, void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index,
std::string key, bool remove) std::string key,
bool remove)
{ {
bool includeUnknown = INCLUDE_UNKNOWN; bool includeUnknown = INCLUDE_UNKNOWN;
if (!includeUnknown && key == UNKNOWN_LABEL) if (!includeUnknown && key == UNKNOWN_LABEL)
@ -617,8 +619,3 @@ void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index,
(index->at(key))++; (index->at(key))++;
} }
} }
void FileFilterIndex::clearIndex(std::map<std::string, int>& indexMap)
{
indexMap.clear();
}

View file

@ -52,13 +52,13 @@ public:
void removeFromIndex(FileData* game); void removeFromIndex(FileData* game);
void setFilter(FilterIndexType type, std::vector<std::string>* values); void setFilter(FilterIndexType type, std::vector<std::string>* values);
void setTextFilter(std::string textFilter); void setTextFilter(std::string textFilter);
std::string getTextFilter() { return mTextFilter; }; std::string getTextFilter() { return mTextFilter; }
void clearAllFilters(); void clearAllFilters();
void debugPrintIndexes(); void debugPrintIndexes();
bool showFile(FileData* game); bool showFile(FileData* game);
bool isFiltered(); bool isFiltered();
bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); bool isKeyBeingFilteredBy(std::string key, FilterIndexType type);
std::vector<FilterDataDecl>& getFilterDataDecls(); std::vector<FilterDataDecl>& getFilterDataDecls() { return filterDataDecl; }
void importIndex(FileFilterIndex* indexToImport); void importIndex(FileFilterIndex* indexToImport);
void resetIndex(); void resetIndex();
@ -81,7 +81,7 @@ private:
void manageIndexEntry(std::map<std::string, int>* index, std::string key, bool remove); void manageIndexEntry(std::map<std::string, int>* index, std::string key, bool remove);
void clearIndex(std::map<std::string, int>& indexMap); void clearIndex(std::map<std::string, int>& indexMap) { indexMap.clear(); }
std::string mTextFilter; std::string mTextFilter;
bool mFilterByText; bool mFilterByText;
@ -117,7 +117,6 @@ private:
std::vector<std::string> mHiddenIndexFilteredKeys; std::vector<std::string> mHiddenIndexFilteredKeys;
FileData* mRootFolder; FileData* mRootFolder;
}; };
#endif // ES_APP_FILE_FILTER_INDEX_H #endif // ES_APP_FILE_FILTER_INDEX_H

View file

@ -48,7 +48,8 @@ namespace FileSorts
FileData::SortType(&compareSystemDescending, "system, descending") FileData::SortType(&compareSystemDescending, "system, descending")
}; };
const std::vector<FileData::SortType> SortTypes(typesArr, typesArr + const std::vector<FileData::SortType> SortTypes(typesArr,
typesArr +
sizeof(typesArr) / sizeof(typesArr[0])); sizeof(typesArr) / sizeof(typesArr[0]));
bool compareName(const FileData* file1, const FileData* file2) bool compareName(const FileData* file1, const FileData* file2)
@ -155,11 +156,13 @@ namespace FileSorts
file2Players = file2Players.substr(dashPos + 1, file2Players.size() - dashPos - 1); file2Players = file2Players.substr(dashPos + 1, file2Players.size() - dashPos - 1);
// Any non-numeric value will end up as zero. // Any non-numeric value will end up as zero.
if (!file1Players.empty() && if (!file1Players.empty() &&
std::all_of(file1Players.begin(), file1Players.end(), ::isdigit)) std::all_of(file1Players.begin(), file1Players.end(), ::isdigit)) {
file1Int = stoi(file1Players); file1Int = stoi(file1Players);
}
if (!file2Players.empty() && if (!file2Players.empty() &&
std::all_of(file2Players.begin(), file2Players.end(), ::isdigit)) std::all_of(file2Players.begin(), file2Players.end(), ::isdigit)) {
file2Int = stoi(file2Players); file2Int = stoi(file2Players);
}
return file1Int < file2Int; return file1Int < file2Int;
} }
@ -177,11 +180,13 @@ namespace FileSorts
if (dashPos != std::string::npos) if (dashPos != std::string::npos)
file2Players = file2Players.substr(dashPos + 1, file2Players.size() - dashPos - 1); file2Players = file2Players.substr(dashPos + 1, file2Players.size() - dashPos - 1);
if (!file1Players.empty() && if (!file1Players.empty() &&
std::all_of(file1Players.begin(), file1Players.end(), ::isdigit)) std::all_of(file1Players.begin(), file1Players.end(), ::isdigit)) {
file1Int = stoi(file1Players); file1Int = stoi(file1Players);
}
if (!file2Players.empty() && if (!file2Players.empty() &&
std::all_of(file2Players.begin(), file2Players.end(), ::isdigit)) std::all_of(file2Players.begin(), file2Players.end(), ::isdigit)) {
file2Int = stoi(file2Players); file2Int = stoi(file2Players);
}
return file1Int > file2Int; return file1Int > file2Int;
} }
@ -201,16 +206,18 @@ namespace FileSorts
{ {
// Only games have playcount metadata. // Only games have playcount metadata.
if (file1->metadata.getType() == GAME_METADATA && if (file1->metadata.getType() == GAME_METADATA &&
file2->metadata.getType() == GAME_METADATA) file2->metadata.getType() == GAME_METADATA) {
return (file1)->metadata.getInt("playcount") < (file2)->metadata.getInt("playcount"); return (file1)->metadata.getInt("playcount") < (file2)->metadata.getInt("playcount");
}
return false; return false;
} }
bool compareTimesPlayedDescending(const FileData* file1, const FileData* file2) bool compareTimesPlayedDescending(const FileData* file1, const FileData* file2)
{ {
if (file1->metadata.getType() == GAME_METADATA && if (file1->metadata.getType() == GAME_METADATA &&
file2->metadata.getType() == GAME_METADATA) file2->metadata.getType() == GAME_METADATA) {
return (file1)->metadata.getInt("playcount") > (file2)->metadata.getInt("playcount"); return (file1)->metadata.getInt("playcount") > (file2)->metadata.getInt("playcount");
}
return false; return false;
} }
@ -227,4 +234,5 @@ namespace FileSorts
std::string system2 = Utils::String::toUpper(file2->getSystemName()); std::string system2 = Utils::String::toUpper(file2->getSystemName());
return system1.compare(system2) > 0; return system1.compare(system2) > 0;
} }
};
}; // namespace FileSorts

View file

@ -38,6 +38,6 @@ namespace FileSorts
bool compareSystemDescending(const FileData* file1, const FileData* file2); bool compareSystemDescending(const FileData* file1, const FileData* file2);
extern const std::vector<FileData::SortType> SortTypes; extern const std::vector<FileData::SortType> SortTypes;
}; }; // namespace FileSorts
#endif // ES_APP_FILE_SORTS_H #endif // ES_APP_FILE_SORTS_H

View file

@ -8,12 +8,12 @@
#include "Gamelist.h" #include "Gamelist.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "FileData.h" #include "FileData.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include <pugixml.hpp> #include <pugixml.hpp>
@ -25,8 +25,8 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains); std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains);
if (!contains) { if (!contains) {
LOG(LogError) << "Path \"" << path << "\" is outside system path \"" << LOG(LogError) << "Path \"" << path << "\" is outside system path \""
system->getStartPath() << "\""; << system->getStartPath() << "\"";
return nullptr; return nullptr;
} }
@ -71,8 +71,9 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
} }
// Create missing folder. // Create missing folder.
FileData* folder = new FileData(FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) FileData* folder = new FileData(
+ "/" + *path_it, system->getSystemEnvData(), system); FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it,
system->getSystemEnvData(), system);
treeNode->addChild(folder); treeNode->addChild(folder);
treeNode = folder; treeNode = folder;
} }
@ -89,8 +90,8 @@ void parseGamelist(SystemData* system)
std::string xmlpath = system->getGamelistPath(false); std::string xmlpath = system->getGamelistPath(false);
if (!Utils::FileSystem::exists(xmlpath)) { if (!Utils::FileSystem::exists(xmlpath)) {
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName() << LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
"\" does not have a gamelist.xml file"; << "\" does not have a gamelist.xml file";
return; return;
} }
@ -105,8 +106,8 @@ void parseGamelist(SystemData* system)
#endif #endif
if (!result) { if (!result) {
LOG(LogError) << "Error parsing gamelist file \"" << xmlpath << LOG(LogError) << "Error parsing gamelist file \"" << xmlpath
"\": " << result.description(); << "\": " << result.description();
return; return;
} }
@ -124,24 +125,24 @@ void parseGamelist(SystemData* system)
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
std::string tag = tagList[i]; std::string tag = tagList[i];
FileType type = typeList[i]; FileType type = typeList[i];
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode; fileNode = for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
fileNode.next_sibling(tag.c_str())) { fileNode = fileNode.next_sibling(tag.c_str())) {
const std::string path = const std::string path = Utils::FileSystem::resolveRelativePath(
Utils::FileSystem::resolveRelativePath(fileNode.child("path").text().get(), fileNode.child("path").text().get(), relativeTo, false);
relativeTo, false);
if (!trustGamelist && !Utils::FileSystem::exists(path)) { if (!trustGamelist && !Utils::FileSystem::exists(path)) {
LOG(LogWarning) << (type == GAME ? "File \"" : "Folder \"") << path << LOG(LogWarning) << (type == GAME ? "File \"" : "Folder \"") << path
"\" does not exist, ignoring entry"; << "\" does not exist, ignoring entry";
continue; continue;
} }
// Skip hidden files, check both the file itself and the directory in which // Skip hidden files, check both the file itself and the directory in which
// it is located. // it is located.
if (!showHiddenFiles && (Utils::FileSystem::isHidden(path) || if (!showHiddenFiles &&
(Utils::FileSystem::isHidden(path) ||
Utils::FileSystem::isHidden(Utils::FileSystem::getParent(path)))) { Utils::FileSystem::isHidden(Utils::FileSystem::getParent(path)))) {
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden file \"" << LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden file \"" << path
path << "\""; << "\"";
continue; continue;
} }
@ -163,8 +164,8 @@ void parseGamelist(SystemData* system)
else { else {
// Skip arcade asset entries as these will not be used in any way inside // Skip arcade asset entries as these will not be used in any way inside
// the application. // the application.
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping arcade asset \"" << LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping arcade asset \""
file->getName() << "\""; << file->getName() << "\"";
delete file; delete file;
continue; continue;
} }
@ -174,9 +175,10 @@ void parseGamelist(SystemData* system)
// application restart. // application restart.
if (!Settings::getInstance()->getBool("ShowHiddenGames")) { if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
if (file->getHidden()) { if (file->getHidden()) {
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden " << LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden "
(type == GAME ? "file" : "folder") << " entry \"" << << (type == GAME ? "file" : "folder") << " entry \""
file->getName() << "\"" << " (\"" << file->getPath() << "\")"; << file->getName() << "\""
<< " (\"" << file->getPath() << "\")";
delete file; delete file;
} }
// Also delete any folders which are empty, i.e. all their entries are hidden. // Also delete any folders which are empty, i.e. all their entries are hidden.
@ -188,8 +190,10 @@ void parseGamelist(SystemData* system)
} }
} }
void addFileDataNode(pugi::xml_node& parent, const FileData* file, void addFileDataNode(pugi::xml_node& parent,
const std::string& tag, SystemData* system) const FileData* file,
const std::string& tag,
SystemData* system)
{ {
// Create game and add to parent node. // Create game and add to parent node.
pugi::xml_node newNode = parent.append_child(tag.c_str()); pugi::xml_node newNode = parent.append_child(tag.c_str());
@ -211,8 +215,9 @@ void addFileDataNode(pugi::xml_node& parent, const FileData* file,
// Try and make the path relative if we can so things still // Try and make the path relative if we can so things still
// work if we change the ROM folder location in the future. // work if we change the ROM folder location in the future.
newNode.prepend_child("path").text().set(Utils::FileSystem::createRelativePath(file-> newNode.prepend_child("path").text().set(
getPath(), system->getStartPath(), false).c_str()); Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false)
.c_str());
} }
} }
@ -232,6 +237,7 @@ void updateGamelist(SystemData* system)
if (Utils::FileSystem::exists(xmlReadPath)) { if (Utils::FileSystem::exists(xmlReadPath)) {
// Parse an existing file first. // Parse an existing file first.
#if defined(_WIN64) #if defined(_WIN64)
pugi::xml_parse_result result = pugi::xml_parse_result result =
doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str()); doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str());
@ -240,15 +246,14 @@ void updateGamelist(SystemData* system)
#endif #endif
if (!result) { if (!result) {
LOG(LogError) << "Error parsing gamelist file \"" << xmlReadPath << "\": " << LOG(LogError) << "Error parsing gamelist file \"" << xmlReadPath
result.description(); << "\": " << result.description();
return; return;
} }
root = doc.child("gameList"); root = doc.child("gameList");
if (!root) { if (!root) {
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlReadPath << "\"";
xmlReadPath << "\"";
return; return;
} }
} }
@ -266,7 +271,7 @@ void updateGamelist(SystemData* system)
// Get only files, no folders. // Get only files, no folders.
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER); std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
// Iterate through all files, checking if they're already in the XML file. // Iterate through all files, checking if they're already in the XML file.
for (std::vector<FileData*>::const_iterator fit = files.cbegin(); for (std::vector<FileData*>::const_iterator fit = files.cbegin(); // Line break.
fit != files.cend(); fit++) { fit != files.cend(); fit++) {
const std::string tag = ((*fit)->getType() == GAME) ? "game" : "folder"; const std::string tag = ((*fit)->getType() == GAME) ? "game" : "folder";
@ -284,9 +289,9 @@ void updateGamelist(SystemData* system)
continue; continue;
} }
std::string nodePath =Utils::FileSystem::getCanonicalPath( std::string nodePath =
Utils::FileSystem::resolveRelativePath(pathNode.text().get(), Utils::FileSystem::getCanonicalPath(Utils::FileSystem::resolveRelativePath(
system->getStartPath(), true)); pathNode.text().get(), system->getStartPath(), true));
std::string gamePath = Utils::FileSystem::getCanonicalPath((*fit)->getPath()); std::string gamePath = Utils::FileSystem::getCanonicalPath((*fit)->getPath());
if (nodePath == gamePath) { if (nodePath == gamePath) {
@ -312,16 +317,17 @@ void updateGamelist(SystemData* system)
std::string xmlWritePath(system->getGamelistPath(true)); std::string xmlWritePath(system->getGamelistPath(true));
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath)); Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated << LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
(numUpdated == 1 ? " entity in \"" : " entities in \"") << xmlReadPath << "\""; << (numUpdated == 1 ? " entity in \"" : " entities in \"") << xmlReadPath
<< "\"";
#if defined(_WIN64) #if defined(_WIN64)
if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) { if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) {
#else #else
if (!doc.save_file(xmlWritePath.c_str())) { if (!doc.save_file(xmlWritePath.c_str())) {
#endif #endif
LOG(LogError) << "Error saving gamelist.xml to \"" << LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath
xmlWritePath << "\" (for system " << system->getName() << ")"; << "\" (for system " << system->getName() << ")";
} }
} }
} }

View file

@ -12,11 +12,14 @@
#if defined(BUILD_VLC_PLAYER) #if defined(BUILD_VLC_PLAYER)
#include "components/VideoVlcComponent.h" #include "components/VideoVlcComponent.h"
#endif #endif
#include "views/ViewController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "Sound.h" #include "Sound.h"
#include "views/ViewController.h"
MediaViewer::MediaViewer(Window* window) : mWindow(window), mVideo(nullptr), mImage(nullptr) MediaViewer::MediaViewer(Window* window)
: mWindow(window)
, mVideo(nullptr)
, mImage(nullptr)
{ {
mWindow->setMediaViewer(this); mWindow->setMediaViewer(this);
} }
@ -98,6 +101,7 @@ void MediaViewer::render()
if (Settings::getInstance()->getBool("MediaViewerVideoBlur")) { if (Settings::getInstance()->getBool("MediaViewerVideoBlur")) {
shaders |= Renderer::SHADER_BLUR_HORIZONTAL; shaders |= Renderer::SHADER_BLUR_HORIZONTAL;
float heightModifier = Renderer::getScreenHeightModifier(); float heightModifier = Renderer::getScreenHeightModifier();
// clang-format off
if (heightModifier < 1) if (heightModifier < 1)
videoParameters.blurPasses = 2; // Below 1080 videoParameters.blurPasses = 2; // Below 1080
else if (heightModifier >= 4) else if (heightModifier >= 4)
@ -112,11 +116,12 @@ void MediaViewer::render()
videoParameters.blurPasses = 3; // 1440 videoParameters.blurPasses = 3; // 1440
else if (heightModifier >= 1) else if (heightModifier >= 1)
videoParameters.blurPasses = 2; // 1080 videoParameters.blurPasses = 2; // 1080
// clang-format on
} }
Renderer::shaderPostprocessing(shaders, videoParameters); Renderer::shaderPostprocessing(shaders, videoParameters);
#endif #endif
} }
else if (mImage && mImage->hasImage()) { else if (mImage && mImage->hasImage() && mImage->getSize() != 0) {
mImage->render(transform); mImage->render(transform);
#if defined(USE_OPENGL_21) #if defined(USE_OPENGL_21)

View file

@ -9,10 +9,10 @@
#ifndef ES_APP_MEDIA_VIEWER_H #ifndef ES_APP_MEDIA_VIEWER_H
#define ES_APP_MEDIA_VIEWER_H #define ES_APP_MEDIA_VIEWER_H
#include "components/ImageComponent.h"
#include "components/VideoComponent.h"
#include "FileData.h" #include "FileData.h"
#include "Window.h" #include "Window.h"
#include "components/ImageComponent.h"
#include "components/VideoComponent.h"
class MediaViewer : public Window::MediaViewer class MediaViewer : public Window::MediaViewer
{ {

View file

@ -9,11 +9,12 @@
#include "MetaData.h" #include "MetaData.h"
#include "utils/FileSystemUtil.h"
#include "Log.h" #include "Log.h"
#include "utils/FileSystemUtil.h"
#include <pugixml.hpp> #include <pugixml.hpp>
// clang-format off
MetaDataDecl gameDecls[] = { MetaDataDecl gameDecls[] = {
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd, shouldScrape // key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd, shouldScrape
{"name", MD_STRING, "", false, "name", "enter name", true}, {"name", MD_STRING, "", false, "name", "enter name", true},
@ -39,9 +40,6 @@ MetaDataDecl gameDecls[] = {
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false}
}; };
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls +
sizeof(gameDecls) / sizeof(gameDecls[0]));
MetaDataDecl folderDecls[] = { MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false, "name", "enter name", true}, {"name", MD_STRING, "", false, "name", "enter name", true},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true},
@ -59,8 +57,13 @@ MetaDataDecl folderDecls[] = {
{"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false}, {"hidemetadata", MD_BOOL, "false", false, "hide metadata fields", "enter hide metadata off/on", false},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false} {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date", false}
}; };
// clang-format on
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + const std::vector<MetaDataDecl> gameMDD(gameDecls,
gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0]));
const std::vector<MetaDataDecl> folderMDD(folderDecls,
folderDecls +
sizeof(folderDecls) / sizeof(folderDecls[0])); sizeof(folderDecls) / sizeof(folderDecls[0]));
const std::vector<MetaDataDecl>& getMDDByType(MetaDataListType type) const std::vector<MetaDataDecl>& getMDDByType(MetaDataListType type)
@ -77,7 +80,8 @@ const std::vector<MetaDataDecl>& getMDDByType(MetaDataListType type)
} }
MetaDataList::MetaDataList(MetaDataListType type) MetaDataList::MetaDataList(MetaDataListType type)
: mType(type), mWasChanged(false) : mType(type)
, mWasChanged(false)
{ {
const std::vector<MetaDataDecl>& mdd = getMDD(); const std::vector<MetaDataDecl>& mdd = getMDD();
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
@ -85,7 +89,8 @@ MetaDataList::MetaDataList(MetaDataListType type)
} }
MetaDataList MetaDataList::createFromXML(MetaDataListType type, MetaDataList MetaDataList::createFromXML(MetaDataListType type,
pugi::xml_node& node, const std::string& relativeTo) pugi::xml_node& node,
const std::string& relativeTo)
{ {
MetaDataList mdl(type); MetaDataList mdl(type);
@ -107,7 +112,8 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type,
return mdl; return mdl;
} }
void MetaDataList::appendToXML(pugi::xml_node& parent, bool ignoreDefaults, void MetaDataList::appendToXML(pugi::xml_node& parent,
bool ignoreDefaults,
const std::string& relativeTo) const const std::string& relativeTo) const
{ {
const std::vector<MetaDataDecl>& mdd = getMDD(); const std::vector<MetaDataDecl>& mdd = getMDD();
@ -138,30 +144,33 @@ void MetaDataList::set(const std::string& key, const std::string& value)
const std::string& MetaDataList::get(const std::string& key) const const std::string& MetaDataList::get(const std::string& key) const
{ {
// Check that the key actually exists, otherwise return empty string. // Check that the key actually exists, otherwise return an empty string.
if (mMap.count(key) > 0) if (mMap.count(key) > 0)
return mMap.at(key); return mMap.at(key);
else else
return mNoResult; return mNoResult;
} }
int MetaDataList::getInt(const std::string& key) const int MetaDataList::getInt(const std::string& key) const
{ {
// Return integer value.
return atoi(get(key).c_str()); return atoi(get(key).c_str());
} }
float MetaDataList::getFloat(const std::string& key) const float MetaDataList::getFloat(const std::string& key) const
{ {
// Return float value.
return static_cast<float>(atof(get(key).c_str())); return static_cast<float>(atof(get(key).c_str()));
} }
bool MetaDataList::wasChanged() const bool MetaDataList::wasChanged() const
{ {
// Return whether the metadata was changed.
return mWasChanged; return mWasChanged;
} }
void MetaDataList::resetChangedFlag() void MetaDataList::resetChangedFlag()
{ {
// Reset the change flag.
mWasChanged = false; mWasChanged = false;
} }

View file

@ -18,7 +18,10 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace pugi { class xml_node; } namespace pugi
{
class xml_node;
}
enum MetaDataType { enum MetaDataType {
// Generic types. // Generic types.
@ -51,7 +54,7 @@ struct MetaDataDecl {
}; };
enum MetaDataListType { enum MetaDataListType {
GAME_METADATA, GAME_METADATA, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
FOLDER_METADATA FOLDER_METADATA
}; };
@ -61,8 +64,10 @@ class MetaDataList
{ {
public: public:
static MetaDataList createFromXML(MetaDataListType type, static MetaDataList createFromXML(MetaDataListType type,
pugi::xml_node& node, const std::string& relativeTo); pugi::xml_node& node,
void appendToXML(pugi::xml_node& parent, bool ignoreDefaults, const std::string& relativeTo);
void appendToXML(pugi::xml_node& parent,
bool ignoreDefaults,
const std::string& relativeTo) const; const std::string& relativeTo) const;
MetaDataList(MetaDataListType type); MetaDataList(MetaDataListType type);
@ -76,10 +81,12 @@ public:
bool wasChanged() const; bool wasChanged() const;
void resetChangedFlag(); void resetChangedFlag();
inline MetaDataListType getType() const { return mType; } MetaDataListType getType() const { return mType; }
inline const std::vector<MetaDataDecl>& getMDD() const { return getMDDByType(getType()); } const std::vector<MetaDataDecl>& getMDD() const { return getMDDByType(getType()); }
inline const std::vector<MetaDataDecl>& getMDD(MetaDataListType type) const const std::vector<MetaDataDecl>& getMDD(MetaDataListType type) const
{ return getMDDByType(type); } {
return getMDDByType(type);
}
private: private:
MetaDataListType mType; MetaDataListType mType;

View file

@ -9,28 +9,26 @@
#include "MiximageGenerator.h" #include "MiximageGenerator.h"
#include "math/Misc.h"
#include "utils/StringUtil.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "math/Misc.h"
#include "utils/StringUtil.h"
#include <chrono> #include <chrono>
MiximageGenerator::MiximageGenerator(FileData* game, std::string& resultMessage) MiximageGenerator::MiximageGenerator(FileData* game, std::string& resultMessage)
: mGame(game), : mGame(game)
mResultMessage(resultMessage), , mResultMessage(resultMessage)
mWidth(1280), , mWidth(1280)
mHeight(960), , mHeight(960)
mMarquee(false), , mMarquee(false)
mBox3D(false), , mBox3D(false)
mCover(false) , mCover(false)
{ {
} }
MiximageGenerator::~MiximageGenerator() MiximageGenerator::~MiximageGenerator() {}
{
}
void MiximageGenerator::startThread(std::promise<bool>* miximagePromise) void MiximageGenerator::startThread(std::promise<bool>* miximagePromise)
{ {
@ -93,9 +91,10 @@ void MiximageGenerator::startThread(std::promise<bool>* miximagePromise)
else { else {
const auto endTime = std::chrono::system_clock::now(); const auto endTime = std::chrono::system_clock::now();
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): Processing completed in: " << LOG(LogDebug)
std::chrono::duration_cast<std::chrono::milliseconds> << "MiximageGenerator::MiximageGenerator(): Processing completed in: "
(endTime - startTime).count() << " ms"; << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
<< " ms";
} }
mResultMessage = mMessage; mResultMessage = mMessage;
@ -136,8 +135,8 @@ bool MiximageGenerator::generateImage()
// Make sure that we can actually read this format. // Make sure that we can actually read this format.
if (FreeImage_FIFSupportsReading(fileFormat)) { if (FreeImage_FIFSupportsReading(fileFormat)) {
#if defined(_WIN64) #if defined(_WIN64)
screenshotFile = FreeImage_LoadU(fileFormat, screenshotFile =
Utils::String::stringToWideString(mScreenshotPath).c_str()); FreeImage_LoadU(fileFormat, Utils::String::stringToWideString(mScreenshotPath).c_str());
#else #else
screenshotFile = FreeImage_Load(fileFormat, mScreenshotPath.c_str()); screenshotFile = FreeImage_Load(fileFormat, mScreenshotPath.c_str());
#endif #endif
@ -156,8 +155,8 @@ bool MiximageGenerator::generateImage()
if (mMarquee) { if (mMarquee) {
#if defined(_WIN64) #if defined(_WIN64)
fileFormat = FreeImage_GetFileTypeU( fileFormat =
Utils::String::stringToWideString(mMarqueePath).c_str()); FreeImage_GetFileTypeU(Utils::String::stringToWideString(mMarqueePath).c_str());
#else #else
fileFormat = FreeImage_GetFileType(mMarqueePath.c_str()); fileFormat = FreeImage_GetFileType(mMarqueePath.c_str());
#endif #endif
@ -220,8 +219,8 @@ bool MiximageGenerator::generateImage()
} }
else { else {
#if defined(_WIN64) #if defined(_WIN64)
boxFile = FreeImage_LoadU(fileFormat, boxFile =
Utils::String::stringToWideString(mBox3DPath).c_str()); FreeImage_LoadU(fileFormat, Utils::String::stringToWideString(mBox3DPath).c_str());
#else #else
boxFile = FreeImage_Load(fileFormat, mBox3DPath.c_str()); boxFile = FreeImage_Load(fileFormat, mBox3DPath.c_str());
#endif #endif
@ -234,8 +233,7 @@ bool MiximageGenerator::generateImage()
} }
else if (mCover) { else if (mCover) {
#if defined(_WIN64) #if defined(_WIN64)
fileFormat = FreeImage_GetFileTypeU( fileFormat = FreeImage_GetFileTypeU(Utils::String::stringToWideString(mCoverPath).c_str());
Utils::String::stringToWideString(mCoverPath).c_str());
#else #else
fileFormat = FreeImage_GetFileType(mCoverPath.c_str()); fileFormat = FreeImage_GetFileType(mCoverPath.c_str());
#endif #endif
@ -259,8 +257,8 @@ bool MiximageGenerator::generateImage()
} }
else { else {
#if defined(_WIN64) #if defined(_WIN64)
boxFile = FreeImage_LoadU(fileFormat, boxFile =
Utils::String::stringToWideString(mCoverPath).c_str()); FreeImage_LoadU(fileFormat, Utils::String::stringToWideString(mCoverPath).c_str());
#else #else
boxFile = FreeImage_Load(fileFormat, mCoverPath.c_str()); boxFile = FreeImage_Load(fileFormat, mCoverPath.c_str());
#endif #endif
@ -427,17 +425,17 @@ bool MiximageGenerator::generateImage()
std::vector<unsigned char> boxVector(fileWidth * fileHeight * 4); std::vector<unsigned char> boxVector(fileWidth * fileHeight * 4);
FreeImage_ConvertToRawBits(reinterpret_cast<BYTE*>(&boxVector.at(0)), boxFile, FreeImage_ConvertToRawBits(reinterpret_cast<BYTE*>(&boxVector.at(0)), boxFile, filePitch,
filePitch, 32, FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE, 1); 32, FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE, 1);
boxImage = CImg<unsigned char>(FreeImage_GetWidth(boxFile), boxImage =
FreeImage_GetHeight(boxFile), 1, 4); CImg<unsigned char>(FreeImage_GetWidth(boxFile), FreeImage_GetHeight(boxFile), 1, 4);
Utils::CImg::convertRGBAToCImg(boxVector, boxImage); Utils::CImg::convertRGBAToCImg(boxVector, boxImage);
Utils::CImg::removeTransparentPadding(boxImage); Utils::CImg::removeTransparentPadding(boxImage);
float scaleFactor = static_cast<float>(boxTargetHeight) / float scaleFactor =
static_cast<float>(boxImage.height()); static_cast<float>(boxTargetHeight) / static_cast<float>(boxImage.height());
unsigned int width = static_cast<int>(static_cast<float>(boxImage.width()) * scaleFactor); unsigned int width = static_cast<int>(static_cast<float>(boxImage.width()) * scaleFactor);
unsigned int targetWidth = 0; unsigned int targetWidth = 0;
@ -478,20 +476,15 @@ bool MiximageGenerator::generateImage()
sampleFrameColor(screenshotImage, frameColor); sampleFrameColor(screenshotImage, frameColor);
// Upper / lower frame. // Upper / lower frame.
frameImage.draw_rectangle( frameImage.draw_rectangle(xPosScreenshot + 2, yPosScreenshot - screenshotFrameWidth,
xPosScreenshot + 2,
yPosScreenshot - screenshotFrameWidth,
xPosScreenshot + screenshotWidth - 2, xPosScreenshot + screenshotWidth - 2,
yPosScreenshot + screenshotHeight + screenshotFrameWidth - 1, yPosScreenshot + screenshotHeight + screenshotFrameWidth - 1,
frameColor); frameColor);
// Left / right frame. // Left / right frame.
frameImage.draw_rectangle( frameImage.draw_rectangle(xPosScreenshot - screenshotFrameWidth, yPosScreenshot + 2,
xPosScreenshot - screenshotFrameWidth,
yPosScreenshot + 2,
xPosScreenshot + screenshotWidth + screenshotFrameWidth - 1, xPosScreenshot + screenshotWidth + screenshotFrameWidth - 1,
yPosScreenshot + screenshotHeight - 2, yPosScreenshot + screenshotHeight - 2, frameColor);
frameColor);
// We draw circles in order to get rounded corners for the frame. // We draw circles in order to get rounded corners for the frame.
const unsigned int circleRadius = 8 * resolutionMultiplier; const unsigned int circleRadius = 8 * resolutionMultiplier;
@ -505,10 +498,12 @@ bool MiximageGenerator::generateImage()
yPosScreenshot + circleOffset, circleRadius, frameColor); yPosScreenshot + circleOffset, circleRadius, frameColor);
// Lower right corner. // Lower right corner.
frameImage.draw_circle(xPosScreenshot + screenshotWidth - circleOffset - 1, frameImage.draw_circle(xPosScreenshot + screenshotWidth - circleOffset - 1,
yPosScreenshot + screenshotHeight - circleOffset - 1, circleRadius, frameColor); yPosScreenshot + screenshotHeight - circleOffset - 1, circleRadius,
frameColor);
// Lower left corner. // Lower left corner.
frameImage.draw_circle(xPosScreenshot + circleOffset, frameImage.draw_circle(xPosScreenshot + circleOffset,
yPosScreenshot + screenshotHeight - circleOffset - 1, circleRadius, frameColor); yPosScreenshot + screenshotHeight - circleOffset - 1, circleRadius,
frameColor);
CImg<unsigned char> frameImageRGB(frameImage.get_shared_channels(0, 2)); CImg<unsigned char> frameImageRGB(frameImage.get_shared_channels(0, 2));
@ -516,8 +511,8 @@ bool MiximageGenerator::generateImage()
canvasImage.draw_image(xPosScreenshot, yPosScreenshot, screenshotImage); canvasImage.draw_image(xPosScreenshot, yPosScreenshot, screenshotImage);
if (mMarquee) if (mMarquee)
canvasImage.draw_image(xPosMarquee, yPosMarquee, marqueeImageRGB, canvasImage.draw_image(xPosMarquee, yPosMarquee, marqueeImageRGB, marqueeImageAlpha, 1,
marqueeImageAlpha, 1, 255); 255);
if (mBox3D || mCover) if (mBox3D || mCover)
canvasImage.draw_image(xPosBox, yPosBox, boxImageRGB, boxImageAlpha, 1, 255); canvasImage.draw_image(xPosBox, yPosBox, boxImageRGB, boxImageAlpha, 1, 255);
@ -532,7 +527,8 @@ bool MiximageGenerator::generateImage()
FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE); FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE);
#if defined(_WIN64) #if defined(_WIN64)
bool savedImage = (FreeImage_SaveU(FIF_PNG, mixImage, bool savedImage =
(FreeImage_SaveU(FIF_PNG, mixImage,
Utils::String::stringToWideString(getSavePath()).c_str()) != 0); Utils::String::stringToWideString(getSavePath()).c_str()) != 0);
#else #else
bool savedImage = (FreeImage_Save(FIF_PNG, mixImage, getSavePath().c_str()) != 0); bool savedImage = (FreeImage_Save(FIF_PNG, mixImage, getSavePath().c_str()) != 0);
@ -555,7 +551,9 @@ bool MiximageGenerator::generateImage()
} }
void MiximageGenerator::calculateMarqueeSize(const unsigned int& targetWidth, void MiximageGenerator::calculateMarqueeSize(const unsigned int& targetWidth,
const unsigned int& targetHeight, unsigned int& width, unsigned int& height) const unsigned int& targetHeight,
unsigned int& width,
unsigned int& height)
{ {
unsigned int adjustedTargetWidth = 0; unsigned int adjustedTargetWidth = 0;
float widthModifier = 0.5f; float widthModifier = 0.5f;
@ -572,8 +570,8 @@ void MiximageGenerator::calculateMarqueeSize(const unsigned int& targetWidth,
if (widthRatio >= 4) if (widthRatio >= 4)
widthModifier += Math::clamp(widthRatio / 40.0f, 0.0f, 0.3f); widthModifier += Math::clamp(widthRatio / 40.0f, 0.0f, 0.3f);
adjustedTargetWidth = static_cast<unsigned int>( adjustedTargetWidth =
static_cast<float>(targetWidth) * widthModifier); static_cast<unsigned int>(static_cast<float>(targetWidth) * widthModifier);
scaleFactor = static_cast<float>(adjustedTargetWidth) / static_cast<float>(width); scaleFactor = static_cast<float>(adjustedTargetWidth) / static_cast<float>(width);
// For really tall and narrow images, we may have exceeded the target height. // For really tall and narrow images, we may have exceeded the target height.
@ -629,7 +627,7 @@ void MiximageGenerator::sampleFrameColor(CImg<unsigned char>& screenshotImage,
// Convert to the HSL color space to be able to modify saturation and lightness. // Convert to the HSL color space to be able to modify saturation and lightness.
CImg<float> colorHSL = CImg<>(1, 1, 1, 3).fill(redC, greenC, blueC).RGBtoHSL(); CImg<float> colorHSL = CImg<>(1, 1, 1, 3).fill(redC, greenC, blueC).RGBtoHSL();
float hue = colorHSL(0, 0, 0, 0); // float hue = colorHSL(0, 0, 0, 0);
float saturation = colorHSL(0, 0, 0, 1); float saturation = colorHSL(0, 0, 0, 1);
float lightness = colorHSL(0, 0, 0, 2); float lightness = colorHSL(0, 0, 0, 2);

View file

@ -10,9 +10,9 @@
#ifndef ES_APP_SCRAPERS_MIXIMAGE_GENERATOR_H #ifndef ES_APP_SCRAPERS_MIXIMAGE_GENERATOR_H
#define ES_APP_SCRAPERS_MIXIMAGE_GENERATOR_H #define ES_APP_SCRAPERS_MIXIMAGE_GENERATOR_H
#include "utils/CImgUtil.h"
#include "FileData.h" #include "FileData.h"
#include "GuiComponent.h" #include "GuiComponent.h"
#include "utils/CImgUtil.h"
#include <FreeImage.h> #include <FreeImage.h>
#include <future> #include <future>
@ -29,8 +29,10 @@ public:
private: private:
bool generateImage(); bool generateImage();
void calculateMarqueeSize(const unsigned int& targetWidth, const unsigned int& targetHeight, void calculateMarqueeSize(const unsigned int& targetWidth,
unsigned int& width, unsigned int& height); const unsigned int& targetHeight,
unsigned int& width,
unsigned int& height);
void sampleFrameColor(CImg<unsigned char>& screenshotImage, unsigned char (&frameColor)[4]); void sampleFrameColor(CImg<unsigned char>& screenshotImage, unsigned char (&frameColor)[4]);
std::string getSavePath(); std::string getSavePath();

View file

@ -13,6 +13,7 @@
namespace PlatformIds namespace PlatformIds
{ {
// clang-format off
std::vector<std::string> platformNames = { std::vector<std::string> platformNames = {
"unknown", // Nothing set. "unknown", // Nothing set.
@ -132,6 +133,7 @@ namespace PlatformIds
"ignore", // Do not allow scraping for this system. "ignore", // Do not allow scraping for this system.
"invalid" "invalid"
}; };
// clang-format on
PlatformId getPlatformId(const std::string& str) PlatformId getPlatformId(const std::string& str)
{ {
@ -148,6 +150,8 @@ namespace PlatformIds
const std::string getPlatformName(PlatformId id) const std::string getPlatformName(PlatformId id)
{ {
// Return the platform name.
return platformNames[id]; return platformNames[id];
} }
}
} // namespace PlatformIds

View file

@ -135,6 +135,7 @@ namespace PlatformIds
PlatformId getPlatformId(const std::string& str); PlatformId getPlatformId(const std::string& str);
const std::string getPlatformName(PlatformId id); const std::string getPlatformName(PlatformId id);
}
} // namespace PlatformIds
#endif // ES_APP_PLATFORM_ID_H #endif // ES_APP_PLATFORM_ID_H

View file

@ -11,12 +11,6 @@
#include "SystemData.h" #include "SystemData.h"
#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "views/gamelist/IGameListView.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "FileSorts.h" #include "FileSorts.h"
@ -25,6 +19,12 @@
#include "Platform.h" #include "Platform.h"
#include "Settings.h" #include "Settings.h"
#include "ThemeData.h" #include "ThemeData.h"
#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "views/gamelist/IGameListView.h"
#include <fstream> #include <fstream>
#include <pugixml.hpp> #include <pugixml.hpp>
@ -39,10 +39,6 @@ FindRules::FindRules()
loadFindRules(); loadFindRules();
} }
FindRules::~FindRules()
{
}
void FindRules::loadFindRules() void FindRules::loadFindRules()
{ {
std::string customSystemsDirectory = std::string customSystemsDirectory =
@ -55,14 +51,14 @@ void FindRules::loadFindRules()
} }
else { else {
#if defined(_WIN64) #if defined(_WIN64)
path = ResourceManager::getInstance()-> path = ResourceManager::getInstance()->getResourcePath(
getResourcePath(":/systems/windows/es_find_rules.xml", false); ":/systems/windows/es_find_rules.xml", false);
#elif defined(__APPLE__) #elif defined(__APPLE__)
path = ResourceManager::getInstance()-> path = ResourceManager::getInstance()->getResourcePath(":/systems/macos/es_find_rules.xml",
getResourcePath(":/systems/macos/es_find_rules.xml", false); false);
#else #else
path = ResourceManager::getInstance()-> path = ResourceManager::getInstance()->getResourcePath(":/systems/unix/es_find_rules.xml",
getResourcePath(":/systems/unix/es_find_rules.xml", false); false);
#endif #endif
} }
@ -104,15 +100,15 @@ void FindRules::loadFindRules()
continue; continue;
} }
if (mEmulators.find(emulatorName) != mEmulators.end()) { if (mEmulators.find(emulatorName) != mEmulators.end()) {
LOG(LogWarning) << "Found repeating emulator tag \"" << emulatorName << LOG(LogWarning) << "Found repeating emulator tag \"" << emulatorName
"\", skipping entry"; << "\", skipping entry";
continue; continue;
} }
for (pugi::xml_node rule = emulator.child("rule"); rule; rule = rule.next_sibling("rule")) { for (pugi::xml_node rule = emulator.child("rule"); rule; rule = rule.next_sibling("rule")) {
std::string ruleType = rule.attribute("type").as_string(); std::string ruleType = rule.attribute("type").as_string();
if (ruleType.empty()) { if (ruleType.empty()) {
LOG(LogWarning) << "Found rule tag without type attribute for emulator \"" << LOG(LogWarning) << "Found rule tag without type attribute for emulator \""
emulatorName << "\", skipping entry"; << emulatorName << "\", skipping entry";
continue; continue;
} }
#if defined(_WIN64) #if defined(_WIN64)
@ -121,8 +117,8 @@ void FindRules::loadFindRules()
#else #else
if (ruleType != "systempath" && ruleType != "staticpath") { if (ruleType != "systempath" && ruleType != "staticpath") {
#endif #endif
LOG(LogWarning) << "Found invalid rule type \"" << ruleType << LOG(LogWarning) << "Found invalid rule type \"" << ruleType << "\" for emulator \""
"\" for emulator \"" << emulatorName << "\", skipping entry"; << emulatorName << "\", skipping entry";
continue; continue;
} }
for (pugi::xml_node entry = rule.child("entry"); entry; for (pugi::xml_node entry = rule.child("entry"); entry;
@ -146,28 +142,26 @@ void FindRules::loadFindRules()
#endif #endif
} }
for (pugi::xml_node core = ruleList.child("core"); core; for (pugi::xml_node core = ruleList.child("core"); core; core = core.next_sibling("core")) {
core = core.next_sibling("core")) {
std::string coreName = core.attribute("name").as_string(); std::string coreName = core.attribute("name").as_string();
if (coreName.empty()) { if (coreName.empty()) {
LOG(LogWarning) << "Found core tag without name attribute, skipping entry"; LOG(LogWarning) << "Found core tag without name attribute, skipping entry";
continue; continue;
} }
if (mCores.find(coreName) != mCores.end()) { if (mCores.find(coreName) != mCores.end()) {
LOG(LogWarning) << "Found repeating core tag \"" << coreName << LOG(LogWarning) << "Found repeating core tag \"" << coreName << "\", skipping entry";
"\", skipping entry";
continue; continue;
} }
for (pugi::xml_node rule = core.child("rule"); rule; rule = rule.next_sibling("rule")) { for (pugi::xml_node rule = core.child("rule"); rule; rule = rule.next_sibling("rule")) {
std::string ruleType = rule.attribute("type").as_string(); std::string ruleType = rule.attribute("type").as_string();
if (ruleType.empty()) { if (ruleType.empty()) {
LOG(LogWarning) << "Found rule tag without type attribute for core \"" << LOG(LogWarning) << "Found rule tag without type attribute for core \"" << coreName
coreName << "\", skipping entry"; << "\", skipping entry";
continue; continue;
} }
if (ruleType != "corepath") { if (ruleType != "corepath") {
LOG(LogWarning) << "Found invalid rule type \"" << ruleType << LOG(LogWarning) << "Found invalid rule type \"" << ruleType << "\" for core \""
"\" for core \"" << coreName << "\", skipping entry"; << coreName << "\", skipping entry";
continue; continue;
} }
for (pugi::xml_node entry = rule.child("entry"); entry; for (pugi::xml_node entry = rule.child("entry"); entry;
@ -182,23 +176,22 @@ void FindRules::loadFindRules()
} }
} }
SystemData::SystemData( SystemData::SystemData(const std::string& name,
const std::string& name,
const std::string& fullName, const std::string& fullName,
SystemEnvironmentData* envData, SystemEnvironmentData* envData,
const std::string& themeFolder, const std::string& themeFolder,
bool CollectionSystem, bool CollectionSystem,
bool CustomCollectionSystem) bool CustomCollectionSystem)
: mName(name), : mName(name)
mFullName(fullName), , mFullName(fullName)
mEnvData(envData), , mEnvData(envData)
mThemeFolder(themeFolder), , mThemeFolder(themeFolder)
mIsCollectionSystem(CollectionSystem), , mIsCollectionSystem(CollectionSystem)
mIsCustomCollectionSystem(CustomCollectionSystem), , mIsCustomCollectionSystem(CustomCollectionSystem)
mIsGroupedCustomCollectionSystem(false), , mIsGroupedCustomCollectionSystem(false)
mIsGameSystem(true), , mIsGameSystem(true)
mScrapeFlag(false), , mScrapeFlag(false)
mPlaceholder(nullptr) , mPlaceholder(nullptr)
{ {
mFilterIndex = new FileFilterIndex(); mFilterIndex = new FileFilterIndex();
@ -278,11 +271,20 @@ bool SystemData::populateFolder(FileData* folder)
it != dirContent.cend(); it++) { it != dirContent.cend(); it++) {
filePath = *it; filePath = *it;
// Skip any recursive symlinks as those would hang the application at various places.
if (Utils::FileSystem::isSymlink(filePath)) {
if (Utils::FileSystem::resolveSymlink(filePath) ==
Utils::FileSystem::getFileName(filePath)) {
LOG(LogWarning) << "Skipped \"" << filePath << "\" as it's a recursive symlink";
continue;
}
}
// Skip hidden files and folders. // Skip hidden files and folders.
if (!showHiddenFiles && Utils::FileSystem::isHidden(filePath)) { if (!showHiddenFiles && Utils::FileSystem::isHidden(filePath)) {
LOG(LogDebug) << "SystemData::populateFolder(): Skipping hidden " << LOG(LogDebug) << "SystemData::populateFolder(): Skipping hidden "
(Utils::FileSystem::isDirectory(filePath) ? "directory \"" : "file \"") << << (Utils::FileSystem::isDirectory(filePath) ? "directory \"" : "file \"")
filePath << "\""; << filePath << "\"";
continue; continue;
} }
@ -318,7 +320,8 @@ bool SystemData::populateFolder(FileData* folder)
const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(filePath); const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(filePath);
const std::string canonicalStartPath = const std::string canonicalStartPath =
Utils::FileSystem::getCanonicalPath(mEnvData->mStartPath); Utils::FileSystem::getCanonicalPath(mEnvData->mStartPath);
const std::string combinedPath = mEnvData->mStartPath + const std::string combinedPath =
mEnvData->mStartPath +
canonicalPath.substr(canonicalStartPath.size(), canonicalPath.substr(canonicalStartPath.size(),
canonicalStartPath.size() - canonicalPath.size()); canonicalStartPath.size() - canonicalPath.size());
if (filePath.find(combinedPath) == 0) { if (filePath.find(combinedPath) == 0) {
@ -326,6 +329,7 @@ bool SystemData::populateFolder(FileData* folder)
continue; continue;
} }
} }
FileData* newFolder = new FileData(FOLDER, filePath, mEnvData, this); FileData* newFolder = new FileData(FOLDER, filePath, mEnvData, this);
populateFolder(newFolder); populateFolder(newFolder);
@ -343,16 +347,14 @@ void SystemData::indexAllGameFilters(const FileData* folder)
{ {
const std::vector<FileData*>& children = folder->getChildren(); const std::vector<FileData*>& children = folder->getChildren();
for (std::vector<FileData*>::const_iterator it = children.cbegin(); for (std::vector<FileData*>::const_iterator it = children.cbegin(); // Line break.
it != children.cend(); it++) { it != children.cend(); it++) {
switch ((*it)->getType()) { switch ((*it)->getType()) {
case GAME: { case GAME:
mFilterIndex->addToIndex(*it); mFilterIndex->addToIndex(*it);
}
break; break;
case FOLDER: { case FOLDER:
indexAllGameFilters(*it); indexAllGameFilters(*it);
}
break; break;
default: default:
break; break;
@ -434,14 +436,14 @@ bool SystemData::loadConfig()
// Check that the ROM directory for the system is valid or otherwise abort the processing. // Check that the ROM directory for the system is valid or otherwise abort the processing.
if (!Utils::FileSystem::exists(path)) { if (!Utils::FileSystem::exists(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
name << "\" as the defined ROM directory \"" << path << "\" does not exist"; << "\" as the defined ROM directory \"" << path << "\" does not exist";
continue; continue;
} }
if (!Utils::FileSystem::isDirectory(path)) { if (!Utils::FileSystem::isDirectory(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
name << "\" as the defined ROM directory \"" << path << << "\" as the defined ROM directory \"" << path
"\" is not actually a directory"; << "\" is not actually a directory";
continue; continue;
} }
if (Utils::FileSystem::isSymlink(path)) { if (Utils::FileSystem::isSymlink(path)) {
@ -449,9 +451,9 @@ bool SystemData::loadConfig()
// as that would lead to an infite loop, meaning the application would never start. // as that would lead to an infite loop, meaning the application would never start.
std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath); std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath);
if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) { if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) {
LOG(LogWarning) << "Skipping system \"" << name << LOG(LogWarning) << "Skipping system \"" << name
"\" as the defined ROM directory \"" << path << << "\" as the defined ROM directory \"" << path
"\" is an infinitely recursive symlink"; << "\" is an infinitely recursive symlink";
continue; continue;
} }
} }
@ -464,6 +466,12 @@ bool SystemData::loadConfig()
// Platform ID list // Platform ID list
const std::string platformList = const std::string platformList =
Utils::String::toLower(system.child("platform").text().get()); Utils::String::toLower(system.child("platform").text().get());
if (platformList == "") {
LOG(LogWarning) << "No platform defined for system \"" << name
<< "\", scraper searches will be inaccurate";
}
std::vector<std::string> platformStrs = readList(platformList); std::vector<std::string> platformStrs = readList(platformList);
std::vector<PlatformIds::PlatformId> platformIds; std::vector<PlatformIds::PlatformId> platformIds;
for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) { for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) {
@ -480,8 +488,8 @@ bool SystemData::loadConfig()
// If there's a platform entry defined but it does not match the list of supported // If there's a platform entry defined but it does not match the list of supported
// platforms, then generate a warning. // platforms, then generate a warning.
if (str != "" && platformId == PlatformIds::PLATFORM_UNKNOWN) if (str != "" && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \"" << LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \""
name << "\""; << name << "\", scraper searches will be inaccurate";
else if (platformId != PlatformIds::PLATFORM_UNKNOWN) else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId); platformIds.push_back(platformId);
} }
@ -492,12 +500,13 @@ bool SystemData::loadConfig()
// Validate. // Validate.
if (name.empty()) { if (name.empty()) {
LOG(LogError) << LOG(LogError)
"A system in the es_systems.xml file has no name defined, skipping entry"; << "A system in the es_systems.xml file has no name defined, skipping entry";
continue; continue;
} }
else if (fullname.empty() || path.empty() || extensions.empty() || cmd.empty()) { else if (fullname.empty() || path.empty() || extensions.empty() || cmd.empty()) {
LOG(LogError) << "System \"" << name << "\" is missing the fullname, path, " LOG(LogError) << "System \"" << name
<< "\" is missing the fullname, path, "
"extension, or command tag, skipping entry"; "extension, or command tag, skipping entry";
continue; continue;
} }
@ -526,8 +535,7 @@ bool SystemData::loadConfig()
// If the option to show hidden games has been disabled, then check whether all // If the option to show hidden games has been disabled, then check whether all
// games for the system are hidden. That will flag the system as empty. // games for the system are hidden. That will flag the system as empty.
if (!Settings::getInstance()->getBool("ShowHiddenGames")) { if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
std::vector<FileData*> recursiveGames = std::vector<FileData*> recursiveGames = newSys->getRootFolder()->getChildrenRecursive();
newSys->getRootFolder()->getChildrenRecursive();
onlyHidden = true; onlyHidden = true;
for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) { for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) {
if ((*it)->getType() != FOLDER) { if ((*it)->getType() != FOLDER) {
@ -539,8 +547,8 @@ bool SystemData::loadConfig()
} }
if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) { if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
name << "\" as no files matched any of the defined file extensions"; << "\" as no files matched any of the defined file extensions";
delete newSys; delete newSys;
} }
else { else {
@ -550,8 +558,7 @@ bool SystemData::loadConfig()
// Sort systems by their full names. // Sort systems by their full names.
std::sort(std::begin(sSystemVector), std::end(sSystemVector), std::sort(std::begin(sSystemVector), std::end(sSystemVector),
[](SystemData* a, SystemData* b) { [](SystemData* a, SystemData* b) { return a->getFullName() < b->getFullName(); });
return a->getFullName() < b->getFullName(); });
// Don't load any collections if there are no systems available. // Don't load any collections if there are no systems available.
if (sSystemVector.size() > 0) if (sSystemVector.size() > 0)
@ -571,12 +578,12 @@ void SystemData::deleteSystems()
std::string SystemData::getConfigPath(bool legacyWarning) std::string SystemData::getConfigPath(bool legacyWarning)
{ {
if (legacyWarning) { if (legacyWarning) {
std::string legacyConfigFile = Utils::FileSystem::getHomePath() + std::string legacyConfigFile =
"/.emulationstation/es_systems.cfg"; Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg";
if (Utils::FileSystem::exists(legacyConfigFile)) { if (Utils::FileSystem::exists(legacyConfigFile)) {
LOG(LogInfo) << "Found legacy systems configuration file \"" << legacyConfigFile << LOG(LogInfo) << "Found legacy systems configuration file \"" << legacyConfigFile
"\", to retain your customizations move it to " << "\", to retain your customizations move it to "
"\"custom_systems/es_systems.xml\" or otherwise delete the file"; "\"custom_systems/es_systems.xml\" or otherwise delete the file";
} }
} }
@ -600,14 +607,12 @@ std::string SystemData::getConfigPath(bool legacyWarning)
} }
#if defined(_WIN64) #if defined(_WIN64)
path = ResourceManager::getInstance()-> path =
getResourcePath(":/systems/windows/es_systems.xml", true); ResourceManager::getInstance()->getResourcePath(":/systems/windows/es_systems.xml", true);
#elif defined(__APPLE__) #elif defined(__APPLE__)
path = ResourceManager::getInstance()-> path = ResourceManager::getInstance()->getResourcePath(":/systems/macos/es_systems.xml", true);
getResourcePath(":/systems/macos/es_systems.xml", true);
#else #else
path = ResourceManager::getInstance()-> path = ResourceManager::getInstance()->getResourcePath(":/systems/unix/es_systems.xml", true);
getResourcePath(":/systems/unix/es_systems.xml", true);
#endif #endif
return path; return path;
@ -626,8 +631,8 @@ bool SystemData::createSystemDirectories()
LOG(LogInfo) << "Generating ROM directory structure..."; LOG(LogInfo) << "Generating ROM directory structure...";
if (Utils::FileSystem::exists(rompath) && Utils::FileSystem::isRegularFile(rompath)) { if (Utils::FileSystem::exists(rompath) && Utils::FileSystem::isRegularFile(rompath)) {
LOG(LogError) << LOG(LogError) << "Requested ROM directory \"" << rompath
"Requested ROM directory \"" << rompath << "\" is actually a file, aborting"; << "\" is actually a file, aborting";
return true; return true;
} }
@ -692,7 +697,8 @@ bool SystemData::createSystemDirectories()
// Check that the %ROMPATH% variable is actually used for the path element. // Check that the %ROMPATH% variable is actually used for the path element.
// If not, skip the system. // If not, skip the system.
if (path.find("%ROMPATH%") != 0) { if (path.find("%ROMPATH%") != 0) {
LOG(LogWarning) << "The path element for system \"" << name << "\" does not " LOG(LogWarning) << "The path element for system \"" << name
<< "\" does not "
"utilize the %ROMPATH% variable, skipping entry"; "utilize the %ROMPATH% variable, skipping entry";
continue; continue;
} }
@ -702,14 +708,13 @@ bool SystemData::createSystemDirectories()
// Trim any leading directory separator characters. // Trim any leading directory separator characters.
systemDir.erase(systemDir.begin(), systemDir.erase(systemDir.begin(),
std::find_if(systemDir.begin(), systemDir.end(), [](char c) { std::find_if(systemDir.begin(), systemDir.end(),
return c != '/' && c != '\\'; [](char c) { return c != '/' && c != '\\'; }));
}));
if (!Utils::FileSystem::exists(rompath + systemDir)) { if (!Utils::FileSystem::exists(rompath + systemDir)) {
if (!Utils::FileSystem::createDirectory(rompath + systemDir)) { if (!Utils::FileSystem::createDirectory(rompath + systemDir)) {
LOG(LogError) << "Couldn't create system directory \"" << systemDir << LOG(LogError) << "Couldn't create system directory \"" << systemDir
"\", permission problems or disk full?"; << "\", permission problems or disk full?";
return true; return true;
} }
else { else {
@ -731,15 +736,16 @@ bool SystemData::createSystemDirectories()
} }
#if defined(_WIN64) #if defined(_WIN64)
systemInfoFile.open(Utils::String::stringToWideString(rompath + systemInfoFile.open(
systemDir + systemInfoFileName).c_str()); Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName).c_str());
#else #else
systemInfoFile.open(rompath + systemDir + systemInfoFileName); systemInfoFile.open(rompath + systemDir + systemInfoFileName);
#endif #endif
if (systemInfoFile.fail()) { if (systemInfoFile.fail()) {
LOG(LogError) << "Couldn't create system information file \"" << rompath + LOG(LogError) << "Couldn't create system information file \""
systemDir + systemInfoFileName << "\", permission problems or disk full?"; << rompath + systemDir + systemInfoFileName
<< "\", permission problems or disk full?";
systemInfoFile.close(); systemInfoFile.close();
return true; return true;
} }
@ -761,12 +767,12 @@ bool SystemData::createSystemDirectories()
systemsVector.push_back(systemDir + ": " + fullname); systemsVector.push_back(systemDir + ": " + fullname);
if (replaceInfoFile) { if (replaceInfoFile) {
LOG(LogInfo) << "Replaced existing system information file \"" << LOG(LogInfo) << "Replaced existing system information file \""
rompath + systemDir + systemInfoFileName << "\""; << rompath + systemDir + systemInfoFileName << "\"";
} }
else { else {
LOG(LogInfo) << "Created system information file \"" << LOG(LogInfo) << "Created system information file \""
rompath + systemDir + systemInfoFileName << "\""; << rompath + systemDir + systemInfoFileName << "\"";
} }
} }
@ -858,8 +864,8 @@ std::string SystemData::getGamelistPath(bool forWrite) const
if (Utils::FileSystem::exists(filePath)) if (Utils::FileSystem::exists(filePath))
return filePath; return filePath;
filePath = Utils::FileSystem::getHomePath() + "/.emulationstation/gamelists/" + filePath = Utils::FileSystem::getHomePath() + "/.emulationstation/gamelists/" + mName +
mName + "/gamelist.xml"; "/gamelist.xml";
// Make sure the directory exists if we're going to write to it, // Make sure the directory exists if we're going to write to it,
// or crashes will happen. // or crashes will happen.
@ -890,17 +896,12 @@ std::string SystemData::getThemePath() const
return localThemePath; return localThemePath;
// Not system theme, try default system theme in theme set. // Not system theme, try default system theme in theme set.
localThemePath = Utils::FileSystem::getParent(Utils::FileSystem::getParent(localThemePath)) + localThemePath =
"/theme.xml"; Utils::FileSystem::getParent(Utils::FileSystem::getParent(localThemePath)) + "/theme.xml";
return localThemePath; return localThemePath;
} }
bool SystemData::hasGamelist() const
{
return (Utils::FileSystem::exists(getGamelistPath(false)));
}
SystemData* SystemData::getRandomSystem(const SystemData* currentSystem) SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
{ {
unsigned int total = 0; unsigned int total = 0;
@ -933,8 +934,7 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
} }
} }
} }
} } while (randomSystem == currentSystem);
while (randomSystem == currentSystem);
return randomSystem; return randomSystem;
} }
@ -947,13 +947,17 @@ FileData* SystemData::getRandomGame(const FileData* currentGame)
// If we're in the custom collection group list, then get the list of collections, // If we're in the custom collection group list, then get the list of collections,
// otherwise get a list of all the folder and file entries in the view. // otherwise get a list of all the folder and file entries in the view.
if (currentGame && currentGame->getType() == FOLDER && currentGame-> if (currentGame && currentGame->getType() == FOLDER &&
getSystem()->isGroupedCustomCollection()) { currentGame->getSystem()->isGroupedCustomCollection()) {
gameList = mRootFolder->getParent()->getChildrenListToDisplay(); gameList = mRootFolder->getParent()->getChildrenListToDisplay();
} }
else { else {
gameList = ViewController::get()->getGameListView(mRootFolder-> gameList = ViewController::get()
getSystem()).get()->getCursor()->getParent()->getChildrenListToDisplay(); ->getGameListView(mRootFolder->getSystem())
.get()
->getCursor()
->getParent()
->getChildrenListToDisplay();
} }
if (gameList.size() > 0 && gameList.front()->getParent()->getOnlyFoldersFlag()) if (gameList.size() > 0 && gameList.front()->getParent()->getOnlyFoldersFlag())
@ -997,8 +1001,7 @@ FileData* SystemData::getRandomGame(const FileData* currentGame)
std::mt19937 engine { randDev() }; std::mt19937 engine { randDev() };
std::uniform_int_distribution<int> uniform_dist(0, total - 1); std::uniform_int_distribution<int> uniform_dist(0, total - 1);
target = uniform_dist(engine); target = uniform_dist(engine);
} } while (currentGame && gameList.at(target) == currentGame);
while (currentGame && gameList.at(target) == currentGame);
return gameList.at(target); return gameList.at(target);
} }
@ -1011,23 +1014,25 @@ void SystemData::sortSystem(bool reloadGamelist, bool jumpToFirstRow)
bool favoritesSorting; bool favoritesSorting;
if (this->isCustomCollection() || if (this->isCustomCollection() ||
(this->isCollection() && this->getFullName() == "collections")) (this->isCollection() && this->getFullName() == "collections")) {
favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom"); favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom");
else }
else {
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
}
FileData* rootFolder = getRootFolder(); FileData* rootFolder = getRootFolder();
// Assign the sort type to all grouped custom collections. // Assign the sort type to all grouped custom collections.
if (mIsCollectionSystem && mFullName == "collections") { if (mIsCollectionSystem && mFullName == "collections") {
for (auto it = rootFolder->getChildren().begin(); for (auto it = rootFolder->getChildren().begin(); // Line break.
it != rootFolder->getChildren().end(); it++) { it != rootFolder->getChildren().end(); it++) {
setupSystemSortType((*it)->getSystem()->getRootFolder()); setupSystemSortType((*it)->getSystem()->getRootFolder());
} }
} }
setupSystemSortType(rootFolder); setupSystemSortType(rootFolder);
rootFolder->sort(rootFolder->getSortTypeFromString( rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->getSortTypeString()),
rootFolder->getSortTypeString()), favoritesSorting); favoritesSorting);
if (reloadGamelist) if (reloadGamelist)
ViewController::get()->reloadGameListView(this, false); ViewController::get()->reloadGameListView(this, false);
@ -1070,7 +1075,8 @@ void SystemData::loadTheme()
} }
} }
void SystemData::writeMetaData() { void SystemData::writeMetaData()
{
if (Settings::getInstance()->getBool("IgnoreGamelist") || mIsCollectionSystem) if (Settings::getInstance()->getBool("IgnoreGamelist") || mIsCollectionSystem)
return; return;
@ -1078,7 +1084,8 @@ void SystemData::writeMetaData() {
updateGamelist(this); updateGamelist(this);
} }
void SystemData::onMetaDataSavePoint() { void SystemData::onMetaDataSavePoint()
{
if (Settings::getInstance()->getString("SaveGamelistsMode") != "always") if (Settings::getInstance()->getString("SaveGamelistsMode") != "always")
return; return;
@ -1092,14 +1099,14 @@ void SystemData::setupSystemSortType(FileData* mRootFolder)
for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) { for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) {
if (FileSorts::SortTypes.at(i).description == if (FileSorts::SortTypes.at(i).description ==
Settings::getInstance()->getString("DefaultSortOrder")) { Settings::getInstance()->getString("DefaultSortOrder")) {
mRootFolder->setSortTypeString(Settings::getInstance()-> mRootFolder->setSortTypeString(
getString("DefaultSortOrder")); Settings::getInstance()->getString("DefaultSortOrder"));
break; break;
} }
} }
} }
// If no valid sort type was defined in the configuration file, set to default sorting. // If no valid sort type was defined in the configuration file, set to default sorting.
if (mRootFolder->getSortTypeString() == "") if (mRootFolder->getSortTypeString() == "")
mRootFolder->setSortTypeString(Settings::getInstance()-> mRootFolder->setSortTypeString(
getDefaultString("DefaultSortOrder")); Settings::getInstance()->getDefaultString("DefaultSortOrder"));
} }

View file

@ -35,7 +35,6 @@ class FindRules
{ {
public: public:
FindRules(); FindRules();
~FindRules();
void loadFindRules(); void loadFindRules();
@ -61,8 +60,7 @@ private:
class SystemData class SystemData
{ {
public: public:
SystemData( SystemData(const std::string& name,
const std::string& name,
const std::string& fullName, const std::string& fullName,
SystemEnvironmentData* envData, SystemEnvironmentData* envData,
const std::string& themeFolder, const std::string& themeFolder,
@ -71,28 +69,32 @@ public:
~SystemData(); ~SystemData();
inline FileData* getRootFolder() const { return mRootFolder; }; FileData* getRootFolder() const { return mRootFolder; }
inline const std::string& getName() const { return mName; } const std::string& getName() const { return mName; }
inline const std::string& getFullName() const { return mFullName; } const std::string& getFullName() const { return mFullName; }
inline const std::string& getStartPath() const { return mEnvData->mStartPath; } const std::string& getStartPath() const { return mEnvData->mStartPath; }
inline const std::vector<std::string>& getExtensions() const const std::vector<std::string>& getExtensions() const { return mEnvData->mSearchExtensions; }
{ return mEnvData->mSearchExtensions; } const std::string& getThemeFolder() const { return mThemeFolder; }
inline const std::string& getThemeFolder() const { return mThemeFolder; } SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } const std::vector<PlatformIds::PlatformId>& getPlatformIds() const
inline const std::vector<PlatformIds::PlatformId>& getPlatformIds() const {
{ return mEnvData->mPlatformIds; } return mEnvData->mPlatformIds;
inline bool hasPlatformId(PlatformIds::PlatformId id) { if (!mEnvData) return false; }
return std::find(mEnvData->mPlatformIds.cbegin(), mEnvData->mPlatformIds.cend(), id) bool hasPlatformId(PlatformIds::PlatformId id)
!= mEnvData->mPlatformIds.cend(); } {
if (!mEnvData)
return false;
return std::find(mEnvData->mPlatformIds.cbegin(), mEnvData->mPlatformIds.cend(), id) !=
mEnvData->mPlatformIds.cend();
}
inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; } const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; }
std::string getGamelistPath(bool forWrite) const; std::string getGamelistPath(bool forWrite) const;
bool hasGamelist() const;
std::string getThemePath() const; std::string getThemePath() const;
std::pair<unsigned int, unsigned int> getDisplayedGameCount() const; std::pair<unsigned int, unsigned int> getDisplayedGameCount() const;
bool getScrapeFlag() { return mScrapeFlag; }; bool getScrapeFlag() { return mScrapeFlag; }
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; } void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
static void deleteSystems(); static void deleteSystems();
@ -106,16 +108,22 @@ public:
static std::vector<SystemData*> sSystemVector; static std::vector<SystemData*> sSystemVector;
static std::unique_ptr<FindRules> sFindRules; static std::unique_ptr<FindRules> sFindRules;
inline std::vector<SystemData*>::const_iterator getIterator() const std::vector<SystemData*>::const_iterator getIterator() const
{ return std::find(sSystemVector.cbegin(), sSystemVector.cend(), this); }; {
inline std::vector<SystemData*>::const_reverse_iterator getRevIterator() const return std::find(sSystemVector.cbegin(), sSystemVector.cend(), this);
{ return std::find(sSystemVector.crbegin(), sSystemVector.crend(), this); }; }
inline bool isCollection() { return mIsCollectionSystem; }; std::vector<SystemData*>::const_reverse_iterator getRevIterator() const
inline bool isCustomCollection() { return mIsCustomCollectionSystem; }; {
inline bool isGroupedCustomCollection() { return mIsGroupedCustomCollectionSystem; }; return std::find(sSystemVector.crbegin(), sSystemVector.crend(), this);
}
bool isCollection() { return mIsCollectionSystem; }
bool isCustomCollection() { return mIsCustomCollectionSystem; }
bool isGroupedCustomCollection() { return mIsGroupedCustomCollectionSystem; }
void setIsGroupedCustomCollection(bool isGroupedCustom) void setIsGroupedCustomCollection(bool isGroupedCustom)
{ mIsGroupedCustomCollectionSystem = isGroupedCustom; }; {
inline bool isGameSystem() { return mIsGameSystem; }; mIsGroupedCustomCollectionSystem = isGroupedCustom;
};
bool isGameSystem() { return mIsGameSystem; }
bool isVisible(); bool isVisible();
@ -123,14 +131,14 @@ public:
SystemData* getPrev() const; SystemData* getPrev() const;
static SystemData* getRandomSystem(const SystemData* currentSystem); static SystemData* getRandomSystem(const SystemData* currentSystem);
FileData* getRandomGame(const FileData* currentGame = nullptr); FileData* getRandomGame(const FileData* currentGame = nullptr);
FileData* getPlaceholder() { return mPlaceholder; }; FileData* getPlaceholder() { return mPlaceholder; }
void sortSystem(bool reloadGamelist = true, bool jumpToFirstRow = false); void sortSystem(bool reloadGamelist = true, bool jumpToFirstRow = false);
// Load or re-load theme. // Load or re-load theme.
void loadTheme(); void loadTheme();
FileFilterIndex* getIndex() { return mFilterIndex; }; FileFilterIndex* getIndex() { return mFilterIndex; }
void onMetaDataSavePoint(); void onMetaDataSavePoint();
void writeMetaData(); void writeMetaData();

View file

@ -10,21 +10,18 @@
#include "SystemScreensaver.h" #include "SystemScreensaver.h"
#include "components/VideoFFmpegComponent.h" #include "components/VideoFFmpegComponent.h"
#if defined(_RPI_)
#include "components/VideoOmxComponent.h"
#endif
#if defined(BUILD_VLC_PLAYER) #if defined(BUILD_VLC_PLAYER)
#include "components/VideoVlcComponent.h" #include "components/VideoVlcComponent.h"
#endif #endif
#include "resources/Font.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "views/gamelist/IGameListView.h"
#include "views/ViewController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "FileData.h" #include "FileData.h"
#include "Log.h" #include "Log.h"
#include "SystemData.h" #include "SystemData.h"
#include "resources/Font.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
#include "views/gamelist/IGameListView.h"
#include <random> #include <random>
#include <time.h> #include <time.h>
@ -36,24 +33,23 @@
#define FADE_TIME 300 #define FADE_TIME 300
SystemScreensaver::SystemScreensaver( SystemScreensaver::SystemScreensaver(Window* window)
Window* window) : mWindow(window)
: mWindow(window), , mState(STATE_INACTIVE)
mState(STATE_INACTIVE), , mImageScreensaver(nullptr)
mImageScreensaver(nullptr), , mVideoScreensaver(nullptr)
mVideoScreensaver(nullptr), , mCurrentGame(nullptr)
mCurrentGame(nullptr), , mPreviousGame(nullptr)
mPreviousGame(nullptr), , mTimer(0)
mTimer(0), , mMediaSwapTime(0)
mMediaSwapTime(0), , mTriggerNextGame(false)
mTriggerNextGame(false), , mHasMediaFiles(false)
mHasMediaFiles(false), , mFallbackScreensaver(false)
mFallbackScreensaver(false), , mOpacity(0.0f)
mOpacity(0.0f), , mDimValue(1.0)
mDimValue(1.0), , mRectangleFadeIn(50)
mRectangleFadeIn(50), , mTextFadeIn(0)
mTextFadeIn(0), , mSaturationAmount(1.0)
mSaturationAmount(1.0)
{ {
mWindow->setScreensaver(this); mWindow->setScreensaver(this);
} }
@ -65,21 +61,6 @@ SystemScreensaver::~SystemScreensaver()
delete mImageScreensaver; delete mImageScreensaver;
} }
bool SystemScreensaver::allowSleep()
{
return ((mVideoScreensaver == nullptr) && (mImageScreensaver == nullptr));
}
bool SystemScreensaver::isScreensaverActive()
{
return (mState != STATE_INACTIVE);
}
bool SystemScreensaver::isFallbackScreensaver()
{
return mFallbackScreensaver;
}
void SystemScreensaver::startScreensaver(bool generateMediaList) void SystemScreensaver::startScreensaver(bool generateMediaList)
{ {
std::string path = ""; std::string path = "";
@ -179,15 +160,7 @@ void SystemScreensaver::startScreensaver(bool generateMediaList)
if (Settings::getInstance()->getBool("ScreensaverVideoGameInfo")) if (Settings::getInstance()->getBool("ScreensaverVideoGameInfo"))
generateOverlayInfo(); generateOverlayInfo();
#if defined(_RPI_) #if defined(BUILD_VLC_PLAYER)
// Create the correct type of video component.
if (Settings::getInstance()->getBool("ScreensaverOmxPlayer"))
mVideoScreensaver = new VideoOmxComponent(mWindow);
else if (Settings::getInstance()->getString("VideoPlayer") == "vlc")
mVideoScreensaver = new VideoVlcComponent(mWindow);
else
mVideoScreensaver = new VideoFFmpegComponent(mWindow);
#elif defined(BUILD_VLC_PLAYER)
if (Settings::getInstance()->getString("VideoPlayer") == "vlc") if (Settings::getInstance()->getString("VideoPlayer") == "vlc")
mVideoScreensaver = new VideoVlcComponent(mWindow); mVideoScreensaver = new VideoVlcComponent(mWindow);
else else
@ -229,16 +202,17 @@ void SystemScreensaver::stopScreensaver()
mState = STATE_INACTIVE; mState = STATE_INACTIVE;
mDimValue = 1.0; mDimValue = 1.0f;
mRectangleFadeIn = 50; mRectangleFadeIn = 50;
mTextFadeIn = 0; mTextFadeIn = 0;
mSaturationAmount = 1.0; mSaturationAmount = 1.0f;
if (mGameOverlay) if (mGameOverlay)
mGameOverlay.reset(); mGameOverlay.reset();
} }
void SystemScreensaver::nextGame() { void SystemScreensaver::nextGame()
{
stopScreensaver(); stopScreensaver();
startScreensaver(false); startScreensaver(false);
} }
@ -249,8 +223,8 @@ void SystemScreensaver::launchGame()
// Launching game // Launching game
ViewController::get()->triggerGameLaunch(mCurrentGame); ViewController::get()->triggerGameLaunch(mCurrentGame);
ViewController::get()->goToGameList(mCurrentGame->getSystem()); ViewController::get()->goToGameList(mCurrentGame->getSystem());
IGameListView* view = ViewController::get()-> IGameListView* view =
getGameListView(mCurrentGame->getSystem()).get(); ViewController::get()->getGameListView(mCurrentGame->getSystem()).get();
view->setCursor(mCurrentGame); view->setCursor(mCurrentGame);
ViewController::get()->cancelViewTransitions(); ViewController::get()->cancelViewTransitions();
} }
@ -261,8 +235,8 @@ void SystemScreensaver::goToGame()
if (mCurrentGame != nullptr) { if (mCurrentGame != nullptr) {
// Go to the game in the gamelist view, but don't launch it. // Go to the game in the gamelist view, but don't launch it.
ViewController::get()->goToGameList(mCurrentGame->getSystem()); ViewController::get()->goToGameList(mCurrentGame->getSystem());
IGameListView* view = ViewController::get()-> IGameListView* view =
getGameListView(mCurrentGame->getSystem()).get(); ViewController::get()->getGameListView(mCurrentGame->getSystem()).get();
view->setCursor(mCurrentGame); view->setCursor(mCurrentGame);
ViewController::get()->cancelViewTransitions(); ViewController::get()->cancelViewTransitions();
} }
@ -312,13 +286,13 @@ void SystemScreensaver::renderScreensaver()
if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo") && if (Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo") &&
mGameOverlay) { mGameOverlay) {
if (mGameOverlayRectangleCoords.size() == 4) { if (mGameOverlayRectangleCoords.size() == 4) {
Renderer::drawRect(mGameOverlayRectangleCoords[0], Renderer::drawRect(
mGameOverlayRectangleCoords[1], mGameOverlayRectangleCoords[2], mGameOverlayRectangleCoords[0], mGameOverlayRectangleCoords[1],
mGameOverlayRectangleCoords[3], 0x00000000 | mRectangleFadeIn, mGameOverlayRectangleCoords[2], mGameOverlayRectangleCoords[3],
0x00000000 | mRectangleFadeIn ); 0x00000000 | mRectangleFadeIn, 0x00000000 | mRectangleFadeIn);
} }
mRectangleFadeIn = Math::clamp(mRectangleFadeIn + 6 + mRectangleFadeIn =
mRectangleFadeIn / 20, 0, 170); Math::clamp(mRectangleFadeIn + 6 + mRectangleFadeIn / 20, 0, 170);
mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn); mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn);
if (mTextFadeIn > 50) if (mTextFadeIn > 50)
@ -341,6 +315,7 @@ void SystemScreensaver::renderScreensaver()
if (Settings::getInstance()->getBool("ScreensaverVideoBlur")) { if (Settings::getInstance()->getBool("ScreensaverVideoBlur")) {
shaders |= Renderer::SHADER_BLUR_HORIZONTAL; shaders |= Renderer::SHADER_BLUR_HORIZONTAL;
float heightModifier = Renderer::getScreenHeightModifier(); float heightModifier = Renderer::getScreenHeightModifier();
// clang-format off
if (heightModifier < 1) if (heightModifier < 1)
videoParameters.blurPasses = 2; // Below 1080 videoParameters.blurPasses = 2; // Below 1080
else if (heightModifier >= 4) else if (heightModifier >= 4)
@ -355,6 +330,7 @@ void SystemScreensaver::renderScreensaver()
videoParameters.blurPasses = 3; // 1440 videoParameters.blurPasses = 3; // 1440
else if (heightModifier >= 1) else if (heightModifier >= 1)
videoParameters.blurPasses = 2; // 1080 videoParameters.blurPasses = 2; // 1080
// clang-format on
} }
Renderer::shaderPostprocessing(shaders, videoParameters); Renderer::shaderPostprocessing(shaders, videoParameters);
#endif #endif
@ -363,13 +339,13 @@ void SystemScreensaver::renderScreensaver()
#if defined(USE_OPENGL_21) #if defined(USE_OPENGL_21)
Renderer::shaderPostprocessing(Renderer::SHADER_OPACITY); Renderer::shaderPostprocessing(Renderer::SHADER_OPACITY);
#endif #endif
Renderer::drawRect(mGameOverlayRectangleCoords[0], Renderer::drawRect(
mGameOverlayRectangleCoords[1], mGameOverlayRectangleCoords[2], mGameOverlayRectangleCoords[0], mGameOverlayRectangleCoords[1],
mGameOverlayRectangleCoords[3], 0x00000000 | mRectangleFadeIn, mGameOverlayRectangleCoords[2], mGameOverlayRectangleCoords[3],
0x00000000 | mRectangleFadeIn ); 0x00000000 | mRectangleFadeIn, 0x00000000 | mRectangleFadeIn);
} }
mRectangleFadeIn = Math::clamp(mRectangleFadeIn + 6 + mRectangleFadeIn =
mRectangleFadeIn / 20, 0, 170); Math::clamp(mRectangleFadeIn + 6 + mRectangleFadeIn / 20, 0, 170);
mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn); mGameOverlay.get()->setColor(0xFFFFFF00 | mTextFadeIn);
if (mTextFadeIn > 50) if (mTextFadeIn > 50)
@ -395,8 +371,8 @@ void SystemScreensaver::renderScreensaver()
if (mSaturationAmount > 0.0) if (mSaturationAmount > 0.0)
mSaturationAmount = Math::clamp(mSaturationAmount - 0.035f, 0.0f, 1.0f); mSaturationAmount = Math::clamp(mSaturationAmount - 0.035f, 0.0f, 1.0f);
#else #else
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
Renderer::getScreenHeight(), 0x000000A0, 0x000000A0); 0x000000A0, 0x000000A0);
#endif #endif
} }
else if (Settings::getInstance()->getString("ScreensaverType") == "black") { else if (Settings::getInstance()->getString("ScreensaverType") == "black") {
@ -407,8 +383,8 @@ void SystemScreensaver::renderScreensaver()
if (mDimValue > 0.0) if (mDimValue > 0.0)
mDimValue = Math::clamp(mDimValue - 0.045f, 0.0f, 1.0f); mDimValue = Math::clamp(mDimValue - 0.045f, 0.0f, 1.0f);
#else #else
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
Renderer::getScreenHeight(), 0x000000FF, 0x000000FF); 0x000000FF, 0x000000FF);
#endif #endif
} }
} }
@ -458,7 +434,7 @@ void SystemScreensaver::update(int deltaTime)
void SystemScreensaver::generateImageList() void SystemScreensaver::generateImageList()
{ {
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
// We only want nodes from game systems that are not collections. // We only want nodes from game systems that are not collections.
if (!(*it)->isGameSystem() || (*it)->isCollection()) if (!(*it)->isGameSystem() || (*it)->isCollection())
@ -475,7 +451,7 @@ void SystemScreensaver::generateImageList()
void SystemScreensaver::generateVideoList() void SystemScreensaver::generateVideoList()
{ {
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
// We only want nodes from game systems that are not collections. // We only want nodes from game systems that are not collections.
if (!(*it)->isGameSystem() || (*it)->isCollection()) if (!(*it)->isGameSystem() || (*it)->isCollection())
@ -513,8 +489,7 @@ void SystemScreensaver::generateCustomImageList()
} }
} }
else { else {
LOG(LogWarning) << "Custom screensaver image directory '" << LOG(LogWarning) << "Custom screensaver image directory '" << imageDir << "' does not exist";
imageDir << "' does not exist.";
} }
} }
@ -541,11 +516,10 @@ void SystemScreensaver::pickRandomImage(std::string& path)
std::random_device randDev; std::random_device randDev;
// Mersenne Twister pseudorandom number generator. // Mersenne Twister pseudorandom number generator.
std::mt19937 engine { randDev() }; std::mt19937 engine { randDev() };
std::uniform_int_distribution<int> std::uniform_int_distribution<int> uniform_dist(0,
uniform_dist(0, static_cast<int>(mImageFiles.size()) - 1); static_cast<int>(mImageFiles.size()) - 1);
index = uniform_dist(engine); index = uniform_dist(engine);
} } while (mPreviousGame && mImageFiles.at(index) == mPreviousGame);
while (mPreviousGame && mImageFiles.at(index) == mPreviousGame);
path = mImageFiles.at(index)->getImagePath(); path = mImageFiles.at(index)->getImagePath();
mGameName = mImageFiles.at(index)->getName(); mGameName = mImageFiles.at(index)->getName();
@ -576,11 +550,10 @@ void SystemScreensaver::pickRandomVideo(std::string& path)
std::random_device randDev; std::random_device randDev;
// Mersenne Twister pseudorandom number generator. // Mersenne Twister pseudorandom number generator.
std::mt19937 engine { randDev() }; std::mt19937 engine { randDev() };
std::uniform_int_distribution<int> std::uniform_int_distribution<int> uniform_dist(0,
uniform_dist(0, static_cast<int>(mVideoFiles.size()) - 1); static_cast<int>(mVideoFiles.size()) - 1);
index = uniform_dist(engine); index = uniform_dist(engine);
} } while (mPreviousGame && mVideoFiles.at(index) == mPreviousGame);
while (mPreviousGame && mVideoFiles.at(index) == mPreviousGame);
path = mVideoFiles.at(index)->getVideoPath(); path = mVideoFiles.at(index)->getVideoPath();
mGameName = mVideoFiles.at(index)->getName(); mGameName = mVideoFiles.at(index)->getName();
@ -605,11 +578,10 @@ void SystemScreensaver::pickRandomCustomImage(std::string& path)
std::random_device randDev; std::random_device randDev;
// Mersenne Twister pseudorandom number generator. // Mersenne Twister pseudorandom number generator.
std::mt19937 engine { randDev() }; std::mt19937 engine { randDev() };
std::uniform_int_distribution<int> std::uniform_int_distribution<int> uniform_dist(
uniform_dist(0, static_cast<int>(mImageCustomFiles.size()) - 1); 0, static_cast<int>(mImageCustomFiles.size()) - 1);
index = uniform_dist(engine); index = uniform_dist(engine);
} } while (mPreviousCustomImage != "" && mImageCustomFiles.at(index) == mPreviousCustomImage);
while (mPreviousCustomImage != "" && mImageCustomFiles.at(index) == mPreviousCustomImage);
path = mImageCustomFiles.at(index); path = mImageCustomFiles.at(index);
mPreviousCustomImage = path; mPreviousCustomImage = path;
@ -633,8 +605,8 @@ void SystemScreensaver::generateOverlayInfo()
const std::string systemName = Utils::String::toUpper(mSystemName); const std::string systemName = Utils::String::toUpper(mSystemName);
const std::string overlayText = gameName + "\n" + systemName; const std::string overlayText = gameName + "\n" + systemName;
mGameOverlay = std::unique_ptr<TextCache>(mGameOverlayFont.at(0)-> mGameOverlay = std::unique_ptr<TextCache>(
buildTextCache(overlayText, posX, posY, 0xFFFFFFFF)); mGameOverlayFont.at(0)->buildTextCache(overlayText, posX, posY, 0xFFFFFFFF));
float textSizeX; float textSizeX;
float textSizeY = mGameOverlayFont[0].get()->sizeText(overlayText).y(); float textSizeY = mGameOverlayFont[0].get()->sizeText(overlayText).y();

View file

@ -22,9 +22,12 @@ public:
SystemScreensaver(Window* window); SystemScreensaver(Window* window);
virtual ~SystemScreensaver(); virtual ~SystemScreensaver();
virtual bool allowSleep(); virtual bool allowSleep()
virtual bool isScreensaverActive(); {
virtual bool isFallbackScreensaver(); return ((mVideoScreensaver == nullptr) && (mImageScreensaver == nullptr));
}
virtual bool isScreensaverActive() { return (mState != STATE_INACTIVE); }
virtual bool isFallbackScreensaver() { return mFallbackScreensaver; }
virtual void startScreensaver(bool generateMediaList); virtual void startScreensaver(bool generateMediaList);
virtual void stopScreensaver(); virtual void stopScreensaver();
@ -35,8 +38,8 @@ public:
virtual void renderScreensaver(); virtual void renderScreensaver();
virtual void update(int deltaTime); virtual void update(int deltaTime);
virtual FileData* getCurrentGame() { return mCurrentGame; }; virtual FileData* getCurrentGame() { return mCurrentGame; }
virtual void triggerNextGame() { mTriggerNextGame = true; }; virtual void triggerNextGame() { mTriggerNextGame = true; }
private: private:
void generateImageList(); void generateImageList();

View file

@ -8,30 +8,15 @@
#include "VolumeControl.h" #include "VolumeControl.h"
#include "math/Misc.h"
#include "Log.h" #include "Log.h"
#include "math/Misc.h"
#if defined(_RPI_)
#include "Settings.h"
#endif
#if defined(_WIN64) #if defined(_WIN64)
#include <cmath> #include <cmath>
#endif #endif
// The ALSA Audio Card and Audio Device selection code is disabled at the moment.
// As PulseAudio controls the sound devices for the desktop environment, it doesn't
// make much sense to be able to select ALSA devices directly.
// The code is still active for Raspberry Pi though as I'm not sure if this is
// useful for that device.
// Keeping mixerName and mixerCard at their default values should make sure that
// the rest of the volume control code in here compiles and works fine.
#if defined(__linux__) #if defined(__linux__)
#if defined(_RPI_) || defined(_VERO4K_)
std::string VolumeControl::mixerName = "PCM";
#else
std::string VolumeControl::mixerName = "Master"; std::string VolumeControl::mixerName = "Master";
#endif
std::string VolumeControl::mixerCard = "default"; std::string VolumeControl::mixerCard = "default";
#endif #endif
@ -39,13 +24,13 @@ VolumeControl* VolumeControl::sInstance = nullptr;
VolumeControl::VolumeControl() VolumeControl::VolumeControl()
#if defined(__linux__) #if defined(__linux__)
: mixerIndex(0), : mixerIndex(0)
mixerHandle(nullptr), , mixerHandle(nullptr)
mixerElem(nullptr), , mixerElem(nullptr)
mixerSelemId(nullptr) , mixerSelemId(nullptr)
#elif defined(_WIN64) #elif defined(_WIN64)
: mixerHandle(nullptr), : mixerHandle(nullptr)
endpointVolume(nullptr) , endpointVolume(nullptr)
#endif #endif
{ {
init(); init();
@ -79,15 +64,10 @@ void VolumeControl::deleteInstance()
void VolumeControl::init() void VolumeControl::init()
{ {
// Initialize audio mixer interface. // Initialize audio mixer interface.
#if defined(__linux__) #if defined(__linux__)
// Try to open mixer device. // Try to open mixer device.
if (mixerHandle == nullptr) { if (mixerHandle == nullptr) {
// Allow user to override the AudioCard and AudioDevice in es_settings.xml.
#if defined(_RPI_)
mixerCard = Settings::getInstance()->getString("AudioCard");
mixerName = Settings::getInstance()->getString("AudioDevice");
#endif
snd_mixer_selem_id_alloca(&mixerSelemId); snd_mixer_selem_id_alloca(&mixerSelemId);
// Sets simple-mixer index and name. // Sets simple-mixer index and name.
snd_mixer_selem_id_set_index(mixerSelemId, mixerIndex); snd_mixer_selem_id_set_index(mixerSelemId, mixerIndex);
@ -106,8 +86,8 @@ void VolumeControl::init()
LOG(LogDebug) << "VolumeControl::init(): Mixer initialized"; LOG(LogDebug) << "VolumeControl::init(): Mixer initialized";
} }
else { else {
LOG(LogError) << LOG(LogError)
"VolumeControl::init(): Failed to find mixer elements!"; << "VolumeControl::init(): Failed to find mixer elements!";
snd_mixer_close(mixerHandle); snd_mixer_close(mixerHandle);
mixerHandle = nullptr; mixerHandle = nullptr;
} }
@ -119,8 +99,8 @@ void VolumeControl::init()
} }
} }
else { else {
LOG(LogError) << LOG(LogError)
"VolumeControl::init(): Failed to register simple element class!"; << "VolumeControl::init(): Failed to register simple element class!";
snd_mixer_close(mixerHandle); snd_mixer_close(mixerHandle);
mixerHandle = nullptr; mixerHandle = nullptr;
} }
@ -141,16 +121,16 @@ void VolumeControl::init()
CoInitialize(nullptr); CoInitialize(nullptr);
IMMDeviceEnumerator* deviceEnumerator = nullptr; IMMDeviceEnumerator* deviceEnumerator = nullptr;
CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
__uuidof(IMMDeviceEnumerator), reinterpret_cast<LPVOID *>(&deviceEnumerator)); __uuidof(IMMDeviceEnumerator),
reinterpret_cast<LPVOID*>(&deviceEnumerator));
if (deviceEnumerator != nullptr) { if (deviceEnumerator != nullptr) {
// Get default endpoint. // Get default endpoint.
IMMDevice* defaultDevice = nullptr; IMMDevice* defaultDevice = nullptr;
deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice); deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
if (defaultDevice != nullptr) { if (defaultDevice != nullptr) {
// Retrieve endpoint volume. // Retrieve endpoint volume.
defaultDevice->Activate(__uuidof(IAudioEndpointVolume), defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER,
CLSCTX_INPROC_SERVER, nullptr, nullptr, reinterpret_cast<LPVOID*>(&endpointVolume));
reinterpret_cast<LPVOID *>(&endpointVolume));
if (endpointVolume == nullptr) if (endpointVolume == nullptr)
LOG(LogError) << "VolumeControl::init(): " LOG(LogError) << "VolumeControl::init(): "
"Failed to get default audio endpoint volume!"; "Failed to get default audio endpoint volume!";
@ -158,8 +138,7 @@ void VolumeControl::init()
defaultDevice->Release(); defaultDevice->Release();
} }
else { else {
LOG(LogError) << LOG(LogError) << "VolumeControl::init(): Failed to get default audio endpoint!";
"VolumeControl::init(): Failed to get default audio endpoint!";
} }
// Release device enumerator. we don't need it anymore. // Release device enumerator. we don't need it anymore.
deviceEnumerator->Release(); deviceEnumerator->Release();
@ -175,6 +154,7 @@ void VolumeControl::init()
void VolumeControl::deinit() void VolumeControl::deinit()
{ {
// Deinitialize audio mixer interface. // Deinitialize audio mixer interface.
#if defined(__linux__) #if defined(__linux__)
if (mixerHandle != nullptr) { if (mixerHandle != nullptr) {
snd_mixer_detach(mixerHandle, mixerCard.c_str()); snd_mixer_detach(mixerHandle, mixerCard.c_str());
@ -203,8 +183,8 @@ int VolumeControl::getVolume() const
long maxVolume; long maxVolume;
if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) { if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) {
long rawVolume; long rawVolume;
if (snd_mixer_selem_get_playback_volume(mixerElem, if (snd_mixer_selem_get_playback_volume(mixerElem, SND_MIXER_SCHN_MONO, &rawVolume) ==
SND_MIXER_SCHN_MONO, &rawVolume) == 0) { 0) {
// Bring into range 0-100. // Bring into range 0-100.
rawVolume -= minVolume; rawVolume -= minVolume;
if (rawVolume > 0) if (rawVolume > 0)
@ -248,10 +228,10 @@ void VolumeControl::setVolume(int volume)
if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) { if (snd_mixer_selem_get_playback_volume_range(mixerElem, &minVolume, &maxVolume) == 0) {
// Bring into minVolume-maxVolume range and set. // Bring into minVolume-maxVolume range and set.
long rawVolume = (volume * (maxVolume - minVolume) / 100) + minVolume; long rawVolume = (volume * (maxVolume - minVolume) / 100) + minVolume;
if (snd_mixer_selem_set_playback_volume(mixerElem, if (snd_mixer_selem_set_playback_volume(mixerElem, SND_MIXER_SCHN_FRONT_LEFT,
SND_MIXER_SCHN_FRONT_LEFT, rawVolume) < 0 || rawVolume) < 0 ||
snd_mixer_selem_set_playback_volume(mixerElem, snd_mixer_selem_set_playback_volume(mixerElem, SND_MIXER_SCHN_FRONT_RIGHT,
SND_MIXER_SCHN_FRONT_RIGHT, rawVolume) < 0) { rawVolume) < 0) {
LOG(LogError) << "VolumeControl::getVolume(): Failed to set mixer volume"; LOG(LogError) << "VolumeControl::getVolume(): Failed to set mixer volume";
} }
} }

View file

@ -14,12 +14,12 @@
class MoveCameraAnimation : public Animation class MoveCameraAnimation : public Animation
{ {
public: public:
MoveCameraAnimation( MoveCameraAnimation(Transform4x4f& camera, const Vector3f& target)
Transform4x4f& camera, : mCameraStart(camera)
const Vector3f& target) , mTarget(target)
: mCameraStart(camera), , cameraOut(camera)
mTarget(target), {
cameraOut(camera) {} }
int getDuration() const override { return 400; } int getDuration() const override { return 400; }
@ -27,7 +27,8 @@ public:
{ {
// Cubic ease out. // Cubic ease out.
t -= 1; t -= 1;
cameraOut.translation() = -Vector3f().lerp(-mCameraStart.translation(), mTarget, t*t*t + 1); cameraOut.translation() =
-Vector3f().lerp(-mCameraStart.translation(), mTarget, t * t * t + 1);
} }
private: private:

View file

@ -9,6 +9,7 @@
#include "guis/GuiCollectionSystemsOptions.h" #include "guis/GuiCollectionSystemsOptions.h"
#include "CollectionSystemsManager.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
@ -16,21 +17,23 @@
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "CollectionSystemsManager.h"
GuiCollectionSystemsOptions::GuiCollectionSystemsOptions( GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::string title)
Window* window, : GuiSettings(window, title)
std::string title) , mAddedCustomCollection(false)
: GuiSettings(window, title), , mDeletedCustomCollection(false)
mAddedCustomCollection(false),
mDeletedCustomCollection(false)
{ {
// Finish editing custom collection. // Finish editing custom collection.
if (CollectionSystemsManager::get()->isEditing()) { if (CollectionSystemsManager::get()->isEditing()) {
ComponentListRow row; ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, "FINISH EDITING '" + row.addElement(std::make_shared<TextComponent>(
Utils::String::toUpper(CollectionSystemsManager::get()->getEditingCollection()) + mWindow,
"' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); "FINISH EDITING '" +
Utils::String::toUpper(
CollectionSystemsManager::get()->getEditingCollection()) +
"' COLLECTION",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.makeAcceptInputHandler([this] { row.makeAcceptInputHandler([this] {
CollectionSystemsManager::get()->exitEditMode(); CollectionSystemsManager::get()->exitEditMode();
mWindow->invalidateCachedBackground(); mWindow->invalidateCachedBackground();
@ -40,13 +43,14 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
} }
// Automatic collections. // Automatic collections.
collection_systems_auto = std::make_shared<OptionListComponent<std::string>> collection_systems_auto = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "SELECT COLLECTIONS", true); mWindow, getHelpStyle(), "SELECT COLLECTIONS", true);
std::map<std::string, CollectionSystemData, stringComparator> autoSystems = std::map<std::string, CollectionSystemData, stringComparator> autoSystems =
CollectionSystemsManager::get()->getAutoCollectionSystems(); CollectionSystemsManager::get()->getAutoCollectionSystems();
// Add automatic systems. // Add automatic systems.
for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator it =
it = autoSystems.cbegin(); it != autoSystems.cend() ; it++) autoSystems.cbegin();
it != autoSystems.cend(); it++)
collection_systems_auto->add(it->second.decl.longName, it->second.decl.name, collection_systems_auto->add(it->second.decl.longName, it->second.decl.name,
it->second.isEnabled); it->second.isEnabled);
addWithLabel("AUTOMATIC GAME COLLECTIONS", collection_systems_auto); addWithLabel("AUTOMATIC GAME COLLECTIONS", collection_systems_auto);
@ -78,8 +82,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
} }
if (!addedAutoSystems.empty()) { if (!addedAutoSystems.empty()) {
for (std::string system : addedAutoSystems) for (std::string system : addedAutoSystems)
CollectionSystemsManager::get()-> CollectionSystemsManager::get()->repopulateCollection(
repopulateCollection(autoSystems.find(system)->second.system); autoSystems.find(system)->second.system);
} }
setNeedsSaving(); setNeedsSaving();
setNeedsReloading(); setNeedsReloading();
@ -88,22 +92,24 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
}); });
// Custom collections. // Custom collections.
collection_systems_custom = std::make_shared<OptionListComponent<std::string>> collection_systems_custom = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "SELECT COLLECTIONS", true); mWindow, getHelpStyle(), "SELECT COLLECTIONS", true);
std::map<std::string, CollectionSystemData, stringComparator> customSystems = std::map<std::string, CollectionSystemData, stringComparator> customSystems =
CollectionSystemsManager::get()->getCustomCollectionSystems(); CollectionSystemsManager::get()->getCustomCollectionSystems();
// Add custom systems. // Add custom systems.
for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator it =
it = customSystems.cbegin(); it != customSystems.cend() ; it++) customSystems.cbegin();
it != customSystems.cend(); it++)
collection_systems_custom->add(it->second.decl.longName, it->second.decl.name, collection_systems_custom->add(it->second.decl.longName, it->second.decl.name,
it->second.isEnabled); it->second.isEnabled);
addWithLabel("CUSTOM GAME COLLECTIONS", collection_systems_custom); addWithLabel("CUSTOM GAME COLLECTIONS", collection_systems_custom);
addSaveFunc([this, customSystems] { addSaveFunc([this, customSystems] {
if (!mDeletedCustomCollection) { if (!mDeletedCustomCollection) {
std::string customSystemsSelected = Utils::String::vectorToDelimitedString( std::string customSystemsSelected = Utils::String::vectorToDelimitedString(
collection_systems_custom->getSelectedObjects(), ",", true); collection_systems_custom->getSelectedObjects(), ",", true);
std::string customSystemsConfig = Settings::getInstance()-> std::string customSystemsConfig =
getString("CollectionSystemsCustom"); Settings::getInstance()->getString("CollectionSystemsCustom");
if (customSystemsSelected != customSystemsConfig) { if (customSystemsSelected != customSystemsConfig) {
if (CollectionSystemsManager::get()->isEditing()) if (CollectionSystemsManager::get()->isEditing())
CollectionSystemsManager::get()->exitEditMode(); CollectionSystemsManager::get()->exitEditMode();
@ -130,8 +136,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
} }
if (!mAddedCustomCollection && !addedCustomSystems.empty()) { if (!mAddedCustomCollection && !addedCustomSystems.empty()) {
for (std::string system : addedCustomSystems) for (std::string system : addedCustomSystems)
CollectionSystemsManager::get()-> CollectionSystemsManager::get()->repopulateCollection(
repopulateCollection(customSystems.find(system)->second.system); customSystems.find(system)->second.system);
} }
setNeedsSaving(); setNeedsSaving();
setNeedsReloading(); setNeedsReloading();
@ -146,19 +152,20 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
CollectionSystemsManager::get()->getUnusedSystemsFromTheme(); CollectionSystemsManager::get()->getUnusedSystemsFromTheme();
if (unusedFolders.size() > 0) { if (unusedFolders.size() > 0) {
ComponentListRow row; ComponentListRow row;
auto themeCollection = std::make_shared<TextComponent>(mWindow, auto themeCollection =
"CREATE NEW CUSTOM COLLECTION FROM THEME", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION FROM THEME",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
auto bracketThemeCollection = std::make_shared<ImageComponent>(mWindow); auto bracketThemeCollection = std::make_shared<ImageComponent>(mWindow);
bracketThemeCollection->setImage(":/graphics/arrow.svg"); bracketThemeCollection->setImage(":/graphics/arrow.svg");
bracketThemeCollection->setResize(Vector2f(0, bracketThemeCollection->setResize(
Font::get(FONT_SIZE_MEDIUM)->getLetterHeight())); Vector2f(0, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
row.addElement(themeCollection, true); row.addElement(themeCollection, true);
row.addElement(bracketThemeCollection, false); row.addElement(bracketThemeCollection, false);
row.makeAcceptInputHandler([this, unusedFolders] { row.makeAcceptInputHandler([this, unusedFolders] {
auto ss = new GuiSettings(mWindow, "SELECT THEME FOLDER"); auto ss = new GuiSettings(mWindow, "SELECT THEME FOLDER");
std::shared_ptr<OptionListComponent<std::string>> folderThemes = std::shared_ptr<OptionListComponent<std::string>> folderThemes =
std::make_shared<OptionListComponent<std::string>>(mWindow, std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(),
getHelpStyle(), "SELECT THEME FOLDER", true); "SELECT THEME FOLDER", true);
// Add custom systems. // Add custom systems.
for (auto it = unusedFolders.cbegin(); it != unusedFolders.cend(); it++) { for (auto it = unusedFolders.cbegin(); it != unusedFolders.cend(); it++) {
ComponentListRow row; ComponentListRow row;
@ -167,8 +174,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
createCustomCollection(name); createCustomCollection(name);
}; };
row.makeAcceptInputHandler(createCollectionCall); row.makeAcceptInputHandler(createCollectionCall);
auto themeFolder = std::make_shared<TextComponent>(mWindow, auto themeFolder = std::make_shared<TextComponent>(
Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(themeFolder, true); row.addElement(themeFolder, true);
// This transparent bracket is only added to generate the correct help prompts. // This transparent bracket is only added to generate the correct help prompts.
auto bracket = std::make_shared<ImageComponent>(mWindow); auto bracket = std::make_shared<ImageComponent>(mWindow);
@ -184,12 +191,11 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
// Create new custom collection. // Create new custom collection.
ComponentListRow row; ComponentListRow row;
auto newCollection = std::make_shared<TextComponent>(mWindow, auto newCollection = std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION",
"CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
auto bracketNewCollection = std::make_shared<ImageComponent>(mWindow); auto bracketNewCollection = std::make_shared<ImageComponent>(mWindow);
bracketNewCollection->setImage(":/graphics/arrow.svg"); bracketNewCollection->setImage(":/graphics/arrow.svg");
bracketNewCollection->setResize(Vector2f(0, bracketNewCollection->setResize(Vector2f(0, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
row.addElement(newCollection, true); row.addElement(newCollection, true);
row.addElement(bracketNewCollection, false); row.addElement(bracketNewCollection, false);
auto createCollectionCall = [this](const std::string& newVal) { auto createCollectionCall = [this](const std::string& newVal) {
@ -202,36 +208,38 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
createCustomCollection(name); createCustomCollection(name);
}; };
row.makeAcceptInputHandler([this, createCollectionCall] { row.makeAcceptInputHandler([this, createCollectionCall] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name", "",
"New Collection Name", "", createCollectionCall, false, "SAVE")); createCollectionCall, false, "SAVE"));
}); });
addRow(row); addRow(row);
// Delete custom collection. // Delete custom collection.
row.elements.clear(); row.elements.clear();
auto deleteCollection = std::make_shared<TextComponent>(mWindow, auto deleteCollection = std::make_shared<TextComponent>(
"DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); mWindow, "DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
auto bracketDeleteCollection = std::make_shared<ImageComponent>(mWindow); auto bracketDeleteCollection = std::make_shared<ImageComponent>(mWindow);
bracketDeleteCollection->setImage(":/graphics/arrow.svg"); bracketDeleteCollection->setImage(":/graphics/arrow.svg");
bracketDeleteCollection->setResize(Vector2f(0, bracketDeleteCollection->setResize(Vector2f(0, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
row.addElement(deleteCollection, true); row.addElement(deleteCollection, true);
row.addElement(bracketDeleteCollection, false); row.addElement(bracketDeleteCollection, false);
row.makeAcceptInputHandler([this, customSystems] { row.makeAcceptInputHandler([this, customSystems] {
auto ss = new GuiSettings(mWindow, "SELECT COLLECTION TO DELETE"); auto ss = new GuiSettings(mWindow, "SELECT COLLECTION TO DELETE");
std::shared_ptr<OptionListComponent<std::string>> customCollections = std::shared_ptr<OptionListComponent<std::string>> customCollections =
std::make_shared<OptionListComponent<std::string>>(mWindow, std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(), "", true);
getHelpStyle(), "", true); for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator it =
for (std::map<std::string, CollectionSystemData, stringComparator>::const_iterator customSystems.cbegin();
it = customSystems.cbegin(); it != customSystems.cend() ; it++) { it != customSystems.cend(); it++) {
ComponentListRow row; ComponentListRow row;
std::string name = (*it).first; std::string name = (*it).first;
std::function<void()> deleteCollectionCall = [this, name] { std::function<void()> deleteCollectionCall = [this, name] {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
mWindow, getHelpStyle(),
"THIS WILL PERMANENTLY\nDELETE THE COLLECTION\n'" + "THIS WILL PERMANENTLY\nDELETE THE COLLECTION\n'" +
Utils::String::toUpper(name) + "'\n" Utils::String::toUpper(name) +
"'\n"
"ARE YOU SURE?", "ARE YOU SURE?",
"YES", [this, name] { "YES",
[this, name] {
if (CollectionSystemsManager::get()->isEditing()) if (CollectionSystemsManager::get()->isEditing())
CollectionSystemsManager::get()->exitEditMode(); CollectionSystemsManager::get()->exitEditMode();
mDeletedCustomCollection = true; mDeletedCustomCollection = true;
@ -261,13 +269,11 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
CollectionSystemsManager::get()->deleteCustomCollection(name); CollectionSystemsManager::get()->deleteCustomCollection(name);
return true; return true;
}, },
"NO", [this] { "NO", [this] { return false; }));
return false;
}));
}; };
row.makeAcceptInputHandler(deleteCollectionCall); row.makeAcceptInputHandler(deleteCollectionCall);
auto customCollection = std::make_shared<TextComponent>(mWindow, auto customCollection = std::make_shared<TextComponent>(
Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(customCollection, true); row.addElement(customCollection, true);
// This transparent bracket is only added generate the correct help prompts. // This transparent bracket is only added generate the correct help prompts.
auto bracket = std::make_shared<ImageComponent>(mWindow); auto bracket = std::make_shared<ImageComponent>(mWindow);
@ -310,8 +316,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
// Group unthemed custom collections. // Group unthemed custom collections.
auto use_custom_collections_system = std::make_shared<SwitchComponent>(mWindow); auto use_custom_collections_system = std::make_shared<SwitchComponent>(mWindow);
use_custom_collections_system->setState(Settings::getInstance()-> use_custom_collections_system->setState(
getBool("UseCustomCollectionsSystem")); Settings::getInstance()->getBool("UseCustomCollectionsSystem"));
addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", use_custom_collections_system); addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", use_custom_collections_system);
addSaveFunc([this, use_custom_collections_system] { addSaveFunc([this, use_custom_collections_system] {
if (use_custom_collections_system->getState() != if (use_custom_collections_system->getState() !=
@ -330,8 +336,8 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
// Show system names in collections. // Show system names in collections.
auto collection_show_system_info = std::make_shared<SwitchComponent>(mWindow); auto collection_show_system_info = std::make_shared<SwitchComponent>(mWindow);
collection_show_system_info->setState(Settings::getInstance()-> collection_show_system_info->setState(
getBool("CollectionShowSystemInfo")); Settings::getInstance()->getBool("CollectionShowSystemInfo"));
addWithLabel("SHOW SYSTEM NAMES IN COLLECTIONS", collection_show_system_info); addWithLabel("SHOW SYSTEM NAMES IN COLLECTIONS", collection_show_system_info);
addSaveFunc([this, collection_show_system_info] { addSaveFunc([this, collection_show_system_info] {
if (collection_show_system_info->getState() != if (collection_show_system_info->getState() !=
@ -350,10 +356,9 @@ void GuiCollectionSystemsOptions::createCustomCollection(std::string inName)
if (CollectionSystemsManager::get()->isEditing()) if (CollectionSystemsManager::get()->isEditing())
CollectionSystemsManager::get()->exitEditMode(); CollectionSystemsManager::get()->exitEditMode();
std::string collectionName = CollectionSystemsManager::get()-> std::string collectionName = CollectionSystemsManager::get()->getValidNewCollectionName(inName);
getValidNewCollectionName(inName); SystemData* newCollection =
SystemData* newCollection = CollectionSystemsManager::get()-> CollectionSystemsManager::get()->addNewCustomCollection(collectionName);
addNewCustomCollection(collectionName);
CollectionSystemsManager::get()->saveCustomCollection(newCollection); CollectionSystemsManager::get()->saveCustomCollection(newCollection);
collection_systems_custom->add(collectionName, collectionName, true); collection_systems_custom->add(collectionName, collectionName, true);

View file

@ -12,8 +12,7 @@
#include "GuiSettings.h" #include "GuiSettings.h"
template<typename T> template <typename T> class OptionListComponent;
class OptionListComponent;
class GuiCollectionSystemsOptions : public GuiSettings class GuiCollectionSystemsOptions : public GuiSettings
{ {

View file

@ -10,23 +10,22 @@
#include "guis/GuiGameScraper.h" #include "guis/GuiGameScraper.h"
#include "FileData.h"
#include "MameNames.h"
#include "SystemData.h"
#include "components/ButtonComponent.h" #include "components/ButtonComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "FileData.h"
#include "MameNames.h"
#include "SystemData.h"
GuiGameScraper::GuiGameScraper( GuiGameScraper::GuiGameScraper(Window* window,
Window* window,
ScraperSearchParams params, ScraperSearchParams params,
std::function<void(const ScraperSearchResult&)> doneFunc) std::function<void(const ScraperSearchResult&)> doneFunc)
: GuiComponent(window), : GuiComponent(window)
mGrid(window, Vector2i(1, 7)), , mGrid(window, Vector2i(1, 7))
mBox(window, ":/graphics/frame.svg"), , mBox(window, ":/graphics/frame.svg")
mSearchParams(params), , mSearchParams(params)
mClose(false) , mClose(false)
{ {
addChild(&mBox); addChild(&mBox);
addChild(&mGrid); addChild(&mGrid);
@ -41,42 +40,45 @@ GuiGameScraper::GuiGameScraper(
else { else {
if (params.game->isArcadeGame() && if (params.game->isArcadeGame() &&
Settings::getInstance()->getString("Scraper") == "thegamesdb") Settings::getInstance()->getString("Scraper") == "thegamesdb")
scrapeName = Utils::FileSystem::getFileName(mSearchParams.game->getPath()) + " (" + scrapeName =
MameNames::getInstance()->getCleanName(mSearchParams.game->getCleanName()) + Utils::FileSystem::getFileName(mSearchParams.game->getPath()) + " (" +
")"; MameNames::getInstance()->getCleanName(mSearchParams.game->getCleanName()) + ")";
else else
scrapeName = Utils::FileSystem::getFileName(mSearchParams.game->getPath()); scrapeName = Utils::FileSystem::getFileName(mSearchParams.game->getPath());
} }
mGameName = std::make_shared<TextComponent>(mWindow, scrapeName + mGameName = std::make_shared<TextComponent>(
mWindow,
scrapeName +
((mSearchParams.game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : ""), ((mSearchParams.game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR : ""),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER);
mGrid.setEntry(mGameName, Vector2i(0, 1), false, true); mGrid.setEntry(mGameName, Vector2i(0, 1), false, true);
// Row 2 is a spacer. // Row 2 is a spacer.
mSystemName = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper( mSystemName = std::make_shared<TextComponent>(
mSearchParams.system->getFullName()), Font::get(FONT_SIZE_SMALL), mWindow, Utils::String::toUpper(mSearchParams.system->getFullName()),
0x888888FF, ALIGN_CENTER); Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER);
mGrid.setEntry(mSystemName, Vector2i(0, 3), false, true); mGrid.setEntry(mSystemName, Vector2i(0, 3), false, true);
// Row 4 is a spacer. // Row 4 is a spacer.
// GuiScraperSearch. // GuiScraperSearch.
mSearch = std::make_shared<GuiScraperSearch>(window, mSearch = std::make_shared<GuiScraperSearch>(window, GuiScraperSearch::NEVER_AUTO_ACCEPT, 1);
GuiScraperSearch::NEVER_AUTO_ACCEPT, 1);
mGrid.setEntry(mSearch, Vector2i(0, 5), true); mGrid.setEntry(mSearch, Vector2i(0, 5), true);
// Buttons // Buttons
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "REFINE SEARCH", buttons.push_back(
"refine search", [&] { std::make_shared<ButtonComponent>(mWindow, "REFINE SEARCH", "refine search", [&] {
// Refine the search, unless the result has already been accepted.
if (!mSearch->getAcceptedResult()) {
mSearch->openInputScreen(mSearchParams); mSearch->openInputScreen(mSearchParams);
mGrid.resetCursor(); mGrid.resetCursor();
}
})); }));
buttons.push_back(std::make_shared<ButtonComponent>( buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel", [&] {
mWindow, "CANCEL", "cancel", [&] {
if (mSearch->getSavedNewMedia()) { if (mSearch->getSavedNewMedia()) {
// If the user aborted the scraping but there was still some media downloaded, // If the user aborted the scraping but there was still some media downloaded,
// then force an unload of the textures for the game image and marquee, and make // then force an unload of the textures for the game image and marquee, and make
@ -86,41 +88,26 @@ GuiGameScraper::GuiGameScraper(
TextureResource::manualUnload(mSearchParams.game->getMarqueePath(), false); TextureResource::manualUnload(mSearchParams.game->getMarqueePath(), false);
ViewController::get()->onFileChanged(mSearchParams.game, true); ViewController::get()->onFileChanged(mSearchParams.game, true);
} }
delete this; })); delete this;
}));
mButtonGrid = makeButtonGrid(mWindow, buttons); mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false); mGrid.setEntry(mButtonGrid, Vector2i(0, 6), true, false);
// We call this->close() instead of just 'delete this' in the accept callback.
// This is because of how GuiComponent::update works. If it was just 'delete this',
// the following would happen when the metadata resolver is done:
// GuiGameScraper::update()
// GuiComponent::update()
// it = mChildren.cbegin();
// mBox::update()
// it++;
// mSearchComponent::update()
// acceptCallback -> delete this
// it++; // Error, mChildren has been deleted because it was part of 'this'.
// So instead we do this:
// GuiGameScraper::update()
// GuiComponent::update()
// it = mChildren.cbegin();
// mBox::update()
// it++;
// mSearchComponent::update()
// acceptCallback -> close() -> mClose = true
// it++; // OK.
// if (mClose)
// delete this;
mSearch->setAcceptCallback([this, doneFunc](const ScraperSearchResult& result) { mSearch->setAcceptCallback([this, doneFunc](const ScraperSearchResult& result) {
doneFunc(result); close(); }); doneFunc(result);
close();
});
mSearch->setCancelCallback([&] { delete this; }); mSearch->setCancelCallback([&] { delete this; });
setSize(Renderer::getScreenWidth() * 0.95f, Renderer::getScreenHeight() * 0.747f); // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - // the 16:9 reference.
mSize.y()) / 2); float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = Math::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth();
setSize(width, Renderer::getScreenHeight() * 0.747f);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f,
(Renderer::getScreenHeight() - mSize.y()) / 2.0f);
mGrid.resetCursor(); mGrid.resetCursor();
mSearch->search(params); // Start the search. mSearch->search(params); // Start the search.
@ -128,14 +115,14 @@ GuiGameScraper::GuiGameScraper(
void GuiGameScraper::onSizeChanged() void GuiGameScraper::onSizeChanged()
{ {
mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32.0f, -32.0f));
mGrid.setRowHeightPerc(0, 0.04f, false); mGrid.setRowHeightPerc(0, 0.04f, false);
mGrid.setRowHeightPerc(1, mGameName->getFont()->getLetterHeight() / mGrid.setRowHeightPerc(1, mGameName->getFont()->getLetterHeight() / mSize.y(),
mSize.y(), false); // Game name. false); // Game name.
mGrid.setRowHeightPerc(2, 0.04f, false); mGrid.setRowHeightPerc(2, 0.04f, false);
mGrid.setRowHeightPerc(3, mSystemName->getFont()->getLetterHeight() / mGrid.setRowHeightPerc(3, mSystemName->getFont()->getLetterHeight() / mSize.y(),
mSize.y(), false); // System name. false); // System name.
mGrid.setRowHeightPerc(4, 0.04f, false); mGrid.setRowHeightPerc(4, 0.04f, false);
mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() / mSize.y(), false); // Buttons. mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() / mSize.y(), false); // Buttons.
mGrid.setSize(mSize); mGrid.setSize(mSize);
@ -170,7 +157,10 @@ void GuiGameScraper::update(int deltaTime)
std::vector<HelpPrompt> GuiGameScraper::getHelpPrompts() std::vector<HelpPrompt> GuiGameScraper::getHelpPrompts()
{ {
return mGrid.getHelpPrompts(); std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
prompts.push_back(HelpPrompt("b", "back (cancel)"));
return prompts;
} }
HelpStyle GuiGameScraper::getHelpStyle() HelpStyle GuiGameScraper::getHelpStyle()
@ -182,5 +172,6 @@ HelpStyle GuiGameScraper::getHelpStyle()
void GuiGameScraper::close() void GuiGameScraper::close()
{ {
// This will cause update() to close the GUI.
mClose = true; mClose = true;
} }

View file

@ -11,14 +11,15 @@
#ifndef ES_APP_GUIS_GUI_GAME_SCRAPER_H #ifndef ES_APP_GUIS_GUI_GAME_SCRAPER_H
#define ES_APP_GUIS_GUI_GAME_SCRAPER_H #define ES_APP_GUIS_GUI_GAME_SCRAPER_H
#include "GuiComponent.h"
#include "components/NinePatchComponent.h" #include "components/NinePatchComponent.h"
#include "guis/GuiScraperSearch.h" #include "guis/GuiScraperSearch.h"
#include "GuiComponent.h"
class GuiGameScraper : public GuiComponent class GuiGameScraper : public GuiComponent
{ {
public: public:
GuiGameScraper(Window* window, ScraperSearchParams params, GuiGameScraper(Window* window,
ScraperSearchParams params,
std::function<void(const ScraperSearchResult&)> doneFunc); std::function<void(const ScraperSearchResult&)> doneFunc);
void onSizeChanged() override; void onSizeChanged() override;

View file

@ -10,21 +10,20 @@
#include "guis/GuiGamelistFilter.h" #include "guis/GuiGamelistFilter.h"
#include "SystemData.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "views/UIModeController.h" #include "views/UIModeController.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "SystemData.h"
GuiGamelistFilter::GuiGamelistFilter( GuiGamelistFilter::GuiGamelistFilter(Window* window,
Window* window,
SystemData* system, SystemData* system,
std::function<void(bool)> filterChangedCallback) std::function<void(bool)> filterChangedCallback)
: GuiComponent(window), : GuiComponent(window)
mMenu(window, "FILTER GAMELIST BY"), , mMenu(window, "FILTER GAMELIST BY")
mSystem(system), , mSystem(system)
mFiltersChangedCallback(filterChangedCallback), , mFiltersChangedCallback(filterChangedCallback)
mFiltersChanged(false) , mFiltersChanged(false)
{ {
initializeMenu(); initializeMenu();
} }
@ -41,7 +40,8 @@ void GuiGamelistFilter::initializeMenu()
// Show filtered menu. // Show filtered menu.
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "RESET ALL FILTERS", row.addElement(std::make_shared<TextComponent>(mWindow, "RESET ALL FILTERS",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this));
mMenu.addRow(row); mMenu.addRow(row);
row.elements.clear(); row.elements.clear();
@ -56,8 +56,10 @@ void GuiGamelistFilter::initializeMenu()
// Save the initial filter values to be able to check later if any changes were made. // Save the initial filter values to be able to check later if any changes were made.
mInitialTextFilter = mTextFilterField->getValue(); mInitialTextFilter = mTextFilterField->getValue();
for (std::map<FilterIndexType, std::shared_ptr<OptionListComponent<std::string>>>:: for (std::map<FilterIndexType,
const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); it++) { std::shared_ptr<OptionListComponent<std::string>>>::const_iterator it =
mFilterOptions.cbegin();
it != mFilterOptions.cend(); it++) {
std::shared_ptr<OptionListComponent<std::string>> optionList = it->second; std::shared_ptr<OptionListComponent<std::string>> optionList = it->second;
std::vector<std::string> filters = optionList->getSelectedObjects(); std::vector<std::string> filters = optionList->getSelectedObjects();
mInitialFilters.push_back(filters); mInitialFilters.push_back(filters);
@ -67,8 +69,10 @@ void GuiGamelistFilter::initializeMenu()
void GuiGamelistFilter::resetAllFilters() void GuiGamelistFilter::resetAllFilters()
{ {
mFilterIndex->resetFilters(); mFilterIndex->resetFilters();
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string>>>:: for (std::map<FilterIndexType,
const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); it++) { std::shared_ptr<OptionListComponent<std::string>>>::const_iterator it =
mFilterOptions.cbegin();
it != mFilterOptions.cend(); it++) {
std::shared_ptr<OptionListComponent<std::string>> optionList = it->second; std::shared_ptr<OptionListComponent<std::string>> optionList = it->second;
optionList->selectNone(); optionList->selectNone();
} }
@ -78,21 +82,18 @@ void GuiGamelistFilter::resetAllFilters()
mFiltersChanged = true; mFiltersChanged = true;
} }
GuiGamelistFilter::~GuiGamelistFilter() GuiGamelistFilter::~GuiGamelistFilter() { mFilterOptions.clear(); }
{
mFilterOptions.clear();
}
void GuiGamelistFilter::addFiltersToMenu() void GuiGamelistFilter::addFiltersToMenu()
{ {
ComponentListRow row; ComponentListRow row;
auto lbl = std::make_shared<TextComponent>(mWindow, auto lbl =
Utils::String::toUpper("TEXT FILTER (GAME NAME)"), std::make_shared<TextComponent>(mWindow, Utils::String::toUpper("TEXT FILTER (GAME NAME)"),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF); Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
mTextFilterField = std::make_shared<TextComponent>(mWindow, "", mTextFilterField = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_MEDIUM),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); 0x777777FF, ALIGN_RIGHT);
// Don't show the free text filter entry unless there are any games in the system. // Don't show the free text filter entry unless there are any games in the system.
if (mSystem->getRootFolder()->getChildren().size() > 0) { if (mSystem->getRootFolder()->getChildren().size() > 0) {
@ -118,19 +119,19 @@ void GuiGamelistFilter::addFiltersToMenu()
}; };
row.makeAcceptInputHandler([this, updateVal] { row.makeAcceptInputHandler([this, updateVal] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "TEXT FILTER (GAME NAME)",
"TEXT FILTER (GAME NAME)", mTextFilterField->getValue(), mTextFilterField->getValue(), updateVal, false, "OK",
updateVal, false, "OK", "APPLY CHANGES?")); "APPLY CHANGES?"));
}); });
mMenu.addRow(row); mMenu.addRow(row);
std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls(); std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls();
for (std::vector<FilterDataDecl>::const_iterator it = for (std::vector<FilterDataDecl>::const_iterator it = decls.cbegin(); // Line break.
decls.cbegin(); it != decls.cend(); it++) { it != decls.cend(); it++) {
FilterIndexType type = (*it).type; // Type of filter. FilterIndexType type = (*it).type; // Type of filter.
// All possible filters for this type. // All possible filters for this type.
std::map<std::string, int>* allKeys = (*it).allIndexKeys; std::map<std::string, int>* allKeys = (*it).allIndexKeys;
std::string menuLabel = (*it).menuLabel; // Text to show in menu. std::string menuLabel = (*it).menuLabel; // Text to show in menu.
@ -140,8 +141,8 @@ void GuiGamelistFilter::addFiltersToMenu()
ComponentListRow row; ComponentListRow row;
// Add genres. // Add genres.
optionList = std::make_shared<OptionListComponent<std::string>> optionList = std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(),
(mWindow, getHelpStyle(), menuLabel, true); menuLabel, true);
for (auto it : *allKeys) for (auto it : *allKeys)
optionList->add(it.first, it.first, mFilterIndex->isKeyBeingFilteredBy(it.first, type)); optionList->add(it.first, it.first, mFilterIndex->isKeyBeingFilteredBy(it.first, type));
if (allKeys->size() > 0) if (allKeys->size() > 0)
@ -157,8 +158,10 @@ void GuiGamelistFilter::applyFilters()
mFiltersChanged = true; mFiltersChanged = true;
std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls(); std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls();
for (std::map<FilterIndexType, std::shared_ptr<OptionListComponent<std::string>>>:: for (std::map<FilterIndexType,
const_iterator it = mFilterOptions.cbegin(); it != mFilterOptions.cend(); it++) { std::shared_ptr<OptionListComponent<std::string>>>::const_iterator it =
mFilterOptions.cbegin();
it != mFilterOptions.cend(); it++) {
std::shared_ptr<OptionListComponent<std::string>> optionList = it->second; std::shared_ptr<OptionListComponent<std::string>> optionList = it->second;
std::vector<std::string> filters = optionList->getSelectedObjects(); std::vector<std::string> filters = optionList->getSelectedObjects();
auto iteratorDistance = std::distance(mFilterOptions.cbegin(), it); auto iteratorDistance = std::distance(mFilterOptions.cbegin(), it);

View file

@ -11,19 +11,19 @@
#ifndef ES_APP_GUIS_GUI_GAME_LIST_FILTER_H #ifndef ES_APP_GUIS_GUI_GAME_LIST_FILTER_H
#define ES_APP_GUIS_GUI_GAME_LIST_FILTER_H #define ES_APP_GUIS_GUI_GAME_LIST_FILTER_H
#include "components/MenuComponent.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "GuiComponent.h" #include "GuiComponent.h"
#include "components/MenuComponent.h"
template<typename T> template <typename T> class OptionListComponent;
class OptionListComponent;
class SystemData; class SystemData;
class GuiGamelistFilter : public GuiComponent class GuiGamelistFilter : public GuiComponent
{ {
public: public:
GuiGamelistFilter(Window* window, GuiGamelistFilter(Window* window,
SystemData* system, std::function<void(bool)> filtersChangedCallback); SystemData* system,
std::function<void(bool)> filtersChangedCallback);
~GuiGamelistFilter(); ~GuiGamelistFilter();
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;

View file

@ -12,11 +12,6 @@
#include "GuiGamelistOptions.h" #include "GuiGamelistOptions.h"
#include "guis/GuiGamelistFilter.h"
#include "scrapers/Scraper.h"
#include "views/gamelist/IGameListView.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "FileSorts.h" #include "FileSorts.h"
@ -24,18 +19,21 @@
#include "MameNames.h" #include "MameNames.h"
#include "Sound.h" #include "Sound.h"
#include "SystemData.h" #include "SystemData.h"
#include "guis/GuiGamelistFilter.h"
#include "scrapers/Scraper.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "views/gamelist/IGameListView.h"
GuiGamelistOptions::GuiGamelistOptions( GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system)
Window* window, : GuiComponent(window)
SystemData* system) , mSystem(system)
: GuiComponent(window), , mMenu(window, "OPTIONS")
mSystem(system), , mFiltersChanged(false)
mMenu(window, "OPTIONS"), , mCancelled(false)
mFiltersChanged(false), , mIsCustomCollection(false)
mCancelled(false), , mIsCustomCollectionGroup(false)
mIsCustomCollection(false), , mCustomCollectionSystem(nullptr)
mIsCustomCollectionGroup(false),
mCustomCollectionSystem(nullptr)
{ {
addChild(&mMenu); addChild(&mMenu);
@ -46,8 +44,7 @@ GuiGamelistOptions::GuiGamelistOptions(
ComponentListRow row; ComponentListRow row;
// There is some special logic required for custom collections. // There is some special logic required for custom collections.
if (file->getSystem()->isCustomCollection() && if (file->getSystem()->isCustomCollection() && file->getPath() != file->getSystem()->getName())
file->getPath() != file->getSystem()->getName())
mIsCustomCollection = true; mIsCustomCollection = true;
else if (file->getSystem()->isCustomCollection() && else if (file->getSystem()->isCustomCollection() &&
file->getPath() == file->getSystem()->getName()) file->getPath() == file->getSystem()->getName())
@ -84,12 +81,12 @@ GuiGamelistOptions::GuiGamelistOptions(
else { else {
// Check if the currently selected game is a favorite. // Check if the currently selected game is a favorite.
bool isFavorite = false; bool isFavorite = false;
if (mFirstLetterIndex.size() == 1 && mFirstLetterIndex.front() == if (mFirstLetterIndex.size() == 1 &&
ViewController::FAVORITE_CHAR) mFirstLetterIndex.front() == ViewController::FAVORITE_CHAR)
isFavorite = true; isFavorite = true;
else if (mFirstLetterIndex.size() > 1 && (mFirstLetterIndex.front() == else if (mFirstLetterIndex.size() > 1 &&
ViewController::FAVORITE_CHAR || mFirstLetterIndex[1] == (mFirstLetterIndex.front() == ViewController::FAVORITE_CHAR ||
ViewController::FAVORITE_CHAR)) mFirstLetterIndex[1] == ViewController::FAVORITE_CHAR))
isFavorite = true; isFavorite = true;
// Get the first character of the game name (which could be a Unicode character). // Get the first character of the game name (which could be a Unicode character).
@ -99,8 +96,8 @@ GuiGamelistOptions::GuiGamelistOptions(
mCurrentFirstCharacter = Utils::String::getFirstCharacter(file->getSortName()); mCurrentFirstCharacter = Utils::String::getFirstCharacter(file->getSortName());
} }
mJumpToLetterList = std::make_shared<LetterList>(mWindow, getHelpStyle(), mJumpToLetterList =
"JUMP TO...", false); std::make_shared<LetterList>(mWindow, getHelpStyle(), "JUMP TO...", false);
// Populate the quick selector. // Populate the quick selector.
for (unsigned int i = 0; i < mFirstLetterIndex.size(); i++) { for (unsigned int i = 0; i < mFirstLetterIndex.size(); i++) {
@ -146,8 +143,9 @@ GuiGamelistOptions::GuiGamelistOptions(
if (!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() > 0) { if (!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() > 0) {
if (system->getName() != "recent" && Settings::getInstance()->getBool("GamelistFilters")) { if (system->getName() != "recent" && Settings::getInstance()->getBool("GamelistFilters")) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent> row.addElement(std::make_shared<TextComponent>(mWindow, "FILTER GAMELIST",
(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(makeArrow(mWindow), false); row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this));
mMenu.addRow(row); mMenu.addRow(row);
@ -155,12 +153,12 @@ GuiGamelistOptions::GuiGamelistOptions(
} }
// Add a dummy entry when applicable as the menu looks quite ugly if it's just blank. // Add a dummy entry when applicable as the menu looks quite ugly if it's just blank.
else if (!CollectionSystemsManager::get()->isEditing() && else if (!CollectionSystemsManager::get()->isEditing() &&
mSystem->getRootFolder()->getChildren().size() == 0 && mSystem->getRootFolder()->getChildren().size() == 0 && !mIsCustomCollectionGroup &&
!mIsCustomCollectionGroup && !mIsCustomCollection) { !mIsCustomCollection) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent> row.addElement(std::make_shared<TextComponent>(mWindow, "THIS SYSTEM HAS NO GAMES",
(mWindow, "THIS SYSTEM HAS NO GAMES", Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); true);
mMenu.addRow(row); mMenu.addRow(row);
} }
@ -174,9 +172,10 @@ GuiGamelistOptions::GuiGamelistOptions(
(mIsCustomCollection || mIsCustomCollectionGroup)) { (mIsCustomCollection || mIsCustomCollectionGroup)) {
if (CollectionSystemsManager::get()->getEditingCollection() != customSystem) { if (CollectionSystemsManager::get()->getEditingCollection() != customSystem) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>( row.addElement(std::make_shared<TextComponent>(mWindow,
mWindow, "ADD/REMOVE GAMES TO THIS COLLECTION", "ADD/REMOVE GAMES TO THIS COLLECTION",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::startEditMode, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::startEditMode, this));
mMenu.addRow(row); mMenu.addRow(row);
} }
@ -186,9 +185,13 @@ GuiGamelistOptions::GuiGamelistOptions(
CollectionSystemsManager::get()->isEditing()) { CollectionSystemsManager::get()->isEditing()) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>( row.addElement(std::make_shared<TextComponent>(
mWindow, "FINISH EDITING '" + Utils::String::toUpper( mWindow,
"FINISH EDITING '" +
Utils::String::toUpper(
CollectionSystemsManager::get()->getEditingCollection()) + CollectionSystemsManager::get()->getEditingCollection()) +
"' COLLECTION",Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); "' COLLECTION",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::exitEditMode, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::exitEditMode, this));
mMenu.addRow(row); mMenu.addRow(row);
} }
@ -197,8 +200,9 @@ GuiGamelistOptions::GuiGamelistOptions(
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) { !(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS FOLDER'S METADATA",
"EDIT THIS FOLDER'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(makeArrow(mWindow), false); row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row); mMenu.addRow(row);
@ -208,8 +212,9 @@ GuiGamelistOptions::GuiGamelistOptions(
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) { !(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA",
"EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(makeArrow(mWindow), false); row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this)); row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row); mMenu.addRow(row);
@ -219,18 +224,24 @@ GuiGamelistOptions::GuiGamelistOptions(
// Buttons. The logic to apply or cancel settings are handled by the destructor. // Buttons. The logic to apply or cancel settings are handled by the destructor.
if ((!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() == 0) || if ((!mIsCustomCollectionGroup && system->getRootFolder()->getChildren().size() == 0) ||
system->getName() == "recent") { system->getName() == "recent") {
mMenu.addButton("CLOSE", "close", [&] { mCancelled = true; delete this; }); mMenu.addButton("CLOSE", "close", [&] {
mCancelled = true;
delete this;
});
} }
else { else {
mMenu.addButton("APPLY", "apply", [&] { delete this; }); mMenu.addButton("APPLY", "apply", [&] { delete this; });
mMenu.addButton("CANCEL", "cancel", [&] { mCancelled = true; delete this; }); mMenu.addButton("CANCEL", "cancel", [&] {
mCancelled = true;
delete this;
});
} }
// Center the menu. // Center the menu.
setSize(static_cast<float>(Renderer::getScreenWidth()), setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())); static_cast<float>(Renderer::getScreenHeight()));
mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2.0f, (mSize.y() - mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2.0f,
mMenu.getSize().y()) / 2.0f); (mSize.y() - mMenu.getSize().y()) / 2.0f);
} }
GuiGamelistOptions::~GuiGamelistOptions() GuiGamelistOptions::~GuiGamelistOptions()
@ -248,11 +259,12 @@ GuiGamelistOptions::~GuiGamelistOptions()
if (!mFromPlaceholder) { if (!mFromPlaceholder) {
ViewController::get()->reloadGameListView(mSystem); ViewController::get()->reloadGameListView(mSystem);
} }
else if (!mCustomCollectionSystem->getRootFolder()-> else if (!mCustomCollectionSystem->getRootFolder()
getChildrenListToDisplay().empty()) { ->getChildrenListToDisplay()
.empty()) {
ViewController::get()->reloadGameListView(mSystem); ViewController::get()->reloadGameListView(mSystem);
getGamelist()->setCursor(mCustomCollectionSystem-> getGamelist()->setCursor(
getRootFolder()->getChildrenListToDisplay().front()); mCustomCollectionSystem->getRootFolder()->getChildrenListToDisplay().front());
} }
} }
} }
@ -262,6 +274,7 @@ GuiGamelistOptions::~GuiGamelistOptions()
if (!mFromPlaceholder) { if (!mFromPlaceholder) {
FileData* root; FileData* root;
if (mIsCustomCollection) if (mIsCustomCollection)
root = getGamelist()->getCursor()->getSystem()->getRootFolder(); root = getGamelist()->getCursor()->getSystem()->getRootFolder();
else else
@ -329,8 +342,7 @@ void GuiGamelistOptions::startEditMode()
// Display the indication icons which show what games are part of the custom collection // Display the indication icons which show what games are part of the custom collection
// currently being edited. This is done cheaply using onFileChanged() which will trigger // currently being edited. This is done cheaply using onFileChanged() which will trigger
// populateList(). // populateList().
for (auto it = SystemData::sSystemVector.begin(); for (auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) {
it != SystemData::sSystemVector.end(); it++) {
ViewController::get()->getGameListView((*it))->onFileChanged( ViewController::get()->getGameListView((*it))->onFileChanged(
ViewController::get()->getGameListView((*it))->getCursor(), false); ViewController::get()->getGameListView((*it))->getCursor(), false);
} }
@ -362,12 +374,12 @@ void GuiGamelistOptions::openMetaDataEd()
clearGameBtnFunc = [this, file] { clearGameBtnFunc = [this, file] {
if (file->getType() == FOLDER) { if (file->getType() == FOLDER) {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder \"" << LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder \""
file->getFullPath() << "\""; << file->getFullPath() << "\"";
} }
else { else {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file \"" << LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file \""
file->getFullPath() << "\""; << file->getFullPath() << "\"";
} }
ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file); ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file);
@ -377,8 +389,8 @@ void GuiGamelistOptions::openMetaDataEd()
if (it->key == "name") { if (it->key == "name") {
if (file->isArcadeGame()) { if (file->isArcadeGame()) {
// If it's a MAME or Neo Geo game, expand the game name accordingly. // If it's a MAME or Neo Geo game, expand the game name accordingly.
file->metadata.set(it->key, MameNames::getInstance()-> file->metadata.set(
getCleanName(file->getCleanName())); it->key, MameNames::getInstance()->getCleanName(file->getCleanName()));
} }
else { else {
file->metadata.set(it->key, file->getDisplayName()); file->metadata.set(it->key, file->getDisplayName());
@ -408,8 +420,8 @@ void GuiGamelistOptions::openMetaDataEd()
}; };
deleteGameBtnFunc = [this, file] { deleteGameBtnFunc = [this, file] {
LOG(LogInfo) << "Deleting the game file \"" << file->getFullPath() << LOG(LogInfo) << "Deleting the game file \"" << file->getFullPath()
"\", all its media files and its gamelist.xml entry."; << "\", all its media files and its gamelist.xml entry.";
CollectionSystemsManager::get()->deleteCollectionFiles(file); CollectionSystemsManager::get()->deleteCollectionFiles(file);
ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file); ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file);
ViewController::get()->getGameListView(file->getSystem()).get()->remove(file, true); ViewController::get()->getGameListView(file->getSystem()).get()->remove(file, true);
@ -420,19 +432,19 @@ void GuiGamelistOptions::openMetaDataEd()
}; };
if (file->getType() == FOLDER) { if (file->getType() == FOLDER) {
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, mWindow->pushGui(new GuiMetaDataEd(
file->metadata.getMDD(FOLDER_METADATA), p, mWindow, &file->metadata, file->metadata.getMDD(FOLDER_METADATA), p,
Utils::FileSystem::getFileName(file->getPath()), std::bind( Utils::FileSystem::getFileName(file->getPath()),
&IGameListView::onFileChanged, ViewController::get()->getGameListView( std::bind(&IGameListView::onFileChanged,
file->getSystem()).get(), file, true), ViewController::get()->getGameListView(file->getSystem()).get(), file, true),
clearGameBtnFunc, deleteGameBtnFunc)); clearGameBtnFunc, deleteGameBtnFunc));
} }
else { else {
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, mWindow->pushGui(new GuiMetaDataEd(
file->metadata.getMDD(GAME_METADATA), p, mWindow, &file->metadata, file->metadata.getMDD(GAME_METADATA), p,
Utils::FileSystem::getFileName(file->getPath()), std::bind( Utils::FileSystem::getFileName(file->getPath()),
&IGameListView::onFileChanged, ViewController::get()->getGameListView( std::bind(&IGameListView::onFileChanged,
file->getSystem()).get(), file, true), ViewController::get()->getGameListView(file->getSystem()).get(), file, true),
clearGameBtnFunc, deleteGameBtnFunc)); clearGameBtnFunc, deleteGameBtnFunc));
} }
} }
@ -442,14 +454,14 @@ void GuiGamelistOptions::jumpToLetter()
char letter = mJumpToLetterList->getSelected().front(); char letter = mJumpToLetterList->getSelected().front();
// Get the gamelist. // Get the gamelist.
const std::vector<FileData*>& files = getGamelist()->getCursor()-> const std::vector<FileData*>& files =
getParent()->getChildrenListToDisplay(); getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
for (unsigned int i = 0; i < files.size(); i++) { for (unsigned int i = 0; i < files.size(); i++) {
if (mFavoritesSorting && (mFirstLetterIndex.front() == ViewController::FAVORITE_CHAR || if (mFavoritesSorting && (mFirstLetterIndex.front() == ViewController::FAVORITE_CHAR ||
mFirstLetterIndex.front() == ViewController::FOLDER_CHAR)) { mFirstLetterIndex.front() == ViewController::FOLDER_CHAR)) {
if (static_cast<char>(toupper(files.at(i)->getSortName().front())) == if (static_cast<char>(toupper(files.at(i)->getSortName().front())) == letter &&
letter && !files.at(i)->getFavorite()) { !files.at(i)->getFavorite()) {
if (!mOnlyHasFolders && mFoldersOnTop && files.at(i)->getType() == FOLDER) { if (!mOnlyHasFolders && mFoldersOnTop && files.at(i)->getType() == FOLDER) {
continue; continue;
} }
@ -477,8 +489,8 @@ void GuiGamelistOptions::jumpToFirstRow()
{ {
if (mFoldersOnTop && mJumpToLetterList->getSelected() == ViewController::FAVORITE_CHAR) { if (mFoldersOnTop && mJumpToLetterList->getSelected() == ViewController::FAVORITE_CHAR) {
// Get the gamelist. // Get the gamelist.
const std::vector<FileData*>& files = getGamelist()->getCursor()-> const std::vector<FileData*>& files =
getParent()->getChildrenListToDisplay(); getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
// Select the first game that is not a folder, unless it's a folder-only list in // Select the first game that is not a folder, unless it's a folder-only list in
// which case the first line overall is selected. // which case the first line overall is selected.
for (auto it = files.cbegin(); it != files.cend(); it++) { for (auto it = files.cbegin(); it != files.cend(); it++) {
@ -502,8 +514,7 @@ bool GuiGamelistOptions::input(InputConfig* config, Input input)
if (input.value != 0 && config->isMappedTo("back", input)) if (input.value != 0 && config->isMappedTo("back", input))
mCancelled = true; mCancelled = true;
if (input.value != 0 && (config->isMappedTo("b", input) || if (input.value != 0 && (config->isMappedTo("b", input) || config->isMappedTo("back", input))) {
config->isMappedTo("back", input))) {
delete this; delete this;
return true; return true;
} }
@ -521,9 +532,8 @@ HelpStyle GuiGamelistOptions::getHelpStyle()
std::vector<HelpPrompt> GuiGamelistOptions::getHelpPrompts() std::vector<HelpPrompt> GuiGamelistOptions::getHelpPrompts()
{ {
auto prompts = mMenu.getHelpPrompts(); auto prompts = mMenu.getHelpPrompts();
if (mSystem->getRootFolder()->getChildren().size() > 0 || if (mSystem->getRootFolder()->getChildren().size() > 0 || mIsCustomCollectionGroup ||
mIsCustomCollectionGroup || mIsCustomCollection || mIsCustomCollection || CollectionSystemsManager::get()->isEditing())
CollectionSystemsManager::get()->isEditing())
prompts.push_back(HelpPrompt("a", "select")); prompts.push_back(HelpPrompt("a", "select"));
if (mSystem->getRootFolder()->getChildren().size() > 0 && mSystem->getName() != "recent") { if (mSystem->getRootFolder()->getChildren().size() > 0 && mSystem->getName() != "recent") {
prompts.push_back(HelpPrompt("b", "close (apply)")); prompts.push_back(HelpPrompt("b", "close (apply)"));

View file

@ -13,11 +13,11 @@
#ifndef ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H #ifndef ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
#define ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H #define ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
#include "FileData.h"
#include "GuiComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "FileData.h"
#include "GuiComponent.h"
class IGameListView; class IGameListView;
class SystemData; class SystemData;

View file

@ -14,21 +14,18 @@
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
GuiInfoPopup::GuiInfoPopup( GuiInfoPopup::GuiInfoPopup(Window* window, std::string message, int duration)
Window* window, : GuiComponent(window)
std::string message, , mMessage(message)
int duration) , mDuration(duration)
: GuiComponent(window), , mRunning(true)
mMessage(message),
mDuration(duration),
mRunning(true)
{ {
mFrame = new NinePatchComponent(window); mFrame = new NinePatchComponent(window);
float maxWidth = Renderer::getScreenWidth() * 0.9f; float maxWidth = Renderer::getScreenWidth() * 0.9f;
float maxHeight = Renderer::getScreenHeight() * 0.2f; float maxHeight = Renderer::getScreenHeight() * 0.2f;
std::shared_ptr<TextComponent> s = std::make_shared<TextComponent>(mWindow, "", std::shared_ptr<TextComponent> s = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_MINI), 0x444444FF, ALIGN_CENTER); mWindow, "", Font::get(FONT_SIZE_MINI), 0x444444FF, ALIGN_CENTER);
// We do this to force the text container to resize and return the actual expected popup size. // We do this to force the text container to resize and return the actual expected popup size.
s->setSize(0.0f, 0.0f); s->setSize(0.0f, 0.0f);
@ -57,7 +54,7 @@ GuiInfoPopup::GuiInfoPopup(
setPosition(posX, posY, 0); setPosition(posX, posY, 0);
mFrame->setImagePath(":/graphics/frame.svg"); mFrame->setImagePath(":/graphics/frame.svg");
mFrame->fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mFrame->fitTo(mSize, Vector3f::Zero(), Vector2f(-32.0f, -32.0f));
addChild(mFrame); addChild(mFrame);
// We only initialize the actual time when we first start to render. // We only initialize the actual time when we first start to render.

View file

@ -22,7 +22,7 @@ public:
~GuiInfoPopup(); ~GuiInfoPopup();
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
inline void stop() override { mRunning = false; } void stop() override { mRunning = false; }
private: private:
bool updateState(); bool updateState();

View file

@ -8,20 +8,19 @@
#include "guis/GuiLaunchScreen.h" #include "guis/GuiLaunchScreen.h"
#include "FileData.h"
#include "SystemData.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "math/Misc.h" #include "math/Misc.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "FileData.h"
#include "SystemData.h"
GuiLaunchScreen::GuiLaunchScreen( GuiLaunchScreen::GuiLaunchScreen(Window* window)
Window* window) : GuiComponent(window)
: GuiComponent(window), , mBackground(window, ":/graphics/frame.svg")
mBackground(window, ":/graphics/frame.svg"), , mGrid(nullptr)
mGrid(nullptr), , mMarquee(nullptr)
mMarquee(nullptr), , mWindow(window)
mWindow(window)
{ {
addChild(&mBackground); addChild(&mBackground);
mWindow->setLaunchScreen(this); mWindow->setLaunchScreen(this);
@ -29,6 +28,7 @@ GuiLaunchScreen::GuiLaunchScreen(
GuiLaunchScreen::~GuiLaunchScreen() GuiLaunchScreen::~GuiLaunchScreen()
{ {
// This only executes when exiting the application.
closeLaunchScreen(); closeLaunchScreen();
} }
@ -51,49 +51,53 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
const float gameNameFontSize = 0.073f; const float gameNameFontSize = 0.073f;
// Spacer row. // Spacer row.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0), false, false,
false, false, Vector2i(1, 1)); Vector2i(1, 1));
// Title. // Title.
mTitle = std::make_shared<TextComponent>(mWindow, "LAUNCHING GAME", mTitle = std::make_shared<TextComponent>(
Font::get(static_cast<int>(titleFontSize * std::min(Renderer::getScreenHeight(), mWindow, "LAUNCHING GAME",
Renderer::getScreenWidth()))), 0x666666FF, ALIGN_CENTER); Font::get(static_cast<int>(
titleFontSize * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))),
0x666666FF, ALIGN_CENTER);
mGrid->setEntry(mTitle, Vector2i(1, 1), false, true, Vector2i(1, 1)); mGrid->setEntry(mTitle, Vector2i(1, 1), false, true, Vector2i(1, 1));
// Spacer row. // Spacer row.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 2), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 2), false, false,
false, false, Vector2i(1, 1)); Vector2i(1, 1));
// Row for the marquee. // Row for the marquee.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 3), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 3), false, false,
false, false, Vector2i(1, 1)); Vector2i(1, 1));
// Spacer row. // Spacer row.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 4), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 4), false, false,
false, false, Vector2i(1, 1)); Vector2i(1, 1));
// Game name. // Game name.
mGameName = std::make_shared<TextComponent>(mWindow, "GAME NAME", mGameName = std::make_shared<TextComponent>(
Font::get(static_cast<int>(gameNameFontSize * std::min(Renderer::getScreenHeight(), mWindow, "GAME NAME",
Renderer::getScreenWidth()))), 0x444444FF, ALIGN_CENTER); Font::get(static_cast<int>(
gameNameFontSize * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))),
0x444444FF, ALIGN_CENTER);
mGrid->setEntry(mGameName, Vector2i(1, 5), false, true, Vector2i(1, 1)); mGrid->setEntry(mGameName, Vector2i(1, 5), false, true, Vector2i(1, 1));
// System name. // System name.
mSystemName = std::make_shared<TextComponent>(mWindow, "SYSTEM NAME", mSystemName = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_MEDIUM), 0x666666FF, ALIGN_CENTER); mWindow, "SYSTEM NAME", Font::get(FONT_SIZE_MEDIUM), 0x666666FF, ALIGN_CENTER);
mGrid->setEntry(mSystemName, Vector2i(1, 6), false, true, Vector2i(1, 1)); mGrid->setEntry(mSystemName, Vector2i(1, 6), false, true, Vector2i(1, 1));
// Spacer row. // Spacer row.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 7), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 7), false, false,
false, false, Vector2i(1, 1)); Vector2i(1, 1));
// Left spacer. // Left spacer.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0), false, false,
false, false, Vector2i(1, 8)); Vector2i(1, 8));
// Right spacer. // Right spacer.
mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(2, 0), mGrid->setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(2, 0), false, false,
false, false, Vector2i(1, 8)); Vector2i(1, 8));
// Adjust the width depending on the aspect ratio of the screen, to make the screen look // Adjust the width depending on the aspect ratio of the screen, to make the screen look
// somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9 // somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9
@ -106,9 +110,11 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
float maxWidth = static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier; float maxWidth = static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
float minWidth = static_cast<float>(Renderer::getScreenWidth()) * minWidthModifier; float minWidth = static_cast<float>(Renderer::getScreenWidth()) * minWidthModifier;
float fontWidth = Font::get(static_cast<int>(gameNameFontSize * float fontWidth =
std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth())))-> Font::get(static_cast<int>(gameNameFontSize * std::min(Renderer::getScreenHeight(),
sizeText(Utils::String::toUpper(game->getName())).x(); Renderer::getScreenWidth())))
->sizeText(Utils::String::toUpper(game->getName()))
.x();
// Add a bit of width to compensate for the left and right spacers. // Add a bit of width to compensate for the left and right spacers.
fontWidth += static_cast<float>(Renderer::getScreenWidth()) * 0.05f; fontWidth += static_cast<float>(Renderer::getScreenWidth()) * 0.05f;
@ -159,7 +165,8 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
if (mImagePath != "") { if (mImagePath != "") {
mMarquee->setImage(game->getMarqueePath(), false); mMarquee->setImage(game->getMarqueePath(), false);
mMarquee->cropTransparentPadding(static_cast<float>(Renderer::getScreenWidth()) * mMarquee->cropTransparentPadding(static_cast<float>(Renderer::getScreenWidth()) *
(0.25f * (1.778f / Renderer::getScreenAspectRatio())), mGrid->getRowHeight(3)); (0.25f * (1.778f / Renderer::getScreenAspectRatio())),
mGrid->getRowHeight(3));
mMarquee->setOrigin(0.5f, 0.5f); mMarquee->setOrigin(0.5f, 0.5f);
Vector3f currentPos = mMarquee->getPosition(); Vector3f currentPos = mMarquee->getPosition();
@ -167,8 +174,8 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
// Position the image in the middle of row four. // Position the image in the middle of row four.
currentPos.x() = mSize.x() / 2.0f; currentPos.x() = mSize.x() / 2.0f;
currentPos.y() = mGrid->getRowHeight(0) + mGrid->getRowHeight(1) + currentPos.y() = mGrid->getRowHeight(0) + mGrid->getRowHeight(1) + mGrid->getRowHeight(2) +
mGrid->getRowHeight(2) + mGrid->getRowHeight(3) / 2.0f; mGrid->getRowHeight(3) / 2.0f;
mMarquee->setPosition(currentPos); mMarquee->setPosition(currentPos);
} }
@ -178,7 +185,7 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
setPosition(static_cast<float>(Renderer::getScreenWidth()) / 2.0f, setPosition(static_cast<float>(Renderer::getScreenWidth()) / 2.0f,
static_cast<float>(Renderer::getScreenHeight()) / 2.25f); static_cast<float>(Renderer::getScreenHeight()) / 2.25f);
mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32.0f, -32.0f));
mBackground.setEdgeColor(0xEEEEEEFF); mBackground.setEdgeColor(0xEEEEEEFF);
} }
@ -203,6 +210,7 @@ void GuiLaunchScreen::closeLaunchScreen()
void GuiLaunchScreen::onSizeChanged() void GuiLaunchScreen::onSizeChanged()
{ {
// Update mGrid size.
mGrid->setSize(mSize); mGrid->setSize(mSize);
} }

View file

@ -9,12 +9,12 @@
#ifndef ES_APP_GUIS_GUI_LAUNCH_SCREEN_H #ifndef ES_APP_GUIS_GUI_LAUNCH_SCREEN_H
#define ES_APP_GUIS_GUI_LAUNCH_SCREEN_H #define ES_APP_GUIS_GUI_LAUNCH_SCREEN_H
#include "GuiComponent.h"
#include "Window.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "components/NinePatchComponent.h" #include "components/NinePatchComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "GuiComponent.h"
#include "Window.h"
class FileData; class FileData;
@ -35,7 +35,6 @@ public:
private: private:
Window* mWindow; Window* mWindow;
ComponentGrid* mGrid; ComponentGrid* mGrid;
NinePatchComponent mBackground; NinePatchComponent mBackground;
std::shared_ptr<TextComponent> mTitle; std::shared_ptr<TextComponent> mTitle;

View file

@ -9,8 +9,8 @@
#include "guis/GuiMediaViewerOptions.h" #include "guis/GuiMediaViewerOptions.h"
#include "components/SwitchComponent.h"
#include "Settings.h" #include "Settings.h"
#include "components/SwitchComponent.h"
GuiMediaViewerOptions::GuiMediaViewerOptions(Window* window, const std::string& title) GuiMediaViewerOptions::GuiMediaViewerOptions(Window* window, const std::string& title)
: GuiSettings(window, title) : GuiSettings(window, title)
@ -60,18 +60,16 @@ GuiMediaViewerOptions::GuiMediaViewerOptions(Window* window, const std::string&
video_blur->setState(Settings::getInstance()->getBool("MediaViewerVideoBlur")); video_blur->setState(Settings::getInstance()->getBool("MediaViewerVideoBlur"));
addWithLabel("RENDER BLUR FOR VIDEOS", video_blur); addWithLabel("RENDER BLUR FOR VIDEOS", video_blur);
addSaveFunc([video_blur, this] { addSaveFunc([video_blur, this] {
if (video_blur->getState() != if (video_blur->getState() != Settings::getInstance()->getBool("MediaViewerVideoBlur")) {
Settings::getInstance()->getBool("MediaViewerVideoBlur")) { Settings::getInstance()->setBool("MediaViewerVideoBlur", video_blur->getState());
Settings::getInstance()->setBool("MediaViewerVideoBlur",
video_blur->getState());
setNeedsSaving(); setNeedsSaving();
} }
}); });
// Render scanlines for screenshots using a shader. // Render scanlines for screenshots using a shader.
auto screenshot_scanlines = std::make_shared<SwitchComponent>(mWindow); auto screenshot_scanlines = std::make_shared<SwitchComponent>(mWindow);
screenshot_scanlines->setState(Settings::getInstance()-> screenshot_scanlines->setState(
getBool("MediaViewerScreenshotScanlines")); Settings::getInstance()->getBool("MediaViewerScreenshotScanlines"));
addWithLabel("RENDER SCANLINES FOR SCREENSHOTS", screenshot_scanlines); addWithLabel("RENDER SCANLINES FOR SCREENSHOTS", screenshot_scanlines);
addSaveFunc([screenshot_scanlines, this] { addSaveFunc([screenshot_scanlines, this] {
if (screenshot_scanlines->getState() != if (screenshot_scanlines->getState() !=

View file

@ -9,6 +9,14 @@
#include "guis/GuiMenu.h" #include "guis/GuiMenu.h"
#include "CollectionSystemsManager.h"
#include "EmulationStation.h"
#include "FileFilterIndex.h"
#include "FileSorts.h"
#include "Platform.h"
#include "Scripting.h"
#include "SystemData.h"
#include "VolumeControl.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "components/SliderComponent.h" #include "components/SliderComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
@ -19,23 +27,17 @@
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiScraperMenu.h" #include "guis/GuiScraperMenu.h"
#include "guis/GuiScreensaverOptions.h" #include "guis/GuiScreensaverOptions.h"
#include "views/gamelist/IGameListView.h"
#include "views/UIModeController.h" #include "views/UIModeController.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "views/gamelist/IGameListView.h"
#include "EmulationStation.h"
#include "FileFilterIndex.h"
#include "FileSorts.h"
#include "Platform.h"
#include "Scripting.h"
#include "SystemData.h"
#include "VolumeControl.h"
#include <algorithm>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <algorithm>
GuiMenu::GuiMenu(Window* window) : GuiComponent(window), GuiMenu::GuiMenu(Window* window)
mMenu(window, "MAIN MENU"), mVersion(window) : GuiComponent(window)
, mMenu(window, "MAIN MENU")
, mVersion(window)
{ {
bool isFullUI = UIModeController::getInstance()->isUIModeFull(); bool isFullUI = UIModeController::getInstance()->isUIModeFull();
@ -48,12 +50,11 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window),
addEntry("SOUND SETTINGS", 0x777777FF, true, [this] { openSoundOptions(); }); addEntry("SOUND SETTINGS", 0x777777FF, true, [this] { openSoundOptions(); });
if (isFullUI) if (isFullUI)
addEntry("INPUT DEVICE SETTINGS", 0x777777FF, true, [this] { addEntry("INPUT DEVICE SETTINGS", 0x777777FF, true, [this] { openInputDeviceOptions(); });
openInputDeviceOptions(); });
if (isFullUI) if (isFullUI)
addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, [this] { addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true,
openCollectionSystemOptions(); }); [this] { openCollectionSystemOptions(); });
if (isFullUI) if (isFullUI)
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherOptions(); }); addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherOptions(); });
@ -86,26 +87,24 @@ GuiMenu::~GuiMenu()
ViewController::get()->stopScrolling(); ViewController::get()->stopScrolling();
} }
void GuiMenu::openScraperOptions() void GuiMenu::openScraperOptions() { mWindow->pushGui(new GuiScraperMenu(mWindow, "SCRAPER")); }
{
mWindow->pushGui(new GuiScraperMenu(mWindow, "SCRAPER"));
}
void GuiMenu::openUIOptions() void GuiMenu::openUIOptions()
{ {
auto s = new GuiSettings(mWindow, "UI SETTINGS"); auto s = new GuiSettings(mWindow, "UI SETTINGS");
// Optionally start in selected system/gamelist. // Optionally start in selected system/gamelist.
auto startup_system = std::make_shared<OptionListComponent<std::string>> auto startup_system = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "GAMELIST ON STARTUP", false); mWindow, getHelpStyle(), "GAMELIST ON STARTUP", false);
startup_system->add("NONE", "", Settings::getInstance()->getString("StartupSystem") == ""); startup_system->add("NONE", "", Settings::getInstance()->getString("StartupSystem") == "");
float dotsSize = Font::get(FONT_SIZE_MEDIUM)->sizeText("...").x(); float dotsSize = Font::get(FONT_SIZE_MEDIUM)->sizeText("...").x();
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend();
it != SystemData::sSystemVector.cend(); it++) { it++) {
if ((*it)->getName() != "retropie") { if ((*it)->getName() != "retropie") {
// If required, abbreviate the system name so it doesn't overlap the setting name. // If required, abbreviate the system name so it doesn't overlap the setting name.
std::string abbreviatedString = Font::get(FONT_SIZE_MEDIUM)-> std::string abbreviatedString =
getTextMaxWidth((*it)->getFullName(), mSize.x() * 0.47f); Font::get(FONT_SIZE_MEDIUM)
->getTextMaxWidth((*it)->getFullName(), mSize.x() * 0.47f);
float sizeDifference = Font::get(FONT_SIZE_MEDIUM)->sizeText((*it)->getFullName()).x() - float sizeDifference = Font::get(FONT_SIZE_MEDIUM)->sizeText((*it)->getFullName()).x() -
Font::get(FONT_SIZE_MEDIUM)->sizeText(abbreviatedString).x(); Font::get(FONT_SIZE_MEDIUM)->sizeText(abbreviatedString).x();
if (sizeDifference > 0) { if (sizeDifference > 0) {
@ -122,7 +121,8 @@ void GuiMenu::openUIOptions()
} }
} }
startup_system->add(abbreviatedString, (*it)->getName(), startup_system->add(abbreviatedString, (*it)->getName(),
Settings::getInstance()->getString("StartupSystem") == (*it)->getName()); Settings::getInstance()->getString("StartupSystem") ==
(*it)->getName());
} }
} }
s->addWithLabel("GAMELIST ON STARTUP", startup_system); s->addWithLabel("GAMELIST ON STARTUP", startup_system);
@ -134,8 +134,8 @@ void GuiMenu::openUIOptions()
}); });
// GameList view style. // GameList view style.
auto gamelist_view_style = std::make_shared<OptionListComponent<std::string>> auto gamelist_view_style = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "GAMELIST VIEW STYLE", false); mWindow, getHelpStyle(), "GAMELIST VIEW STYLE", false);
std::string selectedViewStyle = Settings::getInstance()->getString("GamelistViewStyle"); std::string selectedViewStyle = Settings::getInstance()->getString("GamelistViewStyle");
gamelist_view_style->add("automatic", "automatic", selectedViewStyle == "automatic"); gamelist_view_style->add("automatic", "automatic", selectedViewStyle == "automatic");
gamelist_view_style->add("basic", "basic", selectedViewStyle == "basic"); gamelist_view_style->add("basic", "basic", selectedViewStyle == "basic");
@ -159,15 +159,15 @@ void GuiMenu::openUIOptions()
}); });
// Transition style. // Transition style.
auto transition_style = std::make_shared<OptionListComponent<std::string>> auto transition_style = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "TRANSITION STYLE", false); mWindow, getHelpStyle(), "TRANSITION STYLE", false);
std::vector<std::string> transitions; std::vector<std::string> transitions;
transitions.push_back("slide"); transitions.push_back("slide");
transitions.push_back("fade"); transitions.push_back("fade");
transitions.push_back("instant"); transitions.push_back("instant");
for (auto it = transitions.cbegin(); it != transitions.cend(); it++) for (auto it = transitions.cbegin(); it != transitions.cend(); it++)
transition_style->add(*it, *it, Settings::getInstance()-> transition_style->add(*it, *it,
getString("TransitionStyle") == *it); Settings::getInstance()->getString("TransitionStyle") == *it);
s->addWithLabel("TRANSITION STYLE", transition_style); s->addWithLabel("TRANSITION STYLE", transition_style);
s->addSaveFunc([transition_style, s] { s->addSaveFunc([transition_style, s] {
if (transition_style->getSelected() != if (transition_style->getSelected() !=
@ -184,8 +184,8 @@ void GuiMenu::openUIOptions()
themeSets.find(Settings::getInstance()->getString("ThemeSet")); themeSets.find(Settings::getInstance()->getString("ThemeSet"));
if (selectedSet == themeSets.cend()) if (selectedSet == themeSets.cend())
selectedSet = themeSets.cbegin(); selectedSet = themeSets.cbegin();
auto theme_set = std::make_shared<OptionListComponent<std::string>> auto theme_set = std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(),
(mWindow, getHelpStyle(), "THEME SET", false); "THEME SET", false);
for (auto it = themeSets.cbegin(); it != themeSets.cend(); it++) for (auto it = themeSets.cbegin(); it != themeSets.cend(); it++)
theme_set->add(it->first, it->first, it == selectedSet); theme_set->add(it->first, it->first, it == selectedSet);
s->addWithLabel("THEME SET", theme_set); s->addWithLabel("THEME SET", theme_set);
@ -211,8 +211,8 @@ void GuiMenu::openUIOptions()
} }
// UI mode. // UI mode.
auto ui_mode = std::make_shared<OptionListComponent<std::string>> auto ui_mode = std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(),
(mWindow, getHelpStyle(), "UI MODE", false); "UI MODE", false);
std::vector<std::string> uiModes; std::vector<std::string> uiModes;
uiModes.push_back("full"); uiModes.push_back("full");
uiModes.push_back("kiosk"); uiModes.push_back("kiosk");
@ -250,8 +250,9 @@ void GuiMenu::openUIOptions()
msg += "TO UNLOCK AND RETURN TO THE FULL UI, ENTER THIS CODE: \n"; msg += "TO UNLOCK AND RETURN TO THE FULL UI, ENTER THIS CODE: \n";
msg += UIModeController::getInstance()->getFormattedPassKeyStr() + "\n\n"; msg += UIModeController::getInstance()->getFormattedPassKeyStr() + "\n\n";
msg += "DO YOU WANT TO PROCEED?"; msg += "DO YOU WANT TO PROCEED?";
mWindow->pushGui(new GuiMsgBox(mWindow, this->getHelpStyle(), msg, mWindow->pushGui(new GuiMsgBox(
"YES", [this, selectedMode] { mWindow, this->getHelpStyle(), msg, "YES",
[this, selectedMode] {
LOG(LogDebug) << "GuiMenu::openUISettings(): Setting UI mode to '" LOG(LogDebug) << "GuiMenu::openUISettings(): Setting UI mode to '"
<< selectedMode << "'."; << selectedMode << "'.";
Settings::getInstance()->setString("UIMode", selectedMode); Settings::getInstance()->setString("UIMode", selectedMode);
@ -273,11 +274,12 @@ void GuiMenu::openUIOptions()
ViewController::get()->reloadAll(); ViewController::get()->reloadAll();
ViewController::get()->goToSystem(SystemData::sSystemVector.front(), false); ViewController::get()->goToSystem(SystemData::sSystemVector.front(), false);
mWindow->invalidateCachedBackground(); mWindow->invalidateCachedBackground();
}, "NO", nullptr)); },
"NO", nullptr));
} }
else { else {
LOG(LogDebug) << "GuiMenu::openUISettings(): Setting UI mode to '" << LOG(LogDebug) << "GuiMenu::openUISettings(): Setting UI mode to '" << selectedMode
selectedMode << "'."; << "'.";
Settings::getInstance()->setString("UIMode", ui_mode->getSelected()); Settings::getInstance()->setString("UIMode", ui_mode->getSelected());
Settings::getInstance()->setBool("ForceFull", false); Settings::getInstance()->setBool("ForceFull", false);
Settings::getInstance()->setBool("ForceKiosk", false); Settings::getInstance()->setBool("ForceKiosk", false);
@ -296,8 +298,8 @@ void GuiMenu::openUIOptions()
// Default gamelist sort order. // Default gamelist sort order.
typedef OptionListComponent<const FileData::SortType*> SortList; typedef OptionListComponent<const FileData::SortType*> SortList;
std::string sortOrder; std::string sortOrder;
auto default_sort_order = std::make_shared<SortList> auto default_sort_order =
(mWindow, getHelpStyle(), "DEFAULT SORT ORDER", false); std::make_shared<SortList>(mWindow, getHelpStyle(), "DEFAULT SORT ORDER", false);
// Exclude the System sort options. // Exclude the System sort options.
unsigned int numSortTypes = static_cast<unsigned int>(FileSorts::SortTypes.size() - 2); unsigned int numSortTypes = static_cast<unsigned int>(FileSorts::SortTypes.size() - 2);
for (unsigned int i = 0; i < numSortTypes; i++) { for (unsigned int i = 0; i < numSortTypes; i++) {
@ -331,8 +333,8 @@ void GuiMenu::openUIOptions()
}); });
// Open menu effect. // Open menu effect.
auto menu_opening_effect = std::make_shared<OptionListComponent<std::string>> auto menu_opening_effect = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "MENU OPENING EFFECT", false); mWindow, getHelpStyle(), "MENU OPENING EFFECT", false);
std::string selectedMenuEffect = Settings::getInstance()->getString("MenuOpeningEffect"); std::string selectedMenuEffect = Settings::getInstance()->getString("MenuOpeningEffect");
menu_opening_effect->add("SCALE-UP", "scale-up", selectedMenuEffect == "scale-up"); menu_opening_effect->add("SCALE-UP", "scale-up", selectedMenuEffect == "scale-up");
menu_opening_effect->add("NONE", "none", selectedMenuEffect == "none"); menu_opening_effect->add("NONE", "none", selectedMenuEffect == "none");
@ -351,8 +353,8 @@ void GuiMenu::openUIOptions()
}); });
// Launch screen duration. // Launch screen duration.
auto launch_screen_duration = std::make_shared<OptionListComponent<std::string>> auto launch_screen_duration = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "LAUNCH SCREEN DURATION", false); mWindow, getHelpStyle(), "LAUNCH SCREEN DURATION", false);
std::string selectedDuration = Settings::getInstance()->getString("LaunchScreenDuration"); std::string selectedDuration = Settings::getInstance()->getString("LaunchScreenDuration");
launch_screen_duration->add("NORMAL", "normal", selectedDuration == "normal"); launch_screen_duration->add("NORMAL", "normal", selectedDuration == "normal");
launch_screen_duration->add("BRIEF", "brief", selectedDuration == "brief"); launch_screen_duration->add("BRIEF", "brief", selectedDuration == "brief");
@ -421,8 +423,7 @@ void GuiMenu::openUIOptions()
folders_on_top->setState(Settings::getInstance()->getBool("FoldersOnTop")); folders_on_top->setState(Settings::getInstance()->getBool("FoldersOnTop"));
s->addWithLabel("SORT FOLDERS ON TOP OF GAMELISTS", folders_on_top); s->addWithLabel("SORT FOLDERS ON TOP OF GAMELISTS", folders_on_top);
s->addSaveFunc([folders_on_top, s] { s->addSaveFunc([folders_on_top, s] {
if (folders_on_top->getState() != if (folders_on_top->getState() != Settings::getInstance()->getBool("FoldersOnTop")) {
Settings::getInstance()->getBool("FoldersOnTop")) {
Settings::getInstance()->setBool("FoldersOnTop", folders_on_top->getState()); Settings::getInstance()->setBool("FoldersOnTop", folders_on_top->getState());
s->setNeedsSaving(); s->setNeedsSaving();
s->setNeedsSorting(); s->setNeedsSorting();
@ -435,8 +436,7 @@ void GuiMenu::openUIOptions()
favorites_first->setState(Settings::getInstance()->getBool("FavoritesFirst")); favorites_first->setState(Settings::getInstance()->getBool("FavoritesFirst"));
s->addWithLabel("SORT FAVORITE GAMES ABOVE NON-FAVORITES", favorites_first); s->addWithLabel("SORT FAVORITE GAMES ABOVE NON-FAVORITES", favorites_first);
s->addSaveFunc([favorites_first, s] { s->addSaveFunc([favorites_first, s] {
if (favorites_first->getState() != if (favorites_first->getState() != Settings::getInstance()->getBool("FavoritesFirst")) {
Settings::getInstance()->getBool("FavoritesFirst")) {
Settings::getInstance()->setBool("FavoritesFirst", favorites_first->getState()); Settings::getInstance()->setBool("FavoritesFirst", favorites_first->getState());
s->setNeedsSaving(); s->setNeedsSaving();
s->setNeedsSorting(); s->setNeedsSorting();
@ -465,8 +465,7 @@ void GuiMenu::openUIOptions()
s->addSaveFunc([special_chars_ascii, s] { s->addSaveFunc([special_chars_ascii, s] {
if (special_chars_ascii->getState() != if (special_chars_ascii->getState() !=
Settings::getInstance()->getBool("SpecialCharsASCII")) { Settings::getInstance()->getBool("SpecialCharsASCII")) {
Settings::getInstance()->setBool("SpecialCharsASCII", Settings::getInstance()->setBool("SpecialCharsASCII", special_chars_ascii->getState());
special_chars_ascii->getState());
s->setNeedsSaving(); s->setNeedsSaving();
s->setNeedsReloading(); s->setNeedsReloading();
s->setInvalidateCachedBackground(); s->setInvalidateCachedBackground();
@ -503,10 +502,8 @@ void GuiMenu::openUIOptions()
random_add_button->setState(Settings::getInstance()->getBool("RandomAddButton")); random_add_button->setState(Settings::getInstance()->getBool("RandomAddButton"));
s->addWithLabel("ENABLE RANDOM SYSTEM OR GAME BUTTON", random_add_button); s->addWithLabel("ENABLE RANDOM SYSTEM OR GAME BUTTON", random_add_button);
s->addSaveFunc([random_add_button, s] { s->addSaveFunc([random_add_button, s] {
if (Settings::getInstance()->getBool("RandomAddButton") != if (Settings::getInstance()->getBool("RandomAddButton") != random_add_button->getState()) {
random_add_button->getState()) { Settings::getInstance()->setBool("RandomAddButton", random_add_button->getState());
Settings::getInstance()->setBool("RandomAddButton",
random_add_button->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -516,8 +513,7 @@ void GuiMenu::openUIOptions()
gamelist_filters->setState(Settings::getInstance()->getBool("GamelistFilters")); gamelist_filters->setState(Settings::getInstance()->getBool("GamelistFilters"));
s->addWithLabel("ENABLE GAMELIST FILTERS", gamelist_filters); s->addWithLabel("ENABLE GAMELIST FILTERS", gamelist_filters);
s->addSaveFunc([gamelist_filters, s] { s->addSaveFunc([gamelist_filters, s] {
if (Settings::getInstance()->getBool("GamelistFilters") != if (Settings::getInstance()->getBool("GamelistFilters") != gamelist_filters->getState()) {
gamelist_filters->getState()) {
Settings::getInstance()->setBool("GamelistFilters", gamelist_filters->getState()); Settings::getInstance()->setBool("GamelistFilters", gamelist_filters->getState());
s->setNeedsSaving(); s->setNeedsSaving();
s->setNeedsReloading(); s->setNeedsReloading();
@ -563,8 +559,10 @@ void GuiMenu::openUIOptions()
// Media viewer. // Media viewer.
ComponentListRow media_viewer_row; ComponentListRow media_viewer_row;
media_viewer_row.elements.clear(); media_viewer_row.elements.clear();
media_viewer_row.addElement(std::make_shared<TextComponent> media_viewer_row.addElement(std::make_shared<TextComponent>(mWindow, "MEDIA VIEWER SETTINGS",
(mWindow, "MEDIA VIEWER SETTINGS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM),
0x777777FF),
true);
media_viewer_row.addElement(makeArrow(mWindow), false); media_viewer_row.addElement(makeArrow(mWindow), false);
media_viewer_row.makeAcceptInputHandler(std::bind(&GuiMenu::openMediaViewerOptions, this)); media_viewer_row.makeAcceptInputHandler(std::bind(&GuiMenu::openMediaViewerOptions, this));
s->addRow(media_viewer_row); s->addRow(media_viewer_row);
@ -572,8 +570,10 @@ void GuiMenu::openUIOptions()
// Screensaver. // Screensaver.
ComponentListRow screensaver_row; ComponentListRow screensaver_row;
screensaver_row.elements.clear(); screensaver_row.elements.clear();
screensaver_row.addElement(std::make_shared<TextComponent> screensaver_row.addElement(std::make_shared<TextComponent>(mWindow, "SCREENSAVER SETTINGS",
(mWindow, "SCREENSAVER SETTINGS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM),
0x777777FF),
true);
screensaver_row.addElement(makeArrow(mWindow), false); screensaver_row.addElement(makeArrow(mWindow), false);
screensaver_row.makeAcceptInputHandler(std::bind(&GuiMenu::openScreensaverOptions, this)); screensaver_row.makeAcceptInputHandler(std::bind(&GuiMenu::openScreensaverOptions, this));
s->addRow(screensaver_row); s->addRow(screensaver_row);
@ -593,8 +593,8 @@ void GuiMenu::openSoundOptions()
system_volume->setValue(static_cast<float>(VolumeControl::getInstance()->getVolume())); system_volume->setValue(static_cast<float>(VolumeControl::getInstance()->getVolume()));
s->addWithLabel("SYSTEM VOLUME", system_volume); s->addWithLabel("SYSTEM VOLUME", system_volume);
s->addSaveFunc([system_volume] { s->addSaveFunc([system_volume] {
VolumeControl::getInstance()-> VolumeControl::getInstance()->setVolume(
setVolume(static_cast<int>(std::round(system_volume->getValue()))); static_cast<int>(std::round(system_volume->getValue())));
// Explicitly delete the VolumeControl instance so that it will reinitialize the // Explicitly delete the VolumeControl instance so that it will reinitialize the
// next time the menu is entered. This is the easiest way to detect new default // next time the menu is entered. This is the easiest way to detect new default
// audio devices or changes to the audio volume done by the operating system. // audio devices or changes to the audio volume done by the operating system.
@ -603,10 +603,9 @@ void GuiMenu::openSoundOptions()
#endif #endif
// Volume for navigation sounds. // Volume for navigation sounds.
auto sound_volume_navigation = auto sound_volume_navigation = std::make_shared<SliderComponent>(mWindow, 0.f, 100.f, 1.f, "%");
std::make_shared<SliderComponent>(mWindow, 0.f, 100.f, 1.f, "%"); sound_volume_navigation->setValue(
sound_volume_navigation->setValue(static_cast<float>(Settings::getInstance()-> static_cast<float>(Settings::getInstance()->getInt("SoundVolumeNavigation")));
getInt("SoundVolumeNavigation")));
s->addWithLabel("NAVIGATION SOUNDS VOLUME", sound_volume_navigation); s->addWithLabel("NAVIGATION SOUNDS VOLUME", sound_volume_navigation);
s->addSaveFunc([sound_volume_navigation, s] { s->addSaveFunc([sound_volume_navigation, s] {
if (sound_volume_navigation->getValue() != if (sound_volume_navigation->getValue() !=
@ -618,10 +617,9 @@ void GuiMenu::openSoundOptions()
}); });
// Volume for videos. // Volume for videos.
auto sound_volume_videos = auto sound_volume_videos = std::make_shared<SliderComponent>(mWindow, 0.f, 100.f, 1.f, "%");
std::make_shared<SliderComponent>(mWindow, 0.f, 100.f, 1.f, "%"); sound_volume_videos->setValue(
sound_volume_videos->setValue(static_cast<float>(Settings::getInstance()-> static_cast<float>(Settings::getInstance()->getInt("SoundVolumeVideos")));
getInt("SoundVolumeVideos")));
s->addWithLabel("VIDEO PLAYER VOLUME", sound_volume_videos); s->addWithLabel("VIDEO PLAYER VOLUME", sound_volume_videos);
s->addSaveFunc([sound_volume_videos, s] { s->addSaveFunc([sound_volume_videos, s] {
if (sound_volume_videos->getValue() != if (sound_volume_videos->getValue() !=
@ -633,106 +631,6 @@ void GuiMenu::openSoundOptions()
}); });
if (UIModeController::getInstance()->isUIModeFull()) { if (UIModeController::getInstance()->isUIModeFull()) {
// The ALSA Audio Card and Audio Device selection code is disabled at the moment.
// As PulseAudio controls the sound devices for the desktop environment, it doesn't
// make much sense to be able to select ALSA devices directly. Normally (always?)
// the selection doesn't make any difference at all. But maybe some PulseAudio
// settings could be added later on, if needed.
// The code is still active for Raspberry Pi though as I'm not sure if this is
// useful for that device.
// #if defined(__linux__)
#if defined(_RPI_)
// Audio card.
auto audio_card = std::make_shared<OptionListComponent<std::string>>
(mWindow, getHelpStyle(), "AUDIO CARD", false);
std::vector<std::string> audio_cards;
#if defined(_RPI_)
// RPi Specific Audio Cards.
audio_cards.push_back("local");
audio_cards.push_back("hdmi");
audio_cards.push_back("both");
#endif
audio_cards.push_back("default");
audio_cards.push_back("sysdefault");
audio_cards.push_back("dmix");
audio_cards.push_back("hw");
audio_cards.push_back("plughw");
audio_cards.push_back("null");
if (Settings::getInstance()->getString("AudioCard") != "") {
if (std::find(audio_cards.begin(), audio_cards.end(),
Settings::getInstance()->getString("AudioCard")) == audio_cards.end()) {
audio_cards.push_back(Settings::getInstance()->getString("AudioCard"));
}
}
for (auto ac = audio_cards.cbegin(); ac != audio_cards.cend(); ac++)
audio_card->add(*ac, *ac, Settings::getInstance()->getString("AudioCard") == *ac);
s->addWithLabel("AUDIO CARD", audio_card);
s->addSaveFunc([audio_card, s] {
if (audio_card->getSelected() != Settings::getInstance()->getString("AudioCard")) {
Settings::getInstance()->setString("AudioCard", audio_card->getSelected());
VolumeControl::getInstance()->deinit();
VolumeControl::getInstance()->init();
s->setNeedsSaving();
}
});
// Volume control device.
auto vol_dev = std::make_shared<OptionListComponent<std::string>>
(mWindow, getHelpStyle(), "AUDIO DEVICE", false);
std::vector<std::string> transitions;
transitions.push_back("PCM");
transitions.push_back("Speaker");
transitions.push_back("Master");
transitions.push_back("Digital");
transitions.push_back("Analogue");
if (Settings::getInstance()->getString("AudioDevice") != "") {
if (std::find(transitions.begin(), transitions.end(),
Settings::getInstance()->getString("AudioDevice")) == transitions.end()) {
transitions.push_back(Settings::getInstance()->getString("AudioDevice"));
}
}
for (auto it = transitions.cbegin(); it != transitions.cend(); it++)
vol_dev->add(*it, *it, Settings::getInstance()->getString("AudioDevice") == *it);
s->addWithLabel("AUDIO DEVICE", vol_dev);
s->addSaveFunc([vol_dev, s] {
if (vol_dev->getSelected() != Settings::getInstance()->getString("AudioDevice")) {
Settings::getInstance()->setString("AudioDevice", vol_dev->getSelected());
VolumeControl::getInstance()->deinit();
VolumeControl::getInstance()->init();
s->setNeedsSaving();
}
});
#endif
#if defined(_RPI_)
// OMXPlayer audio device.
auto omx_audio_dev = std::make_shared<OptionListComponent<std::string>>
(mWindow, getHelpStyle(), "OMX PLAYER AUDIO DEVICE", false);
std::vector<std::string> omx_cards;
// RPi Specific Audio Cards
omx_cards.push_back("local");
omx_cards.push_back("hdmi");
omx_cards.push_back("both");
omx_cards.push_back("alsa:hw:0,0");
omx_cards.push_back("alsa:hw:1,0");
if (Settings::getInstance()->getString("OMXAudioDev") != "") {
if (std::find(omx_cards.begin(), omx_cards.end(),
Settings::getInstance()->getString("OMXAudioDev")) == omx_cards.end()) {
omx_cards.push_back(Settings::getInstance()->getString("OMXAudioDev"));
}
}
for (auto it = omx_cards.cbegin(); it != omx_cards.cend(); it++)
omx_audio_dev->add(*it, *it, Settings::getInstance()->getString("OMXAudioDev") == *it);
s->addWithLabel("OMX PLAYER AUDIO DEVICE", omx_audio_dev);
s->addSaveFunc([omx_audio_dev, s] {
if (omx_audio_dev->getSelected() !=
Settings::getInstance()->getString("OMXAudioDev")) {
Settings::getInstance()->setString("OMXAudioDev", omx_audio_dev->getSelected());
s->setNeedsSaving();
}
});
#endif
// Play audio for gamelist videos. // Play audio for gamelist videos.
auto gamelist_video_audio = std::make_shared<SwitchComponent>(mWindow); auto gamelist_video_audio = std::make_shared<SwitchComponent>(mWindow);
gamelist_video_audio->setState(Settings::getInstance()->getBool("GamelistVideoAudio")); gamelist_video_audio->setState(Settings::getInstance()->getBool("GamelistVideoAudio"));
@ -748,8 +646,8 @@ void GuiMenu::openSoundOptions()
// Play audio for media viewer videos. // Play audio for media viewer videos.
auto media_viewer_video_audio = std::make_shared<SwitchComponent>(mWindow); auto media_viewer_video_audio = std::make_shared<SwitchComponent>(mWindow);
media_viewer_video_audio->setState(Settings::getInstance()-> media_viewer_video_audio->setState(
getBool("MediaViewerVideoAudio")); Settings::getInstance()->getBool("MediaViewerVideoAudio"));
s->addWithLabel("PLAY AUDIO FOR MEDIA VIEWER VIDEOS", media_viewer_video_audio); s->addWithLabel("PLAY AUDIO FOR MEDIA VIEWER VIDEOS", media_viewer_video_audio);
s->addSaveFunc([media_viewer_video_audio, s] { s->addSaveFunc([media_viewer_video_audio, s] {
if (media_viewer_video_audio->getState() != if (media_viewer_video_audio->getState() !=
@ -762,8 +660,8 @@ void GuiMenu::openSoundOptions()
// Play audio for screensaver videos. // Play audio for screensaver videos.
auto screensaver_video_audio = std::make_shared<SwitchComponent>(mWindow); auto screensaver_video_audio = std::make_shared<SwitchComponent>(mWindow);
screensaver_video_audio->setState(Settings::getInstance()-> screensaver_video_audio->setState(
getBool("ScreensaverVideoAudio")); Settings::getInstance()->getBool("ScreensaverVideoAudio"));
s->addWithLabel("PLAY AUDIO FOR SCREENSAVER VIDEOS", screensaver_video_audio); s->addWithLabel("PLAY AUDIO FOR SCREENSAVER VIDEOS", screensaver_video_audio);
s->addSaveFunc([screensaver_video_audio, s] { s->addSaveFunc([screensaver_video_audio, s] {
if (screensaver_video_audio->getState() != if (screensaver_video_audio->getState() !=
@ -776,14 +674,12 @@ void GuiMenu::openSoundOptions()
// Navigation sounds. // Navigation sounds.
auto navigation_sounds = std::make_shared<SwitchComponent>(mWindow); auto navigation_sounds = std::make_shared<SwitchComponent>(mWindow);
navigation_sounds->setState(Settings::getInstance()-> navigation_sounds->setState(Settings::getInstance()->getBool("NavigationSounds"));
getBool("NavigationSounds"));
s->addWithLabel("ENABLE NAVIGATION SOUNDS", navigation_sounds); s->addWithLabel("ENABLE NAVIGATION SOUNDS", navigation_sounds);
s->addSaveFunc([navigation_sounds, s] { s->addSaveFunc([navigation_sounds, s] {
if (navigation_sounds->getState() != if (navigation_sounds->getState() !=
Settings::getInstance()->getBool("NavigationSounds")) { Settings::getInstance()->getBool("NavigationSounds")) {
Settings::getInstance()->setBool("NavigationSounds", Settings::getInstance()->setBool("NavigationSounds", navigation_sounds->getState());
navigation_sounds->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -797,8 +693,8 @@ void GuiMenu::openInputDeviceOptions()
auto s = new GuiSettings(mWindow, "INPUT DEVICE SETTINGS"); auto s = new GuiSettings(mWindow, "INPUT DEVICE SETTINGS");
// Controller type. // Controller type.
auto input_controller_type = std::make_shared<OptionListComponent<std::string>> auto input_controller_type = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "CONTROLLER TYPE", false); mWindow, getHelpStyle(), "CONTROLLER TYPE", false);
std::string selectedPlayer = Settings::getInstance()->getString("InputControllerType"); std::string selectedPlayer = Settings::getInstance()->getString("InputControllerType");
input_controller_type->add("XBOX", "xbox", selectedPlayer == "xbox"); input_controller_type->add("XBOX", "xbox", selectedPlayer == "xbox");
input_controller_type->add("XBOX 360", "xbox360", selectedPlayer == "xbox360"); input_controller_type->add("XBOX 360", "xbox360", selectedPlayer == "xbox360");
@ -822,8 +718,8 @@ void GuiMenu::openInputDeviceOptions()
// Whether to only accept input from the first controller. // Whether to only accept input from the first controller.
auto input_only_first_controller = std::make_shared<SwitchComponent>(mWindow); auto input_only_first_controller = std::make_shared<SwitchComponent>(mWindow);
input_only_first_controller->setState(Settings::getInstance()-> input_only_first_controller->setState(
getBool("InputOnlyFirstController")); Settings::getInstance()->getBool("InputOnlyFirstController"));
s->addWithLabel("ONLY ACCEPT INPUT FROM FIRST CONTROLLER", input_only_first_controller); s->addWithLabel("ONLY ACCEPT INPUT FROM FIRST CONTROLLER", input_only_first_controller);
s->addSaveFunc([input_only_first_controller, s] { s->addSaveFunc([input_only_first_controller, s] {
if (Settings::getInstance()->getBool("InputOnlyFirstController") != if (Settings::getInstance()->getBool("InputOnlyFirstController") !=
@ -837,9 +733,10 @@ void GuiMenu::openInputDeviceOptions()
// Configure keyboard and controllers. // Configure keyboard and controllers.
ComponentListRow configure_input_row; ComponentListRow configure_input_row;
configure_input_row.elements.clear(); configure_input_row.elements.clear();
configure_input_row.addElement(std::make_shared<TextComponent> configure_input_row.addElement(
(mWindow, "CONFIGURE KEYBOARD AND CONTROLLERS", std::make_shared<TextComponent>(mWindow, "CONFIGURE KEYBOARD AND CONTROLLERS",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
configure_input_row.addElement(makeArrow(mWindow), false); configure_input_row.addElement(makeArrow(mWindow), false);
configure_input_row.makeAcceptInputHandler(std::bind(&GuiMenu::openConfigInput, this, s)); configure_input_row.makeAcceptInputHandler(std::bind(&GuiMenu::openConfigInput, this, s));
s->addRow(configure_input_row); s->addRow(configure_input_row);
@ -856,19 +753,17 @@ void GuiMenu::openConfigInput(GuiSettings* settings)
// the input device settings menu later on. // the input device settings menu later on.
settings->setNeedsSaving(false); settings->setNeedsSaving(false);
std::string message = std::string message = "THE KEYBOARD AND CONTROLLERS ARE AUTOMATICALLY\n"
"THE KEYBOARD AND ANY CONNECTED CONTROLLERS\n" "CONFIGURED, BUT USING THIS CONFIGURATION TOOL\n"
"ARE AUTOMATICALLY CONFIGURED ON STARTUP, BUT\n" "YOU CAN OVERRIDE THE DEFAULT BUTTON MAPPINGS\n"
"USING THIS CONFIGURATION TOOL YOU ARE ABLE TO\n" "(THIS WILL NOT AFFECT THE HELP PROMPTS)\n"
"OVERRIDE THE DEFAULT BUTTON MAPPINGS (NOTE\n"
"THAT THIS WILL NOT AFFECT THE HELP PROMPTS)\n"
"CONTINUE?"; "CONTINUE?";
Window* window = mWindow; Window* window = mWindow;
window->pushGui(new GuiMsgBox(window, getHelpStyle(), window->pushGui(new GuiMsgBox(
message, "YES", [window] { window, getHelpStyle(), message, "YES",
window->pushGui(new GuiDetectDevice(window, false, false, nullptr)); [window] { window->pushGui(new GuiDetectDevice(window, false, false, nullptr)); }, "NO",
}, "NO", nullptr)); nullptr));
} }
void GuiMenu::openOtherOptions() void GuiMenu::openOtherOptions()
@ -888,8 +783,8 @@ void GuiMenu::openOtherOptions()
}); });
// Display/monitor. // Display/monitor.
auto display_index = std::make_shared<OptionListComponent<std::string>> auto display_index = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "DISPLAY/MONITOR INDEX", false); mWindow, getHelpStyle(), "DISPLAY/MONITOR INDEX", false);
std::vector<std::string> displayIndex; std::vector<std::string> displayIndex;
displayIndex.push_back("1"); displayIndex.push_back("1");
displayIndex.push_back("2"); displayIndex.push_back("2");
@ -910,8 +805,8 @@ void GuiMenu::openOtherOptions()
#if defined(__unix__) #if defined(__unix__)
// Fullscreen mode. // Fullscreen mode.
auto fullscreen_mode = std::make_shared<OptionListComponent<std::string>> auto fullscreen_mode = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "FULLSCREEN MODE", false); mWindow, getHelpStyle(), "FULLSCREEN MODE", false);
std::vector<std::string> screenmode; std::vector<std::string> screenmode;
screenmode.push_back("normal"); screenmode.push_back("normal");
screenmode.push_back("borderless"); screenmode.push_back("borderless");
@ -929,8 +824,8 @@ void GuiMenu::openOtherOptions()
#if defined(BUILD_VLC_PLAYER) #if defined(BUILD_VLC_PLAYER)
// Video player. // Video player.
auto video_player = std::make_shared<OptionListComponent<std::string>> auto video_player = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "FULLSCREEN MODE", false); mWindow, getHelpStyle(), "FULLSCREEN MODE", false);
std::string selectedPlayer = Settings::getInstance()->getString("VideoPlayer"); std::string selectedPlayer = Settings::getInstance()->getString("VideoPlayer");
video_player->add("FFmpeg", "ffmpeg", selectedPlayer == "ffmpeg"); video_player->add("FFmpeg", "ffmpeg", selectedPlayer == "ffmpeg");
video_player->add("VLC", "vlc", selectedPlayer == "vlc"); video_player->add("VLC", "vlc", selectedPlayer == "vlc");
@ -971,15 +866,15 @@ void GuiMenu::openOtherOptions()
}); });
// When to save game metadata. // When to save game metadata.
auto save_gamelist_mode = std::make_shared<OptionListComponent<std::string>> auto save_gamelist_mode = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "WHEN TO SAVE METADATA", false); mWindow, getHelpStyle(), "WHEN TO SAVE METADATA", false);
std::vector<std::string> saveModes; std::vector<std::string> saveModes;
saveModes.push_back("on exit"); saveModes.push_back("on exit");
saveModes.push_back("always"); saveModes.push_back("always");
saveModes.push_back("never"); saveModes.push_back("never");
for (auto it = saveModes.cbegin(); it != saveModes.cend(); it++) { for (auto it = saveModes.cbegin(); it != saveModes.cend(); it++) {
save_gamelist_mode->add(*it, *it, Settings::getInstance()-> save_gamelist_mode->add(*it, *it,
getString("SaveGamelistsMode") == *it); Settings::getInstance()->getString("SaveGamelistsMode") == *it);
} }
s->addWithLabel("WHEN TO SAVE GAME METADATA", save_gamelist_mode); s->addWithLabel("WHEN TO SAVE GAME METADATA", save_gamelist_mode);
s->addSaveFunc([save_gamelist_mode, s] { s->addSaveFunc([save_gamelist_mode, s] {
@ -987,7 +882,7 @@ void GuiMenu::openOtherOptions()
Settings::getInstance()->getString("SaveGamelistsMode")) { Settings::getInstance()->getString("SaveGamelistsMode")) {
Settings::getInstance()->setString("SaveGamelistsMode", Settings::getInstance()->setString("SaveGamelistsMode",
save_gamelist_mode->getSelected()); save_gamelist_mode->getSelected());
// Always save the gamelist.xml files if switching to 'always' as there may // Always save the gamelist.xml files if switching to "always" as there may
// be changes that will otherwise be lost. // be changes that will otherwise be lost.
if (Settings::getInstance()->getString("SaveGamelistsMode") == "always") { if (Settings::getInstance()->getString("SaveGamelistsMode") == "always") {
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin();
@ -1019,38 +914,22 @@ void GuiMenu::openOtherOptions()
mWindow->invalidateCachedBackground(); mWindow->invalidateCachedBackground();
}; };
rowMediaDir.makeAcceptInputHandler([this, titleMediaDir, mediaDirectoryStaticText, rowMediaDir.makeAcceptInputHandler([this, titleMediaDir, mediaDirectoryStaticText,
defaultDirectoryText, initValueMediaDir, updateValMediaDir, multiLineMediaDir] { defaultDirectoryText, initValueMediaDir, updateValMediaDir,
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, getHelpStyle(), multiLineMediaDir] {
titleMediaDir, mediaDirectoryStaticText, defaultDirectoryText, mWindow->pushGui(new GuiComplexTextEditPopup(
Settings::getInstance()->getString("MediaDirectory"), mWindow, getHelpStyle(), titleMediaDir, mediaDirectoryStaticText, defaultDirectoryText,
updateValMediaDir, multiLineMediaDir, "SAVE", "SAVE CHANGES?")); Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir,
multiLineMediaDir, "SAVE", "SAVE CHANGES?"));
}); });
s->addRow(rowMediaDir); s->addRow(rowMediaDir);
#if defined(_RPI_)
// Video playing using OMXPlayer.
auto video_omx_player = std::make_shared<SwitchComponent>(mWindow);
video_omx_player->setState(Settings::getInstance()->getBool("VideoOmxPlayer"));
s->addWithLabel("USE OMX PLAYER (HW ACCELERATED)", video_omx_player);
s->addSaveFunc([video_omx_player, s] {
if (video_omx_player->getState() !=
Settings::getInstance()->getBool("VideoOmxPlayer")) {
Settings::getInstance()->setBool("VideoOmxPlayer", video_omx_player->getState());
s->setNeedsSaving();
// Need to reload all views to re-create the right video components.
s->setNeedsReloading();
}
});
#endif
#if defined(_WIN64) #if defined(_WIN64)
// Hide taskbar during the ES program session. // Hide taskbar during the ES program session.
auto hide_taskbar = std::make_shared<SwitchComponent>(mWindow); auto hide_taskbar = std::make_shared<SwitchComponent>(mWindow);
hide_taskbar->setState(Settings::getInstance()->getBool("HideTaskbar")); hide_taskbar->setState(Settings::getInstance()->getBool("HideTaskbar"));
s->addWithLabel("HIDE TASKBAR (REQUIRES RESTART)", hide_taskbar); s->addWithLabel("HIDE TASKBAR (REQUIRES RESTART)", hide_taskbar);
s->addSaveFunc([hide_taskbar, s] { s->addSaveFunc([hide_taskbar, s] {
if (hide_taskbar->getState() != if (hide_taskbar->getState() != Settings::getInstance()->getBool("HideTaskbar")) {
Settings::getInstance()->getBool("HideTaskbar")) {
Settings::getInstance()->setBool("HideTaskbar", hide_taskbar->getState()); Settings::getInstance()->setBool("HideTaskbar", hide_taskbar->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
@ -1080,15 +959,35 @@ void GuiMenu::openOtherOptions()
} }
}); });
// If the RunInBackground setting is enabled, then disable this option. // If the RunInBackground setting is enabled, then gray out this option.
if (Settings::getInstance()->getBool("RunInBackground")) { if (Settings::getInstance()->getBool("RunInBackground")) {
launch_workaround->setEnabled(false); launch_workaround->setEnabled(false);
launch_workaround->setOpacity(DISABLED_OPACITY); launch_workaround->setOpacity(DISABLED_OPACITY);
launch_workaround->getParent()->getChild(launch_workaround-> launch_workaround->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(launch_workaround->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
#endif #endif
#if !defined(_RPI_)
// Whether to enable hardware decoding for the FFmpeg video player.
auto video_hardware_decoding = std::make_shared<SwitchComponent>(mWindow);
video_hardware_decoding->setState(Settings::getInstance()->getBool("VideoHardwareDecoding"));
#if defined(BUILD_VLC_PLAYER)
s->addWithLabel("FFMPEG HARDWARE DECODING (EXPERIMENTAL)", video_hardware_decoding);
#else
s->addWithLabel("VIDEO HARDWARE DECODING (EXPERIMENTAL)", video_hardware_decoding);
#endif
s->addSaveFunc([video_hardware_decoding, s] {
if (video_hardware_decoding->getState() !=
Settings::getInstance()->getBool("VideoHardwareDecoding")) {
Settings::getInstance()->setBool("VideoHardwareDecoding",
video_hardware_decoding->getState());
s->setNeedsSaving();
}
});
#endif
// Whether to upscale the video frame rate to 60 FPS. // Whether to upscale the video frame rate to 60 FPS.
auto video_upscale_frame_rate = std::make_shared<SwitchComponent>(mWindow); auto video_upscale_frame_rate = std::make_shared<SwitchComponent>(mWindow);
video_upscale_frame_rate->setState(Settings::getInstance()->getBool("VideoUpscaleFrameRate")); video_upscale_frame_rate->setState(Settings::getInstance()->getBool("VideoUpscaleFrameRate"));
@ -1100,8 +999,8 @@ void GuiMenu::openOtherOptions()
s->addSaveFunc([video_upscale_frame_rate, s] { s->addSaveFunc([video_upscale_frame_rate, s] {
if (video_upscale_frame_rate->getState() != if (video_upscale_frame_rate->getState() !=
Settings::getInstance()->getBool("VideoUpscaleFrameRate")) { Settings::getInstance()->getBool("VideoUpscaleFrameRate")) {
Settings::getInstance()-> Settings::getInstance()->setBool("VideoUpscaleFrameRate",
setBool("VideoUpscaleFrameRate", video_upscale_frame_rate->getState()); video_upscale_frame_rate->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -1114,8 +1013,8 @@ void GuiMenu::openOtherOptions()
s->addSaveFunc([launchcommand_override, s] { s->addSaveFunc([launchcommand_override, s] {
if (launchcommand_override->getState() != if (launchcommand_override->getState() !=
Settings::getInstance()->getBool("LaunchCommandOverride")) { Settings::getInstance()->getBool("LaunchCommandOverride")) {
Settings::getInstance()-> Settings::getInstance()->setBool("LaunchCommandOverride",
setBool("LaunchCommandOverride", launchcommand_override->getState()); launchcommand_override->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -1174,8 +1073,7 @@ void GuiMenu::openOtherOptions()
s->addSaveFunc([disable_composition, s] { s->addSaveFunc([disable_composition, s] {
if (disable_composition->getState() != if (disable_composition->getState() !=
Settings::getInstance()->getBool("DisableComposition")) { Settings::getInstance()->getBool("DisableComposition")) {
Settings::getInstance()->setBool("DisableComposition", Settings::getInstance()->setBool("DisableComposition", disable_composition->getState());
disable_composition->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -1214,8 +1112,7 @@ void GuiMenu::openOtherOptions()
show_quit_menu->setState(Settings::getInstance()->getBool("ShowQuitMenu")); show_quit_menu->setState(Settings::getInstance()->getBool("ShowQuitMenu"));
s->addWithLabel("SHOW QUIT MENU (REBOOT AND POWER OFF ENTRIES)", show_quit_menu); s->addWithLabel("SHOW QUIT MENU (REBOOT AND POWER OFF ENTRIES)", show_quit_menu);
s->addSaveFunc([this, show_quit_menu, s] { s->addSaveFunc([this, show_quit_menu, s] {
if (show_quit_menu->getState() != if (show_quit_menu->getState() != Settings::getInstance()->getBool("ShowQuitMenu")) {
Settings::getInstance()->getBool("ShowQuitMenu")) {
Settings::getInstance()->setBool("ShowQuitMenu", show_quit_menu->getState()); Settings::getInstance()->setBool("ShowQuitMenu", show_quit_menu->getState());
s->setNeedsSaving(); s->setNeedsSaving();
GuiMenu::close(false); GuiMenu::close(false);
@ -1229,14 +1126,16 @@ void GuiMenu::openOtherOptions()
if (launch_workaround->getEnabled()) { if (launch_workaround->getEnabled()) {
launch_workaround->setEnabled(false); launch_workaround->setEnabled(false);
launch_workaround->setOpacity(DISABLED_OPACITY); launch_workaround->setOpacity(DISABLED_OPACITY);
launch_workaround->getParent()->getChild(launch_workaround-> launch_workaround->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(launch_workaround->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
else { else {
launch_workaround->setEnabled(true); launch_workaround->setEnabled(true);
launch_workaround->setOpacity(255); launch_workaround->setOpacity(255);
launch_workaround->getParent()->getChild(launch_workaround-> launch_workaround->getParent()
getChildIndex() - 1)->setOpacity(255); ->getChild(launch_workaround->getChildIndex() - 1)
->setOpacity(255);
} }
}; };
run_in_background->setCallback(launchWorkaroundToggleFunc); run_in_background->setCallback(launchWorkaroundToggleFunc);
@ -1248,19 +1147,20 @@ void GuiMenu::openOtherOptions()
void GuiMenu::openUtilitiesMenu() void GuiMenu::openUtilitiesMenu()
{ {
auto s = new GuiSettings(mWindow, "UTILITIES"); auto s = new GuiSettings(mWindow, "UTILITIES");
mWindow->pushGui(s); mWindow->pushGui(s);
} }
void GuiMenu::openQuitMenu() void GuiMenu::openQuitMenu()
{ {
if (!Settings::getInstance()->getBool("ShowQuitMenu")) { if (!Settings::getInstance()->getBool("ShowQuitMenu")) {
mWindow->pushGui(new GuiMsgBox(mWindow, this->getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
"REALLY QUIT?", "YES", [this] { mWindow, this->getHelpStyle(), "REALLY QUIT?", "YES",
[this] {
Scripting::fireEvent("quit"); Scripting::fireEvent("quit");
close(true); close(true);
quitES(); quitES();
}, "NO", nullptr)); },
"NO", nullptr));
} }
else { else {
auto s = new GuiSettings(mWindow, "QUIT"); auto s = new GuiSettings(mWindow, "QUIT");
@ -1276,47 +1176,56 @@ void GuiMenu::openQuitMenu()
ComponentListRow row; ComponentListRow row;
row.makeAcceptInputHandler([window, this] { row.makeAcceptInputHandler([window, this] {
window->pushGui(new GuiMsgBox(window, this->getHelpStyle(), window->pushGui(new GuiMsgBox(
"REALLY QUIT?", "YES", [this] { window, this->getHelpStyle(), "REALLY QUIT?", "YES",
[this] {
Scripting::fireEvent("quit"); Scripting::fireEvent("quit");
close(true); close(true);
quitES(); quitES();
}, "NO", nullptr)); },
"NO", nullptr));
}); });
row.addElement(std::make_shared<TextComponent>(window, "QUIT EMULATIONSTATION", row.addElement(std::make_shared<TextComponent>(window, "QUIT EMULATIONSTATION",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(bracket, false); row.addElement(bracket, false);
s->addRow(row); s->addRow(row);
row.elements.clear(); row.elements.clear();
row.makeAcceptInputHandler([window, this] { row.makeAcceptInputHandler([window, this] {
window->pushGui(new GuiMsgBox(window, this->getHelpStyle(), window->pushGui(new GuiMsgBox(
"REALLY REBOOT?", "YES", [] { window, this->getHelpStyle(), "REALLY REBOOT?", "YES",
[] {
Scripting::fireEvent("quit", "reboot"); Scripting::fireEvent("quit", "reboot");
Scripting::fireEvent("reboot"); Scripting::fireEvent("reboot");
if (quitES(QuitMode::REBOOT) != 0) { if (quitES(QuitMode::REBOOT) != 0) {
LOG(LogWarning) << "Reboot terminated with non-zero result!"; LOG(LogWarning) << "Reboot terminated with non-zero result!";
} }
}, "NO", nullptr)); },
"NO", nullptr));
}); });
row.addElement(std::make_shared<TextComponent>(window, "REBOOT SYSTEM", row.addElement(std::make_shared<TextComponent>(window, "REBOOT SYSTEM",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(bracket, false); row.addElement(bracket, false);
s->addRow(row); s->addRow(row);
row.elements.clear(); row.elements.clear();
row.makeAcceptInputHandler([window, this] { row.makeAcceptInputHandler([window, this] {
window->pushGui(new GuiMsgBox(window, this->getHelpStyle(), window->pushGui(new GuiMsgBox(
"REALLY POWER OFF?", "YES", [] { window, this->getHelpStyle(), "REALLY POWER OFF?", "YES",
[] {
Scripting::fireEvent("quit", "poweroff"); Scripting::fireEvent("quit", "poweroff");
Scripting::fireEvent("poweroff"); Scripting::fireEvent("poweroff");
if (quitES(QuitMode::POWEROFF) != 0) { if (quitES(QuitMode::POWEROFF) != 0) {
LOG(LogWarning) << "Power off terminated with non-zero result!"; LOG(LogWarning) << "Power off terminated with non-zero result!";
} }
}, "NO", nullptr)); },
"NO", nullptr));
}); });
row.addElement(std::make_shared<TextComponent>(window, "POWER OFF SYSTEM", row.addElement(std::make_shared<TextComponent>(window, "POWER OFF SYSTEM",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(bracket, false); row.addElement(bracket, false);
s->addRow(row); s->addRow(row);
@ -1354,8 +1263,10 @@ void GuiMenu::onSizeChanged()
mVersion.setPosition(0, mSize.y() - mVersion.getSize().y()); mVersion.setPosition(0, mSize.y() - mVersion.getSize().y());
} }
void GuiMenu::addEntry(const std::string& name, unsigned int color, void GuiMenu::addEntry(const std::string& name,
bool add_arrow, const std::function<void()>& func) unsigned int color,
bool add_arrow,
const std::function<void()>& func)
{ {
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM); std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);

View file

@ -10,9 +10,9 @@
#ifndef ES_APP_GUIS_GUI_MENU_H #ifndef ES_APP_GUIS_GUI_MENU_H
#define ES_APP_GUIS_GUI_MENU_H #define ES_APP_GUIS_GUI_MENU_H
#include "GuiComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "guis/GuiSettings.h" #include "guis/GuiSettings.h"
#include "GuiComponent.h"
class GuiMenu : public GuiComponent class GuiMenu : public GuiComponent
{ {
@ -27,8 +27,10 @@ public:
private: private:
void close(bool closeAllWindows); void close(bool closeAllWindows);
void addEntry(const std::string& name, unsigned int color, void addEntry(const std::string& name,
bool add_arrow, const std::function<void()>& func); unsigned int color,
bool add_arrow,
const std::function<void()>& func);
void addVersionInfo(); void addVersionInfo();
void openScraperOptions(); void openScraperOptions();

View file

@ -11,6 +11,12 @@
#include "guis/GuiMetaDataEd.h" #include "guis/GuiMetaDataEd.h"
#include "CollectionSystemsManager.h"
#include "FileData.h"
#include "FileFilterIndex.h"
#include "Gamelist.h"
#include "SystemData.h"
#include "Window.h"
#include "components/ButtonComponent.h" #include "components/ButtonComponent.h"
#include "components/ComponentList.h" #include "components/ComponentList.h"
#include "components/DateTimeEditComponent.h" #include "components/DateTimeEditComponent.h"
@ -18,22 +24,15 @@
#include "components/RatingComponent.h" #include "components/RatingComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiGameScraper.h" #include "guis/GuiGameScraper.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "CollectionSystemsManager.h"
#include "FileData.h"
#include "FileFilterIndex.h"
#include "Gamelist.h"
#include "SystemData.h"
#include "Window.h"
GuiMetaDataEd::GuiMetaDataEd( GuiMetaDataEd::GuiMetaDataEd(Window* window,
Window* window,
MetaDataList* md, MetaDataList* md,
const std::vector<MetaDataDecl>& mdd, const std::vector<MetaDataDecl>& mdd,
ScraperSearchParams scraperParams, ScraperSearchParams scraperParams,
@ -41,28 +40,28 @@ GuiMetaDataEd::GuiMetaDataEd(
std::function<void()> saveCallback, std::function<void()> saveCallback,
std::function<void()> clearGameFunc, std::function<void()> clearGameFunc,
std::function<void()> deleteGameFunc) std::function<void()> deleteGameFunc)
: GuiComponent(window), : GuiComponent(window)
mScraperParams(scraperParams), , mScraperParams(scraperParams)
mBackground(window, ":/graphics/frame.svg"), , mBackground(window, ":/graphics/frame.svg")
mGrid(window, Vector2i(1, 3)), , mGrid(window, Vector2i(1, 3))
mMetaDataDecl(mdd), , mMetaDataDecl(mdd)
mMetaData(md), , mMetaData(md)
mSavedCallback(saveCallback), , mSavedCallback(saveCallback)
mClearGameFunc(clearGameFunc), , mClearGameFunc(clearGameFunc)
mDeleteGameFunc(deleteGameFunc), , mDeleteGameFunc(deleteGameFunc)
mMediaFilesUpdated(false) , mMediaFilesUpdated(false)
{ {
addChild(&mBackground); addChild(&mBackground);
addChild(&mGrid); addChild(&mGrid);
mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5)); mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5));
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE),
Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); 0x555555FF, ALIGN_CENTER);
// Extract possible subfolders from the path. // Extract possible subfolders from the path.
std::string folderPath = Utils::String::replace( std::string folderPath =
Utils::FileSystem::getParent(scraperParams.game->getPath()), Utils::String::replace(Utils::FileSystem::getParent(scraperParams.game->getPath()),
scraperParams.system->getSystemEnvData()->mStartPath, ""); scraperParams.system->getSystemEnvData()->mStartPath, "");
if (folderPath.size() >= 2) { if (folderPath.size() >= 2) {
@ -75,9 +74,10 @@ GuiMetaDataEd::GuiMetaDataEd(
#endif #endif
} }
mSubtitle = std::make_shared<TextComponent>(mWindow, folderPath + mSubtitle = std::make_shared<TextComponent>(
Utils::FileSystem::getFileName(scraperParams.game->getPath()) + mWindow,
" [" + Utils::String::toUpper(scraperParams.system->getName()) + "]" + folderPath + Utils::FileSystem::getFileName(scraperParams.game->getPath()) + " [" +
Utils::String::toUpper(scraperParams.system->getName()) + "]" +
(scraperParams.game->getType() == FOLDER ? " " + ViewController::FOLDER_CHAR : ""), (scraperParams.game->getType() == FOLDER ? " " + ViewController::FOLDER_CHAR : ""),
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER, Vector3f(0.0f, 0.0f, 0.0f), Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER, Vector3f(0.0f, 0.0f, 0.0f),
Vector2f(0.0f, 0.0f), 0x00000000, 0.05f); Vector2f(0.0f, 0.0f), 0x00000000, 0.05f);
@ -103,22 +103,21 @@ GuiMetaDataEd::GuiMetaDataEd(
// Don't show the launch command override entry if this option has been disabled. // Don't show the launch command override entry if this option has been disabled.
if (!Settings::getInstance()->getBool("LaunchCommandOverride") && if (!Settings::getInstance()->getBool("LaunchCommandOverride") &&
iter->type == MD_LAUNCHCOMMAND) { iter->type == MD_LAUNCHCOMMAND) {
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, ed = std::make_shared<TextComponent>(
FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
assert(ed); assert(ed);
ed->setValue(mMetaData->get(iter->key)); ed->setValue(mMetaData->get(iter->key));
mEditors.push_back(ed); mEditors.push_back(ed);
continue; continue;
} }
// Create ed and add it (and any related components) to mMenu.
// ed's value will be set below.
// It's very important to put the element with the help prompt as the last row // It's very important to put the element with the help prompt as the last row
// entry instead of for instance the spacer. That is so because ComponentList // entry instead of for instance the spacer. That is so because ComponentList
// always looks for the help prompt at the back of the element stack. // always looks for the help prompt at the back of the element stack.
ComponentListRow row; ComponentListRow row;
auto lbl = std::make_shared<TextComponent>(mWindow, auto lbl =
Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF); std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(iter->displayName),
Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(lbl, true); // Label. row.addElement(lbl, true); // Label.
switch (iter->type) { switch (iter->type) {
@ -135,7 +134,7 @@ GuiMetaDataEd::GuiMetaDataEd(
} }
case MD_RATING: { case MD_RATING: {
auto spacer = std::make_shared<GuiComponent>(mWindow); auto spacer = std::make_shared<GuiComponent>(mWindow);
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0); spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0.0f);
row.addElement(spacer, false); row.addElement(spacer, false);
ed = std::make_shared<RatingComponent>(window, true); ed = std::make_shared<RatingComponent>(window, true);
@ -145,17 +144,17 @@ GuiMetaDataEd::GuiMetaDataEd(
row.addElement(ed, false, true); row.addElement(ed, false, true);
auto ratingSpacer = std::make_shared<GuiComponent>(mWindow); auto ratingSpacer = std::make_shared<GuiComponent>(mWindow);
ratingSpacer->setSize(Renderer::getScreenWidth() * 0.001f, 0); ratingSpacer->setSize(Renderer::getScreenWidth() * 0.001f, 0.0f);
row.addElement(ratingSpacer, false); row.addElement(ratingSpacer, false);
// Pass input to the actual RatingComponent instead of the spacer. // Pass input to the actual RatingComponent instead of the spacer.
row.input_handler = std::bind(&GuiComponent::input, row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
ed.get(), std::placeholders::_1, std::placeholders::_2); std::placeholders::_2);
break; break;
} }
case MD_DATE: { case MD_DATE: {
auto spacer = std::make_shared<GuiComponent>(mWindow); auto spacer = std::make_shared<GuiComponent>(mWindow);
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0); spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0.0f);
row.addElement(spacer, false); row.addElement(spacer, false);
ed = std::make_shared<DateTimeEditComponent>(window, true); ed = std::make_shared<DateTimeEditComponent>(window, true);
@ -164,29 +163,22 @@ GuiMetaDataEd::GuiMetaDataEd(
row.addElement(ed, false); row.addElement(ed, false);
auto dateSpacer = std::make_shared<GuiComponent>(mWindow); auto dateSpacer = std::make_shared<GuiComponent>(mWindow);
dateSpacer->setSize(Renderer::getScreenWidth() * 0.0035f, 0); dateSpacer->setSize(Renderer::getScreenWidth() * 0.0035f, 0.0f);
row.addElement(dateSpacer, false); row.addElement(dateSpacer, false);
// Pass input to the actual DateTimeEditComponent instead of the spacer. // Pass input to the actual DateTimeEditComponent instead of the spacer.
row.input_handler = std::bind(&GuiComponent::input, ed.get(), row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
std::placeholders::_1, std::placeholders::_2); std::placeholders::_2);
break; break;
} }
// Not in use as 'lastplayed' is flagged as statistics and these are skipped.
// Let's still keep the code because it may be needed in the future.
// case MD_TIME: {
// ed = std::make_shared<DateTimeEditComponent>(window,
// DateTimeEditComponent::DISP_RELATIVE_TO_NOW);
// row.addElement(ed, false);
// break;
// }
case MD_LAUNCHCOMMAND: { case MD_LAUNCHCOMMAND: {
ed = std::make_shared<TextComponent>(window, "", ed = std::make_shared<TextComponent>(window, "",
Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
0x777777FF, ALIGN_RIGHT);
row.addElement(ed, true); row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(mWindow); auto spacer = std::make_shared<GuiComponent>(mWindow);
spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0); spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f);
row.addElement(spacer, false); row.addElement(spacer, false);
auto bracket = std::make_shared<ImageComponent>(mWindow); auto bracket = std::make_shared<ImageComponent>(mWindow);
@ -207,26 +199,27 @@ GuiMetaDataEd::GuiMetaDataEd(
}; };
std::string staticTextString = "Default value from es_systems.xml:"; std::string staticTextString = "Default value from es_systems.xml:";
std::string defaultLaunchCommand = scraperParams.system-> std::string defaultLaunchCommand =
getSystemEnvData()->mLaunchCommand; scraperParams.system->getSystemEnvData()->mLaunchCommand;
row.makeAcceptInputHandler([this, title, staticTextString, row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchCommand, ed,
defaultLaunchCommand, ed, updateVal, multiLine] { updateVal, multiLine] {
mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, getHelpStyle(), mWindow->pushGui(new GuiComplexTextEditPopup(
title, staticTextString, defaultLaunchCommand, ed->getValue(), mWindow, getHelpStyle(), title, staticTextString, defaultLaunchCommand,
updateVal, multiLine, "APPLY", "APPLY CHANGES?")); ed->getValue(), updateVal, multiLine, "APPLY", "APPLY CHANGES?"));
}); });
break; break;
} }
case MD_MULTILINE_STRING: case MD_MULTILINE_STRING:
default: { default: {
// MD_STRING. // MD_STRING.
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, ed = std::make_shared<TextComponent>(window, "",
FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
0x777777FF, ALIGN_RIGHT);
row.addElement(ed, true); row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(mWindow); auto spacer = std::make_shared<GuiComponent>(mWindow);
spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0); spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f);
row.addElement(spacer, false); row.addElement(spacer, false);
auto bracket = std::make_shared<ImageComponent>(mWindow); auto bracket = std::make_shared<ImageComponent>(mWindow);
@ -240,8 +233,8 @@ GuiMetaDataEd::GuiMetaDataEd(
gamePath = Utils::FileSystem::getStem(mScraperParams.game->getPath()); gamePath = Utils::FileSystem::getStem(mScraperParams.game->getPath());
// OK callback (apply new value to ed). // OK callback (apply new value to ed).
auto updateVal = [ed, currentKey, originalValue, gamePath] auto updateVal = [ed, currentKey, originalValue,
(const std::string& newVal) { gamePath](const std::string& newVal) {
// If the user has entered a blank game name, then set the name to the ROM // If the user has entered a blank game name, then set the name to the ROM
// filename (minus the extension). // filename (minus the extension).
if (currentKey == "name" && newVal == "") { if (currentKey == "name" && newVal == "") {
@ -251,9 +244,9 @@ GuiMetaDataEd::GuiMetaDataEd(
else else
ed->setColor(TEXTCOLOR_USERMARKED); ed->setColor(TEXTCOLOR_USERMARKED);
} }
else if (newVal == "" && (currentKey == "developer" || else if (newVal == "" &&
currentKey == "publisher" || currentKey == "genre" || (currentKey == "developer" || currentKey == "publisher" ||
currentKey == "players")) { currentKey == "genre" || currentKey == "players")) {
ed->setValue("unknown"); ed->setValue("unknown");
if (originalValue == "unknown") if (originalValue == "unknown")
ed->setColor(DEFAULT_TEXTCOLOR); ed->setColor(DEFAULT_TEXTCOLOR);
@ -271,7 +264,8 @@ GuiMetaDataEd::GuiMetaDataEd(
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title,
ed->getValue(), updateVal, multiLine, "APPLY", "APPLY CHANGES?")); ed->getValue(), updateVal, multiLine,
"APPLY", "APPLY CHANGES?"));
}); });
break; break;
} }
@ -286,8 +280,8 @@ GuiMetaDataEd::GuiMetaDataEd(
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape", buttons.push_back(std::make_shared<ButtonComponent>(
std::bind(&GuiMetaDataEd::fetch, this))); mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save metadata", [&] { buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save metadata", [&] {
save(); save();
@ -296,10 +290,12 @@ GuiMetaDataEd::GuiMetaDataEd(
})); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel changes", buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel changes",
[&] { delete this; })); [&] { delete this; }));
if (scraperParams.game->getType() == FOLDER) { if (scraperParams.game->getType() == FOLDER) {
if (mClearGameFunc) { if (mClearGameFunc) {
auto clearSelf = [&] { mClearGameFunc(); delete this; }; auto clearSelf = [&] {
mClearGameFunc();
delete this;
};
auto clearSelfBtnFunc = [this, clearSelf] { auto clearSelfBtnFunc = [this, clearSelf] {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
"THIS WILL DELETE ANY MEDIA FILES AND\n" "THIS WILL DELETE ANY MEDIA FILES AND\n"
@ -307,14 +303,18 @@ GuiMetaDataEd::GuiMetaDataEd(
"BUT NEITHER THE FOLDER ITSELF OR ANY\n" "BUT NEITHER THE FOLDER ITSELF OR ANY\n"
"CONTENT INSIDE IT WILL BE REMOVED\n" "CONTENT INSIDE IT WILL BE REMOVED\n"
"ARE YOU SURE?", "ARE YOU SURE?",
"YES", clearSelf, "NO", nullptr)); }; "YES", clearSelf, "NO", nullptr));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", };
"clear folder", clearSelfBtnFunc)); buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear folder",
clearSelfBtnFunc));
} }
} }
else { else {
if (mClearGameFunc) { if (mClearGameFunc) {
auto clearSelf = [&] { mClearGameFunc(); delete this; }; auto clearSelf = [&] {
mClearGameFunc();
delete this;
};
auto clearSelfBtnFunc = [this, clearSelf] { auto clearSelfBtnFunc = [this, clearSelf] {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
"THIS WILL DELETE ANY MEDIA FILES\n" "THIS WILL DELETE ANY MEDIA FILES\n"
@ -322,22 +322,27 @@ GuiMetaDataEd::GuiMetaDataEd(
"THIS GAME, BUT THE GAME FILE\n" "THIS GAME, BUT THE GAME FILE\n"
"ITSELF WILL NOT BE REMOVED\n" "ITSELF WILL NOT BE REMOVED\n"
"ARE YOU SURE?", "ARE YOU SURE?",
"YES", clearSelf, "NO", nullptr)); }; "YES", clearSelf, "NO", nullptr));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", };
"clear file", clearSelfBtnFunc)); buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear file",
clearSelfBtnFunc));
} }
if (mDeleteGameFunc) { if (mDeleteGameFunc) {
auto deleteFilesAndSelf = [&] { mDeleteGameFunc(); delete this; }; auto deleteFilesAndSelf = [&] {
mDeleteGameFunc();
delete this;
};
auto deleteGameBtnFunc = [this, deleteFilesAndSelf] { auto deleteGameBtnFunc = [this, deleteFilesAndSelf] {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
"THIS WILL DELETE THE GAME\n" "THIS WILL DELETE THE GAME\n"
"FILE, ANY MEDIA FILES AND\n" "FILE, ANY MEDIA FILES AND\n"
"THE GAMELIST.XML ENTRY\n" "THE GAMELIST.XML ENTRY\n"
"ARE YOU SURE?", "ARE YOU SURE?",
"YES", deleteFilesAndSelf, "NO", nullptr)); }; "YES", deleteFilesAndSelf, "NO", nullptr));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "DELETE", };
"delete game", deleteGameBtnFunc)); buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "DELETE", "delete game",
deleteGameBtnFunc));
} }
} }
@ -345,11 +350,12 @@ GuiMetaDataEd::GuiMetaDataEd(
mGrid.setEntry(mButtons, Vector2i(0, 2), true, false); mGrid.setEntry(mButtons, Vector2i(0, 2), true, false);
// Resize + center. // Resize + center.
float width = static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * float width =
1.05f), static_cast<int>(Renderer::getScreenWidth() * 0.90f))); static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * 1.05f),
static_cast<int>(Renderer::getScreenWidth() * 0.90f)));
setSize(width, Renderer::getScreenHeight() * 0.83f); setSize(width, Renderer::getScreenHeight() * 0.83f);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f,
(Renderer::getScreenHeight() - mSize.y()) / 2); (Renderer::getScreenHeight() - mSize.y()) / 2.0f);
} }
void GuiMetaDataEd::onSizeChanged() void GuiMetaDataEd::onSizeChanged()
@ -360,8 +366,8 @@ void GuiMetaDataEd::onSizeChanged()
const float subtitleHeight = mSubtitle->getFont()->getLetterHeight(); const float subtitleHeight = mSubtitle->getFont()->getLetterHeight();
const float titleSubtitleSpacing = mSize.y() * 0.03f; const float titleSubtitleSpacing = mSize.y() * 0.03f;
mGrid.setRowHeightPerc(0, (titleHeight + titleSubtitleSpacing + subtitleHeight + mGrid.setRowHeightPerc(
TITLE_VERT_PADDING) / mSize.y()); 0, (titleHeight + titleSubtitleSpacing + subtitleHeight + TITLE_VERT_PADDING) / mSize.y());
mGrid.setRowHeightPerc(2, mButtons->getSize().y() / mSize.y()); mGrid.setRowHeightPerc(2, mButtons->getSize().y() / mSize.y());
// Snap list size to the row height to prevent a fraction of a row from being displayed. // Snap list size to the row height to prevent a fraction of a row from being displayed.
@ -383,7 +389,7 @@ void GuiMetaDataEd::onSizeChanged()
mList->setSize(mList->getSize().x(), listHeight); mList->setSize(mList->getSize().x(), listHeight);
Vector2f newWindowSize = mSize; Vector2f newWindowSize = mSize;
newWindowSize.y() -= heightAdjustment; newWindowSize.y() -= heightAdjustment;
mBackground.fitTo(newWindowSize, Vector3f::Zero(), Vector2f(-32, -32)); mBackground.fitTo(newWindowSize, Vector3f::Zero(), Vector2f(-32.0f, -32.0f));
// Move the buttons up as well to make the layout align correctly after the resize. // Move the buttons up as well to make the layout align correctly after the resize.
Vector3f newButtonPos = mButtons->getPosition(); Vector3f newButtonPos = mButtons->getPosition();
@ -501,8 +507,9 @@ void GuiMetaDataEd::save()
void GuiMetaDataEd::fetch() void GuiMetaDataEd::fetch()
{ {
GuiGameScraper* scr = new GuiGameScraper(mWindow, mScraperParams, mMediaFilesUpdated = false;
std::bind(&GuiMetaDataEd::fetchDone, this, std::placeholders::_1)); GuiGameScraper* scr = new GuiGameScraper(
mWindow, mScraperParams, std::bind(&GuiMetaDataEd::fetchDone, this, std::placeholders::_1));
mWindow->pushGui(scr); mWindow->pushGui(scr);
} }
@ -514,10 +521,6 @@ void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
mMediaFilesUpdated = result.savedNewMedia; mMediaFilesUpdated = result.savedNewMedia;
// Curently disabled as I'm not sure if this is more annoying than helpful.
// // Select the Save button.
// mButtons->moveCursor(Vector2i(1, 0));
// Check if any values were manually changed before starting the scraping. // Check if any values were manually changed before starting the scraping.
// If so, it's these values we should compare against when scraping, not // If so, it's these values we should compare against when scraping, not
// the values previously saved for the game. // the values previously saved for the game.
@ -561,19 +564,6 @@ void GuiMetaDataEd::close()
} }
} }
// Keep code for potential future use.
// std::function<void()> closeFunc;
// if (!closeAllWindows) {
// closeFunc = [this] { delete this; };
// }
// else {
// Window* window = mWindow;
// closeFunc = [window, this] {
// while (window->peekGui() != ViewController::get())
// delete window->peekGui();
// };
// }
std::function<void()> closeFunc; std::function<void()> closeFunc;
closeFunc = [this] { closeFunc = [this] {
if (mMediaFilesUpdated) { if (mMediaFilesUpdated) {
@ -592,11 +582,13 @@ void GuiMetaDataEd::close()
if (metadataUpdated) { if (metadataUpdated) {
// Changes were made, ask if the user wants to save them. // Changes were made, ask if the user wants to save them.
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
"SAVE CHANGES?", mWindow, getHelpStyle(), "SAVE CHANGES?", "YES",
"YES", [this, closeFunc] { save(); closeFunc(); }, [this, closeFunc] {
"NO", closeFunc save();
)); closeFunc();
},
"NO", closeFunc));
} }
else { else {
// Always save if the media files have been changed (i.e. newly scraped images). // Always save if the media files have been changed (i.e. newly scraped images).

View file

@ -12,11 +12,11 @@
#ifndef ES_APP_GUIS_GUI_META_DATA_ED_H #ifndef ES_APP_GUIS_GUI_META_DATA_ED_H
#define ES_APP_GUIS_GUI_META_DATA_ED_H #define ES_APP_GUIS_GUI_META_DATA_ED_H
#include "GuiComponent.h"
#include "MetaData.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "components/NinePatchComponent.h" #include "components/NinePatchComponent.h"
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
#include "GuiComponent.h"
#include "MetaData.h"
class ComponentList; class ComponentList;
class TextComponent; class TextComponent;
@ -24,9 +24,9 @@ class TextComponent;
class GuiMetaDataEd : public GuiComponent class GuiMetaDataEd : public GuiComponent
{ {
public: public:
GuiMetaDataEd( GuiMetaDataEd(Window* window,
Window* window, MetaDataList* md,
MetaDataList* md, const std::vector<MetaDataDecl>&mdd, const std::vector<MetaDataDecl>& mdd,
ScraperSearchParams params, ScraperSearchParams params,
const std::string& header, const std::string& header,
std::function<void()> savedCallback, std::function<void()> savedCallback,

View file

@ -9,17 +9,15 @@
#include "guis/GuiOfflineGenerator.h" #include "guis/GuiOfflineGenerator.h"
#include "SystemData.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "SystemData.h"
GuiOfflineGenerator::GuiOfflineGenerator( GuiOfflineGenerator::GuiOfflineGenerator(Window* window, const std::queue<FileData*>& gameQueue)
Window* window, : GuiComponent(window)
const std::queue<FileData*>& gameQueue) , mBackground(window, ":/graphics/frame.svg")
: GuiComponent(window), , mGrid(window, Vector2i(6, 13))
mBackground(window, ":/graphics/frame.svg"), , mGameQueue(gameQueue)
mGrid(window, Vector2i(6, 13)),
mGameQueue(gameQueue)
{ {
addChild(&mBackground); addChild(&mBackground);
addChild(&mGrid); addChild(&mGrid);
@ -42,105 +40,104 @@ GuiOfflineGenerator::GuiOfflineGenerator(
Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
mGrid.setEntry(mTitle, Vector2i(0, 0), false, true, Vector2i(6, 1)); mGrid.setEntry(mTitle, Vector2i(0, 0), false, true, Vector2i(6, 1));
mStatus = std::make_shared<TextComponent>(mWindow, "NOT STARTED", mStatus = std::make_shared<TextComponent>(mWindow, "NOT STARTED", Font::get(FONT_SIZE_MEDIUM),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); 0x777777FF, ALIGN_CENTER);
mGrid.setEntry(mStatus, Vector2i(0, 1), false, true, Vector2i(6, 1)); mGrid.setEntry(mStatus, Vector2i(0, 1), false, true, Vector2i(6, 1));
mGameCounter = std::make_shared<TextComponent>(mWindow, mGameCounter = std::make_shared<TextComponent>(
mWindow,
std::to_string(mGamesProcessed) + " OF " + std::to_string(mTotalGames) + std::to_string(mGamesProcessed) + " OF " + std::to_string(mTotalGames) +
(mTotalGames == 1 ? " GAME " : " GAMES ") + "PROCESSED", (mTotalGames == 1 ? " GAME " : " GAMES ") + "PROCESSED",
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER);
mGrid.setEntry(mGameCounter, Vector2i(0, 2), false, true, Vector2i(6, 1)); mGrid.setEntry(mGameCounter, Vector2i(0, 2), false, true, Vector2i(6, 1));
// Spacer row with top border. // Spacer row with top border.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 3), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 3), false, false,
false, false, Vector2i(6, 1), GridFlags::BORDER_TOP); Vector2i(6, 1), GridFlags::BORDER_TOP);
// Left spacer. // Left spacer.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 4), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 4), false, false,
false, false, Vector2i(1, 7)); Vector2i(1, 7));
// Generated label. // Generated label.
mGeneratedLbl = std::make_shared<TextComponent>(mWindow, "Generated:", mGeneratedLbl = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); mWindow, "Generated:", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mGeneratedLbl, Vector2i(1, 4), false, true, Vector2i(1, 1)); mGrid.setEntry(mGeneratedLbl, Vector2i(1, 4), false, true, Vector2i(1, 1));
// Generated value/counter. // Generated value/counter.
mGeneratedVal = std::make_shared<TextComponent>(mWindow, mGeneratedVal =
std::to_string(mGamesProcessed), std::make_shared<TextComponent>(mWindow, std::to_string(mGamesProcessed),
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mGeneratedVal, Vector2i(2, 4), false, true, Vector2i(1, 1)); mGrid.setEntry(mGeneratedVal, Vector2i(2, 4), false, true, Vector2i(1, 1));
// Overwritten label. // Overwritten label.
mOverwrittenLbl = std::make_shared<TextComponent>(mWindow, "Overwritten:", mOverwrittenLbl = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); mWindow, "Overwritten:", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mOverwrittenLbl, Vector2i(1, 5), false, true, Vector2i(1, 1)); mGrid.setEntry(mOverwrittenLbl, Vector2i(1, 5), false, true, Vector2i(1, 1));
// Overwritten value/counter. // Overwritten value/counter.
mOverwrittenVal = std::make_shared<TextComponent>(mWindow, mOverwrittenVal =
std::to_string(mImagesOverwritten), std::make_shared<TextComponent>(mWindow, std::to_string(mImagesOverwritten),
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mOverwrittenVal, Vector2i(2, 5), false, true, Vector2i(1, 1)); mGrid.setEntry(mOverwrittenVal, Vector2i(2, 5), false, true, Vector2i(1, 1));
// Skipping label. // Skipping label.
mSkippedLbl = std::make_shared<TextComponent>(mWindow, "Skipped (existing):", mSkippedLbl = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); mWindow, "Skipped (existing):", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mSkippedLbl, Vector2i(1, 6), false, true, Vector2i(1, 1)); mGrid.setEntry(mSkippedLbl, Vector2i(1, 6), false, true, Vector2i(1, 1));
// Skipping value/counter. // Skipping value/counter.
mSkippedVal= std::make_shared<TextComponent>(mWindow, mSkippedVal = std::make_shared<TextComponent>(
std::to_string(mGamesSkipped), mWindow, std::to_string(mGamesSkipped), Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mSkippedVal, Vector2i(2, 6), false, true, Vector2i(1, 1)); mGrid.setEntry(mSkippedVal, Vector2i(2, 6), false, true, Vector2i(1, 1));
// Failed label. // Failed label.
mFailedLbl = std::make_shared<TextComponent>(mWindow, "Failed:", mFailedLbl = std::make_shared<TextComponent>(mWindow, "Failed:", Font::get(FONT_SIZE_SMALL),
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mFailedLbl, Vector2i(1, 7), false, true, Vector2i(1, 1)); mGrid.setEntry(mFailedLbl, Vector2i(1, 7), false, true, Vector2i(1, 1));
// Failed value/counter. // Failed value/counter.
mFailedVal = std::make_shared<TextComponent>(mWindow, mFailedVal = std::make_shared<TextComponent>(
std::to_string(mGamesFailed), mWindow, std::to_string(mGamesFailed), Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mFailedVal, Vector2i(2, 7), false, true, Vector2i(1, 1)); mGrid.setEntry(mFailedVal, Vector2i(2, 7), false, true, Vector2i(1, 1));
// Processing label. // Processing label.
mProcessingLbl = std::make_shared<TextComponent>(mWindow, "Processing: ", mProcessingLbl = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); mWindow, "Processing: ", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mProcessingLbl, Vector2i(3, 4), false, true, Vector2i(1, 1)); mGrid.setEntry(mProcessingLbl, Vector2i(3, 4), false, true, Vector2i(1, 1));
// Processing value. // Processing value.
mProcessingVal = std::make_shared<TextComponent>(mWindow, "", mProcessingVal = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_SMALL),
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mProcessingVal, Vector2i(4, 4), false, true, Vector2i(1, 1)); mGrid.setEntry(mProcessingVal, Vector2i(4, 4), false, true, Vector2i(1, 1));
// Spacer row. // Spacer row.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 8), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 8), false, false,
false, false, Vector2i(4, 1)); Vector2i(4, 1));
// Last error message label. // Last error message label.
mLastErrorLbl = std::make_shared<TextComponent>(mWindow, "Last error message:", mLastErrorLbl = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); mWindow, "Last error message:", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mLastErrorLbl, Vector2i(1, 9), false, true, Vector2i(4, 1)); mGrid.setEntry(mLastErrorLbl, Vector2i(1, 9), false, true, Vector2i(4, 1));
// Last error message value. // Last error message value.
mLastErrorVal = std::make_shared<TextComponent>(mWindow, "", mLastErrorVal = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_SMALL),
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_LEFT); 0x888888FF, ALIGN_LEFT);
mGrid.setEntry(mLastErrorVal, Vector2i(1, 10), false, true, Vector2i(4, 1)); mGrid.setEntry(mLastErrorVal, Vector2i(1, 10), false, true, Vector2i(4, 1));
// Right spacer. // Right spacer.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(5, 4), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(5, 4), false, false,
false, false, Vector2i(1, 7)); Vector2i(1, 7));
// Spacer row with bottom border. // Spacer row with bottom border.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 11), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 11), false, false,
false, false, Vector2i(6, 1), GridFlags::BORDER_BOTTOM); Vector2i(6, 1), GridFlags::BORDER_BOTTOM);
// Buttons. // Buttons.
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
mStartPauseButton = std::make_shared<ButtonComponent>(mWindow, "START", mStartPauseButton =
"start processing", [this](){ std::make_shared<ButtonComponent>(mWindow, "START", "start processing", [this]() {
if (!mProcessing) { if (!mProcessing) {
mProcessing = true; mProcessing = true;
mPaused = false; mPaused = false;
@ -167,12 +164,12 @@ GuiOfflineGenerator::GuiOfflineGenerator(
mCloseButton = std::make_shared<ButtonComponent>(mWindow, "CLOSE", "close", [this]() { mCloseButton = std::make_shared<ButtonComponent>(mWindow, "CLOSE", "close", [this]() {
if (mGamesProcessed != 0 && mGamesProcessed != mTotalGames) { if (mGamesProcessed != 0 && mGamesProcessed != mTotalGames) {
LOG(LogInfo) << "GuiOfflineGenerator: Aborted after processing " << LOG(LogInfo) << "GuiOfflineGenerator: Aborted after processing " << mGamesProcessed
mGamesProcessed << (mGamesProcessed == 1 ? " game (" : " games (") << << (mGamesProcessed == 1 ? " game (" : " games (") << mImagesGenerated
mImagesGenerated << (mImagesGenerated == 1 ? " image " : " images ") << << (mImagesGenerated == 1 ? " image " : " images ") << "generated, "
"generated, " << mGamesSkipped << << mGamesSkipped << (mGamesSkipped == 1 ? " game " : " games ")
(mGamesSkipped == 1 ? " game " : " games ") << "skipped, " << mGamesFailed << << "skipped, " << mGamesFailed
(mGamesFailed == 1 ? " game " : " games ") << "failed)"; << (mGamesFailed == 1 ? " game " : " games ") << "failed)";
} }
delete this; delete this;
}); });
@ -184,8 +181,8 @@ GuiOfflineGenerator::GuiOfflineGenerator(
// For narrower displays (e.g. in 4:3 ratio), allow the window to fill 95% of the screen // For narrower displays (e.g. in 4:3 ratio), allow the window to fill 95% of the screen
// width rather than the 85% allowed for wider displays. // width rather than the 85% allowed for wider displays.
float width = Renderer::getScreenWidth() * float width =
((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f); Renderer::getScreenWidth() * ((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f);
setSize(width, Renderer::getScreenHeight() * 0.75f); setSize(width, Renderer::getScreenHeight() * 0.75f);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f, setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f,
@ -281,8 +278,8 @@ void GuiOfflineGenerator::update(int deltaTime)
mGame = mGameQueue.front(); mGame = mGameQueue.front();
mGameQueue.pop(); mGameQueue.pop();
mGameName = mGame->getName() + " [" + mGameName =
Utils::String::toUpper(mGame->getSystem()->getName()) + "]"; mGame->getName() + " [" + Utils::String::toUpper(mGame->getSystem()->getName()) + "]";
mProcessingVal->setText(mGameName); mProcessingVal->setText(mGameName);
if (!Settings::getInstance()->getBool("MiximageOverwrite") && if (!Settings::getInstance()->getBool("MiximageOverwrite") &&
@ -322,10 +319,10 @@ void GuiOfflineGenerator::update(int deltaTime)
mStartPauseButton->setPressedFunc([this]() { delete this; }); mStartPauseButton->setPressedFunc([this]() { delete this; });
mCloseButton->setText("CLOSE", "close"); mCloseButton->setText("CLOSE", "close");
mProcessingVal->setText(""); mProcessingVal->setText("");
LOG(LogInfo) << "GuiOfflineGenerator: Completed processing (" << mImagesGenerated << LOG(LogInfo) << "GuiOfflineGenerator: Completed processing (" << mImagesGenerated
(mImagesGenerated == 1 ? " image " : " images ") << "generated, " << << (mImagesGenerated == 1 ? " image " : " images ") << "generated, "
mGamesSkipped << (mGamesSkipped == 1 ? " game " : " games ") << "skipped, " << << mGamesSkipped << (mGamesSkipped == 1 ? " game " : " games ") << "skipped, "
mGamesFailed << (mGamesFailed == 1 ? " game " : " games ") << "failed)"; << mGamesFailed << (mGamesFailed == 1 ? " game " : " games ") << "failed)";
mProcessing = false; mProcessing = false;
} }
} }

View file

@ -10,10 +10,10 @@
#ifndef ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H #ifndef ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H
#define ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H #define ES_APP_GUIS_GUI_OFFLINE_GENERATOR_H
#include "components/ButtonComponent.h"
#include "components/ComponentGrid.h"
#include "GuiComponent.h" #include "GuiComponent.h"
#include "MiximageGenerator.h" #include "MiximageGenerator.h"
#include "components/ButtonComponent.h"
#include "components/ComponentGrid.h"
#include <queue> #include <queue>

View file

@ -10,23 +10,23 @@
#include "guis/GuiScraperMenu.h" #include "guis/GuiScraperMenu.h"
#include "FileData.h"
#include "FileSorts.h"
#include "SystemData.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiOfflineGenerator.h" #include "guis/GuiOfflineGenerator.h"
#include "guis/GuiScraperMulti.h" #include "guis/GuiScraperMulti.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "FileData.h"
#include "FileSorts.h"
#include "SystemData.h"
GuiScraperMenu::GuiScraperMenu(Window* window, std::string title) GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
: GuiComponent(window), mMenu(window, title) : GuiComponent(window)
, mMenu(window, title)
{ {
// Scraper service. // Scraper service.
mScraper = std::make_shared<OptionListComponent<std::string>> mScraper = std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(),
(mWindow, getHelpStyle(), "SCRAPE FROM", false); "SCRAPE FROM", false);
std::vector<std::string> scrapers = getScraperList(); std::vector<std::string> scrapers = getScraperList();
// Select either the first entry or the one read from the settings, // Select either the first entry or the one read from the settings,
// just in case the scraper from settings has vanished. // just in case the scraper from settings has vanished.
@ -36,22 +36,50 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
// Search filters, getSearches() will generate a queue of games to scrape // Search filters, getSearches() will generate a queue of games to scrape
// based on the outcome of the checks below. // based on the outcome of the checks below.
mFilters = std::make_shared< OptionListComponent<GameFilterFunc>> mFilters = std::make_shared<OptionListComponent<GameFilterFunc>>(mWindow, getHelpStyle(),
(mWindow, getHelpStyle(), "SCRAPE THESE GAMES", false); "SCRAPE THESE GAMES", false);
mFilters->add("ALL GAMES", [](SystemData*, FileData*) -> bool { return true; }, false); mFilters->add(
mFilters->add("FAVORITE GAMES", [](SystemData*, FileData* g) -> bool { "ALL GAMES",
return g->getFavorite(); }, false); [](SystemData*, FileData*) -> bool {
mFilters->add("NO METADATA", [](SystemData*, FileData* g) -> bool { // All games.
return g->metadata.get("desc").empty(); }, false); return true;
mFilters->add("NO GAME IMAGE", },
false);
mFilters->add(
"FAVORITE GAMES",
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
return g->getImagePath().empty(); }, false); // Favorite games.
mFilters->add("NO GAME VIDEO", return g->getFavorite();
},
false);
mFilters->add(
"NO METADATA",
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
return g->getVideoPath().empty(); }, false); // No metadata.
mFilters->add("FOLDERS ONLY", return g->metadata.get("desc").empty();
},
false);
mFilters->add(
"NO GAME IMAGE",
[](SystemData*, FileData* g) -> bool { [](SystemData*, FileData* g) -> bool {
return g->getType() == FOLDER; }, false); // No game image.
return g->getImagePath().empty();
},
false);
mFilters->add(
"NO GAME VIDEO",
[](SystemData*, FileData* g) -> bool {
// No game video.
return g->getVideoPath().empty();
},
false);
mFilters->add(
"FOLDERS ONLY",
[](SystemData*, FileData* g) -> bool {
// Folders only.
return g->getType() == FOLDER;
},
false);
mFilters->selectEntry(Settings::getInstance()->getInt("ScraperFilter")); mFilters->selectEntry(Settings::getInstance()->getInt("ScraperFilter"));
mMenu.addWithLabel("SCRAPE THESE GAMES", mFilters); mMenu.addWithLabel("SCRAPE THESE GAMES", mFilters);
@ -68,20 +96,20 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
}); });
// Add systems (all systems with an existing platform ID are listed). // Add systems (all systems with an existing platform ID are listed).
mSystems = std::make_shared< OptionListComponent<SystemData*>> mSystems = std::make_shared<OptionListComponent<SystemData*>>(mWindow, getHelpStyle(),
(mWindow, getHelpStyle(), "SCRAPE THESE SYSTEMS", true); "SCRAPE THESE SYSTEMS", true);
for (unsigned int i = 0; i < SystemData::sSystemVector.size(); i++) { for (unsigned int i = 0; i < SystemData::sSystemVector.size(); i++) {
if (!SystemData::sSystemVector[i]->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) { if (!SystemData::sSystemVector[i]->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) {
mSystems->add(SystemData::sSystemVector[i]->getFullName(), mSystems->add(SystemData::sSystemVector[i]->getFullName(), SystemData::sSystemVector[i],
SystemData::sSystemVector[i],
!SystemData::sSystemVector[i]->getPlatformIds().empty()); !SystemData::sSystemVector[i]->getPlatformIds().empty());
SystemData::sSystemVector[i]->getScrapeFlag() ? SystemData::sSystemVector[i]->getScrapeFlag() ? mSystems->selectEntry(i) :
mSystems->selectEntry(i) : mSystems->unselectEntry(i); mSystems->unselectEntry(i);
} }
} }
mMenu.addWithLabel("SCRAPE THESE SYSTEMS", mSystems); mMenu.addWithLabel("SCRAPE THESE SYSTEMS", mSystems);
addEntry("ACCOUNT SETTINGS", 0x777777FF, true, [this] { addEntry("ACCOUNT SETTINGS", 0x777777FF, true, [this] {
// Open the account options menu.
openAccountOptions(); openAccountOptions();
}); });
addEntry("CONTENT SETTINGS", 0x777777FF, true, [this] { addEntry("CONTENT SETTINGS", 0x777777FF, true, [this] {
@ -93,6 +121,7 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
openContentOptions(); openContentOptions();
}); });
addEntry("MIXIMAGE SETTINGS", 0x777777FF, true, [this] { addEntry("MIXIMAGE SETTINGS", 0x777777FF, true, [this] {
// Open the miximage options menu.
openMiximageOptions(); openMiximageOptions();
}); });
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { addEntry("OTHER SETTINGS", 0x777777FF, true, [this] {
@ -111,7 +140,7 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
setSize(mMenu.getSize()); setSize(mMenu.getSize());
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f,
Renderer::getScreenHeight() * 0.13f); Renderer::getScreenHeight() * 0.13f);
} }
@ -120,7 +149,7 @@ GuiScraperMenu::~GuiScraperMenu()
// Save the scrape flags to the system settings so that they are // Save the scrape flags to the system settings so that they are
// remembered throughout the program session. // remembered throughout the program session.
std::vector<SystemData*> sys = mSystems->getSelectedObjects(); std::vector<SystemData*> sys = mSystems->getSelectedObjects();
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
(*it)->setScrapeFlag(false); (*it)->setScrapeFlag(false);
for (auto it_sys = sys.cbegin(); it_sys != sys.cend(); it_sys++) { for (auto it_sys = sys.cbegin(); it_sys != sys.cend(); it_sys++) {
@ -136,8 +165,8 @@ void GuiScraperMenu::openAccountOptions()
// Whether to use the ScreenScraper account when scraping. // Whether to use the ScreenScraper account when scraping.
auto scraper_use_account_screenscraper = std::make_shared<SwitchComponent>(mWindow); auto scraper_use_account_screenscraper = std::make_shared<SwitchComponent>(mWindow);
scraper_use_account_screenscraper->setState(Settings::getInstance()-> scraper_use_account_screenscraper->setState(
getBool("ScraperUseAccountScreenScraper")); Settings::getInstance()->getBool("ScraperUseAccountScreenScraper"));
s->addWithLabel("USE THIS ACCOUNT FOR SCREENSCRAPER", scraper_use_account_screenscraper); s->addWithLabel("USE THIS ACCOUNT FOR SCREENSCRAPER", scraper_use_account_screenscraper);
s->addSaveFunc([scraper_use_account_screenscraper, s] { s->addSaveFunc([scraper_use_account_screenscraper, s] {
if (scraper_use_account_screenscraper->getState() != if (scraper_use_account_screenscraper->getState() !=
@ -149,8 +178,8 @@ void GuiScraperMenu::openAccountOptions()
}); });
// ScreenScraper username. // ScreenScraper username.
auto scraper_username_screenscraper = std::make_shared<TextComponent>(mWindow, "", auto scraper_username_screenscraper = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); mWindow, "", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT);
s->addEditableTextComponent("SCREENSCRAPER USERNAME", scraper_username_screenscraper, s->addEditableTextComponent("SCREENSCRAPER USERNAME", scraper_username_screenscraper,
Settings::getInstance()->getString("ScraperUsernameScreenScraper")); Settings::getInstance()->getString("ScraperUsernameScreenScraper"));
s->addSaveFunc([scraper_username_screenscraper, s] { s->addSaveFunc([scraper_username_screenscraper, s] {
@ -163,16 +192,16 @@ void GuiScraperMenu::openAccountOptions()
}); });
// ScreenScraper password. // ScreenScraper password.
auto scraper_password_screenscraper = std::make_shared<TextComponent>(mWindow, "", auto scraper_password_screenscraper = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); mWindow, "", Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT);
std::string passwordMasked; std::string passwordMasked;
if (Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") { if (Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") {
passwordMasked = "********"; passwordMasked = "********";
scraper_password_screenscraper->setHiddenValue( scraper_password_screenscraper->setHiddenValue(
Settings::getInstance()->getString("ScraperPasswordScreenScraper")); Settings::getInstance()->getString("ScraperPasswordScreenScraper"));
} }
s->addEditableTextComponent("SCREENSCRAPER PASSWORD", s->addEditableTextComponent("SCREENSCRAPER PASSWORD", scraper_password_screenscraper,
scraper_password_screenscraper, passwordMasked, "", true); passwordMasked, "", true);
s->addSaveFunc([scraper_password_screenscraper, s] { s->addSaveFunc([scraper_password_screenscraper, s] {
if (scraper_password_screenscraper->getHiddenValue() != if (scraper_password_screenscraper->getHiddenValue() !=
Settings::getInstance()->getString("ScraperPasswordScreenScraper")) { Settings::getInstance()->getString("ScraperPasswordScreenScraper")) {
@ -211,12 +240,13 @@ void GuiScraperMenu::openContentOptions()
} }
}); });
// Ratings are not supported by TheGamesDB, so disable the option if this scraper is selected. // Ratings are not supported by TheGamesDB, so gray out the option if this scraper is selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") { if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
scrape_ratings->setEnabled(false); scrape_ratings->setEnabled(false);
scrape_ratings->setOpacity(DISABLED_OPACITY); scrape_ratings->setOpacity(DISABLED_OPACITY);
scrape_ratings->getParent()->getChild(scrape_ratings-> scrape_ratings->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scrape_ratings->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
// Scrape other metadata. // Scrape other metadata.
@ -241,12 +271,13 @@ void GuiScraperMenu::openContentOptions()
} }
}); });
// Videos are not supported by TheGamesDB, so disable the option if this scraper is selected. // Videos are not supported by TheGamesDB, so gray out the option if this scraper is selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") { if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
scrape_videos->setEnabled(false); scrape_videos->setEnabled(false);
scrape_videos->setOpacity(DISABLED_OPACITY); scrape_videos->setOpacity(DISABLED_OPACITY);
scrape_videos->getParent()->getChild(scrape_videos-> scrape_videos->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scrape_videos->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
// Scrape screenshots images. // Scrape screenshots images.
@ -294,13 +325,14 @@ void GuiScraperMenu::openContentOptions()
} }
}); });
// 3D box images are not supported by TheGamesDB, so disable the option if this scraper // 3D box images are not supported by TheGamesDB, so gray out the option if this scraper
// is selected. // is selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") { if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
scrape_3dboxes->setEnabled(false); scrape_3dboxes->setEnabled(false);
scrape_3dboxes->setOpacity(DISABLED_OPACITY); scrape_3dboxes->setOpacity(DISABLED_OPACITY);
scrape_3dboxes->getParent()->getChild(scrape_3dboxes-> scrape_3dboxes->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scrape_3dboxes->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
mWindow->pushGui(s); mWindow->pushGui(s);
@ -311,8 +343,8 @@ void GuiScraperMenu::openMiximageOptions()
auto s = new GuiSettings(mWindow, "MIXIMAGE SETTINGS"); auto s = new GuiSettings(mWindow, "MIXIMAGE SETTINGS");
// Miximage resolution. // Miximage resolution.
auto miximage_resolution = std::make_shared<OptionListComponent<std::string>> auto miximage_resolution = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "MIXIMAGE RESOLUTION", false); mWindow, getHelpStyle(), "MIXIMAGE RESOLUTION", false);
std::string selectedResolution = Settings::getInstance()->getString("MiximageResolution"); std::string selectedResolution = Settings::getInstance()->getString("MiximageResolution");
miximage_resolution->add("1280x960", "1280x960", selectedResolution == "1280x960"); miximage_resolution->add("1280x960", "1280x960", selectedResolution == "1280x960");
miximage_resolution->add("1920x1440", "1920x1440", selectedResolution == "1920x1440"); miximage_resolution->add("1920x1440", "1920x1440", selectedResolution == "1920x1440");
@ -325,15 +357,15 @@ void GuiScraperMenu::openMiximageOptions()
s->addSaveFunc([miximage_resolution, s] { s->addSaveFunc([miximage_resolution, s] {
if (miximage_resolution->getSelected() != if (miximage_resolution->getSelected() !=
Settings::getInstance()->getString("MiximageResolution")) { Settings::getInstance()->getString("MiximageResolution")) {
Settings::getInstance()-> Settings::getInstance()->setString("MiximageResolution",
setString("MiximageResolution", miximage_resolution->getSelected()); miximage_resolution->getSelected());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
// Screenshot scaling method. // Screenshot scaling method.
auto miximage_scaling = std::make_shared<OptionListComponent<std::string>> auto miximage_scaling = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "SCREENSHOT SCALING", false); mWindow, getHelpStyle(), "SCREENSHOT SCALING", false);
std::string selectedScaling = Settings::getInstance()->getString("MiximageScreenshotScaling"); std::string selectedScaling = Settings::getInstance()->getString("MiximageScreenshotScaling");
miximage_scaling->add("sharp", "sharp", selectedScaling == "sharp"); miximage_scaling->add("sharp", "sharp", selectedScaling == "sharp");
miximage_scaling->add("smooth", "smooth", selectedScaling == "smooth"); miximage_scaling->add("smooth", "smooth", selectedScaling == "smooth");
@ -345,8 +377,8 @@ void GuiScraperMenu::openMiximageOptions()
s->addSaveFunc([miximage_scaling, s] { s->addSaveFunc([miximage_scaling, s] {
if (miximage_scaling->getSelected() != if (miximage_scaling->getSelected() !=
Settings::getInstance()->getString("MiximageScreenshotScaling")) { Settings::getInstance()->getString("MiximageScreenshotScaling")) {
Settings::getInstance()-> Settings::getInstance()->setString("MiximageScreenshotScaling",
setString("MiximageScreenshotScaling", miximage_scaling->getSelected()); miximage_scaling->getSelected());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -356,8 +388,7 @@ void GuiScraperMenu::openMiximageOptions()
miximage_generate->setState(Settings::getInstance()->getBool("MiximageGenerate")); miximage_generate->setState(Settings::getInstance()->getBool("MiximageGenerate"));
s->addWithLabel("GENERATE MIXIMAGES WHEN SCRAPING", miximage_generate); s->addWithLabel("GENERATE MIXIMAGES WHEN SCRAPING", miximage_generate);
s->addSaveFunc([miximage_generate, s] { s->addSaveFunc([miximage_generate, s] {
if (miximage_generate->getState() != if (miximage_generate->getState() != Settings::getInstance()->getBool("MiximageGenerate")) {
Settings::getInstance()->getBool("MiximageGenerate")) {
Settings::getInstance()->setBool("MiximageGenerate", miximage_generate->getState()); Settings::getInstance()->setBool("MiximageGenerate", miximage_generate->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
@ -408,8 +439,8 @@ void GuiScraperMenu::openMiximageOptions()
s->addSaveFunc([miximage_marquee, s] { s->addSaveFunc([miximage_marquee, s] {
if (miximage_marquee->getState() != if (miximage_marquee->getState() !=
Settings::getInstance()->getBool("MiximageIncludeMarquee")) { Settings::getInstance()->getBool("MiximageIncludeMarquee")) {
Settings::getInstance()-> Settings::getInstance()->setBool("MiximageIncludeMarquee",
setBool("MiximageIncludeMarquee", miximage_marquee->getState()); miximage_marquee->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -419,10 +450,8 @@ void GuiScraperMenu::openMiximageOptions()
miximage_box->setState(Settings::getInstance()->getBool("MiximageIncludeBox")); miximage_box->setState(Settings::getInstance()->getBool("MiximageIncludeBox"));
s->addWithLabel("INCLUDE BOX IMAGE", miximage_box); s->addWithLabel("INCLUDE BOX IMAGE", miximage_box);
s->addSaveFunc([miximage_box, s] { s->addSaveFunc([miximage_box, s] {
if (miximage_box->getState() != if (miximage_box->getState() != Settings::getInstance()->getBool("MiximageIncludeBox")) {
Settings::getInstance()->getBool("MiximageIncludeBox")) { Settings::getInstance()->setBool("MiximageIncludeBox", miximage_box->getState());
Settings::getInstance()->
setBool("MiximageIncludeBox", miximage_box->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -434,8 +463,8 @@ void GuiScraperMenu::openMiximageOptions()
s->addSaveFunc([miximage_cover_fallback, s] { s->addSaveFunc([miximage_cover_fallback, s] {
if (miximage_cover_fallback->getState() != if (miximage_cover_fallback->getState() !=
Settings::getInstance()->getBool("MiximageCoverFallback")) { Settings::getInstance()->getBool("MiximageCoverFallback")) {
Settings::getInstance()-> Settings::getInstance()->setBool("MiximageCoverFallback",
setBool("MiximageCoverFallback", miximage_cover_fallback->getState()); miximage_cover_fallback->getState());
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
@ -443,8 +472,10 @@ void GuiScraperMenu::openMiximageOptions()
// Miximage offline generator. // Miximage offline generator.
ComponentListRow offline_generator_row; ComponentListRow offline_generator_row;
offline_generator_row.elements.clear(); offline_generator_row.elements.clear();
offline_generator_row.addElement(std::make_shared<TextComponent> offline_generator_row.addElement(std::make_shared<TextComponent>(mWindow, "OFFLINE GENERATOR",
(mWindow, "OFFLINE GENERATOR", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM),
0x777777FF),
true);
offline_generator_row.addElement(makeArrow(mWindow), false); offline_generator_row.addElement(makeArrow(mWindow), false);
offline_generator_row.makeAcceptInputHandler( offline_generator_row.makeAcceptInputHandler(
std::bind(&GuiScraperMenu::openOfflineGenerator, this, s)); std::bind(&GuiScraperMenu::openOfflineGenerator, this, s));
@ -492,13 +523,15 @@ void GuiScraperMenu::openOtherOptions()
auto s = new GuiSettings(mWindow, "OTHER SETTINGS"); auto s = new GuiSettings(mWindow, "OTHER SETTINGS");
// Scraper region. // Scraper region.
auto scraper_region = std::make_shared<OptionListComponent<std::string>> auto scraper_region = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "REGION", false); mWindow, getHelpStyle(), "REGION", false);
std::string selectedScraperRegion = Settings::getInstance()->getString("ScraperRegion"); std::string selectedScraperRegion = Settings::getInstance()->getString("ScraperRegion");
// clang-format off
scraper_region->add("Europe", "eu", selectedScraperRegion == "eu"); scraper_region->add("Europe", "eu", selectedScraperRegion == "eu");
scraper_region->add("Japan", "jp", selectedScraperRegion == "jp"); scraper_region->add("Japan", "jp", selectedScraperRegion == "jp");
scraper_region->add("USA", "us", selectedScraperRegion == "us"); scraper_region->add("USA", "us", selectedScraperRegion == "us");
scraper_region->add("World", "wor", selectedScraperRegion == "wor"); scraper_region->add("World", "wor", selectedScraperRegion == "wor");
// clang-format on
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the region to "Europe" in this case. // configuration file. Simply set the region to "Europe" in this case.
if (scraper_region->getSelectedObjects().size() == 0) if (scraper_region->getSelectedObjects().size() == 0)
@ -511,18 +544,20 @@ void GuiScraperMenu::openOtherOptions()
} }
}); });
// Regions are not supported by TheGamesDB, so disable the option if this scraper is selected. // Regions are not supported by TheGamesDB, so gray out the option if this scraper is selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") { if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
scraper_region->setEnabled(false); scraper_region->setEnabled(false);
scraper_region->setOpacity(DISABLED_OPACITY); scraper_region->setOpacity(DISABLED_OPACITY);
scraper_region->getParent()->getChild(scraper_region-> scraper_region->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scraper_region->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
// Scraper language. // Scraper language.
auto scraper_language = std::make_shared<OptionListComponent<std::string>> auto scraper_language = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "PREFERRED LANGUAGE", false); mWindow, getHelpStyle(), "PREFERRED LANGUAGE", false);
std::string selectedScraperLanguage = Settings::getInstance()->getString("ScraperLanguage"); std::string selectedScraperLanguage = Settings::getInstance()->getString("ScraperLanguage");
// clang-format off
scraper_language->add("English", "en", selectedScraperLanguage == "en"); scraper_language->add("English", "en", selectedScraperLanguage == "en");
scraper_language->add("Español", "es", selectedScraperLanguage == "es"); scraper_language->add("Español", "es", selectedScraperLanguage == "es");
scraper_language->add("Português", "pt", selectedScraperLanguage == "pt"); scraper_language->add("Português", "pt", selectedScraperLanguage == "pt");
@ -543,6 +578,7 @@ void GuiScraperMenu::openOtherOptions()
scraper_language->add("Čeština", "cz", selectedScraperLanguage == "cz"); scraper_language->add("Čeština", "cz", selectedScraperLanguage == "cz");
scraper_language->add("Slovenčina", "sk", selectedScraperLanguage == "sk"); scraper_language->add("Slovenčina", "sk", selectedScraperLanguage == "sk");
scraper_language->add("Türkçe", "tr", selectedScraperLanguage == "tr"); scraper_language->add("Türkçe", "tr", selectedScraperLanguage == "tr");
// clang-format on
// If there are no objects returned, then there must be a manually modified entry in the // If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the language to "English" in this case. // configuration file. Simply set the language to "English" in this case.
if (scraper_language->getSelectedObjects().size() == 0) if (scraper_language->getSelectedObjects().size() == 0)
@ -556,12 +592,14 @@ void GuiScraperMenu::openOtherOptions()
} }
}); });
// Languages are not supported by TheGamesDB, so disable the option if this scraper is selected. // Languages are not supported by TheGamesDB, so gray out the option if this scraper is
// selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") { if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
scraper_language->setEnabled(false); scraper_language->setEnabled(false);
scraper_language->setOpacity(DISABLED_OPACITY); scraper_language->setOpacity(DISABLED_OPACITY);
scraper_language->getParent()->getChild(scraper_language-> scraper_language->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scraper_language->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
// Overwrite files and data. // Overwrite files and data.
@ -579,8 +617,8 @@ void GuiScraperMenu::openOtherOptions()
// Halt scraping on invalid media files. // Halt scraping on invalid media files.
auto scraper_halt_on_invalid_media = std::make_shared<SwitchComponent>(mWindow); auto scraper_halt_on_invalid_media = std::make_shared<SwitchComponent>(mWindow);
scraper_halt_on_invalid_media-> scraper_halt_on_invalid_media->setState(
setState(Settings::getInstance()->getBool("ScraperHaltOnInvalidMedia")); Settings::getInstance()->getBool("ScraperHaltOnInvalidMedia"));
s->addWithLabel("HALT ON INVALID MEDIA FILES", scraper_halt_on_invalid_media); s->addWithLabel("HALT ON INVALID MEDIA FILES", scraper_halt_on_invalid_media);
s->addSaveFunc([scraper_halt_on_invalid_media, s] { s->addSaveFunc([scraper_halt_on_invalid_media, s] {
if (scraper_halt_on_invalid_media->getState() != if (scraper_halt_on_invalid_media->getState() !=
@ -593,8 +631,8 @@ void GuiScraperMenu::openOtherOptions()
// Search using metadata names. // Search using metadata names.
auto scraper_search_metadata_name = std::make_shared<SwitchComponent>(mWindow); auto scraper_search_metadata_name = std::make_shared<SwitchComponent>(mWindow);
scraper_search_metadata_name-> scraper_search_metadata_name->setState(
setState(Settings::getInstance()->getBool("ScraperSearchMetadataName")); Settings::getInstance()->getBool("ScraperSearchMetadataName"));
s->addWithLabel("SEARCH USING METADATA NAMES", scraper_search_metadata_name); s->addWithLabel("SEARCH USING METADATA NAMES", scraper_search_metadata_name);
s->addSaveFunc([scraper_search_metadata_name, s] { s->addSaveFunc([scraper_search_metadata_name, s] {
if (scraper_search_metadata_name->getState() != if (scraper_search_metadata_name->getState() !=
@ -630,15 +668,15 @@ void GuiScraperMenu::openOtherOptions()
} }
}); });
// If interactive mode is set to off, then disable this option. // If interactive mode is set to off, then gray out this option.
if (!Settings::getInstance()->getBool("ScraperInteractive")) { if (!Settings::getInstance()->getBool("ScraperInteractive")) {
scraper_semiautomatic->setEnabled(false); scraper_semiautomatic->setEnabled(false);
scraper_semiautomatic->setOpacity(DISABLED_OPACITY); scraper_semiautomatic->setOpacity(DISABLED_OPACITY);
scraper_semiautomatic->getParent()->getChild(scraper_semiautomatic-> scraper_semiautomatic->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scraper_semiautomatic->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
// Respect the per-file multi-scraper exclusion flag. // Respect the per-file multi-scraper exclusion flag.
auto scraper_respect_exclusions = std::make_shared<SwitchComponent>(mWindow); auto scraper_respect_exclusions = std::make_shared<SwitchComponent>(mWindow);
scraper_respect_exclusions->setState( scraper_respect_exclusions->setState(
@ -667,18 +705,18 @@ void GuiScraperMenu::openOtherOptions()
} }
}); });
// If respecting excluded files is set to off, then disable this option. // If respecting excluded files is set to off, then gray out this option.
if (!Settings::getInstance()->getBool("ScraperRespectExclusions")) { if (!Settings::getInstance()->getBool("ScraperRespectExclusions")) {
scraper_exclude_recursively->setEnabled(false); scraper_exclude_recursively->setEnabled(false);
scraper_exclude_recursively->setOpacity(DISABLED_OPACITY); scraper_exclude_recursively->setOpacity(DISABLED_OPACITY);
scraper_exclude_recursively->getParent()->getChild(scraper_exclude_recursively-> scraper_exclude_recursively->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scraper_exclude_recursively->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
// Include actual folders when scraping. // Include actual folders when scraping.
auto scraper_include_folders = std::make_shared<SwitchComponent>(mWindow); auto scraper_include_folders = std::make_shared<SwitchComponent>(mWindow);
scraper_include_folders->setState( scraper_include_folders->setState(Settings::getInstance()->getBool("ScraperIncludeFolders"));
Settings::getInstance()->getBool("ScraperIncludeFolders"));
s->addWithLabel("SCRAPE ACTUAL FOLDERS", scraper_include_folders); s->addWithLabel("SCRAPE ACTUAL FOLDERS", scraper_include_folders);
s->addSaveFunc([scraper_include_folders, s] { s->addSaveFunc([scraper_include_folders, s] {
if (scraper_include_folders->getState() != if (scraper_include_folders->getState() !=
@ -703,19 +741,31 @@ void GuiScraperMenu::openOtherOptions()
} }
}); });
// The TLS/certificate issue is not present for TheGamesDB, so gray out the option if this
// scraper is selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
retry_peer_verification->setEnabled(false);
retry_peer_verification->setOpacity(DISABLED_OPACITY);
retry_peer_verification->getParent()
->getChild(retry_peer_verification->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
// Switch callbacks. // Switch callbacks.
auto interactiveToggleFunc = [scraper_semiautomatic]() { auto interactiveToggleFunc = [scraper_semiautomatic]() {
if (scraper_semiautomatic->getEnabled()) { if (scraper_semiautomatic->getEnabled()) {
scraper_semiautomatic->setEnabled(false); scraper_semiautomatic->setEnabled(false);
scraper_semiautomatic->setOpacity(DISABLED_OPACITY); scraper_semiautomatic->setOpacity(DISABLED_OPACITY);
scraper_semiautomatic->getParent()->getChild(scraper_semiautomatic-> scraper_semiautomatic->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scraper_semiautomatic->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
else { else {
scraper_semiautomatic->setEnabled(true); scraper_semiautomatic->setEnabled(true);
scraper_semiautomatic->setOpacity(255); scraper_semiautomatic->setOpacity(255);
scraper_semiautomatic->getParent()->getChild(scraper_semiautomatic-> scraper_semiautomatic->getParent()
getChildIndex() - 1)->setOpacity(255); ->getChild(scraper_semiautomatic->getChildIndex() - 1)
->setOpacity(255);
} }
}; };
@ -723,14 +773,16 @@ void GuiScraperMenu::openOtherOptions()
if (scraper_exclude_recursively->getEnabled()) { if (scraper_exclude_recursively->getEnabled()) {
scraper_exclude_recursively->setEnabled(false); scraper_exclude_recursively->setEnabled(false);
scraper_exclude_recursively->setOpacity(DISABLED_OPACITY); scraper_exclude_recursively->setOpacity(DISABLED_OPACITY);
scraper_exclude_recursively->getParent()->getChild(scraper_exclude_recursively-> scraper_exclude_recursively->getParent()
getChildIndex() - 1)->setOpacity(DISABLED_OPACITY); ->getChild(scraper_exclude_recursively->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
} }
else { else {
scraper_exclude_recursively->setEnabled(true); scraper_exclude_recursively->setEnabled(true);
scraper_exclude_recursively->setOpacity(255); scraper_exclude_recursively->setOpacity(255);
scraper_exclude_recursively->getParent()->getChild(scraper_exclude_recursively-> scraper_exclude_recursively->getParent()
getChildIndex() - 1)->setOpacity(255); ->getChild(scraper_exclude_recursively->getChildIndex() - 1)
->setOpacity(255);
} }
}; };
@ -763,9 +815,8 @@ void GuiScraperMenu::pressedStart()
"Continue anyway?"; "Continue anyway?";
} }
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
Utils::String::toUpper(warningString), Utils::String::toUpper(warningString), "YES",
"YES", std::bind(&GuiScraperMenu::start, this), std::bind(&GuiScraperMenu::start, this), "NO", nullptr));
"NO", nullptr));
return; return;
} }
} }
@ -775,8 +826,8 @@ void GuiScraperMenu::pressedStart()
void GuiScraperMenu::start() void GuiScraperMenu::start()
{ {
if (mSystems->getSelectedObjects().empty()) { if (mSystems->getSelectedObjects().empty()) {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(
"PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE")); new GuiMsgBox(mWindow, getHelpStyle(), "PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE"));
return; return;
} }
@ -798,8 +849,7 @@ void GuiScraperMenu::start()
contentToScrape = true; contentToScrape = true;
break; break;
} }
if (scraperService == "screenscraper" && if (scraperService == "screenscraper" && Settings::getInstance()->getBool("ScrapeVideos")) {
Settings::getInstance()->getBool("ScrapeVideos")) {
contentToScrape = true; contentToScrape = true;
break; break;
} }
@ -832,20 +882,20 @@ void GuiScraperMenu::start()
getSearches(mSystems->getSelectedObjects(), mFilters->getSelected()); getSearches(mSystems->getSelectedObjects(), mFilters->getSelected());
if (searches.empty()) { if (searches.empty()) {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(
"ALL GAMES WERE FILTERED, NOTHING TO SCRAPE")); new GuiMsgBox(mWindow, getHelpStyle(), "ALL GAMES WERE FILTERED, NOTHING TO SCRAPE"));
} }
else { else {
GuiScraperMulti* gsm = new GuiScraperMulti(mWindow, searches, GuiScraperMulti* gsm = new GuiScraperMulti(
Settings::getInstance()->getBool("ScraperInteractive")); mWindow, searches, Settings::getInstance()->getBool("ScraperInteractive"));
mWindow->pushGui(gsm); mWindow->pushGui(gsm);
mMenu.setCursorToList(); mMenu.setCursorToList();
mMenu.setCursorToFirstListEntry(); mMenu.setCursorToFirstListEntry();
} }
} }
std::queue<ScraperSearchParams> GuiScraperMenu::getSearches( std::queue<ScraperSearchParams> GuiScraperMenu::getSearches(std::vector<SystemData*> systems,
std::vector<SystemData*> systems, GameFilterFunc selector) GameFilterFunc selector)
{ {
std::queue<ScraperSearchParams> queue; std::queue<ScraperSearchParams> queue;
for (auto sys = systems.cbegin(); sys != systems.cend(); sys++) { for (auto sys = systems.cbegin(); sys != systems.cend(); sys++) {
@ -866,8 +916,10 @@ std::queue<ScraperSearchParams> GuiScraperMenu::getSearches(
return queue; return queue;
} }
void GuiScraperMenu::addEntry(const std::string& name, unsigned int color, void GuiScraperMenu::addEntry(const std::string& name,
bool add_arrow, const std::function<void()>& func) unsigned int color,
bool add_arrow,
const std::function<void()>& func)
{ {
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM); std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);

View file

@ -16,11 +16,10 @@
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
class FileData; class FileData;
template<typename T>
class OptionListComponent;
class SwitchComponent; class SwitchComponent;
class SystemData; class SystemData;
template <typename T> class OptionListComponent;
typedef std::function<bool(SystemData*, FileData*)> GameFilterFunc; typedef std::function<bool(SystemData*, FileData*)> GameFilterFunc;
class GuiScraperMenu : public GuiComponent class GuiScraperMenu : public GuiComponent
@ -38,16 +37,18 @@ private:
void pressedStart(); void pressedStart();
void start(); void start();
void addEntry(const std::string&, unsigned int color, void addEntry(const std::string&,
bool add_arrow, const std::function<void()>& func); unsigned int color,
bool add_arrow,
const std::function<void()>& func);
void openAccountOptions(); void openAccountOptions();
void openContentOptions(); void openContentOptions();
void openMiximageOptions(); void openMiximageOptions();
void openOfflineGenerator(GuiSettings* settings); void openOfflineGenerator(GuiSettings* settings);
void openOtherOptions(); void openOtherOptions();
std::queue<ScraperSearchParams> getSearches( std::queue<ScraperSearchParams> getSearches(std::vector<SystemData*> systems,
std::vector<SystemData*> systems, GameFilterFunc selector); GameFilterFunc selector);
std::shared_ptr<OptionListComponent<std::string>> mScraper; std::shared_ptr<OptionListComponent<std::string>> mScraper;
std::shared_ptr<OptionListComponent<GameFilterFunc>> mFilters; std::shared_ptr<OptionListComponent<GameFilterFunc>> mFilters;

View file

@ -11,27 +11,26 @@
#include "guis/GuiScraperMulti.h" #include "guis/GuiScraperMulti.h"
#include "CollectionSystemsManager.h"
#include "Gamelist.h"
#include "MameNames.h"
#include "SystemData.h"
#include "Window.h"
#include "components/ButtonComponent.h" #include "components/ButtonComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiScraperSearch.h" #include "guis/GuiScraperSearch.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "CollectionSystemsManager.h"
#include "Gamelist.h"
#include "MameNames.h"
#include "SystemData.h"
#include "Window.h"
GuiScraperMulti::GuiScraperMulti( GuiScraperMulti::GuiScraperMulti(Window* window,
Window* window,
const std::queue<ScraperSearchParams>& searches, const std::queue<ScraperSearchParams>& searches,
bool approveResults) bool approveResults)
: GuiComponent(window), : GuiComponent(window)
mBackground(window, ":/graphics/frame.svg"), , mBackground(window, ":/graphics/frame.svg")
mGrid(window, Vector2i(1, 5)), , mGrid(window, Vector2i(1, 5))
mSearchQueue(searches), , mSearchQueue(searches)
mApproveResults(approveResults) , mApproveResults(approveResults)
{ {
assert(mSearchQueue.size()); assert(mSearchQueue.size());
@ -50,54 +49,69 @@ GuiScraperMulti::GuiScraperMulti(
Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
mGrid.setEntry(mTitle, Vector2i(0, 0), false, true); mGrid.setEntry(mTitle, Vector2i(0, 0), false, true);
mSystem = std::make_shared<TextComponent>(mWindow, "SYSTEM", mSystem = std::make_shared<TextComponent>(mWindow, "SYSTEM", Font::get(FONT_SIZE_MEDIUM),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_CENTER); 0x777777FF, ALIGN_CENTER);
mGrid.setEntry(mSystem, Vector2i(0, 1), false, true); mGrid.setEntry(mSystem, Vector2i(0, 1), false, true);
mSubtitle = std::make_shared<TextComponent>(mWindow, "subtitle text", mSubtitle = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); mWindow, "subtitle text", Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER);
mGrid.setEntry(mSubtitle, Vector2i(0, 2), false, true); mGrid.setEntry(mSubtitle, Vector2i(0, 2), false, true);
if (mApproveResults && !Settings::getInstance()->getBool("ScraperSemiautomatic")) if (mApproveResults && !Settings::getInstance()->getBool("ScraperSemiautomatic"))
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow, mSearchComp = std::make_shared<GuiScraperSearch>(
GuiScraperSearch::NEVER_AUTO_ACCEPT, mTotalGames); mWindow, GuiScraperSearch::NEVER_AUTO_ACCEPT, mTotalGames);
else if (mApproveResults && Settings::getInstance()->getBool("ScraperSemiautomatic")) else if (mApproveResults && Settings::getInstance()->getBool("ScraperSemiautomatic"))
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow, mSearchComp = std::make_shared<GuiScraperSearch>(
GuiScraperSearch::ACCEPT_SINGLE_MATCHES, mTotalGames); mWindow, GuiScraperSearch::ACCEPT_SINGLE_MATCHES, mTotalGames);
else if (!mApproveResults) else if (!mApproveResults)
mSearchComp = std::make_shared<GuiScraperSearch>(mWindow, mSearchComp = std::make_shared<GuiScraperSearch>(
GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, mTotalGames); mWindow, GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, mTotalGames);
mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult, mSearchComp->setAcceptCallback(
this, std::placeholders::_1)); std::bind(&GuiScraperMulti::acceptResult, this, std::placeholders::_1));
mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this)); mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this));
mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this)); mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this));
mGrid.setEntry(mSearchComp, Vector2i(0, 3), mSearchComp->getSearchType() != mGrid.setEntry(mSearchComp, Vector2i(0, 3),
GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT, true); mSearchComp->getSearchType() != GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT,
true);
std::vector<std::shared_ptr<ButtonComponent>> buttons; std::vector<std::shared_ptr<ButtonComponent>> buttons;
if (mApproveResults) { if (mApproveResults) {
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "REFINE SEARCH", buttons.push_back(
"refine search", [&] { std::make_shared<ButtonComponent>(mWindow, "REFINE SEARCH", "refine search", [&] {
// Refine the search, unless the result has already been accepted or we're in
// semi-automatic mode and there are less than 2 search results.
if (!mSearchComp->getAcceptedResult() &&
!(mSearchComp->getSearchType() == GuiScraperSearch::ACCEPT_SINGLE_MATCHES &&
mSearchComp->getScraperResultsSize() < 2)) {
mSearchComp->openInputScreen(mSearchQueue.front()); mSearchComp->openInputScreen(mSearchQueue.front());
mGrid.resetCursor(); mGrid.resetCursor();
}
})); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SKIP", "skip game", [&] { buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SKIP", "skip game", [&] {
// Skip game, unless the result has already been accepted.
if (!mSearchComp->getAcceptedResult()) {
skip(); skip();
mGrid.resetCursor(); mGrid.resetCursor();
}
})); }));
} }
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "STOP", buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "STOP", "stop",
"stop", std::bind(&GuiScraperMulti::finish, this))); std::bind(&GuiScraperMulti::finish, this)));
mButtonGrid = makeButtonGrid(mWindow, buttons); mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false); mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false);
setSize(Renderer::getScreenWidth() * 0.95f, Renderer::getScreenHeight() * 0.849f); // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - // the 16:9 reference.
mSize.y()) / 2); float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = Math::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth();
setSize(width, Renderer::getScreenHeight() * 0.849f);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2.0f,
(Renderer::getScreenHeight() - mSize.y()) / 2.0f);
doNextSearch(); doNextSearch();
} }
@ -106,7 +120,7 @@ GuiScraperMulti::~GuiScraperMulti()
{ {
if (mTotalSuccessful > 0) { if (mTotalSuccessful > 0) {
// Sort all systems to possibly update their view style from Basic to Detailed or Video. // Sort all systems to possibly update their view style from Basic to Detailed or Video.
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
(*it)->sortSystem(); (*it)->sortSystem();
} }
@ -116,10 +130,10 @@ GuiScraperMulti::~GuiScraperMulti()
void GuiScraperMulti::onSizeChanged() void GuiScraperMulti::onSizeChanged()
{ {
mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32.0f, -32.0f));
mGrid.setRowHeightPerc(0, mTitle->getFont()->getLetterHeight() * 1.9725f / mSize.y(), false); mGrid.setRowHeightPerc(0, mTitle->getFont()->getLetterHeight() * 1.9725f / mSize.y(), false);
mGrid.setRowHeightPerc(1, (mSystem->getFont()->getLetterHeight() + 2) / mSize.y(), false); mGrid.setRowHeightPerc(1, (mSystem->getFont()->getLetterHeight() + 2.0f) / mSize.y(), false);
mGrid.setRowHeightPerc(2, mSubtitle->getFont()->getHeight() * 1.75f / mSize.y(), false); mGrid.setRowHeightPerc(2, mSubtitle->getFont()->getHeight() * 1.75f / mSize.y(), false);
mGrid.setRowHeightPerc(4, mButtonGrid->getSize().y() / mSize.y(), false); mGrid.setRowHeightPerc(4, mButtonGrid->getSize().y() / mSize.y(), false);
mGrid.setSize(mSize); mGrid.setSize(mSize);
@ -144,16 +158,17 @@ void GuiScraperMulti::doNextSearch()
else { else {
if (mSearchQueue.front().game->isArcadeGame() && if (mSearchQueue.front().game->isArcadeGame() &&
Settings::getInstance()->getString("Scraper") == "thegamesdb") Settings::getInstance()->getString("Scraper") == "thegamesdb")
scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()) + scrapeName =
" (" + MameNames::getInstance()->getCleanName(mSearchQueue.front().game-> Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()) + " (" +
getCleanName()) + ")"; MameNames::getInstance()->getCleanName(mSearchQueue.front().game->getCleanName()) +
")";
else else
scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()); scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath());
} }
// Extract possible subfolders from the path. // Extract possible subfolders from the path.
std::string folderPath = Utils::String::replace( std::string folderPath =
Utils::FileSystem::getParent(mSearchQueue.front().game->getPath()), Utils::String::replace(Utils::FileSystem::getParent(mSearchQueue.front().game->getPath()),
mSearchQueue.front().system->getSystemEnvData()->mStartPath, ""); mSearchQueue.front().system->getSystemEnvData()->mStartPath, "");
if (folderPath.size() >= 2) { if (folderPath.size() >= 2) {
@ -168,9 +183,10 @@ void GuiScraperMulti::doNextSearch()
// Update subtitle. // Update subtitle.
ss.str(""); ss.str("");
ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " << folderPath << ss << "GAME " << (mCurrentGame + 1) << " OF " << mTotalGames << " - " << folderPath
scrapeName << ((mSearchQueue.front().game->getType() == FOLDER) ? " " + << scrapeName
ViewController::FOLDER_CHAR : ""); << ((mSearchQueue.front().game->getType() == FOLDER) ? " " + ViewController::FOLDER_CHAR :
"");
mSubtitle->setText(ss.str()); mSubtitle->setText(ss.str());
mSearchComp->search(mSearchQueue.front()); mSearchComp->search(mSearchQueue.front());
@ -207,12 +223,12 @@ void GuiScraperMulti::finish()
ss << "NO GAMES WERE SCRAPED"; ss << "NO GAMES WERE SCRAPED";
} }
else { else {
ss << mTotalSuccessful << " GAME" << ss << mTotalSuccessful << " GAME" << ((mTotalSuccessful > 1) ? "S" : "")
((mTotalSuccessful > 1) ? "S" : "") << " SUCCESSFULLY SCRAPED"; << " SUCCESSFULLY SCRAPED";
if (mTotalSkipped > 0) if (mTotalSkipped > 0)
ss << "\n" << mTotalSkipped << " GAME" ss << "\n"
<< ((mTotalSkipped > 1) ? "S" : "") << " SKIPPED"; << mTotalSkipped << " GAME" << ((mTotalSkipped > 1) ? "S" : "") << " SKIPPED";
} }
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), ss.str(), "OK", [&] { mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), ss.str(), "OK", [&] {

View file

@ -12,11 +12,11 @@
#ifndef ES_APP_GUIS_GUI_SCRAPER_MULTI_H #ifndef ES_APP_GUIS_GUI_SCRAPER_MULTI_H
#define ES_APP_GUIS_GUI_SCRAPER_MULTI_H #define ES_APP_GUIS_GUI_SCRAPER_MULTI_H
#include "GuiComponent.h"
#include "MetaData.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "components/NinePatchComponent.h" #include "components/NinePatchComponent.h"
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
#include "GuiComponent.h"
#include "MetaData.h"
class GuiScraperSearch; class GuiScraperSearch;
class TextComponent; class TextComponent;
@ -24,8 +24,7 @@ class TextComponent;
class GuiScraperMulti : public GuiComponent class GuiScraperMulti : public GuiComponent
{ {
public: public:
GuiScraperMulti( GuiScraperMulti(Window* window,
Window* window,
const std::queue<ScraperSearchParams>& searches, const std::queue<ScraperSearchParams>& searches,
bool approveResults); bool approveResults);
@ -40,15 +39,7 @@ private:
void acceptResult(const ScraperSearchResult& result); void acceptResult(const ScraperSearchResult& result);
void skip(); void skip();
void doNextSearch(); void doNextSearch();
void finish(); void finish();
unsigned int mTotalGames;
unsigned int mCurrentGame;
unsigned int mTotalSuccessful;
unsigned int mTotalSkipped;
std::queue<ScraperSearchParams> mSearchQueue;
std::vector<MetaDataDecl> mMetaDataDecl;
bool mApproveResults;
NinePatchComponent mBackground; NinePatchComponent mBackground;
ComponentGrid mGrid; ComponentGrid mGrid;
@ -58,6 +49,14 @@ private:
std::shared_ptr<TextComponent> mSubtitle; std::shared_ptr<TextComponent> mSubtitle;
std::shared_ptr<GuiScraperSearch> mSearchComp; std::shared_ptr<GuiScraperSearch> mSearchComp;
std::shared_ptr<ComponentGrid> mButtonGrid; std::shared_ptr<ComponentGrid> mButtonGrid;
std::queue<ScraperSearchParams> mSearchQueue;
std::vector<MetaDataDecl> mMetaDataDecl;
unsigned int mTotalGames;
unsigned int mCurrentGame;
unsigned int mTotalSuccessful;
unsigned int mTotalSkipped;
bool mApproveResults;
}; };
#endif // ES_APP_GUIS_GUI_SCRAPER_MULTI_H #endif // ES_APP_GUIS_GUI_SCRAPER_MULTI_H

View file

@ -15,6 +15,13 @@
#include "guis/GuiScraperSearch.h" #include "guis/GuiScraperSearch.h"
#include "CollectionSystemsManager.h"
#include "FileData.h"
#include "Log.h"
#include "MameNames.h"
#include "PlatformId.h"
#include "SystemData.h"
#include "Window.h"
#include "components/ComponentList.h" #include "components/ComponentList.h"
#include "components/DateTimeEditComponent.h" #include "components/DateTimeEditComponent.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
@ -26,38 +33,29 @@
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "CollectionSystemsManager.h"
#include "FileData.h"
#include "Log.h"
#include "MameNames.h"
#include "PlatformId.h"
#include "SystemData.h"
#include "Window.h"
#define FAILED_VERIFICATION_RETRIES 8 #define FAILED_VERIFICATION_RETRIES 8
GuiScraperSearch::GuiScraperSearch( GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount)
Window* window, : GuiComponent(window)
SearchType type, , mGrid(window, Vector2i(4, 3))
unsigned int scrapeCount) , mBusyAnim(window)
: GuiComponent(window), , mSearchType(type)
mGrid(window, Vector2i(4, 3)), , mScrapeCount(scrapeCount)
mBusyAnim(window), , mScrapeRatings(false)
mSearchType(type), , mRefinedSearch(false)
mScrapeCount(scrapeCount), , mFoundGame(false)
mScrapeRatings(false),
mRefinedSearch(false),
mFoundGame(false)
{ {
addChild(&mGrid); addChild(&mGrid);
mBlockAccept = false; mBlockAccept = false;
mAcceptedResult = false;
mRetrySearch = false; mRetrySearch = false;
mRetryCount = 0; mRetryCount = 0;
// Left spacer (empty component, needed for borders). // Left spacer (empty component, needed for borders).
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 0), false, false,
false, false, Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); Vector2i(1, 3), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
// Selected result name. // Selected result name.
mResultName = std::make_shared<TextComponent>(mWindow, "Result name", mResultName = std::make_shared<TextComponent>(mWindow, "Result name",
@ -89,40 +87,47 @@ GuiScraperSearch::GuiScraperSearch(
mMD_ReleaseDate = std::make_shared<DateTimeEditComponent>(mWindow); mMD_ReleaseDate = std::make_shared<DateTimeEditComponent>(mWindow);
mMD_ReleaseDate->setColor(mdColor); mMD_ReleaseDate->setColor(mdColor);
mMD_ReleaseDate->setUppercase(true); mMD_ReleaseDate->setUppercase(true);
mMD_Developer = std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, mMD_Developer =
Vector3f::Zero(), Vector2f::Zero(), 0x00000000, 0.02f); std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
mMD_Publisher = std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector2f::Zero(), 0x00000000, 0.02f);
Vector3f::Zero(), Vector2f::Zero(), 0x00000000, 0.02f); mMD_Publisher =
mMD_Genre = std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
Vector3f::Zero(), Vector2f::Zero(), 0x00000000, 0.02f); Vector2f::Zero(), 0x00000000, 0.02f);
mMD_Players = std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, mMD_Genre =
Vector3f::Zero(), Vector2f::Zero(), 0x00000000, 0.02f); std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
Vector2f::Zero(), 0x00000000, 0.02f);
mMD_Players =
std::make_shared<TextComponent>(mWindow, "", font, mdColor, ALIGN_LEFT, Vector3f::Zero(),
Vector2f::Zero(), 0x00000000, 0.02f);
mMD_Filler = std::make_shared<TextComponent>(mWindow, "", font, mdColor); mMD_Filler = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
if (Settings::getInstance()->getString("Scraper") != "thegamesdb") if (Settings::getInstance()->getString("Scraper") != "thegamesdb")
mScrapeRatings = true; mScrapeRatings = true;
if (mScrapeRatings) if (mScrapeRatings)
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Pairs.push_back(
(mWindow, "RATING:", font, mdLblColor), mMD_Rating, false)); MetaDataPair(std::make_shared<TextComponent>(mWindow, "RATING:", font, mdLblColor),
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Rating, false));
(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Pairs.push_back(MetaDataPair(
(mWindow, "DEVELOPER:", font, mdLblColor), mMD_Developer)); std::make_shared<TextComponent>(mWindow, "RELEASED:", font, mdLblColor), mMD_ReleaseDate));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Pairs.push_back(MetaDataPair(
(mWindow, "PUBLISHER:", font, mdLblColor), mMD_Publisher)); std::make_shared<TextComponent>(mWindow, "DEVELOPER:", font, mdLblColor), mMD_Developer));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Pairs.push_back(MetaDataPair(
(mWindow, "GENRE:", font, mdLblColor), mMD_Genre)); std::make_shared<TextComponent>(mWindow, "PUBLISHER:", font, mdLblColor), mMD_Publisher));
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Pairs.push_back(MetaDataPair(
(mWindow, "PLAYERS:", font, mdLblColor), mMD_Players)); std::make_shared<TextComponent>(mWindow, "GENRE:", font, mdLblColor), mMD_Genre));
mMD_Pairs.push_back(MetaDataPair(
std::make_shared<TextComponent>(mWindow, "PLAYERS:", font, mdLblColor), mMD_Players));
// If no rating is being scraped, add a filler to make sure that the fonts keep the same // If no rating is being scraped, add a filler to make sure that the fonts keep the same
// size so the GUI looks consistent. // size so the GUI looks consistent.
if (!mScrapeRatings) if (!mScrapeRatings)
mMD_Pairs.push_back(MetaDataPair(std::make_shared<TextComponent> mMD_Pairs.push_back(MetaDataPair(
(mWindow, "", font, mdLblColor), mMD_Filler)); std::make_shared<TextComponent>(mWindow, "", font, mdLblColor), mMD_Filler));
mMD_Grid = std::make_shared<ComponentGrid>(mWindow, mMD_Grid = std::make_shared<ComponentGrid>(
Vector2i(2, static_cast<int>(mMD_Pairs.size()*2 - 1))); mWindow, Vector2i(2, static_cast<int>(mMD_Pairs.size() * 2 - 1)));
unsigned int i = 0; unsigned int i = 0;
for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++) { for (auto it = mMD_Pairs.cbegin(); it != mMD_Pairs.cend(); it++) {
mMD_Grid->setEntry(it->first, Vector2i(0, i), false, true); mMD_Grid->setEntry(it->first, Vector2i(0, i), false, true);
@ -135,7 +140,9 @@ GuiScraperSearch::GuiScraperSearch(
// Result list. // Result list.
mResultList = std::make_shared<ComponentList>(mWindow); mResultList = std::make_shared<ComponentList>(mWindow);
mResultList->setCursorChangedCallback([this](CursorState state) { mResultList->setCursorChangedCallback([this](CursorState state) {
if (state == CURSOR_STOPPED) updateInfoPane(); }); if (state == CURSOR_STOPPED)
updateInfoPane();
});
updateViewStyle(); updateViewStyle();
} }
@ -216,11 +223,11 @@ void GuiScraperSearch::onSizeChanged()
resizeMetadata(); resizeMetadata();
if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT) if (mSearchType != ALWAYS_ACCEPT_FIRST_RESULT)
mDescContainer->setSize(mGrid.getColWidth(1) * boxartCellScale + mDescContainer->setSize(mGrid.getColWidth(1) * boxartCellScale + mGrid.getColWidth(2),
mGrid.getColWidth(2), mResultDesc->getFont()->getHeight() * 3); mResultDesc->getFont()->getHeight() * 3.0f);
else else
mDescContainer->setSize(mGrid.getColWidth(3) * boxartCellScale, mDescContainer->setSize(mGrid.getColWidth(3) * boxartCellScale,
mResultDesc->getFont()->getHeight() * 6); mResultDesc->getFont()->getHeight() * 6.0f);
// Make description text wrap at edge of container. // Make description text wrap at edge of container.
mResultDesc->setSize(mDescContainer->getSize().x(), 0); mResultDesc->setSize(mDescContainer->getSize().x(), 0);
@ -247,13 +254,14 @@ void GuiScraperSearch::resizeMetadata()
it->first->setFont(fontLbl); it->first->setFont(fontLbl);
it->first->setSize(0, 0); it->first->setSize(0, 0);
if (it->first->getSize().x() > maxLblWidth) if (it->first->getSize().x() > maxLblWidth)
maxLblWidth = it->first->getSize().x() + maxLblWidth =
(16.0f * Renderer::getScreenWidthModifier()); it->first->getSize().x() + (16.0f * Renderer::getScreenWidthModifier());
} }
for (unsigned int i = 0; i < mMD_Pairs.size(); i++) for (unsigned int i = 0; i < mMD_Pairs.size(); i++)
mMD_Grid->setRowHeightPerc(i * 2, (fontLbl->getLetterHeight() + mMD_Grid->setRowHeightPerc(
(2.0f * Renderer::getScreenHeightModifier())) / mMD_Grid->getSize().y()); i * 2, (fontLbl->getLetterHeight() + (2.0f * Renderer::getScreenHeightModifier())) /
mMD_Grid->getSize().y());
// Update component fonts. // Update component fonts.
mMD_ReleaseDate->setFont(fontComp); mMD_ReleaseDate->setFont(fontComp);
@ -289,8 +297,8 @@ void GuiScraperSearch::updateViewStyle()
GridFlags::BORDER_TOP); GridFlags::BORDER_TOP);
// Need a border on the bottom left. // Need a border on the bottom left.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 2), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(0, 2), false, false,
false, false, Vector2i(3, 1), GridFlags::BORDER_BOTTOM); Vector2i(3, 1), GridFlags::BORDER_BOTTOM);
// Show description on the right. // Show description on the right.
mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3), mGrid.setEntry(mDescContainer, Vector2i(3, 0), false, false, Vector2i(1, 3),
@ -300,8 +308,8 @@ void GuiScraperSearch::updateViewStyle()
} }
else { else {
// Fake row where name would be. // Fake row where name would be.
mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0), mGrid.setEntry(std::make_shared<GuiComponent>(mWindow), Vector2i(1, 0), false, true,
false, true, Vector2i(2, 1), GridFlags::BORDER_TOP); Vector2i(2, 1), GridFlags::BORDER_TOP);
// Show result list on the right. // Show result list on the right.
mGrid.setEntry(mResultList, Vector2i(3, 0), true, true, Vector2i(1, 3), mGrid.setEntry(mResultList, Vector2i(3, 0), true, true, Vector2i(1, 3),
@ -318,6 +326,7 @@ void GuiScraperSearch::updateViewStyle()
void GuiScraperSearch::search(const ScraperSearchParams& params) void GuiScraperSearch::search(const ScraperSearchParams& params)
{ {
mBlockAccept = true; mBlockAccept = true;
mAcceptedResult = false;
mMiximageResult = false; mMiximageResult = false;
mScrapeResult = {}; mScrapeResult = {};
@ -340,6 +349,7 @@ void GuiScraperSearch::stop()
mMDRetrieveURLsHandle.reset(); mMDRetrieveURLsHandle.reset();
mMiximageGenerator.reset(); mMiximageGenerator.reset();
mBlockAccept = false; mBlockAccept = false;
mAcceptedResult = false;
mMiximageResult = false; mMiximageResult = false;
mScrapeResult = {}; mScrapeResult = {};
} }
@ -355,7 +365,8 @@ void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& resu
if (results.empty()) { if (results.empty()) {
// Check if the scraper used is still valid. // Check if the scraper used is still valid.
if (!isValidConfiguredScraper()) { if (!isValidConfiguredScraper()) {
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
mWindow, getHelpStyle(),
Utils::String::toUpper("Configured scraper is no longer available.\n" Utils::String::toUpper("Configured scraper is no longer available.\n"
"Please change the scraping source in the settings."), "Please change the scraping source in the settings."),
"FINISH", mSkipCallback)); "FINISH", mSkipCallback));
@ -363,8 +374,8 @@ void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& resu
else { else {
mFoundGame = false; mFoundGame = false;
ComponentListRow row; ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, "NO GAMES FOUND", row.addElement(std::make_shared<TextComponent>(mWindow, "NO GAMES FOUND", font, color),
font, color), true); true);
if (mSkipCallback) if (mSkipCallback)
row.makeAcceptInputHandler(mSkipCallback); row.makeAcceptInputHandler(mSkipCallback);
@ -379,8 +390,10 @@ void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& resu
for (size_t i = 0; i < results.size(); i++) { for (size_t i = 0; i < results.size(); i++) {
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, row.addElement(
Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), true); std::make_shared<TextComponent>(
mWindow, Utils::String::toUpper(results.at(i).mdl.get("name")), font, color),
true);
row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); }); row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
mResultList->addRow(row); mResultList->addRow(row);
} }
@ -399,8 +412,8 @@ void GuiScraperSearch::onSearchDone(const std::vector<ScraperSearchResult>& resu
// For automatic mode, if there's no thumbnail to download or no matching games found, // For automatic mode, if there's no thumbnail to download or no matching games found,
// proceed directly or we'll get stuck forever. // proceed directly or we'll get stuck forever.
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) { if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) {
if (mScraperResults.size() == 0 || (mScraperResults.size() > 0 && if (mScraperResults.size() == 0 ||
mScraperResults.front().thumbnailImageUrl == "")) { (mScraperResults.size() > 0 && mScraperResults.front().thumbnailImageUrl == "")) {
if (mScraperResults.size() == 0) if (mScraperResults.size() == 0)
mSkipCallback(); mSkipCallback();
else else
@ -424,8 +437,8 @@ void GuiScraperSearch::onSearchError(const std::string& error, HttpReq::Status s
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", ""); LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
mRetrySearch = true; mRetrySearch = true;
mRetryCount++; mRetryCount++;
LOG(LogError) << "GuiScraperSearch: Attempting automatic retry " << mRetryCount << LOG(LogError) << "GuiScraperSearch: Attempting automatic retry " << mRetryCount << " of "
" of " << FAILED_VERIFICATION_RETRIES; << FAILED_VERIFICATION_RETRIES;
return; return;
} }
else { else {
@ -435,14 +448,15 @@ void GuiScraperSearch::onSearchError(const std::string& error, HttpReq::Status s
if (mScrapeCount > 1) { if (mScrapeCount > 1) {
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", ""); LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), Utils::String::toUpper(error), mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), Utils::String::toUpper(error),
"RETRY", std::bind(&GuiScraperSearch::search, this, mLastSearch), "RETRY",
"SKIP", mSkipCallback, std::bind(&GuiScraperSearch::search, this, mLastSearch),
"CANCEL", mCancelCallback, true)); "SKIP", mSkipCallback, "CANCEL", mCancelCallback, true));
} }
else { else {
LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", ""); LOG(LogError) << "GuiScraperSearch: " << Utils::String::replace(error, "\n", "");
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), Utils::String::toUpper(error), mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), Utils::String::toUpper(error),
"RETRY", std::bind(&GuiScraperSearch::search, this, mLastSearch), "RETRY",
std::bind(&GuiScraperSearch::search, this, mLastSearch),
"CANCEL", mCancelCallback, "", nullptr, true)); "CANCEL", mCancelCallback, "", nullptr, true));
} }
} }
@ -489,8 +503,8 @@ void GuiScraperSearch::updateInfoPane()
// Add an entry into the thumbnail map, this way we can track and download // Add an entry into the thumbnail map, this way we can track and download
// each thumbnail separately even as they're downloading while scrolling // each thumbnail separately even as they're downloading while scrolling
// through the result list. // through the result list.
mThumbnailReqMap.insert(std::pair<std::string, mThumbnailReqMap.insert(std::pair<std::string, std::unique_ptr<HttpReq>>(
std::unique_ptr<HttpReq>>(mScraperResults[i].thumbnailImageUrl, mScraperResults[i].thumbnailImageUrl,
std::unique_ptr<HttpReq>(new HttpReq(thumb)))); std::unique_ptr<HttpReq>(new HttpReq(thumb))));
} }
} }
@ -535,12 +549,17 @@ bool GuiScraperSearch::input(InputConfig* config, Input input)
return true; return true;
} }
// Refine search. // Refine the search, unless the result has already been accepted or we're in semi-automatic
if (config->isMappedTo("y", input) && input.value != 0) // mode and there are less than 2 search results.
if (!mAcceptedResult && config->isMappedTo("y", input) && input.value != 0) {
if (mSearchType != ACCEPT_SINGLE_MATCHES ||
(mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1)) {
openInputScreen(mLastSearch); openInputScreen(mLastSearch);
}
}
// Skip game. // Skip game, unless the result has already been accepted.
if (mScrapeCount > 1 && config->isMappedTo("x", input) && input.value != 0) if (!mAcceptedResult && mScrapeCount > 1 && config->isMappedTo("x", input) && input.value != 0)
mSkipCallback(); mSkipCallback();
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
@ -562,6 +581,7 @@ void GuiScraperSearch::render(const Transform4x4f& parentTrans)
void GuiScraperSearch::returnResult(ScraperSearchResult result) void GuiScraperSearch::returnResult(ScraperSearchResult result)
{ {
mBlockAccept = true; mBlockAccept = true;
mAcceptedResult = true;
// Resolve metadata image before returning. // Resolve metadata image before returning.
if (result.mediaFilesDownloadStatus != COMPLETED) { if (result.mediaFilesDownloadStatus != COMPLETED) {
@ -593,8 +613,8 @@ void GuiScraperSearch::update(int deltaTime)
// Check if the thumbnail for the currently selected game has finished downloading. // Check if the thumbnail for the currently selected game has finished downloading.
if (mScraperResults.size() > 0) { if (mScraperResults.size() > 0) {
auto it = mThumbnailReqMap.find(mScraperResults[mResultList-> auto it =
getCursorId()].thumbnailImageUrl); mThumbnailReqMap.find(mScraperResults[mResultList->getCursorId()].thumbnailImageUrl);
if (it != mThumbnailReqMap.end() && it->second->status() != HttpReq::REQ_IN_PROGRESS) if (it != mThumbnailReqMap.end() && it->second->status() != HttpReq::REQ_IN_PROGRESS)
updateThumbnail(); updateThumbnail();
} }
@ -683,11 +703,12 @@ void GuiScraperSearch::update(int deltaTime)
if (mScrapeResult.mediaFilesDownloadStatus == COMPLETED && if (mScrapeResult.mediaFilesDownloadStatus == COMPLETED &&
Settings::getInstance()->getBool("MiximageGenerate")) { Settings::getInstance()->getBool("MiximageGenerate")) {
std::string currentMiximage = mLastSearch.game->getMiximagePath(); std::string currentMiximage = mLastSearch.game->getMiximagePath();
if (currentMiximage == "" || (currentMiximage != "" && if (currentMiximage == "" ||
(currentMiximage != "" &&
Settings::getInstance()->getBool("MiximageOverwrite"))) { Settings::getInstance()->getBool("MiximageOverwrite"))) {
mMiximageGenerator = std::make_unique<MiximageGenerator>(mLastSearch.game, mMiximageGenerator =
mResultMessage); std::make_unique<MiximageGenerator>(mLastSearch.game, mResultMessage);
// The promise/future mechanism is used as signaling for the thread to // The promise/future mechanism is used as signaling for the thread to
// indicate that processing has been completed. The reason to run a separate // indicate that processing has been completed. The reason to run a separate
@ -697,8 +718,9 @@ void GuiScraperSearch::update(int deltaTime)
std::promise<bool>().swap(mGeneratorPromise); std::promise<bool>().swap(mGeneratorPromise);
mGeneratorFuture = mGeneratorPromise.get_future(); mGeneratorFuture = mGeneratorPromise.get_future();
mMiximageGeneratorThread = std::thread(&MiximageGenerator::startThread, mMiximageGeneratorThread =
mMiximageGenerator.get(), &mGeneratorPromise); std::thread(&MiximageGenerator::startThread, mMiximageGenerator.get(),
&mGeneratorPromise);
} }
else { else {
returnResult(mScrapeResult); returnResult(mScrapeResult);
@ -760,12 +782,12 @@ void GuiScraperSearch::updateThumbnail()
void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
{ {
auto searchForFunc = [&](const std::string& name) { auto searchForFunc = [&](const std::string& name) {
stop();
mRefinedSearch = true; mRefinedSearch = true;
params.nameOverride = name; params.nameOverride = name;
search(params); search(params);
}; };
stop();
mRetryCount = 0; mRetryCount = 0;
std::string searchString; std::string searchString;
@ -778,8 +800,8 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
} }
else { else {
// If searching based on the actual file name, then expand to the full game name // If searching based on the actual file name, then expand to the full game name
// in case the scraper is set to TheGamesDB and it's an arcade game. This is required // in case the scraper is set to TheGamesDB and it's an arcade game. This is
// as TheGamesDB has issues with searches using the short MAME names. // required as TheGamesDB does not support searches using the short MAME names.
if (params.game->isArcadeGame() && if (params.game->isArcadeGame() &&
Settings::getInstance()->getString("Scraper") == "thegamesdb") Settings::getInstance()->getString("Scraper") == "thegamesdb")
searchString = MameNames::getInstance()->getCleanName(params.game->getCleanName()); searchString = MameNames::getInstance()->getCleanName(params.game->getCleanName());
@ -791,12 +813,13 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
searchString = params.nameOverride; searchString = params.nameOverride;
} }
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString,
searchString, searchForFunc, false, "SEARCH", "APPLY CHANGES?")); searchForFunc, false, "SEARCH", "APPLY CHANGES?"));
} }
bool GuiScraperSearch::saveMetadata( bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result,
const ScraperSearchResult& result, MetaDataList& metadata, FileData* scrapedGame) MetaDataList& metadata,
FileData* scrapedGame)
{ {
bool metadataUpdated = false; bool metadataUpdated = false;
bool hasDefaultName = false; bool hasDefaultName = false;
@ -897,13 +920,3 @@ HelpStyle GuiScraperSearch::getHelpStyle()
style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system"); style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system");
return style; return style;
} }
void GuiScraperSearch::onFocusGained()
{
mGrid.onFocusGained();
}
void GuiScraperSearch::onFocusLost()
{
mGrid.onFocusLost();
}

View file

@ -16,11 +16,11 @@
#ifndef ES_APP_GUIS_GUI_SCRAPER_SEARCH_H #ifndef ES_APP_GUIS_GUI_SCRAPER_SEARCH_H
#define ES_APP_GUIS_GUI_SCRAPER_SEARCH_H #define ES_APP_GUIS_GUI_SCRAPER_SEARCH_H
#include "GuiComponent.h"
#include "MiximageGenerator.h"
#include "components/BusyComponent.h" #include "components/BusyComponent.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
#include "GuiComponent.h"
#include "MiximageGenerator.h"
#include <future> #include <future>
#include <thread> #include <thread>
@ -36,9 +36,9 @@ class GuiScraperSearch : public GuiComponent
{ {
public: public:
enum SearchType { enum SearchType {
ALWAYS_ACCEPT_FIRST_RESULT, ALWAYS_ACCEPT_FIRST_RESULT, // Automatic mode.
ACCEPT_SINGLE_MATCHES, ACCEPT_SINGLE_MATCHES, // Semi-automatic mode.
NEVER_AUTO_ACCEPT NEVER_AUTO_ACCEPT // Manual mode.
}; };
GuiScraperSearch(Window* window, SearchType searchType, unsigned int scrapeCount = 1); GuiScraperSearch(Window* window, SearchType searchType, unsigned int scrapeCount = 1);
@ -47,20 +47,31 @@ public:
void search(const ScraperSearchParams& params); void search(const ScraperSearchParams& params);
void openInputScreen(ScraperSearchParams& from); void openInputScreen(ScraperSearchParams& from);
void stop(); void stop();
inline SearchType getSearchType() const { return mSearchType; } int getScraperResultsSize() { return static_cast<int>(mScraperResults.size()); }
bool getAcceptedResult() { return mAcceptedResult; }
SearchType getSearchType() const { return mSearchType; }
bool getSavedNewMedia() bool getSavedNewMedia()
{ return (mMDResolveHandle ? mMDResolveHandle->getSavedNewMedia() : false); }; {
return (mMDResolveHandle ? mMDResolveHandle->getSavedNewMedia() : false);
}
static bool saveMetadata(const ScraperSearchResult& result, static bool saveMetadata(const ScraperSearchResult& result,
MetaDataList& metadata, FileData* scrapedGame); MetaDataList& metadata,
FileData* scrapedGame);
// Metadata assets will be resolved before calling the accept callback // Metadata assets will be resolved before calling the accept callback.
// (e.g. result.mdl's "image" is automatically downloaded and properly set). void setAcceptCallback(const std::function<void(const ScraperSearchResult&)>& acceptCallback)
inline void setAcceptCallback(const std::function<void(const ScraperSearchResult&)>& {
acceptCallback) { mAcceptCallback = acceptCallback; } mAcceptCallback = acceptCallback;
inline void setSkipCallback(const std::function<void()>& }
skipCallback) { mSkipCallback = skipCallback; }; void setSkipCallback(const std::function<void()>& skipCallback)
inline void setCancelCallback(const std::function<void()>& {
cancelCallback) { mScrapeCount -= 1; mCancelCallback = cancelCallback; } mSkipCallback = skipCallback;
}
void setCancelCallback(const std::function<void()>& cancelCallback)
{
mScrapeCount -= 1;
mCancelCallback = cancelCallback;
}
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;
@ -68,20 +79,19 @@ public:
std::vector<HelpPrompt> getHelpPrompts() override; std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override; HelpStyle getHelpStyle() override;
void onSizeChanged() override; void onSizeChanged() override;
void onFocusGained() override;
void onFocusLost() override;
void unsetRefinedSearch() { mRefinedSearch = false; } void unsetRefinedSearch() { mRefinedSearch = false; }
void onFocusGained() override { mGrid.onFocusGained(); }
void onFocusLost() override { mGrid.onFocusLost(); }
private: private:
void updateViewStyle(); void updateViewStyle();
void updateThumbnail(); void updateThumbnail();
void updateInfoPane(); void updateInfoPane();
void resizeMetadata(); void resizeMetadata();
void onSearchError(const std::string& error, HttpReq::Status status = void onSearchError(const std::string& error,
HttpReq::REQ_UNDEFINED_ERROR); HttpReq::Status status = HttpReq::REQ_UNDEFINED_ERROR);
void onSearchDone(const std::vector<ScraperSearchResult>& results); void onSearchDone(const std::vector<ScraperSearchResult>& results);
int getSelectedIndex(); int getSelectedIndex();
@ -117,8 +127,13 @@ private:
bool resize; bool resize;
MetaDataPair(const std::shared_ptr<TextComponent>& f, MetaDataPair(const std::shared_ptr<TextComponent>& f,
const std::shared_ptr<GuiComponent>& s, bool r = true) const std::shared_ptr<GuiComponent>& s,
: first(f), second(s), resize(r) {}; bool r = true)
: first(f)
, second(s)
, resize(r)
{
}
}; };
std::vector<MetaDataPair> mMD_Pairs; std::vector<MetaDataPair> mMD_Pairs;
@ -132,6 +147,7 @@ private:
unsigned int mScrapeCount; unsigned int mScrapeCount;
bool mRefinedSearch; bool mRefinedSearch;
bool mBlockAccept; bool mBlockAccept;
bool mAcceptedResult;
bool mFoundGame; bool mFoundGame;
bool mScrapeRatings; bool mScrapeRatings;

View file

@ -9,54 +9,55 @@
#include "guis/GuiScreensaverOptions.h" #include "guis/GuiScreensaverOptions.h"
#include "Settings.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "components/SliderComponent.h" #include "components/SliderComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "Settings.h"
GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string& title) GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string& title)
: GuiSettings(window, title) : GuiSettings(window, title)
{ {
// Screensaver timer. // Screensaver timer.
auto screensaver_timer = std::make_shared<SliderComponent>(mWindow, 0.f, 30.f, 1.f, "m"); auto screensaver_timer = std::make_shared<SliderComponent>(mWindow, 0.0f, 30.0f, 1.0f, "m");
screensaver_timer->setValue(static_cast<float>(Settings::getInstance()-> screensaver_timer->setValue(
getInt("ScreensaverTimer") / (1000 * 60))); static_cast<float>(Settings::getInstance()->getInt("ScreensaverTimer") / (1000 * 60)));
addWithLabel("START SCREENSAVER AFTER (MINUTES)", screensaver_timer); addWithLabel("START SCREENSAVER AFTER (MINUTES)", screensaver_timer);
addSaveFunc([screensaver_timer, this] { addSaveFunc([screensaver_timer, this] {
if (static_cast<int>(std::round(screensaver_timer->getValue()) * (1000 * 60)) != if (static_cast<int>(std::round(screensaver_timer->getValue()) * (1000 * 60)) !=
Settings::getInstance()->getInt("ScreensaverTimer")) { Settings::getInstance()->getInt("ScreensaverTimer")) {
Settings::getInstance()->setInt("ScreensaverTimer", Settings::getInstance()->setInt(
"ScreensaverTimer",
static_cast<int>(std::round(screensaver_timer->getValue()) * (1000 * 60))); static_cast<int>(std::round(screensaver_timer->getValue()) * (1000 * 60)));
setNeedsSaving(); setNeedsSaving();
} }
}); });
// Screensaver type. // Screensaver type.
auto screensaver_type = std::make_shared<OptionListComponent<std::string>> auto screensaver_type = std::make_shared<OptionListComponent<std::string>>(
(mWindow, getHelpStyle(), "SCREENSAVER TYPE", false); mWindow, getHelpStyle(), "SCREENSAVER TYPE", false);
std::vector<std::string> screensavers; std::vector<std::string> screensavers;
screensavers.push_back("dim"); screensavers.push_back("dim");
screensavers.push_back("black"); screensavers.push_back("black");
screensavers.push_back("slideshow"); screensavers.push_back("slideshow");
screensavers.push_back("video"); screensavers.push_back("video");
for (auto it = screensavers.cbegin(); it != screensavers.cend(); it++) for (auto it = screensavers.cbegin(); it != screensavers.cend(); it++)
screensaver_type->add(*it, *it, Settings::getInstance()-> screensaver_type->add(*it, *it,
getString("ScreensaverType") == *it); Settings::getInstance()->getString("ScreensaverType") == *it);
addWithLabel("SCREENSAVER TYPE", screensaver_type); addWithLabel("SCREENSAVER TYPE", screensaver_type);
addSaveFunc([screensaver_type, this] { addSaveFunc([screensaver_type, this] {
if (screensaver_type->getSelected() != if (screensaver_type->getSelected() !=
Settings::getInstance()->getString("ScreensaverType")) { Settings::getInstance()->getString("ScreensaverType")) {
if (screensaver_type->getSelected() == "video") { if (screensaver_type->getSelected() == "video") {
// If before it wasn't risky but now there's a risk of problems, show warning. // If before it wasn't risky but now there's a risk of problems, show warning.
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
mWindow, getHelpStyle(),
"THE 'VIDEO' SCREENSAVER SHOWS\nVIDEOS FROM YOUR GAMELISTS\n\n" "THE 'VIDEO' SCREENSAVER SHOWS\nVIDEOS FROM YOUR GAMELISTS\n\n"
"IF YOU DO NOT HAVE ANY VIDEOS, THE\n" "IF YOU DO NOT HAVE ANY VIDEOS, THE\n"
"SCREENSAVER WILL DEFAULT TO 'DIM'", "SCREENSAVER WILL DEFAULT TO 'DIM'",
"OK", [] { return; }, "", nullptr, "", nullptr)); "OK", [] { return; }, "", nullptr, "", nullptr));
} }
Settings::getInstance()->setString("ScreensaverType", Settings::getInstance()->setString("ScreensaverType", screensaver_type->getSelected());
screensaver_type->getSelected());
setNeedsSaving(); setNeedsSaving();
} }
}); });
@ -77,19 +78,21 @@ GuiScreensaverOptions::GuiScreensaverOptions(Window* window, const std::string&
// Show filtered menu. // Show filtered menu.
ComponentListRow row; ComponentListRow row;
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, row.addElement(std::make_shared<TextComponent>(mWindow, "SLIDESHOW SCREENSAVER SETTINGS",
"SLIDESHOW SCREENSAVER SETTINGS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(makeArrow(mWindow), false); row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind( row.makeAcceptInputHandler(
&GuiScreensaverOptions::openSlideshowScreensaverOptions, this)); std::bind(&GuiScreensaverOptions::openSlideshowScreensaverOptions, this));
addRow(row); addRow(row);
row.elements.clear(); row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, row.addElement(std::make_shared<TextComponent>(mWindow, "VIDEO SCREENSAVER SETTINGS",
"VIDEO SCREENSAVER SETTINGS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); Font::get(FONT_SIZE_MEDIUM), 0x777777FF),
true);
row.addElement(makeArrow(mWindow), false); row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind( row.makeAcceptInputHandler(
&GuiScreensaverOptions::openVideoScreensaverOptions, this)); std::bind(&GuiScreensaverOptions::openVideoScreensaverOptions, this));
addRow(row); addRow(row);
} }
@ -99,25 +102,25 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
// Timer for swapping images (in seconds). // Timer for swapping images (in seconds).
auto screensaver_swap_image_timeout = auto screensaver_swap_image_timeout =
std::make_shared<SliderComponent>(mWindow, 2.f, 120.f, 2.f, "s"); std::make_shared<SliderComponent>(mWindow, 2.0f, 120.0f, 2.0f, "s");
screensaver_swap_image_timeout->setValue(static_cast<float>(Settings::getInstance()-> screensaver_swap_image_timeout->setValue(static_cast<float>(
getInt("ScreensaverSwapImageTimeout") / (1000))); Settings::getInstance()->getInt("ScreensaverSwapImageTimeout") / (1000)));
s->addWithLabel("SWAP IMAGES AFTER (SECONDS)", screensaver_swap_image_timeout); s->addWithLabel("SWAP IMAGES AFTER (SECONDS)", screensaver_swap_image_timeout);
s->addSaveFunc([screensaver_swap_image_timeout, s] { s->addSaveFunc([screensaver_swap_image_timeout, s] {
if (screensaver_swap_image_timeout->getValue() != if (screensaver_swap_image_timeout->getValue() !=
static_cast<float>(Settings::getInstance()-> static_cast<float>(Settings::getInstance()->getInt("ScreensaverSwapImageTimeout") /
getInt("ScreensaverSwapImageTimeout") / (1000))) { (1000))) {
Settings::getInstance()->setInt("ScreensaverSwapImageTimeout", Settings::getInstance()->setInt(
static_cast<int>(std::round(screensaver_swap_image_timeout->getValue()) * "ScreensaverSwapImageTimeout",
(1000))); static_cast<int>(std::round(screensaver_swap_image_timeout->getValue()) * (1000)));
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
// Stretch images to screen resolution. // Stretch images to screen resolution.
auto screensaver_stretch_images = std::make_shared<SwitchComponent>(mWindow); auto screensaver_stretch_images = std::make_shared<SwitchComponent>(mWindow);
screensaver_stretch_images-> screensaver_stretch_images->setState(
setState(Settings::getInstance()->getBool("ScreensaverStretchImages")); Settings::getInstance()->getBool("ScreensaverStretchImages"));
s->addWithLabel("STRETCH IMAGES TO SCREEN RESOLUTION", screensaver_stretch_images); s->addWithLabel("STRETCH IMAGES TO SCREEN RESOLUTION", screensaver_stretch_images);
s->addSaveFunc([screensaver_stretch_images, s] { s->addSaveFunc([screensaver_stretch_images, s] {
if (screensaver_stretch_images->getState() != if (screensaver_stretch_images->getState() !=
@ -130,8 +133,8 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
// Show game info overlay for slideshow screensaver. // Show game info overlay for slideshow screensaver.
auto screensaver_slideshow_game_info = std::make_shared<SwitchComponent>(mWindow); auto screensaver_slideshow_game_info = std::make_shared<SwitchComponent>(mWindow);
screensaver_slideshow_game_info-> screensaver_slideshow_game_info->setState(
setState(Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo")); Settings::getInstance()->getBool("ScreensaverSlideshowGameInfo"));
s->addWithLabel("DISPLAY GAME INFO OVERLAY", screensaver_slideshow_game_info); s->addWithLabel("DISPLAY GAME INFO OVERLAY", screensaver_slideshow_game_info);
s->addSaveFunc([screensaver_slideshow_game_info, s] { s->addSaveFunc([screensaver_slideshow_game_info, s] {
if (screensaver_slideshow_game_info->getState() != if (screensaver_slideshow_game_info->getState() !=
@ -145,8 +148,8 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
#if defined(USE_OPENGL_21) #if defined(USE_OPENGL_21)
// Render scanlines using a shader. // Render scanlines using a shader.
auto screensaver_slideshow_scanlines = std::make_shared<SwitchComponent>(mWindow); auto screensaver_slideshow_scanlines = std::make_shared<SwitchComponent>(mWindow);
screensaver_slideshow_scanlines-> screensaver_slideshow_scanlines->setState(
setState(Settings::getInstance()->getBool("ScreensaverSlideshowScanlines")); Settings::getInstance()->getBool("ScreensaverSlideshowScanlines"));
s->addWithLabel("RENDER SCANLINES", screensaver_slideshow_scanlines); s->addWithLabel("RENDER SCANLINES", screensaver_slideshow_scanlines);
s->addSaveFunc([screensaver_slideshow_scanlines, s] { s->addSaveFunc([screensaver_slideshow_scanlines, s] {
if (screensaver_slideshow_scanlines->getState() != if (screensaver_slideshow_scanlines->getState() !=
@ -160,8 +163,8 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
// Whether to use custom images. // Whether to use custom images.
auto screensaver_slideshow_custom_images = std::make_shared<SwitchComponent>(mWindow); auto screensaver_slideshow_custom_images = std::make_shared<SwitchComponent>(mWindow);
screensaver_slideshow_custom_images->setState(Settings::getInstance()-> screensaver_slideshow_custom_images->setState(
getBool("ScreensaverSlideshowCustomImages")); Settings::getInstance()->getBool("ScreensaverSlideshowCustomImages"));
s->addWithLabel("USE CUSTOM IMAGES", screensaver_slideshow_custom_images); s->addWithLabel("USE CUSTOM IMAGES", screensaver_slideshow_custom_images);
s->addSaveFunc([screensaver_slideshow_custom_images, s] { s->addSaveFunc([screensaver_slideshow_custom_images, s] {
if (screensaver_slideshow_custom_images->getState() != if (screensaver_slideshow_custom_images->getState() !=
@ -174,8 +177,8 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
// Whether to recurse the custom image directory. // Whether to recurse the custom image directory.
auto screensaver_slideshow_recurse = std::make_shared<SwitchComponent>(mWindow); auto screensaver_slideshow_recurse = std::make_shared<SwitchComponent>(mWindow);
screensaver_slideshow_recurse->setState(Settings::getInstance()-> screensaver_slideshow_recurse->setState(
getBool("ScreensaverSlideshowRecurse")); Settings::getInstance()->getBool("ScreensaverSlideshowRecurse"));
s->addWithLabel("CUSTOM IMAGE DIRECTORY RECURSIVE SEARCH", screensaver_slideshow_recurse); s->addWithLabel("CUSTOM IMAGE DIRECTORY RECURSIVE SEARCH", screensaver_slideshow_recurse);
s->addSaveFunc([screensaver_slideshow_recurse, s] { s->addSaveFunc([screensaver_slideshow_recurse, s] {
if (screensaver_slideshow_recurse->getState() != if (screensaver_slideshow_recurse->getState() !=
@ -187,9 +190,10 @@ void GuiScreensaverOptions::openSlideshowScreensaverOptions()
}); });
// Custom image directory. // Custom image directory.
auto screensaver_slideshow_image_dir = std::make_shared<TextComponent>(mWindow, "", auto screensaver_slideshow_image_dir = std::make_shared<TextComponent>(
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_RIGHT); mWindow, "", Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_RIGHT);
s->addEditableTextComponent("CUSTOM IMAGE DIRECTORY", screensaver_slideshow_image_dir, s->addEditableTextComponent(
"CUSTOM IMAGE DIRECTORY", screensaver_slideshow_image_dir,
Settings::getInstance()->getString("ScreensaverSlideshowImageDir"), Settings::getInstance()->getString("ScreensaverSlideshowImageDir"),
Settings::getInstance()->getDefaultString("ScreensaverSlideshowImageDir")); Settings::getInstance()->getDefaultString("ScreensaverSlideshowImageDir"));
s->addSaveFunc([screensaver_slideshow_image_dir, s] { s->addSaveFunc([screensaver_slideshow_image_dir, s] {
@ -210,25 +214,25 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
// Timer for swapping videos (in seconds). // Timer for swapping videos (in seconds).
auto screensaver_swap_video_timeout = auto screensaver_swap_video_timeout =
std::make_shared<SliderComponent>(mWindow, 0.f, 120.f, 2.f, "s"); std::make_shared<SliderComponent>(mWindow, 0.0f, 120.0f, 2.0f, "s");
screensaver_swap_video_timeout->setValue(static_cast<float>(Settings::getInstance()-> screensaver_swap_video_timeout->setValue(static_cast<float>(
getInt("ScreensaverSwapVideoTimeout") / (1000))); Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") / (1000)));
s->addWithLabel("SWAP VIDEOS AFTER (SECONDS)", screensaver_swap_video_timeout); s->addWithLabel("SWAP VIDEOS AFTER (SECONDS)", screensaver_swap_video_timeout);
s->addSaveFunc([screensaver_swap_video_timeout, s] { s->addSaveFunc([screensaver_swap_video_timeout, s] {
if (screensaver_swap_video_timeout->getValue() != if (screensaver_swap_video_timeout->getValue() !=
static_cast<float>(Settings::getInstance()-> static_cast<float>(Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") /
getInt("ScreensaverSwapVideoTimeout") / (1000))) { (1000))) {
Settings::getInstance()->setInt("ScreensaverSwapVideoTimeout", Settings::getInstance()->setInt(
static_cast<int>(std::round(screensaver_swap_video_timeout->getValue()) * "ScreensaverSwapVideoTimeout",
(1000))); static_cast<int>(std::round(screensaver_swap_video_timeout->getValue()) * (1000)));
s->setNeedsSaving(); s->setNeedsSaving();
} }
}); });
// Stretch videos to screen resolution. // Stretch videos to screen resolution.
auto screensaver_stretch_videos = std::make_shared<SwitchComponent>(mWindow); auto screensaver_stretch_videos = std::make_shared<SwitchComponent>(mWindow);
screensaver_stretch_videos-> screensaver_stretch_videos->setState(
setState(Settings::getInstance()->getBool("ScreensaverStretchVideos")); Settings::getInstance()->getBool("ScreensaverStretchVideos"));
s->addWithLabel("STRETCH VIDEOS TO SCREEN RESOLUTION", screensaver_stretch_videos); s->addWithLabel("STRETCH VIDEOS TO SCREEN RESOLUTION", screensaver_stretch_videos);
s->addSaveFunc([screensaver_stretch_videos, s] { s->addSaveFunc([screensaver_stretch_videos, s] {
if (screensaver_stretch_videos->getState() != if (screensaver_stretch_videos->getState() !=
@ -241,8 +245,8 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
// Show game info overlay for video screensaver. // Show game info overlay for video screensaver.
auto screensaver_video_game_info = std::make_shared<SwitchComponent>(mWindow); auto screensaver_video_game_info = std::make_shared<SwitchComponent>(mWindow);
screensaver_video_game_info-> screensaver_video_game_info->setState(
setState(Settings::getInstance()->getBool("ScreensaverVideoGameInfo")); Settings::getInstance()->getBool("ScreensaverVideoGameInfo"));
s->addWithLabel("DISPLAY GAME INFO OVERLAY", screensaver_video_game_info); s->addWithLabel("DISPLAY GAME INFO OVERLAY", screensaver_video_game_info);
s->addSaveFunc([screensaver_video_game_info, s] { s->addSaveFunc([screensaver_video_game_info, s] {
if (screensaver_video_game_info->getState() != if (screensaver_video_game_info->getState() !=
@ -253,26 +257,11 @@ void GuiScreensaverOptions::openVideoScreensaverOptions()
} }
}); });
#if defined(_RPI_)
// Use OMX player for screensaver.
auto screensaver_omx_player = std::make_shared<SwitchComponent>(mWindow);
screensaver_omx_player->setState(Settings::getInstance()->getBool("ScreensaverOmxPlayer"));
s->addWithLabel("USE OMX PLAYER FOR SCREENSAVER", screensaver_omx_player);
s->addSaveFunc([screensaver_omx_player, s] {
if (screensaver_omx_player->getState() !=
Settings::getInstance()->getBool("ScreensaverOmxPlayer")) {
Settings::getInstance()->
setBool("ScreensaverOmxPlayer", screensaver_omx_player->getState());
s->setNeedsSaving();
}
});
#endif
#if defined(USE_OPENGL_21) #if defined(USE_OPENGL_21)
// Render scanlines using a shader. // Render scanlines using a shader.
auto screensaver_video_scanlines = std::make_shared<SwitchComponent>(mWindow); auto screensaver_video_scanlines = std::make_shared<SwitchComponent>(mWindow);
screensaver_video_scanlines-> screensaver_video_scanlines->setState(
setState(Settings::getInstance()->getBool("ScreensaverVideoScanlines")); Settings::getInstance()->getBool("ScreensaverVideoScanlines"));
s->addWithLabel("RENDER SCANLINES", screensaver_video_scanlines); s->addWithLabel("RENDER SCANLINES", screensaver_video_scanlines);
s->addSaveFunc([screensaver_video_scanlines, s] { s->addSaveFunc([screensaver_video_scanlines, s] {
if (screensaver_video_scanlines->getState() != if (screensaver_video_scanlines->getState() !=

View file

@ -10,33 +10,31 @@
#include "guis/GuiSettings.h" #include "guis/GuiSettings.h"
#include "components/HelpComponent.h"
#include "guis/GuiTextEditPopup.h"
#include "views/gamelist/IGameListView.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "Window.h" #include "Window.h"
#include "components/HelpComponent.h"
#include "guis/GuiTextEditPopup.h"
#include "views/ViewController.h"
#include "views/gamelist/IGameListView.h"
GuiSettings::GuiSettings( GuiSettings::GuiSettings(Window* window, std::string title)
Window* window, : GuiComponent(window)
std::string title) , mMenu(window, title)
: GuiComponent(window), , mNeedsSaving(false)
mMenu(window, title), , mNeedsReloadHelpPrompts(false)
mNeedsSaving(false), , mNeedsCollectionsUpdate(false)
mNeedsReloadHelpPrompts(false), , mNeedsSorting(false)
mNeedsCollectionsUpdate(false), , mNeedsSortingCollections(false)
mNeedsSorting(false), , mNeedsResetFilters(false)
mNeedsSortingCollections(false), , mNeedsReloading(false)
mNeedsResetFilters(false), , mNeedsGoToStart(false)
mNeedsReloading(false), , mNeedsGoToSystem(false)
mNeedsGoToStart(false), , mNeedsGoToGroupedCollections(false)
mNeedsGoToSystem(false), , mInvalidateCachedBackground(false)
mNeedsGoToGroupedCollections(false), , mGoToSystem(nullptr)
mInvalidateCachedBackground(false),
mGoToSystem(nullptr)
{ {
addChild(&mMenu); addChild(&mMenu);
mMenu.addButton("BACK", "back", [this] { delete this; }); mMenu.addButton("BACK", "back", [this] { delete this; });
@ -49,6 +47,7 @@ GuiSettings::GuiSettings(
GuiSettings::~GuiSettings() GuiSettings::~GuiSettings()
{ {
// Save on exit.
save(); save();
} }
@ -72,11 +71,11 @@ void GuiSettings::save()
} }
if (mNeedsSorting) { if (mNeedsSorting) {
for (auto it = SystemData::sSystemVector.cbegin(); it != for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend();
SystemData::sSystemVector.cend(); it++) { it++) {
if (!(!mNeedsSortingCollections && (*it)->isCollection())) { if (!(!mNeedsSortingCollections && (*it)->isCollection()))
(*it)->sortSystem(true); (*it)->sortSystem(true);
}
// Jump to the first row of the gamelist. // Jump to the first row of the gamelist.
IGameListView* gameList = ViewController::get()->getGameListView((*it)).get(); IGameListView* gameList = ViewController::get()->getGameListView((*it)).get();
gameList->setCursor(gameList->getFirstEntry()); gameList->setCursor(gameList->getFirstEntry());
@ -84,11 +83,10 @@ void GuiSettings::save()
} }
if (mNeedsResetFilters) { if (mNeedsResetFilters) {
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
if ((*it)->getThemeFolder() == "custom-collections") { if ((*it)->getThemeFolder() == "custom-collections") {
for (FileData* customSystem : for (FileData* customSystem : (*it)->getRootFolder()->getChildrenListToDisplay())
(*it)->getRootFolder()->getChildrenListToDisplay())
customSystem->getSystem()->getIndex()->resetFilters(); customSystem->getSystem()->getIndex()->resetFilters();
} }
(*it)->getIndex()->resetFilters(); (*it)->getIndex()->resetFilters();
@ -152,8 +150,7 @@ void GuiSettings::save()
} }
} }
void GuiSettings::addEditableTextComponent( void GuiSettings::addEditableTextComponent(const std::string label,
const std::string label,
std::shared_ptr<GuiComponent> ed, std::shared_ptr<GuiComponent> ed,
std::string value, std::string value,
std::string defaultValue, std::string defaultValue,
@ -200,11 +197,11 @@ void GuiSettings::addEditableTextComponent(
row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] {
// Never display the value if it's a password, instead set it to blank. // Never display the value if it's a password, instead set it to blank.
if (isPassword) if (isPassword)
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, mWindow->pushGui(
"", updateVal, false)); new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal, false));
else else
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, ed->getValue(),
ed->getValue(), updateVal, false)); updateVal, false));
}); });
assert(ed); assert(ed);
addRow(row); addRow(row);
@ -219,15 +216,6 @@ bool GuiSettings::input(InputConfig* config, Input input)
return true; return true;
} }
// Keep code for potential future use.
// if (config->isMappedTo("start", input) && input.value != 0) {
// // Close everything.
// Window* window = mWindow;
// while (window->peekGui() && window->peekGui() != ViewController::get())
// delete window->peekGui();
// return true;
// }
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }

View file

@ -11,8 +11,8 @@
#ifndef ES_APP_GUIS_GUI_SETTINGS_H #ifndef ES_APP_GUIS_GUI_SETTINGS_H
#define ES_APP_GUIS_GUI_SETTINGS_H #define ES_APP_GUIS_GUI_SETTINGS_H
#include "components/MenuComponent.h"
#include "SystemData.h" #include "SystemData.h"
#include "components/MenuComponent.h"
// This is just a really simple template for a GUI that calls some save functions when closed. // This is just a really simple template for a GUI that calls some save functions when closed.
class GuiSettings : public GuiComponent class GuiSettings : public GuiComponent
@ -22,29 +22,33 @@ public:
virtual ~GuiSettings(); virtual ~GuiSettings();
void save(); void save();
inline void addRow(const ComponentListRow& row) { mMenu.addRow(row); }; void addRow(const ComponentListRow& row) { mMenu.addRow(row); }
inline void addWithLabel(const std::string& label, void addWithLabel(const std::string& label, const std::shared_ptr<GuiComponent>& comp)
const std::shared_ptr<GuiComponent>& comp) { mMenu.addWithLabel(label, comp); }; {
void addEditableTextComponent( mMenu.addWithLabel(label, comp);
const std::string label, }
void addEditableTextComponent(const std::string label,
std::shared_ptr<GuiComponent> ed, std::shared_ptr<GuiComponent> ed,
std::string value, std::string value,
std::string defaultValue = "", std::string defaultValue = "",
bool isPassword = false); bool isPassword = false);
inline void addSaveFunc(const std::function<void()>& func) { mSaveFuncs.push_back(func); }; void addSaveFunc(const std::function<void()>& func) { mSaveFuncs.push_back(func); }
void setNeedsSaving(bool state = true) { mNeedsSaving = state; }; void setNeedsSaving(bool state = true) { mNeedsSaving = state; }
void setNeedsReloadHelpPrompts() { mNeedsReloadHelpPrompts = true; }; void setNeedsReloadHelpPrompts() { mNeedsReloadHelpPrompts = true; }
void setNeedsCollectionsUpdate() { mNeedsCollectionsUpdate = true; }; void setNeedsCollectionsUpdate() { mNeedsCollectionsUpdate = true; }
void setNeedsSorting() { mNeedsSorting = true; }; void setNeedsSorting() { mNeedsSorting = true; }
void setNeedsSortingCollections() { mNeedsSortingCollections = true; }; void setNeedsSortingCollections() { mNeedsSortingCollections = true; }
void setNeedsResetFilters() { mNeedsResetFilters = true; } void setNeedsResetFilters() { mNeedsResetFilters = true; }
void setNeedsReloading() { mNeedsReloading = true; }; void setNeedsReloading() { mNeedsReloading = true; }
void setNeedsGoToStart() { mNeedsGoToStart = true; }; void setNeedsGoToStart() { mNeedsGoToStart = true; }
void setNeedsGoToSystem(SystemData* goToSystem) void setNeedsGoToSystem(SystemData* goToSystem)
{ mNeedsGoToSystem = true; mGoToSystem = goToSystem; }; {
void setNeedsGoToGroupedCollections() { mNeedsGoToGroupedCollections = true; }; mNeedsGoToSystem = true;
void setInvalidateCachedBackground() { mInvalidateCachedBackground = true; }; mGoToSystem = goToSystem;
};
void setNeedsGoToGroupedCollections() { mNeedsGoToGroupedCollections = true; }
void setInvalidateCachedBackground() { mInvalidateCachedBackground = true; }
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
std::vector<HelpPrompt> getHelpPrompts() override; std::vector<HelpPrompt> getHelpPrompts() override;
@ -53,6 +57,8 @@ public:
private: private:
MenuComponent mMenu; MenuComponent mMenu;
std::vector<std::function<void()>> mSaveFuncs; std::vector<std::function<void()>> mSaveFuncs;
SystemData* mGoToSystem;
bool mNeedsSaving; bool mNeedsSaving;
bool mNeedsReloadHelpPrompts; bool mNeedsReloadHelpPrompts;
bool mNeedsCollectionsUpdate; bool mNeedsCollectionsUpdate;
@ -64,8 +70,6 @@ private:
bool mNeedsGoToSystem; bool mNeedsGoToSystem;
bool mNeedsGoToGroupedCollections; bool mNeedsGoToGroupedCollections;
bool mInvalidateCachedBackground; bool mInvalidateCachedBackground;
SystemData* mGoToSystem;
}; };
#endif // ES_APP_GUIS_GUI_SETTINGS_H #endif // ES_APP_GUIS_GUI_SETTINGS_H

View file

@ -1,15 +1,14 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// EmulationStation Desktop Edition, an emulator front-end // EmulationStation Desktop Edition (ES-DE) is a front-end for browsing
// with controller navigation and theming support. // and launching games from your multi-platform game collection.
// //
// Originally created by Alec "Aloshi" Lofquist. // Originally created by Alec Lofquist.
// http://www.aloshi.com
// Improved and extended by the RetroPie community. // Improved and extended by the RetroPie community.
// Desktop Edition fork by Leon Styhre. // Desktop Edition fork by Leon Styhre.
// //
// The line length limit is 100 characters and the indentations are 4 spaces. // The column limit is 100 characters.
// Line breaks are Unix-style (line feed only). // All ES-DE C++ source code is formatted using clang-format.
// //
// main.cpp // main.cpp
// //
@ -18,13 +17,6 @@
// environment and starts listening to SDL events. // environment and starts listening to SDL events.
// //
#include "guis/GuiDetectDevice.h"
#include "guis/GuiMsgBox.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiLaunchScreen.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "EmulationStation.h" #include "EmulationStation.h"
@ -37,6 +29,13 @@
#include "Sound.h" #include "Sound.h"
#include "SystemData.h" #include "SystemData.h"
#include "SystemScreensaver.h" #include "SystemScreensaver.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiDetectDevice.h"
#include "guis/GuiLaunchScreen.h"
#include "guis/GuiMsgBox.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <SDL2/SDL_main.h> #include <SDL2/SDL_main.h>
@ -56,14 +55,14 @@ bool forceInputConfig = false;
bool settingsNeedSaving = false; bool settingsNeedSaving = false;
enum loadSystemsReturnCode { enum loadSystemsReturnCode {
LOADING_OK, LOADING_OK, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
INVALID_FILE, INVALID_FILE,
NO_ROMS NO_ROMS
}; };
#if defined(_WIN64) #if defined(_WIN64)
enum win64ConsoleType { enum win64ConsoleType {
NO_CONSOLE, NO_CONSOLE, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
PARENT_CONSOLE, PARENT_CONSOLE,
ALLOCATED_CONSOLE ALLOCATED_CONSOLE
}; };
@ -195,8 +194,8 @@ bool parseArgs(int argc, char* argv[])
return false; return false;
} }
if (Utils::FileSystem::isRegularFile(argv[i + 1])) { if (Utils::FileSystem::isRegularFile(argv[i + 1])) {
std::cerr << "Error: Home path \'" << argv[i + 1] << std::cerr << "Error: Home path \'" << argv[i + 1]
"\' is a file and not a directory\n"; << "\' is a file and not a directory\n";
return false; return false;
} }
Utils::FileSystem::setHomePath(argv[i + 1]); Utils::FileSystem::setHomePath(argv[i + 1]);
@ -236,8 +235,8 @@ bool parseArgs(int argc, char* argv[])
int height = atoi(argv[i + 2]); int height = atoi(argv[i + 2]);
if (width < 640 || height < 480 || width > 7680 || height > 4320 || if (width < 640 || height < 480 || width > 7680 || height > 4320 ||
height < width / 4 || width < height / 2) { height < width / 4 || width < height / 2) {
std::cerr << "Error: Unsupported resolution " std::cerr << "Error: Unsupported resolution " << width << "x" << height
<< width << "x" << height << " supplied.\n"; << " supplied.\n";
return false; return false;
} }
Settings::getInstance()->setInt("WindowWidth", width); Settings::getInstance()->setInt("WindowWidth", width);
@ -277,6 +276,7 @@ bool parseArgs(int argc, char* argv[])
} }
// On Unix, enable settings for the fullscreen mode. // On Unix, enable settings for the fullscreen mode.
// On macOS and Windows only windowed mode is supported. // On macOS and Windows only windowed mode is supported.
#if defined(__unix__) #if defined(__unix__)
else if (strcmp(argv[i], "--windowed") == 0) { else if (strcmp(argv[i], "--windowed") == 0) {
Settings::getInstance()->setBool("Windowed", true); Settings::getInstance()->setBool("Windowed", true);
@ -350,12 +350,12 @@ bool parseArgs(int argc, char* argv[])
Log::setReportingLevel(LogDebug); Log::setReportingLevel(LogDebug);
} }
else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
std::cout << std::cout << "EmulationStation Desktop Edition v" << PROGRAM_VERSION_STRING << "\n";
"EmulationStation Desktop Edition v" << PROGRAM_VERSION_STRING << "\n";
return false; return false;
} }
else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
std::cout << std::cout <<
// clang-format off
"Usage: emulationstation [options]\n" "Usage: emulationstation [options]\n"
"EmulationStation Desktop Edition, Emulator Front-end\n\n" "EmulationStation Desktop Edition, Emulator Front-end\n\n"
"Options:\n" "Options:\n"
@ -381,6 +381,7 @@ bool parseArgs(int argc, char* argv[])
" --debug Print debug information\n" " --debug Print debug information\n"
" --version, -v Display version information\n" " --version, -v Display version information\n"
" --help, -h Summon a sentient, angry tuba\n"; " --help, -h Summon a sentient, angry tuba\n";
// clang-format on
return false; // Exit after printing help. return false; // Exit after printing help.
} }
else { else {
@ -401,11 +402,11 @@ bool checkApplicationHomeDirectory()
std::string applicationHome = home + "/.emulationstation"; std::string applicationHome = home + "/.emulationstation";
if (!Utils::FileSystem::exists(applicationHome)) { if (!Utils::FileSystem::exists(applicationHome)) {
#if defined(_WIN64) #if defined(_WIN64)
std::cout << "First startup, creating application home directory \"" << std::cout << "First startup, creating application home directory \""
Utils::String::replace(applicationHome, "/", "\\") << "\"\n"; << Utils::String::replace(applicationHome, "/", "\\") << "\"\n";
#else #else
std::cout << "First startup, creating application home directory \"" << std::cout << "First startup, creating application home directory \"" << applicationHome
applicationHome << "\"\n"; << "\"\n";
#endif #endif
Utils::FileSystem::createDirectory(applicationHome); Utils::FileSystem::createDirectory(applicationHome);
if (!Utils::FileSystem::exists(applicationHome)) { if (!Utils::FileSystem::exists(applicationHome)) {
@ -431,9 +432,9 @@ loadSystemsReturnCode loadSystemConfigFile()
return LOADING_OK; return LOADING_OK;
} }
// Called on exit, assuming we get far enough to have the log initialized.
void onExit() void onExit()
{ {
// Called on exit, assuming we get far enough to have the log initialized.
Log::close(); Log::close();
} }
@ -492,8 +493,8 @@ int main(int argc, char* argv[])
} }
#endif #endif
// Call this ONLY when linking with FreeImage as a static library.
#if defined(FREEIMAGE_LIB) #if defined(FREEIMAGE_LIB)
// Call this ONLY when linking with FreeImage as a static library.
FreeImage_Initialise(); FreeImage_Initialise();
#endif #endif
@ -504,8 +505,8 @@ int main(int argc, char* argv[])
// Start the logger. // Start the logger.
Log::init(); Log::init();
Log::open(); Log::open();
LOG(LogInfo) << "EmulationStation Desktop Edition v" << PROGRAM_VERSION_STRING << LOG(LogInfo) << "EmulationStation Desktop Edition v" << PROGRAM_VERSION_STRING << ", built "
", built " << PROGRAM_BUILT_STRING; << PROGRAM_BUILT_STRING;
// Always close the log on exit. // Always close the log on exit.
atexit(&onExit); atexit(&onExit);
@ -525,15 +526,15 @@ int main(int argc, char* argv[])
// Check if the application version has changed, which would normally mean that the // Check if the application version has changed, which would normally mean that the
// user has upgraded to a newer release. // user has upgraded to a newer release.
std::string applicationVersion; std::string applicationVersion;
if ((applicationVersion = Settings::getInstance()-> if ((applicationVersion = Settings::getInstance()->getString("ApplicationVersion")) !=
getString("ApplicationVersion")) != PROGRAM_VERSION_STRING) { PROGRAM_VERSION_STRING) {
if (applicationVersion != "") { if (applicationVersion != "") {
LOG(LogInfo) << "Application version changed from previous startup, from \"" << LOG(LogInfo) << "Application version changed from previous startup, from \""
applicationVersion << "\" to \"" << PROGRAM_VERSION_STRING << "\""; << applicationVersion << "\" to \"" << PROGRAM_VERSION_STRING << "\"";
} }
else { else {
LOG(LogInfo) << "Application version setting is blank, changing it to \"" << LOG(LogInfo) << "Application version setting is blank, changing it to \""
PROGRAM_VERSION_STRING << "\""; << PROGRAM_VERSION_STRING << "\"";
} }
Settings::getInstance()->setString("ApplicationVersion", PROGRAM_VERSION_STRING); Settings::getInstance()->setString("ApplicationVersion", PROGRAM_VERSION_STRING);
Settings::getInstance()->saveFile(); Settings::getInstance()->saveFile();
@ -582,9 +583,9 @@ int main(int argc, char* argv[])
if (event.type == SDL_QUIT) if (event.type == SDL_QUIT)
return 1; return 1;
#if !defined(__APPLE__)
// This hides the mouse cursor during startup, i.e. before we have begun to capture SDL events. // This hides the mouse cursor during startup, i.e. before we have begun to capture SDL events.
// On macOS this causes the mouse cursor to jump back to the Dock so don't do it on this OS. // On macOS this causes the mouse cursor to jump back to the Dock so don't do it on this OS.
#if !defined(__APPLE__)
SDL_SetRelativeMouseMode(SDL_TRUE); SDL_SetRelativeMouseMode(SDL_TRUE);
#endif #endif
@ -625,8 +626,8 @@ int main(int argc, char* argv[])
// Open the input configuration GUI if the flag to force this was passed from the command line. // Open the input configuration GUI if the flag to force this was passed from the command line.
if (!loadSystemsStatus) { if (!loadSystemsStatus) {
if (forceInputConfig) { if (forceInputConfig) {
window.pushGui(new GuiDetectDevice(&window, false, true, [] { window.pushGui(new GuiDetectDevice(&window, false, true,
ViewController::get()->goToStart(); })); [] { ViewController::get()->goToStart(); }));
} }
else { else {
ViewController::get()->goToStart(); ViewController::get()->goToStart();
@ -639,14 +640,17 @@ int main(int argc, char* argv[])
int lastTime = SDL_GetTicks(); int lastTime = SDL_GetTicks();
const auto applicationEndTime = std::chrono::system_clock::now(); const auto applicationEndTime = std::chrono::system_clock::now();
LOG(LogInfo) << "Application startup time: " << LOG(LogInfo) << "Application startup time: "
std::chrono::duration_cast<std::chrono::milliseconds> << std::chrono::duration_cast<std::chrono::milliseconds>(applicationEndTime -
(applicationEndTime - applicationStartTime).count() << " ms"; applicationStartTime)
.count()
<< " ms";
bool running = true; bool running = true;
#if !defined(__APPLE__)
// Now that we've finished loading, disable the relative mouse mode or otherwise mouse // Now that we've finished loading, disable the relative mouse mode or otherwise mouse
// input wouldn't work in any games that are launched. // input wouldn't work in any games that are launched.
#if !defined(__APPLE__)
SDL_SetRelativeMouseMode(SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE);
#endif #endif
@ -657,8 +661,8 @@ int main(int argc, char* argv[])
if (event.type == SDL_QUIT) if (event.type == SDL_QUIT)
running = false; running = false;
}
while (SDL_PollEvent(&event)); } while (SDL_PollEvent(&event));
} }
if (window.isSleeping()) { if (window.isSleeping()) {
@ -694,8 +698,8 @@ int main(int argc, char* argv[])
NavigationSounds::getInstance()->deinit(); NavigationSounds::getInstance()->deinit();
Settings::deinit(); Settings::deinit();
// Call this ONLY when linking with FreeImage as a static library.
#if defined(FREEIMAGE_LIB) #if defined(FREEIMAGE_LIB)
// Call this ONLY when linking with FreeImage as a static library.
FreeImage_DeInitialise(); FreeImage_DeInitialise();
#endif #endif

View file

@ -10,20 +10,20 @@
#include "scrapers/GamesDBJSONScraper.h" #include "scrapers/GamesDBJSONScraper.h"
#include "scrapers/GamesDBJSONScraperResources.h" #include "scrapers/GamesDBJSONScraperResources.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "FileData.h" #include "FileData.h"
#include "Log.h" #include "Log.h"
#include "MameNames.h" #include "MameNames.h"
#include "PlatformId.h" #include "PlatformId.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <exception> #include <exception>
#include <map> #include <map>
#include <pugixml.hpp> #include <pugixml.hpp>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
using namespace PlatformIds; using namespace PlatformIds;
using namespace rapidjson; using namespace rapidjson;
@ -118,7 +118,8 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map {
{ TANDY_TRS80, "4941" }, { TANDY_TRS80, "4941" },
}; };
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params, void thegamesdb_generate_json_scraper_requests(
const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results) std::vector<ScraperSearchResult>& results)
{ {
@ -168,7 +169,7 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
if (!platforms.empty()) { if (!platforms.empty()) {
bool first = true; bool first = true;
platformQueryParam += "&filter%5Bplatform%5D="; platformQueryParam += "&filter%5Bplatform%5D=";
for (auto platformIt = platforms.cbegin(); for (auto platformIt = platforms.cbegin(); // Line break.
platformIt != platforms.cend(); platformIt++) { platformIt != platforms.cend(); platformIt++) {
auto mapIt = gamesdb_new_platformid_map.find(*platformIt); auto mapIt = gamesdb_new_platformid_map.find(*platformIt);
if (mapIt != gamesdb_new_platformid_map.cend()) { if (mapIt != gamesdb_new_platformid_map.cend()) {
@ -178,8 +179,9 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
first = false; first = false;
} }
else { else {
LOG(LogWarning) << "TheGamesDB scraper: No support for platform \"" << LOG(LogWarning)
getPlatformName(*platformIt) << "\", search will be inaccurate"; << "TheGamesDB scraper: No support for platform \""
<< getPlatformName(*platformIt) << "\", search will be inaccurate";
} }
} }
path += platformQueryParam; path += platformQueryParam;
@ -188,8 +190,8 @@ void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params
LOG(LogWarning) << "TheGamesDB scraper: No platform defined, search will be inaccurate"; LOG(LogWarning) << "TheGamesDB scraper: No platform defined, search will be inaccurate";
} }
requests.push(std::unique_ptr<ScraperRequest> requests.push(
(new TheGamesDBJSONRequest(requests, results, path))); std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(requests, results, path)));
} }
} }
@ -204,13 +206,12 @@ void thegamesdb_generate_json_scraper_requests(
path += "/Games/Images/GamesImages?" + apiKey + "&games_id=" + gameIDs; path += "/Games/Images/GamesImages?" + apiKey + "&games_id=" + gameIDs;
requests.push(std::unique_ptr<ScraperRequest> requests.push(
(new TheGamesDBJSONRequest(requests, results, path))); std::unique_ptr<ScraperRequest>(new TheGamesDBJSONRequest(requests, results, path)));
} }
namespace namespace
{ {
std::string getStringOrThrow(const Value& v, const std::string& key) std::string getStringOrThrow(const Value& v, const std::string& key)
{ {
if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsString()) { if (!v.HasMember(key.c_str()) || !v[key.c_str()].IsString()) {
@ -319,34 +320,34 @@ void processGame(const Value& game, std::vector<ScraperSearchResult>& results)
if (game.HasMember("release_date") && game["release_date"].IsString()) { if (game.HasMember("release_date") && game["release_date"].IsString()) {
result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime( result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(
game["release_date"].GetString(), "%Y-%m-%d"))); game["release_date"].GetString(), "%Y-%m-%d")));
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Release Date (unparsed): " << LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Release Date (unparsed): "
game["release_date"].GetString(); << game["release_date"].GetString();
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Release Date (parsed): " << LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Release Date (parsed): "
result.mdl.get("releasedate"); << result.mdl.get("releasedate");
} }
if (game.HasMember("developers") && game["developers"].IsArray()) { if (game.HasMember("developers") && game["developers"].IsArray()) {
result.mdl.set("developer", getDeveloperString(game["developers"])); result.mdl.set("developer", getDeveloperString(game["developers"]));
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Developer: " << LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Developer: "
result.mdl.get("developer"); << result.mdl.get("developer");
} }
if (game.HasMember("publishers") && game["publishers"].IsArray()) { if (game.HasMember("publishers") && game["publishers"].IsArray()) {
result.mdl.set("publisher", getPublisherString(game["publishers"])); result.mdl.set("publisher", getPublisherString(game["publishers"]));
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Publisher: " << LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Publisher: "
result.mdl.get("publisher"); << result.mdl.get("publisher");
} }
if (game.HasMember("genres") && game["genres"].IsArray()) { if (game.HasMember("genres") && game["genres"].IsArray()) {
result.mdl.set("genre", getGenreString(game["genres"])); result.mdl.set("genre", getGenreString(game["genres"]));
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Genre: " << LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Genre: "
result.mdl.get("genre"); << result.mdl.get("genre");
} }
if (game.HasMember("players") && game["players"].IsInt()) { if (game.HasMember("players") && game["players"].IsInt()) {
result.mdl.set("players", std::to_string(game["players"].GetInt())); result.mdl.set("players", std::to_string(game["players"].GetInt()));
LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Players: " << LOG(LogDebug) << "GamesDBJSONScraper::processGame(): Players: "
result.mdl.get("players"); << result.mdl.get("players");
} }
result.mediaURLFetch = NOT_STARTED; result.mediaURLFetch = NOT_STARTED;
@ -354,7 +355,8 @@ void processGame(const Value& game, std::vector<ScraperSearchResult>& results)
} }
} // namespace } // namespace
void processMediaURLs(const Value& images, const std::string& base_url, void processMediaURLs(const Value& images,
const std::string& base_url,
std::vector<ScraperSearchResult>& results) std::vector<ScraperSearchResult>& results)
{ {
ScraperSearchResult result; ScraperSearchResult result;
@ -367,9 +369,8 @@ void processMediaURLs(const Value& images, const std::string& base_url,
result.marqueeUrl = ""; result.marqueeUrl = "";
result.screenshotUrl = ""; result.screenshotUrl = "";
// Quite excessive testing for valid values, but you never know // Quite excessive testing for valid values, but you never know what the server has
// what the server has returned and we don't want to crash the // returned and we don't want to crash the program due to malformed data.
// program due to malformed data.
if (gameMedia.IsArray()) { if (gameMedia.IsArray()) {
for (SizeType i = 0; i < gameMedia.Size(); i++) { for (SizeType i = 0; i < gameMedia.Size(); i++) {
std::string mediatype; std::string mediatype;
@ -404,8 +405,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
doc.Parse(req->getContent().c_str()); doc.Parse(req->getContent().c_str());
if (doc.HasParseError()) { if (doc.HasParseError()) {
std::string err = std::string err = std::string("TheGamesDBJSONRequest - Error parsing JSON \n\t") +
std::string("TheGamesDBJSONRequest - Error parsing JSON \n\t") +
GetParseError_En(doc.GetParseError()); GetParseError_En(doc.GetParseError());
setError(err); setError(err);
LOG(LogError) << err; LOG(LogError) << err;
@ -440,12 +440,11 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
if (doc.HasMember("remaining_monthly_allowance") && doc.HasMember("extra_allowance")) { if (doc.HasMember("remaining_monthly_allowance") && doc.HasMember("extra_allowance")) {
for (auto i = 0; i < results.size(); i++) { for (auto i = 0; i < results.size(); i++) {
results[i].scraperRequestAllowance = results[i].scraperRequestAllowance =
doc["remaining_monthly_allowance"].GetInt() + doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt();
doc["extra_allowance"].GetInt();
} }
LOG(LogDebug) << "TheGamesDBJSONRequest::process(): " LOG(LogDebug) << "TheGamesDBJSONRequest::process(): "
"Remaining monthly scraping allowance: " << "Remaining monthly scraping allowance: "
results.back().scraperRequestAllowance; << results.back().scraperRequestAllowance;
} }
return; return;
} }

View file

@ -12,15 +12,18 @@
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
namespace pugi { namespace pugi
{
class xml_document; class xml_document;
} }
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params, void thegamesdb_generate_json_scraper_requests(
const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results); std::vector<ScraperSearchResult>& results);
void thegamesdb_generate_json_scraper_requests(const std::string& gameIDs, void thegamesdb_generate_json_scraper_requests(
const std::string& gameIDs,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results); std::vector<ScraperSearchResult>& results);
@ -28,17 +31,17 @@ class TheGamesDBJSONRequest : public ScraperHttpRequest
{ {
public: public:
// Constructor for a GetGameList request. // Constructor for a GetGameList request.
TheGamesDBJSONRequest( TheGamesDBJSONRequest(std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
std::vector<ScraperSearchResult>& resultsWrite, std::vector<ScraperSearchResult>& resultsWrite,
const std::string& url) const std::string& url)
: ScraperHttpRequest(resultsWrite, url), : ScraperHttpRequest(resultsWrite, url)
mRequestQueue(&requestsWrite) , mRequestQueue(&requestsWrite)
{ {
} }
// Constructior for a GetGame request // Constructior for a GetGame request
TheGamesDBJSONRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url) TheGamesDBJSONRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
: ScraperHttpRequest(resultsWrite, url), mRequestQueue(nullptr) : ScraperHttpRequest(resultsWrite, url)
, mRequestQueue(nullptr)
{ {
} }

View file

@ -14,19 +14,20 @@
#include "scrapers/GamesDBJSONScraperResources.h" #include "scrapers/GamesDBJSONScraperResources.h"
#include "utils/FileSystemUtil.h"
#include "Log.h" #include "Log.h"
#include "utils/FileSystemUtil.h"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <chrono> #include <chrono>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <thread> #include <thread>
using namespace rapidjson; using namespace rapidjson;
namespace { namespace
{
constexpr char GamesDBAPIKey[] = constexpr char GamesDBAPIKey[] =
"445fcbc3f32bb2474bc27016b99eb963d318ee3a608212c543b9a79de1041600"; "445fcbc3f32bb2474bc27016b99eb963d318ee3a608212c543b9a79de1041600";
@ -58,8 +59,8 @@ void ensureScrapersResourcesDir()
std::string getScrapersResouceDir() std::string getScrapersResouceDir()
{ {
return Utils::FileSystem::getGenericPath( return Utils::FileSystem::getGenericPath(Utils::FileSystem::getHomePath() +
Utils::FileSystem::getHomePath() + "/.emulationstation/" + SCRAPER_RESOURCES_DIR); "/.emulationstation/" + SCRAPER_RESOURCES_DIR);
} }
std::string TheGamesDBJSONRequestResources::getApiKey() const { return GamesDBAPIKey; } std::string TheGamesDBJSONRequestResources::getApiKey() const { return GamesDBAPIKey; }
@ -69,16 +70,16 @@ void TheGamesDBJSONRequestResources::prepare()
if (checkLoaded()) if (checkLoaded())
return; return;
if (loadResource(gamesdb_new_developers_map, "developers", if (loadResource(gamesdb_new_developers_map, "developers", genFilePath(DEVELOPERS_JSON_FILE)) &&
genFilePath(DEVELOPERS_JSON_FILE)) && !gamesdb_developers_resource_request) !gamesdb_developers_resource_request)
gamesdb_developers_resource_request = fetchResource(DEVELOPERS_ENDPOINT); gamesdb_developers_resource_request = fetchResource(DEVELOPERS_ENDPOINT);
if (loadResource(gamesdb_new_publishers_map, "publishers", if (loadResource(gamesdb_new_publishers_map, "publishers", genFilePath(PUBLISHERS_JSON_FILE)) &&
genFilePath(PUBLISHERS_JSON_FILE)) && !gamesdb_publishers_resource_request) !gamesdb_publishers_resource_request)
gamesdb_publishers_resource_request = fetchResource(PUBLISHERS_ENDPOINT); gamesdb_publishers_resource_request = fetchResource(PUBLISHERS_ENDPOINT);
if (loadResource(gamesdb_new_genres_map, "genres", if (loadResource(gamesdb_new_genres_map, "genres", genFilePath(GENRES_JSON_FILE)) &&
genFilePath(GENRES_JSON_FILE)) && !gamesdb_genres_resource_request) !gamesdb_genres_resource_request)
gamesdb_genres_resource_request = fetchResource(GENRES_ENDPOINT); gamesdb_genres_resource_request = fetchResource(GENRES_ENDPOINT);
} }
@ -90,17 +91,18 @@ void TheGamesDBJSONRequestResources::ensureResources()
for (int i = 0; i < MAX_WAIT_ITER; i++) { for (int i = 0; i < MAX_WAIT_ITER; i++) {
if (gamesdb_developers_resource_request && if (gamesdb_developers_resource_request &&
saveResource(gamesdb_developers_resource_request.get(), saveResource(gamesdb_developers_resource_request.get(), gamesdb_new_developers_map,
gamesdb_new_developers_map, "developers", genFilePath(DEVELOPERS_JSON_FILE))) "developers", genFilePath(DEVELOPERS_JSON_FILE)))
gamesdb_developers_resource_request.reset(nullptr); gamesdb_developers_resource_request.reset(nullptr);
if (gamesdb_publishers_resource_request && if (gamesdb_publishers_resource_request &&
saveResource(gamesdb_publishers_resource_request.get(), saveResource(gamesdb_publishers_resource_request.get(), gamesdb_new_publishers_map,
gamesdb_new_publishers_map, "publishers", genFilePath(PUBLISHERS_JSON_FILE))) "publishers", genFilePath(PUBLISHERS_JSON_FILE)))
gamesdb_publishers_resource_request.reset(nullptr); gamesdb_publishers_resource_request.reset(nullptr);
if (gamesdb_genres_resource_request && saveResource(gamesdb_genres_resource_request.get(), if (gamesdb_genres_resource_request &&
gamesdb_new_genres_map, "genres", genFilePath(GENRES_JSON_FILE))) saveResource(gamesdb_genres_resource_request.get(), gamesdb_new_genres_map, "genres",
genFilePath(GENRES_JSON_FILE)))
gamesdb_genres_resource_request.reset(nullptr); gamesdb_genres_resource_request.reset(nullptr);
if (!gamesdb_developers_resource_request && !gamesdb_publishers_resource_request && if (!gamesdb_developers_resource_request && !gamesdb_publishers_resource_request &&
@ -118,8 +120,7 @@ bool TheGamesDBJSONRequestResources::checkLoaded()
!gamesdb_new_publishers_map.empty(); !gamesdb_new_publishers_map.empty();
} }
bool TheGamesDBJSONRequestResources::saveResource( bool TheGamesDBJSONRequestResources::saveResource(HttpReq* req,
HttpReq* req,
std::unordered_map<int, std::string>& resource, std::unordered_map<int, std::string>& resource,
const std::string& resource_name, const std::string& resource_name,
const std::string& file_name) const std::string& file_name)
@ -133,8 +134,8 @@ bool TheGamesDBJSONRequestResources::saveResource(
return false; // Not ready: wait some more. return false; // Not ready: wait some more.
} }
if (req->status() != HttpReq::REQ_SUCCESS) { if (req->status() != HttpReq::REQ_SUCCESS) {
LOG(LogError) << "Resource request for " << file_name << LOG(LogError) << "Resource request for " << file_name << " failed:\n\t"
" failed:\n\t" << req->getErrorMsg(); << req->getErrorMsg();
return true; // Request failed, resetting request.. return true; // Request failed, resetting request..
} }
@ -156,8 +157,7 @@ std::unique_ptr<HttpReq> TheGamesDBJSONRequestResources::fetchResource(const std
return std::unique_ptr<HttpReq>(new HttpReq(path)); return std::unique_ptr<HttpReq>(new HttpReq(path));
} }
int TheGamesDBJSONRequestResources::loadResource( int TheGamesDBJSONRequestResources::loadResource(std::unordered_map<int, std::string>& resource,
std::unordered_map<int, std::string>& resource,
const std::string& resource_name, const std::string& resource_name,
const std::string& file_name) const std::string& file_name)
{ {
@ -172,8 +172,8 @@ int TheGamesDBJSONRequestResources::loadResource(
if (doc.HasParseError()) { if (doc.HasParseError()) {
std::string err = std::string("TheGamesDBJSONRequest - " std::string err = std::string("TheGamesDBJSONRequest - "
"Error parsing JSON for resource file ") + file_name + "Error parsing JSON for resource file ") +
":\n\t" + GetParseError_En(doc.GetParseError()); file_name + ":\n\t" + GetParseError_En(doc.GetParseError());
LOG(LogError) << err; LOG(LogError) << err;
return 1; return 1;
} }

View file

@ -36,15 +36,13 @@ struct TheGamesDBJSONRequestResources {
private: private:
bool checkLoaded(); bool checkLoaded();
bool saveResource( bool saveResource(HttpReq* req,
HttpReq* req,
std::unordered_map<int, std::string>& resource, std::unordered_map<int, std::string>& resource,
const std::string& resource_name, const std::string& resource_name,
const std::string& file_name); const std::string& file_name);
std::unique_ptr<HttpReq> fetchResource(const std::string& endpoint); std::unique_ptr<HttpReq> fetchResource(const std::string& endpoint);
int loadResource( int loadResource(std::unordered_map<int, std::string>& resource,
std::unordered_map<int, std::string>& resource,
const std::string& resource_name, const std::string& resource_name,
const std::string& file_name); const std::string& file_name);

View file

@ -10,20 +10,20 @@
#include "scrapers/Scraper.h" #include "scrapers/Scraper.h"
#include "utils/StringUtil.h"
#include "FileData.h" #include "FileData.h"
#include "GamesDBJSONScraper.h" #include "GamesDBJSONScraper.h"
#include "Log.h" #include "Log.h"
#include "ScreenScraper.h" #include "ScreenScraper.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/StringUtil.h"
#if defined(_WIN64) #if defined(_WIN64)
#include "views/ViewController.h" #include "views/ViewController.h"
#endif #endif
#include <cmath>
#include <FreeImage.h> #include <FreeImage.h>
#include <cmath>
#include <fstream> #include <fstream>
const std::map<std::string, generate_scraper_requests_func> scraper_request_funcs { const std::map<std::string, generate_scraper_requests_func> scraper_request_funcs {
@ -41,9 +41,9 @@ std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParam
LOG(LogError) << "Configured scraper (" << name << ") unavailable, scraping aborted"; LOG(LogError) << "Configured scraper (" << name << ") unavailable, scraping aborted";
} }
else { else {
LOG(LogDebug) << "Scraper::startScraperSearch(): Scraping system \"" << LOG(LogDebug) << "Scraper::startScraperSearch(): Scraping system \""
params.system->getName() << "\", game file \"" << << params.system->getName() << "\", game file \""
params.game->getFileName() << "\""; << params.game->getFileName() << "\"";
scraper_request_funcs.at(name)(params, handle->mRequestQueue, handle->mResults); scraper_request_funcs.at(name)(params, handle->mRequestQueue, handle->mResults);
} }
@ -84,12 +84,6 @@ bool isValidConfiguredScraper()
return scraper_request_funcs.find(name) != scraper_request_funcs.end(); return scraper_request_funcs.find(name) != scraper_request_funcs.end();
} }
// ScraperSearchHandle.
ScraperSearchHandle::ScraperSearchHandle()
{
setStatus(ASYNC_IN_PROGRESS);
}
void ScraperSearchHandle::update() void ScraperSearchHandle::update()
{ {
if (mStatus == ASYNC_DONE) if (mStatus == ASYNC_DONE)
@ -134,7 +128,8 @@ ScraperRequest::ScraperRequest(std::vector<ScraperSearchResult>& resultsWrite)
// ScraperHttpRequest. // ScraperHttpRequest.
ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>& resultsWrite, ScraperHttpRequest::ScraperHttpRequest(std::vector<ScraperSearchResult>& resultsWrite,
const std::string& url) : ScraperRequest(resultsWrite) const std::string& url)
: ScraperRequest(resultsWrite)
{ {
setStatus(ASYNC_IN_PROGRESS); setStatus(ASYNC_IN_PROGRESS);
mReq = std::unique_ptr<HttpReq>(new HttpReq(url)); mReq = std::unique_ptr<HttpReq>(new HttpReq(url));
@ -168,7 +163,8 @@ std::unique_ptr<MDResolveHandle> resolveMetaDataAssets(const ScraperSearchResult
} }
MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
const ScraperSearchParams& search) : mResult(result) const ScraperSearchParams& search)
: mResult(result)
{ {
struct mediaFileInfoStruct { struct mediaFileInfoStruct {
std::string fileURL; std::string fileURL;
@ -253,8 +249,7 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
// If the image is cached already as the thumbnail, then we don't need // If the image is cached already as the thumbnail, then we don't need
// to download it again, in this case just save it to disk and resize it. // to download it again, in this case just save it to disk and resize it.
if (mResult.thumbnailImageUrl == it->fileURL && if (mResult.thumbnailImageUrl == it->fileURL && mResult.thumbnailImageData.size() > 0) {
mResult.thumbnailImageData.size() > 0) {
// This is just a temporary workaround to avoid saving media files to disk that // This is just a temporary workaround to avoid saving media files to disk that
// are actually just containing error messages from the scraper service. The // are actually just containing error messages from the scraper service. The
@ -267,8 +262,8 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
if (Settings::getInstance()->getBool("ScraperHaltOnInvalidMedia") && if (Settings::getInstance()->getBool("ScraperHaltOnInvalidMedia") &&
mResult.thumbnailImageData.size() < 350) { mResult.thumbnailImageData.size() < 350) {
FIMEMORY* memoryStream = FreeImage_OpenMemory( FIMEMORY* memoryStream =
reinterpret_cast<BYTE*>(&mResult.thumbnailImageData.at(0)), FreeImage_OpenMemory(reinterpret_cast<BYTE*>(&mResult.thumbnailImageData.at(0)),
static_cast<DWORD>(mResult.thumbnailImageData.size())); static_cast<DWORD>(mResult.thumbnailImageData.size()));
FREE_IMAGE_FORMAT imageFormat = FreeImage_GetFileTypeFromMemory(memoryStream, 0); FREE_IMAGE_FORMAT imageFormat = FreeImage_GetFileTypeFromMemory(memoryStream, 0);
@ -294,8 +289,8 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
if (!Utils::FileSystem::isDirectory(Utils::FileSystem::getParent(filePath))) { if (!Utils::FileSystem::isDirectory(Utils::FileSystem::getParent(filePath))) {
setError("Media directory does not exist and can't be created. " setError("Media directory does not exist and can't be created. "
"Permission problems?"); "Permission problems?");
LOG(LogError) << "Couldn't create media directory: \"" << LOG(LogError) << "Couldn't create media directory: \""
Utils::FileSystem::getParent(filePath) << "\""; << Utils::FileSystem::getParent(filePath) << "\"";
return; return;
} }
@ -331,7 +326,8 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
// If it's not cached, then initiate the download. // If it's not cached, then initiate the download.
else { else {
mFuncs.push_back(ResolvePair(downloadMediaAsync(it->fileURL, filePath, mFuncs.push_back(ResolvePair(downloadMediaAsync(it->fileURL, filePath,
it->existingMediaFile, it->subDirectory, it->resizeFile, mResult.savedNewMedia), it->existingMediaFile, it->subDirectory,
it->resizeFile, mResult.savedNewMedia),
[this, filePath] {})); [this, filePath] {}));
} }
} }
@ -361,8 +357,7 @@ void MDResolveHandle::update()
setStatus(ASYNC_DONE); setStatus(ASYNC_DONE);
} }
std::unique_ptr<MediaDownloadHandle> downloadMediaAsync( std::unique_ptr<MediaDownloadHandle> downloadMediaAsync(const std::string& url,
const std::string& url,
const std::string& saveAs, const std::string& saveAs,
const std::string& existingMediaPath, const std::string& existingMediaPath,
const std::string& mediaType, const std::string& mediaType,
@ -370,26 +365,20 @@ std::unique_ptr<MediaDownloadHandle> downloadMediaAsync(
bool& savedNewMedia) bool& savedNewMedia)
{ {
return std::unique_ptr<MediaDownloadHandle>(new MediaDownloadHandle( return std::unique_ptr<MediaDownloadHandle>(new MediaDownloadHandle(
url, url, saveAs, existingMediaPath, mediaType, resizeFile, savedNewMedia));
saveAs,
existingMediaPath,
mediaType,
resizeFile,
savedNewMedia));
} }
MediaDownloadHandle::MediaDownloadHandle( MediaDownloadHandle::MediaDownloadHandle(const std::string& url,
const std::string& url,
const std::string& path, const std::string& path,
const std::string& existingMediaPath, const std::string& existingMediaPath,
const std::string& mediaType, const std::string& mediaType,
const bool resizeFile, const bool resizeFile,
bool& savedNewMedia) bool& savedNewMedia)
: mSavePath(path), : mSavePath(path)
mExistingMediaFile(existingMediaPath), , mExistingMediaFile(existingMediaPath)
mMediaType(mediaType), , mMediaType(mediaType)
mResizeFile(resizeFile), , mResizeFile(resizeFile)
mReq(new HttpReq(url)) , mReq(new HttpReq(url))
{ {
mSavedNewMediaPtr = &savedNewMedia; mSavedNewMediaPtr = &savedNewMedia;
} }
@ -427,11 +416,8 @@ void MediaDownloadHandle::update()
if (mMediaType != "videos") { if (mMediaType != "videos") {
std::string imageData = mReq->getContent(); std::string imageData = mReq->getContent();
FIMEMORY* memoryStream = FreeImage_OpenMemory(reinterpret_cast<BYTE*>(&imageData.at(0)),
FIMEMORY* memoryStream = FreeImage_OpenMemory(
reinterpret_cast<BYTE*>(&imageData.at(0)),
static_cast<DWORD>(imageData.size())); static_cast<DWORD>(imageData.size()));
imageFormat = FreeImage_GetFileTypeFromMemory(memoryStream, 0); imageFormat = FreeImage_GetFileTypeFromMemory(memoryStream, 0);
FreeImage_CloseMemory(memoryStream); FreeImage_CloseMemory(memoryStream);
} }
@ -455,8 +441,8 @@ void MediaDownloadHandle::update()
// problems or the MediaDirectory setting points to a file instead of a directory. // problems or the MediaDirectory setting points to a file instead of a directory.
if (!Utils::FileSystem::isDirectory(Utils::FileSystem::getParent(mSavePath))) { if (!Utils::FileSystem::isDirectory(Utils::FileSystem::getParent(mSavePath))) {
setError("Media directory does not exist and can't be created. Permission problems?"); setError("Media directory does not exist and can't be created. Permission problems?");
LOG(LogError) << "Couldn't create media directory: \"" << LOG(LogError) << "Couldn't create media directory: \""
Utils::FileSystem::getParent(mSavePath) << "\""; << Utils::FileSystem::getParent(mSavePath) << "\"";
return; return;
} }
@ -512,6 +498,7 @@ bool resizeImage(const std::string& path, const std::string& mediaType)
FIBITMAP* image = nullptr; FIBITMAP* image = nullptr;
// Detect the file format. // Detect the file format.
#if defined(_WIN64) #if defined(_WIN64)
format = FreeImage_GetFileTypeU(Utils::String::stringToWideString(path).c_str(), 0); format = FreeImage_GetFileTypeU(Utils::String::stringToWideString(path).c_str(), 0);
if (format == FIF_UNKNOWN) if (format == FIF_UNKNOWN)
@ -545,8 +532,8 @@ bool resizeImage(const std::string& path, const std::string& mediaType)
// If the image is smaller than (or the same size as) maxWidth and maxHeight, then don't // If the image is smaller than (or the same size as) maxWidth and maxHeight, then don't
// do any scaling. It doesn't make sense to upscale the image and waste disk space. // do any scaling. It doesn't make sense to upscale the image and waste disk space.
if (maxWidth >= width && maxHeight >= height) { if (maxWidth >= width && maxHeight >= height) {
LOG(LogDebug) << "Scraper::resizeImage(): Saving image \"" << path << LOG(LogDebug) << "Scraper::resizeImage(): Saving image \"" << path
"\" at its original resolution " << width << "x" << height; << "\" at its original resolution " << width << "x" << height;
FreeImage_Unload(image); FreeImage_Unload(image);
return true; return true;
} }
@ -596,7 +583,8 @@ bool resizeImage(const std::string& path, const std::string& mediaType)
} }
std::string getSaveAsPath(const ScraperSearchParams& params, std::string getSaveAsPath(const ScraperSearchParams& params,
const std::string& filetypeSubdirectory, const std::string& extension) const std::string& filetypeSubdirectory,
const std::string& extension)
{ {
const std::string systemsubdirectory = params.system->getName(); const std::string systemsubdirectory = params.system->getName();
const std::string name = Utils::FileSystem::getStem(params.game->getPath()); const std::string name = Utils::FileSystem::getStem(params.game->getPath());

View file

@ -27,7 +27,7 @@ class FileData;
class SystemData; class SystemData;
enum downloadStatus { enum downloadStatus {
NOT_STARTED, NOT_STARTED, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
IN_PROGRESS, IN_PROGRESS,
COMPLETED COMPLETED
}; };
@ -40,7 +40,10 @@ struct ScraperSearchParams {
}; };
struct ScraperSearchResult { struct ScraperSearchResult {
ScraperSearchResult() : mdl(GAME_METADATA) {}; ScraperSearchResult()
: mdl(GAME_METADATA)
{
}
MetaDataList mdl; MetaDataList mdl;
std::string gameID; std::string gameID;
@ -73,34 +76,6 @@ struct ScraperSearchResult {
bool savedNewMedia; bool savedNewMedia;
}; };
// So let me explain why I've abstracted this so heavily.
// There are two ways I can think of that you'd want to write a scraper.
// 1. Do some HTTP request(s) -> process it -> return the results.
// 2. Do some local filesystem queries (an offline scraper) -> return the results.
// The first way needs to be asynchronous while it's waiting for the HTTP request to return.
// The second doesn't.
// It would be nice if we could write it like this:
// search = generate_http_request(searchparams);
// wait_until_done(search);
// ... process search ...
// return results;
// We could do this if we used threads. Right now ES doesn't because I'm pretty sure I'll
// fuck it up, and I'm not sure of the performance of threads on the Pi (single-core ARM).
// We could also do this if we used coroutines.
// I can't find a really good cross-platform coroutine library (x86/64/ARM Linux + Windows),
// and I don't want to spend more time chasing libraries than just writing it the long way once.
// So, I did it the "long" way.
// ScraperSearchHandle - one logical search, e.g. "search for mario".
// ScraperRequest - encapsulates some sort of asynchronous request that will ultimately
// return some results.
// ScraperHttpRequest - implementation of ScraperRequest that waits on an HttpReq, then
// processes it with some processing function.
// A scraper search gathers results from (potentially multiple) ScraperRequests. // A scraper search gathers results from (potentially multiple) ScraperRequests.
class ScraperRequest : public AsyncHandle class ScraperRequest : public AsyncHandle
{ {
@ -133,21 +108,20 @@ private:
class ScraperSearchHandle : public AsyncHandle class ScraperSearchHandle : public AsyncHandle
{ {
public: public:
ScraperSearchHandle(); ScraperSearchHandle() { setStatus(ASYNC_IN_PROGRESS); }
void update(); void update();
inline const std::vector<ScraperSearchResult>& getResults() const const std::vector<ScraperSearchResult>& getResults() const
{ {
assert(mStatus != ASYNC_IN_PROGRESS); assert(mStatus != ASYNC_IN_PROGRESS);
return mResults; return mResults;
} }
protected: protected:
friend std::unique_ptr<ScraperSearchHandle> friend std::unique_ptr<ScraperSearchHandle> startScraperSearch(
startScraperSearch(const ScraperSearchParams& params); const ScraperSearchParams& params);
friend std::unique_ptr<ScraperSearchHandle> friend std::unique_ptr<ScraperSearchHandle> startMediaURLsFetch(const std::string& gameIDs);
startMediaURLsFetch(const std::string& gameIDs);
std::queue<std::unique_ptr<ScraperRequest>> mRequestQueue; std::queue<std::unique_ptr<ScraperRequest>> mRequestQueue;
std::vector<ScraperSearchResult> mResults; std::vector<ScraperSearchResult> mResults;
@ -164,7 +138,8 @@ std::vector<std::string> getScraperList();
// Returns true if the scraper configured in the settings is still valid. // Returns true if the scraper configured in the settings is still valid.
bool isValidConfiguredScraper(); bool isValidConfiguredScraper();
typedef void (*generate_scraper_requests_func)(const ScraperSearchParams& params, typedef void (*generate_scraper_requests_func)(
const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results); std::vector<ScraperSearchResult>& results);
@ -177,8 +152,11 @@ public:
MDResolveHandle(const ScraperSearchResult& result, const ScraperSearchParams& search); MDResolveHandle(const ScraperSearchResult& result, const ScraperSearchParams& search);
void update() override; void update() override;
inline const ScraperSearchResult& getResult() const const ScraperSearchResult& getResult() const
{ assert(mStatus == ASYNC_DONE); return mResult; } {
assert(mStatus == ASYNC_DONE);
return mResult;
}
bool getSavedNewMedia() { return mResult.savedNewMedia; } bool getSavedNewMedia() { return mResult.savedNewMedia; }
private: private:
@ -191,8 +169,7 @@ private:
class MediaDownloadHandle : public AsyncHandle class MediaDownloadHandle : public AsyncHandle
{ {
public: public:
MediaDownloadHandle( MediaDownloadHandle(const std::string& url,
const std::string& url,
const std::string& path, const std::string& path,
const std::string& existingMediaPath, const std::string& existingMediaPath,
const std::string& mediaType, const std::string& mediaType,
@ -214,10 +191,10 @@ private:
// ".emulationstation/downloaded_media/[system_name]/[media_type]/[game_name].[file_extension]". // ".emulationstation/downloaded_media/[system_name]/[media_type]/[game_name].[file_extension]".
// The subdirectories are automatically created if they do not exist. // The subdirectories are automatically created if they do not exist.
std::string getSaveAsPath(const ScraperSearchParams& params, std::string getSaveAsPath(const ScraperSearchParams& params,
const std::string& filetypeSubdirectory, const std::string& url); const std::string& filetypeSubdirectory,
const std::string& url);
std::unique_ptr<MediaDownloadHandle> downloadMediaAsync( std::unique_ptr<MediaDownloadHandle> downloadMediaAsync(const std::string& url,
const std::string& url,
const std::string& saveAs, const std::string& saveAs,
const std::string& existingMediaPath, const std::string& existingMediaPath,
const std::string& mediaType, const std::string& mediaType,

View file

@ -9,14 +9,14 @@
#include "scrapers/ScreenScraper.h" #include "scrapers/ScreenScraper.h"
#include "math/Misc.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "FileData.h" #include "FileData.h"
#include "Log.h" #include "Log.h"
#include "PlatformId.h" #include "PlatformId.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "math/Misc.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
@ -131,7 +131,8 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map {
// Helper XML parsing method, finding a node-by-name recursively. // Helper XML parsing method, finding a node-by-name recursively.
pugi::xml_node find_node_by_name_re(const pugi::xml_node& node, pugi::xml_node find_node_by_name_re(const pugi::xml_node& node,
const std::vector<std::string> node_names) { const std::vector<std::string> node_names)
{
for (const std::string& _val : node_names) { for (const std::string& _val : node_names) {
pugi::xpath_query query_node_name((static_cast<std::string>("//") + _val).c_str()); pugi::xpath_query query_node_name((static_cast<std::string>("//") + _val).c_str());
@ -147,7 +148,8 @@ pugi::xml_node find_node_by_name_re(const pugi::xml_node& node,
// Help XML parsing method, finding an direct child XML node starting from the parent and // Help XML parsing method, finding an direct child XML node starting from the parent and
// filtering by an attribute value list. // filtering by an attribute value list.
pugi::xml_node find_child_by_attribute_list(const pugi::xml_node& node_parent, pugi::xml_node find_child_by_attribute_list(const pugi::xml_node& node_parent,
const std::string& node_name, const std::string& attribute_name, const std::string& node_name,
const std::string& attribute_name,
const std::vector<std::string> attribute_values) const std::vector<std::string> attribute_values)
{ {
for (auto _val : attribute_values) { for (auto _val : attribute_values) {
@ -195,19 +197,19 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
p_ids.push_back(mapIt->second); p_ids.push_back(mapIt->second);
} }
else { else {
LOG(LogWarning) << "ScreenScraper: No support for platform \"" << LOG(LogWarning) << "ScreenScraper: No support for platform \""
getPlatformName(*platformIt) << "\", search will be inaccurate"; << getPlatformName(*platformIt) << "\", search will be inaccurate";
// Add the scrape request without a platform/system ID. // Add the scrape request without a platform/system ID.
requests.push(std::unique_ptr<ScraperRequest> requests.push(
(new ScreenScraperRequest(requests, results, path))); std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(requests, results, path)));
} }
} }
if (p_ids.size() == 0) { if (p_ids.size() == 0) {
LOG(LogWarning) << "ScreenScraper: No platform defined, search will be inaccurate"; LOG(LogWarning) << "ScreenScraper: No platform defined, search will be inaccurate";
// Add the scrape request without a platform/system ID. // Add the scrape request without a platform/system ID.
requests.push(std::unique_ptr<ScraperRequest> requests.push(
(new ScreenScraperRequest(requests, results, path))); std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(requests, results, path)));
} }
// Sort the platform IDs and remove duplicates. // Sort the platform IDs and remove duplicates.
@ -218,8 +220,8 @@ void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
for (auto platform = p_ids.cbegin(); platform != p_ids.cend(); platform++) { for (auto platform = p_ids.cbegin(); platform != p_ids.cend(); platform++) {
path += "&systemeid="; path += "&systemeid=";
path += HttpReq::urlEncode(std::to_string(*platform)); path += HttpReq::urlEncode(std::to_string(*platform));
requests.push(std::unique_ptr<ScraperRequest> requests.push(
(new ScreenScraperRequest(requests, results, path))); std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(requests, results, path)));
} }
} }
@ -284,13 +286,14 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") { Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") {
std::string userID = data.child("ssuser").child("id").text().get(); std::string userID = data.child("ssuser").child("id").text().get();
if (userID != "") { if (userID != "") {
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Scraping using account \"" << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Scraping using account \""
userID << "\""; << userID << "\"";
} }
else { else {
LOG(LogDebug) << "ScreenScraperRequest::processGame(): The configured account '" << LOG(LogDebug)
Settings::getInstance()->getString("ScraperUsernameScreenScraper") << << "ScreenScraperRequest::processGame(): The configured account '"
"' was not included in the scraper response, wrong username or password?"; << Settings::getInstance()->getString("ScraperUsernameScreenScraper")
<< "' was not included in the scraper response, wrong username or password?";
} }
} }
@ -304,9 +307,9 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
// Scraping allowance. // Scraping allowance.
if (maxRequestsPerDay > 0) { if (maxRequestsPerDay > 0) {
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Daily scraping allowance: " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Daily scraping allowance: "
requestsToday << "/" << maxRequestsPerDay << " (" << << requestsToday << "/" << maxRequestsPerDay << " ("
scraperRequestAllowance << " remaining)"; << scraperRequestAllowance << " remaining)";
} }
else { else {
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Daily scraping allowance: " LOG(LogDebug) << "ScreenScraperRequest::processGame(): Daily scraping allowance: "
@ -329,8 +332,11 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
Utils::String::toLower(Settings::getInstance()->getString("ScraperLanguage")); Utils::String::toLower(Settings::getInstance()->getString("ScraperLanguage"));
// Name fallback: US, WOR(LD). (Xpath: Data/jeu[0]/noms/nom[*]). // Name fallback: US, WOR(LD). (Xpath: Data/jeu[0]/noms/nom[*]).
result.mdl.set("name", find_child_by_attribute_list(game.child("noms"), result.mdl.set("name",
"nom", "region", { region, "wor", "us" , "ss", "eu", "jp" }).text().get()); find_child_by_attribute_list(game.child("noms"), "nom", "region",
{ region, "wor", "us", "ss", "eu", "jp" })
.text()
.get());
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Name: " << result.mdl.get("name"); LOG(LogDebug) << "ScreenScraperRequest::processGame(): Name: " << result.mdl.get("name");
// Validate rating. // Validate rating.
@ -346,14 +352,16 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
ss << ratingVal; ss << ratingVal;
if (ratingVal > 0) { if (ratingVal > 0) {
result.mdl.set("rating", ss.str()); result.mdl.set("rating", ss.str());
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Rating: " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Rating: "
result.mdl.get("rating"); << result.mdl.get("rating");
} }
} }
// Description fallback language: EN, WOR(LD). // Description fallback language: EN, WOR(LD).
std::string description = find_child_by_attribute_list(game.child("synopsis"), std::string description = find_child_by_attribute_list(game.child("synopsis"), "synopsis",
"synopsis", "langue", { language, "en", "wor" }).text().get(); "langue", { language, "en", "wor" })
.text()
.get();
// Translate some HTML character codes to UTF-8 characters. // Translate some HTML character codes to UTF-8 characters.
if (!description.empty()) { if (!description.empty()) {
@ -362,80 +370,83 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
} }
// Get the date proper. The API returns multiple 'date' children nodes to the 'dates' // Get the date proper. The API returns multiple 'date' children nodes to the 'dates'
// main child of 'jeu'. // main child of 'jeu'. Date fallback: WOR(LD), US, SS, JP, EU.
// Date fallback: WOR(LD), US, SS, JP, EU.
std::string _date = find_child_by_attribute_list(game.child("dates"), "date", "region", std::string _date = find_child_by_attribute_list(game.child("dates"), "date", "region",
{ region, "wor", "us", "ss", "jp", "eu" }).text().get(); { region, "wor", "us", "ss", "jp", "eu" })
.text()
.get();
// Date can be YYYY-MM-DD or just YYYY. // Date can be YYYY-MM-DD or just YYYY.
if (_date.length() > 4) { if (_date.length() > 4) {
result.mdl.set("releasedate", Utils::Time::DateTime( result.mdl.set("releasedate",
Utils::Time::stringToTime(_date, "%Y-%m-%d"))); Utils::Time::DateTime(Utils::Time::stringToTime(_date, "%Y-%m-%d")));
} }
else if (_date.length() > 0) { else if (_date.length() > 0) {
result.mdl.set("releasedate", Utils::Time::DateTime( result.mdl.set("releasedate",
Utils::Time::stringToTime(_date, "%Y"))); Utils::Time::DateTime(Utils::Time::stringToTime(_date, "%Y")));
} }
if (_date.length() > 0) { if (_date.length() > 0) {
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Release Date (unparsed): " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Release Date (unparsed): "
_date; << _date;
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Release Date (parsed): " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Release Date (parsed): "
result.mdl.get("releasedate"); << result.mdl.get("releasedate");
} }
// Developer for the game (Xpath: Data/jeu[0]/developpeur). // Developer for the game (Xpath: Data/jeu[0]/developpeur).
std::string developer = game.child("developpeur").text().get(); std::string developer = game.child("developpeur").text().get();
if (!developer.empty()) { if (!developer.empty()) {
result.mdl.set("developer", Utils::String::replace(developer, "&nbsp;", " ")); result.mdl.set("developer", Utils::String::replace(developer, "&nbsp;", " "));
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Developer: " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Developer: "
result.mdl.get("developer"); << result.mdl.get("developer");
} }
// Publisher for the game (Xpath: Data/jeu[0]/editeur). // Publisher for the game (Xpath: Data/jeu[0]/editeur).
std::string publisher = game.child("editeur").text().get(); std::string publisher = game.child("editeur").text().get();
if (!publisher.empty()) { if (!publisher.empty()) {
result.mdl.set("publisher", Utils::String::replace(publisher, "&nbsp;", " ")); result.mdl.set("publisher", Utils::String::replace(publisher, "&nbsp;", " "));
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Publisher: " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Publisher: "
result.mdl.get("publisher"); << result.mdl.get("publisher");
} }
// Genre fallback language: EN. (Xpath: Data/jeu[0]/genres/genre[*]). // Genre fallback language: EN. (Xpath: Data/jeu[0]/genres/genre[*]).
std::string genre = find_child_by_attribute_list(game.child("genres"), std::string genre = find_child_by_attribute_list(game.child("genres"), "genre", "langue",
"genre", "langue", { language, "en" }).text().get(); { language, "en" })
.text()
.get();
if (!genre.empty()) { if (!genre.empty()) {
result.mdl.set("genre", genre); result.mdl.set("genre", genre);
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Genre: " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Genre: "
result.mdl.get("genre"); << result.mdl.get("genre");
} }
// Players. // Players.
std::string players = game.child("joueurs").text().get(); std::string players = game.child("joueurs").text().get();
if (!players.empty()) { if (!players.empty()) {
result.mdl.set("players", players); result.mdl.set("players", players);
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Players: " << LOG(LogDebug) << "ScreenScraperRequest::processGame(): Players: "
result.mdl.get("players"); << result.mdl.get("players");
} }
// Media super-node. // Media super-node.
pugi::xml_node media_list = game.child("medias"); pugi::xml_node media_list = game.child("medias");
if (media_list) { if (media_list) {
// 3D box // 3D box.
processMedia(result, media_list, ssConfig.media_3dbox, processMedia(result, media_list, ssConfig.media_3dbox, result.box3DUrl,
result.box3DUrl, result.box3DFormat, region); result.box3DFormat, region);
// Cover // Cover.
processMedia(result, media_list, ssConfig.media_cover, processMedia(result, media_list, ssConfig.media_cover, result.coverUrl,
result.coverUrl, result.coverFormat, region); result.coverFormat, region);
// Marquee (wheel) // Marquee (wheel).
processMedia(result, media_list, ssConfig.media_marquee, processMedia(result, media_list, ssConfig.media_marquee, result.marqueeUrl,
result.marqueeUrl, result.marqueeFormat, region); result.marqueeFormat, region);
// Screenshot // Screenshot.
processMedia(result, media_list, ssConfig.media_screenshot, processMedia(result, media_list, ssConfig.media_screenshot, result.screenshotUrl,
result.screenshotUrl, result.screenshotFormat, region); result.screenshotFormat, region);
// Video // Video.
processMedia(result, media_list, ssConfig.media_video, processMedia(result, media_list, ssConfig.media_video, result.videoUrl,
result.videoUrl, result.videoFormat, region); result.videoFormat, region);
} }
result.mediaURLFetch = COMPLETED; result.mediaURLFetch = COMPLETED;
out_results.push_back(result); out_results.push_back(result);
@ -446,8 +457,7 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
} }
} }
void ScreenScraperRequest::processMedia( void ScreenScraperRequest::processMedia(ScraperSearchResult& result,
ScraperSearchResult& result,
const pugi::xml_node& media_list, const pugi::xml_node& media_list,
std::string mediaType, std::string mediaType,
std::string& fileURL, std::string& fileURL,
@ -460,8 +470,8 @@ void ScreenScraperRequest::processMedia(
// We need to do this because any child of 'medias' has the form // We need to do this because any child of 'medias' has the form
// <media type="..." region="..." format="..."> // <media type="..." region="..." format="...">
// and we need to find the right media for the region. // and we need to find the right media for the region.
pugi::xpath_node_set results = media_list.select_nodes((static_cast<std::string> pugi::xpath_node_set results = media_list.select_nodes(
("media[@type='") + mediaType + "']").c_str()); (static_cast<std::string>("media[@type='") + mediaType + "']").c_str());
if (results.size()) { if (results.size()) {
// Videos don't have any region attributes, so just take the first entry // Videos don't have any region attributes, so just take the first entry
@ -471,8 +481,8 @@ void ScreenScraperRequest::processMedia(
} }
else { else {
// Region fallback: WOR(LD), US, CUS(TOM?), JP, EU. // Region fallback: WOR(LD), US, CUS(TOM?), JP, EU.
for (auto _region : std::vector<std::string>{ for (auto _region :
region, "wor", "us", "cus", "jp", "eu" }) { std::vector<std::string> { region, "wor", "us", "cus", "jp", "eu" }) {
if (art) if (art)
break; break;
@ -498,7 +508,8 @@ void ScreenScraperRequest::processMedia(
} }
else { else {
LOG(LogDebug) << "ScreenScraperRequest::processMedia(): " LOG(LogDebug) << "ScreenScraperRequest::processMedia(): "
"Failed to find media XML node with name '" << mediaType << "'"; "Failed to find media XML node with name '"
<< mediaType << "'";
} }
} }
@ -527,11 +538,11 @@ void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc,
std::string id = game.child("id").text().get(); std::string id = game.child("id").text().get();
std::string name = game.child("nom").text().get(); std::string name = game.child("nom").text().get();
std::string platformId = game.child("systemeid").text().get(); std::string platformId = game.child("systemeid").text().get();
std::string path = ssConfig.getGameSearchUrl(name) + "&systemeid=" + std::string path =
platformId + "&gameid=" + id; ssConfig.getGameSearchUrl(name) + "&systemeid=" + platformId + "&gameid=" + id;
mRequestQueue->push(std::unique_ptr<ScraperRequest> mRequestQueue->push(
(new ScreenScraperRequest(results, path))); std::unique_ptr<ScraperRequest>(new ScreenScraperRequest(results, path)));
game = game.next_sibling("jeu"); game = game.next_sibling("jeu");
} }
@ -549,9 +560,11 @@ std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(
std::find_if(searchName.begin(), searchName.end(), [](char c) { std::find_if(searchName.begin(), searchName.end(), [](char c) {
return !std::isspace(static_cast<unsigned char>(c)); return !std::isspace(static_cast<unsigned char>(c));
})); }));
searchName.erase(std::find_if(searchName.rbegin(), searchName.rend(), [](char c) { searchName.erase(
return !std::isspace(static_cast<unsigned char>(c)); std::find_if(searchName.rbegin(), searchName.rend(),
}).base(), searchName.end()); [](char c) { return !std::isspace(static_cast<unsigned char>(c)); })
.base(),
searchName.end());
// If only whitespaces were entered as the search string, then search using a random string // If only whitespaces were entered as the search string, then search using a random string
// that will not return any results. This is a quick and dirty way to avoid french error // that will not return any results. This is a quick and dirty way to avoid french error
@ -578,9 +591,10 @@ std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(
// than four characters which would break the wide search. // than four characters which would break the wide search.
std::string trimTrailingPluses = searchName; std::string trimTrailingPluses = searchName;
trimTrailingPluses.erase(std::find_if(trimTrailingPluses.rbegin(), trimTrailingPluses.erase(std::find_if(trimTrailingPluses.rbegin(),
trimTrailingPluses.rend(), [](char c) { trimTrailingPluses.rend(),
return c != '+'; [](char c) { return c != '+'; })
}).base(), trimTrailingPluses.end()); .base(),
trimTrailingPluses.end());
if (trimTrailingPluses.size() < 4) if (trimTrailingPluses.size() < 4)
singleSearch = true; singleSearch = true;
@ -605,20 +619,18 @@ std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(
} }
if (singleSearch) { if (singleSearch) {
screenScraperURL = API_URL_BASE screenScraperURL = API_URL_BASE + "/jeuInfos.php?devid=" +
+ "/jeuInfos.php?devid=" + Utils::String::scramble(API_DEV_U, API_DEV_KEY) Utils::String::scramble(API_DEV_U, API_DEV_KEY) +
+ "&devpassword=" + Utils::String::scramble(API_DEV_P, API_DEV_KEY) "&devpassword=" + Utils::String::scramble(API_DEV_P, API_DEV_KEY) +
+ "&softname=" + HttpReq::urlEncode(API_SOFT_NAME) "&softname=" + HttpReq::urlEncode(API_SOFT_NAME) + "&output=xml" +
+ "&output=xml" "&romnom=" + HttpReq::urlEncode(searchName);
+ "&romnom=" + HttpReq::urlEncode(searchName);
} }
else { else {
screenScraperURL = API_URL_BASE screenScraperURL = API_URL_BASE + "/jeuRecherche.php?devid=" +
+ "/jeuRecherche.php?devid=" + Utils::String::scramble(API_DEV_U, API_DEV_KEY) Utils::String::scramble(API_DEV_U, API_DEV_KEY) +
+ "&devpassword=" + Utils::String::scramble(API_DEV_P, API_DEV_KEY) "&devpassword=" + Utils::String::scramble(API_DEV_P, API_DEV_KEY) +
+ "&softname=" + HttpReq::urlEncode(API_SOFT_NAME) "&softname=" + HttpReq::urlEncode(API_SOFT_NAME) + "&output=xml" +
+ "&output=xml" "&recherche=" + HttpReq::urlEncode(searchName);
+ "&recherche=" + HttpReq::urlEncode(searchName);
} }
// Username / password, if this has been setup and activated. // Username / password, if this has been setup and activated.
@ -626,8 +638,8 @@ std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl(
std::string username = Settings::getInstance()->getString("ScraperUsernameScreenScraper"); std::string username = Settings::getInstance()->getString("ScraperUsernameScreenScraper");
std::string password = Settings::getInstance()->getString("ScraperPasswordScreenScraper"); std::string password = Settings::getInstance()->getString("ScraperPasswordScreenScraper");
if (!username.empty() && !password.empty()) if (!username.empty() && !password.empty())
screenScraperURL += "&ssid=" + HttpReq::urlEncode(username) + "&sspassword=" + screenScraperURL += "&ssid=" + HttpReq::urlEncode(username) +
HttpReq::urlEncode(password); "&sspassword=" + HttpReq::urlEncode(password);
} }
return screenScraperURL; return screenScraperURL;

View file

@ -10,13 +10,15 @@
#ifndef ES_APP_SCRAPERS_SCREEN_SCRAPER_H #ifndef ES_APP_SCRAPERS_SCREEN_SCRAPER_H
#define ES_APP_SCRAPERS_SCREEN_SCRAPER_H #define ES_APP_SCRAPERS_SCREEN_SCRAPER_H
#include "scrapers/Scraper.h"
#include "EmulationStation.h" #include "EmulationStation.h"
#include "scrapers/Scraper.h"
namespace pugi { class xml_document; } namespace pugi
{
class xml_document;
}
void screenscraper_generate_scraper_requests( void screenscraper_generate_scraper_requests(const ScraperSearchParams& params,
const ScraperSearchParams& params,
std::queue<std::unique_ptr<ScraperRequest>>& requests, std::queue<std::unique_ptr<ScraperRequest>>& requests,
std::vector<ScraperSearchResult>& results); std::vector<ScraperSearchResult>& results);
@ -26,33 +28,36 @@ public:
// ctor for a GetGameList request. // ctor for a GetGameList request.
ScreenScraperRequest(std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite, ScreenScraperRequest(std::queue<std::unique_ptr<ScraperRequest>>& requestsWrite,
std::vector<ScraperSearchResult>& resultsWrite, std::vector<ScraperSearchResult>& resultsWrite,
const std::string& url) : ScraperHttpRequest(resultsWrite, url), const std::string& url)
mRequestQueue(&requestsWrite) {} : ScraperHttpRequest(resultsWrite, url)
, mRequestQueue(&requestsWrite)
{
}
// ctor for a GetGame request. // ctor for a GetGame request.
ScreenScraperRequest(std::vector<ScraperSearchResult>& resultsWrite, ScreenScraperRequest(std::vector<ScraperSearchResult>& resultsWrite, const std::string& url)
const std::string& url) : ScraperHttpRequest(resultsWrite, url), : ScraperHttpRequest(resultsWrite, url)
mRequestQueue(nullptr) {} , mRequestQueue(nullptr)
{
}
// Settings for the scraper. // Settings for the scraper.
static const struct ScreenScraperConfig { static const struct ScreenScraperConfig {
std::string getGameSearchUrl(const std::string gameName) const; std::string getGameSearchUrl(const std::string gameName) const;
// Access to the API. // Access to the API.
const std::string API_DEV_U = const std::string API_DEV_U = { 15, 21, 39, 22, 42, 40 };
{ 15, 21, 39, 22, 42, 40 }; const std::string API_DEV_P = { 32, 70, 46, 54, 12, 5, 13, 120, 50, 66, 25 };
const std::string API_DEV_P = const std::string API_DEV_KEY = { 67, 112, 72, 120, 121, 77, 119, 74, 84, 56,
{ 32, 70, 46, 54, 12, 5, 13, 120, 50, 66, 25 }; 75, 122, 78, 98, 69, 86, 56, 120, 120, 49 };
const std::string API_DEV_KEY =
{ 67, 112, 72, 120, 121, 77, 119, 74, 84, 56, 75, 122, 78, 98, 69, 86, 56, 120, 120, 49 };
const std::string API_URL_BASE = "https://www.screenscraper.fr/api2"; const std::string API_URL_BASE = "https://www.screenscraper.fr/api2";
const std::string API_SOFT_NAME = "EmulationStation-DE " + const std::string API_SOFT_NAME =
static_cast<std::string>(PROGRAM_VERSION_STRING); "EmulationStation-DE " + static_cast<std::string>(PROGRAM_VERSION_STRING);
// Which type of image artwork we need. Possible values (not a comprehensive list): // Which type of image artwork we need. Possible values (not a comprehensive list):
// - ss: in-game screenshot // - ss: in-game screenshot
// - box-3D: 3D boxart // - box-3D: 3D boxart
// - box-2D: 2D boxart (default) // - box-2D: 2D boxart
// - screenmarque : marquee // - screenmarque : marquee
// - sstitle: in-game start screenshot // - sstitle: in-game start screenshot
// - steamgrid: Steam artwork // - steamgrid: Steam artwork
@ -75,13 +80,13 @@ public:
// Which Region to use when selecting the artwork. // Which Region to use when selecting the artwork.
// Applies to: artwork, name of the game, date of release. // Applies to: artwork, name of the game, date of release.
// This is read from es_settings.xml, setting 'ScraperRegion'. // This is read from es_settings.xml, setting "ScraperRegion".
// Which Language to use when selecting the textual information. // Which Language to use when selecting the textual information.
// Applies to: description, genre. // Applies to: description, genre.
// This is read from es_settings.xml, setting 'ScraperLanguage'. // This is read from es_settings.xml, setting "ScraperLanguage".
ScreenScraperConfig() {}; ScreenScraperConfig() {}
} configuration; } configuration;
protected: protected:

View file

@ -8,15 +8,15 @@
#include "views/SystemView.h" #include "views/SystemView.h"
#include "animations/LambdaAnimation.h"
#include "guis/GuiMsgBox.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "Sound.h" #include "Sound.h"
#include "SystemData.h" #include "SystemData.h"
#include "Window.h" #include "Window.h"
#include "animations/LambdaAnimation.h"
#include "guis/GuiMsgBox.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#if defined(_WIN64) #if defined(_WIN64)
#include <cmath> #include <cmath>
@ -26,14 +26,12 @@
const int logoBuffersLeft[] = { -5, -2, -1 }; const int logoBuffersLeft[] = { -5, -2, -1 };
const int logoBuffersRight[] = { 1, 2, 5 }; const int logoBuffersRight[] = { 1, 2, 5 };
SystemView::SystemView( SystemView::SystemView(Window* window)
Window* window) : IList<SystemViewData, SystemData*>(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP)
: IList<SystemViewData, SystemData*> , mPreviousScrollVelocity(0)
(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP), , mUpdatedGameCount(false)
mPreviousScrollVelocity(0), , mViewNeedsReload(true)
mUpdatedGameCount(false), , mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER)
mViewNeedsReload(true),
mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER)
{ {
mCamOffset = 0; mCamOffset = 0;
mExtrasCamOffset = 0; mExtrasCamOffset = 0;
@ -58,7 +56,7 @@ void SystemView::populate()
{ {
mEntries.clear(); mEntries.clear();
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
const std::shared_ptr<ThemeData>& theme = (*it)->getTheme(); const std::shared_ptr<ThemeData>& theme = (*it)->getTheme();
@ -74,8 +72,8 @@ void SystemView::populate()
const ThemeData::ThemeElement* logoElem = theme->getElement("system", "logo", "image"); const ThemeData::ThemeElement* logoElem = theme->getElement("system", "logo", "image");
if (logoElem) { if (logoElem) {
std::string path = logoElem->get<std::string>("path"); std::string path = logoElem->get<std::string>("path");
std::string defaultPath = logoElem->has("default") ? std::string defaultPath =
logoElem->get<std::string>("default") : ""; logoElem->has("default") ? logoElem->get<std::string>("default") : "";
if ((!path.empty() && ResourceManager::getInstance()->fileExists(path)) || if ((!path.empty() && ResourceManager::getInstance()->fileExists(path)) ||
(!defaultPath.empty() && (!defaultPath.empty() &&
ResourceManager::getInstance()->fileExists(defaultPath))) { ResourceManager::getInstance()->fileExists(defaultPath))) {
@ -88,16 +86,14 @@ void SystemView::populate()
} }
if (!e.data.logo) { if (!e.data.logo) {
// No logo in theme; use text. // No logo in theme; use text.
TextComponent* text = new TextComponent( TextComponent* text =
mWindow, new TextComponent(mWindow, (*it)->getName(), Font::get(FONT_SIZE_LARGE),
(*it)->getName(), 0x000000FF, ALIGN_CENTER);
Font::get(FONT_SIZE_LARGE),
0x000000FF,
ALIGN_CENTER);
text->setSize(mCarousel.logoSize * mCarousel.logoScale); text->setSize(mCarousel.logoSize * mCarousel.logoScale);
text->applyTheme((*it)->getTheme(), "system", "logoText", text->applyTheme((*it)->getTheme(), "system", "logoText",
ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR |
ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | ThemeFlags::TEXT); ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING |
ThemeFlags::TEXT);
e.data.logo = std::shared_ptr<GuiComponent>(text); e.data.logo = std::shared_ptr<GuiComponent>(text);
if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) { if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) {
@ -134,10 +130,9 @@ void SystemView::populate()
e.data.backgroundExtras = ThemeData::makeExtras((*it)->getTheme(), "system", mWindow); e.data.backgroundExtras = ThemeData::makeExtras((*it)->getTheme(), "system", mWindow);
// Sort the extras by z-index. // Sort the extras by z-index.
std::stable_sort(e.data.backgroundExtras.begin(), e.data.backgroundExtras.end(), std::stable_sort(
[](GuiComponent* a, GuiComponent* b) { e.data.backgroundExtras.begin(), e.data.backgroundExtras.end(),
return b->getZIndex() > a->getZIndex(); [](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); });
});
this->add(e); this->add(e);
} }
@ -146,9 +141,10 @@ void SystemView::populate()
// Something is wrong, there is not a single system to show, check if UI mode is not full. // Something is wrong, there is not a single system to show, check if UI mode is not full.
if (!UIModeController::getInstance()->isUIModeFull()) { if (!UIModeController::getInstance()->isUIModeFull()) {
Settings::getInstance()->setString("UIMode", "full"); Settings::getInstance()->setString("UIMode", "full");
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), mWindow->pushGui(new GuiMsgBox(
"The selected UI mode has nothing to show,\n returning to UI mode \"Full\"", mWindow, getHelpStyle(),
"OK", nullptr)); "The selected UI mode has nothing to show,\n returning to UI mode \"Full\"", "OK",
nullptr));
} }
} }
} }
@ -162,14 +158,14 @@ void SystemView::updateGameCount()
ss << "CONFIGURATION"; ss << "CONFIGURATION";
else if (getSelected()->isCollection() && (getSelected()->getName() == "favorites")) else if (getSelected()->isCollection() && (getSelected()->getName() == "favorites"))
ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S"); ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S");
// The 'recent' gamelist has probably been trimmed after sorting, so we'll cap it at // The "recent" gamelist has probably been trimmed after sorting, so we'll cap it at
// its maximum limit of 50 games. // its maximum limit of 50 games.
else if (getSelected()->isCollection() && (getSelected()->getName() == "recent")) else if (getSelected()->isCollection() && (getSelected()->getName() == "recent"))
ss << (gameCount.first > 50 ? 50 : gameCount.first) << " GAME" << ss << (gameCount.first > 50 ? 50 : gameCount.first) << " GAME"
(gameCount.first == 1 ? " " : "S"); << (gameCount.first == 1 ? " " : "S");
else else
ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S ") << "(" << ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S ") << "("
gameCount.second << " FAVORITE" << (gameCount.second == 1 ? ")" : "S)"); << gameCount.second << " FAVORITE" << (gameCount.second == 1 ? ")" : "S)");
mSystemInfo.setText(ss.str()); mSystemInfo.setText(ss.str());
} }
@ -241,8 +237,7 @@ bool SystemView::input(InputConfig* config, Input input)
return true; return true;
} }
if (!UIModeController::getInstance()->isUIModeKid() && if (!UIModeController::getInstance()->isUIModeKid() && config->isMappedTo("back", input) &&
config->isMappedTo("back", input) &&
Settings::getInstance()->getBool("ScreensaverControls")) { Settings::getInstance()->getBool("ScreensaverControls")) {
if (!mWindow->isScreensaverActive()) { if (!mWindow->isScreensaverActive()) {
ViewController::get()->stopScrolling(); ViewController::get()->stopScrolling();
@ -254,10 +249,8 @@ bool SystemView::input(InputConfig* config, Input input)
} }
} }
else { else {
if (config->isMappedLike("left", input) || if (config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
config->isMappedLike("right", input) || config->isMappedLike("up", input) || config->isMappedLike("down", input))
config->isMappedLike("up", input) ||
config->isMappedLike("down", input))
listInput(0); listInput(0);
} }
@ -302,8 +295,7 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
std::string transition_style = Settings::getInstance()->getString("TransitionStyle"); std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
// To prevent ugly jumps with two systems when quickly repeating the same direction. // To prevent ugly jumps with two systems when quickly repeating the same direction.
if (mPreviousScrollVelocity != 0 && posMax == 2 && if (mPreviousScrollVelocity != 0 && posMax == 2 && mScrollVelocity == mPreviousScrollVelocity) {
mScrollVelocity == mPreviousScrollVelocity ) {
if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) { if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) {
(mScrollVelocity < 0) ? endPos -= 1 : endPos += 1; (mScrollVelocity < 0) ? endPos -= 1 : endPos += 1;
(mCursor == 0) ? mCursor = 1 : mCursor = 0; (mCursor == 0) ? mCursor = 1 : mCursor = 0;
@ -343,9 +335,10 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
this->mExtrasCamOffset = endPos; this->mExtrasCamOffset = endPos;
// Update the game count when the entire animation has been completed. // Update the game count when the entire animation has been completed.
if (mExtrasFadeOpacity == 1.0) if (mExtrasFadeOpacity == 1.0f)
updateGameCount(); updateGameCount();
}, 500); },
500);
} }
else if (transition_style == "slide") { else if (transition_style == "slide") {
mUpdatedGameCount = false; mUpdatedGameCount = false;
@ -363,19 +356,24 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
// Hack to make the game count being updated in the middle of the animation. // Hack to make the game count being updated in the middle of the animation.
bool update = false; bool update = false;
if (endPos == -1 && fabs(fabs(posMax) - fabs(mCamOffset)) > 0.5 && !mUpdatedGameCount) if (endPos == -1.0f && fabs(fabs(posMax) - fabs(mCamOffset)) > 0.5f &&
!mUpdatedGameCount) {
update = true; update = true;
else if (endPos > posMax && fabs(endPos - posMax - fabs(mCamOffset)) < }
0.5 && !mUpdatedGameCount) else if (endPos > posMax && fabs(endPos - posMax - fabs(mCamOffset)) < 0.5f &&
!mUpdatedGameCount) {
update = true; update = true;
else if (fabs(fabs(endPos) - fabs(mCamOffset)) < 0.5 && !mUpdatedGameCount) }
else if (fabs(fabs(endPos) - fabs(mCamOffset)) < 0.5f && !mUpdatedGameCount) {
update = true; update = true;
}
if (update) { if (update) {
mUpdatedGameCount = true; mUpdatedGameCount = true;
updateGameCount(); updateGameCount();
} }
}, 500); },
500);
} }
else { else {
// Instant. // Instant.
@ -391,7 +389,8 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
this->mCamOffset = f; this->mCamOffset = f;
this->mExtrasCamOffset = endPos; this->mExtrasCamOffset = endPos;
}, 500); },
500);
} }
setAnimation(anim, 0, nullptr, false, 0); setAnimation(anim, 0, nullptr, false, 0);
@ -460,13 +459,14 @@ void SystemView::getViewElements(const std::shared_ptr<ThemeData>& theme)
if (!theme->hasView("system")) if (!theme->hasView("system"))
return; return;
const ThemeData::ThemeElement* carouselElem = theme-> const ThemeData::ThemeElement* carouselElem =
getElement("system", "systemcarousel", "carousel"); theme->getElement("system", "systemcarousel", "carousel");
if (carouselElem) if (carouselElem)
getCarouselFromTheme(carouselElem); getCarouselFromTheme(carouselElem);
const ThemeData::ThemeElement* sysInfoElem = theme-> const ThemeData::ThemeElement* sysInfoElem = theme->getElement("system", "systemInfo", "text");
getElement("system", "systemInfo", "text");
if (sysInfoElem) if (sysInfoElem)
mSystemInfo.applyTheme(theme, "system", "systemInfo", ThemeFlags::ALL); mSystemInfo.applyTheme(theme, "system", "systemInfo", ThemeFlags::ALL);
@ -479,72 +479,80 @@ void SystemView::renderCarousel(const Transform4x4f& trans)
// Background box behind logos. // Background box behind logos.
Transform4x4f carouselTrans = trans; Transform4x4f carouselTrans = trans;
carouselTrans.translate(Vector3f(mCarousel.pos.x(), mCarousel.pos.y(), 0.0)); carouselTrans.translate(Vector3f(mCarousel.pos.x(), mCarousel.pos.y(), 0.0));
carouselTrans.translate(Vector3f(mCarousel.origin.x() * mCarousel.size.x() * -1, carouselTrans.translate(Vector3f(mCarousel.origin.x() * mCarousel.size.x() * -1.0f,
mCarousel.origin.y() * mCarousel.size.y() * -1, 0.0f)); mCarousel.origin.y() * mCarousel.size.y() * -1.0f, 0.0f));
Vector2f clipPos(carouselTrans.translation().x(), carouselTrans.translation().y()); Vector2f clipPos(carouselTrans.translation().x(), carouselTrans.translation().y());
Renderer::pushClipRect(Vector2i(static_cast<int>(clipPos.x()), static_cast<int>(clipPos.y())), Renderer::pushClipRect(
Vector2i(static_cast<int>(clipPos.x()), static_cast<int>(clipPos.y())),
Vector2i(static_cast<int>(mCarousel.size.x()), static_cast<int>(mCarousel.size.y()))); Vector2i(static_cast<int>(mCarousel.size.x()), static_cast<int>(mCarousel.size.y())));
Renderer::setMatrix(carouselTrans); Renderer::setMatrix(carouselTrans);
Renderer::drawRect(0.0f, 0.0f, mCarousel.size.x(), mCarousel.size.y(), Renderer::drawRect(0.0f, 0.0f, mCarousel.size.x(), mCarousel.size.y(), mCarousel.color,
mCarousel.color, mCarousel.colorEnd, mCarousel.colorGradientHorizontal); mCarousel.colorEnd, mCarousel.colorGradientHorizontal);
// Draw logos. // Draw logos.
// Note: logoSpacing will also include the size of the logo itself. // Note: logoSpacing will also include the size of the logo itself.
Vector2f logoSpacing(0.0, 0.0); Vector2f logoSpacing(0.0f, 0.0f);
float xOff = 0.0; float xOff = 0.0f;
float yOff = 0.0; float yOff = 0.0f;
switch (mCarousel.type) { switch (mCarousel.type) {
case VERTICAL_WHEEL: case VERTICAL_WHEEL: {
yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.f - yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.0f -
(mCamOffset * logoSpacing[1]); (mCamOffset * logoSpacing[1]);
if (mCarousel.logoAlignment == ALIGN_LEFT) if (mCarousel.logoAlignment == ALIGN_LEFT)
xOff = mCarousel.logoSize.x() / 10.f; xOff = mCarousel.logoSize.x() / 10.0f;
else if (mCarousel.logoAlignment == ALIGN_RIGHT) else if (mCarousel.logoAlignment == ALIGN_RIGHT)
xOff = mCarousel.size.x() - (mCarousel.logoSize.x() * 1.1f); xOff = mCarousel.size.x() - (mCarousel.logoSize.x() * 1.1f);
else else
xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2.f; xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2.0f;
break; break;
case VERTICAL: }
logoSpacing[1] = ((mCarousel.size.y() - (mCarousel.logoSize.y() * case VERTICAL: {
mCarousel.maxLogoCount)) / (mCarousel.maxLogoCount)) + mCarousel.logoSize.y(); logoSpacing[1] =
yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.f - ((mCarousel.size.y() - (mCarousel.logoSize.y() * mCarousel.maxLogoCount)) /
(mCarousel.maxLogoCount)) +
mCarousel.logoSize.y();
yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.0f -
(mCamOffset * logoSpacing[1]); (mCamOffset * logoSpacing[1]);
if (mCarousel.logoAlignment == ALIGN_LEFT) if (mCarousel.logoAlignment == ALIGN_LEFT)
xOff = mCarousel.logoSize.x() / 10.f; xOff = mCarousel.logoSize.x() / 10.0f;
else if (mCarousel.logoAlignment == ALIGN_RIGHT) else if (mCarousel.logoAlignment == ALIGN_RIGHT)
xOff = mCarousel.size.x() - (mCarousel.logoSize.x() * 1.1f); xOff = mCarousel.size.x() - (mCarousel.logoSize.x() * 1.1f);
else else
xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2; xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2.0f;
break; break;
case HORIZONTAL_WHEEL: }
xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2 - case HORIZONTAL_WHEEL: {
xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2.0f -
(mCamOffset * logoSpacing[1]); (mCamOffset * logoSpacing[1]);
if (mCarousel.logoAlignment == ALIGN_TOP) if (mCarousel.logoAlignment == ALIGN_TOP)
yOff = mCarousel.logoSize.y() / 10; yOff = mCarousel.logoSize.y() / 10.0f;
else if (mCarousel.logoAlignment == ALIGN_BOTTOM) else if (mCarousel.logoAlignment == ALIGN_BOTTOM)
yOff = mCarousel.size.y() - (mCarousel.logoSize.y() * 1.1f); yOff = mCarousel.size.y() - (mCarousel.logoSize.y() * 1.1f);
else else
yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2; yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.0f;
break; break;
case HORIZONTAL: }
default: case HORIZONTAL: {
logoSpacing[0] = ((mCarousel.size.x() - (mCarousel.logoSize.x() * }
mCarousel.maxLogoCount)) / (mCarousel.maxLogoCount)) + mCarousel.logoSize.x(); default: {
xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2.f - logoSpacing[0] =
((mCarousel.size.x() - (mCarousel.logoSize.x() * mCarousel.maxLogoCount)) /
(mCarousel.maxLogoCount)) +
mCarousel.logoSize.x();
xOff = (mCarousel.size.x() - mCarousel.logoSize.x()) / 2.0f -
(mCamOffset * logoSpacing[0]); (mCamOffset * logoSpacing[0]);
if (mCarousel.logoAlignment == ALIGN_TOP) if (mCarousel.logoAlignment == ALIGN_TOP)
yOff = mCarousel.logoSize.y() / 10.f; yOff = mCarousel.logoSize.y() / 10.0f;
else if (mCarousel.logoAlignment == ALIGN_BOTTOM) else if (mCarousel.logoAlignment == ALIGN_BOTTOM)
yOff = mCarousel.size.y() - (mCarousel.logoSize.y() * 1.1f); yOff = mCarousel.size.y() - (mCarousel.logoSize.y() * 1.1f);
else else
yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.f; yOff = (mCarousel.size.y() - mCarousel.logoSize.y()) / 2.0f;
break; break;
} }
}
int center = static_cast<int>(mCamOffset); int center = static_cast<int>(mCamOffset);
int logoCount = std::min(mCarousel.maxLogoCount, static_cast<int>(mEntries.size())); int logoCount = std::min(mCarousel.maxLogoCount, static_cast<int>(mEntries.size()));
@ -558,9 +566,10 @@ void SystemView::renderCarousel(const Transform4x4f& trans)
bufferRight = 0; bufferRight = 0;
} }
for (int i = center - logoCount / 2 + bufferLeft; for (int i = center - logoCount / 2 + bufferLeft; // Line break.
i <= center + logoCount / 2 + bufferRight; i++) { i <= center + logoCount / 2 + bufferRight; i++) {
int index = i; int index = i;
while (index < 0) while (index < 0)
index += static_cast<int>(mEntries.size()); index += static_cast<int>(mEntries.size());
while (index >= static_cast<int>(mEntries.size())) while (index >= static_cast<int>(mEntries.size()))
@ -575,8 +584,8 @@ void SystemView::renderCarousel(const Transform4x4f& trans)
scale = std::min(mCarousel.logoScale, std::max(1.0f, scale)); scale = std::min(mCarousel.logoScale, std::max(1.0f, scale));
scale /= mCarousel.logoScale; scale /= mCarousel.logoScale;
int opacity = static_cast<int>(std::round(0x80 + ((0xFF - 0x80) * int opacity =
(1.0f - fabs(distance))))); static_cast<int>(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabs(distance)))));
opacity = std::max(static_cast<int>(0x80), opacity); opacity = std::max(static_cast<int>(0x80), opacity);
const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo; const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo;
@ -599,11 +608,11 @@ void SystemView::renderExtras(const Transform4x4f& trans, float lower, float upp
// Adding texture loading buffers depending on scrolling speed and status. // Adding texture loading buffers depending on scrolling speed and status.
int bufferIndex = getScrollingVelocity() + 1; int bufferIndex = getScrollingVelocity() + 1;
Renderer::pushClipRect(Vector2i::Zero(), Vector2i(static_cast<int>(mSize.x()), Renderer::pushClipRect(Vector2i::Zero(),
static_cast<int>(mSize.y()))); Vector2i(static_cast<int>(mSize.x()), static_cast<int>(mSize.y())));
for (int i = extrasCenter + logoBuffersLeft[bufferIndex]; i <= extrasCenter + for (int i = extrasCenter + logoBuffersLeft[bufferIndex];
logoBuffersRight[bufferIndex]; i++) { i <= extrasCenter + logoBuffersRight[bufferIndex]; i++) {
int index = i; int index = i;
while (index < 0) while (index < 0)
index += static_cast<int>(mEntries.size()); index += static_cast<int>(mEntries.size());
@ -611,15 +620,15 @@ void SystemView::renderExtras(const Transform4x4f& trans, float lower, float upp
index -= static_cast<int>(mEntries.size()); index -= static_cast<int>(mEntries.size());
// Only render selected system when not showing. // Only render selected system when not showing.
if (mShowing || index == mCursor) if (mShowing || index == mCursor) {
{
Transform4x4f extrasTrans = trans; Transform4x4f extrasTrans = trans;
if (mCarousel.type == HORIZONTAL || mCarousel.type == HORIZONTAL_WHEEL) if (mCarousel.type == HORIZONTAL || mCarousel.type == HORIZONTAL_WHEEL)
extrasTrans.translate(Vector3f((i - mExtrasCamOffset) * mSize.x(), 0, 0)); extrasTrans.translate(Vector3f((i - mExtrasCamOffset) * mSize.x(), 0, 0));
else else
extrasTrans.translate(Vector3f(0, (i - mExtrasCamOffset) * mSize.y(), 0)); extrasTrans.translate(Vector3f(0, (i - mExtrasCamOffset) * mSize.y(), 0));
Renderer::pushClipRect(Vector2i(static_cast<int>(extrasTrans.translation()[0]), Renderer::pushClipRect(
Vector2i(static_cast<int>(extrasTrans.translation()[0]),
static_cast<int>(extrasTrans.translation()[1])), static_cast<int>(extrasTrans.translation()[1])),
Vector2i(static_cast<int>(mSize.x()), static_cast<int>(mSize.y()))); Vector2i(static_cast<int>(mSize.x()), static_cast<int>(mSize.y())));
SystemViewData data = mEntries.at(index).data; SystemViewData data = mEntries.at(index).data;
@ -637,7 +646,7 @@ void SystemView::renderExtras(const Transform4x4f& trans, float lower, float upp
void SystemView::renderFade(const Transform4x4f& trans) void SystemView::renderFade(const Transform4x4f& trans)
{ {
unsigned int fadeColor = 0x00000000 | static_cast<unsigned char>(mExtrasFadeOpacity * 255); unsigned int fadeColor = 0x00000000 | static_cast<unsigned char>(mExtrasFadeOpacity * 255.0f);
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), fadeColor, fadeColor); Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), fadeColor, fadeColor);
} }
@ -658,13 +667,13 @@ void SystemView::getDefaultElements(void)
mCarousel.colorEnd = 0xFFFFFFD8; mCarousel.colorEnd = 0xFFFFFFD8;
mCarousel.colorGradientHorizontal = true; mCarousel.colorGradientHorizontal = true;
mCarousel.logoScale = 1.2f; mCarousel.logoScale = 1.2f;
mCarousel.logoRotation = 7.5; mCarousel.logoRotation = 7.5f;
mCarousel.logoRotationOrigin.x() = -5; mCarousel.logoRotationOrigin.x() = -5.0f;
mCarousel.logoRotationOrigin.y() = 0.5; mCarousel.logoRotationOrigin.y() = 0.5f;
mCarousel.logoSize.x() = 0.25f * mSize.x(); mCarousel.logoSize.x() = 0.25f * mSize.x();
mCarousel.logoSize.y() = 0.155f * mSize.y(); mCarousel.logoSize.y() = 0.155f * mSize.y();
mCarousel.maxLogoCount = 3; mCarousel.maxLogoCount = 3;
mCarousel.zIndex = 40; mCarousel.zIndex = 40.0f;
// System info bar. // System info bar.
mSystemInfo.setSize(mSize.x(), mSystemInfo.getFont()->getLetterHeight() * 2.2f); mSystemInfo.setSize(mSize.x(), mSystemInfo.getFont()->getLetterHeight() * 2.2f);
@ -702,7 +711,8 @@ void SystemView::getCarouselFromTheme(const ThemeData::ThemeElement* elem)
if (elem->has("colorEnd")) if (elem->has("colorEnd"))
mCarousel.colorEnd = elem->get<unsigned int>("colorEnd"); mCarousel.colorEnd = elem->get<unsigned int>("colorEnd");
if (elem->has("gradientType")) if (elem->has("gradientType"))
mCarousel.colorGradientHorizontal = !(elem->get<std::string>("gradientType").compare("horizontal")); mCarousel.colorGradientHorizontal =
!(elem->get<std::string>("gradientType").compare("horizontal"));
if (elem->has("logoScale")) if (elem->has("logoScale"))
mCarousel.logoScale = elem->get<float>("logoScale"); mCarousel.logoScale = elem->get<float>("logoScale");
if (elem->has("logoSize")) if (elem->has("logoSize"))
@ -728,13 +738,3 @@ void SystemView::getCarouselFromTheme(const ThemeData::ThemeElement* elem)
mCarousel.logoAlignment = ALIGN_CENTER; mCarousel.logoAlignment = ALIGN_CENTER;
} }
} }
void SystemView::onShow()
{
mShowing = true;
}
void SystemView::onHide()
{
mShowing = false;
}

View file

@ -9,11 +9,11 @@
#ifndef ES_APP_VIEWS_SYSTEM_VIEW_H #ifndef ES_APP_VIEWS_SYSTEM_VIEW_H
#define ES_APP_VIEWS_SYSTEM_VIEW_H #define ES_APP_VIEWS_SYSTEM_VIEW_H
#include "GuiComponent.h"
#include "Sound.h"
#include "components/IList.h" #include "components/IList.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "GuiComponent.h"
#include "Sound.h"
#include <memory> #include <memory>
@ -55,8 +55,8 @@ public:
SystemView(Window* window); SystemView(Window* window);
~SystemView(); ~SystemView();
virtual void onShow() override; virtual void onShow() override { mShowing = true; }
virtual void onHide() override; virtual void onHide() override { mShowing = false; }
void goToSystem(SystemData* system, bool animate); void goToSystem(SystemData* system, bool animate);
@ -69,12 +69,14 @@ public:
std::vector<HelpPrompt> getHelpPrompts() override; std::vector<HelpPrompt> getHelpPrompts() override;
virtual HelpStyle getHelpStyle() override; virtual HelpStyle getHelpStyle() override;
CarouselType getCarouselType() { return mCarousel.type; }; CarouselType getCarouselType() { return mCarousel.type; }
protected: protected:
void onCursorChanged(const CursorState& state) override; void onCursorChanged(const CursorState& state) override;
virtual void onScroll() override { virtual void onScroll() override
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND); } {
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND);
}
private: private:
void populate(); void populate();

View file

@ -9,12 +9,12 @@
#include "UIModeController.h" #include "UIModeController.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "Log.h" #include "Log.h"
#include "SystemData.h" #include "SystemData.h"
#include "Window.h" #include "Window.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
UIModeController* UIModeController::sInstance = nullptr; UIModeController* UIModeController::sInstance = nullptr;
@ -34,7 +34,8 @@ void UIModeController::deinit()
} }
} }
UIModeController::UIModeController() : mPassKeyCounter(0) UIModeController::UIModeController()
: mPassKeyCounter(0)
{ {
mPassKeySequence = Settings::getInstance()->getString("UIMode_passkey"); mPassKeySequence = Settings::getInstance()->getString("UIMode_passkey");
mCurrentUIMode = Settings::getInstance()->getString("UIMode"); mCurrentUIMode = Settings::getInstance()->getString("UIMode");
@ -47,13 +48,12 @@ void UIModeController::monitorUIMode()
if (uimode != mCurrentUIMode && !ViewController::get()->isCameraMoving()) { if (uimode != mCurrentUIMode && !ViewController::get()->isCameraMoving()) {
mCurrentUIMode = uimode; mCurrentUIMode = uimode;
// Reset filters and sort gamelists (which will update the game counter). // Reset filters and sort gamelists (which will update the game counter).
for (auto it = SystemData::sSystemVector.cbegin(); it != for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
(*it)->sortSystem(true); (*it)->sortSystem(true);
(*it)->getIndex()->resetFilters(); (*it)->getIndex()->resetFilters();
if ((*it)->getThemeFolder() == "custom-collections") { if ((*it)->getThemeFolder() == "custom-collections") {
for (FileData* customSystem : for (FileData* customSystem : (*it)->getRootFolder()->getChildrenListToDisplay())
(*it)->getRootFolder()->getChildrenListToDisplay())
customSystem->getSystem()->getIndex()->resetFilters(); customSystem->getSystem()->getIndex()->resetFilters();
} }
} }
@ -75,6 +75,7 @@ bool UIModeController::listen(InputConfig* config, Input input)
unlockUIMode(); unlockUIMode();
return true; return true;
} }
return false; return false;
} }
@ -101,9 +102,9 @@ void UIModeController::unlockUIMode()
bool UIModeController::isUIModeFull() bool UIModeController::isUIModeFull()
{ {
return ((mCurrentUIMode == "full" || (isUIModeKid() && return ((mCurrentUIMode == "full" ||
Settings::getInstance()->getBool("EnableMenuKidMode"))) (isUIModeKid() && Settings::getInstance()->getBool("EnableMenuKidMode"))) &&
&& !Settings::getInstance()->getBool("ForceKiosk")); !Settings::getInstance()->getBool("ForceKiosk"));
} }
bool UIModeController::isUIModeKid() bool UIModeController::isUIModeKid()
@ -139,12 +140,19 @@ std::string UIModeController::getFormattedPassKeyStr()
symbolY = "X"; symbolY = "X";
} }
else if (controllerType == "ps4" || controllerType == "ps5") { else if (controllerType == "ps4" || controllerType == "ps5") {
#if defined(_MSC_VER) // MSVC compiler.
// These symbols are far from perfect but you can at least understand what // These symbols are far from perfect but you can at least understand what
// they are supposed to depict. // they are supposed to depict.
symbolA = Utils::String::wideStringToString(L"\uF00D"); // Cross.
symbolB = Utils::String::wideStringToString(L"\uF111"); // Circle
symbolX = Utils::String::wideStringToString(L"\uF04D"); // Square.
symbolY = Utils::String::wideStringToString(L"\uF0D8"); // Triangle.
#else
symbolA = "\uF00D"; // Cross. symbolA = "\uF00D"; // Cross.
symbolB = "\uF111"; // Circle symbolB = "\uF111"; // Circle
symbolX = "\uF04D"; // Square. symbolX = "\uF04D"; // Square.
symbolY = "\uF0D8"; // Triangle. symbolY = "\uF0D8"; // Triangle.
#endif
} }
else { else {
// Xbox controller. // Xbox controller.

View file

@ -39,7 +39,7 @@ public:
bool isUIModeKid(); bool isUIModeKid();
bool isUIModeKiosk(); bool isUIModeKiosk();
void setCurrentUIMode(const std::string& mode) { mCurrentUIMode = mode; }; void setCurrentUIMode(const std::string& mode) { mCurrentUIMode = mode; }
private: private:
UIModeController(); UIModeController();
@ -58,8 +58,9 @@ private:
int mPassKeyCounter; int mPassKeyCounter;
// These are Xbox button names, so they may be different in pracise on non-Xbox controllers. // These are Xbox button names, so they may be different in pracise on non-Xbox controllers.
const std::vector<std::string> mInputVals = const std::vector<std::string> mInputVals = {
{ "up", "down", "left", "right", "a", "b", "x", "y" }; "up", "down", "left", "right", "a", "b", "x", "y"
};
}; };
#endif // ES_APP_VIEWS_UI_MODE_CONTROLLER_H #endif // ES_APP_VIEWS_UI_MODE_CONTROLLER_H

View file

@ -12,17 +12,6 @@
#include "views/ViewController.h" #include "views/ViewController.h"
#include "animations/Animation.h"
#include "animations/LambdaAnimation.h"
#include "animations/MoveCameraAnimation.h"
#include "guis/GuiInfoPopup.h"
#include "guis/GuiMenu.h"
#include "views/gamelist/DetailedGameListView.h"
#include "views/gamelist/GridGameListView.h"
#include "views/gamelist/IGameListView.h"
#include "views/gamelist/VideoGameListView.h"
#include "views/SystemView.h"
#include "views/UIModeController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "InputManager.h" #include "InputManager.h"
@ -32,8 +21,20 @@
#include "SystemData.h" #include "SystemData.h"
#include "SystemView.h" #include "SystemView.h"
#include "Window.h" #include "Window.h"
#include "animations/Animation.h"
#include "animations/LambdaAnimation.h"
#include "animations/MoveCameraAnimation.h"
#include "guis/GuiInfoPopup.h"
#include "guis/GuiMenu.h"
#include "views/SystemView.h"
#include "views/UIModeController.h"
#include "views/gamelist/DetailedGameListView.h"
#include "views/gamelist/GridGameListView.h"
#include "views/gamelist/IGameListView.h"
#include "views/gamelist/VideoGameListView.h"
ViewController* ViewController::sInstance = nullptr; ViewController* ViewController::sInstance = nullptr;
#if defined(_MSC_VER) // MSVC compiler. #if defined(_MSC_VER) // MSVC compiler.
const std::string ViewController::FAVORITE_CHAR = Utils::String::wideStringToString(L"\uF005"); const std::string ViewController::FAVORITE_CHAR = Utils::String::wideStringToString(L"\uF005");
const std::string ViewController::FOLDER_CHAR = Utils::String::wideStringToString(L"\uF07C"); const std::string ViewController::FOLDER_CHAR = Utils::String::wideStringToString(L"\uF07C");
@ -60,21 +61,20 @@ void ViewController::init(Window* window)
sInstance = new ViewController(window); sInstance = new ViewController(window);
} }
ViewController::ViewController( ViewController::ViewController(Window* window)
Window* window) : GuiComponent(window)
: GuiComponent(window), , mCurrentView(nullptr)
mCurrentView(nullptr), , mPreviousView(nullptr)
mPreviousView(nullptr), , mSkipView(nullptr)
mSkipView(nullptr), , mCamera(Transform4x4f::Identity())
mCamera(Transform4x4f::Identity()), , mSystemViewTransition(false)
mSystemViewTransition(false), , mWrappedViews(false)
mWrappedViews(false), , mFadeOpacity(0)
mFadeOpacity(0), , mCancelledTransition(false)
mCancelledTransition(false), , mLockInput(false)
mLockInput(false), , mNextSystem(false)
mNextSystem(false), , mGameToLaunch(nullptr)
mGameToLaunch(nullptr), , mNoGamesMessageBox(nullptr)
mNoGamesMessageBox(nullptr)
{ {
mState.viewing = NOTHING; mState.viewing = NOTHING;
mState.viewstyle = AUTOMATIC; mState.viewstyle = AUTOMATIC;
@ -89,27 +89,26 @@ ViewController::~ViewController()
void ViewController::invalidSystemsFileDialog() void ViewController::invalidSystemsFileDialog()
{ {
std::string errorMessage = std::string errorMessage = "COULDN'T PARSE THE SYSTEMS CONFIGURATION FILE.\n"
"COULDN'T PARSE THE SYSTEMS CONFIGURATION FILE.\n"
"IF YOU HAVE A CUSTOMIZED es_systems.xml FILE, THEN\n" "IF YOU HAVE A CUSTOMIZED es_systems.xml FILE, THEN\n"
"SOMETHING IS LIKELY WRONG WITH YOUR XML SYNTAX.\n" "SOMETHING IS LIKELY WRONG WITH YOUR XML SYNTAX.\n"
"IF YOU DON'T HAVE A CUSTOM SYSTEMS FILE, THEN THE\n" "IF YOU DON'T HAVE A CUSTOM SYSTEMS FILE, THEN THE\n"
"EMULATIONSTATION INSTALLATION IS BROKEN. SEE THE\n" "EMULATIONSTATION INSTALLATION IS BROKEN. SEE THE\n"
"APPLICATION LOG FILE es_log.txt FOR ADDITIONAL INFO."; "APPLICATION LOG FILE es_log.txt FOR ADDITIONAL INFO.";
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), mWindow->pushGui(new GuiMsgBox(
errorMessage.c_str(), mWindow, HelpStyle(), errorMessage.c_str(), "QUIT",
"QUIT", [] { [] {
SDL_Event quit; SDL_Event quit;
quit.type = SDL_QUIT; quit.type = SDL_QUIT;
SDL_PushEvent(&quit); SDL_PushEvent(&quit);
}, "", nullptr, "", nullptr, true)); },
"", nullptr, "", nullptr, true));
} }
void ViewController::noGamesDialog() void ViewController::noGamesDialog()
{ {
mNoGamesErrorMessage = mNoGamesErrorMessage = "NO GAME FILES WERE FOUND. EITHER PLACE YOUR GAMES IN\n"
"NO GAME FILES WERE FOUND. EITHER PLACE YOUR GAMES IN\n"
"THE CURRENTLY CONFIGURED ROM DIRECTORY OR CHANGE\n" "THE CURRENTLY CONFIGURED ROM DIRECTORY OR CHANGE\n"
"ITS PATH USING THE BUTTON BELOW. OPTIONALLY THE ROM\n" "ITS PATH USING THE BUTTON BELOW. OPTIONALLY THE ROM\n"
"DIRECTORY STRUCTURE CAN BE GENERATED WHICH WILL\n" "DIRECTORY STRUCTURE CAN BE GENERATED WHICH WILL\n"
@ -123,8 +122,9 @@ void ViewController::noGamesDialog()
mRomDirectory = FileData::getROMDirectory(); mRomDirectory = FileData::getROMDirectory();
#endif #endif
mNoGamesMessageBox = new GuiMsgBox(mWindow, HelpStyle(), mNoGamesErrorMessage + mRomDirectory, mNoGamesMessageBox = new GuiMsgBox(
"CHANGE ROM DIRECTORY", [this] { mWindow, HelpStyle(), mNoGamesErrorMessage + mRomDirectory, "CHANGE ROM DIRECTORY",
[this] {
std::string currentROMDirectory; std::string currentROMDirectory;
#if defined(_WIN64) #if defined(_WIN64)
currentROMDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); currentROMDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
@ -133,12 +133,8 @@ void ViewController::noGamesDialog()
#endif #endif
mWindow->pushGui(new GuiComplexTextEditPopup( mWindow->pushGui(new GuiComplexTextEditPopup(
mWindow, mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH",
HelpStyle(), "Currently configured path:", currentROMDirectory, currentROMDirectory,
"ENTER ROM DIRECTORY PATH",
"Currently configured path:",
currentROMDirectory,
currentROMDirectory,
[this](const std::string& newROMDirectory) { [this](const std::string& newROMDirectory) {
Settings::getInstance()->setString("ROMDirectory", newROMDirectory); Settings::getInstance()->setString("ROMDirectory", newROMDirectory);
Settings::getInstance()->saveFile(); Settings::getInstance()->saveFile();
@ -153,44 +149,46 @@ void ViewController::noGamesDialog()
"THE APPLICATION TO RESCAN THE SYSTEMS", "THE APPLICATION TO RESCAN THE SYSTEMS",
"OK", nullptr, "", nullptr, "", nullptr, true)); "OK", nullptr, "", nullptr, "", nullptr, true));
}, },
false, false, "SAVE", "SAVE CHANGES?", "LOAD CURRENT", "LOAD CURRENTLY CONFIGURED VALUE",
"SAVE", "CLEAR", "CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)", false));
"SAVE CHANGES?",
"LOAD CURRENT",
"LOAD CURRENTLY CONFIGURED VALUE",
"CLEAR",
"CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)",
false));
}, },
"CREATE DIRECTORIES", [this] { "CREATE DIRECTORIES",
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), [this] {
mWindow->pushGui(new GuiMsgBox(
mWindow, HelpStyle(),
"THIS WILL CREATE DIRECTORIES FOR ALL THE\n" "THIS WILL CREATE DIRECTORIES FOR ALL THE\n"
"GAME SYSTEMS DEFINED IN es_systems.xml\n\n" "GAME SYSTEMS DEFINED IN es_systems.xml\n\n"
"THIS MAY CREATE A LOT OF FOLDERS SO IT'S\n" "THIS MAY CREATE A LOT OF FOLDERS SO IT'S\n"
"ADVICED TO REMOVE THE ONES YOU DON'T NEED\n\n" "ADVICED TO REMOVE THE ONES YOU DON'T NEED\n\n"
"PROCEED?", "PROCEED?",
"YES", [this] { "YES",
[this] {
if (!SystemData::createSystemDirectories()) { if (!SystemData::createSystemDirectories()) {
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"THE SYSTEM DIRECTORIES WERE SUCCESSFULLY\n" "THE SYSTEM DIRECTORIES WERE SUCCESSFULLY\n"
"GENERATED, EXIT THE APPLICATION AND PLACE\n" "GENERATED, EXIT THE APPLICATION AND PLACE\n"
"YOUR GAMES IN THE NEWLY CREATED FOLDERS", "OK", nullptr, "YOUR GAMES IN THE NEWLY CREATED FOLDERS",
"", nullptr, "", nullptr, true)); "OK", nullptr, "", nullptr, "", nullptr,
true));
} }
else { else {
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"ERROR CREATING THE SYSTEM DIRECTORIES,\n" "ERROR CREATING THE SYSTEM DIRECTORIES,\n"
"PERMISSION PROBLEMS OR DISK FULL?\n\n" "PERMISSION PROBLEMS OR DISK FULL?\n\n"
"SEE THE LOG FILE FOR MORE DETAILS", "OK", nullptr, "SEE THE LOG FILE FOR MORE DETAILS",
"", nullptr, "", nullptr, true)); "OK", nullptr, "", nullptr, "", nullptr,
true));
} }
}, "NO", nullptr, "", nullptr, true));
}, },
"QUIT", [] { "NO", nullptr, "", nullptr, true));
},
"QUIT",
[] {
SDL_Event quit; SDL_Event quit;
quit.type = SDL_QUIT; quit.type = SDL_QUIT;
SDL_PushEvent(&quit); SDL_PushEvent(&quit);
}, true, false); },
true, false);
mWindow->pushGui(mNoGamesMessageBox); mWindow->pushGui(mNoGamesMessageBox);
} }
@ -205,7 +203,7 @@ void ViewController::goToStart()
// If a specific system is requested, go directly to its game list. // If a specific system is requested, go directly to its game list.
auto requestedSystem = Settings::getInstance()->getString("StartupSystem"); auto requestedSystem = Settings::getInstance()->getString("StartupSystem");
if ("" != requestedSystem && "retropie" != requestedSystem) { if ("" != requestedSystem && "retropie" != requestedSystem) {
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
if ((*it)->getName() == requestedSystem) { if ((*it)->getName() == requestedSystem) {
goToGameList(*it); goToGameList(*it);
@ -396,14 +394,20 @@ void ViewController::goToGameList(SystemData* system)
restoreViewPosition(); restoreViewPosition();
if (mPreviousView && Settings::getInstance()->getString("TransitionStyle") == "fade" && if (mPreviousView && Settings::getInstance()->getString("TransitionStyle") == "fade" &&
isAnimationPlaying(0)) isAnimationPlaying(0)) {
mPreviousView->onHide(); mPreviousView->onHide();
}
if (mPreviousView) { if (mPreviousView) {
mSkipView = mPreviousView; mSkipView = mPreviousView;
mPreviousView.reset(); mPreviousView.reset();
mPreviousView = nullptr; mPreviousView = nullptr;
} }
else if (!mPreviousView && mState.viewing == GAME_LIST) {
// This is needed as otherwise the static image would not get rendered during the
// first Slide transition when coming from the System view.
mSkipView = getGameListView(system);
}
if (mState.viewing != SYSTEM_SELECT) { if (mState.viewing != SYSTEM_SELECT) {
mPreviousView = mCurrentView; mPreviousView = mCurrentView;
@ -536,11 +540,13 @@ void ViewController::playViewTransition(bool instant)
std::string transition_style = Settings::getInstance()->getString("TransitionStyle"); std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
if (instant || transition_style == "instant") { if (instant || transition_style == "instant") {
setAnimation(new LambdaAnimation([this, target](float /*t*/) { setAnimation(new LambdaAnimation(
[this, target](float /*t*/) {
this->mCamera.translation() = -target; this->mCamera.translation() = -target;
if (mPreviousView) if (mPreviousView)
mPreviousView->onHide(); mPreviousView->onHide();
}, 1)); },
1));
updateHelpPrompts(); updateHelpPrompts();
} }
else if (transition_style == "fade") { else if (transition_style == "fade") {
@ -553,7 +559,7 @@ void ViewController::playViewTransition(bool instant)
// Without this, a (much shorter) fade transition would still play as // Without this, a (much shorter) fade transition would still play as
// finishedCallback is calling this function. // finishedCallback is calling this function.
if (!mCancelledTransition) if (!mCancelledTransition)
mFadeOpacity = Math::lerp(0, 1, t); mFadeOpacity = Math::lerp(0.0f, 1.0f, t);
}; };
auto fadeCallback = [this]() { auto fadeCallback = [this]() {
@ -567,8 +573,8 @@ void ViewController::playViewTransition(bool instant)
[this, fadeFunc, fadeCallback, target] { [this, fadeFunc, fadeCallback, target] {
this->mCamera.translation() = -target; this->mCamera.translation() = -target;
updateHelpPrompts(); updateHelpPrompts();
setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), FADE_WAIT,
FADE_WAIT, fadeCallback, true); fadeCallback, true);
}); });
// Fast-forward animation if we're partway faded. // Fast-forward animation if we're partway faded.
@ -641,8 +647,9 @@ void ViewController::launch(FileData* game)
if (durationString == "disabled") { if (durationString == "disabled") {
// If the game launch screen has been set as disabled, show a simple info popup // If the game launch screen has been set as disabled, show a simple info popup
// notification instead. // notification instead.
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "LAUNCHING GAME '" + GuiInfoPopup* s = new GuiInfoPopup(
Utils::String::toUpper(game->metadata.get("name") + "'"), 10000); mWindow, "LAUNCHING GAME '" + Utils::String::toUpper(game->metadata.get("name") + "'"),
10000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
duration = 1700; duration = 1700;
} }
@ -672,9 +679,7 @@ void ViewController::launch(FileData* game)
onFileChanged(game, true); onFileChanged(game, true);
// This is a workaround so that any keys or button presses used for exiting the emulator // This is a workaround so that any keys or button presses used for exiting the emulator
// are not captured upon returning. // are not captured upon returning.
setAnimation(new LambdaAnimation([](float t){}, 1), 0, [this] { setAnimation(new LambdaAnimation([](float t) {}, 1), 0, [this] { mLockInput = false; });
mLockInput = false;
});
}); });
} }
@ -728,36 +733,39 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
} }
// Create the view. // Create the view.
switch (selectedViewStyle) switch (selectedViewStyle) {
{ case VIDEO: {
case VIDEO:
view = std::shared_ptr<IGameListView>( view = std::shared_ptr<IGameListView>(
new VideoGameListView(mWindow, system->getRootFolder())); new VideoGameListView(mWindow, system->getRootFolder()));
mState.viewstyle = VIDEO; mState.viewstyle = VIDEO;
break; break;
case DETAILED: }
case DETAILED: {
view = std::shared_ptr<IGameListView>( view = std::shared_ptr<IGameListView>(
new DetailedGameListView(mWindow, system->getRootFolder())); new DetailedGameListView(mWindow, system->getRootFolder()));
mState.viewstyle = DETAILED; mState.viewstyle = DETAILED;
break; break;
case GRID: }
case GRID: {
view = std::shared_ptr<IGameListView>( view = std::shared_ptr<IGameListView>(
new GridGameListView(mWindow, system->getRootFolder())); new GridGameListView(mWindow, system->getRootFolder()));
mState.viewstyle = GRID; mState.viewstyle = GRID;
break; break;
case BASIC: }
default: case BASIC: {
}
default: {
view = std::shared_ptr<IGameListView>( view = std::shared_ptr<IGameListView>(
new BasicGameListView(mWindow, system->getRootFolder())); new BasicGameListView(mWindow, system->getRootFolder()));
mState.viewstyle = BASIC; mState.viewstyle = BASIC;
break; break;
} }
}
view->setTheme(system->getTheme()); view->setTheme(system->getTheme());
std::vector<SystemData*>& sysVec = SystemData::sSystemVector; std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
int id = static_cast<int>( int id = static_cast<int>(std::find(sysVec.cbegin(), sysVec.cend(), system) - sysVec.cbegin());
std::find(sysVec.cbegin(), sysVec.cend(), system) - sysVec.cbegin());
view->setPosition(id * static_cast<float>(Renderer::getScreenWidth()), view->setPosition(id * static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight() * 2)); static_cast<float>(Renderer::getScreenHeight() * 2));
@ -865,8 +873,8 @@ void ViewController::render(const Transform4x4f& parentTrans)
if (it->second == mCurrentView || (it->second == mPreviousView && isCameraMoving())) { if (it->second == mCurrentView || (it->second == mPreviousView && isCameraMoving())) {
// Clipping. // Clipping.
Vector3f guiStart = it->second->getPosition(); Vector3f guiStart = it->second->getPosition();
Vector3f guiEnd = it->second->getPosition() + Vector3f(it->second->getSize().x(), Vector3f guiEnd = it->second->getPosition() +
it->second->getSize().y(), 0); Vector3f(it->second->getSize().x(), it->second->getSize().y(), 0);
if (guiEnd.x() >= viewStart.x() && guiEnd.y() >= viewStart.y() && if (guiEnd.x() >= viewStart.x() && guiEnd.y() >= viewStart.y() &&
guiStart.x() <= viewEnd.x() && guiStart.y() <= viewEnd.y()) guiStart.x() <= viewEnd.x() && guiStart.y() <= viewEnd.y())
@ -890,13 +898,14 @@ void ViewController::preload()
{ {
unsigned int systemCount = static_cast<int>(SystemData::sSystemVector.size()); unsigned int systemCount = static_cast<int>(SystemData::sSystemVector.size());
for (auto it = SystemData::sSystemVector.cbegin(); for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend();
it != SystemData::sSystemVector.cend(); it ++) { it++) {
if (Settings::getInstance()->getBool("SplashScreen") && if (Settings::getInstance()->getBool("SplashScreen") &&
Settings::getInstance()->getBool("SplashScreenProgress")) { Settings::getInstance()->getBool("SplashScreenProgress")) {
mWindow->renderLoadingScreen("Loading '" + (*it)->getFullName() + "' (" + mWindow->renderLoadingScreen(
std::to_string(std::distance(SystemData::sSystemVector.cbegin(), it)+1) + "Loading '" + (*it)->getFullName() + "' (" +
"/" + std::to_string(systemCount) + ")"); std::to_string(std::distance(SystemData::sSystemVector.cbegin(), it) + 1) + "/" +
std::to_string(systemCount) + ")");
} }
(*it)->getIndex()->resetFilters(); (*it)->getIndex()->resetFilters();
getGameListView(*it); getGameListView(*it);

View file

@ -13,11 +13,11 @@
#ifndef ES_APP_VIEWS_VIEW_CONTROLLER_H #ifndef ES_APP_VIEWS_VIEW_CONTROLLER_H
#define ES_APP_VIEWS_VIEW_CONTROLLER_H #define ES_APP_VIEWS_VIEW_CONTROLLER_H
#include "FileData.h"
#include "GuiComponent.h"
#include "guis/GuiComplexTextEditPopup.h" #include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "renderers/Renderer.h" #include "renderers/Renderer.h"
#include "FileData.h"
#include "GuiComponent.h"
#include <vector> #include <vector>
@ -46,8 +46,10 @@ public:
// If a basic view detected a metadata change, it can request to recreate // If a basic view detected a metadata change, it can request to recreate
// the current gamelist view (as it may change to be detailed). // the current gamelist view (as it may change to be detailed).
void reloadGameListView(IGameListView* gamelist, bool reloadTheme = false); void reloadGameListView(IGameListView* gamelist, bool reloadTheme = false);
inline void reloadGameListView(SystemData* system, bool reloadTheme = false) void reloadGameListView(SystemData* system, bool reloadTheme = false)
{ reloadGameListView(getGameListView(system).get(), reloadTheme); } {
reloadGameListView(getGameListView(system).get(), reloadTheme);
}
// Reload everything with a theme. // Reload everything with a theme.
// Used when the "ThemeSet" setting changes. // Used when the "ThemeSet" setting changes.
void reloadAll(); void reloadAll();
@ -67,22 +69,26 @@ public:
void stopScrolling(); void stopScrolling();
void onFileChanged(FileData* file, bool reloadGameList); void onFileChanged(FileData* file, bool reloadGameList);
void triggerGameLaunch(FileData* game) { mGameToLaunch = game; mLockInput = true; }; void triggerGameLaunch(FileData* game)
bool getGameLaunchTriggered() { return (mGameToLaunch != nullptr); }; {
mGameToLaunch = game;
mLockInput = true;
};
bool getGameLaunchTriggered() { return (mGameToLaunch != nullptr); }
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;
void render(const Transform4x4f& parentTrans) override; void render(const Transform4x4f& parentTrans) override;
enum ViewMode { enum ViewMode {
NOTHING, NOTHING, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
START_SCREEN, START_SCREEN,
SYSTEM_SELECT, SYSTEM_SELECT,
GAME_LIST GAME_LIST
}; };
enum GameListViewStyle { enum GameListViewStyle {
AUTOMATIC, AUTOMATIC, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
BASIC, BASIC,
DETAILED, DETAILED,
GRID, GRID,
@ -93,7 +99,7 @@ public:
ViewMode viewing; ViewMode viewing;
GameListViewStyle viewstyle; GameListViewStyle viewstyle;
inline SystemData* getSystem() const SystemData* getSystem() const
{ {
assert(viewing == GAME_LIST || viewing == SYSTEM_SELECT); assert(viewing == GAME_LIST || viewing == SYSTEM_SELECT);
return system; return system;
@ -104,7 +110,7 @@ public:
SystemData* system; SystemData* system;
}; };
inline const State& getState() const { return mState; } const State& getState() const { return mState; }
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
virtual HelpStyle getHelpStyle() override; virtual HelpStyle getHelpStyle() override;
@ -143,6 +149,9 @@ private:
std::map<SystemData*, std::shared_ptr<IGameListView>> mGameListViews; std::map<SystemData*, std::shared_ptr<IGameListView>> mGameListViews;
std::shared_ptr<SystemView> mSystemListView; std::shared_ptr<SystemView> mSystemListView;
FileData* mGameToLaunch;
State mState;
Transform4x4f mCamera; Transform4x4f mCamera;
bool mSystemViewTransition; bool mSystemViewTransition;
bool mWrappedViews; bool mWrappedViews;
@ -151,9 +160,6 @@ private:
bool mCancelledTransition; // Needed only for the Fade transition style. bool mCancelledTransition; // Needed only for the Fade transition style.
bool mLockInput; bool mLockInput;
bool mNextSystem; bool mNextSystem;
FileData* mGameToLaunch;
State mState;
}; };
#endif // ES_APP_VIEWS_VIEW_CONTROLLER_H #endif // ES_APP_VIEWS_VIEW_CONTROLLER_H

View file

@ -8,15 +8,16 @@
#include "views/gamelist/BasicGameListView.h" #include "views/gamelist/BasicGameListView.h"
#include "utils/FileSystemUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "Settings.h" #include "Settings.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/FileSystemUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
BasicGameListView::BasicGameListView(Window* window, FileData* root) BasicGameListView::BasicGameListView(Window* window, FileData* root)
: ISimpleGameListView(window, root), mList(window) : ISimpleGameListView(window, root)
, mList(window)
{ {
mList.setSize(mSize.x(), mSize.y() * 0.8f); mList.setSize(mSize.x(), mSize.y() * 0.8f);
mList.setPosition(0, mSize.y() * 0.2f); mList.setPosition(0, mSize.y() * 0.2f);
@ -91,14 +92,14 @@ void BasicGameListView::populateList(const std::vector<FileData*>& files, FileDa
if ((*it)->getFavorite() && favoriteStar && if ((*it)->getFavorite() && favoriteStar &&
mRoot->getSystem()->getName() != "favorites") { mRoot->getSystem()->getName() != "favorites") {
if (Settings::getInstance()->getBool("SpecialCharsASCII")) if (Settings::getInstance()->getBool("SpecialCharsASCII"))
mList.add(inCollectionPrefix + "* " + mList.add(inCollectionPrefix + "* " + (*it)->getName(), *it,
(*it)->getName(), *it, ((*it)->getType() == FOLDER)); ((*it)->getType() == FOLDER));
else else
mList.add(inCollectionPrefix + ViewController::FAVORITE_CHAR + " " + mList.add(inCollectionPrefix + ViewController::FAVORITE_CHAR + " " +
(*it)->getName(), *it, ((*it)->getType() == FOLDER)); (*it)->getName(),
*it, ((*it)->getType() == FOLDER));
} }
else if ((*it)->getType() == FOLDER && else if ((*it)->getType() == FOLDER && mRoot->getSystem()->getName() != "collections") {
mRoot->getSystem()->getName() != "collections") {
if (Settings::getInstance()->getBool("SpecialCharsASCII")) if (Settings::getInstance()->getBool("SpecialCharsASCII"))
mList.add("# " + (*it)->getName(), *it, true); mList.add("# " + (*it)->getName(), *it, true);
else else
@ -117,11 +118,6 @@ void BasicGameListView::populateList(const std::vector<FileData*>& files, FileDa
generateFirstLetterIndex(files); generateFirstLetterIndex(files);
} }
FileData* BasicGameListView::getCursor()
{
return mList.getSelected();
}
void BasicGameListView::setCursor(FileData* cursor) void BasicGameListView::setCursor(FileData* cursor)
{ {
if (!mList.setCursor(cursor) && (!cursor->isPlaceHolder())) { if (!mList.setCursor(cursor) && (!cursor->isPlaceHolder())) {
@ -133,6 +129,7 @@ void BasicGameListView::setCursor(FileData* cursor)
if (mCursorStack.empty() || mCursorStack.top() != cursor->getParent()) { if (mCursorStack.empty() || mCursorStack.top() != cursor->getParent()) {
std::stack<FileData*> tmp; std::stack<FileData*> tmp;
FileData* ptr = cursor->getParent(); FileData* ptr = cursor->getParent();
while (ptr && ptr != mRoot) { while (ptr && ptr != mRoot) {
tmp.push(ptr); tmp.push(ptr);
ptr = ptr->getParent(); ptr = ptr->getParent();
@ -148,31 +145,6 @@ void BasicGameListView::setCursor(FileData* cursor)
} }
} }
FileData* BasicGameListView::getNextEntry()
{
return mList.getNext();
}
FileData* BasicGameListView::getPreviousEntry()
{
return mList.getPrevious();
}
FileData* BasicGameListView::getFirstEntry()
{
return mList.getFirst();
}
FileData* BasicGameListView::getLastEntry()
{
return mList.getLast();
}
FileData* BasicGameListView::getFirstGameEntry()
{
return mFirstGameEntry;
}
void BasicGameListView::addPlaceholder(FileData* firstEntry) void BasicGameListView::addPlaceholder(FileData* firstEntry)
{ {
// Empty list, add a placeholder. // Empty list, add a placeholder.
@ -186,18 +158,9 @@ void BasicGameListView::addPlaceholder(FileData* firstEntry)
mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER)); mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER));
} }
std::string BasicGameListView::getQuickSystemSelectRightButton()
{
return "right";
}
std::string BasicGameListView::getQuickSystemSelectLeftButton()
{
return "left";
}
void BasicGameListView::launch(FileData* game) void BasicGameListView::launch(FileData* game)
{ {
// This triggers ViewController to launch the game.
ViewController::get()->triggerGameLaunch(game); ViewController::get()->triggerGameLaunch(game);
} }
@ -251,8 +214,8 @@ void BasicGameListView::removeMedia(FileData* game)
// If there are no media files left in the directory after the deletion, then remove // If there are no media files left in the directory after the deletion, then remove
// the directory too. Remove any empty parent directories as well. // the directory too. Remove any empty parent directories as well.
auto removeEmptyDirFunc = [] auto removeEmptyDirFunc = [](std::string systemMediaDir, std::string mediaType,
(std::string systemMediaDir, std::string mediaType, std::string path) { std::string path) {
std::string parentPath = Utils::FileSystem::getParent(path); std::string parentPath = Utils::FileSystem::getParent(path);
while (parentPath != systemMediaDir + "/" + mediaType) { while (parentPath != systemMediaDir + "/" + mediaType) {
if (Utils::FileSystem::getDirContent(parentPath).size() == 0) { if (Utils::FileSystem::getDirContent(parentPath).size() == 0) {

View file

@ -21,30 +21,33 @@ public:
virtual void onFileChanged(FileData* file, bool reloadGameList) override; virtual void onFileChanged(FileData* file, bool reloadGameList) override;
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override; virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual FileData* getCursor() override;
virtual void setCursor(FileData* cursor) override; virtual void setCursor(FileData* cursor) override;
virtual FileData* getNextEntry() override;
virtual FileData* getPreviousEntry() override; virtual FileData* getCursor() override { return mList.getSelected(); }
virtual FileData* getFirstEntry() override; virtual FileData* getNextEntry() override { return mList.getNext(); }
virtual FileData* getLastEntry() override; virtual FileData* getPreviousEntry() override { return mList.getPrevious(); }
virtual FileData* getFirstGameEntry() override; virtual FileData* getFirstEntry() override { return mList.getFirst(); }
virtual FileData* getLastEntry() override { return mList.getLast(); }
virtual FileData* getFirstGameEntry() override { return mFirstGameEntry; }
virtual std::string getName() const override { return "basic"; } virtual std::string getName() const override { return "basic"; }
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
virtual void launch(FileData* game) override;
virtual bool isListScrolling() override { return mList.isScrolling(); }; virtual bool isListScrolling() override { return mList.isScrolling(); }
virtual void stopListScrolling() override { mList.stopScrolling(); }; virtual void stopListScrolling() override { mList.stopScrolling(); }
virtual const std::vector<std::string>& getFirstLetterIndex() override virtual const std::vector<std::string>& getFirstLetterIndex() override
{ return mFirstLetterIndex; }; {
return mFirstLetterIndex;
}
virtual void addPlaceholder(FileData* firstEntry = nullptr) override; virtual void addPlaceholder(FileData* firstEntry = nullptr) override;
virtual void launch(FileData* game) override;
protected: protected:
virtual std::string getQuickSystemSelectRightButton() override; virtual std::string getQuickSystemSelectRightButton() override { return "right"; }
virtual std::string getQuickSystemSelectLeftButton() override; virtual std::string getQuickSystemSelectLeftButton() override { return "left"; }
virtual void populateList(const std::vector<FileData*>& files, FileData* firstEntry) override; virtual void populateList(const std::vector<FileData*>& files, FileData* firstEntry) override;
virtual void remove(FileData* game, bool deleteFile) override; virtual void remove(FileData* game, bool deleteFile) override;
virtual void removeMedia(FileData* game) override; virtual void removeMedia(FileData* game) override;

View file

@ -8,45 +8,40 @@
#include "views/gamelist/DetailedGameListView.h" #include "views/gamelist/DetailedGameListView.h"
#include "animations/LambdaAnimation.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "SystemData.h" #include "SystemData.h"
#include "animations/LambdaAnimation.h"
#include "views/ViewController.h"
#define FADE_IN_START_OPACITY 0.5f #define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 650 #define FADE_IN_TIME 650
DetailedGameListView::DetailedGameListView( DetailedGameListView::DetailedGameListView(Window* window, FileData* root)
Window* window, : BasicGameListView(window, root)
FileData* root) , mDescContainer(window)
: BasicGameListView(window, root), , mDescription(window)
mDescContainer(window), , mGamelistInfo(window)
mDescription(window), , mThumbnail(window)
mGamelistInfo(window), , mMarquee(window)
, mImage(window)
mThumbnail(window), , mLblRating(window)
mMarquee(window), , mLblReleaseDate(window)
mImage(window), , mLblDeveloper(window)
, mLblPublisher(window)
mLblRating(window), , mLblGenre(window)
mLblReleaseDate(window), , mLblPlayers(window)
mLblDeveloper(window), , mLblLastPlayed(window)
mLblPublisher(window), , mLblPlayCount(window)
mLblGenre(window), , mRating(window)
mLblPlayers(window), , mReleaseDate(window)
mLblLastPlayed(window), , mDeveloper(window)
mLblPlayCount(window), , mPublisher(window)
, mGenre(window)
mRating(window), , mPlayers(window)
mReleaseDate(window), , mLastPlayed(window)
mDeveloper(window), , mPlayCount(window)
mPublisher(window), , mName(window)
mGenre(window), , mLastUpdated(nullptr)
mPlayers(window),
mLastPlayed(window),
mPlayCount(window),
mName(window),
mLastUpdated(nullptr)
{ {
const float padding = 0.01f; const float padding = 0.01f;
@ -114,8 +109,8 @@ DetailedGameListView::DetailedGameListView(
addChild(&mName); addChild(&mName);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f); mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2 * padding), mSize.y() - mDescContainer.setSize(mSize.x() * (0.50f - 2.0f * padding),
mDescContainer.getPosition().y()); mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true); mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40); mDescContainer.setDefaultZIndex(40);
addChild(&mDescContainer); addChild(&mDescContainer);
@ -150,10 +145,10 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
initMDLabels(); initMDLabels();
std::vector<TextComponent*> labels = getMDLabels(); std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8); assert(labels.size() == 8);
std::vector<std::string> lblElements = { std::vector<std::string> lblElements = { "md_lbl_rating", "md_lbl_releasedate",
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount" "md_lbl_genre", "md_lbl_players",
}; "md_lbl_lastplayed", "md_lbl_playcount" };
for (unsigned int i = 0; i < labels.size(); i++) for (unsigned int i = 0; i < labels.size(); i++)
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL); labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
@ -161,10 +156,9 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
initMDValues(); initMDValues();
std::vector<GuiComponent*> values = getMDValues(); std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8); assert(values.size() == 8);
std::vector<std::string> valElements = { std::vector<std::string> valElements = { "md_rating", "md_releasedate", "md_developer",
"md_rating", "md_releasedate", "md_developer", "md_publisher", "md_publisher", "md_genre", "md_players",
"md_genre", "md_players", "md_lastplayed", "md_playcount" "md_lastplayed", "md_playcount" };
};
for (unsigned int i = 0; i < values.size(); i++) for (unsigned int i = 0; i < values.size(); i++)
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
@ -172,7 +166,8 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
mDescContainer.applyTheme(theme, getName(), "md_description", mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE); POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x(), 0); mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", mDescription.applyTheme(
theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION)); ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT); mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT);
@ -232,13 +227,13 @@ void DetailedGameListView::initMDValues()
float bottom = 0.0f; float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2; const float colSize = (mSize.x() * 0.48f) / 2.0f;
for (unsigned int i = 0; i < labels.size(); i++) { for (unsigned int i = 0; i < labels.size(); i++) {
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2; const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2.0f;
values[i]->setPosition(labels[i]->getPosition() + values[i]->setPosition(labels[i]->getPosition() +
Vector3f(labels[i]->getSize().x(), heightDiff, 0)); Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y()); values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
values[i]->setDefaultZIndex(40); values[i]->setDefaultZIndex(40.0f);
float testBot = values[i]->getPosition().y() + values[i]->getSize().y(); float testBot = values[i]->getPosition().y() + values[i]->getSize().y();
@ -247,8 +242,8 @@ void DetailedGameListView::initMDValues()
} }
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f); mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.setSize(mDescContainer.getSize().x(),
mDescContainer.getPosition().y()); mSize.y() - mDescContainer.getPosition().y());
} }
void DetailedGameListView::updateInfoPanel() void DetailedGameListView::updateInfoPanel()
@ -334,8 +329,8 @@ void DetailedGameListView::updateInfoPanel()
// the first of these so that we can display its game media. // the first of these so that we can display its game media.
if (file->getSystem()->isCustomCollection() && if (file->getSystem()->isCustomCollection() &&
file->getPath() == file->getSystem()->getName()) { file->getPath() == file->getSystem()->getName()) {
mRandomGame = CollectionSystemsManager::get()-> mRandomGame =
updateCollectionFolderMetadata(file->getSystem()); CollectionSystemsManager::get()->updateCollectionFolderMetadata(file->getSystem());
if (mRandomGame) { if (mRandomGame) {
mThumbnail.setImage(mRandomGame->getThumbnailPath()); mThumbnail.setImage(mRandomGame->getThumbnailPath());
mMarquee.setImage(mRandomGame->getMarqueePath()); mMarquee.setImage(mRandomGame->getMarqueePath());
@ -371,16 +366,16 @@ void DetailedGameListView::updateInfoPanel()
else else
gamelistInfoString += ViewController::FILTER_CHAR + " " + gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " + " + std::to_string(mFilteredGameCount) + " + " +
std::to_string(mFilteredGameCountAll - mFilteredGameCount) + " / " + std::to_string(mFilteredGameCountAll - mFilteredGameCount) +
std::to_string(mGameCount); " / " + std::to_string(mGameCount);
} }
else { else {
gamelistInfoString += ViewController::CONTROLLER_CHAR + " " + gamelistInfoString +=
std::to_string(mGameCount); ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() && if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites")) file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
+ std::to_string(mFavoritesGameCount); std::to_string(mFavoritesGameCount);
} }
if (mIsFolder && infoAlign != ALIGN_RIGHT) if (mIsFolder && infoAlign != ALIGN_RIGHT)
@ -390,8 +385,8 @@ void DetailedGameListView::updateInfoPanel()
// Fade in the game image. // Fade in the game image.
auto func = [this](float t) { auto func = [this](float t) {
mImage.setOpacity(static_cast<unsigned char>(Math::lerp( mImage.setOpacity(static_cast<unsigned char>(
static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255)); Math::lerp(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
}; };
mImage.setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); mImage.setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);

View file

@ -8,48 +8,42 @@
#include "views/gamelist/GridGameListView.h" #include "views/gamelist/GridGameListView.h"
#include "animations/LambdaAnimation.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "Settings.h" #include "Settings.h"
#include "Sound.h" #include "Sound.h"
#include "SystemData.h" #include "SystemData.h"
#include "animations/LambdaAnimation.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#define FADE_IN_START_OPACITY 0.5f #define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 650 #define FADE_IN_TIME 650
GridGameListView::GridGameListView( GridGameListView::GridGameListView(Window* window, FileData* root)
Window* window, : ISimpleGameListView(window, root)
FileData* root) , mGrid(window)
: ISimpleGameListView(window, root), , mMarquee(window)
, mImage(window)
mGrid(window), , mDescContainer(window)
mMarquee(window), , mDescription(window)
mImage(window), , mGamelistInfo(window)
, mLblRating(window)
mDescContainer(window), , mLblReleaseDate(window)
mDescription(window), , mLblDeveloper(window)
mGamelistInfo(window), , mLblPublisher(window)
, mLblGenre(window)
mLblRating(window), , mLblPlayers(window)
mLblReleaseDate(window), , mLblLastPlayed(window)
mLblDeveloper(window), , mLblPlayCount(window)
mLblPublisher(window), , mRating(window)
mLblGenre(window), , mReleaseDate(window)
mLblPlayers(window), , mDeveloper(window)
mLblLastPlayed(window), , mPublisher(window)
mLblPlayCount(window), , mGenre(window)
, mPlayers(window)
mRating(window), , mLastPlayed(window)
mReleaseDate(window), , mPlayCount(window)
mDeveloper(window), , mName(window)
mPublisher(window),
mGenre(window),
mPlayers(window),
mLastPlayed(window),
mPlayCount(window),
mName(window)
{ {
const float padding = 0.01f; const float padding = 0.01f;
@ -95,8 +89,8 @@ GridGameListView::GridGameListView(
addChild(&mName); addChild(&mName);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f); mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2 * padding), mSize.y() - mDescContainer.setSize(mSize.x() * (0.50f - 2.0f * padding),
mDescContainer.getPosition().y()); mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true); mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40); mDescContainer.setDefaultZIndex(40);
addChild(&mDescContainer); addChild(&mDescContainer);
@ -107,7 +101,7 @@ GridGameListView::GridGameListView(
mMarquee.setOrigin(0.5f, 0.5f); mMarquee.setOrigin(0.5f, 0.5f);
mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f); mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f);
mMarquee.setMaxSize(mSize.x() * (0.5f - 2 * padding), mSize.y() * 0.18f); mMarquee.setMaxSize(mSize.x() * (0.5f - 2.0f * padding), mSize.y() * 0.18f);
mMarquee.setDefaultZIndex(35); mMarquee.setDefaultZIndex(35);
mMarquee.setVisible(false); mMarquee.setVisible(false);
addChild(&mMarquee); addChild(&mMarquee);
@ -130,10 +124,6 @@ GridGameListView::GridGameListView(
updateInfoPanel(); updateInfoPanel();
} }
GridGameListView::~GridGameListView()
{
}
void GridGameListView::onFileChanged(FileData* file, bool reloadGameList) void GridGameListView::onFileChanged(FileData* file, bool reloadGameList)
{ {
if (reloadGameList) { if (reloadGameList) {
@ -145,11 +135,6 @@ void GridGameListView::onFileChanged(FileData* file, bool reloadGameList)
ISimpleGameListView::onFileChanged(file, reloadGameList); ISimpleGameListView::onFileChanged(file, reloadGameList);
} }
FileData* GridGameListView::getCursor()
{
return mGrid.getSelected();
}
void GridGameListView::setCursor(FileData* cursor) void GridGameListView::setCursor(FileData* cursor)
{ {
if (!mGrid.setCursor(cursor) && (!cursor->isPlaceHolder())) { if (!mGrid.setCursor(cursor) && (!cursor->isPlaceHolder())) {
@ -176,47 +161,11 @@ void GridGameListView::setCursor(FileData* cursor)
} }
} }
FileData* GridGameListView::getNextEntry()
{
return mGrid.getNext();;
}
FileData* GridGameListView::getPreviousEntry()
{
return mGrid.getPrevious();
}
FileData* GridGameListView::getFirstEntry()
{
return mGrid.getFirst();;
}
FileData* GridGameListView::getLastEntry()
{
return mGrid.getLast();
}
FileData* GridGameListView::getFirstGameEntry()
{
return firstGameEntry;
}
std::string GridGameListView::getQuickSystemSelectRightButton()
{
return "rightshoulder";
}
std::string GridGameListView::getQuickSystemSelectLeftButton()
{
return "leftshoulder";
}
bool GridGameListView::input(InputConfig* config, Input input) bool GridGameListView::input(InputConfig* config, Input input)
{ {
if (input.value == 0 && (config->isMappedLike("left", input) || if (input.value == 0 &&
config->isMappedLike("right", input) || (config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
(config->isMappedLike("up", input)) || (config->isMappedLike("up", input)) || (config->isMappedLike("down", input))))
(config->isMappedLike("down", input)) ))
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
if (input.value != 0 && config->isMappedLike("righttrigger", input)) { if (input.value != 0 && config->isMappedLike("righttrigger", input)) {
@ -295,10 +244,10 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
initMDLabels(); initMDLabels();
std::vector<TextComponent*> labels = getMDLabels(); std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8); assert(labels.size() == 8);
std::vector<std::string> lblElements = { std::vector<std::string> lblElements = { "md_lbl_rating", "md_lbl_releasedate",
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount" "md_lbl_genre", "md_lbl_players",
}; "md_lbl_lastplayed", "md_lbl_playcount" };
for (unsigned int i = 0; i < labels.size(); i++) for (unsigned int i = 0; i < labels.size(); i++)
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL); labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
@ -306,10 +255,9 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
initMDValues(); initMDValues();
std::vector<GuiComponent*> values = getMDValues(); std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8); assert(values.size() == 8);
std::vector<std::string> valElements = { std::vector<std::string> valElements = { "md_rating", "md_releasedate", "md_developer",
"md_rating", "md_releasedate", "md_developer", "md_publisher", "md_publisher", "md_genre", "md_players",
"md_genre", "md_players", "md_lastplayed", "md_playcount" "md_lastplayed", "md_playcount" };
};
for (unsigned int i = 0; i < values.size(); i++) for (unsigned int i = 0; i < values.size(); i++)
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
@ -317,7 +265,8 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mDescContainer.applyTheme(theme, getName(), "md_description", mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE); POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x(), 0); mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", mDescription.applyTheme(
theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION)); ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
// Repopulate list in case a new theme is displaying a different image. // Repopulate list in case a new theme is displaying a different image.
@ -336,6 +285,12 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
sortChildren(); sortChildren();
} }
void GridGameListView::onShow()
{
GuiComponent::onShow();
updateInfoPanel();
}
void GridGameListView::initMDLabels() void GridGameListView::initMDLabels()
{ {
std::vector<TextComponent*> components = getMDLabels(); std::vector<TextComponent*> components = getMDLabels();
@ -383,9 +338,9 @@ void GridGameListView::initMDValues()
float bottom = 0.0f; float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2; const float colSize = (mSize.x() * 0.48f) / 2.0f;
for (unsigned int i = 0; i < labels.size(); i++) { for (unsigned int i = 0; i < labels.size(); i++) {
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2; const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2.0f;
values[i]->setPosition(labels[i]->getPosition() + values[i]->setPosition(labels[i]->getPosition() +
Vector3f(labels[i]->getSize().x(), heightDiff, 0)); Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y()); values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
@ -397,8 +352,8 @@ void GridGameListView::initMDValues()
} }
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f); mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.setSize(mDescContainer.getSize().x(),
mDescContainer.getPosition().y()); mSize.y() - mDescContainer.getPosition().y());
} }
void GridGameListView::updateInfoPanel() void GridGameListView::updateInfoPanel()
@ -465,17 +420,18 @@ void GridGameListView::updateInfoPanel()
if (mIsFiltered) { if (mIsFiltered) {
if (mFilteredGameCountAll == mFilteredGameCount) if (mFilteredGameCountAll == mFilteredGameCount)
gamelistInfoString += ViewController::FILTER_CHAR + " " gamelistInfoString += ViewController::FILTER_CHAR + " " +
+ std::to_string(mFilteredGameCount) + " / " + std::to_string(mGameCount); std::to_string(mFilteredGameCount) + " / " +
std::to_string(mGameCount);
else else
gamelistInfoString += ViewController::FILTER_CHAR + " " + gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " + " + std::to_string(mFilteredGameCount) + " + " +
std::to_string(mFilteredGameCountAll - mFilteredGameCount) + " / " + std::to_string(mFilteredGameCountAll - mFilteredGameCount) +
std::to_string(mGameCount); " / " + std::to_string(mGameCount);
} }
else { else {
gamelistInfoString += ViewController::CONTROLLER_CHAR + " " + gamelistInfoString +=
std::to_string(mGameCount); ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() && if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites")) file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " + gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
@ -489,8 +445,8 @@ void GridGameListView::updateInfoPanel()
// Fade in the game image. // Fade in the game image.
auto func = [this](float t) { auto func = [this](float t) {
mImage.setOpacity(static_cast<unsigned char>(Math::lerp( mImage.setOpacity(static_cast<unsigned char>(
static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255)); Math::lerp(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
}; };
mImage.setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); mImage.setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
@ -560,6 +516,7 @@ void GridGameListView::addPlaceholder(FileData* firstEntry)
void GridGameListView::launch(FileData* game) void GridGameListView::launch(FileData* game)
{ {
// This triggers ViewController to launch the game.
ViewController::get()->triggerGameLaunch(game); ViewController::get()->triggerGameLaunch(game);
} }
@ -605,8 +562,8 @@ void GridGameListView::removeMedia(FileData* game)
// If there are no media files left in the directory after the deletion, then remove // If there are no media files left in the directory after the deletion, then remove
// the directory too. Remove any empty parent directories as well. // the directory too. Remove any empty parent directories as well.
auto removeEmptyDirFunc = [] auto removeEmptyDirFunc = [](std::string systemMediaDir, std::string mediaType,
(std::string systemMediaDir, std::string mediaType, std::string path) { std::string path) {
std::string parentPath = Utils::FileSystem::getParent(path); std::string parentPath = Utils::FileSystem::getParent(path);
while (parentPath != systemMediaDir + "/" + mediaType) { while (parentPath != systemMediaDir + "/" + mediaType) {
if (Utils::FileSystem::getDirContent(parentPath).size() == 0) { if (Utils::FileSystem::getDirContent(parentPath).size() == 0) {
@ -724,13 +681,11 @@ std::vector<HelpPrompt> GridGameListView::getHelpPrompts()
if (!UIModeController::getInstance()->isUIModeKid()) if (!UIModeController::getInstance()->isUIModeKid())
prompts.push_back(HelpPrompt("back", "options")); prompts.push_back(HelpPrompt("back", "options"));
if (mRoot->getSystem()->isGameSystem() && if (mRoot->getSystem()->isGameSystem() && Settings::getInstance()->getBool("RandomAddButton"))
Settings::getInstance()->getBool("RandomAddButton"))
prompts.push_back(HelpPrompt("thumbstickclick", "random")); prompts.push_back(HelpPrompt("thumbstickclick", "random"));
if (mRoot->getSystem()->isGameSystem() && if (mRoot->getSystem()->isGameSystem() &&
(mRoot->getSystem()->getThemeFolder() != "custom-collections" || (mRoot->getSystem()->getThemeFolder() != "custom-collections" || !mCursorStack.empty()) &&
!mCursorStack.empty()) &&
!UIModeController::getInstance()->isUIModeKid() && !UIModeController::getInstance()->isUIModeKid() &&
!UIModeController::getInstance()->isUIModeKiosk() && !UIModeController::getInstance()->isUIModeKiosk() &&
(Settings::getInstance()->getBool("FavoritesAddButton") || (Settings::getInstance()->getBool("FavoritesAddButton") ||
@ -749,11 +704,6 @@ std::vector<HelpPrompt> GridGameListView::getHelpPrompts()
void GridGameListView::update(int deltaTime) void GridGameListView::update(int deltaTime)
{ {
// Update.
ISimpleGameListView::update(deltaTime); ISimpleGameListView::update(deltaTime);
} }
void GridGameListView::onShow()
{
GuiComponent::onShow();
updateInfoPanel();
}

View file

@ -20,48 +20,50 @@ class GridGameListView : public ISimpleGameListView
{ {
public: public:
GridGameListView(Window* window, FileData* root); GridGameListView(Window* window, FileData* root);
virtual ~GridGameListView(); virtual ~GridGameListView() {}
// Called when a FileData* is added, has its metadata changed, or is removed. // Called when a FileData* is added, has its metadata changed, or is removed.
virtual void onFileChanged(FileData* file, bool reloadGameList) override; virtual void onFileChanged(FileData* file, bool reloadGameList) override;
virtual void onShow() override; virtual void onShow() override;
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override; virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual FileData* getCursor() override;
virtual void setCursor(FileData* cursor) override; virtual void setCursor(FileData* cursor) override;
virtual FileData* getNextEntry() override;
virtual FileData* getPreviousEntry() override;
virtual FileData* getFirstEntry() override;
virtual FileData* getLastEntry() override;
virtual FileData* getFirstGameEntry() override;
virtual bool input(InputConfig* config, Input input) override; virtual FileData* getCursor() override { return mGrid.getSelected(); }
virtual FileData* getNextEntry() override { return mGrid.getNext(); }
virtual FileData* getPreviousEntry() override { return mGrid.getPrevious(); }
virtual FileData* getFirstEntry() override { return mGrid.getFirst(); }
virtual FileData* getLastEntry() override { return mGrid.getLast(); }
virtual FileData* getFirstGameEntry() override { return firstGameEntry; }
virtual std::string getName() const override { return "grid"; } virtual std::string getName() const override { return "grid"; }
virtual bool input(InputConfig* config, Input input) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
virtual void launch(FileData* game) override; virtual void launch(FileData* game) override;
virtual bool isListScrolling() override { return mGrid.isScrolling(); }; virtual bool isListScrolling() override { return mGrid.isScrolling(); }
virtual void stopListScrolling() override virtual void stopListScrolling() override
{ {
mGrid.stopAllAnimations(); mGrid.stopAllAnimations();
mGrid.stopScrolling(); mGrid.stopScrolling();
}; }
virtual const std::vector<std::string>& getFirstLetterIndex() override virtual const std::vector<std::string>& getFirstLetterIndex() override
{ return mFirstLetterIndex; }; {
return mFirstLetterIndex;
}
virtual void addPlaceholder(FileData* firstEntry = nullptr) override; virtual void addPlaceholder(FileData* firstEntry = nullptr) override;
protected: protected:
virtual void update(int deltaTime) override; virtual std::string getQuickSystemSelectRightButton() override { return "rightshoulder"; }
virtual std::string getQuickSystemSelectRightButton() override; virtual std::string getQuickSystemSelectLeftButton() override { return "leftshoulder"; }
virtual std::string getQuickSystemSelectLeftButton() override;
virtual void populateList(const std::vector<FileData*>& files, FileData* firstEntry) override; virtual void populateList(const std::vector<FileData*>& files, FileData* firstEntry) override;
virtual void remove(FileData* game, bool deleteFile) override; virtual void remove(FileData* game, bool deleteFile) override;
virtual void removeMedia(FileData* game) override; virtual void removeMedia(FileData* game) override;
virtual void update(int deltaTime) override;
ImageGridComponent<FileData*> mGrid; ImageGridComponent<FileData*> mGrid;
// Points to the first game in the list, i.e. the first entry which is of the type 'GAME'. // Points to the first game in the list, i.e. the first entry which is of the type 'GAME'.

View file

@ -8,17 +8,31 @@
#include "views/gamelist/IGameListView.h" #include "views/gamelist/IGameListView.h"
#include "guis/GuiGamelistOptions.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "Sound.h" #include "Sound.h"
#include "Window.h" #include "Window.h"
#include "guis/GuiGamelistOptions.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
IGameListView::IGameListView(Window* window, FileData* root)
: GuiComponent(window)
, mRoot(root)
{
setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
}
void IGameListView::setTheme(const std::shared_ptr<ThemeData>& theme)
{
mTheme = theme;
onThemeChanged(theme);
}
bool IGameListView::input(InputConfig* config, Input input) bool IGameListView::input(InputConfig* config, Input input)
{ {
// Select button opens GuiGamelistOptions. // Select button opens GuiGamelistOptions.
if (!UIModeController::getInstance()->isUIModeKid() && if (!UIModeController::getInstance()->isUIModeKid() && // Line break.
config->isMappedTo("back", input) && input.value) { config->isMappedTo("back", input) && input.value) {
ViewController::get()->cancelViewTransitions(); ViewController::get()->cancelViewTransitions();
stopListScrolling(); stopListScrolling();
@ -29,8 +43,8 @@ bool IGameListView::input(InputConfig* config, Input input)
// Ctrl-R reloads the view when debugging. // Ctrl-R reloads the view when debugging.
else if (Settings::getInstance()->getBool("Debug") && else if (Settings::getInstance()->getBool("Debug") &&
config->getDeviceId() == DEVICE_KEYBOARD && config->getDeviceId() == DEVICE_KEYBOARD &&
(SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) && (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) && input.id == SDLK_r &&
input.id == SDLK_r && input.value != 0) { input.value != 0) {
LOG(LogDebug) << "IGameListView::input(): Reloading view"; LOG(LogDebug) << "IGameListView::input(): Reloading view";
ViewController::get()->reloadGameListView(this, true); ViewController::get()->reloadGameListView(this, true);
return true; return true;
@ -39,12 +53,6 @@ bool IGameListView::input(InputConfig* config, Input input)
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
void IGameListView::setTheme(const std::shared_ptr<ThemeData>& theme)
{
mTheme = theme;
onThemeChanged(theme);
}
HelpStyle IGameListView::getHelpStyle() HelpStyle IGameListView::getHelpStyle()
{ {
HelpStyle style; HelpStyle style;

View file

@ -9,9 +9,9 @@
#ifndef ES_APP_VIEWS_GAME_LIST_IGAME_LIST_VIEW_H #ifndef ES_APP_VIEWS_GAME_LIST_IGAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_IGAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_IGAME_LIST_VIEW_H
#include "renderers/Renderer.h"
#include "FileData.h" #include "FileData.h"
#include "GuiComponent.h" #include "GuiComponent.h"
#include "renderers/Renderer.h"
class ThemeData; class ThemeData;
class Window; class Window;
@ -20,12 +20,7 @@ class Window;
class IGameListView : public GuiComponent class IGameListView : public GuiComponent
{ {
public: public:
IGameListView(Window* window, FileData* root) : GuiComponent(window), mRoot(root) IGameListView(Window* window, FileData* root);
{
setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
}
virtual ~IGameListView() {} virtual ~IGameListView() {}
// Called when a FileData* is added, has its metadata changed, or is removed. // Called when a FileData* is added, has its metadata changed, or is removed.
@ -35,7 +30,7 @@ public:
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) = 0; virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) = 0;
void setTheme(const std::shared_ptr<ThemeData>& theme); void setTheme(const std::shared_ptr<ThemeData>& theme);
inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; } const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; }
virtual FileData* getCursor() = 0; virtual FileData* getCursor() = 0;
virtual void setCursor(FileData*) = 0; virtual void setCursor(FileData*) = 0;

View file

@ -8,26 +8,24 @@
#include "views/gamelist/ISimpleGameListView.h" #include "views/gamelist/ISimpleGameListView.h"
#include "guis/GuiInfoPopup.h"
#include "utils/StringUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "Settings.h" #include "Settings.h"
#include "Sound.h" #include "Sound.h"
#include "SystemData.h" #include "SystemData.h"
#include "guis/GuiInfoPopup.h"
#include "utils/StringUtil.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "Log.h" #include "Log.h"
ISimpleGameListView::ISimpleGameListView( ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root)
Window* window, : IGameListView(window, root)
FileData* root) , mHeaderText(window)
: IGameListView(window, root), , mHeaderImage(window)
mHeaderText(window), , mBackground(window)
mHeaderImage(window), , mRandomGame(nullptr)
mBackground(window),
mRandomGame(nullptr)
{ {
mHeaderText.setText("Logo Text"); mHeaderText.setText("Logo Text");
mHeaderText.setSize(mSize.x(), 0); mHeaderText.setSize(mSize.x(), 0);
@ -124,7 +122,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
std::vector<FileData*> listEntries = cursor->getChildrenListToDisplay(); std::vector<FileData*> listEntries = cursor->getChildrenListToDisplay();
// Check if there is an entry in the cursor stack history matching any entry // Check if there is an entry in the cursor stack history matching any entry
// in the currect folder. If so, select that entry. // in the currect folder. If so, select that entry.
for (auto it = mCursorStackHistory.begin(); for (auto it = mCursorStackHistory.begin(); // Line break.
it != mCursorStackHistory.end(); it++) { it != mCursorStackHistory.end(); it++) {
if (std::find(listEntries.begin(), listEntries.end(), *it) != if (std::find(listEntries.begin(), listEntries.end(), *it) !=
listEntries.end()) { listEntries.end()) {
@ -185,8 +183,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
} }
else if (config->isMappedTo("x", input) && else if (config->isMappedTo("x", input) &&
mRoot->getSystem()->getThemeFolder() == "custom-collections" && mRoot->getSystem()->getThemeFolder() == "custom-collections" &&
mCursorStack.empty() && ViewController::get()->getState().viewing == mCursorStack.empty() &&
ViewController::GAME_LIST) { ViewController::get()->getState().viewing == ViewController::GAME_LIST) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
// Jump to the randomly selected game. // Jump to the randomly selected game.
if (mRandomGame) { if (mRandomGame) {
@ -239,9 +237,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
} }
else if (config->isMappedTo("y", input) && else if (config->isMappedTo("y", input) &&
mRoot->getSystem()->getThemeFolder() == "custom-collections" && mRoot->getSystem()->getThemeFolder() == "custom-collections" &&
!CollectionSystemsManager::get()->isEditing() && !CollectionSystemsManager::get()->isEditing() && mCursorStack.empty() &&
mCursorStack.empty() && ViewController::get()->getState().viewing == ViewController::get()->getState().viewing == ViewController::GAME_LIST) {
ViewController::GAME_LIST) {
// Jump to the randomly selected game. // Jump to the randomly selected game.
if (mRandomGame) { if (mRandomGame) {
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND);
@ -249,8 +246,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
// remove it so we don't get multiple entries. // remove it so we don't get multiple entries.
std::vector<FileData*> listEntries = std::vector<FileData*> listEntries =
mRandomGame->getSystem()->getRootFolder()->getChildrenListToDisplay(); mRandomGame->getSystem()->getRootFolder()->getChildrenListToDisplay();
for (auto it = mCursorStackHistory.begin(); for (auto it = mCursorStackHistory.begin(); it != mCursorStackHistory.end(); it++) {
it != mCursorStackHistory.end(); it++) {
if (std::find(listEntries.begin(), listEntries.end(), *it) != if (std::find(listEntries.begin(), listEntries.end(), *it) !=
listEntries.end()) { listEntries.end()) {
mCursorStackHistory.erase(it); mCursorStackHistory.erase(it);
@ -278,8 +274,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
getCursor()->getParent()->getPath() == "collections") { getCursor()->getParent()->getPath() == "collections") {
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND);
GuiInfoPopup* s; GuiInfoPopup* s;
s = new GuiInfoPopup(mWindow, s = new GuiInfoPopup(mWindow, "CAN'T ADD CUSTOM COLLECTIONS TO CUSTOM COLLECTIONS",
"CAN'T ADD CUSTOM COLLECTIONS TO CUSTOM COLLECTIONS", 4000); 4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
// Notify the user if attempting to add a placeholder to a custom collection. // Notify the user if attempting to add a placeholder to a custom collection.
@ -287,8 +283,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
mRoot->getSystem()->isGameSystem() && getCursor()->getType() == PLACEHOLDER) { mRoot->getSystem()->isGameSystem() && getCursor()->getType() == PLACEHOLDER) {
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND);
GuiInfoPopup* s; GuiInfoPopup* s;
s = new GuiInfoPopup(mWindow, s = new GuiInfoPopup(mWindow, "CAN'T ADD PLACEHOLDERS TO CUSTOM COLLECTIONS", 4000);
"CAN'T ADD PLACEHOLDERS TO CUSTOM COLLECTIONS", 4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
else if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER && else if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER &&
@ -317,8 +312,9 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else else
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
if (favoritesSorting && static_cast<std::string>( if (favoritesSorting &&
mRoot->getSystem()->getName()) != "recent" && !isEditing) { static_cast<std::string>(mRoot->getSystem()->getName()) != "recent" &&
!isEditing) {
FileData* entryToSelect; FileData* entryToSelect;
// Add favorite flag. // Add favorite flag.
if (!getCursor()->getFavorite()) { if (!getCursor()->getFavorite()) {
@ -348,8 +344,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
// For all other scenarios try to select the next entry, and if it doesn't // For all other scenarios try to select the next entry, and if it doesn't
// exist, select the previous entry. // exist, select the previous entry.
else { else {
entryToSelect = getCursor() != getNextEntry() ? entryToSelect =
getNextEntry() : getPreviousEntry(); getCursor() != getNextEntry() ? getNextEntry() : getPreviousEntry();
} }
} }
// Remove favorite flag. // Remove favorite flag.
@ -367,7 +363,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else if (foldersOnTop && else if (foldersOnTop &&
getCursor()->getFavorite() != getNextEntry()->getFavorite()) { getCursor()->getFavorite() != getNextEntry()->getFavorite()) {
entryToSelect = getPreviousEntry()->getType() == FOLDER ? entryToSelect = getPreviousEntry()->getType() == FOLDER ?
getCursor() : getPreviousEntry(); getCursor() :
getPreviousEntry();
} }
// If we are on the favorite marking boundary, select the previous entry. // If we are on the favorite marking boundary, select the previous entry.
else if (getCursor()->getFavorite() != getNextEntry()->getFavorite()) { else if (getCursor()->getFavorite() != getNextEntry()->getFavorite()) {
@ -376,8 +373,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
// For all other scenarios try to select the next entry, and if it doesn't // For all other scenarios try to select the next entry, and if it doesn't
// exist, select the previous entry. // exist, select the previous entry.
else { else {
entryToSelect = getCursor() != getNextEntry() ? entryToSelect =
getNextEntry() : getPreviousEntry(); getCursor() != getNextEntry() ? getNextEntry() : getPreviousEntry();
} }
// If we removed the last favorite marking, set the flag to jump to the // If we removed the last favorite marking, set the flag to jump to the
@ -399,22 +396,30 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
if (entryToUpdate->getType() == FOLDER) { if (entryToUpdate->getType() == FOLDER) {
GuiInfoPopup* s; GuiInfoPopup* s;
if (isEditing) { if (isEditing) {
s = new GuiInfoPopup(mWindow, s = new GuiInfoPopup(mWindow, "CAN'T ADD FOLDERS TO CUSTOM COLLECTIONS",
"CAN'T ADD FOLDERS TO CUSTOM COLLECTIONS", 4000); 4000);
} }
else { else {
MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata; MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata;
if (md->get("favorite") == "false") { if (md->get("favorite") == "false") {
md->set("favorite", "true"); md->set("favorite", "true");
s = new GuiInfoPopup(mWindow, "MARKED FOLDER '" + s = new GuiInfoPopup(
mWindow,
"MARKED FOLDER '" +
Utils::String::toUpper(Utils::String::removeParenthesis( Utils::String::toUpper(Utils::String::removeParenthesis(
entryToUpdate->getName())) + "' AS FAVORITE", 4000); entryToUpdate->getName())) +
"' AS FAVORITE",
4000);
} }
else { else {
md->set("favorite", "false"); md->set("favorite", "false");
s = new GuiInfoPopup(mWindow, "REMOVED FAVORITE MARKING FOR FOLDER '" + s = new GuiInfoPopup(
mWindow,
"REMOVED FAVORITE MARKING FOR FOLDER '" +
Utils::String::toUpper(Utils::String::removeParenthesis( Utils::String::toUpper(Utils::String::removeParenthesis(
entryToUpdate->getName())) + "'", 4000); entryToUpdate->getName())) +
"'",
4000);
} }
} }
@ -431,16 +436,19 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
// was unmarked. We couldn't do this earlier as we didn't have the list // was unmarked. We couldn't do this earlier as we didn't have the list
// sorted yet. // sorted yet.
if (removedLastFavorite) { if (removedLastFavorite) {
ViewController::get()->getGameListView(entryToUpdate-> ViewController::get()
getSystem())->setCursor(ViewController::get()-> ->getGameListView(entryToUpdate->getSystem())
getGameListView(entryToUpdate->getSystem())->getFirstEntry()); ->setCursor(ViewController::get()
->getGameListView(entryToUpdate->getSystem())
->getFirstEntry());
} }
return true; return true;
} }
else if (isEditing && entryToUpdate->metadata.get("nogamecount") == "true") { else if (isEditing && entryToUpdate->metadata.get("nogamecount") == "true") {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, GuiInfoPopup* s = new GuiInfoPopup(mWindow,
"CAN'T ADD ENTRIES THAT ARE NOT COUNTED " "CAN'T ADD ENTRIES THAT ARE NOT COUNTED "
"AS GAMES TO CUSTOM COLLECTIONS", 4000); "AS GAMES TO CUSTOM COLLECTIONS",
4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
else if (CollectionSystemsManager::get()->toggleGameInCollection(entryToUpdate)) { else if (CollectionSystemsManager::get()->toggleGameInCollection(entryToUpdate)) {
@ -451,9 +459,11 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
// Jump to the first entry in the gamelist if the last favorite was unmarked. // Jump to the first entry in the gamelist if the last favorite was unmarked.
if (foldersOnTop && removedLastFavorite && if (foldersOnTop && removedLastFavorite &&
!entryToUpdate->getSystem()->isCustomCollection()) { !entryToUpdate->getSystem()->isCustomCollection()) {
ViewController::get()->getGameListView(entryToUpdate->getSystem())-> ViewController::get()
setCursor(ViewController::get()->getGameListView(entryToUpdate-> ->getGameListView(entryToUpdate->getSystem())
getSystem())->getFirstGameEntry()); ->setCursor(ViewController::get()
->getGameListView(entryToUpdate->getSystem())
->getFirstGameEntry());
} }
else if (removedLastFavorite && else if (removedLastFavorite &&
!entryToUpdate->getSystem()->isCustomCollection()) { !entryToUpdate->getSystem()->isCustomCollection()) {
@ -469,8 +479,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
for (auto it = SystemData::sSystemVector.begin(); for (auto it = SystemData::sSystemVector.begin();
it != SystemData::sSystemVector.end(); it++) { it != SystemData::sSystemVector.end(); it++) {
ViewController::get()->getGameListView((*it))->onFileChanged( ViewController::get()->getGameListView((*it))->onFileChanged(
ViewController::get()->getGameListView((*it))-> ViewController::get()->getGameListView((*it))->getCursor(), false);
getCursor(), false);
} }
} }
return true; return true;
@ -511,13 +520,13 @@ void ISimpleGameListView::generateGamelistInfo(FileData* cursor, FileData* first
if (idx->isFiltered()) { if (idx->isFiltered()) {
mIsFiltered = true; mIsFiltered = true;
mFilteredGameCount = static_cast<unsigned int>(rootFolder-> mFilteredGameCount =
getFilesRecursive(GAME, true, false).size()); static_cast<unsigned int>(rootFolder->getFilesRecursive(GAME, true, false).size());
// Also count the games that are set to not be counted as games, as the filter may // Also count the games that are set to not be counted as games, as the filter may
// apply to such entries as well and this will be indicated with a separate '+ XX' // apply to such entries as well and this will be indicated with a separate '+ XX'
// in the GamelistInfo field. // in the GamelistInfo field.
mFilteredGameCountAll = static_cast<unsigned int>(rootFolder-> mFilteredGameCountAll =
getFilesRecursive(GAME, true, true).size()); static_cast<unsigned int>(rootFolder->getFilesRecursive(GAME, true, true).size());
} }
if (firstEntry->getParent() && firstEntry->getParent()->getType() == FOLDER) if (firstEntry->getParent() && firstEntry->getParent()->getType() == FOLDER)
@ -553,17 +562,21 @@ void ISimpleGameListView::generateFirstLetterIndex(const std::vector<FileData*>&
// Build the index. // Build the index.
for (auto it = files.begin(); it != files.end(); it++) { for (auto it = files.begin(); it != files.end(); it++) {
if ((*it)->getType() == FOLDER && (*it)->getFavorite() && if ((*it)->getType() == FOLDER && (*it)->getFavorite() && favoritesSorting &&
favoritesSorting && !onlyFavorites) !onlyFavorites) {
hasFavorites = true; hasFavorites = true;
else if ((*it)->getType() == FOLDER && foldersOnTop && !onlyFolders) }
else if ((*it)->getType() == FOLDER && foldersOnTop && !onlyFolders) {
hasFolders = true; hasFolders = true;
else if ((*it)->getType() == GAME && (*it)->getFavorite() && }
favoritesSorting && !onlyFavorites) else if ((*it)->getType() == GAME && (*it)->getFavorite() && favoritesSorting &&
!onlyFavorites) {
hasFavorites = true; hasFavorites = true;
else }
else {
mFirstLetterIndex.push_back(Utils::String::getFirstCharacter((*it)->getSortName())); mFirstLetterIndex.push_back(Utils::String::getFirstCharacter((*it)->getSortName()));
} }
}
// Sort and make each entry unique. // Sort and make each entry unique.
std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end()); std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end());

View file

@ -9,9 +9,9 @@
#ifndef ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H #ifndef ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H
#include "views/gamelist/IGameListView.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "views/gamelist/IGameListView.h"
#include <stack> #include <stack>
@ -39,9 +39,13 @@ public:
// These functions are used to retain the folder cursor history, for instance // These functions are used to retain the folder cursor history, for instance
// during a view reload. The calling function stores the history temporarily. // during a view reload. The calling function stores the history temporarily.
void copyCursorHistory(std::vector<FileData*>& cursorHistory) override void copyCursorHistory(std::vector<FileData*>& cursorHistory) override
{ cursorHistory = mCursorStackHistory; }; {
cursorHistory = mCursorStackHistory;
};
void populateCursorHistory(std::vector<FileData*>& cursorHistory) override void populateCursorHistory(std::vector<FileData*>& cursorHistory) override
{ mCursorStackHistory = cursorHistory; }; {
mCursorStackHistory = cursorHistory;
};
protected: protected:
virtual std::string getQuickSystemSelectRightButton() = 0; virtual std::string getQuickSystemSelectRightButton() = 0;

View file

@ -10,66 +10,52 @@
#include "animations/LambdaAnimation.h" #include "animations/LambdaAnimation.h"
#include "components/VideoFFmpegComponent.h" #include "components/VideoFFmpegComponent.h"
#if defined(_RPI_)
#include "components/VideoOmxComponent.h"
#endif
#if defined(BUILD_VLC_PLAYER) #if defined(BUILD_VLC_PLAYER)
#include "components/VideoVlcComponent.h" #include "components/VideoVlcComponent.h"
#endif #endif
#include "utils/FileSystemUtil.h"
#include "views/ViewController.h"
#include "AudioManager.h" #include "AudioManager.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "SystemData.h" #include "SystemData.h"
#include "utils/FileSystemUtil.h"
#include "views/ViewController.h"
#define FADE_IN_START_OPACITY 0.5f #define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 650 #define FADE_IN_TIME 650
VideoGameListView::VideoGameListView( VideoGameListView::VideoGameListView(Window* window, FileData* root)
Window* window, : BasicGameListView(window, root)
FileData* root) , mDescContainer(window)
: BasicGameListView(window, root), , mDescription(window)
mDescContainer(window), , mGamelistInfo(window)
mDescription(window), , mThumbnail(window)
mGamelistInfo(window), , mMarquee(window)
, mImage(window)
mThumbnail(window), , mVideo(nullptr)
mMarquee(window), , mVideoPlaying(false)
mImage(window), , mLblRating(window)
mVideo(nullptr), , mLblReleaseDate(window)
mVideoPlaying(false), , mLblDeveloper(window)
, mLblPublisher(window)
mLblRating(window), , mLblGenre(window)
mLblReleaseDate(window), , mLblPlayers(window)
mLblDeveloper(window), , mLblLastPlayed(window)
mLblPublisher(window), , mLblPlayCount(window)
mLblGenre(window), , mRating(window)
mLblPlayers(window), , mReleaseDate(window)
mLblLastPlayed(window), , mDeveloper(window)
mLblPlayCount(window), , mPublisher(window)
, mGenre(window)
mRating(window), , mPlayers(window)
mReleaseDate(window), , mLastPlayed(window)
mDeveloper(window), , mPlayCount(window)
mPublisher(window), , mName(window)
mGenre(window), , mLastUpdated(nullptr)
mPlayers(window),
mLastPlayed(window),
mPlayCount(window),
mName(window),
mLastUpdated(nullptr)
{ {
const float padding = 0.01f; const float padding = 0.01f;
// Create the correct type of video window. // Create the correct type of video window.
#if defined(_RPI_)
if (Settings::getInstance()->getBool("VideoOmxPlayer")) #if defined(BUILD_VLC_PLAYER)
mVideo = new VideoOmxComponent(window);
else if (Settings::getInstance()->getString("VideoPlayer") == "vlc")
mVideo = new VideoVlcComponent(window);
else
mVideo = new VideoFFmpegComponent(window);
#elif defined(BUILD_VLC_PLAYER)
if (Settings::getInstance()->getString("VideoPlayer") == "vlc") if (Settings::getInstance()->getString("VideoPlayer") == "vlc")
mVideo = new VideoVlcComponent(window); mVideo = new VideoVlcComponent(window);
else else
@ -87,21 +73,21 @@ VideoGameListView::VideoGameListView(
mThumbnail.setOrigin(0.5f, 0.5f); mThumbnail.setOrigin(0.5f, 0.5f);
mThumbnail.setPosition(2.0f, 2.0f); mThumbnail.setPosition(2.0f, 2.0f);
mThumbnail.setVisible(false); mThumbnail.setVisible(false);
mThumbnail.setMaxSize(mSize.x() * (0.25f - 2 * padding), mSize.y() * 0.10f); mThumbnail.setMaxSize(mSize.x() * (0.25f - 2.0f * padding), mSize.y() * 0.10f);
mThumbnail.setDefaultZIndex(35); mThumbnail.setDefaultZIndex(35);
addChild(&mThumbnail); addChild(&mThumbnail);
// Marquee. // Marquee.
mMarquee.setOrigin(0.5f, 0.5f); mMarquee.setOrigin(0.5f, 0.5f);
mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f); mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f);
mMarquee.setMaxSize(mSize.x() * (0.5f - 2 * padding), mSize.y() * 0.18f); mMarquee.setMaxSize(mSize.x() * (0.5f - 2.0f * padding), mSize.y() * 0.18f);
mMarquee.setDefaultZIndex(35); mMarquee.setDefaultZIndex(35);
addChild(&mMarquee); addChild(&mMarquee);
// Video. // Video.
mVideo->setOrigin(0.5f, 0.5f); mVideo->setOrigin(0.5f, 0.5f);
mVideo->setPosition(mSize.x() * 0.25f, mSize.y() * 0.4f); mVideo->setPosition(mSize.x() * 0.25f, mSize.y() * 0.4f);
mVideo->setSize(mSize.x() * (0.5f - 2 * padding), mSize.y() * 0.4f); mVideo->setSize(mSize.x() * (0.5f - 2.0f * padding), mSize.y() * 0.4f);
mVideo->setDefaultZIndex(30); mVideo->setDefaultZIndex(30);
addChild(mVideo); addChild(mVideo);
@ -140,8 +126,8 @@ VideoGameListView::VideoGameListView(
addChild(&mName); addChild(&mName);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f); mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2 * padding), mSize.y() - mDescContainer.setSize(mSize.x() * (0.50f - 2.0f * padding),
mDescContainer.getPosition().y()); mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true); mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40); mDescContainer.setDefaultZIndex(40);
addChild(&mDescContainer); addChild(&mDescContainer);
@ -160,10 +146,7 @@ VideoGameListView::VideoGameListView(
initMDValues(); initMDValues();
} }
VideoGameListView::~VideoGameListView() VideoGameListView::~VideoGameListView() { delete mVideo; }
{
delete mVideo;
}
void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme) void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{ {
@ -177,16 +160,17 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mImage.applyTheme(theme, getName(), "md_image", mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mVideo->applyTheme(theme, getName(), "md_video", mVideo->applyTheme(theme, getName(), "md_video",
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION |
VISIBLE);
mName.applyTheme(theme, getName(), "md_name", ALL); mName.applyTheme(theme, getName(), "md_name", ALL);
initMDLabels(); initMDLabels();
std::vector<TextComponent*> labels = getMDLabels(); std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8); assert(labels.size() == 8);
std::vector<std::string> lblElements = { std::vector<std::string> lblElements = { "md_lbl_rating", "md_lbl_releasedate",
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount" "md_lbl_genre", "md_lbl_players",
}; "md_lbl_lastplayed", "md_lbl_playcount" };
for (unsigned int i = 0; i < labels.size(); i++) for (unsigned int i = 0; i < labels.size(); i++)
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL); labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
@ -194,10 +178,9 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
initMDValues(); initMDValues();
std::vector<GuiComponent*> values = getMDValues(); std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8); assert(values.size() == 8);
std::vector<std::string> valElements = { std::vector<std::string> valElements = { "md_rating", "md_releasedate", "md_developer",
"md_rating", "md_releasedate", "md_developer", "md_publisher", "md_publisher", "md_genre", "md_players",
"md_genre", "md_players", "md_lastplayed", "md_playcount" "md_lastplayed", "md_playcount" };
};
for (unsigned int i = 0; i < values.size(); i++) for (unsigned int i = 0; i < values.size(); i++)
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT); values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
@ -205,7 +188,8 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mDescContainer.applyTheme(theme, getName(), "md_description", mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE); POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x(), 0); mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", mDescription.applyTheme(
theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION)); ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT); mGamelistInfo.applyTheme(theme, getName(), "gamelistInfo", ALL ^ ThemeFlags::TEXT);
@ -265,9 +249,9 @@ void VideoGameListView::initMDValues()
float bottom = 0.0f; float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2; const float colSize = (mSize.x() * 0.48f) / 2.0f;
for (unsigned int i = 0; i < labels.size(); i++) { for (unsigned int i = 0; i < labels.size(); i++) {
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2; const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2.0f;
values[i]->setPosition(labels[i]->getPosition() + values[i]->setPosition(labels[i]->getPosition() +
Vector3f(labels[i]->getSize().x(), heightDiff, 0)); Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y()); values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
@ -280,8 +264,8 @@ void VideoGameListView::initMDValues()
} }
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f); mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.setSize(mDescContainer.getSize().x(),
mDescContainer.getPosition().y()); mSize.y() - mDescContainer.getPosition().y());
} }
void VideoGameListView::updateInfoPanel() void VideoGameListView::updateInfoPanel()
@ -368,8 +352,8 @@ void VideoGameListView::updateInfoPanel()
// the first of these so that we can display its game media. // the first of these so that we can display its game media.
if (file->getSystem()->isCustomCollection() && if (file->getSystem()->isCustomCollection() &&
file->getPath() == file->getSystem()->getName()) { file->getPath() == file->getSystem()->getName()) {
mRandomGame = CollectionSystemsManager::get()-> mRandomGame =
updateCollectionFolderMetadata(file->getSystem()); CollectionSystemsManager::get()->updateCollectionFolderMetadata(file->getSystem());
if (mRandomGame) { if (mRandomGame) {
mThumbnail.setImage(mRandomGame->getThumbnailPath()); mThumbnail.setImage(mRandomGame->getThumbnailPath());
mMarquee.setImage(mRandomGame->getMarqueePath()); mMarquee.setImage(mRandomGame->getMarqueePath());
@ -398,7 +382,6 @@ void VideoGameListView::updateInfoPanel()
mVideo->setImage(file->getImagePath()); mVideo->setImage(file->getImagePath());
mVideo->onHide(); mVideo->onHide();
if (!mVideo->setVideo(file->getVideoPath())) if (!mVideo->setVideo(file->getVideoPath()))
mVideo->setDefaultVideo(); mVideo->setDefaultVideo();
} }
@ -423,16 +406,16 @@ void VideoGameListView::updateInfoPanel()
else else
gamelistInfoString += ViewController::FILTER_CHAR + " " + gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " + " + std::to_string(mFilteredGameCount) + " + " +
std::to_string(mFilteredGameCountAll - mFilteredGameCount) + " / " + std::to_string(mFilteredGameCountAll - mFilteredGameCount) +
std::to_string(mGameCount); " / " + std::to_string(mGameCount);
} }
else { else {
gamelistInfoString += ViewController::CONTROLLER_CHAR + " " + gamelistInfoString +=
std::to_string(mGameCount); ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() && if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites")) file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
+ std::to_string(mFavoritesGameCount); std::to_string(mFavoritesGameCount);
} }
if (mIsFolder && infoAlign != ALIGN_RIGHT) if (mIsFolder && infoAlign != ALIGN_RIGHT)
@ -442,8 +425,8 @@ void VideoGameListView::updateInfoPanel()
// Fade in the game image. // Fade in the game image.
auto func = [this](float t) { auto func = [this](float t) {
mVideo->setOpacity(static_cast<unsigned char>(Math::lerp( mVideo->setOpacity(static_cast<unsigned char>(
static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255)); Math::lerp(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
}; };
mVideo->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); mVideo->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
@ -498,10 +481,7 @@ void VideoGameListView::updateInfoPanel()
} }
} }
void VideoGameListView::launch(FileData* game) void VideoGameListView::launch(FileData* game) { ViewController::get()->triggerGameLaunch(game); }
{
ViewController::get()->triggerGameLaunch(game);
}
std::vector<TextComponent*> VideoGameListView::getMDLabels() std::vector<TextComponent*> VideoGameListView::getMDLabels()
{ {

View file

@ -48,7 +48,6 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextListComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextListComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoOmxComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h
# GUIs # GUIs
@ -126,7 +125,6 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoFFmpegComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoOmxComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp
# GUIs # GUIs

View file

@ -12,7 +12,7 @@
#include <string> #include <string>
enum AsyncHandleStatus { enum AsyncHandleStatus {
ASYNC_IN_PROGRESS, ASYNC_IN_PROGRESS, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
ASYNC_ERROR, ASYNC_ERROR,
ASYNC_DONE ASYNC_DONE
}; };
@ -21,17 +21,24 @@ enum AsyncHandleStatus {
class AsyncHandle class AsyncHandle
{ {
public: public:
AsyncHandle() : mStatus(ASYNC_IN_PROGRESS) {}; AsyncHandle()
virtual ~AsyncHandle() {}; : mStatus(ASYNC_IN_PROGRESS)
{
}
virtual ~AsyncHandle() {}
virtual void update() = 0; virtual void update() = 0;
// Update and return the latest status. // Update and return the latest status.
inline AsyncHandleStatus status() { update(); return mStatus; } AsyncHandleStatus status()
{
update();
return mStatus;
}
// User-friendly string of our current status. // User-friendly string of our current status.
// Will return error message if status() == SEARCH_ERROR. // Will return error message if status() == SEARCH_ERROR.
inline std::string getStatusString() std::string getStatusString()
{ {
switch (mStatus) { switch (mStatus) {
case ASYNC_IN_PROGRESS: case ASYNC_IN_PROGRESS:
@ -46,8 +53,12 @@ public:
} }
protected: protected:
inline void setStatus(AsyncHandleStatus status) { mStatus = status; } void setStatus(AsyncHandleStatus status) { mStatus = status; }
inline void setError(const std::string& error) { setStatus(ASYNC_ERROR); mError = error; } void setError(const std::string& error)
{
setStatus(ASYNC_ERROR);
mError = error;
}
std::string mError; std::string mError;
AsyncHandleStatus mStatus; AsyncHandleStatus mStatus;

View file

@ -19,17 +19,20 @@ std::vector<std::shared_ptr<Sound>> AudioManager::sSoundVector;
SDL_AudioDeviceID AudioManager::sAudioDevice = 0; SDL_AudioDeviceID AudioManager::sAudioDevice = 0;
SDL_AudioSpec AudioManager::sAudioFormat; SDL_AudioSpec AudioManager::sAudioFormat;
SDL_AudioStream* AudioManager::sConversionStream; SDL_AudioStream* AudioManager::sConversionStream;
bool AudioManager::sMuteStream = false; bool AudioManager::sMuteStream = false;
bool AudioManager::sHasAudioDevice = true; bool AudioManager::sHasAudioDevice = true;
bool AudioManager::mIsClearingStream = false; bool AudioManager::mIsClearingStream = false;
AudioManager::AudioManager() AudioManager::AudioManager()
{ {
// Init on construction.
init(); init();
} }
AudioManager::~AudioManager() AudioManager::~AudioManager()
{ {
// Deinit on destruction.
deinit(); deinit();
} }
@ -82,19 +85,19 @@ void AudioManager::init()
} }
if (sAudioFormat.freq != sRequestedAudioFormat.freq) { if (sAudioFormat.freq != sRequestedAudioFormat.freq) {
LOG(LogDebug) << "AudioManager::init(): Requested sample rate " << LOG(LogDebug) << "AudioManager::init(): Requested sample rate "
std::to_string(sRequestedAudioFormat.freq) << " could not be " << std::to_string(sRequestedAudioFormat.freq)
"set, obtained " << std::to_string(sAudioFormat.freq); << " could not be set, obtained " << std::to_string(sAudioFormat.freq);
} }
if (sAudioFormat.format != sRequestedAudioFormat.format) { if (sAudioFormat.format != sRequestedAudioFormat.format) {
LOG(LogDebug) << "AudioManager::init(): Requested format " << LOG(LogDebug) << "AudioManager::init(): Requested format "
std::to_string(sRequestedAudioFormat.format) << " could not be " << std::to_string(sRequestedAudioFormat.format)
"set, obtained " << std::to_string(sAudioFormat.format); << " could not be set, obtained " << std::to_string(sAudioFormat.format);
} }
if (sAudioFormat.channels != sRequestedAudioFormat.channels) { if (sAudioFormat.channels != sRequestedAudioFormat.channels) {
LOG(LogDebug) << "AudioManager::init(): Requested channel count " << LOG(LogDebug) << "AudioManager::init(): Requested channel count "
std::to_string(sRequestedAudioFormat.channels) << " could not be " << std::to_string(sRequestedAudioFormat.channels)
"set, obtained " << std::to_string(sAudioFormat.channels); << " could not be set, obtained " << std::to_string(sAudioFormat.channels);
} }
#if defined(_WIN64) || defined(__APPLE__) #if defined(_WIN64) || defined(__APPLE__)
// Beats me why the buffer size is not divided by the channel count on some operating systems. // Beats me why the buffer size is not divided by the channel count on some operating systems.
@ -102,9 +105,10 @@ void AudioManager::init()
#else #else
if (sAudioFormat.samples != sRequestedAudioFormat.samples / sRequestedAudioFormat.channels) { if (sAudioFormat.samples != sRequestedAudioFormat.samples / sRequestedAudioFormat.channels) {
#endif #endif
LOG(LogDebug) << "AudioManager::init(): Requested sample buffer size " << LOG(LogDebug) << "AudioManager::init(): Requested sample buffer size "
std::to_string(sRequestedAudioFormat.samples / sRequestedAudioFormat.channels) << << std::to_string(sRequestedAudioFormat.samples /
" could not be set, obtained " << std::to_string(sAudioFormat.samples); sRequestedAudioFormat.channels)
<< " could not be set, obtained " << std::to_string(sAudioFormat.samples);
} }
// Just in case someone changed the es_settings.xml file manually to invalid values. // Just in case someone changed the es_settings.xml file manually to invalid values.
@ -153,9 +157,9 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
restLength = len; restLength = len;
} }
// Mix sample into stream. // Mix sample into stream.
SDL_MixAudioFormat(stream, &(sound->getData()[sound->getPosition()]), SDL_MixAudioFormat(
sAudioFormat.format, restLength, static_cast<int>(Settings::getInstance()-> stream, &(sound->getData()[sound->getPosition()]), sAudioFormat.format, restLength,
getInt("SoundVolumeNavigation") * 1.28f)); static_cast<int>(Settings::getInstance()->getInt("SoundVolumeNavigation") * 1.28f));
if (sound->getPosition() + restLength < sound->getLength()) { if (sound->getPosition() + restLength < sound->getLength()) {
// Sample hasn't ended yet. // Sample hasn't ended yet.
stillPlaying = true; stillPlaying = true;
@ -191,8 +195,8 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
std::vector<Uint8> converted(chunkLength); std::vector<Uint8> converted(chunkLength);
int processedLength = SDL_AudioStreamGet(sConversionStream, int processedLength =
static_cast<void*>(&converted.at(0)), chunkLength); SDL_AudioStreamGet(sConversionStream, static_cast<void*>(&converted.at(0)), chunkLength);
if (processedLength < 0) { if (processedLength < 0) {
LOG(LogError) << "AudioManager::mixAudio(): Couldn't convert sound chunk:"; LOG(LogError) << "AudioManager::mixAudio(): Couldn't convert sound chunk:";
@ -213,7 +217,8 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
SDL_MixAudioFormat(stream, &converted.at(0), sAudioFormat.format, processedLength, 0); SDL_MixAudioFormat(stream, &converted.at(0), sAudioFormat.format, processedLength, 0);
} }
else { else {
SDL_MixAudioFormat(stream, &converted.at(0), sAudioFormat.format, processedLength, SDL_MixAudioFormat(
stream, &converted.at(0), sAudioFormat.format, processedLength,
static_cast<int>(Settings::getInstance()->getInt("SoundVolumeVideos") * 1.28f)); static_cast<int>(Settings::getInstance()->getInt("SoundVolumeVideos") * 1.28f));
} }
@ -224,6 +229,7 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
void AudioManager::registerSound(std::shared_ptr<Sound>& sound) void AudioManager::registerSound(std::shared_ptr<Sound>& sound)
{ {
// Add sound to sound vector.
sSoundVector.push_back(sound); sSoundVector.push_back(sound);
} }
@ -300,19 +306,37 @@ void AudioManager::clearStream()
// to empty the stream. // to empty the stream.
// SDL_AudioStreamClear(sConversionStream); // SDL_AudioStreamClear(sConversionStream);
// If sSoundVector is empty it means we are shutting down. In this case don't attempt
// to clear the stream as this could lead to a crash.
if (sSoundVector.empty())
return;
mIsClearingStream = true; mIsClearingStream = true;
int streamSize; // This code is required as there's seemingly a bug in SDL_AudioStreamAvailable().
int length = sAudioFormat.samples * 4; // The function sometimes returns 0 even if there is data left in the buffer, possibly
// because the remaining data is less than the configured sample size. It happens almost
// permanently on NetBSD but also on at least Linux from time to time. Adding some data
// to the stream buffer to get above this threshold before calling the function will
// return the proper number. So adding 10000 as we do here would give a return value of
// for instance 10880 instead of 0, assuming there were 880 bytes of data left in the buffer.
// Fortunately the SDL_AudioStreamGet() function acts correctly on any arbitrary sample size
// so we can actually clear the entire buffer. If this workaround was not implemented, there
// would be a sound glitch when some samples from the previous video would play any time a
// new video was started (assuming the issue was triggered be some remaining buffer data).
std::vector<Uint8> writeBuffer(10000);
if (SDL_AudioStreamPut(sConversionStream, reinterpret_cast<const void*>(&writeBuffer.at(0)),
10000) == -1) {
LOG(LogError) << "Failed to put samples in the conversion stream:";
LOG(LogError) << SDL_GetError();
mIsClearingStream = false;
return;
}
int length = SDL_AudioStreamAvailable(sConversionStream);
while ((streamSize = SDL_AudioStreamAvailable(sConversionStream)) > 0) {
std::vector<Uint8> readBuffer(length); std::vector<Uint8> readBuffer(length);
int processedLength = SDL_AudioStreamGet(sConversionStream, SDL_AudioStreamGet(sConversionStream, static_cast<void*>(&readBuffer.at(0)), length);
static_cast<void*>(&readBuffer.at(0)), length);
if (processedLength <= 0) {
break;
}
}
mIsClearingStream = false; mIsClearingStream = false;
} }

View file

@ -33,11 +33,12 @@ extern int SDL_USER_CECBUTTONUP;
CECInput* CECInput::sInstance = nullptr; CECInput* CECInput::sInstance = nullptr;
#if defined(HAVE_LIBCEC) #if defined(HAVE_LIBCEC)
static void onAlert(void* /*cbParam*/, const CEC::libcec_alert type, static void onAlert(void* /*cbParam*/,
const CEC::libcec_alert type,
const CEC::libcec_parameter param) const CEC::libcec_parameter param)
{ {
LOG(LogDebug) << "CECInput::onAlert type: " << CECInput::getAlertTypeString(type) << LOG(LogDebug) << "CECInput::onAlert type: " << CECInput::getAlertTypeString(type)
" parameter: " << reinterpret_cast<char*>(param.paramData); << " parameter: " << reinterpret_cast<char*>(param.paramData);
} }
static void onCommand(void* /*cbParam*/, const CEC::cec_command* command) static void onCommand(void* /*cbParam*/, const CEC::cec_command* command)
@ -93,7 +94,8 @@ void CECInput::deinit()
} }
} }
CECInput::CECInput() : mlibCEC(nullptr) CECInput::CECInput()
: mlibCEC(nullptr)
{ {
#if defined(HAVE_LIBCEC) #if defined(HAVE_LIBCEC)
#if defined(_RPI_) #if defined(_RPI_)
@ -136,8 +138,8 @@ CECInput::CECInput() : mlibCEC(nullptr)
} }
for (int i = 0; i < numAdapters; i++) for (int i = 0; i < numAdapters; i++)
LOG(LogDebug) << "CEC adapter: " << i << " path: " << adapters[i].strComPath << LOG(LogDebug) << "CEC adapter: " << i << " path: " << adapters[i].strComPath
" name: " << adapters[i].strComName; << " name: " << adapters[i].strComName;
if (!mlibCEC->Open(adapters[0].strComName)) { if (!mlibCEC->Open(adapters[0].strComName)) {
LOG(LogError) << "CECInput::mAdapter->Open failed"; LOG(LogError) << "CECInput::mAdapter->Open failed";
@ -167,6 +169,7 @@ CECInput::~CECInput()
std::string CECInput::getAlertTypeString(const unsigned int _type) std::string CECInput::getAlertTypeString(const unsigned int _type)
{ {
// clang-format off
switch (_type) { switch (_type) {
#if defined(HAVE_LIBCEC) #if defined(HAVE_LIBCEC)
case CEC::CEC_ALERT_SERVICE_DEVICE: { return "Service-Device"; } break; case CEC::CEC_ALERT_SERVICE_DEVICE: { return "Service-Device"; } break;
@ -180,10 +183,12 @@ std::string CECInput::getAlertTypeString(const unsigned int _type)
#endif // HAVE_LIBCEC #endif // HAVE_LIBCEC
default: { return "Unknown"; } break; default: { return "Unknown"; } break;
} }
// clang-format on
} }
std::string CECInput::getOpCodeString(const unsigned int _opCode) std::string CECInput::getOpCodeString(const unsigned int _opCode)
{ {
// clang-format off
switch (_opCode) { switch (_opCode) {
#if defined(HAVE_LIBCEC) #if defined(HAVE_LIBCEC)
case CEC::CEC_OPCODE_ACTIVE_SOURCE: { return "Active-Source"; } break; case CEC::CEC_OPCODE_ACTIVE_SOURCE: { return "Active-Source"; } break;
@ -261,10 +266,12 @@ std::string CECInput::getOpCodeString(const unsigned int _opCode)
#endif // HAVE_LIBCEC #endif // HAVE_LIBCEC
default: { return "Unknown"; } break; default: { return "Unknown"; } break;
} }
// clang-format on
} }
std::string CECInput::getKeyCodeString(const unsigned int _keyCode) std::string CECInput::getKeyCodeString(const unsigned int _keyCode)
{ {
// clang-format off
switch (_keyCode) { switch (_keyCode) {
#if defined(HAVE_LIBCEC) #if defined(HAVE_LIBCEC)
case CEC::CEC_USER_CONTROL_CODE_SELECT: { return "Select"; } break; case CEC::CEC_USER_CONTROL_CODE_SELECT: { return "Select"; } break;
@ -358,5 +365,6 @@ std::string CECInput::getKeyCodeString(const unsigned int _keyCode)
case 0: case 0:
#endif // HAVE_LIBCEC #endif // HAVE_LIBCEC
default: { return "Unknown"; } break; default: { return "Unknown"; } break;
// clang-format off
} }
} }

View file

@ -11,7 +11,10 @@
#include <string> #include <string>
namespace CEC { class ICECAdapter; } namespace CEC
{
class ICECAdapter;
}
class CECInput class CECInput
{ {

View file

@ -8,31 +8,30 @@
#include "GuiComponent.h" #include "GuiComponent.h"
#include "animations/Animation.h"
#include "animations/AnimationController.h"
#include "renderers/Renderer.h"
#include "Log.h" #include "Log.h"
#include "ThemeData.h" #include "ThemeData.h"
#include "Window.h" #include "Window.h"
#include "animations/Animation.h"
#include "renderers/Renderer.h"
#include <algorithm> #include <algorithm>
GuiComponent::GuiComponent(Window* window) GuiComponent::GuiComponent(Window* window)
: mWindow(window), : mWindow(window)
mParent(nullptr), , mParent(nullptr)
mColor(0), , mColor(0)
mColorShift(0), , mColorShift(0)
mColorShiftEnd(0), , mColorShiftEnd(0)
mOpacity(255), , mOpacity(255)
mSaturation(1.0), , mSaturation(1.0f)
mPosition(Vector3f::Zero()), , mPosition(Vector3f::Zero())
mOrigin(Vector2f::Zero()), , mOrigin(Vector2f::Zero())
mRotationOrigin(0.5, 0.5), , mRotationOrigin(0.5f, 0.5f)
mSize(Vector2f::Zero()), , mSize(Vector2f::Zero())
mTransform(Transform4x4f::Identity()), , mTransform(Transform4x4f::Identity())
mIsProcessing(false), , mIsProcessing(false)
mVisible(true), , mVisible(true)
mEnabled(true) , mEnabled(true)
{ {
for (unsigned char i = 0; i < MAX_ANIMATIONS; i++) for (unsigned char i = 0; i < MAX_ANIMATIONS; i++)
mAnimationMap[i] = nullptr; mAnimationMap[i] = nullptr;
@ -94,105 +93,30 @@ void GuiComponent::renderChildren(const Transform4x4f& transform) const
getChild(i)->render(transform); getChild(i)->render(transform);
} }
Vector3f GuiComponent::getPosition() const
{
return mPosition;
}
void GuiComponent::setPosition(float x, float y, float z) void GuiComponent::setPosition(float x, float y, float z)
{ {
mPosition = Vector3f(x, y, z); mPosition = Vector3f(x, y, z);
onPositionChanged(); onPositionChanged();
} }
Vector2f GuiComponent::getOrigin() const
{
return mOrigin;
}
void GuiComponent::setOrigin(float x, float y) void GuiComponent::setOrigin(float x, float y)
{ {
mOrigin = Vector2f(x, y); mOrigin = Vector2f(x, y);
onOriginChanged(); onOriginChanged();
} }
Vector2f GuiComponent::getRotationOrigin() const
{
return mRotationOrigin;
}
void GuiComponent::setRotationOrigin(float x, float y)
{
mRotationOrigin = Vector2f(x, y);
}
Vector2f GuiComponent::getSize() const
{
return mSize;
}
void GuiComponent::setSize(float w, float h) void GuiComponent::setSize(float w, float h)
{ {
mSize = Vector2f(w, h); mSize = Vector2f(w, h);
onSizeChanged(); onSizeChanged();
} }
float GuiComponent::getRotation() const
{
return mRotation;
}
void GuiComponent::setRotation(float rotation)
{
mRotation = rotation;
}
float GuiComponent::getScale() const
{
return mScale;
}
void GuiComponent::setScale(float scale)
{
mScale = scale;
}
float GuiComponent::getZIndex() const
{
return mZIndex;
}
void GuiComponent::setZIndex(float z)
{
mZIndex = z;
}
float GuiComponent::getDefaultZIndex() const
{
return mDefaultZIndex;
}
void GuiComponent::setDefaultZIndex(float z)
{
mDefaultZIndex = z;
}
bool GuiComponent::isVisible() const
{
return mVisible;
}
void GuiComponent::setVisible(bool visible)
{
mVisible = visible;
}
Vector2f GuiComponent::getCenter() const Vector2f GuiComponent::getCenter() const
{ {
return Vector2f(mPosition.x() - (getSize().x() * mOrigin.x()) + getSize().x() / 2, return Vector2f(mPosition.x() - (getSize().x() * mOrigin.x()) + getSize().x() / 2.0f,
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2); mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2.0f);
} }
// Children stuff.
void GuiComponent::addChild(GuiComponent* cmp) void GuiComponent::addChild(GuiComponent* cmp)
{ {
mChildren.push_back(cmp); mChildren.push_back(cmp);
@ -222,11 +146,6 @@ void GuiComponent::removeChild(GuiComponent* cmp)
} }
} }
void GuiComponent::clearChildren()
{
mChildren.clear();
}
void GuiComponent::sortChildren() void GuiComponent::sortChildren()
{ {
std::stable_sort(mChildren.begin(), mChildren.end(), [](GuiComponent* a, GuiComponent* b) { std::stable_sort(mChildren.begin(), mChildren.end(), [](GuiComponent* a, GuiComponent* b) {
@ -234,11 +153,6 @@ void GuiComponent::sortChildren()
}); });
} }
unsigned int GuiComponent::getChildCount() const
{
return static_cast<int>(mChildren.size());
}
int GuiComponent::getChildIndex() const int GuiComponent::getChildIndex() const
{ {
std::vector<GuiComponent*>::iterator it = std::vector<GuiComponent*>::iterator it =
@ -250,26 +164,6 @@ int GuiComponent::getChildIndex() const
return -1; return -1;
} }
GuiComponent* GuiComponent::getChild(unsigned int i) const
{
return mChildren.at(i);
}
void GuiComponent::setParent(GuiComponent* parent)
{
mParent = parent;
}
GuiComponent* GuiComponent::getParent() const
{
return mParent;
}
unsigned char GuiComponent::getOpacity() const
{
return mOpacity;
}
void GuiComponent::setOpacity(unsigned char opacity) void GuiComponent::setOpacity(unsigned char opacity)
{ {
mOpacity = opacity; mOpacity = opacity;
@ -277,92 +171,47 @@ void GuiComponent::setOpacity(unsigned char opacity)
(*it)->setOpacity(opacity); (*it)->setOpacity(opacity);
} }
unsigned int GuiComponent::getColor() const
{
return mColor;
}
unsigned int GuiComponent::getColorShift() const
{
return mColorShift;
}
void GuiComponent::setColor(unsigned int color)
{
mColor = color;
mColorOpacity = mColor & 0x000000FF;
}
float GuiComponent::getSaturation() const
{
return static_cast<float>(mColor);
}
void GuiComponent::setSaturation(float saturation)
{
mSaturation = saturation;
}
void GuiComponent::setColorShift(unsigned int color)
{
mColorShift = color;
mColorShiftEnd = color;
}
const Transform4x4f& GuiComponent::getTransform() const Transform4x4f& GuiComponent::getTransform()
{ {
mTransform = Transform4x4f::Identity(); mTransform = Transform4x4f::Identity();
mTransform.translate(mPosition); mTransform.translate(mPosition);
if (mScale != 1.0)
if (mScale != 1.0f)
mTransform.scale(mScale); mTransform.scale(mScale);
if (mRotation != 0.0) {
if (mRotation != 0.0f) {
// Calculate offset as difference between origin and rotation origin. // Calculate offset as difference between origin and rotation origin.
Vector2f rotationSize = getRotationSize(); Vector2f rotationSize = getRotationSize();
float xOff = (mOrigin.x() - mRotationOrigin.x()) * rotationSize.x(); float xOff = (mOrigin.x() - mRotationOrigin.x()) * rotationSize.x();
float yOff = (mOrigin.y() - mRotationOrigin.y()) * rotationSize.y(); float yOff = (mOrigin.y() - mRotationOrigin.y()) * rotationSize.y();
// Transform to offset point. // Transform to offset point.
if (xOff != 0.0 || yOff != 0.0) if (xOff != 0.0f || yOff != 0.0f)
mTransform.translate(Vector3f(xOff * -1, yOff * -1, 0.0f)); mTransform.translate(Vector3f(xOff * -1.0f, yOff * -1.0f, 0.0f));
// Apply rotation transform. // Apply rotation transform.
mTransform.rotateZ(mRotation); mTransform.rotateZ(mRotation);
// Transform back to original point. // Transform back to original point.
if (xOff != 0.0 || yOff != 0.0) if (xOff != 0.0f || yOff != 0.0f)
mTransform.translate(Vector3f(xOff, yOff, 0.0f)); mTransform.translate(Vector3f(xOff, yOff, 0.0f));
} }
mTransform.translate(Vector3f(mOrigin.x() * mSize.x() * -1, mTransform.translate(
mOrigin.y() * mSize.y() * -1, 0.0f)); Vector3f(mOrigin.x() * mSize.x() * -1.0f, mOrigin.y() * mSize.y() * -1.0f, 0.0f));
return mTransform; return mTransform;
} }
std::string GuiComponent::getValue() const
{
return "";
}
void GuiComponent::setValue(const std::string& /*value*/)
{
}
std::string GuiComponent::getHiddenValue() const
{
return "";
}
void GuiComponent::setHiddenValue(const std::string& /*value*/)
{
}
void GuiComponent::textInput(const std::string& text) void GuiComponent::textInput(const std::string& text)
{ {
for (auto iter = mChildren.cbegin(); iter != mChildren.cend(); iter++) for (auto iter = mChildren.cbegin(); iter != mChildren.cend(); iter++)
(*iter)->textInput(text); (*iter)->textInput(text);
} }
void GuiComponent::setAnimation(Animation* anim, int delay, void GuiComponent::setAnimation(Animation* anim,
std::function<void()> finishedCallback, bool reverse, unsigned char slot) int delay,
std::function<void()> finishedCallback,
bool reverse,
unsigned char slot)
{ {
assert(slot < MAX_ANIMATIONS); assert(slot < MAX_ANIMATIONS);
@ -447,28 +296,13 @@ void GuiComponent::cancelAllAnimations()
cancelAnimation(i); cancelAnimation(i);
} }
bool GuiComponent::isAnimationPlaying(unsigned char slot) const
{
return mAnimationMap[slot] != nullptr;
}
bool GuiComponent::isAnimationReversed(unsigned char slot) const
{
assert(mAnimationMap[slot] != nullptr);
return mAnimationMap[slot]->isReversed();
}
int GuiComponent::getAnimationTime(unsigned char slot) const
{
assert(mAnimationMap[slot] != nullptr);
return mAnimationMap[slot]->getTime();
}
void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& element, unsigned int properties) const std::string& view,
const std::string& element,
unsigned int properties)
{ {
Vector2f scale = getParent() ? getParent()->getSize() Vector2f scale = getParent() ? getParent()->getSize() :
: Vector2f(static_cast<float>(Renderer::getScreenWidth()), Vector2f(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())); static_cast<float>(Renderer::getScreenHeight()));
const ThemeData::ThemeElement* elem = theme->getElement(view, element, ""); const ThemeData::ThemeElement* elem = theme->getElement(view, element, "");
@ -484,9 +318,9 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (properties & ThemeFlags::SIZE && elem->has("size")) if (properties & ThemeFlags::SIZE && elem->has("size"))
setSize(elem->get<Vector2f>("size") * scale); setSize(elem->get<Vector2f>("size") * scale);
// Position + size also implies origin // Position + size also implies origin.
if ((properties & ORIGIN || (properties & POSITION && if ((properties & ORIGIN || (properties & POSITION && properties & ThemeFlags::SIZE)) &&
properties & ThemeFlags::SIZE)) && elem->has("origin")) { elem->has("origin")) {
setOrigin(elem->get<Vector2f>("origin")); setOrigin(elem->get<Vector2f>("origin"));
} }
@ -521,16 +355,6 @@ void GuiComponent::updateHelpPrompts()
mWindow->setHelpPrompts(prompts, getHelpStyle()); mWindow->setHelpPrompts(prompts, getHelpStyle());
} }
HelpStyle GuiComponent::getHelpStyle()
{
return HelpStyle();
}
bool GuiComponent::isProcessing() const
{
return mIsProcessing;
}
void GuiComponent::onShow() void GuiComponent::onShow()
{ {
for (unsigned int i = 0; i < getChildCount(); i++) for (unsigned int i = 0; i < getChildCount(); i++)

View file

@ -9,11 +9,12 @@
#ifndef ES_CORE_GUI_COMPONENT_H #ifndef ES_CORE_GUI_COMPONENT_H
#define ES_CORE_GUI_COMPONENT_H #define ES_CORE_GUI_COMPONENT_H
#include "math/Misc.h"
#include "math/Transform4x4f.h"
#include "HelpPrompt.h" #include "HelpPrompt.h"
#include "HelpStyle.h" #include "HelpStyle.h"
#include "InputConfig.h" #include "InputConfig.h"
#include "animations/AnimationController.h"
#include "math/Misc.h"
#include "math/Transform4x4f.h"
#include <functional> #include <functional>
#include <memory> #include <memory>
@ -65,72 +66,84 @@ public:
// 4. Tell your children to render, based on your component's transform - renderChildren(t). // 4. Tell your children to render, based on your component's transform - renderChildren(t).
virtual void render(const Transform4x4f& parentTrans); virtual void render(const Transform4x4f& parentTrans);
Vector3f getPosition() const; Vector3f getPosition() const { return mPosition; }
inline void setPosition(const Vector3f& offset) void setPosition(const Vector3f& offset) { setPosition(offset.x(), offset.y(), offset.z()); }
{ setPosition(offset.x(), offset.y(), offset.z()); }
void setPosition(float x, float y, float z = 0.0f); void setPosition(float x, float y, float z = 0.0f);
virtual void onPositionChanged() {}; virtual void onPositionChanged() {}
Vector2f getOrigin() const { return mOrigin; }
// Sets the origin as a percentage of this image. // Sets the origin as a percentage of this image.
// (e.g. (0, 0) is top left, (0.5, 0.5) is the center.) // (e.g. (0, 0) is top left, (0.5, 0.5) is the center.)
Vector2f getOrigin() const;
void setOrigin(float originX, float originY); void setOrigin(float originX, float originY);
inline void setOrigin(Vector2f origin) { setOrigin(origin.x(), origin.y()); } void setOrigin(Vector2f origin) { setOrigin(origin.x(), origin.y()); }
virtual void onOriginChanged() {}; virtual void onOriginChanged() {}
Vector2f getRotationOrigin() const { return mRotationOrigin; }
// Sets the rotation origin as a percentage of this image. // Sets the rotation origin as a percentage of this image.
// (e.g. (0, 0) is top left, (0.5, 0.5) is the center.) // (e.g. (0, 0) is top left, (0.5, 0.5) is the center.)
Vector2f getRotationOrigin() const; void setRotationOrigin(float originX, float originY)
void setRotationOrigin(float originX, float originY); {
inline void setRotationOrigin(Vector2f origin) mRotationOrigin = Vector2f(originX, originY);
{ setRotationOrigin(origin.x(), origin.y()); } }
void setRotationOrigin(Vector2f origin) { setRotationOrigin(origin.x(), origin.y()); }
virtual Vector2f getSize() const; virtual Vector2f getSize() const { return mSize; }
inline void setSize(const Vector2f& size) { setSize(size.x(), size.y()); } void setSize(const Vector2f& size) { setSize(size.x(), size.y()); }
void setSize(float w, float h); void setSize(float w, float h);
virtual void setResize(float width, float height) {}; virtual void setResize(float width, float height) {}
virtual void onSizeChanged() {}; virtual void onSizeChanged() {}
virtual Vector2f getRotationSize() const { return getSize(); }; virtual Vector2f getRotationSize() const { return getSize(); }
float getRotation() const { return mRotation; }
void setRotation(float rotation) { mRotation = rotation; }
void setRotationDegrees(float rotation)
{
setRotation(static_cast<float>(ES_DEG_TO_RAD(rotation)));
}
float getRotation() const; float getScale() const { return mScale; }
void setRotation(float rotation); void setScale(float scale) { mScale = scale; }
inline void setRotationDegrees(float rotation) {
setRotation(static_cast<float>(ES_DEG_TO_RAD(rotation))); }
float getScale() const; float getZIndex() const { return mZIndex; }
void setScale(float scale); void setZIndex(float zIndex) { mZIndex = zIndex; }
float getZIndex() const; float getDefaultZIndex() const { return mDefaultZIndex; }
void setZIndex(float zIndex); void setDefaultZIndex(float zIndex) { mDefaultZIndex = zIndex; }
float getDefaultZIndex() const; bool isVisible() const { return mVisible; }
void setDefaultZIndex(float zIndex); void setVisible(bool visible) { mVisible = visible; }
bool isVisible() const;
void setVisible(bool visible);
// Returns the center point of the image (takes origin into account). // Returns the center point of the image (takes origin into account).
Vector2f getCenter() const; Vector2f getCenter() const;
void setParent(GuiComponent* parent); void setParent(GuiComponent* parent) { mParent = parent; }
GuiComponent* getParent() const; GuiComponent* getParent() const { return mParent; }
void addChild(GuiComponent* cmp); void addChild(GuiComponent* cmp);
void removeChild(GuiComponent* cmp); void removeChild(GuiComponent* cmp);
void clearChildren(); void clearChildren() { mChildren.clear(); }
void sortChildren(); void sortChildren();
unsigned int getChildCount() const; unsigned int getChildCount() const { return static_cast<int>(mChildren.size()); }
int getChildIndex() const; int getChildIndex() const;
GuiComponent* getChild(unsigned int i) const; GuiComponent* getChild(unsigned int i) const { return mChildren.at(i); }
// Animation will be automatically deleted when it completes or is stopped. // Animation will be automatically deleted when it completes or is stopped.
bool isAnimationPlaying(unsigned char slot) const; bool isAnimationPlaying(unsigned char slot) const { return mAnimationMap[slot] != nullptr; }
bool isAnimationReversed(unsigned char slot) const; bool isAnimationReversed(unsigned char slot) const
int getAnimationTime(unsigned char slot) const; {
void setAnimation(Animation* animation, int delay = 0, assert(mAnimationMap[slot] != nullptr);
return mAnimationMap[slot]->isReversed();
}
int getAnimationTime(unsigned char slot) const
{
assert(mAnimationMap[slot] != nullptr);
return mAnimationMap[slot]->getTime();
}
void setAnimation(Animation* animation,
int delay = 0,
std::function<void()> finishedCallback = nullptr, std::function<void()> finishedCallback = nullptr,
bool reverse = false, unsigned char slot = 0); bool reverse = false,
unsigned char slot = 0);
bool stopAnimation(unsigned char slot); bool stopAnimation(unsigned char slot);
// Like stopAnimation, but doesn't call finishedCallback - only removes the animation, leaving // Like stopAnimation, but doesn't call finishedCallback - only removes the animation, leaving
// things in their current state. Returns true if successful (an animation was in this slot). // things in their current state. Returns true if successful (an animation was in this slot).
@ -143,45 +156,53 @@ public:
void stopAllAnimations(); void stopAllAnimations();
void cancelAllAnimations(); void cancelAllAnimations();
virtual bool isListScrolling() { return false; }; virtual bool isListScrolling() { return false; }
virtual void stopListScrolling() {}; virtual void stopListScrolling() {}
virtual unsigned char getOpacity() const; virtual unsigned char getOpacity() const { return mOpacity; }
virtual void setOpacity(unsigned char opacity); virtual void setOpacity(unsigned char opacity);
virtual unsigned int getColor() const; virtual unsigned int getColor() const { return mColor; }
virtual unsigned int getColorShift() const; virtual unsigned int getColorShift() const { return mColorShift; }
virtual void setColor(unsigned int color); virtual void setColor(unsigned int color)
virtual float getSaturation() const; {
virtual void setSaturation(float saturation); mColor = color;
virtual void setColorShift(unsigned int color); mColorOpacity = mColor & 0x000000FF;
virtual void setOriginalColor(unsigned int color) { mColorOriginalValue = color; }; }
virtual void setChangedColor(unsigned int color) { mColorChangedValue = color; }; virtual float getSaturation() const { return static_cast<float>(mColor); }
virtual void setSaturation(float saturation) { mSaturation = saturation; }
virtual void setColorShift(unsigned int color)
{
mColorShift = color;
mColorShiftEnd = color;
}
virtual void setOriginalColor(unsigned int color) { mColorOriginalValue = color; }
virtual void setChangedColor(unsigned int color) { mColorChangedValue = color; }
// These functions are used to enable and disable options in menus, i.e. switches and similar. // These functions are used to enable and disable options in menus, i.e. switches and similar.
virtual bool getEnabled() { return mEnabled; }; virtual bool getEnabled() { return mEnabled; }
virtual void setEnabled(bool state) { mEnabled = state; }; virtual void setEnabled(bool state) { mEnabled = state; }
virtual std::shared_ptr<Font> getFont() const { return nullptr; }; virtual std::shared_ptr<Font> getFont() const { return nullptr; }
const Transform4x4f& getTransform(); const Transform4x4f& getTransform();
virtual std::string getValue() const; virtual std::string getValue() const { return ""; }
virtual void setValue(const std::string& value); virtual void setValue(const std::string& value) {}
virtual std::string getHiddenValue() const; virtual std::string getHiddenValue() const { return ""; }
virtual void setHiddenValue(const std::string& value); virtual void setHiddenValue(const std::string& value) {}
// Used to set the parameters for ScrollableContainer. // Used to set the parameters for ScrollableContainer.
virtual void setScrollParameters(float, float, int) {}; virtual void setScrollParameters(float, float, int) {}
virtual void onFocusGained() {}; virtual void onFocusGained() {}
virtual void onFocusLost() {}; virtual void onFocusLost() {}
virtual void onShow(); virtual void onShow();
virtual void onHide(); virtual void onHide();
virtual void onStopVideo(); virtual void onStopVideo();
virtual void onPauseVideo(); virtual void onPauseVideo();
virtual void onUnpauseVideo(); virtual void onUnpauseVideo();
virtual bool isVideoPaused() { return false; }; virtual bool isVideoPaused() { return false; }
virtual void onScreensaverActivate(); virtual void onScreensaverActivate();
virtual void onScreensaverDeactivate(); virtual void onScreensaverDeactivate();
@ -192,18 +213,20 @@ public:
// Default implementation just handles <pos> and <size> tags as normalized float pairs. // Default implementation just handles <pos> and <size> tags as normalized float pairs.
// You probably want to keep this behavior for any derived classes as well as add your own. // You probably want to keep this behavior for any derived classes as well as add your own.
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& element, unsigned int properties); const std::string& view,
const std::string& element,
unsigned int properties);
// Returns a list of help prompts. // Returns a list of help prompts.
virtual std::vector<HelpPrompt> getHelpPrompts() { return std::vector<HelpPrompt>(); }; virtual std::vector<HelpPrompt> getHelpPrompts() { return std::vector<HelpPrompt>(); }
// Called whenever help prompts change. // Called whenever help prompts change.
void updateHelpPrompts(); void updateHelpPrompts();
virtual HelpStyle getHelpStyle(); virtual HelpStyle getHelpStyle() { return HelpStyle(); }
// Returns true if the component is busy doing background processing (e.g. HTTP downloads). // Returns true if the component is busy doing background processing (e.g. HTTP downloads).
bool isProcessing() const; bool isProcessing() const { return mIsProcessing; }
const static unsigned char MAX_ANIMATIONS = 4; const static unsigned char MAX_ANIMATIONS = 4;

View file

@ -31,8 +31,8 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
return; return;
if (elem->has("pos")) if (elem->has("pos"))
position = elem->get<Vector2f>("pos") * position =
Vector2f(static_cast<float>(Renderer::getScreenWidth()), elem->get<Vector2f>("pos") * Vector2f(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())); static_cast<float>(Renderer::getScreenHeight()));
if (elem->has("origin")) if (elem->has("origin"))

View file

@ -10,9 +10,9 @@
#include "HttpReq.h" #include "HttpReq.h"
#include "Log.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "Log.h"
#include <assert.h> #include <assert.h>
@ -43,11 +43,13 @@ bool HttpReq::isUrl(const std::string& str)
{ {
// The worst guess. // The worst guess.
return (!str.empty() && !Utils::FileSystem::exists(str) && return (!str.empty() && !Utils::FileSystem::exists(str) &&
(str.find("http://") != std::string::npos || str.find("https://") != (str.find("http://") != std::string::npos ||
std::string::npos || str.find("www.") != std::string::npos)); str.find("https://") != std::string::npos || str.find("www.") != std::string::npos));
} }
HttpReq::HttpReq(const std::string& url) : mStatus(REQ_IN_PROGRESS), mHandle(nullptr) HttpReq::HttpReq(const std::string& url)
: mStatus(REQ_IN_PROGRESS)
, mHandle(nullptr)
{ {
// The multi-handle is cleaned up via a call from GuiScraperSearch after the scraping // The multi-handle is cleaned up via a call from GuiScraperSearch after the scraping
// has been completed for a game, meaning the handle is valid for all cURL requests // has been completed for a game, meaning the handle is valid for all cURL requests
@ -57,13 +59,15 @@ HttpReq::HttpReq(const std::string& url) : mStatus(REQ_IN_PROGRESS), mHandle(nul
mHandle = curl_easy_init(); mHandle = curl_easy_init();
#if defined(_WIN64)
// On Windows, use the bundled cURL TLS/SSL certificates (which actually come from the // On Windows, use the bundled cURL TLS/SSL certificates (which actually come from the
// Mozilla project). There is a possibility to use the OS provided Schannel certificates // Mozilla project). There is a possibility to use the OS provided Schannel certificates
// but I haven't been able to get this to work and it also seems to be problematic on // but I haven't been able to get this to work and it also seems to be problematic on
// older Windows versions. // older Windows versions.
#if defined(_WIN64) curl_easy_setopt(mHandle, CURLOPT_CAINFO,
curl_easy_setopt(mHandle, CURLOPT_CAINFO, ResourceManager::getInstance()-> ResourceManager::getInstance()
getResourcePath(":/certificates/curl-ca-bundle.crt").c_str()); ->getResourcePath(":/certificates/curl-ca-bundle.crt")
.c_str());
#endif #endif
if (mHandle == nullptr) { if (mHandle == nullptr) {
@ -140,8 +144,8 @@ HttpReq::~HttpReq()
CURLMcode merr = curl_multi_remove_handle(s_multi_handle, mHandle); CURLMcode merr = curl_multi_remove_handle(s_multi_handle, mHandle);
if (merr != CURLM_OK) { if (merr != CURLM_OK) {
LOG(LogError) << "Error removing curl_easy handle from curl_multi: " << LOG(LogError) << "Error removing curl_easy handle from curl_multi: "
curl_multi_strerror(merr); << curl_multi_strerror(merr);
} }
curl_easy_cleanup(mHandle); curl_easy_cleanup(mHandle);
@ -194,16 +198,6 @@ std::string HttpReq::getContent() const
return mContent.str(); return mContent.str();
} }
void HttpReq::onError(const std::string& msg)
{
mErrorMsg = msg;
}
std::string HttpReq::getErrorMsg()
{
return mErrorMsg;
}
// Used as a curl callback. // Used as a curl callback.
// size = size of an element, nmemb = number of elements. // size = size of an element, nmemb = number of elements.
// Return value is number of elements successfully read. // Return value is number of elements successfully read.

View file

@ -43,6 +43,7 @@ public:
~HttpReq(); ~HttpReq();
enum Status { enum Status {
// clang-format off
REQ_IN_PROGRESS, // Request is in progress. REQ_IN_PROGRESS, // Request is in progress.
REQ_SUCCESS, // Request completed successfully, get it with getContent(). REQ_SUCCESS, // Request completed successfully, get it with getContent().
REQ_IO_ERROR, // Some error happened, get it with getErrorMsg(). REQ_IO_ERROR, // Some error happened, get it with getErrorMsg().
@ -50,10 +51,11 @@ public:
REQ_BAD_STATUS_CODE, // Some invalid HTTP response status code happened (non-200). REQ_BAD_STATUS_CODE, // Some invalid HTTP response status code happened (non-200).
REQ_INVALID_RESPONSE, // The HTTP response was invalid. REQ_INVALID_RESPONSE, // The HTTP response was invalid.
REQ_UNDEFINED_ERROR REQ_UNDEFINED_ERROR
// clang-format on
}; };
Status status(); // Process any received data and return the status afterwards. Status status(); // Process any received data and return the status afterwards.
std::string getErrorMsg(); std::string getErrorMsg() { return mErrorMsg; }
std::string getContent() const; // mStatus must be REQ_SUCCESS. std::string getContent() const; // mStatus must be REQ_SUCCESS.
static std::string urlEncode(const std::string& s); static std::string urlEncode(const std::string& s);
@ -65,11 +67,11 @@ public:
curl_multi_cleanup(s_multi_handle); curl_multi_cleanup(s_multi_handle);
s_multi_handle = nullptr; s_multi_handle = nullptr;
} }
}; }
private: private:
static size_t write_content(void* buff, size_t size, size_t nmemb, void* req_ptr); static size_t write_content(void* buff, size_t size, size_t nmemb, void* req_ptr);
void onError(const std::string& msg); void onError(const std::string& msg) { mErrorMsg = msg; }
// God dammit libcurl why can't you have some way to check the status of an // God dammit libcurl why can't you have some way to check the status of an
// individual handle why do I have to handle ALL messages at once. // individual handle why do I have to handle ALL messages at once.

Some files were not shown because too many files have changed in this diff Show more