merge master

This commit is contained in:
Sophia Hadash 2021-09-23 23:28:19 +02:00
commit dbf76f92e6
111 changed files with 3256 additions and 1696 deletions

View file

@ -11,25 +11,51 @@
### Detailed list of changes ### Detailed list of changes
* Added alternative emulators support where additional emulators can be defined in es_systems.xml and be selected system-wide or per game via the user interface * Added alternative emulators support where additional emulators can be defined in es_systems.xml and be selected system-wide or per game via the user interface
* Added a virtual keyboard partly based on code from batocera-emulationstation
* Added the ability to make complementary game system customizations without having to replace the entire bundled es_systems.xml file
* Added a menu option to change the application exit key combination * Added a menu option to change the application exit key combination
* Expanded the themeable options for "helpsystem" to support custom button graphics, dimmed text and icon colors, upper/lower/camel case and custom spacing
* Added support for using the left and right trigger buttons in the help prompts
* Removed the "Choose" entry from the help prompts in the gamelist view * Removed the "Choose" entry from the help prompts in the gamelist view
* Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver" * Changed the "Toggle screensaver" help entry in the system view to simply "Screensaver"
* Added support for upscaling bitmap images using linear filtering * Added support for upscaling bitmap images using linear filtering
* Changed the marquee image upscale filtering from nearest neighbor to linear for the launch screen and the gamelist views * Changed the marquee image upscale filtering from nearest neighbor to linear for the launch screen and the gamelist views
* Moved the Media Viewer and Screensaver settings higher in the UI Settings menu * Moved the Media Viewer and Screensaver settings higher in the UI Settings menu
* Moved the game media directory setting to the top of the Other Settings menu, following the new Alternative Emulators entry * Moved the game media directory setting to the top of the Other Settings menu, following the new Alternative Emulators entry
* Added a blinking cursor to TextEditComponent
* Changed the filter description "Text filter (game name)" to "Game name"
* Added support for a new type of "flat style" button to ButtonComponent
* Added support for correctly navigating arbitrarily sized ComponentGrid entries, i.e. those spanning multiple cells
* Bundled the bold font version of Fontfabric Akrobat * Bundled the bold font version of Fontfabric Akrobat
* Added the GLM (OpenGL Mathematics) library as a Git subtree * Added the GLM (OpenGL Mathematics) library as a Git subtree
* Replaced all built-in matrix and vector data types and functions with GLM library equivalents * Replaced all built-in matrix and vector data types and functions with GLM library equivalents
* Replaced some additional math functions and moved the remaining built-in functions to a math utility namespace * Replaced some additional math functions and moved the remaining built-in functions to a math utility namespace
* Added a function to generate MD5 hashes * Added a function to generate MD5 hashes
* Changed two clang-format rules related to braced lists and reformatted the codebase * Moved the "complex" mode functionality from GuiComplexTextEditPopup into GuiTextEditPopup and removed the source files for the former
* Increased the warning level for Clang/LLVM and GCC by adding -Wall, -Wpedantic and some additional flags
* Fixed a lot of compiler warnings introduced by the -Wall and -Wpedantic flags
* Changed the language standard from C++14 to C++17 * Changed the language standard from C++14 to C++17
* Increased the minimal required compiler version to 5.0.0 for Clang/LLVM and 7.1 for GCC
* Changed two clang-format rules related to braced lists and reformatted the codebase
### Bug fixes ### Bug fixes
* When multi-scraping in interactive mode with "Auto-accept single game matches" enabled, the game name could not be refined if there were no games found
* When multi-scraping in interactive mode, the game counter was not decreased when skipping games, making it impossible to skip the final games in the queue
* When multi-scraping in interactive mode, "No games found" results could be accepted using the "A" button
* When scraping in interactive mode, any refining done using the "Y" button shortcut would not be shown when doing another refine using the "Refine search" button
* Input consisting of only whitespace characters would get accepted by TextEditComponent which led to various strange behaviors
* Leading and trailing whitespace characters would not get trimmed from the collection name when creating a new custom collection
* Leading and trailing whitespace characters would get included in scraper search refines and TheGamesDB searches
* Game name (text) filters were matching the system names for collection systems if the "Show system names in collections" setting was enabled
* Brackets such as () and [] were filtered from game names in collection systems if the "Show system names in collections" setting was enabled
* When navigating menus, the separator lines and menu components did not align properly and moved up and down slightly * When navigating menus, the separator lines and menu components did not align properly and moved up and down slightly
* When scrolling in menus, pressing other buttons than "Up" or "Down" did not stop the scrolling which caused all sorts of weird behavior
* With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size * With the menu scale-up effect enabled and entering a submenu before the parent menu was completely scaled up, the parent would get stuck at a semi-scaled size
* Disabling a collection while its gamelist was displayed would lead to a slide transition from a black screen if a gamelist on startup had been set
* When marking a game to not be counted in the metadata editor and the game was part of a custom collection, no collection disabling notification was displayed
* Horizontal sizing of the TextComponent input field was not consistent across different screen resolutions
* The "sortname" window header was incorrectly spelled when editing this type of entry in the metadata editor
* When the last row of a menu had its text color changed, this color was completely desaturated when navigating to a button below the list * When the last row of a menu had its text color changed, this color was completely desaturated when navigating to a button below the list
## Version 1.1.0 ## Version 1.1.0

View file

@ -25,9 +25,8 @@ set(CMAKE_VERBOSE_MAKEFILE OFF CACHE BOOL "Show verbose compiler output" FORCE)
set(LINUX_CPACK_GENERATOR "DEB" CACHE STRING "CPack generator, DEB or RPM") set(LINUX_CPACK_GENERATOR "DEB" CACHE STRING "CPack generator, DEB or RPM")
# 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 ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages)
${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages)
# Define the options. # Define the options.
option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES}) option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES})
@ -37,16 +36,22 @@ 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(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}) option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" ${CLANG_TIDY})
if (CLANG_TIDY) if(CLANG_TIDY)
find_program(CLANG_TIDY_BINARY NAMES clang-tidy) find_program(CLANG_TIDY_BINARY NAMES clang-tidy)
if("${CLANG_TIDY_BINARY}" STREQUAL "CLANG_TIDY_BINARY-NOTFOUND") if(CLANG_TIDY_BINARY STREQUAL "CLANG_TIDY_BINARY-NOTFOUND")
message("-- CLANG_TIDY was set but the clang-tidy binary was not found") message("-- CLANG_TIDY was set but the clang-tidy binary was not found")
else() else()
message("-- Building with the clang-tidy static analyzer") message("-- Building with the clang-tidy static analyzer")
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,-fuchsia-*,-hicpp-*,-llvm-*, \ set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,\
-readability-braces-*,-google-readability-braces-*, \ -fuchsia-*,\
-readability-uppercase-literal-suffix,-modernize-use-trailing-return-type, \ -hicpp-*,\
-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers") -llvm-*,\
-readability-braces-*,\
-google-readability-braces-*,\
-readability-uppercase-literal-suffix,\
-modernize-use-trailing-return-type,\
-cppcoreguidelines-avoid-magic-numbers,\
-readability-magic-numbers")
endif() endif()
endif() endif()
@ -59,19 +64,19 @@ if(EXISTS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/bcm_host.h")
# Setting BCMHOST seems to break OpenGL ES on the RPi 4 so set RPI instead. # Setting BCMHOST seems to break OpenGL ES on the RPi 4 so set RPI instead.
#set(BCMHOST found) #set(BCMHOST found)
set(RPI ON) 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) elseif(GLES OR RPI)
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() endif()
set_property(CACHE GLSystem PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL") set_property(CACHE GLSYSTEM PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL")
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# Package dependencies. # Package dependencies.
if(${GLSystem} MATCHES "Desktop OpenGL") if(GLSYSTEM MATCHES "Desktop OpenGL")
set(OpenGL_GL_PREFERENCE "GLVND") set(OpenGL_GL_PREFERENCE "GLVND")
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
else() else()
@ -98,69 +103,67 @@ if(CEC)
endif() endif()
# Add ALSA for Linux. # Add ALSA for Linux.
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(CMAKE_SYSTEM_NAME MATCHES "Linux")
find_package(ALSA REQUIRED) find_package(ALSA REQUIRED)
endif() endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# Compiler and linker settings. # Compiler and linker settings.
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message("-- Compiler is Clang/LLVM") message("-- Compiler is Clang/LLVM")
execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE CLANG_VERSION) if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0)
if(CLANG_VERSION VERSION_LESS 4.2.1) message(SEND_ERROR "You need at least Clang 5.0.0 to compile EmulationStation-DE")
message(SEND_ERROR "You need at least Clang 4.2.1 to compile EmulationStation-DE")
endif() endif()
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) if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1)
if(G++_VERSION VERSION_LESS 5.4) message(SEND_ERROR "You need at least GCC 7.1 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}")
endif() endif()
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
message("-- Compiler is MSVC") message("-- Compiler is MSVC")
# If using the MSVC compiler on Windows, disable the built-in min() and max() macros. # If using the MSVC compiler on Windows, disable the built-in min() and max() macros.
add_definitions(-DNOMINMAX) add_definitions(-DNOMINMAX)
endif() endif()
if (CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE)
message("-- Build type is ${CMAKE_BUILD_TYPE}") message("-- Build type is ${CMAKE_BUILD_TYPE}")
endif() endif()
# Set up compiler and linker flags for debug, profiling or release builds. # Set up compiler and linker flags for debug, profiling or release builds.
if(CMAKE_BUILD_TYPE MATCHES Debug) if(CMAKE_BUILD_TYPE MATCHES Debug)
# Enable the C++17 standard and disable optimizations as it's a debug build. # Enable the C++17 standard and disable optimizations as it's a debug build.
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /Od /DEBUG:FULL") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /Od /DEBUG:FULL")
else() else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O0 -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0")
endif() endif()
# If using Clang, then add additional debug data needed by GDB. # If using Clang, then add additional debug data needed by GDB.
# Comment this out if you're using LLDB for debugging as this flag makes the binary # Comment this out if you're using LLDB for debugging as this flag makes the binary
# much larger and the application much slower. On macOS this setting is never enabled # much larger and the application much slower. On macOS this setting is never enabled
# as LLDB is the default debugger on this OS. # as LLDB is the default debugger on this OS.
if(NOT APPLE AND "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") if(NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG")
endif() endif()
elseif(CMAKE_BUILD_TYPE MATCHES Profiling) elseif(CMAKE_BUILD_TYPE MATCHES Profiling)
# For the profiling build, we enable optimizations and supply the required profiler flags. # For the profiling build, we enable optimizations and supply the required profiler flags.
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /O2 /DEBUG:FULL") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /O2 /DEBUG:FULL")
else() else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -pg -g") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -pg -g -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2 -pg") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2 -pg")
endif() endif()
else() else()
# Enable the C++17 standard and enable optimizations as it's a release build. # Enable the C++17 standard and enable optimizations as it's a release build.
# This will also disable all assert() macros. Strip the binary too. # This will also disable all assert() macros. Strip the binary too.
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG /std:c++17 /O2 /DEBUG:NONE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG /std:c++17 /O2 /DEBUG:NONE")
else() else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -DNDEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -DNDEBUG -Wall -Wpedantic -Wsign-compare -Wnarrowing -Wmissing-field-initializers -Wunused-macros")
if(APPLE) if(APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O2")
else() else()
@ -180,7 +183,7 @@ if(APPLE)
if(MACOS_CODESIGN_IDENTITY) if(MACOS_CODESIGN_IDENTITY)
message("-- Code signing certificate identity: " ${MACOS_CODESIGN_IDENTITY}) message("-- Code signing certificate identity: " ${MACOS_CODESIGN_IDENTITY})
endif() endif()
if(${CMAKE_OSX_DEPLOYMENT_TARGET} VERSION_LESS 10.14) if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14)
message("-- macOS version 10.13 or lower has been set, so if code signing is enabled, Hardened Runtime will not be used") message("-- macOS version 10.13 or lower has been set, so if code signing is enabled, Hardened Runtime will not be used")
endif() endif()
endif() endif()
@ -188,13 +191,13 @@ endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# Preprocessor directives. # Preprocessor directives.
if(${GLSystem} MATCHES "Desktop OpenGL") if(GLSYSTEM MATCHES "Desktop OpenGL")
add_definitions(-DUSE_OPENGL_21) add_definitions(-DUSE_OPENGL_21)
else() else()
add_definitions(-DUSE_OPENGLES_10) add_definitions(-DUSE_OPENGLES_10)
endif() endif()
if (VLC_PLAYER) if(VLC_PLAYER)
add_definitions(-DBUILD_VLC_PLAYER) add_definitions(-DBUILD_VLC_PLAYER)
endif() endif()
@ -214,9 +217,9 @@ add_definitions(-DGLM_FORCE_XYZW_ONLY)
# we use /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD. # we use /usr on Linux, /usr/pkg on NetBSD and /usr/local on FreeBSD and OpenBSD.
if(NOT WIN32 AND NOT APPLE) if(NOT WIN32 AND NOT APPLE)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(CMAKE_INSTALL_PREFIX "/usr" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") set(CMAKE_INSTALL_PREFIX "/usr" CACHE INTERNAL "CMAKE_INSTALL_PREFIX")
elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
set(CMAKE_INSTALL_PREFIX "/usr/pkg" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") set(CMAKE_INSTALL_PREFIX "/usr/pkg" CACHE INTERNAL "CMAKE_INSTALL_PREFIX")
else() else()
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE INTERNAL "CMAKE_INSTALL_PREFIX") set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE INTERNAL "CMAKE_INSTALL_PREFIX")
@ -235,18 +238,17 @@ endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# Include files. # Include files.
set(COMMON_INCLUDE_DIRS set(COMMON_INCLUDE_DIRS ${CURL_INCLUDE_DIR}
${CURL_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIRS}
${FFMPEG_INCLUDE_DIRS} ${FreeImage_INCLUDE_DIRS}
${FreeImage_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS}
${FREETYPE_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIRS}
${PUGIXML_INCLUDE_DIRS} ${RAPIDJSON_INCLUDE_DIRS}
${RAPIDJSON_INCLUDE_DIRS} ${SDL2_INCLUDE_DIR}
${SDL2_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/external/CImg
${CMAKE_CURRENT_SOURCE_DIR}/external/CImg ${CMAKE_CURRENT_SOURCE_DIR}/external/glm
${CMAKE_CURRENT_SOURCE_DIR}/external/glm ${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) 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()
@ -274,69 +276,65 @@ if(DEFINED libCEC_FOUND)
endif() endif()
# For Linux, add the ALSA include directory. # For Linux, add the ALSA include directory.
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 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 OR RPI) 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")
endif() endif()
#--------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------
# Dependency libraries. # Dependency libraries.
if(NOT WIN32) if(NOT WIN32)
set(COMMON_LIBRARIES set(COMMON_LIBRARIES ${CURL_LIBRARIES}
${CURL_LIBRARIES} ${FFMPEG_LIBRARIES}
${FFMPEG_LIBRARIES} ${FreeImage_LIBRARIES}
${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES}
${FREETYPE_LIBRARIES} ${PUGIXML_LIBRARIES}
${PUGIXML_LIBRARIES} ${SDL2_LIBRARY})
${SDL2_LIBRARY})
if(VLC_PLAYER) if(VLC_PLAYER)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${VLC_LIBRARIES}) set(COMMON_LIBRARIES ${COMMON_LIBRARIES} ${VLC_LIBRARIES})
endif() endif()
elseif(WIN32) elseif(WIN32)
if(DEFINED MSVC) if(DEFINED MSVC)
set(COMMON_LIBRARIES set(COMMON_LIBRARIES "${PROJECT_SOURCE_DIR}/avcodec.lib"
"${PROJECT_SOURCE_DIR}/avcodec.lib" "${PROJECT_SOURCE_DIR}/avfilter.lib"
"${PROJECT_SOURCE_DIR}/avfilter.lib" "${PROJECT_SOURCE_DIR}/avformat.lib"
"${PROJECT_SOURCE_DIR}/avformat.lib" "${PROJECT_SOURCE_DIR}/avutil.lib"
"${PROJECT_SOURCE_DIR}/avutil.lib" "${PROJECT_SOURCE_DIR}/swresample.lib"
"${PROJECT_SOURCE_DIR}/swresample.lib" "${PROJECT_SOURCE_DIR}/swscale.lib"
"${PROJECT_SOURCE_DIR}/swscale.lib" "${PROJECT_SOURCE_DIR}/FreeImage.lib"
"${PROJECT_SOURCE_DIR}/FreeImage.lib" "${PROJECT_SOURCE_DIR}/glew32.lib"
"${PROJECT_SOURCE_DIR}/glew32.lib" "${PROJECT_SOURCE_DIR}/libcurl-x64.lib"
"${PROJECT_SOURCE_DIR}/libcurl-x64.lib" "${PROJECT_SOURCE_DIR}/freetype.lib"
"${PROJECT_SOURCE_DIR}/freetype.lib" "${PROJECT_SOURCE_DIR}/pugixml.lib"
"${PROJECT_SOURCE_DIR}/pugixml.lib" "${PROJECT_SOURCE_DIR}/SDL2main.lib"
"${PROJECT_SOURCE_DIR}/SDL2main.lib" "${PROJECT_SOURCE_DIR}/SDL2.lib"
"${PROJECT_SOURCE_DIR}/SDL2.lib" "Winmm.dll")
"Winmm.dll")
if(VLC_PLAYER) if(VLC_PLAYER)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.lib") set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.lib")
endif() endif()
else() else()
set(COMMON_LIBRARIES set(COMMON_LIBRARIES "${PROJECT_SOURCE_DIR}/avcodec-58.dll"
"${PROJECT_SOURCE_DIR}/avcodec-58.dll" "${PROJECT_SOURCE_DIR}/avfilter-7.dll"
"${PROJECT_SOURCE_DIR}/avfilter-7.dll" "${PROJECT_SOURCE_DIR}/avformat-58.dll"
"${PROJECT_SOURCE_DIR}/avformat-58.dll" "${PROJECT_SOURCE_DIR}/avutil-56.dll"
"${PROJECT_SOURCE_DIR}/avutil-56.dll" "${PROJECT_SOURCE_DIR}/swresample-3.dll"
"${PROJECT_SOURCE_DIR}/swresample-3.dll" "${PROJECT_SOURCE_DIR}/swscale-5.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" "${PROJECT_SOURCE_DIR}/libfreetype.dll"
"${PROJECT_SOURCE_DIR}/libfreetype.dll" "${PROJECT_SOURCE_DIR}/libpugixml.dll"
"${PROJECT_SOURCE_DIR}/libpugixml.dll" "${PROJECT_SOURCE_DIR}/libSDL2main.a"
"${PROJECT_SOURCE_DIR}/libSDL2main.a" "${PROJECT_SOURCE_DIR}/SDL2.dll"
"${PROJECT_SOURCE_DIR}/SDL2.dll" "mingw32"
"mingw32" "Winmm.dll")
"Winmm.dll")
if(VLC_PLAYER) if(VLC_PLAYER)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.dll") set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "${PROJECT_SOURCE_DIR}/libvlc.dll")
endif() endif()
@ -344,15 +342,13 @@ elseif(WIN32)
endif() endif()
if(APPLE) if(APPLE)
# See es-app/CMakeLists.txt for an explation for why an extra 'Resources' directory # See es-app/CMakeLists.txt for an explation for why an extra "Resources" directory
# has been added to the install prefix. # has been added to the install prefix.
set(CMAKE_INSTALL_PREFIX set(CMAKE_INSTALL_PREFIX "/Applications/EmulationStation Desktop Edition.app/Contents/Resources")
"/Applications/EmulationStation Desktop Edition.app/Contents/Resources")
if(VLC_PLAYER) if(VLC_PLAYER)
# Required as the VLC find module doesn't work properly on macOS. # Required as the VLC find module doesn't work properly on macOS.
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "/Applications/VLC.app/Contents/MacOS/lib/libvlc.dylib")
"/Applications/VLC.app/Contents/MacOS/lib/libvlc.dylib")
endif() endif()
# Set the same rpath links for the install executable as for the build executable. # Set the same rpath links for the install executable as for the build executable.
@ -370,7 +366,7 @@ if(DEFINED libCEC_FOUND)
endif() endif()
# Add ALSA for Linux libraries. # Add ALSA for Linux libraries.
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(CMAKE_SYSTEM_NAME MATCHES "Linux")
list(APPEND COMMON_LIBRARIES ${ALSA_LIBRARY}) list(APPEND COMMON_LIBRARIES ${ALSA_LIBRARY})
endif() endif()
@ -382,7 +378,7 @@ elseif(RPI)
list(APPEND COMMON_LIBRARIES ${OPENGLES_LIBRARIES}) list(APPEND COMMON_LIBRARIES ${OPENGLES_LIBRARIES})
endif() 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})

View file

@ -31,8 +31,8 @@ This plan is under constant review so expect it to change from time to time. Sti
* 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)
* 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)
* Improved full-screen support, removing the temporary full-screen hacks * Improved full-screen support, removing the temporary full-screen hacks
* On-screen keyboard * Virtual (on-screen) keyboard
* Support for the Raspberry Pi 4 with OpenGL ES 2.0 and GLSL shaders (Raspberry Pi OS) * Support for the Raspberry Pi 4 (Raspberry Pi OS)
* Add GLM library dependency for matrix and vector operations, decommission the built-in functions * Add GLM library dependency for matrix and vector operations, decommission the built-in functions
* Add to more Linux repositories, BSD ports collections etc. * Add to more Linux repositories, BSD ports collections etc.
* Flatpak and Snap releases on Linux * Flatpak and Snap releases on Linux
@ -40,8 +40,9 @@ This plan is under constant review so expect it to change from time to time. Sti
#### 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 * New theme engine with generalized views (only System and Gamelist) and theme variants support
* Scrapping the Grid view style and adding a general grid/wall component instead * Add multiple new gamelist components (wheels, wall/grid etc.)
* Move existing theme logic to legacy support, only to be used for backwards compatibility
* 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
* Improved text and font functions, e.g. faster and cleaner line wrapping and more exact sizing * Improved text and font functions, e.g. faster and cleaner line wrapping and more exact sizing

View file

@ -11,8 +11,6 @@ https://retropie.org.uk
Leon Styhre (Desktop Edition fork, based on the RetroPie version) \ Leon Styhre (Desktop Edition fork, based on the RetroPie version) \
https://gitlab.com/leonstyhre/emulationstation-de https://gitlab.com/leonstyhre/emulationstation-de
The shader code for blur_horizontal.glsl, blur_vertical_glsl and scanlines.glsl has been borrowed from the [RetroArch](https://www.retroarch.com) project.
# UI Art & Design # UI Art & Design
@ -63,6 +61,18 @@ SDL \
https://www.libsdl.org https://www.libsdl.org
# Code
Some code (like the virtual keyboard) was borrowed from Batocera.linux \
https://batocera.org
The MD5 hash functions were adapted from code by the BZFlag project \
https://www.bzflag.org
A few of the GLSL shaders were borrowed from the RetroArch project \
https://www.retroarch.com
# Resources # Resources
Akrobat font \ Akrobat font \

View file

@ -125,7 +125,7 @@ pkg_add vlc
In the same manner as for FreeBSD, Clang/LLVM and cURL should already be installed by default. In the same manner as for FreeBSD, Clang/LLVM and cURL should already be installed by default.
RapidJSON is not part of the OpenBSD ports/package collection as of v6.8, so you need to compile it yourself. At the time of writing, the latest release v1.1.0 does not compile on OpenBSD, so you need to use the master branch: RapidJSON is not part of the OpenBSD ports/package collection as of v6.8, so you need to compile it yourself. At the time of writing, the latest release v1.1.0 does not compile on OpenBSD, so you need to use the latest available code from the master branch:
``` ```
git clone https://github.com/Tencent/rapidjson.git git clone https://github.com/Tencent/rapidjson.git
@ -872,12 +872,12 @@ make
[RapidJSON](http://rapidjson.org) [RapidJSON](http://rapidjson.org)
For RapidJSON you don't need to compile, you just need the include files: For RapidJSON you don't need to compile anything, you just need the include files.
At the time of writing, the latest release v1.1.0 generates some compiler warnings on Windows, but this can be avoided by using the latest available code from the master branch:
``` ```
git clone git://github.com/Tencent/rapidjson.git git clone git://github.com/Tencent/rapidjson.git
cd rapidjson
git checkout v1.1.0
``` ```
@ -1399,16 +1399,16 @@ For the following options, the es_settings.xml file is immediately updated/saved
## es_systems.xml ## es_systems.xml
The es_systems.xml file contains the system configuration data for ES-DE, written in XML format. This defines the system name, the full system name, the ROM path, the allowed file extensions, the launch command, the platform (for scraping) and the theme to use. The es_systems.xml file contains the game systems configuration data for ES-DE, written in XML format. This defines the system name, the full system name, the ROM path, the allowed file extensions, the launch command, the platform (for scraping) and the theme to use.
ES-DE ships with a comprehensive `es_systems.xml` configuration file and normally you shouldn't need to modify this. However there may be special circumstances such as wanting to use alternative emulators for some game systems or perhaps you need to add additional systems altogether. ES-DE ships with a comprehensive `es_systems.xml` file and most users will probably never need to make any customizations. But there may be special circumstances such as wanting to use different emulators for some game systems or perhaps to add additional systems altogether.
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 accomplish this, ES-DE supports customizations via a separate es_systems.xml file that is to be placed in the `custom_systems` folder in the application home directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml`. (The tilde symbol `~` translates to `$HOME` on Unix and macOS, and to `%HOMEPATH%` on Windows unless overridden via the --home command line option.)
This custom file functionality is designed to be complementary to the bundled es_systems.xml file, meaning you should only add entries to the custom configuration file for game systems that you actually want to add or override. So to for example customize a single system, this file should only contain a single `<system>` tag. The structure of the custom file is identical to the bundled file with the exception of an additional optional tag named `<loadExclusive/>`. If this is placed in the custom es_systems.xml file, ES-DE will not load the bundled file. This is normally not recommended and should only be used for special situations. At the end of this section you can find an example of a custom es_systems.xml file.
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. 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 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.
@ -1424,7 +1424,7 @@ Below is an overview of the file layout with various examples. For the command t
<system> <system>
<!-- A short name. Although there can be multiple identical <name> tags in the file, upon successful loading of a system, <!-- A short name. Although there can be multiple identical <name> tags in the file, upon successful loading of a system,
any succeeding entries with identical <name> tags will be skipped. Multiple identical name tags is only required for very any succeeding entries with identical <name> tags will be skipped. Multiple identical name tags is only required for very
special situations so it's normally recommended to keep this tag unique.--> special situations so it's normally recommended to keep this tag unique. -->
<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. -->
@ -1447,10 +1447,10 @@ Below is an overview of the file layout with various examples. For the command t
<!-- It's possible to define alternative emulators by adding additional command tags for a system. When doing this, <!-- It's possible to define alternative emulators by adding additional command tags for a system. When doing this,
the "label" attribute is mandatory for all tags. It's these labels that will be shown in the user interface when the "label" attribute is mandatory for all tags. It's these labels that will be shown in the user interface when
selecting the alternative emulators either system-wide or per game. The first row will be the default emulator. --> selecting the alternative emulators either system-wide or per game. The first row will be the default emulator. -->
<command label="Nestopia UE (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/nestopia_libretro.so %ROM%</command> <command label="Nestopia UE">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/nestopia_libretro.so %ROM%</command>
<command label="FCEUmm (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fceumm_libretro.so %ROM%</command> <command label="FCEUmm">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fceumm_libretro.so %ROM%</command>
<command label="Mesen (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen_libretro.so %ROM%</command> <command label="Mesen">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen_libretro.so %ROM%</command>
<command label="QuickNES (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/quicknes_libretro.so %ROM%</command> <command label="QuickNES">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/quicknes_libretro.so %ROM%</command>
<!-- This example for Unix will search for RetroArch in the PATH environment variable and it also has an absolute path to <!-- This example for Unix will search for RetroArch in the PATH environment variable and it also has an absolute path to
the snes9x_libretro core, If there are spaces in the path or file name, you must enclose them in quotation marks, such as the snes9x_libretro core, If there are spaces in the path or file name, you must enclose them in quotation marks, such as
@ -1531,9 +1531,9 @@ Here are some additional real world examples of system entries, the first one fo
<fullname>DOS (PC)</fullname> <fullname>DOS (PC)</fullname>
<path>%ROMPATH%/dos</path> <path>%ROMPATH%/dos</path>
<extension>.bat .BAT .com .COM .conf .CONF .cue .CUE .exe .EXE .iso .ISO .7z .7Z .zip .ZIP</extension> <extension>.bat .BAT .com .COM .conf .CONF .cue .CUE .exe .EXE .iso .ISO .7z .7Z .zip .ZIP</extension>
<command label="DOSBox-core (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/dosbox_core_libretro.so %ROM%</command> <command label="DOSBox-Core">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/dosbox_core_libretro.so %ROM%</command>
<command label="DOSBox-Pure (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/dosbox_pure_libretro.so %ROM%</command> <command label="DOSBox-Pure">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/dosbox_pure_libretro.so %ROM%</command>
<command label="DOSBox-SVN (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/dosbox_svn_libretro.so %ROM%</command> <command label="DOSBox-SVN">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/dosbox_svn_libretro.so %ROM%</command>
<platform>dos</platform> <platform>dos</platform>
<theme>dos</theme> <theme>dos</theme>
</system> </system>
@ -1557,16 +1557,53 @@ And finally one for Windows:
```xml ```xml
<system> <system>
<name>sega32x</name> <name>pcengine</name>
<fullname>Sega Mega Drive 32X</fullname> <fullname>NEC PC Engine</fullname>
<path>%ROMPATH%\sega32x</path> <path>%ROMPATH%\pcengine</path>
<extension>.bin .BIN .gen .GEN .smd .SMD .md .MD .32x .32X .cue .CUE .iso .ISO .sms .SMS .68k .68K .7z .7Z .zip .ZIP</extension> <extension>.bin .BIN .ccd .CCD .chd .CHD .cue .CUE .img .IMG .iso .ISO .m3u .M3U .pce .PCE .sgx .SGX .toc .TOC .7z .7Z .zip .ZIP</extension>
<command>%EMULATOR_RETROARCH% -L %CORE_RETROARCH%\picodrive_libretro.dll %ROM%</command> <command label="Beetle PCE">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_libretro.dll %ROM%</command>
<platform>sega32x</platform> <command label="Beetle PCE FAST">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%\mednafen_pce_fast_libretro.dll %ROM%</command>
<theme>sega32x</theme> <platform>pcengine</platform>
<theme>pcengine</theme>
</system> </system>
``` ```
As well, here's an example for Unix of a custom es_systems.xml file placed in ~/.emulationstation/custom_systems/ that overrides a single game system from the bundled configuration file:
```xml
<?xml version="1.0"?>
<!-- This is a custom ES-DE game systems configuration file for Unix -->
<systemList>
<system>
<name>nes</name>
<fullname>Nintendo Entertainment System</fullname>
<path>%ROMPATH%/nes</path>
<extension>.nes .NES .zip .ZIP</extension>
<command>/usr/games/fceux %ROM%</command>
<platform>nes</platform>
<theme>nes</theme>
</system>
</systemList>
```
If adding the `<loadExclusive/>` tag to the file, the bundled es_systems.xml file will not be processed. For this example it wouldn't be a very good idea as NES would then be the only platform that could be used in ES-DE.
```xml
<?xml version="1.0"?>
<!-- This is a custom ES-DE game systems configuration file for Unix -->
<loadExclusive/>
<systemList>
<system>
<name>nes</name>
<fullname>Nintendo Entertainment System</fullname>
<path>%ROMPATH%/nes</path>
<extension>.nes .NES .zip .ZIP</extension>
<command>/usr/games/fceux %ROM%</command>
<platform>nes</platform>
<theme>nes</theme>
</system>
</systemList>
```
## es_find_rules.xml ## es_find_rules.xml
This file makes it possible to define rules for where to search for the emulator binaries and emulator cores. This file makes it possible to define rules for where to search for the emulator binaries and emulator cores.

View file

@ -864,9 +864,9 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice
- Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place
the component exactly in the middle of the screen. the component exactly in the middle of the screen.
* `textColor` - type: COLOR. Default is 777777FF. * `textColor` - type: COLOR. Default is 777777FF.
* `textColorDimmed` - type: COLOR. Default is 777777FF. * `textColorDimmed` - type: COLOR. Default is the same value as textColor. Must be placed under the 'system' view.
* `iconColor` - type: COLOR. Default is 777777FF. * `iconColor` - type: COLOR. Default is 777777FF.
* `iconColorDimmed` - type: COLOR. Default is 777777FF. * `iconColorDimmed` - type: COLOR. Default is the same value as iconColor. Must be placed under the 'system' view.
* `fontPath` - type: PATH. * `fontPath` - type: PATH.
* `fontSize` - type: FLOAT. * `fontSize` - type: FLOAT.
* `entrySpacing` - type: FLOAT. Default is 16.0. * `entrySpacing` - type: FLOAT. Default is 16.0.
@ -884,6 +884,8 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice
`button_l`, `button_l`,
`button_r`, `button_r`,
`button_lr`, `button_lr`,
`button_lt`,
`button_rt`,
`button_a_SNES`, `button_a_SNES`,
`button_b_SNES`, `button_b_SNES`,
`button_x_SNES`, `button_x_SNES`,

View file

@ -77,9 +77,9 @@ On Unix this means /home/\<username\>/.emulationstation/, on macOS /Users/\<user
**Note:** As of ES-DE v1.1 there is no internationalization support, which means that the application will always require the physical rather than the localized path to your home directory. For instance on macOS configured for the Swedish language /Users/myusername will be the physical path but /Användare/myusername is the localized path that is actually shown in the user interface. The same is true on Windows where the directories would be C:\Users\myusername and C:\Användare\myusername respectively. If attempting to enter the localized path for any directory-related setting, ES-DE will not be able to find it. But it's always possible to use the tilde `~` symbol when referring to your home directory, which ES-DE will expand to the physical location regardless of what language you have configured for your operating system. If you're using an English-localized system, this whole point is irrelevant as the physical and localized paths are then identical. **Note:** As of ES-DE v1.1 there is no internationalization support, which means that the application will always require the physical rather than the localized path to your home directory. For instance on macOS configured for the Swedish language /Users/myusername will be the physical path but /Användare/myusername is the localized path that is actually shown in the user interface. The same is true on Windows where the directories would be C:\Users\myusername and C:\Användare\myusername respectively. If attempting to enter the localized path for any directory-related setting, ES-DE will not be able to find it. But it's always possible to use the tilde `~` symbol when referring to your home directory, which ES-DE will expand to the physical location regardless of what language you have configured for your operating system. If you're using an English-localized system, this whole point is irrelevant as the physical and localized paths are then identical.
It's also possible to override the home directory path using the --home command line option, but this is normally required only for very special situations so we can safely ignore that option for now. It's possible to override the home directory path using the --home command line option, but this is normally required only for very special situations so we can safely ignore that option for now.
Also on first startup the configuration file `es_settings.xml` will be generated in the ES-DE home directory, containing all the application settings at their default values. Following this, a file named `es_systems.xml` will be loaded from the resources directory (which is part of the ES-DE installation). This file contains the game system definitions including which emulator to use per platform. For some systems there are also alternative emulators defined which can be applied system-wide or per game. How that works is explained later in this guide. The systems configuration file can also be customized, as described in the next section. Also on first startup the configuration file `es_settings.xml` will be generated in the ES-DE home directory, containing all the application settings at their default values. Following this, a file named `es_systems.xml` will be loaded from the resources directory (which is part of the ES-DE installation). This file contains the game system definitions including which emulator to use per platform. For some systems there are also alternative emulators defined which can be applied system-wide or per game. How that works is explained later in this guide. A customized systems configuration file can also be used, as described in the next section.
There's an application log file created in the ES-DE home directory named `es_log.txt`, please refer to this in case of any issues as it should hopefully provide information on what went wrong. Starting ES-DE with the --debug flag provides even more detailed information. There's an application log file created in the ES-DE home directory named `es_log.txt`, please refer to this in case of any issues as it should hopefully provide information on what went wrong. Starting ES-DE with the --debug flag provides even more detailed information.
@ -118,6 +118,8 @@ genesis: Sega Genesis
gx4000: Amstrad GX4000 gx4000: Amstrad GX4000
``` ```
If a custom es_systems.xml file is present in ~/.emulationstation/custom_systems/ any entries from this file will have their names trailed by the text _(custom system)_. So if the GameCube system in the example above would be present in the custom systems configuration file, the system would be shown as `gc (custom system)` instead of simply `gc`. This is only applicable for the systems.txt and systeminfo.txt files, the trailing text is not applied or used anywhere else in the application.
Note that neither the systeminfo.txt files or the systems.txt file are needed to run ES-DE, they're only generated as a convenience to help with the setup. Note that neither the systeminfo.txt files or the systems.txt file are needed to run ES-DE, they're only generated as a convenience to help with the setup.
There will be a lot of directories created if using the es_systems.xml file bundled with the installation, so it may be a good idea to remove the ones you don't need. It's recommended to move them to another location to be able to use them later if more systems should be added. For example a directory named _DISABLED could be created inside the ROMs folder (i.e. ~/ROMs/_DISABLED) and all game system directories you don't need could be moved there. Doing this reduces the application startup time as ES-DE would otherwise need to scan for game files for all these systems. There will be a lot of directories created if using the es_systems.xml file bundled with the installation, so it may be a good idea to remove the ones you don't need. It's recommended to move them to another location to be able to use them later if more systems should be added. For example a directory named _DISABLED could be created inside the ROMs folder (i.e. ~/ROMs/_DISABLED) and all game system directories you don't need could be moved there. Doing this reduces the application startup time as ES-DE would otherwise need to scan for game files for all these systems.
@ -126,9 +128,15 @@ There will be a lot of directories created if using the es_systems.xml file bund
_This is the dialog shown if no game files were found. It lets you configure the ROM directory if you don't want to use the default one, and you can also generate the game systems directory structure. Note that the directory is the physical path, and that your operating system may present this as a localized path if you are using a language other than English._ _This is the dialog shown if no game files were found. It lets you configure the ROM directory if you don't want to use the default one, and you can also generate the game systems directory structure. Note that the directory is the physical path, and that your operating system may present this as a localized path if you are using a language other than English._
## Customizing the systems configuration file ## Game system customizations
The `es_systems.xml` file is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If a customized file is needed, this should instead be placed in the `custom_systems` folder in the ES-DE home directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml`. You can find information on the file structure and how to adapt the configuration in the [INSTALL-DEV.md](INSTALL-DEV.md#es_systemsxml) document. The game systems configuration file `es_systems.xml` is located in the ES-DE resources directory which is part of the application installation. As such this file is not intended to be modified directly. If system customizations are required, a separate es_systems.xml file should instead be placed in the `custom_systems` folder in the ES-DE home directory, i.e. `~/.emulationstation/custom_systems/es_systems.xml`.
Although it's possible to make a copy of the bundled configuration file, to modify it and then place it in this directory, that is not how the system customization is designed to be done. Instead the intention is that the file in the custom_systems directory complements the bundled configuration, meaning only systems that are to be modified should be included.
For example you may want to replace the emulator launch command, modify the full name or change the supported file extensions for a single system. In this case it wouldn't make sense to copy the complete bundled file and just apply these minor modifications, instead an es_systems.xml file only containing the configuration for that single system should be placed in the custom_systems directory.
The instructions for how to customize the es_systems.xml file can be found in [INSTALL-DEV.md](INSTALL-DEV.md#es_systemsxml). There you can also find an example of a custom file that you can copy into ~/.emulationstation/custom_systems/ and modify as required.
## Migrating from other EmulationStation forks ## Migrating from other EmulationStation forks
@ -302,16 +310,16 @@ The supported file extensions are listed in [unix/es_systems.xml](resources/syst
Here is the snippet from the unix/es_systems.xml file: Here is the snippet from the unix/es_systems.xml file:
``` ```xml
<system> <system>
<name>nes</name> <name>nes</name>
<fullname>Nintendo Entertainment System</fullname> <fullname>Nintendo Entertainment System</fullname>
<path>%ROMPATH%/nes</path> <path>%ROMPATH%/nes</path>
<extension>.nes .NES .unf .UNF .unif .UNIF .7z .7Z .zip .ZIP</extension> <extension>.nes .NES .unf .UNF .unif .UNIF .7z .7Z .zip .ZIP</extension>
<command label="Nestopia UE (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/nestopia_libretro.so %ROM%</command> <command label="Nestopia UE">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/nestopia_libretro.so %ROM%</command>
<command label="FCEUmm (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fceumm_libretro.so %ROM%</command> <command label="FCEUmm">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/fceumm_libretro.so %ROM%</command>
<command label="Mesen (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen_libretro.so %ROM%</command> <command label="Mesen">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/mesen_libretro.so %ROM%</command>
<command label="QuickNES (RetroArch)">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/quicknes_libretro.so %ROM%</command> <command label="QuickNES">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/quicknes_libretro.so %ROM%</command>
<platform>nes</platform> <platform>nes</platform>
<theme>nes</theme> <theme>nes</theme>
</system> </system>
@ -369,7 +377,7 @@ The platform name for the Commodore 64 is `c64`, so the following structure woul
~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u ~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u
``` ```
It's highly recommended to create `.m3u` playlist files for multi-disk images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game. It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game.
The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u: The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u:
@ -424,7 +432,7 @@ Apart from the potential difficulty in locating the emulator binary, there are s
There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs. There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs.
An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disk images as described earlier. An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disc images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier.
Here's an example of what the file structure could look like: Here's an example of what the file structure could look like:
@ -864,7 +872,7 @@ If this setting is enabled and a folder has its flag set to be excluded from the
**Scrape actual folders** _(Multi-scraper only)_ **Scrape actual folders** _(Multi-scraper only)_
Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disk games where there is a folder for each individual game. Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disc games where there is a folder for each individual game.
**Auto-retry on peer verification errors** _(ScreenScraper only)_ **Auto-retry on peer verification errors** _(ScreenScraper only)_
@ -946,6 +954,10 @@ There are some special characters in ES-DE such as the favorites star, the folde
With this option enabled, there will be an overlay displayed when scrolling the gamelists quickly, i.e. when holding down the _Up_, _Down_, _Left shoulder_ or _Right shoulder_ buttons for some time. The overlay will darken the background slightly and display the first two characters of the game names. If the game is a favorite and the setting to sort favorites above non-favorites has been enabled, a star will be shown instead. With this option enabled, there will be an overlay displayed when scrolling the gamelists quickly, i.e. when holding down the _Up_, _Down_, _Left shoulder_ or _Right shoulder_ buttons for some time. The overlay will darken the background slightly and display the first two characters of the game names. If the game is a favorite and the setting to sort favorites above non-favorites has been enabled, a star will be shown instead.
**Enable virtual keyboard**
This enables a virtual (on-screen) keyboard that can be used at various places throughout the application to input text and numbers using a controller. The Shift and Alt keys can be toggled individually or combined together to access many special characters. The general use of the virtual keyboard should hopefully be self-explanatory.
**Enable toggle favorites button** **Enable toggle favorites button**
This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as favorite using the metadata editor when this setting is disabled. The option does not affect the use of the _Y_ button to add or remove games when editing custom collections. This setting enables the _Y_ button for quickly toggling a game as favorite. Although this may be convenient at times, it's also quite easy to accidentally remove a favorite tagging of a game when using the application more casually. As such it could sometimes make sense to disable this functionality. It's of course still possible to mark a game as favorite using the metadata editor when this setting is disabled. The option does not affect the use of the _Y_ button to add or remove games when editing custom collections.
@ -1230,7 +1242,7 @@ If this option is disabled, hidden files and folders within the ROMs directory t
**Show hidden games (requires restart)** **Show hidden games (requires restart)**
You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disk games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden. You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden.
**Enable custom event scripts** **Enable custom event scripts**
@ -1318,7 +1330,7 @@ _The gamelist filter screen, accessed from the game options menu._
The following filters can be applied: The following filters can be applied:
**Text Filter (game name)** **Game name**
**Favorites** **Favorites**
@ -1338,9 +1350,9 @@ The following filters can be applied:
**Hidden** **Hidden**
With the exception of the text filter, all available filter values are assembled from metadata from the actual gamelist, so if there for instance are no games marked as completed, the Completed filter will only have the selectable option False, i.e. True will be missing. With the exception of the game name text filter, all available filter values are assembled from metadata from the actual gamelist, so if there for instance are no games marked as completed, the Completed filter will only have the selectable option False, i.e. True will be missing.
Be aware that although folders can have most of the metadata values set, the filters are only applied to files (this is also true for the text/game name filter). So if you for example set a filter to only display your favorite games, any folder that contains a favorite game will be displayed, and other folders which are themselves marked as favorites but that do not contain any favorite games will be hidden. Be aware that although folders can have most of the metadata values set, the filters are only applied to files (this is also true for the game name text filter). So if you for example set a filter to only display your favorite games, any folder that contains a favorite game will be displayed, and other folders which are themselves marked as favorites but that do not contain any favorite games will be hidden.
The filters are always applied for the complete game system, including all folder content. The filters are always applied for the complete game system, including all folder content.
@ -1418,7 +1430,7 @@ A flag to mark whether the game is suitable for children. This will be applied a
**Hidden** **Hidden**
A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disk games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry. A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry.
**Broken/not working** **Broken/not working**
@ -1426,15 +1438,15 @@ A flag to indicate whether the game is broken. Useful for MAME games for instanc
**Exclude from game counter** _(files only)_ **Exclude from game counter** _(files only)_
A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disk Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot. A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot.
**Exclude from multi-scraper** **Exclude from multi-scraper**
Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disk games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it. Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it.
**Hide metadata fields** **Hide metadata fields**
This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disk, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disk games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled.
**Times played** _(files only)_ **Times played** _(files only)_
@ -1624,166 +1636,178 @@ Refer to the [INSTALL-DEV.md](INSTALL-DEV.md#command-line-options) document for
Note as well that the list and corresponding es_systems.xml templates may not reflect what is readily available for all supported operating system. This is especially true on Unix/Linux if installing RetroArch via the OS repository instead of using the Snap or Flatpak distributions (or compiling from source code) as the repository versions are normally quite crippled. Note as well that the list and corresponding es_systems.xml templates may not reflect what is readily available for all supported operating system. This is especially true on Unix/Linux if installing RetroArch via the OS repository instead of using the Snap or Flatpak distributions (or compiling from source code) as the repository versions are normally quite crippled.
The column **Game system name** corresponds to the directory where you should put your game files, e.g. `~/ROMs/c64` or `~/ROMs/megadrive`. The column **System name** corresponds to the directory where you should put your game files, e.g. `~/ROMs/c64` or `~/ROMs/megadrive`.
Regional differences are handled by simply using the game system name corresponding to your region. For example for Sega Mega Drive, _megadrive_ would be used by most people in the world, although persons from North America would use _genesis_ instead. The same is true for _pcengine_ vs _tg16_ etc. This only affects the theme selection and the corresponding theme graphics, the same emulator and scraper settings are still used for the regional variants although that can of course be modified in the es_systems.xml file if you wish. Regional differences are handled by simply using the game system name corresponding to your region. For example for Sega Mega Drive, _megadrive_ would be used by most people in the world, although persons from North America would use _genesis_ instead. The same is true for _pcengine_ vs _tg16_ etc. This only affects the theme selection and the corresponding theme graphics, the same emulator and scraper settings are still used for the regional variants although that can of course be customized in the es_systems.xml file if you wish.
Sometimes the name of the console is (more or less) the same for multiple regions, and in those cases the region has been added as a suffix to the game system name. For instance `na` for North America has been added to `snes` (Super Nintendo) giving the system name `snesna`. The same goes for Japan, as in `megacd` and `megacdjp`. Again, this only affects the theme and theme graphics. Sometimes the name of the console is (more or less) the same for multiple regions, and in those cases the region has been added as a suffix to the game system name. For instance `na` for North America has been added to `snes` (Super Nintendo) giving the system name `snesna`. The same goes for Japan, as in `megacd` and `megacdjp`. Again, this only affects the theme and theme graphics.
For the **Full name** column, text inside square brackets [] are comments and not part of the actual game system name. For the **Full name** column, text inside square brackets [] are comments and not part of the actual system name.
The **Default emulator** column lists the primary emulator as configured in es_systems.xml. Any system marked with an asterisk (*) in this column requires additional system/BIOS ROMs to run, as should be explained in the emulator documentation. The **Default emulator** column lists the primary emulator as configured in es_systems.xml. If this differs between Unix, macOS and Windows then it's specified in square brackets, such as [UW] for Unix and Windows and [M] for macOS. If not all of the three platforms are specified it means that the system is not available on the missing platforms. For example Lutris which is only avaialable on Unix is marked with only a _[U]_. Unless explicitly marked as **(Standalone)**, each emulator is a RetroArch core.
The **Alternative emulators** column lists additional emulators configured in es_systems.xml that can be selected per system and per game, as explained earlier in this guide. Note that not all of these emulators may be available on all operating systems. The **Alternative emulators** column lists additional emulators configured in es_systems.xml that can be selected per system and per game, as explained earlier in this guide. This does not necessarily include everything in existence, as for some platforms there are a lot of emulators to choose from. In those cases the included emulators is a curated selection. In the same manner as the _Default emulator_ column, differences between Unix, macOS and Windows are marked using square brackets. Unless explicitly marked as **(Standalone)**, echo emulator is a RetroArch core.
For additional details regarding which game file extensions are supported per system, refer to the es_systems.xml files [unix/es_systems.xml](resources/systems/unix/es_systems.xml), [macos/es_systems.xml](resources/systems/macos/es_systems.xml) and [windows/es_systems.xml](resources/systems/windows/es_systems.xml). Normally the extensions setup in these files should cover everything that the emulators support. The **Needs BIOS** column indicates if additional BIOS/system ROMs are required, as should be explained by the emulator documentation. Good starting points for such documentation are [https://docs.libretro.com](https://docs.libretro.com) and [https://docs.libretro.com/library/bios](https://docs.libretro.com/library/bios)
For additional details regarding which game file extensions are supported per system, refer to the es_systems.xml files [unix/es_systems.xml](resources/systems/unix/es_systems.xml), [macos/es_systems.xml](resources/systems/macos/es_systems.xml) and [windows/es_systems.xml](resources/systems/windows/es_systems.xml). Normally the extensions setup in these files should cover everything that the emulators support. Note that for systems that have alternative emulators defined, the list of extensions is a combination of what is supported by all the emulators. This approach is necessary as you want to be able to see all games for each system while potentially testing and switching between different emulators, either system-wide or on a per game basis.
If you generated the ROMs directory structure when first starting ES-DE, the systeminfo.txt files located in each game system directory will also contain the information about the emulator core and supported file extensions. If you generated the ROMs directory structure when first starting ES-DE, the systeminfo.txt files located in each game system directory will also contain the information about the emulator core and supported file extensions.
MAME emulation is a bit special as the choice of emulator depends on which ROM set you're using. It's recommended to go for the latest available set, as MAME is constantly improved with more complete and accurate emulation. Therefore the `arcade` system is configured to use _MAME - Current (RetroArch)_ by default, which as the name implies will be the latest available MAME version. But if you have a really slow computer you may want to use another ROM set such as the popular 0.78. In this case, you can either select _MAME 2003-Plus (RetroArch)_ as an alternative emulator, or you can use the `mame` system which comes configured with this emulator as the default. There are more MAME versions available as alternative emulators, as you can see in the table below. For CD-based systems it's generally recommended to use CHD files (extension .chd) as this saves space due to compression compared to BIN/CUE, IMG, ISO etc. The CHD format is also supported by most emulators. You can convert to CHD from various formats using the MAME `chdman` utility, for example `chdman createcd -i mygame.iso -o mygame.chd`. Sometimes chdman has issues converting from the IMG and BIN formats, and in this case it's possible to first convert to ISO using `ccd2iso`, such as `ccd2iso mygame.img mygame.iso`.
MAME emulation is a bit special as the choice of emulator depends on which ROM set you're using. It's recommended to go for the latest available set, as MAME is constantly improved with more complete and accurate emulation. Therefore the `arcade` system is configured to use _MAME - Current_ by default, which as the name implies will be the latest available MAME version. But if you have a really slow computer you may want to use another ROM set such as the popular 0.78. In this case, you can either select _MAME 2003-Plus_ as an alternative emulator, or you can use the `mame` system which comes configured with this emulator as the default. There are more MAME versions available as alternative emulators, as you can see in the table below.
There are also other MAME forks and derivates available such as MAME4ALL, AdvanceMAME, FinalBurn Alpha and FinalBurn Neo but it's beyond the scope of this document to describe those in detail. For more information, refer to the [RetroPie arcade documentation](https://retropie.org.uk/docs/Arcade) which has a good overview of the various MAME alternatives. There are also other MAME forks and derivates available such as MAME4ALL, AdvanceMAME, FinalBurn Alpha and FinalBurn Neo but it's beyond the scope of this document to describe those in detail. For more information, refer to the [RetroPie arcade documentation](https://retropie.org.uk/docs/Arcade) which has a good overview of the various MAME alternatives.
In general .zip or .7z files are recommended for smaller-sized games like those from older systems (assuming the emulator supports it). But for CD-based systems it's not a good approach as uncompressing the larger CD images takes quite some time, leading to slow game launches. As explained above, converting CD images to CHD files is a better solution for achieving file compression while still enjoying fast game launches.
Consider the table below a work in progress as it's obvioulsy not fully populated yet! Consider the table below a work in progress as it's obvioulsy not fully populated yet!
| Game system name | Full name | Default emulator | Alternative emulators | Recommended game setup | Default emulator/Alternative emulators columns: \
| :-------------------- | :--------------------------------------------- | :-------------------------------- | :-------------------------------- | :----------------------------------- | **[U]**: Unix, **[M]**: macOS, **[W]**: Windows
| 3do | 3DO | | | |
| 64dd | Nintendo 64DD | Mupen64Plus-Next (RetroArch) on Unix and Windows, ParaLLEl N64 (RetroArch) on macOS | ParaLLEl N64 (RetroArch) | | All emulators are RetroArch cores unless marked as **(Standalone**)
| ags | Adventure Game Studio game engine | | | |
| amiga | Commodore Amiga | P-UAE (RetroArch)* | | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disk | | System name | Full name | Default emulator | Alternative emulators | Needs BIOS | Recommended game setup |
| amiga600 | Commodore Amiga 600 | P-UAE (RetroArch)* | | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disk | | :-------------------- | :--------------------------------------------- | :-------------------------------- | :-------------------------------- | :----------- | :----------------------------------- |
| amiga1200 | Commodore Amiga 1200 | P-UAE (RetroArch)* | | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disk | | 3do | 3DO | 4DO | | | |
| amigacd32 | Commodore Amiga CD32 | | | | | 64dd | Nintendo 64DD | Mupen64Plus-Next [UW],<br>ParaLLEl N64 [M] | ParaLLEl N64 [UW] | | |
| amstradcpc | Amstrad CPC | | | | | ags | Adventure Game Studio game engine | | | | |
| apple2 | Apple II | | | | | amiga | Commodore Amiga | PUAE | | Yes | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disc, or in separate folder with .m3u playlist if multi-disc |
| apple2gs | Apple IIGS | | | | | amiga600 | Commodore Amiga 600 | PUAE | | Yes | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disc, or in separate folder with .m3u playlist if multi-disc |
| arcade | Arcade | MAME - Current (RetroArch)* | MAME 2000 (RetroArch), MAME 2003-Plus (RetroArch), MAME 2010 (RetroArch) | Single archive file following MAME name standard in root folder | | amiga1200 | Commodore Amiga 1200 | PUAE | | Yes | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disc, or in separate folder with .m3u playlist if multi-disc |
| astrocade | Bally Astrocade | | | | | amigacd32 | Commodore Amiga CD32 | PUAE | | | |
| atari2600 | Atari 2600 | Stella (RetroArch) on macOS and Windows, Stella 2014 (RetroArch) on Unix | | Single archive or ROM file in root folder | | amstradcpc | Amstrad CPC | Caprice32 | | | |
| atari5200 | Atari 5200 | | | | | apple2 | Apple II | | | | |
| atari7800 | Atari 7800 ProSystem | | | | | apple2gs | Apple IIGS | | | | |
| atari800 | Atari 800 | | | | | arcade | Arcade | MAME - Current | MAME 2000,<br>MAME 2003-Plus,<br>MAME 2010,<br>FinalBurn Neo,<br>FB Alpha 2012 | Depends | Single archive file following MAME name standard in root folder |
| atarijaguar | Atari Jaguar | | | | | astrocade | Bally Astrocade | | | | |
| atarijaguarcd | Atari Jaguar CD | | | | | atari2600 | Atari 2600 | Stella | Stella 2014 | No | Single archive or ROM file in root folder |
| atarilynx | Atari Lynx | | | | | atari5200 | Atari 5200 | Atari800 | | | |
| atarist | Atari ST [also STE and Falcon] | | | | | atari7800 | Atari 7800 ProSystem | ProSystem | | | |
| atarixe | Atari XE | | | | | atari800 | Atari 800 | Atari800 | | | |
| atomiswave | Atomiswave | | | | | atarijaguar | Atari Jaguar | Virtual Jaguar | | | |
| bbcmicro | BBC Micro | | | | | atarijaguarcd | Atari Jaguar CD | Virtual Jaguar | | | |
| c64 | Commodore 64 | VICE x64sc, accurate (RetroArch) | | Single disk, tape or cartridge image in root folder and/or multi-disk images in separate folder | | atarilynx | Atari Lynx | Beetle Lynx | | | |
| cavestory | Cave Story (NXEngine) | | | | | atarist | Atari ST [also STE and Falcon] | Hatari | | | |
| cdtv | Commodore CDTV | | | | | atarixe | Atari XE | Atari800 | | | |
| chailove | ChaiLove game engine | | | | | atomiswave | Atomiswave | Flycast | | | |
| channelf | Fairchild Channel F | | | | | bbcmicro | BBC Micro | | | | |
| coco | Tandy Color Computer | | | | | c64 | Commodore 64 | VICE x64sc Accurate | VICE x64 Fast,<br>VICE x64 SuperCPU,<br>VICE x128,<br>Frodo | No | Single disk, tape r cartridge image in root folder and/or multi-disc images in separate folder |
| colecovision | ColecoVision | | | | | cavestory | Cave Story (NXEngine) | NXEngine | | | |
| daphne | Daphne Arcade Laserdisc Emulator | | | | | cdtv | Commodore CDTV | | | | |
| desktop | Desktop applications | N/A | | | | chailove | ChaiLove game engine | ChaiLove | | | |
| doom | Doom | | | | | channelf | Fairchild Channel F | FreeChaF | | | |
| dos | DOS (PC) | DOSBox-core (RetroArch) | DOSBox-Pure (RetroArch), DOSBox-SVN (RetroArch) | In separate folder (one folder per game, with complete file structure retained) | | coco | Tandy Color Computer | | | | |
| dragon32 | Dragon 32 | | | | | colecovision | ColecoVision | blueMSX | | | |
| dreamcast | Sega Dreamcast | | | | | daphne | Daphne Arcade Laserdisc Emulator | | | | |
| famicom | Nintendo Family Computer | Nestopia UE (RetroArch) | FCEUmm (RetroArch), Mesen (RetroArch), QuickNES (RetroArch) | Single archive or ROM file in root folder | | desktop | Desktop applications | N/A | | No | |
| fba | FinalBurn Alpha | FB Alpha 2012 (RetroArch)* | | Single archive file following MAME name standard in root folder | | doom | Doom | PrBoom | | | |
| fbneo | FinalBurn Neo | FinalBurn Neo (RetroArch)* | | Single archive file following MAME name standard in root folder | | dos | DOS (PC) | DOSBox-Core | DOSBox-Pure,<br>DOSBox-SVN | No | In separate folder (one folder per game, with complete file structure retained) |
| fds | Nintendo Famicom Disk System | Nestopia UE (RetroArch)* | | Single archive or ROM file in root folder | | dragon32 | Dragon 32 | | | | |
| gameandwatch | Nintendo Game and Watch | | | | | dreamcast | Sega Dreamcast | Flycast | | | |
| gamegear | Sega Game Gear | | | | | famicom | Nintendo Family Computer | Nestopia UE | FCEUmm,<br>Mesen,<br>QuickNES | No | Single archive or ROM file in root folder |
| gb | Nintendo Game Boy | | | | | fba | FinalBurn Alpha | FB Alpha 2012 | FB Alpha 2012 Neo Geo,<br>FB Alpha 2012 CPS-1,<br>FB Alpha 2012 CPS-2,<br>FB Alpha 2012 CPS-3 | Yes | Single archive file following MAME name standard in root folder |
| gba | Nintendo Game Boy Advance | | | | | fbneo | FinalBurn Neo | FinalBurn Neo | | Yes | Single archive file following MAME name standard in root folder |
| gbc | Nintendo Game Boy Color | | | | | fds | Nintendo Famicom Disk System | Nestopia UE | | Yes | Single archive or ROM file in root folder |
| gc | Nintendo GameCube | | | | | gameandwatch | Nintendo Game and Watch | GW | | | |
| genesis | Sega Genesis | Genesis Plus GX (RetroArch) | | Single archive or ROM file in root folder | | gamegear | Sega Game Gear | Genesis Plus GX | | | |
| gx4000 | Amstrad GX4000 | | | | | gb | Nintendo Game Boy | bsnes | | | |
| intellivision | Mattel Electronics Intellivision | | | | | gba | Nintendo Game Boy Advance | Beetle GBA | | | |
| kodi | Kodi home theatre software | N/A | | | | gbc | Nintendo Game Boy Color | bsnes | | | |
| lutris | Lutris open gaming platform | Lutris application (Unix only) | | Shell script in root folder | | gc | Nintendo GameCube | Dolphin | | | |
| lutro | Lutro game engine | | | | | genesis | Sega Genesis | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm | No | Single archive or ROM file in root folder |
| macintosh | Apple Macintosh | | | | | gx4000 | Amstrad GX4000 | | | | |
| mame | Multiple Arcade Machine Emulator | MAME 2003-Plus (RetroArch)* | MAME 2000 (RetroArch), MAME 2010 (RetroArch), MAME - Current (RetroArch) | Single archive file following MAME name standard in root folder | | intellivision | Mattel Electronics Intellivision | FreeIntv | | | |
| mame-advmame | AdvanceMAME | | | Single archive file following MAME name standard in root folder | | kodi | Kodi home theatre software | N/A | | No | |
| mame-mame4all | MAME4ALL | | | Single archive file following MAME name standard in root folder | | lutris | Lutris open gaming platform | Lutris application **(Standalone)** [U] | | No | Shell script in root folder |
| mastersystem | Sega Master System | Genesis Plus GX (RetroArch) | | Single archive or ROM file in root folder | | lutro | Lutro game engine | Lutro | | | |
| megacd | Sega Mega-CD | | | | | macintosh | Apple Macintosh | | | | |
| megacdjp | Sega Mega-CD [Japan] | | | | | mame | Multiple Arcade Machine Emulator | MAME 2003-Plus | MAME 2000,<br>MAME 2010,<br>MAME - Current,<br>FinalBurn Neo,<br>FB Alpha 2012 | Depends | Single archive file following MAME name standard in root folder |
| megadrive | Sega Mega Drive | Genesis Plus GX (RetroArch) | | Single archive or ROM file in root folder | | mame-advmame | AdvanceMAME | | | Depends | Single archive file following MAME name standard in root folder |
| mess | Multi Emulator Super System | | | | | mame-mame4all | MAME4ALL | | | Depends | Single archive file following MAME name standard in root folder |
| moonlight | Moonlight game streaming | | | | | mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>SMS Plus GX,<br>Gearsystem,<br>PicoDrive | No | Single archive or ROM file in root folder |
| moto | Thomson MO/TO series | Theodore (RetroArch) | | | | megacd | Sega Mega-CD | Genesis Plus GX | | | |
| msx | MSX | blueMSX (RetroArch) | | | | megacdjp | Sega Mega-CD [Japan] | Genesis Plus GX | | | |
| msx1 | MSX1 | blueMSX (RetroArch) | | | | megadrive | Sega Mega Drive | Genesis Plus GX | Genesis Plus GX Wide,<br>PicoDrive,<br>BlastEm | No | Single archive or ROM file in root folder |
| msx2 | MSX2 | blueMSX (RetroArch) | | | | mess | Multi Emulator Super System | MESS 2015 | | | |
| msxturbor | MSX Turbo R | blueMSX (RetroArch) | | | | moonlight | Moonlight game streaming | | | | |
| multivision | Othello Multivision | Gearsystem (RetroArch) | | | | moto | Thomson MO/TO series | Theodore | | | |
| naomi | Sega NAOMI | Flycast (RetroArch) | | | | msx | MSX | blueMSX | | | |
| naomigd | Sega NAOMI GD-ROM | Flycast (RetroArch) | | | | msx1 | MSX1 | blueMSX | | | |
| n3ds | Nintendo 3DS | Citra (RetroArch) | | | | msx2 | MSX2 | blueMSX | | | |
| n64 | Nintendo 64 | Mupen64Plus-Next (RetroArch) on Unix and Windows, ParaLLEl N64 (RetroArch) on macOS | ParaLLEl N64 (RetroArch) | Single archive or ROM file in root folder | | msxturbor | MSX Turbo R | blueMSX | | | |
| nds | Nintendo DS | | | | | multivision | Othello Multivision | Gearsystem | | | |
| neogeo | SNK Neo Geo | FinalBurn Neo (RetroArch)* | | Single archive file following MAME name standard in root folder | | naomi | Sega NAOMI | Flycast | | | |
| neogeocd | SNK Neo Geo CD | NeoCD (RetroArch)* | | Single archive in root folder (which includes the CD image and ripped audio) | | naomigd | Sega NAOMI GD-ROM | Flycast | | | |
| neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD (RetroArch)* | | Single archive in root folder (which includes the CD image and ripped audio) | | n3ds | Nintendo 3DS | Citra | | | |
| nes | Nintendo Entertainment System | Nestopia UE (RetroArch) | FCEUmm (RetroArch), Mesen (RetroArch), QuickNES (RetroArch) | Single archive or ROM file in root folder | | n64 | Nintendo 64 | Mupen64Plus-Next [UW],<br>ParaLLEl N64 [M] | ParaLLEl N64 [UW] | No | Single archive or ROM file in root folder |
| ngp | SNK Neo Geo Pocket | | | | | nds | Nintendo DS | melonDS | | | |
| ngpc | SNK Neo Geo Pocket Color | | | | | neogeo | SNK Neo Geo | FinalBurn Neo | | Yes | Single archive file following MAME name standard in root folder |
| odyssey2 | Magnavox Odyssey2 | | | | | neogeocd | SNK Neo Geo CD | NeoCD | | Yes | Single archive in root folder (which includes the CD image and ripped audio) |
| openbor | OpenBOR game engine | | | | | neogeocdjp | SNK Neo Geo CD [Japan] | NeoCD | | Yes | Single archive in root folder (which includes the CD image and ripped audio) |
| oric | Tangerine Computer Systems Oric | | | | | nes | Nintendo Entertainment System | Nestopia UE | FCEUmm,<br>Mesen,<br>QuickNES | No | Single archive or ROM file in root folder |
| palm | Palm OS | | | | | ngp | SNK Neo Geo Pocket | Beetle NeoPop | | | |
| pc | IBM PC | DOSBox-core (RetroArch) | DOSBox-Pure (RetroArch), DOSBox-SVN (RetroArch) | In separate folder (one folder per game, with complete file structure retained) | | ngpc | SNK Neo Geo Pocket Color | Beetle NeoPop | | | |
| pc88 | NEC PC-8800 series | QUASI88 (RetroArch) | | | | odyssey2 | Magnavox Odyssey2 | O2EM | | | |
| pc98 | NEC PC-9800 series | Neko Project II Kai (RetroArch) | | | | openbor | OpenBOR game engine | | | | |
| pcengine | NEC PC Engine | Beetle PCE (RetroArch) | | Single archive or ROM file in root folder | | oric | Tangerine Computer Systems Oric | | | | |
| pcenginecd | NEC PC Engine CD | Beetle PCE (RetroArch) | | | | palm | Palm OS | Mu | | | |
| pcfx | NEC PC-FX | | | | | pc | IBM PC | DOSBox-Core | DOSBox-Pure,<br>DOSBox-SVN | No | In separate folder (one folder per game, with complete file structure retained) |
| pokemini | Nintendo Pokémon Mini | | | | | pc88 | NEC PC-8800 series | QUASI88 | | | |
| ports | Ports | N/A | | Shell/batch script in separate folder (possibly combined with game data) | | pc98 | NEC PC-9800 series | Neko Project II Kai | | | |
| ps2 | Sony PlayStation 2 | | | | | pcengine | NEC PC Engine | Beetle PCE | Beetle PCE FAST | No | Single archive or ROM file in root folder |
| ps3 | Sony PlayStation 3 | | | | | pcenginecd | NEC PC Engine CD | Beetle PCE | Beetle PCE FAST | Yes | |
| ps4 | Sony PlayStation 4 | | | | | pcfx | NEC PC-FX | Beetle PC-FX | | | |
| psp | Sony PlayStation Portable | | | | | pokemini | Nintendo Pokémon Mini | PokeMini | | No | |
| psvita | Sony PlayStation Vita | | | | | ports | Ports | N/A | | No | Shell/batch script in separate folder (possibly combined with game data) |
| psx | Sony PlayStation | | | | | ps2 | Sony PlayStation 2 | PCSX2 [UW] | | | |
| residualvm | ResidualVM game engine | | | | | ps3 | Sony PlayStation 3 | | | | |
| samcoupe | SAM Coupé | | | | | ps4 | Sony PlayStation 4 | | | | |
| satellaview | Nintendo Satellaview | | | | | psp | Sony PlayStation Portable | PPSSPP | | | |
| saturn | Sega Saturn | Beetle Saturn (RetroArch) | | | | psvita | Sony PlayStation Vita | | | | |
| saturnjp | Sega Saturn [Japan] | Beetle Saturn (RetroArch) | | | | psx | Sony PlayStation | Beetle PSX | Beetle PSX HW,<br>PCSX ReARMed,<br>DuckStation | Yes | .chd file in root folder for single-disc games, .m3u playlist in root folder for multi-disc games |
| scummvm | ScummVM game engine | ScummVM (RetroArch) | | In separate folder (one folder per game, with complete file structure retained) | | residualvm | ResidualVM game engine | | | | |
| sega32x | Sega Mega Drive 32X | PicoDrive (RetroArch) | | Single archive or ROM file in root folder | | samcoupe | SAM Coupé | SimCoupe | | | |
| sega32xjp | Sega Super 32X [Japan] | PicoDrive (RetroArch) | | Single archive or ROM file in root folder | | satellaview | Nintendo Satellaview | Snes9x - Current | | | |
| sega32xna | Sega Genesis 32X [North America] | PicoDrive (RetroArch) | | Single archive or ROM file in root folder | | saturn | Sega Saturn | Beetle Saturn | | | |
| segacd | Sega CD | | | | | saturnjp | Sega Saturn [Japan] | Beetle Saturn | | | |
| sg-1000 | Sega SG-1000 | | | | | scummvm | ScummVM game engine | ScummVM | | No | In separate folder (one folder per game, with complete file structure retained) |
| snes | Nintendo SNES (Super Nintendo) | Snes9x - Current (RetroARch) | | Single archive or ROM file in root folder | | sega32x | Sega Mega Drive 32X | PicoDrive | | No | Single archive or ROM file in root folder |
| snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current (RetroARch) | | Single archive or ROM file in root folder | | sega32xjp | Sega Super 32X [Japan] | PicoDrive | | No | Single archive or ROM file in root folder |
| solarus | Solarus game engine | | | | | sega32xna | Sega Genesis 32X [North America] | PicoDrive | | No | Single archive or ROM file in root folder |
| spectravideo | Spectravideo | | | | | segacd | Sega CD | Genesis Plus GX | | | |
| steam | Valve Steam | Steam application | | Shell script/batch file in root folder | | sg-1000 | Sega SG-1000 | Genesis Plus GX | | | |
| stratagus | Stratagus game engine | | | | | snes | Nintendo SNES (Super Nintendo) | Snes9x - Current | Snes9x 2010,<br>bsnes,<br>bsnes-mercury Accuracy,<br>Beetle Supafaust [UW],<br>Mesen-S | No | Single archive or ROM file in root folder |
| sufami | Bandai SuFami Turbo | | | | | snesna | Nintendo SNES (Super Nintendo) [North America] | Snes9x - Current | Snes9x 2010,<br>bsnes,<br>bsnes-mercury Accuracy,<br>Beetle Supafaust [UW],<br>Mesen-S | No | Single archive or ROM file in root folder |
| supergrafx | NEC SuperGrafx | | | | | solarus | Solarus game engine | | | | |
| switch | Nintendo Switch | Yuzu (Linux and Windows only) | | | | spectravideo | Spectravideo | blueMSX | | | |
| tanodragon | Tano Dragon | | | | | steam | Valve Steam | Steam application **(Standalone)** | | No | Shell script/batch file in root folder |
| tg16 | NEC TurboGrafx-16 | Beetle PCE (RetroArch) | | Single archive or ROM file in root folder | | stratagus | Stratagus game engine | | | | |
| tg-cd | NEC TurboGrafx-CD | Beetle PCE (RetroArch) | | | | sufami | Bandai SuFami Turbo | Snes9x - Current | | | |
| ti99 | Texas Instruments TI-99 | | | | | supergrafx | NEC SuperGrafx | Beetle SuperGrafx | Beetle PCE | | |
| tic80 | TIC-80 game engine | | | | | switch | Nintendo Switch | Yuzu **(Standalone)** [UW] | | Yes | |
| to8 | Thomson TO8 | Theodore (RetroArch) | | | | tanodragon | Tano Dragon | | | | |
| trs-80 | Tandy TRS-80 | | | | | tg16 | NEC TurboGrafx-16 | Beetle PCE | Beetle PCE FAST | No | Single archive or ROM file in root folder |
| uzebox | Uzebox | | | | | tg-cd | NEC TurboGrafx-CD | Beetle PCE | Beetle PCE FAST | Yes | |
| vectrex | Vectrex | | | | | ti99 | Texas Instruments TI-99 | | | | |
| videopac | Philips Videopac G7000 (Magnavox Odyssey2) | | | | | tic80 | TIC-80 game engine | | | | |
| virtualboy | Nintendo Virtual Boy | | | | | to8 | Thomson TO8 | Theodore | | | |
| wii | Nintendo Wii | | | | | trs-80 | Tandy TRS-80 | | | | |
| wiiu | Nintendo Wii U | | | | | uzebox | Uzebox | Uzem | | | |
| wonderswan | Bandai WonderSwan | | | | | vectrex | Vectrex | vecx | | | |
| wonderswancolor | Bandai WonderSwan Color | | | | | videopac | Philips Videopac G7000 (Magnavox Odyssey2) | O2EM | | | |
| x1 | Sharp X1 | x1 (RetroArch) | | Single archive or ROM file in root folder | | virtualboy | Nintendo Virtual Boy | Beetle VB | | | |
| x68000 | Sharp X68000 | | | | | wii | Nintendo Wii | Dolphin | | | |
| xbox | Microsoft Xbox | | | | | wiiu | Nintendo Wii U | | | | |
| xbox360 | Microsoft Xbox 360 | | | | | wonderswan | Bandai WonderSwan | Beetle Cygne | | | |
| zmachine | Infocom Z-machine | | | | | wonderswancolor | Bandai WonderSwan Color | Beetle Cygne | | | |
| zx81 | Sinclair ZX81 | | | | | x1 | Sharp X1 | x1 | | | Single archive or ROM file in root folder |
| zxspectrum | Sinclair ZX Spectrum | | | | | x68000 | Sharp X68000 | PX68k | | | |
| xbox | Microsoft Xbox | | | | |
| xbox360 | Microsoft Xbox 360 | | | | |
| zmachine | Infocom Z-machine | | | | |
| zx81 | Sinclair ZX81 | EightyOne | | | |
| zxspectrum | Sinclair ZX Spectrum | Fuse | | | | | Amstrad GX4000 | | | | |

View file

@ -364,7 +364,7 @@ The platform name for the Commodore 64 is `c64`, so the following structure woul
~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u ~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u
``` ```
It's highly recommended to create `.m3u` playlist files for multi-disk images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game. It's highly recommended to create `.m3u` playlist files for multi-disc images as this normally automates disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game.
The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u: The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u:
@ -419,7 +419,7 @@ Apart from the potential difficulty in locating the emulator binary, there are s
There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs. There are multiple ways to run Amiga games, but the recommended approach is to use WHDLoad. The best way is to use hard disk images in `.hdf` or `.hdz` format, meaning there will be a single file per game. This makes it just as easy to play Amiga games as any console with game ROMs.
An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disk images as described earlier. An alternative would be to use `.adf` images as not all games may be available with WHDLoad support. For this, you can either put single-disk images in the root folder or in a dedicated adf directory, or multiple-disk games in separate folders. It's highly recommended to create `.m3u` playlist files for multi-disc images as described earlier.
Here's an example of what the file structure could look like: Here's an example of what the file structure could look like:
@ -859,7 +859,7 @@ If this setting is enabled and a folder has its flag set to be excluded from the
**Scrape actual folders** _(Multi-scraper only)_ **Scrape actual folders** _(Multi-scraper only)_
Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disk games where there is a folder for each individual game. Enabling this option causes folders themselves to be included by the scraper. This is useful for DOS games or any multi-disc games where there is a folder for each individual game.
**Auto-retry on peer verification errors** _(ScreenScraper only)_ **Auto-retry on peer verification errors** _(ScreenScraper only)_
@ -1206,7 +1206,7 @@ If this option is disabled, hidden files and folders within the ROMs directory t
**Show hidden games (requires restart)** **Show hidden games (requires restart)**
You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disk games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden. You can mark games as hidden in the metadata editor, which is useful for instance for DOS games where you may not want to see some batch files and executables inside ES-DE, or for multi-disc games where you may only want to show the .m3u playlists and not the individual game files. By disabling this option these files will not be processed at all when ES-DE starts up. If you enable the option you will see the files, but their name entries will be almost transparent in the gamelist view to visually indicate that they are hidden.
**Enable custom event scripts** **Enable custom event scripts**
@ -1394,7 +1394,7 @@ A flag to mark whether the game is suitable for children. This will be applied a
**Hidden** **Hidden**
A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disk games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry. A flag to indicate that the game is hidden. If the corresponding option has been set in the main menu, the game will not be shown. Useful for example for DOS games to hide batch scripts and unnecessary binaries or to hide the actual game files for multi-disc games. If a file or folder is flagged as hidden but the corresponding option to hide hidden games has not been enabled, then the opacity of the text will be lowered significantly to make it clear that it's a hidden entry.
**Broken/not working** **Broken/not working**
@ -1402,15 +1402,15 @@ A flag to indicate whether the game is broken. Useful for MAME games for instanc
**Exclude from game counter** _(files only)_ **Exclude from game counter** _(files only)_
A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disk Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot. A flag to indicate whether the game should be excluded from being counted. If this is set for a game, it will not be included in the game counter shown per system on the system view, and it will not be included in the system information field in the gamelist view. As well, it will be excluded from all automatic and custom collections. This option is quite useful for multi-file games such as multi-disc Amiga or Commodore 64 games, or for DOS games where you want to exclude setup programs and similar but still need them available in ES-DE and therefore can't hide them. Files that have this flag set will have a lower opacity in the gamelists, making them easy to spot.
**Exclude from multi-scraper** **Exclude from multi-scraper**
Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disk games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it. Whether to exclude the file from the multi-scraper. This is quite useful in order to avoid scraping all the disks for multi-disc games for example. There is an option in the scraper settings to ignore this flag, but by default the multi-scraper will respect it.
**Hide metadata fields** **Hide metadata fields**
This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disk, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disk games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled. This option will hide most metadata fields in the gamelist view. The intention is to be able to hide the fields for situations such as general folders (Multi-disc, Cartridges etc.) and for setup programs and similar (e.g. SETUP.EXE or INSTALL.BAT for DOS games). It could also be used on the game files for multi-disc games where perhaps only the .m3u playlist should have any metadata values. The only fields shown with this option enabled are the game name and description. Using the description it's possible to write some comments regarding the file or folder, should you want to. It's also possible to display game images and videos with this setting enabled.
**Launch command** _(files only)_ **Launch command** _(files only)_
@ -1625,9 +1625,9 @@ Consider the table below a work in progress as it's obvioulsy not fully populate
| 3do | 3DO | | | | 3do | 3DO | | |
| 64dd | Nintendo 64DD | RetroArch (Mupen64Plus-Next on Unix and Windows, ParaLLEl N64 on macOS) | | | 64dd | Nintendo 64DD | RetroArch (Mupen64Plus-Next on Unix and Windows, ParaLLEl N64 on macOS) | |
| ags | Adventure Game Studio game engine | | | | ags | Adventure Game Studio game engine | | |
| amiga | Commodore Amiga | RetroArch (P-UAE)* | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disk | | amiga | Commodore Amiga | RetroArch (P-UAE)* | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disc |
| amiga600 | Commodore Amiga 600 | RetroArch (P-UAE)* | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disk | | amiga600 | Commodore Amiga 600 | RetroArch (P-UAE)* | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disc |
| amiga1200 | Commodore Amiga 1200 | RetroArch (P-UAE)* | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disk | | amiga1200 | Commodore Amiga 1200 | RetroArch (P-UAE)* | WHDLoad hard disk image in .hdf or .hdz format in root folder, or diskette image in .adf format in root folder if single-disk, or in separate folder with .m3u playlist if multi-disc |
| amigacd32 | Commodore Amiga CD32 | | | | amigacd32 | Commodore Amiga CD32 | | |
| amstradcpc | Amstrad CPC | | | | amstradcpc | Amstrad CPC | | |
| apple2 | Apple II | | | | apple2 | Apple II | | |
@ -1645,7 +1645,7 @@ Consider the table below a work in progress as it's obvioulsy not fully populate
| atarixe | Atari XE | | | | atarixe | Atari XE | | |
| atomiswave | Atomiswave | | | | atomiswave | Atomiswave | | |
| bbcmicro | BBC Micro | | | | bbcmicro | BBC Micro | | |
| c64 | Commodore 64 | RetroArch (VICE x64sc, accurate) | Single disk, tape or cartridge image in root folder and/or multi-disk images in separate folder | | c64 | Commodore 64 | RetroArch (VICE x64sc, accurate) | Single disk, tape or cartridge image in root folder and/or multi-disc images in separate folder |
| cavestory | Cave Story (NXEngine) | | | | cavestory | Cave Story (NXEngine) | | |
| cdtv | Commodore CDTV | | | | cdtv | Commodore CDTV | | |
| chailove | ChaiLove game engine | | | | chailove | ChaiLove game engine | | |

View file

@ -10,105 +10,105 @@
project("emulationstation-de") project("emulationstation-de")
set(ES_HEADERS set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h ${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h
${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.h
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
# GUIs # GUIs
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h
# Scrapers # Scrapers
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.h ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.h
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.h ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.h
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.h ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.h
# Views # Views
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h
) )
set(ES_SOURCES set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileSorts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/MediaViewer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreensaver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
# GUIs # GUIs
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiAlternativeEmulators.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGameScraper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiLaunchScreen.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMediaViewerOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMetaDataEd.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiOfflineGenerator.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperSearch.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScreensaverOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp
# Scrapers # Scrapers
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBJSONScraperResources.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/ScreenScraper.cpp
# Views # Views
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/BasicGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/DetailedGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/UIModeController.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
) )
if(WIN32) if(WIN32)
@ -134,20 +134,48 @@ endif()
# Setup for installation and package generation. # Setup for installation and package generation.
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-58.dll ../avfilter-7.dll ../avformat-58.dll ../avutil-56.dll install(FILES ../avcodec-58.dll
../postproc-55.dll ../swresample-3.dll ../swscale-5.dll ../FreeImage.dll ../avfilter-7.dll
../glew32.dll ../libcrypto-1_1-x64.dll ../libcurl-x64.dll ../freetype.dll ../avformat-58.dll
../pugixml.dll ../libssl-1_1-x64.dll ../SDL2.dll ../MSVCP140.dll ../VCOMP140.DLL ../avutil-56.dll
../VCRUNTIME140.dll ../VCRUNTIME140_1.dll DESTINATION .) ../postproc-55.dll
../swresample-3.dll
../swscale-5.dll
../FreeImage.dll
../freetype.dll
../glew32.dll
../libcrypto-1_1-x64.dll
../libcurl-x64.dll
../libssl-1_1-x64.dll
../MSVCP140.dll
../pugixml.dll
../SDL2.dll
../VCOMP140.DLL
../VCRUNTIME140.dll
../VCRUNTIME140_1.dll
DESTINATION .)
if(VLC_PLAYER) if(VLC_PLAYER)
install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .)
endif() endif()
else() else()
install(FILES ../avcodec-58.dll ../avfilter-7.dll ../avformat-58.dll ../avutil-56.dll install(FILES ../avcodec-58.dll
../postproc-55.dll ../swresample-3.dll ../swscale-5.dll ../FreeImage.dll ../avfilter-7.dll
../glew32.dll ../libcrypto-1_1-x64.dll ../libcurl-x64.dll ../libfreetype.dll ../avformat-58.dll
../libpugixml.dll ../libssl-1_1-x64.dll ../SDL2.dll ../vcomp140.dll DESTINATION .) ../avutil-56.dll
../postproc-55.dll
../swresample-3.dll
../swscale-5.dll
../FreeImage.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 .)
if(VLC_PLAYER) if(VLC_PLAYER)
install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .) install(FILES ../libvlc.dll ../libvlccore.dll DESTINATION .)
endif() endif()
@ -166,12 +194,9 @@ elseif(APPLE)
# So an extra 'Resources' directory was added to the CMAKE_INSTALL_PREFIX variable as well # So an extra 'Resources' directory was added to the CMAKE_INSTALL_PREFIX variable as well
# to compensate for this. It's a bad solution to the problem and there must surely be a # to compensate for this. It's a bad solution to the problem and there must surely be a
# better way to fix this. # better way to fix this.
install(TARGETS EmulationStation RUNTIME install(TARGETS EmulationStation RUNTIME DESTINATION ../MacOS)
DESTINATION ../MacOS) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE.icns DESTINATION ../Resources)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE.icns install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_Info.plist DESTINATION .. RENAME Info.plist)
DESTINATION ../Resources)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_Info.plist
DESTINATION .. RENAME Info.plist)
# Another hack/workaround. I have not been able to find any way whatsover to force the # Another hack/workaround. I have not been able to find any way whatsover to force the
# linker to use rpaths for all shared libraries instead of absolute paths. So instead # linker to use rpaths for all shared libraries instead of absolute paths. So instead
@ -181,84 +206,81 @@ elseif(APPLE)
# on your system (e.g. if using libSDL2-2.1.0.dylib instead of libSDL2-2.0.0.dylib). # on your system (e.g. if using libSDL2-2.1.0.dylib instead of libSDL2-2.0.0.dylib).
# This problem definitely needs to be resolved properly at a later date. # This problem definitely needs to be resolved properly at a later date.
add_custom_command(TARGET EmulationStation POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} 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/libavcodec.58.dylib @rpath/libavcodec.58.dylib
-change /usr/local/lib/libavfilter.7.dylib @rpath/libavfilter.7.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/libavformat.58.dylib @rpath/libavformat.58.dylib
-change /usr/local/lib/libavutil.56.dylib @rpath/libavutil.56.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/libswresample.3.dylib @rpath/libswresample.3.dylib
-change /usr/local/lib/libswscale.5.dylib @rpath/libswscale.5.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/freeimage/lib/libfreeimage.dylib @rpath/libfreeimage.dylib
-change /usr/local/opt/freetype/lib/libfreetype.6.dylib @rpath/libfreetype.6.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/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 -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib
$<TARGET_FILE:EmulationStation>) $<TARGET_FILE:EmulationStation>)
set(APPLE_DYLIB_PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE set(APPLE_DYLIB_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE) WORLD_READ WORLD_EXECUTE)
install(FILES ${CMAKE_SOURCE_DIR}/libavcodec.58.dylib install(FILES ${CMAKE_SOURCE_DIR}/libavcodec.58.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libavfilter.7.dylib install(FILES ${CMAKE_SOURCE_DIR}/libavfilter.7.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libavformat.58.dylib install(FILES ${CMAKE_SOURCE_DIR}/libavformat.58.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libavutil.56.dylib install(FILES ${CMAKE_SOURCE_DIR}/libavutil.56.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libpostproc.55.dylib install(FILES ${CMAKE_SOURCE_DIR}/libpostproc.55.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libswresample.3.dylib install(FILES ${CMAKE_SOURCE_DIR}/libswresample.3.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libswscale.5.dylib install(FILES ${CMAKE_SOURCE_DIR}/libswscale.5.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libfdk-aac.2.dylib install(FILES ${CMAKE_SOURCE_DIR}/libfdk-aac.2.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libfreeimage.dylib install(FILES ${CMAKE_SOURCE_DIR}/libfreeimage.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libfreetype.6.dylib install(FILES ${CMAKE_SOURCE_DIR}/libfreetype.6.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libpng16.16.dylib install(FILES ${CMAKE_SOURCE_DIR}/libpng16.16.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libSDL2-2.0.0.dylib install(FILES ${CMAKE_SOURCE_DIR}/libSDL2-2.0.0.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
if(VLC_PLAYER) if(VLC_PLAYER)
install(FILES ${CMAKE_SOURCE_DIR}/libvlc.dylib install(FILES ${CMAKE_SOURCE_DIR}/libvlc.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libvlccore.dylib install(FILES ${CMAKE_SOURCE_DIR}/libvlccore.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS) PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/plugins install(DIRECTORY ${CMAKE_SOURCE_DIR}/plugins
DESTINATION ../MacOS) DESTINATION ../MacOS)
endif() endif()
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE
DESTINATION ../Resources) install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ../Resources)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources DESTINATION ../Resources)
DESTINATION ../Resources) install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes DESTINATION ../Resources)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ../Resources)
DESTINATION ../Resources)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses
DESTINATION ../Resources)
else() else()
install(TARGETS emulationstation RUNTIME install(TARGETS emulationstation RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) if(CMAKE_SYSTEM_NAME MATCHES "Linux")
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6)
else() else()
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz
DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man6) DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man6)
endif() endif()
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE install(FILES ${CMAKE_SOURCE_DIR}/LICENSE
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.desktop install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.desktop
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.svg install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.svg
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes install(DIRECTORY ${CMAKE_SOURCE_DIR}/themes
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation) DESTINATION ${CMAKE_INSTALL_PREFIX}/share/emulationstation)
endif() endif()
include(InstallRequiredSystemLibraries) include(InstallRequiredSystemLibraries)
@ -295,7 +317,7 @@ endif()
# Settings per operating system and generator type. # Settings per operating system and generator type.
if(APPLE) if(APPLE)
set(CPACK_GENERATOR "Bundle") set(CPACK_GENERATOR "Bundle")
if(${CMAKE_OSX_DEPLOYMENT_TARGET} VERSION_LESS 10.14) if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14)
set(CPACK_PACKAGE_FILE_NAME "EmulationStation-DE-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}_legacy") set(CPACK_PACKAGE_FILE_NAME "EmulationStation-DE-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}_legacy")
set(CPACK_DMG_VOLUME_NAME "EmulationStation Desktop Edition ${CPACK_PACKAGE_VERSION}_legacy") set(CPACK_DMG_VOLUME_NAME "EmulationStation Desktop Edition ${CPACK_PACKAGE_VERSION}_legacy")
else() else()
@ -309,7 +331,7 @@ if(APPLE)
set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_Info.plist") set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/EmulationStation-DE_Info.plist")
if(MACOS_CODESIGN_IDENTITY) if(MACOS_CODESIGN_IDENTITY)
set(CPACK_BUNDLE_APPLE_CERT_APP "Developer ID Application: ${MACOS_CODESIGN_IDENTITY}") set(CPACK_BUNDLE_APPLE_CERT_APP "Developer ID Application: ${MACOS_CODESIGN_IDENTITY}")
if(${CMAKE_OSX_DEPLOYMENT_TARGET} VERSION_GREATER 10.13) if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.13)
set(CPACK_BUNDLE_APPLE_CODESIGN_PARAMETER "--deep --force --options runtime") set(CPACK_BUNDLE_APPLE_CODESIGN_PARAMETER "--deep --force --options runtime")
endif() endif()
endif() endif()
@ -332,7 +354,7 @@ elseif(WIN32)
else() else()
set(CPACK_PACKAGE_INSTALL_DIRECTORY "emulationstation_${CMAKE_PACKAGE_VERSION}") set(CPACK_PACKAGE_INSTALL_DIRECTORY "emulationstation_${CMAKE_PACKAGE_VERSION}")
set(CPACK_PACKAGE_EXECUTABLES "emulationstation" "emulationstation") set(CPACK_PACKAGE_EXECUTABLES "emulationstation" "emulationstation")
if("${LINUX_CPACK_GENERATOR}" STREQUAL "DEB") if(LINUX_CPACK_GENERATOR STREQUAL "DEB")
set(CPACK_GENERATOR "DEB") set(CPACK_GENERATOR "DEB")
endif() endif()
set(CPACK_DEBIAN_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.deb") set(CPACK_DEBIAN_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.deb")
@ -344,7 +366,7 @@ else()
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)
if("${LINUX_CPACK_GENERATOR}" STREQUAL "RPM") if(LINUX_CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_GENERATOR "RPM") set(CPACK_GENERATOR "RPM")
endif() endif()
set(CPACK_RPM_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.rpm") set(CPACK_RPM_FILE_NAME "emulationstation-de-${CPACK_PACKAGE_VERSION}-${CPU_ARCHITECTURE}.rpm")

View file

@ -385,6 +385,14 @@ void CollectionSystemsManager::updateCollectionSystem(FileData* file, Collection
parentRootFolder->sort(parentRootFolder->getSortTypeFromString( parentRootFolder->sort(parentRootFolder->getSortTypeFromString(
parentRootFolder->getSortTypeString()), parentRootFolder->getSortTypeString()),
mFavoritesSorting); mFavoritesSorting);
GuiInfoPopup* s = new GuiInfoPopup(
mWindow,
"DISABLED '" +
Utils::String::toUpper(
Utils::String::removeParenthesis(file->getName())) +
"' IN '" + Utils::String::toUpper(sysData.system->getName()) + "'",
4000);
mWindow->setInfoPopup(s);
} }
else { else {
ViewController::get()->getGameListView(curSys).get()->remove(collectionEntry, ViewController::get()->getGameListView(curSys).get()->remove(collectionEntry,
@ -546,6 +554,15 @@ std::string CollectionSystemsManager::getValidNewCollectionName(std::string inNa
{ {
std::string name = inName; std::string name = inName;
// Trim leading and trailing whitespaces.
name.erase(name.begin(), std::find_if(name.begin(), name.end(), [](char c) {
return !std::isspace(static_cast<unsigned char>(c));
}));
name.erase(std::find_if(name.rbegin(), name.rend(),
[](char c) { return !std::isspace(static_cast<unsigned char>(c)); })
.base(),
name.end());
if (index == 0) { if (index == 0) {
size_t remove = std::string::npos; size_t remove = std::string::npos;
// Get valid name. // Get valid name.
@ -674,7 +691,6 @@ bool CollectionSystemsManager::toggleGameInCollection(FileData* file)
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();
SystemData* systemViewToUpdate = getSystemToView(sysData); SystemData* systemViewToUpdate = getSystemToView(sysData);
@ -1320,33 +1336,41 @@ void CollectionSystemsManager::addEnabledCollectionsToDisplayedSystems(
std::vector<std::string> CollectionSystemsManager::getSystemsFromConfig() std::vector<std::string> CollectionSystemsManager::getSystemsFromConfig()
{ {
std::vector<std::string> systems; std::vector<std::string> systems;
std::string path = SystemData::getConfigPath(false); std::vector<std::string> configPaths = SystemData::getConfigPath(false);
if (!Utils::FileSystem::exists(path)) // Here we don't honor the <loadExclusive> tag which may be present in the custom es_systems.xml
return systems; // file under ~/.emulationstation/custom_systems as we really want to include all the themes
// supported by ES-DE. Otherwise a user may accidentally create a custom collection that
// corresponds to a supported theme.
for (auto path : configPaths) {
if (!Utils::FileSystem::exists(path))
return systems;
pugi::xml_document doc; pugi::xml_document doc;
#if defined(_WIN64) #if defined(_WIN64)
pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str());
#else #else
pugi::xml_parse_result res = doc.load_file(path.c_str()); pugi::xml_parse_result res = doc.load_file(path.c_str());
#endif #endif
if (!res) if (!res)
return systems; return systems;
// Actually read the file. // Actually read the file.
pugi::xml_node systemList = doc.child("systemList"); pugi::xml_node systemList = doc.child("systemList");
if (!systemList) if (!systemList)
return systems; return systems;
for (pugi::xml_node system = systemList.child("system"); system; for (pugi::xml_node system = systemList.child("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); if (std::find(systems.cbegin(), systems.cend(), themeFolder) == systems.cend())
systems.push_back(themeFolder);
}
} }
std::sort(systems.begin(), systems.end()); std::sort(systems.begin(), systems.end());
return systems; return systems;
} }

View file

@ -33,16 +33,15 @@ FileData::FileData(FileType type,
const std::string& path, const std::string& path,
SystemEnvironmentData* envData, SystemEnvironmentData* envData,
SystemData* system) SystemData* system)
: mType(type) : metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
, mPath(path)
, mSystem(system)
, mEnvData(envData)
, mSourceFileData(nullptr) , mSourceFileData(nullptr)
, mParent(nullptr) , mParent(nullptr)
, mType(type)
, mPath(path)
, mEnvData(envData)
, mSystem(system)
, mOnlyFolders(false) , mOnlyFolders(false)
, mDeletionFlag(false) , mDeletionFlag(false)
// Metadata is set in the constructor.
, 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()) {
@ -224,7 +223,7 @@ const std::string FileData::getMediafilePath(std::string subdirectory, std::stri
subFolders + "/" + getDisplayName(); subFolders + "/" + getDisplayName();
// Look for an image file in the media directory. // Look for an image file in the media directory.
for (int i = 0; i < extList.size(); i++) { for (size_t i = 0; i < extList.size(); i++) {
std::string mediaPath = tempPath + extList[i]; std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath)) if (Utils::FileSystem::exists(mediaPath))
return mediaPath; return mediaPath;
@ -299,7 +298,7 @@ const std::string FileData::getVideoPath() const
getMediaDirectory() + mSystemName + "/videos" + subFolders + "/" + getDisplayName(); getMediaDirectory() + mSystemName + "/videos" + subFolders + "/" + getDisplayName();
// Look for media in the media directory. // Look for media in the media directory.
for (int i = 0; i < extList.size(); i++) { for (size_t i = 0; i < extList.size(); i++) {
std::string mediaPath = tempPath + extList[i]; std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath)) if (Utils::FileSystem::exists(mediaPath))
return mediaPath; return mediaPath;
@ -347,9 +346,9 @@ std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask,
out.insert(out.cend(), subChildren.cbegin(), subChildren.cend()); out.insert(out.cend(), subChildren.cbegin(), subChildren.cend());
} }
else { else {
for (auto it = subChildren.cbegin(); it != subChildren.cend(); it++) { for (auto it2 = subChildren.cbegin(); it2 != subChildren.cend(); it2++) {
if ((*it)->getCountAsGame()) if ((*it2)->getCountAsGame())
out.push_back(*it); out.push_back(*it2);
} }
} }
} }
@ -747,36 +746,49 @@ void FileData::launchGame(Window* window)
{ {
LOG(LogInfo) << "Launching game \"" << this->metadata.get("name") << "\"..."; LOG(LogInfo) << "Launching game \"" << this->metadata.get("name") << "\"...";
SystemData* gameSystem = nullptr;
std::string command = ""; std::string command = "";
std::string alternativeEmulator = getSystem()->getAlternativeEmulator(); std::string alternativeEmulator;
if (mSystem->isCollection())
gameSystem = SystemData::getSystemByName(mSystemName);
else
gameSystem = mSystem;
// This is just a precaution as getSystemByName() should always return a valid result.
if (gameSystem == nullptr)
gameSystem = mSystem;
alternativeEmulator = gameSystem->getAlternativeEmulator();
// Check if there is a game-specific alternative emulator configured. // Check if there is a game-specific alternative emulator configured.
// This takes precedence over any system-wide alternative emulator configuration. // This takes precedence over any system-wide alternative emulator configuration.
if (Settings::getInstance()->getBool("AlternativeEmulatorPerGame") && if (Settings::getInstance()->getBool("AlternativeEmulatorPerGame") &&
!metadata.get("altemulator").empty()) { !metadata.get("altemulator").empty()) {
command = getSystem()->getLaunchCommandFromLabel(metadata.get("altemulator")); command = gameSystem->getLaunchCommandFromLabel(metadata.get("altemulator"));
if (command == "") { if (command == "") {
LOG(LogWarning) << "Invalid alternative emulator \"" << metadata.get("altemulator") LOG(LogWarning) << "Invalid alternative emulator \"" << metadata.get("altemulator")
<< "\" configured for game"; << "\" configured for game";
} }
else { else {
LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \"" LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \""
<< metadata.get("altemulator") << "\" as configured for the game"; << metadata.get("altemulator")
<< "\" as configured for the specific game";
} }
} }
// Check if there is a system-wide alternative emulator configured. // Check if there is a system-wide alternative emulator configured.
if (command == "" && alternativeEmulator != "") { if (command == "" && alternativeEmulator != "") {
command = getSystem()->getLaunchCommandFromLabel(alternativeEmulator); command = gameSystem->getLaunchCommandFromLabel(alternativeEmulator);
if (command == "") { if (command == "") {
LOG(LogWarning) << "Invalid alternative emulator \"" LOG(LogWarning) << "Invalid alternative emulator \""
<< alternativeEmulator.substr(9, alternativeEmulator.length() - 9) << alternativeEmulator.substr(9, alternativeEmulator.length() - 9)
<< "\" configured for system \"" << getSystem()->getName() << "\""; << "\" configured for system \"" << gameSystem->getName() << "\"";
} }
else { else {
LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \"" LOG(LogDebug) << "FileData::launchGame(): Using alternative emulator \""
<< getSystem()->getAlternativeEmulator() << "\"" << gameSystem->getAlternativeEmulator() << "\""
<< " as configured for system \"" << this->getSystem()->getName() << "\""; << " as configured for system \"" << gameSystem->getName() << "\"";
} }
} }
@ -1341,10 +1353,10 @@ void CollectionFileData::refreshMetadata()
const std::string& CollectionFileData::getName() const std::string& CollectionFileData::getName()
{ {
if (mDirty) { if (mDirty) {
mCollectionFileName = mCollectionFileName = mSourceFileData->metadata.get("name");
Utils::String::removeParenthesis(mSourceFileData->metadata.get("name")); mCollectionFileName.append(" [")
mCollectionFileName += .append(Utils::String::toUpper(mSourceFileData->getSystem()->getName()))
" [" + Utils::String::toUpper(mSourceFileData->getSystem()->getName()) + "]"; .append("]");
mDirty = false; mDirty = false;
} }

View file

@ -21,6 +21,7 @@
FileFilterIndex::FileFilterIndex() FileFilterIndex::FileFilterIndex()
: mFilterByText(false) : mFilterByText(false)
, mTextRemoveSystem(false)
, mFilterByFavorites(false) , mFilterByFavorites(false)
, mFilterByGenre(false) , mFilterByGenre(false)
, mFilterByPlayers(false) , mFilterByPlayers(false)
@ -35,7 +36,7 @@ FileFilterIndex::FileFilterIndex()
// clang-format off // 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"},
{GENRE_FILTER, &mGenreIndexAllKeys, &mFilterByGenre, &mGenreIndexFilteredKeys, "genre", true, "genre", "GENRE"}, {GENRE_FILTER, &mGenreIndexAllKeys, &mFilterByGenre, &mGenreIndexFilteredKeys, "genre", true, "genre", "GENRE"},
{PLAYER_FILTER, &mPlayersIndexAllKeys, &mFilterByPlayers, &mPlayersIndexFilteredKeys, "players", false, "", "PLAYERS"}, {PLAYER_FILTER, &mPlayersIndexAllKeys, &mFilterByPlayers, &mPlayersIndexFilteredKeys, "players", false, "", "PLAYERS"},
@ -279,7 +280,7 @@ void FileFilterIndex::setTextFilter(std::string textFilter)
mFilterByText = false; mFilterByText = false;
else else
mFilterByText = true; mFilterByText = true;
}; }
void FileFilterIndex::clearAllFilters() void FileFilterIndex::clearAllFilters()
{ {
@ -359,15 +360,22 @@ bool FileFilterIndex::showFile(FileData* game)
bool keepGoing = false; bool keepGoing = false;
// 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 we're in a collection system and the option
if (mTextFilter != "" && // to show the system name has been enabled, then exclude the system name that is encapsulated
!(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) { // in [] from the search string.
if (mTextFilter != "" && mTextRemoveSystem &&
!(Utils::String::toUpper(game->getName().substr(0, game->getName().find_last_of("[")))
.find(mTextFilter) != std::string::npos)) {
return false; return false;
} }
else if (mTextFilter != "") { else if (mTextFilter != "" &&
nameMatch = true; !(Utils::String::toUpper(game->getName()).find(mTextFilter) != std::string::npos)) {
return false;
} }
if (mTextFilter != "")
nameMatch = true;
for (std::vector<FilterDataDecl>::const_iterator it = filterDataDecl.cbegin(); for (std::vector<FilterDataDecl>::const_iterator it = filterDataDecl.cbegin();
it != filterDataDecl.cend(); it++) { it != filterDataDecl.cend(); it++) {
FilterDataDecl filterData = (*it); FilterDataDecl filterData = (*it);

View file

@ -59,6 +59,7 @@ public:
bool isFiltered(); bool isFiltered();
bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); bool isKeyBeingFilteredBy(std::string key, FilterIndexType type);
std::vector<FilterDataDecl>& getFilterDataDecls() { return filterDataDecl; } std::vector<FilterDataDecl>& getFilterDataDecls() { return filterDataDecl; }
void setTextRemoveSystem(bool status) { mTextRemoveSystem = status; }
void importIndex(FileFilterIndex* indexToImport); void importIndex(FileFilterIndex* indexToImport);
void resetIndex(); void resetIndex();
@ -85,6 +86,7 @@ private:
std::string mTextFilter; std::string mTextFilter;
bool mFilterByText; bool mFilterByText;
bool mTextRemoveSystem;
bool mFilterByFavorites; bool mFilterByFavorites;
bool mFilterByGenre; bool mFilterByGenre;
@ -115,8 +117,6 @@ private:
std::vector<std::string> mCompletedIndexFilteredKeys; std::vector<std::string> mCompletedIndexFilteredKeys;
std::vector<std::string> mBrokenIndexFilteredKeys; std::vector<std::string> mBrokenIndexFilteredKeys;
std::vector<std::string> mHiddenIndexFilteredKeys; std::vector<std::string> mHiddenIndexFilteredKeys;
FileData* mRootFolder;
}; };
#endif // ES_APP_FILE_FILTER_INDEX_H #endif // ES_APP_FILE_FILTER_INDEX_H

View file

@ -234,4 +234,4 @@ namespace FileSorts
return system1.compare(system2) > 0; return system1.compare(system2) > 0;
} }
}; // namespace FileSorts } // 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 } // namespace FileSorts
#endif // ES_APP_FILE_SORTS_H #endif // ES_APP_FILE_SORTS_H

View file

@ -81,7 +81,7 @@ void MediaViewer::update(int deltaTime)
mVideo->update(deltaTime); mVideo->update(deltaTime);
} }
void MediaViewer::render() void MediaViewer::render(const glm::mat4& /*parentTrans*/)
{ {
glm::mat4 trans{Renderer::getIdentity()}; glm::mat4 trans{Renderer::getIdentity()};
Renderer::setMatrix(trans); Renderer::setMatrix(trans);
@ -184,7 +184,7 @@ void MediaViewer::findMedia()
void MediaViewer::showNext() void MediaViewer::showNext()
{ {
if (mHasImages && mCurrentImageIndex != mImageFiles.size() - 1) if (mHasImages && mCurrentImageIndex != static_cast<int>(mImageFiles.size()) - 1)
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
bool showedVideo = false; bool showedVideo = false;
@ -205,7 +205,7 @@ void MediaViewer::showNext()
if ((mVideo || showedVideo) && !mDisplayingImage) if ((mVideo || showedVideo) && !mDisplayingImage)
mCurrentImageIndex = 0; mCurrentImageIndex = 0;
else if (mImageFiles.size() > mCurrentImageIndex + 1) else if (static_cast<int>(mImageFiles.size()) > mCurrentImageIndex + 1)
mCurrentImageIndex++; mCurrentImageIndex++;
if (mVideo) if (mVideo)
@ -281,7 +281,7 @@ void MediaViewer::showImage(int index)
mDisplayingImage = true; mDisplayingImage = true;
if (!mImageFiles.empty() && mImageFiles.size() >= index) { if (!mImageFiles.empty() && static_cast<int>(mImageFiles.size()) >= index) {
mImage = new ImageComponent(mWindow, false, false); mImage = new ImageComponent(mWindow, false, false);
mImage->setImage(mImageFiles[index]); mImage->setImage(mImageFiles[index]);
mImage->setOrigin(0.5f, 0.5f); mImage->setOrigin(0.5f, 0.5f);

View file

@ -20,11 +20,11 @@ public:
MediaViewer(Window* window); MediaViewer(Window* window);
virtual ~MediaViewer(); virtual ~MediaViewer();
virtual bool startMediaViewer(FileData* game); virtual bool startMediaViewer(FileData* game) override;
virtual void stopMediaViewer(); virtual void stopMediaViewer() override;
virtual void update(int deltaTime); virtual void update(int deltaTime) override;
virtual void render(); virtual void render(const glm::mat4& parentTrans) override;
private: private:
void initiateViewer(); void initiateViewer();
@ -33,8 +33,8 @@ private:
void playVideo(); void playVideo();
void showImage(int index); void showImage(int index);
virtual void showNext(); virtual void showNext() override;
virtual void showPrevious(); virtual void showPrevious() override;
Window* mWindow; Window* mWindow;
FileData* mGame; FileData* mGame;

View file

@ -20,7 +20,7 @@
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},
{"sortname", MD_STRING, "", false, "sortname", "enter sort name", false}, {"sortname", MD_STRING, "", false, "sortname", "enter sortname", false},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true},
{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, {"rating", MD_RATING, "0", false, "rating", "enter rating", true},
{"releasedate", MD_DATE, "19700101T010000", false, "release date", "enter release date", true}, {"releasedate", MD_DATE, "19700101T010000", false, "release date", "enter release date", true},

View file

@ -387,219 +387,246 @@ bool SystemData::loadConfig()
LOG(LogInfo) << "Populating game systems..."; LOG(LogInfo) << "Populating game systems...";
std::string path = getConfigPath(true); std::vector<std::string> configPaths = getConfigPath(true);
const std::string rompath = FileData::getROMDirectory(); const std::string rompath = FileData::getROMDirectory();
LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"..."; bool onlyProcessCustomFile = false;
pugi::xml_document doc; for (auto configPath : configPaths) {
// If the loadExclusive tag is present in the custom es_systems.xml file, then skip
// processing of the bundled configuration file.
if (onlyProcessCustomFile)
break;
LOG(LogInfo) << "Parsing systems configuration file \"" << configPath << "\"...";
pugi::xml_document doc;
#if defined(_WIN64) #if defined(_WIN64)
pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPath).c_str());
#else #else
pugi::xml_parse_result res = doc.load_file(path.c_str()); pugi::xml_parse_result res = doc.load_file(configPath.c_str());
#endif #endif
if (!res) { if (!res) {
LOG(LogError) << "Couldn't parse es_systems.xml: " << res.description(); LOG(LogError) << "Couldn't parse es_systems.xml: " << res.description();
return true; return true;
} }
// Actually read the file. pugi::xml_node loadExclusive = doc.child("loadExclusive");
pugi::xml_node systemList = doc.child("systemList"); if (loadExclusive) {
if (configPath == configPaths.front() && configPaths.size() > 1) {
if (!systemList) { LOG(LogInfo) << "Only loading custom file as the <loadExclusive> tag is present";
LOG(LogError) << "es_systems.xml is missing the <systemList> tag"; onlyProcessCustomFile = true;
return true;
}
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string name;
std::string fullname;
std::string path;
std::string themeFolder;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
auto nameFindFunc = [&] {
for (auto system : sSystemVector) {
if (system->mName == name) {
LOG(LogWarning) << "A system with the name \"" << name
<< "\" has already been loaded, skipping duplicate entry";
return true;
}
} }
return false; else {
}; LOG(LogWarning) << "A <loadExclusive> tag is present in the bundled es_systems.xml "
"file, ignoring it as this is only supposed to be used for the "
"custom es_systems.xml file";
}
}
// If the name is matching a system that has already been loaded, then skip the entry. // Actually read the file.
if (nameFindFunc()) pugi::xml_node systemList = doc.child("systemList");
continue;
// If there is a %ROMPATH% variable set for the system, expand it. By doing this if (!systemList) {
// it's possible to use either absolute ROM paths in es_systems.xml or to utilize LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
// the ROM path configured as ROMDirectory in es_settings.xml. If it's set to "" return true;
// in this configuration file, the default hardcoded path $HOME/ROMs/ will be used. }
path = Utils::String::replace(path, "%ROMPATH%", rompath);
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string name;
std::string fullname;
std::string path;
std::string themeFolder;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
auto nameFindFunc = [&] {
for (auto system : sSystemVector) {
if (system->mName == name) {
LOG(LogWarning) << "A system with the name \"" << name
<< "\" has already been loaded, skipping duplicate entry";
return true;
}
}
return false;
};
// If the name is matching a system that has already been loaded, then skip the entry.
if (nameFindFunc())
continue;
// If there is a %ROMPATH% variable set for the system, expand it. By doing this
// it's possible to use either absolute ROM paths in es_systems.xml or to utilize
// the ROM path configured as ROMDirectory in es_settings.xml. If it's set to ""
// in this configuration file, the default hardcoded path $HOME/ROMs/ will be used.
path = Utils::String::replace(path, "%ROMPATH%", rompath);
#if defined(_WIN64) #if defined(_WIN64)
path = Utils::String::replace(path, "\\", "/"); path = Utils::String::replace(path, "\\", "/");
#endif #endif
path = Utils::String::replace(path, "//", "/"); path = Utils::String::replace(path, "//", "/");
// 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
if (!Utils::FileSystem::exists(path)) { // processing.
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name if (!Utils::FileSystem::exists(path)) {
<< "\" as the defined ROM directory \"" << path << "\" does not exist"; LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
continue; << "\" as the defined ROM directory \"" << path
} << "\" does not exist";
if (!Utils::FileSystem::isDirectory(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path
<< "\" is not actually a directory";
continue;
}
if (Utils::FileSystem::isSymlink(path)) {
// Make sure that the symlink is not pointing to somewhere higher in the hierarchy
// as that would lead to an infite loop, meaning the application would never start.
std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath);
if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) {
LOG(LogWarning) << "Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path
<< "\" is an infinitely recursive symlink";
continue; continue;
} }
} if (!Utils::FileSystem::isDirectory(path)) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name
<< "\" as the defined ROM directory \"" << path
<< "\" is not actually a directory";
continue;
}
if (Utils::FileSystem::isSymlink(path)) {
// Make sure that the symlink is not pointing to somewhere higher in the hierarchy
// as that would lead to an infite loop, meaning the application would never start.
std::string resolvedRompath = Utils::FileSystem::getCanonicalPath(rompath);
if (resolvedRompath.find(Utils::FileSystem::getCanonicalPath(path)) == 0) {
LOG(LogWarning)
<< "Skipping system \"" << name << "\" as the defined ROM directory \""
<< path << "\" is an infinitely recursive symlink";
continue;
}
}
// Convert extensions list from a string into a vector of strings. // Convert extensions list from a string into a vector of strings.
std::vector<std::string> extensions = readList(system.child("extension").text().get()); std::vector<std::string> extensions = readList(system.child("extension").text().get());
// Load all launch command tags for the system and if there are multiple tags, then // Load all launch command tags for the system and if there are multiple tags, then
// the label attribute needs to be set on all entries as it's a requirement for the // the label attribute needs to be set on all entries as it's a requirement for the
// alternative emulator logic. // alternative emulator logic.
std::vector<std::pair<std::string, std::string>> commands; std::vector<std::pair<std::string, std::string>> commands;
for (pugi::xml_node entry = system.child("command"); entry; for (pugi::xml_node entry = system.child("command"); entry;
entry = entry.next_sibling("command")) { entry = entry.next_sibling("command")) {
if (!entry.attribute("label")) { if (!entry.attribute("label")) {
if (commands.size() == 1) { if (commands.size() == 1) {
// The first command tag had a label but the second one doesn't. // The first command tag had a label but the second one doesn't.
LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator "
"entry, only the first command tag will be processed for system \""
<< name << "\"";
break;
}
else if (commands.size() > 1) {
// At least two command tags had a label but this one doesn't.
LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator "
"entry, no additional command tags will be processed for system \""
<< name << "\"";
break;
}
}
else if (!commands.empty() && commands.back().second == "") {
// There are more than one command tags and the first tag did not have a label.
LOG(LogError) LOG(LogError)
<< "Missing mandatory label attribute for alternative emulator " << "Missing mandatory label attribute for alternative emulator "
"entry, only the first command tag will be processed for system \"" "entry, only the first command tag will be processed for system \""
<< name << "\""; << name << "\"";
break; break;
} }
else if (commands.size() > 1) { commands.push_back(
// At least two command tags had a label but this one doesn't. std::make_pair(entry.text().get(), entry.attribute("label").as_string()));
LOG(LogError) }
<< "Missing mandatory label attribute for alternative emulator "
"entry, no additional command tags will be processed for system \"" // Platform ID list
<< name << "\""; const std::string platformList =
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<PlatformIds::PlatformId> platformIds;
for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) {
std::string str = *it;
PlatformIds::PlatformId platformId = PlatformIds::getPlatformId(str);
if (platformId == PlatformIds::PLATFORM_IGNORE) {
// When platform is PLATFORM_IGNORE, do not allow other platforms.
platformIds.clear();
platformIds.push_back(platformId);
break; break;
} }
}
else if (!commands.empty() && commands.back().second == "") {
// There are more than one command tags and the first tag did not have a label.
LOG(LogError) << "Missing mandatory label attribute for alternative emulator "
"entry, only the first command tag will be processed for system \""
<< name << "\"";
break;
}
commands.push_back(
std::make_pair(entry.text().get(), entry.attribute("label").as_string()));
}
// Platform ID list // If there's a platform entry defined but it does not match the list of supported
const std::string platformList = // platforms, then generate a warning.
Utils::String::toLower(system.child("platform").text().get()); if (str != "" && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \""
if (platformList == "") { << name << "\", scraper searches will be inaccurate";
LOG(LogWarning) << "No platform defined for system \"" << name else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
<< "\", scraper searches will be inaccurate"; platformIds.push_back(platformId);
}
std::vector<std::string> platformStrs = readList(platformList);
std::vector<PlatformIds::PlatformId> platformIds;
for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) {
std::string str = *it;
PlatformIds::PlatformId platformId = PlatformIds::getPlatformId(str);
if (platformId == PlatformIds::PLATFORM_IGNORE) {
// When platform is PLATFORM_IGNORE, do not allow other platforms.
platformIds.clear();
platformIds.push_back(platformId);
break;
} }
// If there's a platform entry defined but it does not match the list of supported // Theme folder.
// platforms, then generate a warning. themeFolder = system.child("theme").text().as_string(name.c_str());
if (str != "" && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \""
<< name << "\", scraper searches will be inaccurate";
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId);
}
// Theme folder. // Validate.
themeFolder = system.child("theme").text().as_string(name.c_str());
// Validate. if (name.empty()) {
LOG(LogError)
<< "A system in the es_systems.xml file has no name defined, skipping entry";
continue;
}
else if (fullname.empty() || path.empty() || extensions.empty() || commands.empty()) {
LOG(LogError) << "System \"" << name
<< "\" is missing the fullname, path, "
"extension, or command tag, skipping entry";
continue;
}
if (name.empty()) { // Convert path to generic directory seperators.
LOG(LogError) path = Utils::FileSystem::getGenericPath(path);
<< "A system in the es_systems.xml file has no name defined, skipping entry";
continue;
}
else if (fullname.empty() || path.empty() || extensions.empty() || commands.empty()) {
LOG(LogError) << "System \"" << name
<< "\" is missing the fullname, path, "
"extension, or command tag, skipping entry";
continue;
}
// Convert path to generic directory seperators.
path = Utils::FileSystem::getGenericPath(path);
#if defined(_WIN64) #if defined(_WIN64)
if (!Settings::getInstance()->getBool("ShowHiddenFiles") && if (!Settings::getInstance()->getBool("ShowHiddenFiles") &&
Utils::FileSystem::isHidden(path)) { Utils::FileSystem::isHidden(path)) {
LOG(LogWarning) << "Skipping hidden ROM folder \"" << path << "\""; LOG(LogWarning) << "Skipping hidden ROM folder \"" << path << "\"";
continue; continue;
} }
#endif #endif
// Create the system runtime environment data. // Create the system runtime environment data.
SystemEnvironmentData* envData = new SystemEnvironmentData; SystemEnvironmentData* envData = new SystemEnvironmentData;
envData->mStartPath = path; envData->mStartPath = path;
envData->mSearchExtensions = extensions; envData->mSearchExtensions = extensions;
envData->mLaunchCommands = commands; envData->mLaunchCommands = commands;
envData->mPlatformIds = platformIds; envData->mPlatformIds = platformIds;
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder); SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
bool onlyHidden = false; bool onlyHidden = false;
// 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 = newSys->getRootFolder()->getChildrenRecursive(); std::vector<FileData*> recursiveGames =
onlyHidden = true; newSys->getRootFolder()->getChildrenRecursive();
for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) { onlyHidden = true;
if ((*it)->getType() != FOLDER) { for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); it++) {
onlyHidden = (*it)->getHidden(); if ((*it)->getType() != FOLDER) {
if (!onlyHidden) onlyHidden = (*it)->getHidden();
break; if (!onlyHidden)
break;
}
} }
} }
}
if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) { if (newSys->getRootFolder()->getChildrenByFilename().size() == 0 || onlyHidden) {
LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << name LOG(LogDebug) << "SystemData::loadConfig(): Skipping system \"" << 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 {
sSystemVector.push_back(newSys); sSystemVector.push_back(newSys);
}
} }
} }
@ -634,8 +661,10 @@ void SystemData::deleteSystems()
sSystemVector.clear(); sSystemVector.clear();
} }
std::string SystemData::getConfigPath(bool legacyWarning) std::vector<std::string> SystemData::getConfigPath(bool legacyWarning)
{ {
std::vector<std::string> paths;
if (legacyWarning) { if (legacyWarning) {
std::string legacyConfigFile = std::string legacyConfigFile =
Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg"; Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg";
@ -662,7 +691,7 @@ std::string SystemData::getConfigPath(bool legacyWarning)
if (Utils::FileSystem::exists(path)) { if (Utils::FileSystem::exists(path)) {
LOG(LogInfo) << "Found custom systems configuration file"; LOG(LogInfo) << "Found custom systems configuration file";
return path; paths.push_back(path);
} }
#if defined(_WIN64) #if defined(_WIN64)
@ -674,18 +703,16 @@ std::string SystemData::getConfigPath(bool legacyWarning)
path = ResourceManager::getInstance()->getResourcePath(":/systems/unix/es_systems.xml", true); path = ResourceManager::getInstance()->getResourcePath(":/systems/unix/es_systems.xml", true);
#endif #endif
return path; paths.push_back(path);
return paths;
} }
bool SystemData::createSystemDirectories() bool SystemData::createSystemDirectories()
{ {
std::string path = getConfigPath(false); std::vector<std::string> configPaths = getConfigPath(true);
const std::string rompath = FileData::getROMDirectory(); const std::string rompath = FileData::getROMDirectory();
if (!Utils::FileSystem::exists(path)) { bool onlyProcessCustomFile = false;
LOG(LogInfo) << "Systems configuration file does not exist, aborting";
return true;
}
LOG(LogInfo) << "Generating ROM directory structure..."; LOG(LogInfo) << "Generating ROM directory structure...";
@ -706,144 +733,187 @@ bool SystemData::createSystemDirectories()
LOG(LogInfo) << "Base ROM directory \"" << rompath << "\" already exists"; LOG(LogInfo) << "Base ROM directory \"" << rompath << "\" already exists";
} }
LOG(LogInfo) << "Parsing systems configuration file \"" << path << "\"..."; if (configPaths.size() > 1) {
// If the loadExclusive tag is present in the custom es_systems.xml file, then skip
pugi::xml_document doc; // processing of the bundled configuration file.
pugi::xml_document doc;
#if defined(_WIN64) #if defined(_WIN64)
pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPaths.front()).c_str());
#else #else
pugi::xml_parse_result res = doc.load_file(path.c_str()); pugi::xml_parse_result res = doc.load_file(configPaths.front().c_str());
#endif #endif
if (res) {
if (!res) { pugi::xml_node loadExclusive = doc.child("loadExclusive");
LOG(LogError) << "Couldn't parse es_systems.xml"; if (loadExclusive)
LOG(LogError) << res.description(); onlyProcessCustomFile = true;
return true;
}
// Actually read the file.
pugi::xml_node systemList = doc.child("systemList");
if (!systemList) {
LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
return true;
}
std::vector<std::string> systemsVector;
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string systemDir;
std::string name;
std::string fullname;
std::string path;
std::string extensions;
std::vector<std::string> commands;
std::string platform;
std::string themeFolder;
const std::string systemInfoFileName = "/systeminfo.txt";
bool replaceInfoFile = false;
std::ofstream systemInfoFile;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
extensions = system.child("extension").text().get();
for (pugi::xml_node entry = system.child("command"); entry;
entry = entry.next_sibling("command")) {
commands.push_back(entry.text().get());
} }
platform = Utils::String::toLower(system.child("platform").text().get()); }
themeFolder = system.child("theme").text().as_string(name.c_str());
// Check that the %ROMPATH% variable is actually used for the path element. // Process the custom es_systems.xml file after the bundled file, as any systems with identical
// If not, skip the system. // <path> tags will be overwritten by the last occurrence.
if (path.find("%ROMPATH%") != 0) { std::reverse(configPaths.begin(), configPaths.end());
LOG(LogWarning) << "The path element for system \"" << name
<< "\" does not " std::vector<std::pair<std::string, std::string>> systemsVector;
"utilize the %ROMPATH% variable, skipping entry";
for (auto configPath : configPaths) {
// If the loadExclusive tag is present.
if (onlyProcessCustomFile && configPath == configPaths.front())
continue; continue;
}
else {
systemDir = path.substr(9, path.size() - 9);
}
// Trim any leading directory separator characters. LOG(LogInfo) << "Parsing systems configuration file \"" << configPath << "\"...";
systemDir.erase(systemDir.begin(),
std::find_if(systemDir.begin(), systemDir.end(),
[](char c) { return c != '/' && c != '\\'; }));
if (!Utils::FileSystem::exists(rompath + systemDir)) {
if (!Utils::FileSystem::createDirectory(rompath + systemDir)) {
LOG(LogError) << "Couldn't create system directory \"" << systemDir
<< "\", permission problems or disk full?";
return true;
}
else {
LOG(LogInfo) << "Created system directory \"" << systemDir << "\"";
}
}
else {
LOG(LogInfo) << "System directory \"" << systemDir << "\" already exists";
}
if (Utils::FileSystem::exists(rompath + systemDir + systemInfoFileName))
replaceInfoFile = true;
else
replaceInfoFile = false;
if (replaceInfoFile) {
if (Utils::FileSystem::removeFile(rompath + systemDir + systemInfoFileName))
return true;
}
pugi::xml_document doc;
#if defined(_WIN64) #if defined(_WIN64)
systemInfoFile.open( pugi::xml_parse_result res =
Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName).c_str()); doc.load_file(Utils::String::stringToWideString(configPath).c_str());
#else #else
systemInfoFile.open(rompath + systemDir + systemInfoFileName); pugi::xml_parse_result res = doc.load_file(configPath.c_str());
#endif #endif
if (systemInfoFile.fail()) { if (!res) {
LOG(LogError) << "Couldn't create system information file \"" LOG(LogError) << "Couldn't parse es_systems.xml";
<< rompath + systemDir + systemInfoFileName LOG(LogError) << res.description();
<< "\", permission problems or disk full?";
systemInfoFile.close();
return true; return true;
} }
systemInfoFile << "System name:" << std::endl; // Actually read the file.
systemInfoFile << name << std::endl << std::endl; pugi::xml_node systemList = doc.child("systemList");
systemInfoFile << "Full system name:" << std::endl;
systemInfoFile << fullname << std::endl << std::endl;
systemInfoFile << "Supported file extensions:" << std::endl;
systemInfoFile << extensions << std::endl << std::endl;
systemInfoFile << "Launch command:" << std::endl;
systemInfoFile << commands.front() << std::endl << std::endl;
// Alternative emulator configuration entries.
if (commands.size() > 1) {
systemInfoFile << (commands.size() == 2 ? "Alternative launch command:" :
"Alternative launch commands:")
<< std::endl;
for (auto it = commands.cbegin() + 1; it != commands.cend(); it++)
systemInfoFile << (*it) << std::endl;
systemInfoFile << std::endl;
}
systemInfoFile << "Platform (for scraping):" << std::endl;
systemInfoFile << platform << std::endl << std::endl;
systemInfoFile << "Theme folder:" << std::endl;
systemInfoFile << themeFolder << std::endl;
systemInfoFile.close();
systemsVector.push_back(systemDir + ": " + fullname); if (!systemList) {
LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
if (replaceInfoFile) { return true;
LOG(LogInfo) << "Replaced existing system information file \""
<< rompath + systemDir + systemInfoFileName << "\"";
} }
else {
LOG(LogInfo) << "Created system information file \"" for (pugi::xml_node system = systemList.child("system"); system;
<< rompath + systemDir + systemInfoFileName << "\""; system = system.next_sibling("system")) {
std::string systemDir;
std::string name;
std::string fullname;
std::string path;
std::string extensions;
std::vector<std::string> commands;
std::string platform;
std::string themeFolder;
const std::string systemInfoFileName = "/systeminfo.txt";
bool replaceInfoFile = false;
std::ofstream systemInfoFile;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
extensions = system.child("extension").text().get();
for (pugi::xml_node entry = system.child("command"); entry;
entry = entry.next_sibling("command")) {
commands.push_back(entry.text().get());
}
platform = Utils::String::toLower(system.child("platform").text().get());
themeFolder = system.child("theme").text().as_string(name.c_str());
// Check that the %ROMPATH% variable is actually used for the path element.
// If not, skip the system.
if (path.find("%ROMPATH%") != 0) {
LOG(LogWarning) << "The path element for system \"" << name
<< "\" does not "
"utilize the %ROMPATH% variable, skipping entry";
continue;
}
else {
systemDir = path.substr(9, path.size() - 9);
}
// Trim any leading directory separator characters.
systemDir.erase(systemDir.begin(),
std::find_if(systemDir.begin(), systemDir.end(),
[](char c) { return c != '/' && c != '\\'; }));
if (!Utils::FileSystem::exists(rompath + systemDir)) {
if (!Utils::FileSystem::createDirectory(rompath + systemDir)) {
LOG(LogError) << "Couldn't create system directory \"" << systemDir
<< "\", permission problems or disk full?";
return true;
}
else {
LOG(LogInfo) << "Created system directory \"" << systemDir << "\"";
}
}
else {
LOG(LogInfo) << "System directory \"" << systemDir << "\" already exists";
}
if (Utils::FileSystem::exists(rompath + systemDir + systemInfoFileName))
replaceInfoFile = true;
else
replaceInfoFile = false;
if (replaceInfoFile) {
if (Utils::FileSystem::removeFile(rompath + systemDir + systemInfoFileName))
return true;
}
#if defined(_WIN64)
systemInfoFile.open(
Utils::String::stringToWideString(rompath + systemDir + systemInfoFileName)
.c_str());
#else
systemInfoFile.open(rompath + systemDir + systemInfoFileName);
#endif
if (systemInfoFile.fail()) {
LOG(LogError) << "Couldn't create system information file \""
<< rompath + systemDir + systemInfoFileName
<< "\", permission problems or disk full?";
systemInfoFile.close();
return true;
}
systemInfoFile << "System name:" << std::endl;
if (configPaths.size() != 1 && configPath == configPaths.back())
systemInfoFile << name << " (custom system)" << std::endl << std::endl;
else
systemInfoFile << name << std::endl << std::endl;
systemInfoFile << "Full system name:" << std::endl;
systemInfoFile << fullname << std::endl << std::endl;
systemInfoFile << "Supported file extensions:" << std::endl;
systemInfoFile << extensions << std::endl << std::endl;
systemInfoFile << "Launch command:" << std::endl;
systemInfoFile << commands.front() << std::endl << std::endl;
// Alternative emulator configuration entries.
if (commands.size() > 1) {
systemInfoFile << (commands.size() == 2 ? "Alternative launch command:" :
"Alternative launch commands:")
<< std::endl;
for (auto it = commands.cbegin() + 1; it != commands.cend(); it++)
systemInfoFile << (*it) << std::endl;
systemInfoFile << std::endl;
}
systemInfoFile << "Platform (for scraping):" << std::endl;
systemInfoFile << platform << std::endl << std::endl;
systemInfoFile << "Theme folder:" << std::endl;
systemInfoFile << themeFolder << std::endl;
systemInfoFile.close();
auto systemIter = std::find_if(systemsVector.cbegin(), systemsVector.cend(),
[systemDir](std::pair<std::string, std::string> system) {
return system.first == systemDir;
});
if (systemIter != systemsVector.cend())
systemsVector.erase(systemIter);
if (configPaths.size() != 1 && configPath == configPaths.back())
systemsVector.push_back(std::make_pair(systemDir + " (custom system)", fullname));
else
systemsVector.push_back(std::make_pair(systemDir, fullname));
if (replaceInfoFile) {
LOG(LogInfo) << "Replaced existing system information file \""
<< rompath + systemDir + systemInfoFileName << "\"";
}
else {
LOG(LogInfo) << "Created system information file \""
<< rompath + systemDir + systemInfoFileName << "\"";
}
} }
} }
@ -870,8 +940,10 @@ bool SystemData::createSystemDirectories()
systemsFileSuccess = false; systemsFileSuccess = false;
} }
else { else {
for (std::string systemEntry : systemsVector) { std::sort(systemsVector.begin(), systemsVector.end());
systemsFile << systemEntry << std::endl; for (auto systemEntry : systemsVector) {
systemsFile << systemEntry.first.append(": ").append(systemEntry.second)
<< std::endl;
} }
systemsFile.close(); systemsFile.close();
} }
@ -897,6 +969,16 @@ bool SystemData::isVisible()
return true; return true;
} }
SystemData* SystemData::getSystemByName(const std::string& systemName)
{
for (auto it : sSystemVector) {
if ((*it).getName() == systemName)
return it;
}
return nullptr;
}
SystemData* SystemData::getNext() const SystemData* SystemData::getNext() const
{ {
std::vector<SystemData*>::const_iterator it = getIterator(); std::vector<SystemData*>::const_iterator it = getIterator();
@ -1163,21 +1245,21 @@ void SystemData::onMetaDataSavePoint()
writeMetaData(); writeMetaData();
} }
void SystemData::setupSystemSortType(FileData* mRootFolder) void SystemData::setupSystemSortType(FileData* rootFolder)
{ {
// If DefaultSortOrder is set to something, check that it is actually a valid value. // If DefaultSortOrder is set to something, check that it is actually a valid value.
if (Settings::getInstance()->getString("DefaultSortOrder") != "") { if (Settings::getInstance()->getString("DefaultSortOrder") != "") {
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( rootFolder->setSortTypeString(
Settings::getInstance()->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 (rootFolder->getSortTypeString() == "")
mRootFolder->setSortTypeString( rootFolder->setSortTypeString(
Settings::getInstance()->getDefaultString("DefaultSortOrder")); Settings::getInstance()->getDefaultString("DefaultSortOrder"));
} }

View file

@ -102,9 +102,9 @@ public:
std::string getLaunchCommandFromLabel(const std::string& label); std::string getLaunchCommandFromLabel(const std::string& label);
static void deleteSystems(); static void deleteSystems();
// Loads the systems configuration file at getConfigPath() and creates the systems. // Loads the systems configuration file(s) at getConfigPath() and creates the systems.
static bool loadConfig(); static bool loadConfig();
static std::string getConfigPath(bool legacyWarning); static std::vector<std::string> getConfigPath(bool legacyWarning);
// Generates the game system directories and information files based on es_systems.xml. // Generates the game system directories and information files based on es_systems.xml.
static bool createSystemDirectories(); static bool createSystemDirectories();
@ -131,6 +131,7 @@ public:
bool isVisible(); bool isVisible();
static SystemData* getSystemByName(const std::string& systemName);
SystemData* getNext() const; SystemData* getNext() const;
SystemData* getPrev() const; SystemData* getPrev() const;
static SystemData* getRandomSystem(const SystemData* currentSystem); static SystemData* getRandomSystem(const SystemData* currentSystem);
@ -146,14 +147,9 @@ public:
void onMetaDataSavePoint(); void onMetaDataSavePoint();
void writeMetaData(); void writeMetaData();
void setupSystemSortType(FileData* mRootFolder); void setupSystemSortType(FileData* rootFolder);
private: private:
bool mIsCollectionSystem;
bool mIsCustomCollectionSystem;
bool mIsGroupedCustomCollectionSystem;
bool mIsGameSystem;
bool mScrapeFlag; // Only used by scraper GUI to remember which systems to scrape.
std::string mName; std::string mName;
std::string mFullName; std::string mFullName;
SystemEnvironmentData* mEnvData; SystemEnvironmentData* mEnvData;
@ -161,6 +157,12 @@ private:
std::string mThemeFolder; std::string mThemeFolder;
std::shared_ptr<ThemeData> mTheme; std::shared_ptr<ThemeData> mTheme;
bool mIsCollectionSystem;
bool mIsCustomCollectionSystem;
bool mIsGroupedCustomCollectionSystem;
bool mIsGameSystem;
bool mScrapeFlag; // Only used by scraper GUI to remember which systems to scrape.
bool populateFolder(FileData* folder); bool populateFolder(FileData* folder);
void indexAllGameFilters(const FileData* folder); void indexAllGameFilters(const FileData* folder);
void setIsGameSystemStatus(); void setIsGameSystemStatus();

View file

@ -440,10 +440,10 @@ void SystemScreensaver::generateImageList()
continue; continue;
std::vector<FileData*> allFiles = (*it)->getRootFolder()->getFilesRecursive(GAME, true); std::vector<FileData*> allFiles = (*it)->getRootFolder()->getFilesRecursive(GAME, true);
for (auto it = allFiles.begin(); it != allFiles.end(); it++) { for (auto it2 = allFiles.begin(); it2 != allFiles.end(); it2++) {
std::string imagePath = (*it)->getImagePath(); std::string imagePath = (*it2)->getImagePath();
if (imagePath != "") if (imagePath != "")
mImageFiles.push_back((*it)); mImageFiles.push_back((*it2));
} }
} }
} }
@ -457,10 +457,10 @@ void SystemScreensaver::generateVideoList()
continue; continue;
std::vector<FileData*> allFiles = (*it)->getRootFolder()->getFilesRecursive(GAME, true); std::vector<FileData*> allFiles = (*it)->getRootFolder()->getFilesRecursive(GAME, true);
for (auto it = allFiles.begin(); it != allFiles.end(); it++) { for (auto it2 = allFiles.begin(); it2 != allFiles.end(); it2++) {
std::string videoPath = (*it)->getVideoPath(); std::string videoPath = (*it2)->getVideoPath();
if (videoPath != "") if (videoPath != "")
mVideoFiles.push_back((*it)); mVideoFiles.push_back((*it2));
} }
} }
} }

View file

@ -131,9 +131,10 @@ void VolumeControl::init()
// Retrieve endpoint volume. // Retrieve endpoint volume.
defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER,
nullptr, reinterpret_cast<LPVOID*>(&endpointVolume)); nullptr, 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!";
}
// Release default device. we don't need it anymore. // Release default device. we don't need it anymore.
defaultDevice->Release(); defaultDevice->Release();
} }
@ -245,8 +246,9 @@ void VolumeControl::setVolume(int volume)
float floatVolume = 0.0f; // 0-1 float floatVolume = 0.0f; // 0-1
if (volume > 0) if (volume > 0)
floatVolume = static_cast<float>(volume) / 100.0f; floatVolume = static_cast<float>(volume) / 100.0f;
if (endpointVolume->SetMasterVolumeLevelScalar(floatVolume, nullptr) != S_OK) if (endpointVolume->SetMasterVolumeLevelScalar(floatVolume, nullptr) != S_OK) {
LOG(LogError) << "VolumeControl::setVolume(): Failed to set master volume"; LOG(LogError) << "VolumeControl::setVolume(): Failed to set master volume";
}
} }
#endif #endif
} }

View file

@ -25,6 +25,9 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window)
float systemSizeX = mMenu.getSize().x / 3.27f; float systemSizeX = mMenu.getSize().x / 3.27f;
float labelSizeX = mMenu.getSize().x / 1.53f; float labelSizeX = mMenu.getSize().x / 1.53f;
if (Renderer::getScreenHeightModifier() > 1.0f)
labelSizeX += 8.0f * Renderer::getScreenHeightModifier();
for (auto it = SystemData::sSystemVector.cbegin(); // Line break. for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
@ -95,7 +98,7 @@ GuiAlternativeEmulators::GuiAlternativeEmulators(Window* window)
row.addElement(labelText, false); row.addElement(labelText, false);
row.makeAcceptInputHandler([this, it, labelText] { row.makeAcceptInputHandler([this, it, labelText] {
if (labelText->getValue() == "<REMOVED ENTRY>") if (labelText->getValue() == ViewController::CROSSEDCIRCLE_CHAR + " CLEARED ENTRY")
return; return;
selectorWindow(*it); selectorWindow(*it);
}); });
@ -154,7 +157,7 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
ComponentListRow row; ComponentListRow row;
if (entry.second == "") if (entry.second == "")
label = "<CLEAR INVALID ENTRY>"; label = ViewController::CROSSEDCIRCLE_CHAR + " CLEAR INVALID ENTRY";
else else
label = entry.second; label = entry.second;
@ -175,7 +178,8 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) { if (entry.second == system->getSystemEnvData()->mLaunchCommands.front().second) {
if (system->getSystemEnvData()->mLaunchCommands.front().second == "") { if (system->getSystemEnvData()->mLaunchCommands.front().second == "") {
updateMenu(system->getName(), "<REMOVED ENTRY>", updateMenu(system->getName(),
ViewController::CROSSEDCIRCLE_CHAR + " CLEARED ENTRY",
(entry.second == (entry.second ==
system->getSystemEnvData()->mLaunchCommands.front().second)); system->getSystemEnvData()->mLaunchCommands.front().second));
} }

View file

@ -14,6 +14,7 @@
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiSettings.h" #include "guis/GuiSettings.h"
#include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -208,10 +209,21 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st
window->removeGui(topGui); window->removeGui(topGui);
createCustomCollection(name); createCustomCollection(name);
}; };
row.makeAcceptInputHandler([this, createCollectionCall] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name", "", if (Settings::getInstance()->getBool("VirtualKeyboard")) {
createCollectionCall, false, "SAVE")); row.makeAcceptInputHandler([this, createCollectionCall] {
}); mWindow->pushGui(new GuiTextEditKeyboardPopup(
mWindow, getHelpStyle(), "New Collection Name", "", createCollectionCall, false,
"CREATE", "CREATE COLLECTION?"));
});
}
else {
row.makeAcceptInputHandler([this, createCollectionCall] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "New Collection Name",
"", createCollectionCall, false, "CREATE",
"CREATE COLLECTION?"));
});
}
addRow(row); addRow(row);
// Delete custom collection. // Delete custom collection.
@ -271,7 +283,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window, std::st
CollectionSystemsManager::get()->deleteCustomCollection(name); CollectionSystemsManager::get()->deleteCustomCollection(name);
return true; return true;
}, },
"NO", [this] { return false; })); "NO", [] { return false; }));
}; };
row.makeAcceptInputHandler(deleteCollectionCall); row.makeAcceptInputHandler(deleteCollectionCall);
auto customCollection = std::make_shared<TextComponent>( auto customCollection = std::make_shared<TextComponent>(

View file

@ -22,10 +22,10 @@ GuiGameScraper::GuiGameScraper(Window* window,
ScraperSearchParams params, ScraperSearchParams params,
std::function<void(const ScraperSearchResult&)> doneFunc) std::function<void(const ScraperSearchResult&)> doneFunc)
: GuiComponent(window) : GuiComponent(window)
, mClose(false)
, mGrid(window, glm::ivec2{1, 7}) , mGrid(window, glm::ivec2{1, 7})
, mBox(window, ":/graphics/frame.svg") , mBox(window, ":/graphics/frame.svg")
, mSearchParams(params) , mSearchParams(params)
, mClose(false)
{ {
addChild(&mBox); addChild(&mBox);
addChild(&mGrid); addChild(&mGrid);

View file

@ -12,6 +12,7 @@
#include "SystemData.h" #include "SystemData.h"
#include "components/OptionListComponent.h" #include "components/OptionListComponent.h"
#include "guis/GuiTextEditKeyboardPopup.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"
@ -35,6 +36,15 @@ void GuiGamelistFilter::initializeMenu()
// Get filters from system. // Get filters from system.
mFilterIndex = mSystem->getIndex(); mFilterIndex = mSystem->getIndex();
// If this is a collection and system names are shown per game, then let FileFilterIndex
// know about this so the system names will not be included in game name text searches.
if (ViewController::get()->getState().getSystem()->isCollection()) {
if (Settings::getInstance()->getBool("CollectionShowSystemInfo"))
mFilterIndex->setTextRemoveSystem(true);
else
mFilterIndex->setTextRemoveSystem(false);
}
ComponentListRow row; ComponentListRow row;
// Show filtered menu. // Show filtered menu.
@ -88,9 +98,9 @@ void GuiGamelistFilter::addFiltersToMenu()
{ {
ComponentListRow row; ComponentListRow row;
auto lbl = auto lbl = std::make_shared<TextComponent>(
std::make_shared<TextComponent>(mWindow, Utils::String::toUpper("TEXT FILTER (GAME NAME)"), mWindow, Utils::String::toUpper(ViewController::KEYBOARD_CHAR + " GAME NAME"),
Font::get(FONT_SIZE_MEDIUM), 0x777777FF); Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
mTextFilterField = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_MEDIUM), mTextFilterField = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_MEDIUM),
0x777777FF, ALIGN_RIGHT); 0x777777FF, ALIGN_RIGHT);
@ -118,11 +128,20 @@ void GuiGamelistFilter::addFiltersToMenu()
mFilterIndex->setTextFilter(Utils::String::toUpper(newVal)); mFilterIndex->setTextFilter(Utils::String::toUpper(newVal));
}; };
row.makeAcceptInputHandler([this, updateVal] { if (Settings::getInstance()->getBool("VirtualKeyboard")) {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "TEXT FILTER (GAME NAME)", row.makeAcceptInputHandler([this, updateVal] {
mTextFilterField->getValue(), updateVal, false, "OK", mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), "GAME NAME",
"APPLY CHANGES?")); mTextFilterField->getValue(), updateVal,
}); false, "OK", "APPLY CHANGES?"));
});
}
else {
row.makeAcceptInputHandler([this, updateVal] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "GAME NAME",
mTextFilterField->getValue(), updateVal, false,
"OK", "APPLY CHANGES?"));
});
}
mMenu.addRow(row); mMenu.addRow(row);
@ -137,9 +156,6 @@ void GuiGamelistFilter::addFiltersToMenu()
std::string menuLabel = (*it).menuLabel; // Text to show in menu. std::string menuLabel = (*it).menuLabel; // Text to show in menu.
std::shared_ptr<OptionListComponent<std::string>> optionList; std::shared_ptr<OptionListComponent<std::string>> optionList;
// Add filters (with first one selected).
ComponentListRow row;
// Add genres. // Add genres.
optionList = std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(), optionList = std::make_shared<OptionListComponent<std::string>>(mWindow, getHelpStyle(),
menuLabel, true); menuLabel, true);

View file

@ -27,8 +27,8 @@
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system)
: GuiComponent(window) : GuiComponent(window)
, mSystem(system)
, mMenu(window, "OPTIONS") , mMenu(window, "OPTIONS")
, mSystem(system)
, mFiltersChanged(false) , mFiltersChanged(false)
, mCancelled(false) , mCancelled(false)
, mIsCustomCollection(false) , mIsCustomCollection(false)

View file

@ -16,10 +16,10 @@
GuiLaunchScreen::GuiLaunchScreen(Window* window) GuiLaunchScreen::GuiLaunchScreen(Window* window)
: GuiComponent(window) : GuiComponent(window)
, mWindow(window)
, mBackground(window, ":/graphics/frame.svg") , mBackground(window, ":/graphics/frame.svg")
, mGrid(nullptr) , mGrid(nullptr)
, mMarquee(nullptr) , mMarquee(nullptr)
, mWindow(window)
{ {
addChild(&mBackground); addChild(&mBackground);
mWindow->setLaunchScreen(this); mWindow->setLaunchScreen(this);
@ -169,7 +169,6 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
mMarquee->setOrigin(0.5f, 0.5f); mMarquee->setOrigin(0.5f, 0.5f);
glm::vec3 currentPos{mMarquee->getPosition()}; glm::vec3 currentPos{mMarquee->getPosition()};
glm::vec2 currentSize{mMarquee->getSize()};
// 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;
@ -221,7 +220,7 @@ void GuiLaunchScreen::update(int deltaTime)
mScaleUp = glm::clamp(mScaleUp + 0.07f, 0.0f, 1.0f); mScaleUp = glm::clamp(mScaleUp + 0.07f, 0.0f, 1.0f);
} }
void GuiLaunchScreen::render() void GuiLaunchScreen::render(const glm::mat4& /*parentTrans*/)
{ {
// Scale up animation. // Scale up animation.
if (mScaleUp < 1.0f) if (mScaleUp < 1.0f)

View file

@ -30,12 +30,12 @@ public:
void onSizeChanged() override; void onSizeChanged() override;
virtual void update(int deltaTime) override; virtual void update(int deltaTime) override;
virtual void render() override; virtual void render(const glm::mat4& parentTrans) override;
private: private:
Window* mWindow; Window* mWindow;
ComponentGrid* mGrid;
NinePatchComponent mBackground; NinePatchComponent mBackground;
ComponentGrid* mGrid;
std::shared_ptr<TextComponent> mTitle; std::shared_ptr<TextComponent> mTitle;
std::shared_ptr<TextComponent> mGameName; std::shared_ptr<TextComponent> mGameName;

View file

@ -22,12 +22,13 @@
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
#include "guis/GuiAlternativeEmulators.h" #include "guis/GuiAlternativeEmulators.h"
#include "guis/GuiCollectionSystemsOptions.h" #include "guis/GuiCollectionSystemsOptions.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiDetectDevice.h" #include "guis/GuiDetectDevice.h"
#include "guis/GuiMediaViewerOptions.h" #include "guis/GuiMediaViewerOptions.h"
#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 "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h"
#include "views/UIModeController.h" #include "views/UIModeController.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "views/gamelist/IGameListView.h" #include "views/gamelist/IGameListView.h"
@ -509,6 +510,18 @@ void GuiMenu::openUIOptions()
} }
}); });
// Enable virtual (on-screen) keyboard.
auto virtual_keyboard = std::make_shared<SwitchComponent>(mWindow);
virtual_keyboard->setState(Settings::getInstance()->getBool("VirtualKeyboard"));
s->addWithLabel("ENABLE VIRTUAL KEYBOARD", virtual_keyboard);
s->addSaveFunc([virtual_keyboard, s] {
if (virtual_keyboard->getState() != Settings::getInstance()->getBool("VirtualKeyboard")) {
Settings::getInstance()->setBool("VirtualKeyboard", virtual_keyboard->getState());
s->setNeedsSaving();
s->setInvalidateCachedBackground();
}
});
// Enable the 'Y' button for tagging games as favorites. // Enable the 'Y' button for tagging games as favorites.
auto favorites_add_button = std::make_shared<SwitchComponent>(mWindow); auto favorites_add_button = std::make_shared<SwitchComponent>(mWindow);
favorites_add_button->setState(Settings::getInstance()->getBool("FavoritesAddButton")); favorites_add_button->setState(Settings::getInstance()->getBool("FavoritesAddButton"));
@ -809,10 +822,20 @@ void GuiMenu::openOtherOptions()
rowMediaDir.makeAcceptInputHandler([this, titleMediaDir, mediaDirectoryStaticText, rowMediaDir.makeAcceptInputHandler([this, titleMediaDir, mediaDirectoryStaticText,
defaultDirectoryText, initValueMediaDir, updateValMediaDir, defaultDirectoryText, initValueMediaDir, updateValMediaDir,
multiLineMediaDir] { multiLineMediaDir] {
mWindow->pushGui(new GuiComplexTextEditPopup( if (Settings::getInstance()->getBool("VirtualKeyboard")) {
mWindow, getHelpStyle(), titleMediaDir, mediaDirectoryStaticText, defaultDirectoryText, mWindow->pushGui(new GuiTextEditKeyboardPopup(
Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir, mWindow, getHelpStyle(), titleMediaDir,
multiLineMediaDir, "SAVE", "SAVE CHANGES?")); Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir,
multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText,
defaultDirectoryText, "load default directory"));
}
else {
mWindow->pushGui(new GuiTextEditPopup(
mWindow, getHelpStyle(), titleMediaDir,
Settings::getInstance()->getString("MediaDirectory"), updateValMediaDir,
multiLineMediaDir, "SAVE", "SAVE CHANGES?", mediaDirectoryStaticText,
defaultDirectoryText, "load default directory"));
}
}); });
s->addRow(rowMediaDir); s->addRow(rowMediaDir);
@ -1308,7 +1331,7 @@ void GuiMenu::close(bool closeAllWindows)
} }
else { else {
Window* window = mWindow; Window* window = mWindow;
closeFunc = [window, this] { closeFunc = [window] {
while (window->peekGui() != ViewController::get()) while (window->peekGui() != ViewController::get())
delete window->peekGui(); delete window->peekGui();
}; };

View file

@ -23,9 +23,9 @@
#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/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
@ -40,9 +40,9 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
std::function<void()> clearGameFunc, std::function<void()> clearGameFunc,
std::function<void()> deleteGameFunc) std::function<void()> deleteGameFunc)
: GuiComponent(window) : GuiComponent(window)
, mScraperParams(scraperParams)
, mBackground(window, ":/graphics/frame.svg") , mBackground(window, ":/graphics/frame.svg")
, mGrid(window, glm::ivec2{1, 3}) , mGrid(window, glm::ivec2{1, 3})
, mScraperParams(scraperParams)
, mMetaDataDecl(mdd) , mMetaDataDecl(mdd)
, mMetaData(md) , mMetaData(md)
, mSavedCallback(saveCallback) , mSavedCallback(saveCallback)
@ -188,7 +188,6 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
bracket->setResize(glm::vec2{0.0f, lbl->getFont()->getLetterHeight()}); bracket->setResize(glm::vec2{0.0f, lbl->getFont()->getLetterHeight()});
row.addElement(bracket, false); row.addElement(bracket, false);
bool multiLine = false;
const std::string title = iter->displayPrompt; const std::string title = iter->displayPrompt;
// OK callback (apply new value to ed). // OK callback (apply new value to ed).
@ -218,20 +217,30 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
if (mInvalidEmulatorEntry || if (mInvalidEmulatorEntry ||
scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) { scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) {
row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal] { row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal,
auto s = new GuiSettings(mWindow, title); originalValue] {
GuiSettings* s = nullptr;
if (!mInvalidEmulatorEntry && ed->getValue() == "" && bool singleEntry =
scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1) scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1;
if (mInvalidEmulatorEntry && singleEntry)
s = new GuiSettings(mWindow, "CLEAR INVALID ENTRY");
else
s = new GuiSettings(mWindow, title);
if (!mInvalidEmulatorEntry && ed->getValue() == "" && singleEntry)
return; return;
std::vector<std::pair<std::string, std::string>> launchCommands = std::vector<std::pair<std::string, std::string>> launchCommands =
scraperParams.system->getSystemEnvData()->mLaunchCommands; scraperParams.system->getSystemEnvData()->mLaunchCommands;
if (ed->getValue() != "" && mInvalidEmulatorEntry) if (ed->getValue() != "" && mInvalidEmulatorEntry && singleEntry)
launchCommands.push_back(std::make_pair("", "<CLEAR INVALID ENTRY>")); launchCommands.push_back(std::make_pair(
"", ViewController::EXCLAMATION_CHAR + " " + originalValue));
else if (ed->getValue() != "") else if (ed->getValue() != "")
launchCommands.push_back(std::make_pair("", "<CLEAR ENTRY>")); launchCommands.push_back(std::make_pair(
"", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY"));
for (auto entry : launchCommands) { for (auto entry : launchCommands) {
std::string selectedLabel = ed->getValue(); std::string selectedLabel = ed->getValue();
@ -364,11 +373,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
} }
}; };
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { if (Settings::getInstance()->getBool("VirtualKeyboard")) {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
ed->getValue(), updateVal, multiLine, mWindow->pushGui(new GuiTextEditKeyboardPopup(
"APPLY", "APPLY CHANGES?")); mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine,
}); "apply", "APPLY CHANGES?", "", ""));
});
}
else {
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title,
ed->getValue(), updateVal, multiLine,
"APPLY", "APPLY CHANGES?"));
});
}
break; break;
} }
} }
@ -377,7 +395,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window,
mList->addRow(row); mList->addRow(row);
if (iter->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true) if (iter->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true)
ed->setValue(ViewController::EXCLAMATION_CHAR + " INVALID ENTRY "); ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue);
else else
ed->setValue(mMetaData->get(iter->key)); ed->setValue(mMetaData->get(iter->key));

View file

@ -15,9 +15,9 @@
GuiOfflineGenerator::GuiOfflineGenerator(Window* window, const std::queue<FileData*>& gameQueue) GuiOfflineGenerator::GuiOfflineGenerator(Window* window, const std::queue<FileData*>& gameQueue)
: GuiComponent(window) : GuiComponent(window)
, mGameQueue(gameQueue)
, mBackground(window, ":/graphics/frame.svg") , mBackground(window, ":/graphics/frame.svg")
, mGrid(window, glm::ivec2{6, 13}) , mGrid(window, glm::ivec2{6, 13})
, mGameQueue(gameQueue)
{ {
addChild(&mBackground); addChild(&mBackground);
addChild(&mGrid); addChild(&mGrid);

View file

@ -91,7 +91,8 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
} }
// The filter setting is only retained during the program session i.e. it's not saved // The filter setting is only retained during the program session i.e. it's not saved
// to es_settings.xml. // to es_settings.xml.
if (mFilters->getSelectedId() != Settings::getInstance()->getInt("ScraperFilter")) if (mFilters->getSelectedId() !=
static_cast<unsigned int>(Settings::getInstance()->getInt("ScraperFilter")))
Settings::getInstance()->setInt("ScraperFilter", mFilters->getSelectedId()); Settings::getInstance()->setInt("ScraperFilter", mFilters->getSelectedId());
}); });

View file

@ -79,13 +79,36 @@ GuiScraperMulti::GuiScraperMulti(Window* window,
if (mApproveResults) { if (mApproveResults) {
buttons.push_back( buttons.push_back(
std::make_shared<ButtonComponent>(mWindow, "REFINE SEARCH", "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 // Check whether we should allow a refine of the game name.
// semi-automatic mode and there are less than 2 search results. if (!mSearchComp->getAcceptedResult()) {
if (!mSearchComp->getAcceptedResult() && bool allowRefine = false;
!(mSearchComp->getSearchType() == GuiScraperSearch::ACCEPT_SINGLE_MATCHES &&
mSearchComp->getScraperResultsSize() < 2)) { // Previously refined.
mSearchComp->openInputScreen(mSearchQueue.front()); if (mSearchComp->getRefinedSearch())
mGrid.resetCursor(); allowRefine = true;
// Interactive mode and "Auto-accept single game matches" not enabled.
else if (mSearchComp->getSearchType() !=
GuiScraperSearch::ACCEPT_SINGLE_MATCHES)
allowRefine = true;
// Interactive mode with "Auto-accept single game matches" enabled and more
// than one result.
else if (mSearchComp->getSearchType() ==
GuiScraperSearch::ACCEPT_SINGLE_MATCHES &&
mSearchComp->getScraperResultsSize() > 1)
allowRefine = true;
// Dito but there were no games found, or the search has not been completed.
else if (mSearchComp->getSearchType() ==
GuiScraperSearch::ACCEPT_SINGLE_MATCHES &&
!mSearchComp->getFoundGame())
allowRefine = true;
if (allowRefine) {
// Copy any search refine that may have been previously entered by opening
// the input screen using the "Y" button shortcut.
mSearchQueue.front().nameOverride = mSearchComp->getNameOverride();
mSearchComp->openInputScreen(mSearchQueue.front());
mGrid.resetCursor();
}
} }
})); }));
@ -212,6 +235,7 @@ void GuiScraperMulti::skip()
mSearchQueue.pop(); mSearchQueue.pop();
mCurrentGame++; mCurrentGame++;
mTotalSkipped++; mTotalSkipped++;
mSearchComp->decreaseScrapeCount();
mSearchComp->unsetRefinedSearch(); mSearchComp->unsetRefinedSearch();
doNextSearch(); doNextSearch();
} }

View file

@ -29,6 +29,7 @@
#include "components/ScrollableContainer.h" #include "components/ScrollableContainer.h"
#include "components/TextComponent.h" #include "components/TextComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "resources/Font.h" #include "resources/Font.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
@ -39,12 +40,12 @@
GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount) GuiScraperSearch::GuiScraperSearch(Window* window, SearchType type, unsigned int scrapeCount)
: GuiComponent(window) : GuiComponent(window)
, mGrid(window, glm::ivec2{4, 3}) , mGrid(window, glm::ivec2{4, 3})
, mBusyAnim(window)
, mSearchType(type) , mSearchType(type)
, mScrapeCount(scrapeCount) , mScrapeCount(scrapeCount)
, mScrapeRatings(false)
, mRefinedSearch(false) , mRefinedSearch(false)
, mFoundGame(false) , mFoundGame(false)
, mScrapeRatings(false)
, mBusyAnim(window)
{ {
addChild(&mGrid); addChild(&mGrid);
@ -323,6 +324,7 @@ void GuiScraperSearch::search(const ScraperSearchParams& params)
mBlockAccept = true; mBlockAccept = true;
mAcceptedResult = false; mAcceptedResult = false;
mMiximageResult = false; mMiximageResult = false;
mFoundGame = false;
mScrapeResult = {}; mScrapeResult = {};
mResultList->clear(); mResultList->clear();
@ -470,7 +472,7 @@ void GuiScraperSearch::updateInfoPane()
if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size()) if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && mScraperResults.size())
i = 0; i = 0;
if (i != -1 && static_cast<int>(mScraperResults.size() > i)) { if (i != -1 && static_cast<int>(mScraperResults.size()) > i) {
ScraperSearchResult& res = mScraperResults.at(i); ScraperSearchResult& res = mScraperResults.at(i);
mResultName->setText(Utils::String::toUpper(res.mdl.get("name"))); mResultName->setText(Utils::String::toUpper(res.mdl.get("name")));
@ -540,21 +542,34 @@ void GuiScraperSearch::updateInfoPane()
bool GuiScraperSearch::input(InputConfig* config, Input input) bool GuiScraperSearch::input(InputConfig* config, Input input)
{ {
if (config->isMappedTo("a", input) && input.value != 0) { if (config->isMappedTo("a", input) && input.value != 0) {
if (mBlockAccept) if (mBlockAccept || mScraperResults.empty())
return true; return true;
} }
// Refine the search, unless the result has already been accepted or we're in semi-automatic // Check whether we should allow a refine of the game name.
// mode and there are less than 2 search results.
if (!mAcceptedResult && config->isMappedTo("y", input) && input.value != 0) { if (!mAcceptedResult && config->isMappedTo("y", input) && input.value != 0) {
if (mSearchType != ACCEPT_SINGLE_MATCHES || bool allowRefine = false;
(mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1)) {
// Previously refined.
if (mRefinedSearch)
allowRefine = true;
// Interactive mode and "Auto-accept single game matches" not enabled.
else if (mSearchType != ACCEPT_SINGLE_MATCHES)
allowRefine = true;
// Interactive mode with "Auto-accept single game matches" enabled and more than one result.
else if (mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1)
allowRefine = true;
// Dito but there were no games found, or the search has not been completed.
else if (mSearchType == ACCEPT_SINGLE_MATCHES && !mFoundGame)
allowRefine = true;
if (allowRefine)
openInputScreen(mLastSearch); openInputScreen(mLastSearch);
}
} }
// Skip game, unless the result has already been accepted. // If multi-scraping, skip game unless the result has already been accepted.
if (!mAcceptedResult && mScrapeCount > 1 && config->isMappedTo("x", input) && input.value != 0) if (mSkipCallback != nullptr && !mAcceptedResult && // Line break.
config->isMappedTo("x", input) && input.value)
mSkipCallback(); mSkipCallback();
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
@ -575,6 +590,7 @@ void GuiScraperSearch::render(const glm::mat4& parentTrans)
void GuiScraperSearch::returnResult(ScraperSearchResult result) void GuiScraperSearch::returnResult(ScraperSearchResult result)
{ {
mBlockAccept = true; mBlockAccept = true;
mAcceptedResult = true; mAcceptedResult = true;
@ -776,7 +792,16 @@ void GuiScraperSearch::updateThumbnail()
void GuiScraperSearch::openInputScreen(ScraperSearchParams& params) void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
{ {
auto searchForFunc = [&](const std::string& name) { auto searchForFunc = [&](std::string name) {
// Trim leading and trailing whitespaces.
name.erase(name.begin(), std::find_if(name.begin(), name.end(), [](char c) {
return !std::isspace(static_cast<unsigned char>(c));
}));
name.erase(std::find_if(name.rbegin(), name.rend(),
[](char c) { return !std::isspace(static_cast<unsigned char>(c)); })
.base(),
name.end());
stop(); stop();
mRefinedSearch = true; mRefinedSearch = true;
params.nameOverride = name; params.nameOverride = name;
@ -808,8 +833,16 @@ void GuiScraperSearch::openInputScreen(ScraperSearchParams& params)
searchString = params.nameOverride; searchString = params.nameOverride;
} }
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH", searchString, if (Settings::getInstance()->getBool("VirtualKeyboard")) {
searchForFunc, false, "SEARCH", "APPLY CHANGES?")); mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), "REFINE SEARCH",
searchString, searchForFunc, false, "SEARCH",
"SEARCH USING REFINED NAME?"));
}
else {
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), "REFINE SEARCH",
searchString, searchForFunc, false, "SEARCH",
"SEARCH USING REFINED NAME?"));
}
} }
bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result, bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result,
@ -900,8 +933,11 @@ std::vector<HelpPrompt> GuiScraperSearch::getHelpPrompts()
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("y", "refine search")); prompts.push_back(HelpPrompt("y", "refine search"));
if (mScrapeCount > 1)
// Only show the skip prompt during multi-scraping.
if (mSkipCallback != nullptr)
prompts.push_back(HelpPrompt("x", "skip")); prompts.push_back(HelpPrompt("x", "skip"));
if (mFoundGame && (mRefinedSearch || mSearchType != ACCEPT_SINGLE_MATCHES || if (mFoundGame && (mRefinedSearch || mSearchType != ACCEPT_SINGLE_MATCHES ||
(mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1))) (mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() > 1)))
prompts.push_back(HelpPrompt("a", "accept result")); prompts.push_back(HelpPrompt("a", "accept result"));

View file

@ -69,7 +69,6 @@ public:
} }
void setCancelCallback(const std::function<void()>& cancelCallback) void setCancelCallback(const std::function<void()>& cancelCallback)
{ {
mScrapeCount -= 1;
mCancelCallback = cancelCallback; mCancelCallback = cancelCallback;
} }
@ -80,7 +79,16 @@ public:
HelpStyle getHelpStyle() override; HelpStyle getHelpStyle() override;
void onSizeChanged() override; void onSizeChanged() override;
void decreaseScrapeCount()
{
if (mScrapeCount > 0)
mScrapeCount--;
}
void unsetRefinedSearch() { mRefinedSearch = false; } void unsetRefinedSearch() { mRefinedSearch = false; }
bool getRefinedSearch() { return mRefinedSearch; }
bool getFoundGame() { return mFoundGame; }
const std::string& getNameOverride() { return mLastSearch.nameOverride; }
void onFocusGained() override { mGrid.onFocusGained(); } void onFocusGained() override { mGrid.onFocusGained(); }
void onFocusLost() override { mGrid.onFocusLost(); } void onFocusLost() override { mGrid.onFocusLost(); }

View file

@ -16,6 +16,7 @@
#include "SystemData.h" #include "SystemData.h"
#include "Window.h" #include "Window.h"
#include "components/HelpComponent.h" #include "components/HelpComponent.h"
#include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "views/ViewController.h" #include "views/ViewController.h"
#include "views/gamelist/IGameListView.h" #include "views/gamelist/IGameListView.h"
@ -23,6 +24,7 @@
GuiSettings::GuiSettings(Window* window, std::string title) GuiSettings::GuiSettings(Window* window, std::string title)
: GuiComponent(window) : GuiComponent(window)
, mMenu(window, title) , mMenu(window, title)
, mGoToSystem(nullptr)
, mNeedsSaving(false) , mNeedsSaving(false)
, mNeedsReloadHelpPrompts(false) , mNeedsReloadHelpPrompts(false)
, mNeedsCollectionsUpdate(false) , mNeedsCollectionsUpdate(false)
@ -34,7 +36,6 @@ GuiSettings::GuiSettings(Window* window, std::string title)
, mNeedsGoToSystem(false) , mNeedsGoToSystem(false)
, mNeedsGoToGroupedCollections(false) , mNeedsGoToGroupedCollections(false)
, mInvalidateCachedBackground(false) , mInvalidateCachedBackground(false)
, mGoToSystem(nullptr)
{ {
addChild(&mMenu); addChild(&mMenu);
mMenu.addButton("BACK", "back", [this] { delete this; }); mMenu.addButton("BACK", "back", [this] { delete this; });
@ -96,7 +97,7 @@ void GuiSettings::save()
ViewController::get()->reloadAll(); ViewController::get()->reloadAll();
if (mNeedsGoToStart) if (mNeedsGoToStart)
ViewController::get()->goToStart(); ViewController::get()->goToStart(true);
if (mNeedsGoToSystem) if (mNeedsGoToSystem)
ViewController::get()->goToSystem(mGoToSystem, false); ViewController::get()->goToSystem(mGoToSystem, false);
@ -123,7 +124,7 @@ void GuiSettings::save()
// the safe side. // the safe side.
if (state.getSystem()->isCollection() && if (state.getSystem()->isCollection() &&
state.getSystem()->getThemeFolder() != "custom-collections") { state.getSystem()->getThemeFolder() != "custom-collections") {
ViewController::get()->goToStart(); ViewController::get()->goToStart(false);
ViewController::get()->goToSystem(SystemData::sSystemVector.front(), false); ViewController::get()->goToSystem(SystemData::sSystemVector.front(), false);
// We don't want to invalidate the cached background when there has been a collection // We don't want to invalidate the cached background when there has been a collection
// systen change as that may show a black screen in some circumstances. // systen change as that may show a black screen in some circumstances.
@ -133,7 +134,7 @@ void GuiSettings::save()
// system view). // system view).
if (std::find(SystemData::sSystemVector.begin(), SystemData::sSystemVector.end(), if (std::find(SystemData::sSystemVector.begin(), SystemData::sSystemVector.end(),
state.getSystem()) == SystemData::sSystemVector.end()) { state.getSystem()) == SystemData::sSystemVector.end()) {
ViewController::get()->goToStart(); ViewController::get()->goToStart(false);
return; return;
} }
} }
@ -193,15 +194,30 @@ void GuiSettings::addEditableTextComponent(const std::string label,
} }
}; };
row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { if (Settings::getInstance()->getBool("VirtualKeyboard")) {
// Never display the value if it's a password, instead set it to blank. row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] {
if (isPassword) // Never display the value if it's a password, instead set it to blank.
mWindow->pushGui( if (isPassword)
new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal, false)); mWindow->pushGui(new GuiTextEditKeyboardPopup(
else mWindow, getHelpStyle(), label, "", updateVal, false, "SAVE", "SAVE CHANGES?"));
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, ed->getValue(), else
updateVal, false)); mWindow->pushGui(new GuiTextEditKeyboardPopup(mWindow, getHelpStyle(), label,
}); ed->getValue(), updateVal, false,
"SAVE", "SAVE CHANGES?"));
});
}
else {
row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] {
if (isPassword)
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, "", updateVal,
false, "SAVE", "SAVE CHANGES?"));
else
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label,
ed->getValue(), updateVal, false, "SAVE",
"SAVE CHANGES?"));
});
}
assert(ed); assert(ed);
addRow(row); addRow(row);

View file

@ -29,7 +29,6 @@
#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/GuiDetectDevice.h"
#include "guis/GuiLaunchScreen.h" #include "guis/GuiLaunchScreen.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
@ -133,7 +132,7 @@ bool parseArgs(int argc, char* argv[])
#if defined(_WIN64) #if defined(_WIN64)
// Print any command line output to the console. // Print any command line output to the console.
if (argc > 1) if (argc > 1)
win64ConsoleType consoleType = outputToConsole(false); outputToConsole(false);
#endif #endif
std::string portableFilePath = Utils::FileSystem::getExePath() + "/portable.txt"; std::string portableFilePath = Utils::FileSystem::getExePath() + "/portable.txt";
@ -637,10 +636,10 @@ int main(int argc, char* argv[])
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(true); }));
} }
else { else {
ViewController::get()->goToStart(); ViewController::get()->goToStart(true);
} }
} }

View file

@ -158,6 +158,18 @@ void thegamesdb_generate_json_scraper_requests(
cleanName = params.game->getCleanName(); cleanName = params.game->getCleanName();
} }
} }
// Trim leading and trailing whitespaces.
cleanName.erase(cleanName.begin(),
std::find_if(cleanName.begin(), cleanName.end(), [](char c) {
return !std::isspace(static_cast<unsigned char>(c));
}));
cleanName.erase(
std::find_if(cleanName.rbegin(), cleanName.rend(),
[](char c) { return !std::isspace(static_cast<unsigned char>(c)); })
.base(),
cleanName.end());
path += "/Games/ByGameName?" + apiKey + path += "/Games/ByGameName?" + apiKey +
"&fields=players,publishers,genres,overview,last_updated,rating," "&fields=players,publishers,genres,overview,last_updated,rating,"
"platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&name=" + "platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates&name=" +
@ -443,7 +455,7 @@ void TheGamesDBJSONRequest::process(const std::unique_ptr<HttpReq>& req,
// Find how many more requests we can make before the scraper // Find how many more requests we can make before the scraper
// request allowance counter is reset. // request allowance counter is reset.
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 (size_t i = 0; i < results.size(); i++) {
results[i].scraperRequestAllowance = results[i].scraperRequestAllowance =
doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt(); doc["remaining_monthly_allowance"].GetInt() + doc["extra_allowance"].GetInt();
} }

View file

@ -327,7 +327,7 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
mFuncs.push_back(ResolvePair(downloadMediaAsync(it->fileURL, filePath, mFuncs.push_back(ResolvePair(downloadMediaAsync(it->fileURL, filePath,
it->existingMediaFile, it->subDirectory, it->existingMediaFile, it->subDirectory,
it->resizeFile, mResult.savedNewMedia), it->resizeFile, mResult.savedNewMedia),
[this, filePath] {})); [filePath] {}));
} }
} }
} }
@ -373,11 +373,11 @@ MediaDownloadHandle::MediaDownloadHandle(const std::string& url,
const std::string& mediaType, const std::string& mediaType,
const bool resizeFile, const bool resizeFile,
bool& savedNewMedia) bool& savedNewMedia)
: mSavePath(path) : mReq(new HttpReq(url))
, mSavePath(path)
, mExistingMediaFile(existingMediaPath) , mExistingMediaFile(existingMediaPath)
, mMediaType(mediaType) , mMediaType(mediaType)
, mResizeFile(resizeFile) , mResizeFile(resizeFile)
, mReq(new HttpReq(url))
{ {
mSavedNewMediaPtr = &savedNewMedia; mSavedNewMediaPtr = &savedNewMedia;
} }

View file

@ -28,10 +28,10 @@ const int logoBuffersRight[] = {1, 2, 5};
SystemView::SystemView(Window* window) SystemView::SystemView(Window* window)
: IList<SystemViewData, SystemData*>(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP) : IList<SystemViewData, SystemData*>(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP)
, mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER)
, mPreviousScrollVelocity(0) , mPreviousScrollVelocity(0)
, mUpdatedGameCount(false) , mUpdatedGameCount(false)
, mViewNeedsReload(true) , mViewNeedsReload(true)
, mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER)
{ {
mCamOffset = 0; mCamOffset = 0;
mExtrasCamOffset = 0; mExtrasCamOffset = 0;
@ -181,9 +181,6 @@ void SystemView::goToSystem(SystemData* system, bool animate)
bool SystemView::input(InputConfig* config, Input input) bool SystemView::input(InputConfig* config, Input input)
{ {
auto it = SystemData::sSystemVector.cbegin();
const std::shared_ptr<ThemeData>& theme = (*it)->getTheme();
if (input.value != 0) { if (input.value != 0) {
if (config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && if (config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r &&
SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) {

View file

@ -26,6 +26,8 @@
#include "animations/MoveCameraAnimation.h" #include "animations/MoveCameraAnimation.h"
#include "guis/GuiInfoPopup.h" #include "guis/GuiInfoPopup.h"
#include "guis/GuiMenu.h" #include "guis/GuiMenu.h"
#include "guis/GuiTextEditKeyboardPopup.h"
#include "guis/GuiTextEditPopup.h"
#include "views/SystemView.h" #include "views/SystemView.h"
#include "views/UIModeController.h" #include "views/UIModeController.h"
#include "views/gamelist/DetailedGameListView.h" #include "views/gamelist/DetailedGameListView.h"
@ -37,19 +39,23 @@ ViewController* ViewController::sInstance = nullptr;
#if defined(_MSC_VER) // MSVC compiler. #if defined(_MSC_VER) // MSVC compiler.
const std::string ViewController::CONTROLLER_CHAR = Utils::String::wideStringToString(L"\uf11b"); const std::string ViewController::CONTROLLER_CHAR = Utils::String::wideStringToString(L"\uf11b");
const std::string ViewController::CROSSEDCIRCLE_CHAR = Utils::String::wideStringToString(L"\uf05e");
const std::string ViewController::EXCLAMATION_CHAR = Utils::String::wideStringToString(L"\uf06a"); const std::string ViewController::EXCLAMATION_CHAR = Utils::String::wideStringToString(L"\uf06a");
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::FILTER_CHAR = Utils::String::wideStringToString(L"\uf0b0"); const std::string ViewController::FILTER_CHAR = Utils::String::wideStringToString(L"\uf0b0");
const std::string ViewController::FOLDER_CHAR = Utils::String::wideStringToString(L"\uf07C"); const std::string ViewController::FOLDER_CHAR = Utils::String::wideStringToString(L"\uf07C");
const std::string ViewController::GEAR_CHAR = Utils::String::wideStringToString(L"\uf013"); const std::string ViewController::GEAR_CHAR = Utils::String::wideStringToString(L"\uf013");
const std::string ViewController::KEYBOARD_CHAR = Utils::String::wideStringToString(L"\uf11c");
const std::string ViewController::TICKMARK_CHAR = Utils::String::wideStringToString(L"\uf14A"); const std::string ViewController::TICKMARK_CHAR = Utils::String::wideStringToString(L"\uf14A");
#else #else
const std::string ViewController::CONTROLLER_CHAR = "\uf11b"; const std::string ViewController::CONTROLLER_CHAR = "\uf11b";
const std::string ViewController::CROSSEDCIRCLE_CHAR = "\uf05e";
const std::string ViewController::EXCLAMATION_CHAR = "\uf06a"; const std::string ViewController::EXCLAMATION_CHAR = "\uf06a";
const std::string ViewController::FAVORITE_CHAR = "\uf005"; const std::string ViewController::FAVORITE_CHAR = "\uf005";
const std::string ViewController::FILTER_CHAR = "\uf0b0"; const std::string ViewController::FILTER_CHAR = "\uf0b0";
const std::string ViewController::FOLDER_CHAR = "\uf07C"; const std::string ViewController::FOLDER_CHAR = "\uf07C";
const std::string ViewController::GEAR_CHAR = "\uf013"; const std::string ViewController::GEAR_CHAR = "\uf013";
const std::string ViewController::KEYBOARD_CHAR = "\uf11c";
const std::string ViewController::TICKMARK_CHAR = "\uf14a"; const std::string ViewController::TICKMARK_CHAR = "\uf14a";
#endif #endif
@ -67,9 +73,11 @@ void ViewController::init(Window* window)
ViewController::ViewController(Window* window) ViewController::ViewController(Window* window)
: GuiComponent(window) : GuiComponent(window)
, mNoGamesMessageBox(nullptr)
, mCurrentView(nullptr) , mCurrentView(nullptr)
, mPreviousView(nullptr) , mPreviousView(nullptr)
, mSkipView(nullptr) , mSkipView(nullptr)
, mGameToLaunch(nullptr)
, mCamera(Renderer::getIdentity()) , mCamera(Renderer::getIdentity())
, mSystemViewTransition(false) , mSystemViewTransition(false)
, mWrappedViews(false) , mWrappedViews(false)
@ -77,8 +85,6 @@ ViewController::ViewController(Window* window)
, mCancelledTransition(false) , mCancelledTransition(false)
, mLockInput(false) , mLockInput(false)
, mNextSystem(false) , mNextSystem(false)
, mGameToLaunch(nullptr)
, mNoGamesMessageBox(nullptr)
{ {
mState.viewing = NOTHING; mState.viewing = NOTHING;
mState.viewstyle = AUTOMATIC; mState.viewstyle = AUTOMATIC;
@ -135,26 +141,52 @@ void ViewController::noGamesDialog()
#else #else
currentROMDirectory = FileData::getROMDirectory(); currentROMDirectory = FileData::getROMDirectory();
#endif #endif
if (Settings::getInstance()->getBool("VirtualKeyboard")) {
mWindow->pushGui(new GuiComplexTextEditPopup( mWindow->pushGui(new GuiTextEditKeyboardPopup(
mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH", mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH", currentROMDirectory,
"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();
#if defined(_WIN64) #if defined(_WIN64)
mRomDirectory = Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); mRomDirectory =
Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else #else
mRomDirectory = FileData::getROMDirectory(); mRomDirectory = FileData::getROMDirectory();
#endif #endif
mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory); mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory);
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(), mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"ROM DIRECTORY SETTING SAVED, RESTART\n" "ROM DIRECTORY SETTING SAVED, RESTART\n"
"THE APPLICATION TO RESCAN THE SYSTEMS", "THE APPLICATION TO RESCAN THE SYSTEMS",
"OK", nullptr, "", nullptr, "", nullptr, true)); "OK", nullptr, "", nullptr, "", nullptr,
}, true));
false, "SAVE", "SAVE CHANGES?", "LOAD CURRENT", "LOAD CURRENTLY CONFIGURED VALUE", },
"CLEAR", "CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)", false)); false, "SAVE", "SAVE CHANGES?", "Currently configured path:",
currentROMDirectory, "LOAD CURRENTLY CONFIGURED PATH",
"CLEAR (LEAVE BLANK TO RESET TO DEFAULT PATH)"));
}
else {
mWindow->pushGui(new GuiTextEditPopup(
mWindow, HelpStyle(), "ENTER ROM DIRECTORY PATH", currentROMDirectory,
[this](const std::string& newROMDirectory) {
Settings::getInstance()->setString("ROMDirectory", newROMDirectory);
Settings::getInstance()->saveFile();
#if defined(_WIN64)
mRomDirectory =
Utils::String::replace(FileData::getROMDirectory(), "/", "\\");
#else
mRomDirectory = FileData::getROMDirectory();
#endif
mNoGamesMessageBox->changeText(mNoGamesErrorMessage + mRomDirectory);
mWindow->pushGui(new GuiMsgBox(mWindow, HelpStyle(),
"ROM DIRECTORY SETTING SAVED, RESTART\n"
"THE APPLICATION TO RESCAN THE SYSTEMS",
"OK", nullptr, "", nullptr, "", nullptr,
true));
},
false, "SAVE", "SAVE CHANGES?", "Currently configured path:",
currentROMDirectory, "LOAD CURRENTLY CONFIGURED PATH",
"CLEAR (LEAVE BLANK TO RESET TO DEFAULT PATH)"));
}
}, },
"CREATE DIRECTORIES", "CREATE DIRECTORIES",
[this] { [this] {
@ -208,7 +240,7 @@ void ViewController::invalidAlternativeEmulatorDialog()
"INTERFACE IN THE 'OTHER SETTINGS' MENU")); "INTERFACE IN THE 'OTHER SETTINGS' MENU"));
} }
void ViewController::goToStart() void ViewController::goToStart(bool playTransition)
{ {
// If the system view does not exist, then create it. We do this here as it would // If the system view does not exist, then create it. We do this here as it would
// otherwise not be done if jumping directly into a specific game system on startup. // otherwise not be done if jumping directly into a specific game system on startup.
@ -222,6 +254,8 @@ void ViewController::goToStart()
it != SystemData::sSystemVector.cend(); it++) { it != SystemData::sSystemVector.cend(); it++) {
if ((*it)->getName() == requestedSystem) { if ((*it)->getName() == requestedSystem) {
goToGameList(*it); goToGameList(*it);
if (!playTransition)
cancelViewTransitions();
return; return;
} }
} }
@ -596,7 +630,7 @@ void ViewController::playViewTransition(bool instant)
fadeCallback, true); fadeCallback, true);
}); });
// Fast-forward animation if we're partway faded. // Fast-forward animation if we're partially faded.
if (target == static_cast<glm::vec3>(-mCamera[3])) { if (target == static_cast<glm::vec3>(-mCamera[3])) {
// Not changing screens, so cancel the first half entirely. // Not changing screens, so cancel the first half entirely.
advanceAnimation(0, FADE_DURATION); advanceAnimation(0, FADE_DURATION);

View file

@ -15,7 +15,6 @@
#include "FileData.h" #include "FileData.h"
#include "GuiComponent.h" #include "GuiComponent.h"
#include "guis/GuiComplexTextEditPopup.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "renderers/Renderer.h" #include "renderers/Renderer.h"
@ -61,7 +60,7 @@ public:
void goToGameList(SystemData* system); void goToGameList(SystemData* system);
void goToSystemView(SystemData* system, bool playTransition); void goToSystemView(SystemData* system, bool playTransition);
void goToSystem(SystemData* system, bool animate); void goToSystem(SystemData* system, bool animate);
void goToStart(); void goToStart(bool playTransition);
void ReloadAndGoToStart(); void ReloadAndGoToStart();
// Functions to make the GUI behave properly. // Functions to make the GUI behave properly.
@ -125,11 +124,13 @@ public:
// Font Awesome symbols. // Font Awesome symbols.
static const std::string CONTROLLER_CHAR; static const std::string CONTROLLER_CHAR;
static const std::string CROSSEDCIRCLE_CHAR;
static const std::string EXCLAMATION_CHAR; static const std::string EXCLAMATION_CHAR;
static const std::string FAVORITE_CHAR; static const std::string FAVORITE_CHAR;
static const std::string FILTER_CHAR; static const std::string FILTER_CHAR;
static const std::string FOLDER_CHAR; static const std::string FOLDER_CHAR;
static const std::string GEAR_CHAR; static const std::string GEAR_CHAR;
static const std::string KEYBOARD_CHAR;
static const std::string TICKMARK_CHAR; static const std::string TICKMARK_CHAR;
private: private:

View file

@ -18,9 +18,6 @@
DetailedGameListView::DetailedGameListView(Window* window, FileData* root) DetailedGameListView::DetailedGameListView(Window* window, FileData* root)
: BasicGameListView(window, root) : BasicGameListView(window, root)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mThumbnail(window) , mThumbnail(window)
, mMarquee(window) , mMarquee(window)
, mImage(window) , mImage(window)
@ -42,6 +39,9 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root)
, mLastPlayed(window) , mLastPlayed(window)
, mPlayCount(window) , mPlayCount(window)
, mName(window) , mName(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mLastUpdated(nullptr) , mLastUpdated(nullptr)
{ {
const float padding = 0.01f; const float padding = 0.01f;

View file

@ -24,9 +24,6 @@ GridGameListView::GridGameListView(Window* window, FileData* root)
, mGrid(window) , mGrid(window)
, mMarquee(window) , mMarquee(window)
, mImage(window) , mImage(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mLblRating(window) , mLblRating(window)
, mLblReleaseDate(window) , mLblReleaseDate(window)
, mLblDeveloper(window) , mLblDeveloper(window)
@ -45,6 +42,9 @@ GridGameListView::GridGameListView(Window* window, FileData* root)
, mLastPlayed(window) , mLastPlayed(window)
, mPlayCount(window) , mPlayCount(window)
, mName(window) , mName(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
{ {
const float padding = 0.01f; const float padding = 0.01f;
@ -492,9 +492,11 @@ void GridGameListView::updateInfoPanel()
// An animation is not playing, then animate if opacity != our target opacity. // An animation is not playing, then animate if opacity != our target opacity.
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) || if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) { (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
// TEMPORARY - This does not seem to work, needs to be reviewed later. // TEMPORARY - This does not seem to work, needs to be reviewed later.
// comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255)); // auto func = [comp](float t) {
auto func = [](float t) {
// comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
}; };
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut); comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
} }

View file

@ -68,15 +68,20 @@ protected:
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'.
FileData* firstGameEntry; FileData *firstGameEntry;
private: private:
void updateInfoPanel(); void updateInfoPanel();
const std::string getImagePath(FileData* file);
const std::string getImagePath(FileData *file);
void initMDLabels(); void initMDLabels();
void initMDValues(); void initMDValues();
ImageComponent mMarquee;
ImageComponent mImage;
TextComponent mLblRating; TextComponent mLblRating;
TextComponent mLblReleaseDate; TextComponent mLblReleaseDate;
TextComponent mLblDeveloper; TextComponent mLblDeveloper;
@ -87,8 +92,6 @@ private:
TextComponent mLblPlayCount; TextComponent mLblPlayCount;
BadgesComponent mBadges; BadgesComponent mBadges;
ImageComponent mMarquee;
ImageComponent mImage;
RatingComponent mRating; RatingComponent mRating;
DateTimeComponent mReleaseDate; DateTimeComponent mReleaseDate;
TextComponent mDeveloper; TextComponent mDeveloper;

View file

@ -24,14 +24,10 @@
VideoGameListView::VideoGameListView(Window* window, FileData* root) VideoGameListView::VideoGameListView(Window* window, FileData* root)
: BasicGameListView(window, root) : BasicGameListView(window, root)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mThumbnail(window) , mThumbnail(window)
, mMarquee(window) , mMarquee(window)
, mImage(window) , mImage(window)
, mVideo(nullptr) , mVideo(nullptr)
, mVideoPlaying(false)
, mLblRating(window) , mLblRating(window)
, mLblReleaseDate(window) , mLblReleaseDate(window)
, mLblDeveloper(window) , mLblDeveloper(window)
@ -50,6 +46,10 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root)
, mLastPlayed(window) , mLastPlayed(window)
, mPlayCount(window) , mPlayCount(window)
, mName(window) , mName(window)
, mDescContainer(window)
, mDescription(window)
, mGamelistInfo(window)
, mVideoPlaying(false)
, mLastUpdated(nullptr) , mLastUpdated(nullptr)
{ {
const float padding = 0.01f; const float padding = 0.01f;

View file

@ -39,8 +39,8 @@ private:
ImageComponent mThumbnail; ImageComponent mThumbnail;
ImageComponent mMarquee; ImageComponent mMarquee;
VideoComponent* mVideo;
ImageComponent mImage; ImageComponent mImage;
VideoComponent* mVideo;
TextComponent mLblRating; TextComponent mLblRating;
TextComponent mLblReleaseDate; TextComponent mLblReleaseDate;

View file

@ -62,10 +62,10 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h
# GUIs # GUIs
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiComplexTextEditPopup.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.h
# Renderers # Renderers
@ -85,7 +85,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.h ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.h
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.h ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.h
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.h ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.h
) )
set(CORE_SOURCES set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioManager.cpp
@ -134,10 +134,10 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp
# GUIs # GUIs
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiComplexTextEditPopup.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInputConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiMsgBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditKeyboardPopup.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiTextEditPopup.cpp
# Renderer # Renderer
@ -159,7 +159,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MathUtil.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.cpp
) )
include_directories(${COMMON_INCLUDE_DIRS}) include_directories(${COMMON_INCLUDE_DIRS})
add_library(es-core STATIC ${CORE_SOURCES} ${CORE_HEADERS}) add_library(es-core STATIC ${CORE_SOURCES} ${CORE_HEADERS})

View file

@ -147,6 +147,9 @@ CECInput::CECInput()
mlibCEC = nullptr; mlibCEC = nullptr;
return; return;
} }
#else
// This is simply to get rid of a Clang -Wunused-private-field compiler warning.
mlibCEC = nullptr;
#endif // HAVE_LIBCEC #endif // HAVE_LIBCEC
} }

View file

@ -19,19 +19,19 @@
GuiComponent::GuiComponent(Window* window) GuiComponent::GuiComponent(Window* window)
: mWindow(window) : mWindow(window)
, mParent(nullptr) , mParent(nullptr)
, mOpacity(255)
, mColor(0) , mColor(0)
, mSaturation(1.0f)
, mColorShift(0) , mColorShift(0)
, mColorShiftEnd(0) , mColorShiftEnd(0)
, mOpacity(255)
, mSaturation(1.0f)
, mPosition({}) , mPosition({})
, mOrigin({}) , mOrigin({})
, mRotationOrigin(0.5f, 0.5f) , mRotationOrigin(0.5f, 0.5f)
, mSize({}) , mSize({})
, mTransform(Renderer::getIdentity())
, mIsProcessing(false) , mIsProcessing(false)
, mVisible(true) , mVisible(true)
, mEnabled(true) , mEnabled(true)
, mTransform(Renderer::getIdentity())
{ {
for (unsigned char i = 0; i < MAX_ANIMATIONS; i++) for (unsigned char i = 0; i < MAX_ANIMATIONS; i++)
mAnimationMap[i] = nullptr; mAnimationMap[i] = nullptr;

View file

@ -234,6 +234,11 @@ protected:
void updateSelf(int deltaTime); // Updates animations. void updateSelf(int deltaTime); // Updates animations.
void updateChildren(int deltaTime); // Updates animations. void updateChildren(int deltaTime); // Updates animations.
Window* mWindow;
GuiComponent* mParent;
std::vector<GuiComponent*> mChildren;
unsigned char mOpacity; unsigned char mOpacity;
unsigned int mColor; unsigned int mColor;
float mSaturation; float mSaturation;
@ -243,11 +248,6 @@ protected:
unsigned int mColorOriginalValue; unsigned int mColorOriginalValue;
unsigned int mColorChangedValue; unsigned int mColorChangedValue;
Window* mWindow;
GuiComponent* mParent;
std::vector<GuiComponent*> mChildren;
glm::vec3 mPosition; glm::vec3 mPosition;
glm::vec2 mOrigin; glm::vec2 mOrigin;
glm::vec2 mRotationOrigin; glm::vec2 mRotationOrigin;

View file

@ -49,12 +49,16 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
if (elem->has("textColorDimmed")) if (elem->has("textColorDimmed"))
textColorDimmed = elem->get<unsigned int>("textColorDimmed"); textColorDimmed = elem->get<unsigned int>("textColorDimmed");
else
textColorDimmed = textColor;
if (elem->has("iconColor")) if (elem->has("iconColor"))
iconColor = elem->get<unsigned int>("iconColor"); iconColor = elem->get<unsigned int>("iconColor");
if (elem->has("iconColorDimmed")) if (elem->has("iconColorDimmed"))
iconColorDimmed = elem->get<unsigned int>("iconColorDimmed"); iconColorDimmed = elem->get<unsigned int>("iconColorDimmed");
else
iconColorDimmed = iconColor;
if (elem->has("fontPath") || elem->has("fontSize")) if (elem->has("fontPath") || elem->has("fontSize"))
font = Font::getFromTheme(elem, ThemeFlags::ALL, font); font = Font::getFromTheme(elem, ThemeFlags::ALL, font);
@ -85,6 +89,10 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
mCustomButtons.button_r = elem->get<std::string>("button_r"); mCustomButtons.button_r = elem->get<std::string>("button_r");
if (elem->has("button_lr")) if (elem->has("button_lr"))
mCustomButtons.button_lr = elem->get<std::string>("button_lr"); mCustomButtons.button_lr = elem->get<std::string>("button_lr");
if (elem->has("button_lt"))
mCustomButtons.button_lt = elem->get<std::string>("button_lt");
if (elem->has("button_rt"))
mCustomButtons.button_rt = elem->get<std::string>("button_rt");
// SNES. // SNES.
if (elem->has("button_a_SNES")) if (elem->has("button_a_SNES"))

View file

@ -40,6 +40,8 @@ struct HelpStyle {
std::string button_l; std::string button_l;
std::string button_r; std::string button_r;
std::string button_lr; std::string button_lr;
std::string button_lt;
std::string button_rt;
// SNES. // SNES.
std::string button_a_SNES; std::string button_a_SNES;

View file

@ -79,8 +79,8 @@ private:
static CURLM* s_multi_handle; static CURLM* s_multi_handle;
CURL* mHandle;
Status mStatus; Status mStatus;
CURL* mHandle;
std::stringstream mContent; std::stringstream mContent;
std::string mErrorMsg; std::string mErrorMsg;

View file

@ -411,7 +411,7 @@ bool InputManager::parseEvent(const SDL_Event& event, Window* window)
return false; return false;
// The event filtering below is required as some controllers send button presses // The event filtering below is required as some controllers send button presses
// starting with the state 0 when using the D-pad. I consider this invalid behaviour // starting with the state 0 when using the D-pad. I consider this invalid behavior
// and the more popular controllers such as those from Microsoft and Sony do not show // and the more popular controllers such as those from Microsoft and Sony do not show
// this strange behavior. // this strange behavior.
int buttonState = int buttonState =

View file

@ -180,6 +180,7 @@ void Settings::setDefaults()
mBoolMap["FavoritesStar"] = {true, true}; mBoolMap["FavoritesStar"] = {true, true};
mBoolMap["SpecialCharsASCII"] = {false, false}; mBoolMap["SpecialCharsASCII"] = {false, false};
mBoolMap["ListScrollOverlay"] = {false, false}; mBoolMap["ListScrollOverlay"] = {false, false};
mBoolMap["VirtualKeyboard"] = {true, true};
mBoolMap["FavoritesAddButton"] = {true, true}; mBoolMap["FavoritesAddButton"] = {true, true};
mBoolMap["RandomAddButton"] = {false, false}; mBoolMap["RandomAddButton"] = {false, false};
mBoolMap["GamelistFilters"] = {true, true}; mBoolMap["GamelistFilters"] = {true, true};
@ -413,7 +414,7 @@ void Settings::loadFile()
} }
// Parameters for the macro defined above. // Parameters for the macro defined above.
SETTINGS_GETSET(bool, mBoolMap, getBool, getDefaultBool, setBool); SETTINGS_GETSET(bool, mBoolMap, getBool, getDefaultBool, setBool)
SETTINGS_GETSET(int, mIntMap, getInt, getDefaultInt, setInt); SETTINGS_GETSET(int, mIntMap, getInt, getDefaultInt, setInt)
SETTINGS_GETSET(float, mFloatMap, getFloat, getDefaultFloat, setFloat); SETTINGS_GETSET(float, mFloatMap, getFloat, getDefaultFloat, setFloat)
SETTINGS_GETSET(const std::string&, mStringMap, getString, getDefaultString, setString); SETTINGS_GETSET(const std::string&, mStringMap, getString, getDefaultString, setString)

View file

@ -218,13 +218,13 @@ void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptr<ThemeData
"Theme set does not include navigation sound support, using fallback sounds"; "Theme set does not include navigation sound support, using fallback sounds";
} }
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "systembrowse"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowse"));
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "quicksysselect"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselect"));
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "select"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "select"));
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "back"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "back"));
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "scroll"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "scroll"));
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "favorite"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "favorite"));
navigationSounds.push_back(std::move(Sound::getFromTheme(theme, "all", "launch"))); navigationSounds.push_back(Sound::getFromTheme(theme, "all", "launch"));
} }
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID) void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)

View file

@ -14,6 +14,7 @@
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/MathUtil.h" #include "utils/MathUtil.h"
#include <any>
#include <deque> #include <deque>
#include <map> #include <map>
#include <memory> #include <memory>
@ -128,17 +129,17 @@ public:
template <typename T> const T get(const std::string& prop) const template <typename T> const T get(const std::string& prop) const
{ {
if (std::is_same<T, glm::vec2>::value) if (std::is_same<T, glm::vec2>::value)
return *(const T*)&properties.at(prop).v; return std::any_cast<const T>(properties.at(prop).v);
else if (std::is_same<T, std::string>::value) else if (std::is_same<T, std::string>::value)
return *(const T*)&properties.at(prop).s; return std::any_cast<const T>(properties.at(prop).s);
else if (std::is_same<T, unsigned int>::value) else if (std::is_same<T, unsigned int>::value)
return *(const T*)&properties.at(prop).i; return std::any_cast<const T>(properties.at(prop).i);
else if (std::is_same<T, float>::value) else if (std::is_same<T, float>::value)
return *(const T*)&properties.at(prop).f; return std::any_cast<const T>(properties.at(prop).f);
else if (std::is_same<T, bool>::value) else if (std::is_same<T, bool>::value)
return *(const T*)&properties.at(prop).b; return std::any_cast<const T>(properties.at(prop).b);
else if (std::is_same<T, glm::vec4>::value) else if (std::is_same<T, glm::vec4>::value)
return *(const T*)&properties.at(prop).r; return std::any_cast<const T>(properties.at(prop).r);
return T(); return T();
} }

View file

@ -30,13 +30,14 @@ Window::Window()
, mMediaViewer(nullptr) , mMediaViewer(nullptr)
, mLaunchScreen(nullptr) , mLaunchScreen(nullptr)
, mInfoPopup(nullptr) , mInfoPopup(nullptr)
, mNormalizeNextUpdate(false) , mListScrollOpacity(0)
, mFrameTimeElapsed(0) , mFrameTimeElapsed(0)
, mFrameCountElapsed(0) , mFrameCountElapsed(0)
, mAverageDeltaTime(10) , mAverageDeltaTime(10)
, mTimeSinceLastInput(0)
, mNormalizeNextUpdate(false)
, mAllowSleep(true) , mAllowSleep(true)
, mSleeping(false) , mSleeping(false)
, mTimeSinceLastInput(0)
, mRenderScreensaver(false) , mRenderScreensaver(false)
, mRenderMediaViewer(false) , mRenderMediaViewer(false)
, mRenderLaunchScreen(false) , mRenderLaunchScreen(false)
@ -46,11 +47,11 @@ Window::Window()
, mInvalidatedCachedBackground(false) , mInvalidatedCachedBackground(false)
, mVideoPlayerCount(0) , mVideoPlayerCount(0)
, mTopScale(0.5) , mTopScale(0.5)
, mListScrollOpacity(0)
, mChangedThemeSet(false) , mChangedThemeSet(false)
{ {
mHelp = new HelpComponent(this); mHelp = new HelpComponent(this);
mBackgroundOverlay = new ImageComponent(this); mBackgroundOverlay = new ImageComponent(this);
mBackgroundOverlayOpacity = 0;
} }
Window::~Window() Window::~Window()
@ -561,10 +562,10 @@ void Window::render()
} }
if (mRenderMediaViewer) if (mRenderMediaViewer)
mMediaViewer->render(); mMediaViewer->render(trans);
if (mRenderLaunchScreen) if (mRenderLaunchScreen)
mLaunchScreen->render(); mLaunchScreen->render(trans);
if (Settings::getInstance()->getBool("DisplayGPUStatistics") && mFrameDataText) { if (Settings::getInstance()->getBool("DisplayGPUStatistics") && mFrameDataText) {
Renderer::setMatrix(Renderer::getIdentity()); Renderer::setMatrix(Renderer::getIdentity());
@ -657,14 +658,16 @@ void Window::setHelpPrompts(const std::vector<HelpPrompt>& prompts, const HelpSt
"b", "b",
"x", "x",
"y", "y",
"l",
"r", "r",
"l",
"rt",
"lt",
"start", "start",
"back"}; "back"};
int i = 0; int i = 0;
int aVal = 0; int aVal = 0;
int bVal = 0; int bVal = 0;
while (i < map.size()) { while (i < static_cast<int>(map.size())) {
if (a.first == map[i]) if (a.first == map[i])
aVal = i; aVal = i;
if (b.first == map[i]) if (b.first == map[i])
@ -790,7 +793,7 @@ int Window::getVideoPlayerCount()
videoPlayerCount = mVideoPlayerCount; videoPlayerCount = mVideoPlayerCount;
mVideoCountMutex.unlock(); mVideoCountMutex.unlock();
return videoPlayerCount; return videoPlayerCount;
}; }
void Window::setLaunchedGame() void Window::setLaunchedGame()
{ {

View file

@ -61,7 +61,7 @@ public:
virtual void showPrevious() = 0; virtual void showPrevious() = 0;
virtual void update(int deltaTime) = 0; virtual void update(int deltaTime) = 0;
virtual void render() = 0; virtual void render(const glm::mat4& parentTrans) = 0;
}; };
class GuiLaunchScreen class GuiLaunchScreen
@ -70,7 +70,7 @@ public:
virtual void displayLaunchScreen(FileData* game) = 0; virtual void displayLaunchScreen(FileData* game) = 0;
virtual void closeLaunchScreen() = 0; virtual void closeLaunchScreen() = 0;
virtual void update(int deltaTime) = 0; virtual void update(int deltaTime) = 0;
virtual void render() = 0; virtual void render(const glm::mat4& parentTrans) = 0;
}; };
class InfoPopup class InfoPopup
@ -158,31 +158,31 @@ private:
HelpComponent* mHelp; HelpComponent* mHelp;
ImageComponent* mBackgroundOverlay; ImageComponent* mBackgroundOverlay;
unsigned char mBackgroundOverlayOpacity; unsigned char mBackgroundOverlayOpacity;
Screensaver* mScreensaver;
InfoPopup* mInfoPopup;
std::vector<GuiComponent*> mGuiStack; std::vector<GuiComponent*> mGuiStack;
std::vector<std::shared_ptr<Font>> mDefaultFonts; std::vector<std::shared_ptr<Font>> mDefaultFonts;
std::unique_ptr<TextCache> mFrameDataText; std::unique_ptr<TextCache> mFrameDataText;
Screensaver* mScreensaver;
MediaViewer* mMediaViewer; MediaViewer* mMediaViewer;
bool mRenderMediaViewer;
GuiLaunchScreen* mLaunchScreen; GuiLaunchScreen* mLaunchScreen;
bool mRenderLaunchScreen; InfoPopup* mInfoPopup;
std::string mListScrollText; std::string mListScrollText;
std::shared_ptr<Font> mListScrollFont; std::shared_ptr<Font> mListScrollFont;
unsigned char mListScrollOpacity; unsigned char mListScrollOpacity;
bool mNormalizeNextUpdate;
int mFrameTimeElapsed; int mFrameTimeElapsed;
int mFrameCountElapsed; int mFrameCountElapsed;
int mAverageDeltaTime; int mAverageDeltaTime;
bool mAllowSleep;
bool mSleeping;
unsigned int mTimeSinceLastInput; unsigned int mTimeSinceLastInput;
bool mNormalizeNextUpdate;
bool mAllowSleep;
bool mSleeping;
bool mRenderScreensaver; bool mRenderScreensaver;
bool mRenderMediaViewer;
bool mRenderLaunchScreen;
bool mGameLaunchedState; bool mGameLaunchedState;
bool mAllowTextScrolling; bool mAllowTextScrolling;
bool mCachedBackground; bool mCachedBackground;

View file

@ -17,8 +17,8 @@ class MoveCameraAnimation : public Animation
public: public:
MoveCameraAnimation(glm::mat4& camera, const glm::vec3& target) MoveCameraAnimation(glm::mat4& camera, const glm::vec3& target)
: mCameraStart(camera) : mCameraStart(camera)
, mTarget(target)
, cameraPosition(camera) , cameraPosition(camera)
, mTarget(target)
{ {
} }

View file

@ -15,24 +15,85 @@
ButtonComponent::ButtonComponent(Window* window, ButtonComponent::ButtonComponent(Window* window,
const std::string& text, const std::string& text,
const std::string& helpText, const std::string& helpText,
const std::function<void()>& func) const std::function<void()>& func,
: GuiComponent(window) bool upperCase,
, mBox(window, ":/graphics/button.svg") bool flatStyle)
, mFont(Font::get(FONT_SIZE_MEDIUM)) : GuiComponent{window}
, mFocused(false) , mBox{window, ":/graphics/button.svg"}
, mEnabled(true) , mFont{Font::get(FONT_SIZE_MEDIUM)}
, mTextColorFocused(0xFFFFFFFF) , mPadding{{}}
, mTextColorUnfocused(0x777777FF) , mFocused{false}
, mEnabled{true}
, mFlatStyle{flatStyle}
, mTextColorFocused{0xFFFFFFFF}
, mTextColorUnfocused{0x777777FF}
, mFlatColorFocused{0x878787FF}
, mFlatColorUnfocused{0x60606025}
{ {
setPressedFunc(func); setPressedFunc(func);
setText(text, helpText); setText(text, helpText, upperCase);
updateImage();
if (!mFlatStyle)
updateImage();
} }
void ButtonComponent::onSizeChanged() void ButtonComponent::onSizeChanged()
{ {
// Fit to mBox. if (mFlatStyle)
mBox.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); return;
auto cornerSize = mBox.getCornerSize();
mBox.fitTo(glm::vec2{mSize.x - mPadding.x - mPadding.z, mSize.y - mPadding.y - mPadding.w},
glm::vec3{mPadding.x, mPadding.y, 0.0f},
glm::vec2{-cornerSize.x * 2.0f, -cornerSize.y * 2.0f});
}
void ButtonComponent::onFocusGained()
{
mFocused = true;
if (!mFlatStyle)
updateImage();
}
void ButtonComponent::onFocusLost()
{
mFocused = false;
if (!mFlatStyle)
updateImage();
}
void ButtonComponent::setText(const std::string& text, const std::string& helpText, bool upperCase)
{
mText = upperCase ? Utils::String::toUpper(text) : text;
mHelpText = helpText;
mTextCache =
std::unique_ptr<TextCache>(mFont->buildTextCache(mText, 0.0f, 0.0f, getCurTextColor()));
float minWidth = mFont->sizeText("DELETE").x + (12.0f * Renderer::getScreenWidthModifier());
setSize(std::max(mTextCache->metrics.size.x + (12.0f * Renderer::getScreenWidthModifier()),
minWidth),
mTextCache->metrics.size.y);
updateHelpPrompts();
}
void ButtonComponent::setEnabled(bool state)
{
mEnabled = state;
if (!mFlatStyle)
updateImage();
}
void ButtonComponent::setPadding(const glm::vec4 padding)
{
if (mPadding == padding)
return;
mPadding = padding;
onSizeChanged();
} }
bool ButtonComponent::input(InputConfig* config, Input input) bool ButtonComponent::input(InputConfig* config, Input input)
@ -46,58 +107,27 @@ bool ButtonComponent::input(InputConfig* config, Input input)
return GuiComponent::input(config, input); return GuiComponent::input(config, input);
} }
void ButtonComponent::setText(const std::string& text, const std::string& helpText)
{
mText = Utils::String::toUpper(text);
mHelpText = helpText;
mTextCache = std::unique_ptr<TextCache>(mFont->buildTextCache(mText, 0, 0, getCurTextColor()));
float minWidth = mFont->sizeText("DELETE").x + (12.0f * Renderer::getScreenWidthModifier());
setSize(std::max(mTextCache->metrics.size.x + (12.0f * Renderer::getScreenWidthModifier()),
minWidth),
mTextCache->metrics.size.y);
updateHelpPrompts();
}
void ButtonComponent::onFocusGained()
{
mFocused = true;
updateImage();
}
void ButtonComponent::onFocusLost()
{
mFocused = false;
updateImage();
}
void ButtonComponent::setEnabled(bool state)
{
mEnabled = state;
updateImage();
}
void ButtonComponent::updateImage()
{
if (!mEnabled || !mPressedFunc) {
mBox.setImagePath(":/graphics/button_filled.svg");
mBox.setCenterColor(0x770000FF);
mBox.setEdgeColor(0x770000FF);
return;
}
mBox.setCenterColor(0xFFFFFFFF);
mBox.setEdgeColor(0xFFFFFFFF);
mBox.setImagePath(mFocused ? ":/graphics/button_filled.svg" : ":/graphics/button.svg");
}
void ButtonComponent::render(const glm::mat4& parentTrans) void ButtonComponent::render(const glm::mat4& parentTrans)
{ {
glm::mat4 trans{parentTrans * getTransform()}; glm::mat4 trans{parentTrans * getTransform()};
mBox.render(trans); if (mFlatStyle) {
if (mFocused) {
Renderer::setMatrix(trans);
Renderer::drawRect(mPadding.x, mPadding.y, mSize.x - mPadding.x - mPadding.z,
mSize.y - mPadding.y - mPadding.w, mFlatColorFocused,
mFlatColorFocused);
}
else {
Renderer::setMatrix(trans);
Renderer::drawRect(mPadding.x, mPadding.y, mSize.x - mPadding.x - mPadding.z,
mSize.y - mPadding.y - mPadding.w, mFlatColorUnfocused,
mFlatColorUnfocused);
}
}
else {
mBox.render(trans);
}
if (mTextCache) { if (mTextCache) {
glm::vec3 centerOffset{(mSize.x - mTextCache->metrics.size.x) / 2.0f, glm::vec3 centerOffset{(mSize.x - mTextCache->metrics.size.x) / 2.0f,
@ -121,6 +151,13 @@ void ButtonComponent::render(const glm::mat4& parentTrans)
renderChildren(trans); renderChildren(trans);
} }
std::vector<HelpPrompt> ButtonComponent::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("a", mHelpText.empty() ? mText.c_str() : mHelpText.c_str()));
return prompts;
}
unsigned int ButtonComponent::getCurTextColor() const unsigned int ButtonComponent::getCurTextColor() const
{ {
if (!mFocused) if (!mFocused)
@ -129,9 +166,16 @@ unsigned int ButtonComponent::getCurTextColor() const
return mTextColorFocused; return mTextColorFocused;
} }
std::vector<HelpPrompt> ButtonComponent::getHelpPrompts() void ButtonComponent::updateImage()
{ {
std::vector<HelpPrompt> prompts; if (!mEnabled || !mPressedFunc) {
prompts.push_back(HelpPrompt("a", mHelpText.empty() ? mText.c_str() : mHelpText.c_str())); mBox.setImagePath(":/graphics/button_filled.svg");
return prompts; mBox.setCenterColor(0x770000FF);
mBox.setEdgeColor(0x770000FF);
return;
}
mBox.setCenterColor(0xFFFFFFFF);
mBox.setEdgeColor(0xFFFFFFFF);
mBox.setImagePath(mFocused ? ":/graphics/button_filled.svg" : ":/graphics/button.svg");
} }

View file

@ -20,41 +20,56 @@ public:
ButtonComponent(Window* window, ButtonComponent(Window* window,
const std::string& text = "", const std::string& text = "",
const std::string& helpText = "", const std::string& helpText = "",
const std::function<void()>& func = nullptr); const std::function<void()>& func = nullptr,
bool upperCase = true,
void setPressedFunc(std::function<void()> f) { mPressedFunc = f; } bool flatStyle = false);
void setEnabled(bool state) override;
bool input(InputConfig* config, Input input) override;
void render(const glm::mat4& parentTrans) override;
void setText(const std::string& text, const std::string& helpText);
const std::string& getText() const { return mText; }
const std::function<void()>& getPressedFunc() const { return mPressedFunc; }
void onSizeChanged() override; void onSizeChanged() override;
void onFocusGained() override; void onFocusGained() override;
void onFocusLost() override; void onFocusLost() override;
void setText(const std::string& text, const std::string& helpText, bool upperCase = true);
const std::string& getText() const { return mText; }
void setPressedFunc(std::function<void()> f) { mPressedFunc = f; }
void setEnabled(bool state) override;
void setPadding(const glm::vec4 padding);
glm::vec4 getPadding() { return mPadding; }
void setFlatColorFocused(unsigned int color) { mFlatColorFocused = color; }
void setFlatColorUnfocused(unsigned int color) { mFlatColorUnfocused = color; }
const std::function<void()>& getPressedFunc() const { return mPressedFunc; }
bool input(InputConfig* config, Input input) override;
void render(const glm::mat4& parentTrans) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
private: private:
std::shared_ptr<Font> mFont;
std::function<void()> mPressedFunc;
bool mFocused;
bool mEnabled;
unsigned int mTextColorFocused;
unsigned int mTextColorUnfocused;
unsigned int getCurTextColor() const; unsigned int getCurTextColor() const;
void updateImage(); void updateImage();
NinePatchComponent mBox;
std::shared_ptr<Font> mFont;
std::unique_ptr<TextCache> mTextCache;
std::function<void()> mPressedFunc;
glm::vec4 mPadding;
std::string mText; std::string mText;
std::string mHelpText; std::string mHelpText;
std::unique_ptr<TextCache> mTextCache;
NinePatchComponent mBox; bool mFocused;
bool mEnabled;
bool mFlatStyle;
unsigned int mTextColorFocused;
unsigned int mTextColorUnfocused;
unsigned int mFlatColorFocused;
unsigned int mFlatColorUnfocused;
}; };
#endif // ES_CORE_COMPONENTS_BUTTON_COMPONENT_H #endif // ES_CORE_COMPONENTS_BUTTON_COMPONENT_H

View file

@ -252,19 +252,24 @@ bool ComponentGrid::input(InputConfig* config, Input input)
if (!input.value) if (!input.value)
return false; return false;
bool withinBoundary = false;
if (config->isMappedLike("down", input)) if (config->isMappedLike("down", input))
return moveCursor(glm::ivec2{0, 1}); withinBoundary = moveCursor(glm::ivec2{0, 1});
if (config->isMappedLike("up", input)) if (config->isMappedLike("up", input))
return moveCursor(glm::ivec2{0, -1}); withinBoundary = moveCursor(glm::ivec2{0, -1});
if (config->isMappedLike("left", input)) if (config->isMappedLike("left", input))
return moveCursor(glm::ivec2{-1, 0}); withinBoundary = moveCursor(glm::ivec2{-1, 0});
if (config->isMappedLike("right", input)) if (config->isMappedLike("right", input))
return moveCursor(glm::ivec2{1, 0}); withinBoundary = moveCursor(glm::ivec2{1, 0});
return false; if (!withinBoundary && mPastBoundaryCallback)
return mPastBoundaryCallback(config, input);
return withinBoundary;
} }
void ComponentGrid::resetCursor() void ComponentGrid::resetCursor()
@ -290,6 +295,34 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir)
const GridEntry* currentCursorEntry = getCellAt(mCursor); const GridEntry* currentCursorEntry = getCellAt(mCursor);
glm::ivec2 searchAxis(dir.x == 0, dir.y == 0); glm::ivec2 searchAxis(dir.x == 0, dir.y == 0);
// Logic to handle entries that span several cells.
if (currentCursorEntry->dim.x > 1) {
if (dir.x < 0 && currentCursorEntry->pos.x == 0 && mCursor.x > currentCursorEntry->pos.x) {
onCursorMoved(mCursor, glm::ivec2{0, mCursor.y});
mCursor.x = 0;
return false;
}
if (dir.x > 0 && currentCursorEntry->pos.x + currentCursorEntry->dim.x == mGridSize.x &&
mCursor.x < currentCursorEntry->pos.x + currentCursorEntry->dim.x - 1) {
onCursorMoved(mCursor, glm::ivec2{mGridSize.x - 1, mCursor.y});
mCursor.x = mGridSize.x - 1;
return false;
}
if (dir.x > 0 && mCursor.x != currentCursorEntry->pos.x + currentCursorEntry->dim.x - 1)
dir.x = currentCursorEntry->dim.x - (mCursor.x - currentCursorEntry->pos.x);
else if (dir.x < 0 && mCursor.x != currentCursorEntry->pos.x)
dir.x = -(mCursor.x - currentCursorEntry->pos.x + 1);
}
if (currentCursorEntry->dim.y > 1) {
if (dir.y > 0 && mCursor.y != currentCursorEntry->pos.y + currentCursorEntry->dim.y - 1)
dir.y = currentCursorEntry->dim.y - (mCursor.y - currentCursorEntry->pos.y);
else if (dir.y < 0 && mCursor.y != currentCursorEntry->pos.y)
dir.y = -(mCursor.y - currentCursorEntry->pos.y + 1);
}
while (mCursor.x >= 0 && mCursor.y >= 0 && mCursor.x < mGridSize.x && mCursor.y < mGridSize.y) { while (mCursor.x >= 0 && mCursor.y >= 0 && mCursor.x < mGridSize.x && mCursor.y < mGridSize.y) {
mCursor = mCursor + dir; mCursor = mCursor + dir;
glm::ivec2 curDirPos{mCursor}; glm::ivec2 curDirPos{mCursor};
@ -299,9 +332,18 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir)
while (mCursor.x < mGridSize.x && mCursor.y < mGridSize.y && mCursor.x >= 0 && while (mCursor.x < mGridSize.x && mCursor.y < mGridSize.y && mCursor.x >= 0 &&
mCursor.y >= 0) { mCursor.y >= 0) {
cursorEntry = getCellAt(mCursor); cursorEntry = getCellAt(mCursor);
if (cursorEntry && cursorEntry->canFocus && cursorEntry != currentCursorEntry) {
onCursorMoved(origCursor, mCursor); // Multi-cell entries.
return true; if (cursorEntry != nullptr) {
if (dir.x < 0 && cursorEntry->dim.x > 1)
mCursor.x = getCellAt(origCursor)->pos.x - cursorEntry->dim.x;
if (dir.y < 0 && cursorEntry->dim.y > 1)
mCursor.y = getCellAt(origCursor)->pos.y - cursorEntry->dim.y;
if (cursorEntry->canFocus && cursorEntry != currentCursorEntry) {
onCursorMoved(origCursor, mCursor);
return true;
}
} }
mCursor += searchAxis; mCursor += searchAxis;
} }
@ -326,6 +368,24 @@ bool ComponentGrid::moveCursor(glm::ivec2 dir)
return false; return false;
} }
void ComponentGrid::moveCursorTo(int xPos, int yPos, bool selectLeftCell)
{
const glm::ivec2 origCursor{mCursor};
if (xPos != -1)
mCursor.x = xPos;
if (yPos != -1)
mCursor.y = yPos;
const GridEntry* currentCursorEntry = getCellAt(mCursor);
// If requested, select the leftmost cell of entries wider than 1 cell.
if (selectLeftCell && mCursor.x > currentCursorEntry->pos.x)
mCursor.x = currentCursorEntry->pos.x;
onCursorMoved(origCursor, mCursor);
}
void ComponentGrid::onFocusLost() void ComponentGrid::onFocusLost()
{ {
const GridEntry* cursorEntry = getCellAt(mCursor); const GridEntry* cursorEntry = getCellAt(mCursor);

View file

@ -27,7 +27,7 @@ namespace GridFlags
BORDER_LEFT = 4, BORDER_LEFT = 4,
BORDER_RIGHT = 8 BORDER_RIGHT = 8
}; };
}; // namespace GridFlags } // namespace GridFlags
// Provides basic layout of components in an X*Y grid. // Provides basic layout of components in an X*Y grid.
class ComponentGrid : public GuiComponent class ComponentGrid : public GuiComponent
@ -46,6 +46,11 @@ public:
unsigned int border = GridFlags::BORDER_NONE, unsigned int border = GridFlags::BORDER_NONE,
GridFlags::UpdateType updateType = GridFlags::UPDATE_ALWAYS); GridFlags::UpdateType updateType = GridFlags::UPDATE_ALWAYS);
void setPastBoundaryCallback(const std::function<bool(InputConfig* config, Input input)>& func)
{
mPastBoundaryCallback = func;
}
void textInput(const std::string& text) override; void textInput(const std::string& text) override;
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;
@ -65,6 +70,8 @@ public:
void setRowHeightPerc(int row, float height, bool update = true); void setRowHeightPerc(int row, float height, bool update = true);
bool moveCursor(glm::ivec2 dir); bool moveCursor(glm::ivec2 dir);
// Pass -1 for xPos or yPos to keep its axis cursor position.
void moveCursorTo(int xPos, int yPos, bool selectLeftCell = false);
void setCursorTo(const std::shared_ptr<GuiComponent>& comp); void setCursorTo(const std::shared_ptr<GuiComponent>& comp);
std::shared_ptr<GuiComponent> getSelectedComponent() std::shared_ptr<GuiComponent> getSelectedComponent()
@ -126,6 +133,8 @@ private:
std::vector<GridEntry> mCells; std::vector<GridEntry> mCells;
glm::ivec2 mCursor; glm::ivec2 mCursor;
std::function<bool(InputConfig* config, Input input)> mPastBoundaryCallback;
float* mRowHeights; float* mRowHeights;
float* mColWidths; float* mColWidths;
}; };

View file

@ -61,6 +61,12 @@ bool ComponentList::input(InputConfig* config, Input input)
if (size() == 0) if (size() == 0)
return false; return false;
if (input.value &&
(config->isMappedTo("a", input) || config->isMappedLike("lefttrigger", input) ||
config->isMappedLike("righttrigger", input))) {
stopScrolling();
}
// Give it to the current row's input handler. // Give it to the current row's input handler.
if (mEntries.at(mCursor).data.input_handler) { if (mEntries.at(mCursor).data.input_handler) {
if (mEntries.at(mCursor).data.input_handler(config, input)) if (mEntries.at(mCursor).data.input_handler(config, input))
@ -190,7 +196,7 @@ void ComponentList::render(const glm::mat4& parentTrans)
// Draw our entries. // Draw our entries.
std::vector<GuiComponent*> drawAfterCursor; std::vector<GuiComponent*> drawAfterCursor;
bool drawAll; bool drawAll;
for (unsigned int i = 0; i < mEntries.size(); i++) { for (size_t i = 0; i < mEntries.size(); i++) {
auto& entry = mEntries.at(i); auto& entry = mEntries.at(i);
drawAll = !mFocused || i != static_cast<unsigned int>(mCursor); drawAll = !mFocused || i != static_cast<unsigned int>(mCursor);
for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++) { for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++) {
@ -198,7 +204,8 @@ void ComponentList::render(const glm::mat4& parentTrans)
// For the row where the cursor is at, we want to remove any hue from the // For the row where the cursor is at, we want to remove any hue from the
// font or image before inverting, as it would otherwise lead to an ugly // font or image before inverting, as it would otherwise lead to an ugly
// inverted color (e.g. red inverting to a green hue). // inverted color (e.g. red inverting to a green hue).
if (mFocused && i == mCursor && it->component->getValue() != "") { if (mFocused && i == static_cast<size_t>(mCursor) &&
it->component->getValue() != "") {
// Check if we're dealing with text or an image component. // Check if we're dealing with text or an image component.
bool isTextComponent = true; bool isTextComponent = true;
unsigned int origColor = it->component->getColor(); unsigned int origColor = it->component->getColor();

View file

@ -20,9 +20,9 @@ DateTimeEditComponent::DateTimeEditComponent(Window* window, bool alignRight, Di
, mRelativeUpdateAccumulator(0) , mRelativeUpdateAccumulator(0)
, mColor(0x777777FF) , mColor(0x777777FF)
, mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)) , mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT))
, mAlignRight(alignRight)
, mUppercase(false) , mUppercase(false)
, mAutoSize(true) , mAutoSize(true)
, mAlignRight(alignRight)
{ {
updateTextCache(); updateTextCache();
} }

View file

@ -315,7 +315,7 @@ std::shared_ptr<TextureResource> GridTileComponent::getTexture()
return mImage->getTexture(); return mImage->getTexture();
return nullptr; return nullptr;
}; }
void GridTileComponent::forceSize(glm::vec2 size, float selectedZoom) void GridTileComponent::forceSize(glm::vec2 size, float selectedZoom)
{ {

View file

@ -51,6 +51,10 @@ void HelpComponent::assignIcons()
mStyle.mCustomButtons.button_r; mStyle.mCustomButtons.button_r;
sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/help/button_lr.svg" : sIconPathMap["lr"] = mStyle.mCustomButtons.button_lr.empty() ? ":/help/button_lr.svg" :
mStyle.mCustomButtons.button_lr; mStyle.mCustomButtons.button_lr;
sIconPathMap["lt"] = mStyle.mCustomButtons.button_lt.empty() ? ":/help/button_lt.svg" :
mStyle.mCustomButtons.button_lt;
sIconPathMap["rt"] = mStyle.mCustomButtons.button_rt.empty() ? ":/help/button_rt.svg" :
mStyle.mCustomButtons.button_rt;
// These graphics files are custom per controller type. // These graphics files are custom per controller type.
if (controllerType == "snes") { if (controllerType == "snes") {

View file

@ -137,7 +137,7 @@ public:
const UserData& getNext() const const UserData& getNext() const
{ {
// If there is a next entry, then return it, otherwise return the current entry. // If there is a next entry, then return it, otherwise return the current entry.
if (mCursor + 1 < mEntries.size()) if (mCursor + 1 < static_cast<int>(mEntries.size()))
return mEntries.at(mCursor + 1).object; return mEntries.at(mCursor + 1).object;
else else
return mEntries.at(mCursor).object; return mEntries.at(mCursor).object;

View file

@ -29,20 +29,20 @@ glm::vec2 ImageComponent::getSize() const
ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic)
: GuiComponent(window) : GuiComponent(window)
, mTargetIsMax(false) , mTargetSize({})
, mTargetIsMin(false)
, mFlipX(false) , mFlipX(false)
, mFlipY(false) , mFlipY(false)
, mTargetSize(0, 0) , mTargetIsMax(false)
, mTargetIsMin(false)
, mColorShift(0xFFFFFFFF) , mColorShift(0xFFFFFFFF)
, mColorShiftEnd(0xFFFFFFFF) , mColorShiftEnd(0xFFFFFFFF)
, mColorGradientHorizontal(true) , mColorGradientHorizontal(true)
, mForceLoad(forceLoad)
, mDynamic(dynamic)
, mFadeOpacity(0) , mFadeOpacity(0)
, mFading(false) , mFading(false)
, mForceLoad(forceLoad)
, mDynamic(dynamic)
, mRotateByTargetSize(false) , mRotateByTargetSize(false)
, mTopLeftCrop(0.0f, 0.0f) , mTopLeftCrop({})
, mBottomRightCrop(1.0f, 1.0f) , mBottomRightCrop(1.0f, 1.0f)
{ {
updateColors(); updateColors();
@ -77,7 +77,7 @@ void ImageComponent::resize()
// This will be mTargetSize.x. We can't exceed it, nor be lower than it. // This will be mTargetSize.x. We can't exceed it, nor be lower than it.
mSize.x *= resizeScale.x; mSize.x *= resizeScale.x;
// We need to make sure we're not creating an image larger than max size. // We need to make sure we're not creating an image larger than max size.
mSize.y = std::min(floorf(mSize.y *= resizeScale.x), mTargetSize.y); mSize.y = std::min(floorf(mSize.y * resizeScale.x), mTargetSize.y);
} }
else { else {
// This will be mTargetSize.y(). We can't exceed it. // This will be mTargetSize.y(). We can't exceed it.

View file

@ -101,7 +101,10 @@ public:
private: private:
glm::vec2 mTargetSize; glm::vec2 mTargetSize;
bool mFlipX, mFlipY, mTargetIsMax, mTargetIsMin; bool mFlipX;
bool mFlipY;
bool mTargetIsMax;
bool mTargetIsMin;
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change. // Used internally whenever the resizing parameters or texture change.

View file

@ -394,7 +394,7 @@ template <typename T> void ImageGridComponent<T>::onCursorChanged(const CursorSt
bool direction = mCursor >= mLastCursor; bool direction = mCursor >= mLastCursor;
int diff = direction ? mCursor - mLastCursor : mLastCursor - mCursor; int diff = direction ? mCursor - mLastCursor : mLastCursor - mCursor;
if (isScrollLoop() && diff == mEntries.size() - 1) if (isScrollLoop() && diff == static_cast<int>(mEntries.size()) - 1)
direction = !direction; direction = !direction;
int oldStart = mStartPosition; int oldStart = mStartPosition;
@ -426,18 +426,18 @@ template <typename T> void ImageGridComponent<T>::onCursorChanged(const CursorSt
std::shared_ptr<GridTileComponent> newTile = nullptr; std::shared_ptr<GridTileComponent> newTile = nullptr;
int oldIdx = mLastCursor - mStartPosition + (dimOpposite * EXTRAITEMS); int oldIdx = mLastCursor - mStartPosition + (dimOpposite * EXTRAITEMS);
if (oldIdx >= 0 && oldIdx < mTiles.size()) if (oldIdx >= 0 && oldIdx < static_cast<int>(mTiles.size()))
oldTile = mTiles[oldIdx]; oldTile = mTiles[oldIdx];
int newIdx = mCursor - mStartPosition + (dimOpposite * EXTRAITEMS); int newIdx = mCursor - mStartPosition + (dimOpposite * EXTRAITEMS);
if (isScrollLoop()) { if (isScrollLoop()) {
if (newIdx < 0) if (newIdx < 0)
newIdx += static_cast<int>(mEntries.size()); newIdx += static_cast<int>(mEntries.size());
else if (newIdx >= mTiles.size()) else if (newIdx >= static_cast<int>(mTiles.size()))
newIdx -= static_cast<int>(mEntries.size()); newIdx -= static_cast<int>(mEntries.size());
} }
if (newIdx >= 0 && newIdx < mTiles.size()) if (newIdx >= 0 && newIdx < static_cast<int>(mTiles.size()))
newTile = mTiles[newIdx]; newTile = mTiles[newIdx];
for (auto it = mTiles.begin(); it != mTiles.end(); it++) { for (auto it = mTiles.begin(); it != mTiles.end(); it++) {
@ -670,7 +670,7 @@ void ImageGridComponent<T>::updateTileAtPos(int tilePos,
int dif = mCursor - tilePos; int dif = mCursor - tilePos;
int idx = mLastCursor - dif; int idx = mLastCursor - dif;
if (idx < 0 || idx >= mTiles.size()) if (idx < 0 || idx >= static_cast<int>(mTiles.size()))
idx = 0; idx = 0;
glm::vec3 pos{mTiles.at(idx)->getBackgroundPosition()}; glm::vec3 pos{mTiles.at(idx)->getBackgroundPosition()};
@ -720,8 +720,10 @@ template <typename T> bool ImageGridComponent<T>::isScrollLoop()
if (!mScrollLoop) if (!mScrollLoop)
return false; return false;
if (isVertical()) if (isVertical())
return (mGridDimension.x * (mGridDimension.y - 2 * EXTRAITEMS)) <= mEntries.size(); return (mGridDimension.x * (mGridDimension.y - 2 * EXTRAITEMS)) <=
return (mGridDimension.y * (mGridDimension.x - 2 * EXTRAITEMS)) <= mEntries.size(); static_cast<int>(mEntries.size());
return (mGridDimension.y * (mGridDimension.x - 2 * EXTRAITEMS)) <=
static_cast<int>(mEntries.size());
} }
#endif // ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H #endif // ES_CORE_COMPONENTS_IMAGE_GRID_COMPONENT_H

View file

@ -17,11 +17,11 @@ NinePatchComponent::NinePatchComponent(Window* window,
unsigned int edgeColor, unsigned int edgeColor,
unsigned int centerColor) unsigned int centerColor)
: GuiComponent(window) : GuiComponent(window)
, mVertices(nullptr)
, mPath(path)
, mCornerSize(16.0f, 16.0f) , mCornerSize(16.0f, 16.0f)
, mEdgeColor(edgeColor) , mEdgeColor(edgeColor)
, mCenterColor(centerColor) , mCenterColor(centerColor)
, mPath(path)
, mVertices(nullptr)
{ {
if (!mPath.empty()) if (!mPath.empty())
buildVertices(); buildVertices();
@ -95,13 +95,13 @@ void NinePatchComponent::buildVertices()
const glm::vec2 imgPos{imgPosX[sliceX], imgPosY[sliceY]}; const glm::vec2 imgPos{imgPosX[sliceX], imgPosY[sliceY]};
const glm::vec2 imgSize{imgSizeX[sliceX], imgSizeY[sliceY]}; const glm::vec2 imgSize{imgSizeX[sliceX], imgSizeY[sliceY]};
const glm::vec2 texPos{texPosX[sliceX], texPosY[sliceY]}; const glm::vec2 texPos{texPosX[sliceX], texPosY[sliceY]};
const glm::vec2 texSize{texSizeX[sliceX], texSizeY[sliceY]}; const glm::vec2 texSizeSlice{texSizeX[sliceX], texSizeY[sliceY]};
// clang-format off // clang-format off
mVertices[v + 1] = {{imgPos.x , imgPos.y }, {texPos.x, texPos.y }, 0}; mVertices[v + 1] = {{imgPos.x , imgPos.y }, {texPos.x, texPos.y }, 0};
mVertices[v + 2] = {{imgPos.x , imgPos.y + imgSize.y}, {texPos.x, texPos.y + texSize.y}, 0}; mVertices[v + 2] = {{imgPos.x , imgPos.y + imgSize.y}, {texPos.x, texPos.y + texSizeSlice.y}, 0};
mVertices[v + 3] = {{imgPos.x + imgSize.x, imgPos.y }, {texPos.x + texSize.x, texPos.y }, 0}; mVertices[v + 3] = {{imgPos.x + imgSize.x, imgPos.y }, {texPos.x + texSizeSlice.x, texPos.y }, 0};
mVertices[v + 4] = {{imgPos.x + imgSize.x, imgPos.y + imgSize.y}, {texPos.x + texSize.x, texPos.y + texSize.y}, 0}; mVertices[v + 4] = {{imgPos.x + imgSize.x, imgPos.y + imgSize.y}, {texPos.x + texSizeSlice.x, texPos.y + texSizeSlice.y}, 0};
// clang-format on // clang-format on
// Round vertices. // Round vertices.

View file

@ -288,9 +288,9 @@ private:
OptionListComponent<T>* parent, OptionListComponent<T>* parent,
const std::string& title) const std::string& title)
: GuiComponent(window) : GuiComponent(window)
, mHelpStyle(helpstyle)
, mMenu(window, title.c_str()) , mMenu(window, title.c_str())
, mParent(parent) , mParent(parent)
, mHelpStyle(helpstyle)
{ {
auto font = Font::get(FONT_SIZE_MEDIUM); auto font = Font::get(FONT_SIZE_MEDIUM);
ComponentListRow row; ComponentListRow row;

View file

@ -15,12 +15,12 @@
RatingComponent::RatingComponent(Window* window, bool colorizeChanges) RatingComponent::RatingComponent(Window* window, bool colorizeChanges)
: GuiComponent(window) : GuiComponent(window)
, mColorOriginalValue(DEFAULT_COLORSHIFT)
, mColorChangedValue(DEFAULT_COLORSHIFT)
, mColorShift(DEFAULT_COLORSHIFT) , mColorShift(DEFAULT_COLORSHIFT)
, mColorShiftEnd(DEFAULT_COLORSHIFT) , mColorShiftEnd(DEFAULT_COLORSHIFT)
, mUnfilledColor(DEFAULT_COLORSHIFT) , mUnfilledColor(DEFAULT_COLORSHIFT)
, mColorizeChanges(colorizeChanges) , mColorizeChanges(colorizeChanges)
, mColorOriginalValue(DEFAULT_COLORSHIFT)
, mColorChangedValue(DEFAULT_COLORSHIFT)
{ {
mFilledTexture = TextureResource::get(":/graphics/star_filled.svg", true); mFilledTexture = TextureResource::get(":/graphics/star_filled.svg", true);
mUnfilledTexture = TextureResource::get(":/graphics/star_unfilled.svg", true); mUnfilledTexture = TextureResource::get(":/graphics/star_unfilled.svg", true);

View file

@ -16,13 +16,13 @@
ScrollableContainer::ScrollableContainer(Window* window) ScrollableContainer::ScrollableContainer(Window* window)
: GuiComponent(window) : GuiComponent(window)
, mScrollPos({})
, mScrollDir({})
, mFontSize(0.0f)
, mAutoScrollDelay(0) , mAutoScrollDelay(0)
, mAutoScrollSpeed(0) , mAutoScrollSpeed(0)
, mAutoScrollAccumulator(0) , mAutoScrollAccumulator(0)
, mScrollPos(0, 0)
, mScrollDir(0, 0)
, mAutoScrollResetAccumulator(0) , mAutoScrollResetAccumulator(0)
, mFontSize(0.0f)
{ {
// Set the modifier to get equivalent scrolling speed regardless of screen resolution. // Set the modifier to get equivalent scrolling speed regardless of screen resolution.
mResolutionModifier = Renderer::getScreenHeightModifier(); mResolutionModifier = Renderer::getScreenHeightModifier();

View file

@ -110,8 +110,6 @@ void SliderComponent::setValue(float value)
onValueChanged(); onValueChanged();
} }
float SliderComponent::getValue() { return mValue; }
void SliderComponent::onSizeChanged() void SliderComponent::onSizeChanged()
{ {
if (!mSuffix.empty()) if (!mSuffix.empty())

View file

@ -19,13 +19,16 @@ class TextCache;
class SliderComponent : public GuiComponent class SliderComponent : public GuiComponent
{ {
public: public:
using GuiComponent::getValue;
using GuiComponent::setValue;
// Minimum value (far left of the slider), maximum value (far right of the slider), // Minimum value (far left of the slider), maximum value (far right of the slider),
// increment size (how much pressing L/R moves by), unit to display (optional). // increment size (how much pressing L/R moves by), unit to display (optional).
SliderComponent( SliderComponent(
Window* window, float min, float max, float increment, const std::string& suffix = ""); Window* window, float min, float max, float increment, const std::string& suffix = "");
void setValue(float val); void setValue(float value);
float getValue(); float getValue() { return mValue; }
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;

View file

@ -15,16 +15,16 @@
TextComponent::TextComponent(Window* window) TextComponent::TextComponent(Window* window)
: GuiComponent(window) : GuiComponent(window)
, mFont(Font::get(FONT_SIZE_MEDIUM)) , mFont(Font::get(FONT_SIZE_MEDIUM))
, mUppercase(false)
, mColor(0x000000FF) , mColor(0x000000FF)
, mBgColor(0)
, mMargin(0.0f)
, mRenderBackground(false)
, mUppercase(false)
, mAutoCalcExtent(true, true) , mAutoCalcExtent(true, true)
, mHorizontalAlignment(ALIGN_LEFT) , mHorizontalAlignment(ALIGN_LEFT)
, mVerticalAlignment(ALIGN_CENTER) , mVerticalAlignment(ALIGN_CENTER)
, mLineSpacing(1.5f) , mLineSpacing(1.5f)
, mNoTopMargin(false) , mNoTopMargin(false)
, mBgColor(0)
, mMargin(0.0f)
, mRenderBackground(false)
{ {
} }
@ -39,16 +39,16 @@ TextComponent::TextComponent(Window* window,
float margin) float margin)
: GuiComponent(window) : GuiComponent(window)
, mFont(nullptr) , mFont(nullptr)
, mUppercase(false)
, mColor(0x000000FF) , mColor(0x000000FF)
, mBgColor(0)
, mMargin(margin)
, mRenderBackground(false)
, mUppercase(false)
, mAutoCalcExtent(true, true) , mAutoCalcExtent(true, true)
, mHorizontalAlignment(align) , mHorizontalAlignment(align)
, mVerticalAlignment(ALIGN_CENTER) , mVerticalAlignment(ALIGN_CENTER)
, mLineSpacing(1.5f) , mLineSpacing(1.5f)
, mNoTopMargin(false) , mNoTopMargin(false)
, mBgColor(0)
, mMargin(margin)
, mRenderBackground(false)
{ {
setFont(font); setFont(font);
setColor(color); setColor(color);

View file

@ -10,25 +10,27 @@
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#define TEXT_PADDING_HORIZ 10.0f #define TEXT_PADDING_HORIZ 12.0f
#define TEXT_PADDING_VERT 2.0f #define TEXT_PADDING_VERT 2.0f
#define CURSOR_REPEAT_START_DELAY 500 #define CURSOR_REPEAT_START_DELAY 500
#define CURSOR_REPEAT_SPEED 28 // Lower is faster. #define CURSOR_REPEAT_SPEED 28 // Lower is faster.
#define BLINKTIME 1000
TextEditComponent::TextEditComponent(Window* window) TextEditComponent::TextEditComponent(Window* window)
: GuiComponent(window) : GuiComponent{window}
, mBox(window, ":/graphics/textinput.svg") , mFocused{false}
, mFocused(false) , mEditing{false}
, mScrollOffset(0.0f, 0.0f) , mCursor{0}
, mCursor(0) , mBlinkTime{0}
, mEditing(false) , mCursorRepeatDir{0}
, mFont(Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT)) , mScrollOffset{0.0f, 0.0f}
, mCursorRepeatDir(0) , mBox{window, ":/graphics/textinput.svg"}
, mFont{Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT)}
{ {
addChild(&mBox); addChild(&mBox);
onFocusLost(); onFocusLost();
mResolutionAdjustment = -(34.0f * Renderer::getScreenWidthModifier() - 34.0f);
setSize(4096, mFont->getHeight() + (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())); setSize(4096, mFont->getHeight() + (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier()));
} }
@ -36,6 +38,7 @@ void TextEditComponent::onFocusGained()
{ {
mFocused = true; mFocused = true;
mBox.setImagePath(":/graphics/textinput_focused.svg"); mBox.setImagePath(":/graphics/textinput_focused.svg");
startEditing();
} }
void TextEditComponent::onFocusLost() void TextEditComponent::onFocusLost()
@ -46,9 +49,9 @@ void TextEditComponent::onFocusLost()
void TextEditComponent::onSizeChanged() void TextEditComponent::onSizeChanged()
{ {
mBox.fitTo(mSize, glm::vec3{}, mBox.fitTo(
glm::vec2{-34.0f + mResolutionAdjustment, mSize, glm::vec3{},
-32.0f - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())}); glm::vec2{-34.0f, -32.0f - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())});
onTextChanged(); // Wrap point probably changed. onTextChanged(); // Wrap point probably changed.
} }
@ -62,6 +65,7 @@ void TextEditComponent::setValue(const std::string& val)
void TextEditComponent::textInput(const std::string& text) void TextEditComponent::textInput(const std::string& text)
{ {
if (mEditing) { if (mEditing) {
mBlinkTime = 0;
mCursorRepeatDir = 0; mCursorRepeatDir = 0;
if (text[0] == '\b') { if (text[0] == '\b') {
if (mCursor > 0) { if (mCursor > 0) {
@ -80,19 +84,35 @@ void TextEditComponent::textInput(const std::string& text)
onCursorChanged(); onCursorChanged();
} }
std::string TextEditComponent::getValue() const
{
if (mText.empty())
return "";
// If mText only contains whitespace characters, then return an empty string.
if (std::find_if(mText.cbegin(), mText.cend(), [](char c) {
return !std::isspace(static_cast<unsigned char>(c));
}) == mText.cend()) {
return "";
}
else {
return mText;
}
}
void TextEditComponent::startEditing() void TextEditComponent::startEditing()
{ {
if (!isMultiline())
setCursor(mText.size());
SDL_StartTextInput(); SDL_StartTextInput();
mEditing = true; mEditing = true;
updateHelpPrompts(); updateHelpPrompts();
mBlinkTime = BLINKTIME / 6;
} }
void TextEditComponent::stopEditing() void TextEditComponent::stopEditing()
{ {
SDL_StopTextInput(); SDL_StopTextInput();
mEditing = false; mEditing = false;
mCursorRepeatDir = 0;
updateHelpPrompts(); updateHelpPrompts();
} }
@ -141,41 +161,33 @@ bool TextEditComponent::input(InputConfig* config, Input input)
return true; return true;
} }
// Done editing (accept changes). if (cursor_left || cursor_right) {
if ((config->getDeviceId() == DEVICE_KEYBOARD && input.id == SDLK_ESCAPE) || mBlinkTime = 0;
(config->getDeviceId() != DEVICE_KEYBOARD &&
(config->isMappedTo("a", input) || config->isMappedTo("b", input)))) {
mTextOrig = mText;
stopEditing();
return true;
}
else if (cursor_left || cursor_right) {
mCursorRepeatDir = cursor_left ? -1 : 1; mCursorRepeatDir = cursor_left ? -1 : 1;
mCursorRepeatTimer = -(CURSOR_REPEAT_START_DELAY - CURSOR_REPEAT_SPEED); mCursorRepeatTimer = -(CURSOR_REPEAT_START_DELAY - CURSOR_REPEAT_SPEED);
moveCursor(mCursorRepeatDir); moveCursor(mCursorRepeatDir);
} }
else if (cursor_up) { // Stop editing and let the button down event be captured by the parent component.
// TODO
}
else if (cursor_down) { else if (cursor_down) {
// TODO stopEditing();
return false;
} }
else if (shoulder_left || shoulder_right) { else if (shoulder_left || shoulder_right) {
mBlinkTime = 0;
mCursorRepeatDir = shoulder_left ? -10 : 10; mCursorRepeatDir = shoulder_left ? -10 : 10;
mCursorRepeatTimer = -(CURSOR_REPEAT_START_DELAY - CURSOR_REPEAT_SPEED); mCursorRepeatTimer = -(CURSOR_REPEAT_START_DELAY - CURSOR_REPEAT_SPEED);
moveCursor(mCursorRepeatDir); moveCursor(mCursorRepeatDir);
} }
// Jump to beginning of text. // Jump to beginning of text.
else if (trigger_left) { else if (trigger_left) {
mBlinkTime = 0;
setCursor(0); setCursor(0);
} }
// Jump to end of text. // Jump to end of text.
else if (trigger_right) { else if (trigger_right) {
mBlinkTime = 0;
setCursor(mText.length()); setCursor(mText.length());
} }
else if (config->getDeviceId() != DEVICE_KEYBOARD && config->isMappedTo("y", input)) {
textInput("\b");
}
else if (config->getDeviceId() == DEVICE_KEYBOARD) { else if (config->getDeviceId() == DEVICE_KEYBOARD) {
switch (input.id) { switch (input.id) {
case SDLK_HOME: { case SDLK_HOME: {
@ -187,7 +199,7 @@ bool TextEditComponent::input(InputConfig* config, Input input)
break; break;
} }
case SDLK_DELETE: { case SDLK_DELETE: {
if (mCursor < mText.length()) { if (mCursor < static_cast<int>(mText.length())) {
// Fake as Backspace one char to the right. // Fake as Backspace one char to the right.
moveCursor(1); moveCursor(1);
textInput("\b"); textInput("\b");
@ -207,6 +219,10 @@ void TextEditComponent::update(int deltaTime)
{ {
updateCursorRepeat(deltaTime); updateCursorRepeat(deltaTime);
GuiComponent::update(deltaTime); GuiComponent::update(deltaTime);
mBlinkTime += deltaTime;
if (mBlinkTime >= BLINKTIME)
mBlinkTime = 0;
} }
void TextEditComponent::updateCursorRepeat(int deltaTime) void TextEditComponent::updateCursorRepeat(int deltaTime)
@ -216,6 +232,7 @@ void TextEditComponent::updateCursorRepeat(int deltaTime)
mCursorRepeatTimer += deltaTime; mCursorRepeatTimer += deltaTime;
while (mCursorRepeatTimer >= CURSOR_REPEAT_SPEED) { while (mCursorRepeatTimer >= CURSOR_REPEAT_SPEED) {
mBlinkTime = 0;
moveCursor(mCursorRepeatDir); moveCursor(mCursorRepeatDir);
mCursorRepeatTimer -= CURSOR_REPEAT_SPEED; mCursorRepeatTimer -= CURSOR_REPEAT_SPEED;
} }
@ -244,7 +261,7 @@ void TextEditComponent::onTextChanged()
mFont->buildTextCache(wrappedText, 0.0f, 0.0f, 0x77777700 | getOpacity())); mFont->buildTextCache(wrappedText, 0.0f, 0.0f, 0x77777700 | getOpacity()));
if (mCursor > static_cast<int>(mText.length())) if (mCursor > static_cast<int>(mText.length()))
mCursor = static_cast<unsigned int>(mText.length()); mCursor = static_cast<int>(mText.length());
} }
void TextEditComponent::onCursorChanged() void TextEditComponent::onCursorChanged()
@ -298,34 +315,39 @@ void TextEditComponent::render(const glm::mat4& parentTrans)
Renderer::popClipRect(); Renderer::popClipRect();
// Draw cursor. // Draw cursor.
if (mEditing) { glm::vec2 cursorPos;
glm::vec2 cursorPos; if (isMultiline()) {
if (isMultiline()) { cursorPos = mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor);
cursorPos = mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor); }
} else {
else { cursorPos = mFont->sizeText(mText.substr(0, mCursor));
cursorPos = mFont->sizeText(mText.substr(0, mCursor)); cursorPos[1] = 0;
cursorPos[1] = 0; }
}
float cursorHeight = mFont->getHeight() * 0.8f; float cursorHeight = mFont->getHeight() * 0.8f;
if (!mEditing) {
Renderer::drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, Renderer::drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f,
2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0x000000FF, 2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0xC7C7C7FF,
0x000000FF); 0xC7C7C7FF);
}
if (mEditing && mBlinkTime < BLINKTIME / 2) {
Renderer::drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f,
2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0x777777FF,
0x777777FF);
} }
} }
glm::vec2 TextEditComponent::getTextAreaPos() const glm::vec2 TextEditComponent::getTextAreaPos() const
{ {
return glm::vec2{ return glm::vec2{(TEXT_PADDING_HORIZ * Renderer::getScreenWidthModifier()) / 2.0f,
(-mResolutionAdjustment + (TEXT_PADDING_HORIZ * Renderer::getScreenWidthModifier())) / 2.0f, (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier()) / 2.0f};
(TEXT_PADDING_VERT * Renderer::getScreenHeightModifier()) / 2.0f};
} }
glm::vec2 TextEditComponent::getTextAreaSize() const glm::vec2 TextEditComponent::getTextAreaSize() const
{ {
return glm::vec2{mSize.x + mResolutionAdjustment - return glm::vec2{mSize.x - (TEXT_PADDING_HORIZ * Renderer::getScreenWidthModifier()),
(TEXT_PADDING_HORIZ * Renderer::getScreenWidthModifier()),
mSize.y - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())}; mSize.y - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())};
} }
@ -333,10 +355,10 @@ std::vector<HelpPrompt> TextEditComponent::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts; std::vector<HelpPrompt> prompts;
if (mEditing) { if (mEditing) {
prompts.push_back(HelpPrompt("up/down/left/right", "move cursor")); prompts.push_back(HelpPrompt("lt", "first"));
prompts.push_back(HelpPrompt("y", "backspace")); prompts.push_back(HelpPrompt("rt", "last"));
prompts.push_back(HelpPrompt("a", "accept changes")); prompts.push_back(HelpPrompt("left/right", "move cursor"));
prompts.push_back(HelpPrompt("b", "accept changes")); prompts.push_back(HelpPrompt("b", "back"));
} }
else { else {
prompts.push_back(HelpPrompt("a", "edit")); prompts.push_back(HelpPrompt("a", "edit"));

View file

@ -33,7 +33,7 @@ public:
void onSizeChanged() override; void onSizeChanged() override;
void setValue(const std::string& val) override; void setValue(const std::string& val) override;
std::string getValue() const override { return mText; } std::string getValue() const override;
void startEditing(); void startEditing();
void stopEditing(); void stopEditing();
@ -60,7 +60,8 @@ private:
std::string mTextOrig; std::string mTextOrig;
bool mFocused; bool mFocused;
bool mEditing; bool mEditing;
unsigned int mCursor; // Cursor position in characters. int mCursor; // Cursor position in characters.
int mBlinkTime;
int mCursorRepeatTimer; int mCursorRepeatTimer;
int mCursorRepeatDir; int mCursorRepeatDir;
@ -68,7 +69,6 @@ private:
glm::vec2 mScrollOffset; glm::vec2 mScrollOffset;
NinePatchComponent mBox; NinePatchComponent mBox;
float mResolutionAdjustment;
std::shared_ptr<Font> mFont; std::shared_ptr<Font> mFont;
std::unique_ptr<TextCache> mTextCache; std::unique_ptr<TextCache> mTextCache;

View file

@ -42,6 +42,7 @@ public:
using IList<TextListData, T>::size; using IList<TextListData, T>::size;
using IList<TextListData, T>::isScrolling; using IList<TextListData, T>::isScrolling;
using IList<TextListData, T>::stopScrolling; using IList<TextListData, T>::stopScrolling;
using GuiComponent::setColor;
TextListComponent(Window* window); TextListComponent(Window* window);
@ -61,7 +62,11 @@ public:
ALIGN_RIGHT ALIGN_RIGHT
}; };
void setAlignment(Alignment align) { mAlignment = align; } void setAlignment(Alignment align)
{
// Set alignment.
mAlignment = align;
}
void setCursorChangedCallback(const std::function<void(CursorState state)>& func) void setCursorChangedCallback(const std::function<void(CursorState state)>& func)
{ {

View file

@ -22,8 +22,11 @@ VideoComponent::VideoComponent(Window* window)
: GuiComponent(window) : GuiComponent(window)
, mWindow(window) , mWindow(window)
, mStaticImage(window) , mStaticImage(window)
, mVideoHeight(0)
, mVideoWidth(0) , mVideoWidth(0)
, mVideoHeight(0)
, mTargetSize(0, 0)
, mVideoAreaPos(0, 0)
, mVideoAreaSize(0, 0)
, mStartDelayed(false) , mStartDelayed(false)
, mIsPlaying(false) , mIsPlaying(false)
, mIsActuallyPlaying(false) , mIsActuallyPlaying(false)
@ -37,9 +40,6 @@ VideoComponent::VideoComponent(Window* window)
, mBlockPlayer(false) , mBlockPlayer(false)
, mTargetIsMax(false) , mTargetIsMax(false)
, mFadeIn(1.0) , mFadeIn(1.0)
, mTargetSize(0, 0)
, mVideoAreaPos(0, 0)
, mVideoAreaSize(0, 0)
{ {
// Setup the default configuration. // Setup the default configuration.
mConfig.showSnapshotDelay = false; mConfig.showSnapshotDelay = false;

View file

@ -108,6 +108,8 @@ private:
protected: protected:
Window* mWindow; Window* mWindow;
ImageComponent mStaticImage;
unsigned mVideoWidth; unsigned mVideoWidth;
unsigned mVideoHeight; unsigned mVideoHeight;
glm::vec2 mTargetSize; glm::vec2 mTargetSize;
@ -115,7 +117,6 @@ protected:
glm::vec2 mVideoAreaSize; glm::vec2 mVideoAreaSize;
std::shared_ptr<TextureResource> mTexture; std::shared_ptr<TextureResource> mTexture;
std::string mStaticImagePath; std::string mStaticImagePath;
ImageComponent mStaticImage;
std::string mVideoPath; std::string mVideoPath;
std::string mPlayingVideoPath; std::string mPlayingVideoPath;

View file

@ -44,9 +44,9 @@ VideoFFmpegComponent::VideoFFmpegComponent(Window* window)
, mAFilterGraph(nullptr) , mAFilterGraph(nullptr)
, mAFilterInputs(nullptr) , mAFilterInputs(nullptr)
, mAFilterOutputs(nullptr) , mAFilterOutputs(nullptr)
, mVideoTimeBase(0.0l)
, mVideoTargetQueueSize(0) , mVideoTargetQueueSize(0)
, mAudioTargetQueueSize(0) , mAudioTargetQueueSize(0)
, mVideoTimeBase(0.0l)
, mAccumulatedTime(0) , mAccumulatedTime(0)
, mStartTimeAccumulation(false) , mStartTimeAccumulation(false)
, mDecodedFrame(false) , mDecodedFrame(false)
@ -529,8 +529,9 @@ void VideoFFmpegComponent::readFrames()
return; return;
if (mVideoCodecContext && mFormatContext) { if (mVideoCodecContext && mFormatContext) {
if (mVideoFrameQueue.size() < mVideoTargetQueueSize || if (static_cast<int>(mVideoFrameQueue.size()) < mVideoTargetQueueSize ||
(mAudioStreamIndex >= 0 && mAudioFrameQueue.size() < mAudioTargetQueueSize)) { (mAudioStreamIndex >= 0 &&
static_cast<int>(mAudioFrameQueue.size()) < mAudioTargetQueueSize)) {
while ((readFrameReturn = av_read_frame(mFormatContext, mPacket)) >= 0) { while ((readFrameReturn = av_read_frame(mFormatContext, mPacket)) >= 0) {
if (mPacket->stream_index == mVideoStreamIndex) { if (mPacket->stream_index == mVideoStreamIndex) {
if (!avcodec_send_packet(mVideoCodecContext, mPacket) && if (!avcodec_send_packet(mVideoCodecContext, mPacket) &&

View file

@ -34,8 +34,8 @@ libvlc_instance_t* VideoVlcComponent::mVLC = nullptr;
VideoVlcComponent::VideoVlcComponent(Window* window) VideoVlcComponent::VideoVlcComponent(Window* window)
: VideoComponent(window) : VideoComponent(window)
, mMediaPlayer(nullptr)
, mMedia(nullptr) , mMedia(nullptr)
, mMediaPlayer(nullptr)
, mContext({}) , mContext({})
, mHasSetAudioVolume(false) , mHasSetAudioVolume(false)
{ {

View file

@ -1,155 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GuiComplexTextEditPopup.cpp
//
// Text edit popup with a title, two text strings, a text input box and buttons
// to load the second text string and to clear the input field.
// Intended for updating settings for configuration files and similar.
//
#include "guis/GuiComplexTextEditPopup.h"
#include "Window.h"
#include "components/ButtonComponent.h"
#include "components/MenuComponent.h"
#include "components/TextEditComponent.h"
#include "guis/GuiMsgBox.h"
GuiComplexTextEditPopup::GuiComplexTextEditPopup(
Window* window,
const HelpStyle& helpstyle,
const std::string& title,
const std::string& infoString1,
const std::string& infoString2,
const std::string& initValue,
const std::function<void(const std::string&)>& okCallback,
bool multiLine,
const std::string& acceptBtnText,
const std::string& saveConfirmationText,
const std::string& loadBtnText,
const std::string& loadBtnHelpText,
const std::string& clearBtnText,
const std::string& clearBtnHelpText,
bool hideCancelButton)
: GuiComponent(window)
, mHelpStyle(helpstyle)
, mBackground(window, ":/graphics/frame.svg")
, mGrid(window, glm::ivec2{1, 5})
, mMultiLine(multiLine)
, mInitValue(initValue)
, mOkCallback(okCallback)
, mSaveConfirmationText(saveConfirmationText)
, mHideCancelButton(hideCancelButton)
{
addChild(&mBackground);
addChild(&mGrid);
mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title),
Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER);
mInfoString1 = std::make_shared<TextComponent>(mWindow, infoString1, Font::get(FONT_SIZE_SMALL),
0x555555FF, ALIGN_CENTER);
mInfoString2 = std::make_shared<TextComponent>(mWindow, infoString2, Font::get(FONT_SIZE_SMALL),
0x555555FF, ALIGN_CENTER);
mText = std::make_shared<TextEditComponent>(mWindow);
mText->setValue(initValue);
std::vector<std::shared_ptr<ButtonComponent>> buttons;
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, acceptBtnText, acceptBtnText,
[this, okCallback] {
okCallback(mText->getValue());
delete this;
}));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, loadBtnText, loadBtnHelpText,
[this, infoString2] {
mText->setValue(infoString2);
mText->setCursor(0);
mText->setCursor(infoString2.size());
}));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, clearBtnText, clearBtnHelpText,
[this] { mText->setValue(""); }));
if (!mHideCancelButton)
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes",
[this] { delete this; }));
mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true);
mGrid.setEntry(mInfoString1, glm::ivec2{0, 1}, false, true);
mGrid.setEntry(mInfoString2, glm::ivec2{0, 2}, false, false);
mGrid.setEntry(mText, glm::ivec2{0, 3}, true, false, glm::ivec2{1, 1},
GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
mGrid.setEntry(mButtonGrid, glm::ivec2{0, 4}, true, false);
mGrid.setRowHeightPerc(1, 0.15f, true);
float textHeight = mText->getFont()->getHeight();
if (multiLine)
textHeight *= 6.0f;
// Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent
// regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float infoWidth = glm::clamp(0.70f * aspectValue, 0.60f, 0.85f) * Renderer::getScreenWidth();
float windowWidth = glm::clamp(0.75f * aspectValue, 0.65f, 0.90f) * Renderer::getScreenWidth();
mText->setSize(0, textHeight);
mInfoString2->setSize(infoWidth, mInfoString2->getFont()->getHeight());
setSize(windowWidth, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y +
mButtonGrid->getSize().y * 1.85f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
(Renderer::getScreenHeight() - mSize.y) / 2.0f);
mText->startEditing();
}
void GuiComplexTextEditPopup::onSizeChanged()
{
mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f});
mText->setSize(mSize.x - 40.0f, mText->getSize().y);
// Update grid.
mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y);
mGrid.setRowHeightPerc(2, mButtonGrid->getSize().y / mSize.y);
mGrid.setSize(mSize);
}
bool GuiComplexTextEditPopup::input(InputConfig* config, Input input)
{
if (GuiComponent::input(config, input))
return true;
if (!mHideCancelButton) {
// Pressing back when not text editing closes us.
if (config->isMappedTo("b", input) && input.value) {
if (mText->getValue() != mInitValue) {
// Changes were made, ask if the user wants to save them.
mWindow->pushGui(new GuiMsgBox(
mWindow, mHelpStyle, mSaveConfirmationText, "YES",
[this] {
this->mOkCallback(mText->getValue());
delete this;
return true;
},
"NO",
[this] {
delete this;
return false;
}));
}
else {
delete this;
}
}
}
return false;
}
std::vector<HelpPrompt> GuiComplexTextEditPopup::getHelpPrompts()
{
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
if (!mHideCancelButton)
prompts.push_back(HelpPrompt("b", "back"));
return prompts;
}

View file

@ -1,64 +0,0 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GuiComplexTextEditPopup.h
//
// Text edit popup with a title, two text strings, a text input box and buttons
// to load the second text string and to clear the input field.
// Intended for updating settings for configuration files and similar.
//
#ifndef ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H
#define ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H
#include "GuiComponent.h"
#include "components/ComponentGrid.h"
#include "components/NinePatchComponent.h"
class TextComponent;
class TextEditComponent;
class GuiComplexTextEditPopup : public GuiComponent
{
public:
GuiComplexTextEditPopup(Window* window,
const HelpStyle& helpstyle,
const std::string& title,
const std::string& infoString1,
const std::string& infoString2,
const std::string& initValue,
const std::function<void(const std::string&)>& okCallback,
bool multiLine,
const std::string& acceptBtnText = "OK",
const std::string& saveConfirmationText = "SAVE CHANGES?",
const std::string& loadBtnText = "LOAD",
const std::string& loadBtnHelpText = "load default",
const std::string& clearBtnText = "CLEAR",
const std::string& clearBtnHelpText = "clear",
bool hideCancelButton = false);
bool input(InputConfig* config, Input input) override;
void onSizeChanged() override;
std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override { return mHelpStyle; }
private:
NinePatchComponent mBackground;
ComponentGrid mGrid;
std::shared_ptr<TextComponent> mTitle;
std::shared_ptr<TextComponent> mInfoString1;
std::shared_ptr<TextComponent> mInfoString2;
std::shared_ptr<TextEditComponent> mText;
std::shared_ptr<ComponentGrid> mButtonGrid;
HelpStyle mHelpStyle;
bool mMultiLine;
bool mHideCancelButton;
std::string mInitValue;
std::function<void(const std::string&)> mOkCallback;
std::string mSaveConfirmationText;
};
#endif // ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H

View file

@ -180,8 +180,8 @@ GuiInputConfig::GuiInputConfig(Window* window,
delete this; delete this;
}; };
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "OK", "ok", buttons.push_back(
[this, okFunction] { okFunction(); })); std::make_shared<ButtonComponent>(mWindow, "OK", "ok", [okFunction] { okFunction(); }));
mButtonGrid = makeButtonGrid(mWindow, buttons); mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mButtonGrid, glm::ivec2{0, 6}, true, false); mGrid.setEntry(mButtonGrid, glm::ivec2{0, 6}, true, false);

View file

@ -68,7 +68,6 @@ private:
Input mHeldInput; Input mHeldInput;
int mHeldTime; int mHeldTime;
int mHeldInputId; int mHeldInputId;
bool mSkipAxis;
}; };
#endif // ES_CORE_GUIS_GUI_INPUT_CONFIG_H #endif // ES_CORE_GUIS_GUI_INPUT_CONFIG_H

View file

@ -26,9 +26,9 @@ GuiMsgBox::GuiMsgBox(Window* window,
bool disableBackButton, bool disableBackButton,
bool deleteOnButtonPress) bool deleteOnButtonPress)
: GuiComponent(window) : GuiComponent(window)
, mHelpStyle(helpstyle)
, mBackground(window, ":/graphics/frame.svg") , mBackground(window, ":/graphics/frame.svg")
, mGrid(window, glm::ivec2{1, 2}) , mGrid(window, glm::ivec2{1, 2})
, mHelpStyle(helpstyle)
, mDisableBackButton(disableBackButton) , mDisableBackButton(disableBackButton)
, mDeleteOnButtonPress(deleteOnButtonPress) , mDeleteOnButtonPress(deleteOnButtonPress)
{ {

View file

@ -0,0 +1,702 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GuiTextEditKeyboardPopup.cpp
//
// Text edit popup with a virtual keyboard.
// Has a default mode and a complex mode, both with various options passed as arguments.
//
#define KEYBOARD_HEIGHT Renderer::getScreenHeight() * 0.60f
#define KEYBOARD_PADDINGX (Renderer::getScreenWidth() * 0.02f)
#define KEYBOARD_PADDINGY (Renderer::getScreenWidth() * 0.01f)
#define BUTTON_GRID_HORIZ_PADDING (10.0f * Renderer::getScreenHeightModifier())
#define NAVIGATION_REPEAT_START_DELAY 400
#define NAVIGATION_REPEAT_SPEED 70 // Lower is faster.
#define DELETE_REPEAT_START_DELAY 600
#define DELETE_REPEAT_SPEED 90 // Lower is faster.
#if defined(_MSC_VER) // MSVC compiler.
#define DELETE_SYMBOL Utils::String::wideStringToString(L"\uf177")
#define OK_SYMBOL Utils::String::wideStringToString(L"\uf058")
#define SHIFT_SYMBOL Utils::String::wideStringToString(L"\uf176")
#define ALT_SYMBOL Utils::String::wideStringToString(L"\uf141")
#else
#define DELETE_SYMBOL "\uf177"
#define OK_SYMBOL "\uf058"
#define SHIFT_SYMBOL "\uf176"
#define ALT_SYMBOL "\uf141"
#endif
#include "guis/GuiTextEditKeyboardPopup.h"
#include "components/MenuComponent.h"
#include "guis/GuiMsgBox.h"
#include "utils/StringUtil.h"
// clang-format off
std::vector<std::vector<const char*>> kbBaseUS{
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "DEL"},
{"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "DEL"},
{"¡", "²", "³", "¤", "", "¼", "½", "¾", "", "", "¥", "×", "DEL"},
{"¹", "", "", "£", "", "", "", "", "", "", "", "÷", "DEL"},
{"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "OK"},
{"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "OK"},
{"ä", "å", "é", "®", "þ", "ü", "ú", "í", "ó", "ö", "«", "»", "OK"},
{"Ä", "Å", "É", "", "Þ", "Ü", "Ú", "Í", "Ó", "Ö", "", "", "OK"},
{"a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "-rowspan-"},
{"A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "|", "-rowspan-"},
{"á", "ß", "ð", "", "", "", "", "", "ø", "", "´", "¬", "-rowspan-"},
{"Á", "§", "Ð", "", "", "", "", "", "Ø", "°", "¨", "¦", "-rowspan-"},
{"`", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "ALT", "-colspan-"},
{"~", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "ALT", "-colspan-"},
{"", "æ", "", "©", "", "", "ñ", "µ", "ç", "", "¿", "ALT", "-colspan-"},
{"", "Æ", "", "¢", "", "", "Ñ", "Μ", "Ç", "", "", "ALT", "-colspan-"}};
std::vector<std::vector<const char*>> kbLastRowNormal{
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"},
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"},
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"},
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}};
std::vector<std::vector<const char*>> kbLastRowLoad{
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"},
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"},
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"},
{"SHIFT", "-colspan-", "SPACE", "-colspan-", "-colspan-", "-colspan-", "-colspan-", "LOAD", "-colspan-", "CLEAR", "-colspan-", "CANCEL", "-colspan-"}};
// clang-format on
GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup(
Window* window,
const HelpStyle& helpstyle,
const std::string& title,
const std::string& initValue,
const std::function<void(const std::string&)>& okCallback,
bool multiLine,
const std::string& acceptBtnHelpText,
const std::string& saveConfirmationText,
const std::string& infoString,
const std::string& defaultValue,
const std::string& loadBtnHelpText,
const std::string& clearBtnHelpText,
const std::string& cancelBtnHelpText)
: GuiComponent{window}
, mBackground{window, ":/graphics/frame.svg"}
, mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 8 : 6)}}
, mHelpStyle{helpstyle}
, mInitValue{initValue}
, mAcceptBtnHelpText{acceptBtnHelpText}
, mSaveConfirmationText{saveConfirmationText}
, mLoadBtnHelpText{loadBtnHelpText}
, mClearBtnHelpText{clearBtnHelpText}
, mCancelBtnHelpText{cancelBtnHelpText}
, mOkCallback{okCallback}
, mMultiLine{multiLine}
, mComplexMode{(infoString != "" && defaultValue != "")}
, mDeleteRepeat{false}
, mShift{false}
, mAlt{false}
, mDeleteRepeatTimer{0}
, mNavigationRepeatTimer{0}
, mNavigationRepeatDirX{0}
, mNavigationRepeatDirY{0}
{
addChild(&mBackground);
addChild(&mGrid);
mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title),
Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
std::vector<std::vector<const char*>> kbLayout;
// At the moment there is only the US keyboard layout available.
kbLayout.insert(kbLayout.cend(), kbBaseUS.cbegin(), kbBaseUS.cend());
// In complex mode, the last row of the keyboard contains an additional "LOAD" button.
if (mComplexMode)
kbLayout.insert(kbLayout.cend(), kbLastRowLoad.cbegin(), kbLastRowLoad.cend());
else
kbLayout.insert(kbLayout.cend(), kbLastRowNormal.cbegin(), kbLastRowNormal.cend());
mHorizontalKeyCount = static_cast<int>(kbLayout[0].size());
mKeyboardGrid = std::make_shared<ComponentGrid>(
mWindow, glm::ivec2(mHorizontalKeyCount, static_cast<int>(kbLayout.size()) / 3));
mText = std::make_shared<TextEditComponent>(mWindow);
mText->setValue(initValue);
if (!multiLine)
mText->setCursor(initValue.size());
// Header.
mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true);
int yPos = 1;
if (mComplexMode) {
mInfoString = std::make_shared<TextComponent>(
mWindow, infoString, Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER);
mGrid.setEntry(mInfoString, glm::ivec2{0, yPos}, false, true);
mDefaultValue = std::make_shared<TextComponent>(
mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
mGrid.setEntry(mDefaultValue, glm::ivec2{0, yPos + 1}, false, true);
yPos += 2;
}
// Text edit field.
mGrid.setEntry(mText, glm::ivec2{0, yPos}, true, false, glm::ivec2{1, 1},
GridFlags::BORDER_TOP);
std::vector<std::vector<std::shared_ptr<ButtonComponent>>> buttonList;
// Create keyboard.
for (int i = 0; i < static_cast<int>(kbLayout.size()) / 4; i++) {
std::vector<std::shared_ptr<ButtonComponent>> buttons;
for (int j = 0; j < static_cast<int>(kbLayout[i].size()); j++) {
std::string lower = kbLayout[4 * i][j];
if (lower.empty() || lower == "-rowspan-" || lower == "-colspan-")
continue;
std::string upper = kbLayout[4 * i + 1][j];
std::string alted = kbLayout[4 * i + 2][j];
std::string altshifted = kbLayout[4 * i + 3][j];
std::shared_ptr<ButtonComponent> button = nullptr;
if (lower == "DEL") {
lower = DELETE_SYMBOL;
upper = DELETE_SYMBOL;
alted = DELETE_SYMBOL;
altshifted = DELETE_SYMBOL;
}
else if (lower == "OK") {
lower = OK_SYMBOL;
upper = OK_SYMBOL;
alted = OK_SYMBOL;
altshifted = OK_SYMBOL;
}
else if (lower == "SPACE") {
lower = " ";
upper = " ";
alted = " ";
altshifted = " ";
}
else if (lower != "SHIFT" && lower.length() > 1) {
lower = (lower.c_str());
upper = (upper.c_str());
alted = (alted.c_str());
altshifted = (altshifted.c_str());
}
if (lower == "SHIFT") {
mShiftButton = std::make_shared<ButtonComponent>(
mWindow, (SHIFT_SYMBOL), ("SHIFT"), [this] { shiftKeys(); }, false, true);
button = mShiftButton;
}
else if (lower == "ALT") {
mAltButton = std::make_shared<ButtonComponent>(
mWindow, (ALT_SYMBOL), ("ALT"), [this] { altKeys(); }, false, true);
button = mAltButton;
}
else {
button = makeButton(lower, upper, alted, altshifted);
}
button->setPadding(
glm::vec4(BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f,
BUTTON_GRID_HORIZ_PADDING / 4.0f, BUTTON_GRID_HORIZ_PADDING / 4.0f));
buttons.push_back(button);
int colSpan = 1;
for (int cs = j + 1; cs < static_cast<int>(kbLayout[i].size()); cs++) {
if (std::string(kbLayout[4 * i][cs]) == "-colspan-")
colSpan++;
else
break;
}
int rowSpan = 1;
for (int cs = (4 * i) + 4; cs < static_cast<int>(kbLayout.size()); cs += 4) {
if (std::string(kbLayout[cs][j]) == "-rowspan-")
rowSpan++;
else
break;
}
mKeyboardGrid->setEntry(button, glm::ivec2{j, i}, true, true,
glm::ivec2{colSpan, rowSpan});
buttonList.push_back(buttons);
}
}
mGrid.setEntry(mKeyboardGrid, glm::ivec2{0, yPos + 1}, true, true, glm::ivec2{2, 4});
float textHeight = mText->getFont()->getHeight();
// If the multiLine option has been set, then include three lines of text on screen.
if (multiLine) {
textHeight *= 3.0f;
textHeight += 2.0f * Renderer::getScreenHeightModifier();
}
mText->setSize(0.0f, textHeight);
// If attempting to navigate beyond the edge of the keyboard grid, then wrap around.
mGrid.setPastBoundaryCallback([this, kbLayout](InputConfig* config, Input input) -> bool {
if (config->isMappedLike("left", input)) {
if (mGrid.getSelectedComponent() == mKeyboardGrid) {
mKeyboardGrid->moveCursorTo(mHorizontalKeyCount - 1, -1, true);
return true;
}
}
else if (config->isMappedLike("right", input)) {
if (mGrid.getSelectedComponent() == mKeyboardGrid) {
mKeyboardGrid->moveCursorTo(0, -1);
return true;
}
}
return false;
});
// Adapt width to the geometry of the display. The 1.778 aspect ratio is the 16:9 reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = glm::clamp(0.78f * aspectValue, 0.35f, 0.90f) * Renderer::getScreenWidth();
// The combination of multiLine and complex mode is not supported as there is currently
// no need for that.
if (mMultiLine) {
setSize(width, KEYBOARD_HEIGHT + textHeight - mText->getFont()->getHeight());
setPosition((static_cast<float>(Renderer::getScreenWidth()) - mSize.x) / 2.0f,
(static_cast<float>(Renderer::getScreenHeight()) - mSize.y) / 2.0f);
}
else {
if (mComplexMode)
setSize(width, KEYBOARD_HEIGHT + mDefaultValue->getSize().y * 3.0f);
else
setSize(width, KEYBOARD_HEIGHT);
setPosition((static_cast<float>(Renderer::getScreenWidth()) - mSize.x) / 2.0f,
(static_cast<float>(Renderer::getScreenHeight()) - mSize.y) / 2.0f);
}
}
void GuiTextEditKeyboardPopup::onSizeChanged()
{
mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f});
mText->setSize(mSize.x - KEYBOARD_PADDINGX - KEYBOARD_PADDINGX, mText->getSize().y);
// Update grid.
mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y);
if (mInfoString && mDefaultValue) {
mGrid.setRowHeightPerc(1, (mInfoString->getSize().y * 0.6f) / mSize.y);
mGrid.setRowHeightPerc(2, (mDefaultValue->getSize().y * 1.6f) / mSize.y);
mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.0f) / mSize.y);
}
else if (mMultiLine) {
mGrid.setRowHeightPerc(1, (mText->getSize().y * 1.15f) / mSize.y);
}
mGrid.setSize(mSize);
auto pos = mKeyboardGrid->getPosition();
auto sz = mKeyboardGrid->getSize();
// Add a small margin between buttons.
mKeyboardGrid->setSize(mSize.x - KEYBOARD_PADDINGX - KEYBOARD_PADDINGX,
sz.y - KEYBOARD_PADDINGY + 70.0f * Renderer::getScreenHeightModifier());
mKeyboardGrid->setPosition(KEYBOARD_PADDINGX, pos.y);
}
bool GuiTextEditKeyboardPopup::input(InputConfig* config, Input input)
{
// Enter/return key or numpad enter key accepts the changes.
if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine &&
input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) {
this->mOkCallback(mText->getValue());
delete this;
return true;
}
// Dito for the A button if using a controller.
else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() &&
config->isMappedTo("a", input) && input.value) {
this->mOkCallback(mText->getValue());
delete this;
return true;
}
// Pressing a key stops the navigation repeat, i.e. the cursor stops at the selected key.
if (config->isMappedTo("a", input) && input.value && !mText->isEditing()) {
mNavigationRepeatDirX = 0;
mNavigationRepeatDirY = 0;
}
// If the keyboard has been configured with backspace as the back button (which is the default
// configuration) then ignore this key if we're currently editing or otherwise it would be
// impossible to erase characters using this key.
bool keyboardBackspace = (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() &&
input.id == SDLK_BACKSPACE);
// Pressing back (or the escape key if using keyboard input) closes us.
if ((config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_ESCAPE) ||
(!keyboardBackspace && input.value && config->isMappedTo("b", input))) {
if (mText->getValue() != mInitValue) {
// Changes were made, ask if the user wants to save them.
mWindow->pushGui(new GuiMsgBox(
mWindow, mHelpStyle, mSaveConfirmationText, "YES",
[this] {
this->mOkCallback(mText->getValue());
delete this;
return true;
},
"NO",
[this] {
delete this;
return true;
}));
}
else {
delete this;
return true;
}
}
if (mText->isEditing() && config->isMappedLike("down", input) && input.value) {
mText->stopEditing();
mGrid.setCursorTo(mGrid.getSelectedComponent());
}
// Left trigger button outside text editing field toggles Shift key.
if (!mText->isEditing() && config->isMappedLike("lefttrigger", input) && input.value)
shiftKeys();
// Right trigger button outside text editing field toggles Alt key.
if (!mText->isEditing() && config->isMappedLike("righttrigger", input) && input.value)
altKeys();
// Left shoulder button deletes a character (backspace).
if (config->isMappedTo("leftshoulder", input)) {
if (input.value) {
mDeleteRepeat = true;
mDeleteRepeatTimer = -(DELETE_REPEAT_START_DELAY - DELETE_REPEAT_SPEED);
bool editing = mText->isEditing();
if (!editing)
mText->startEditing();
mText->textInput("\b");
if (!editing)
mText->stopEditing();
}
else {
mDeleteRepeat = false;
}
return true;
}
// Right shoulder button inserts a blank space.
if (config->isMappedTo("rightshoulder", input) && input.value) {
bool editing = mText->isEditing();
if (!editing)
mText->startEditing();
mText->textInput(" ");
if (!editing)
mText->stopEditing();
return true;
}
// Actual navigation of the keyboard grid is done in ComponentGrid, this code only handles
// key repeat while holding the left/right/up/down buttons.
if (!mText->isEditing() && config->isMappedLike("left", input)) {
if (input.value) {
mNavigationRepeatDirX = -1;
mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED);
}
else {
mNavigationRepeatDirX = 0;
}
}
if (!mText->isEditing() && config->isMappedLike("right", input)) {
if (input.value) {
mNavigationRepeatDirX = 1;
mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED);
}
else {
mNavigationRepeatDirX = 0;
}
}
if (!mText->isEditing() && config->isMappedLike("up", input)) {
if (input.value) {
mNavigationRepeatDirY = -1;
mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED);
}
else {
mNavigationRepeatDirY = 0;
}
}
if (!mText->isEditing() && config->isMappedLike("down", input)) {
if (input.value) {
mNavigationRepeatDirY = 1;
mNavigationRepeatTimer = -(NAVIGATION_REPEAT_START_DELAY - NAVIGATION_REPEAT_SPEED);
}
else {
mNavigationRepeatDirY = 0;
}
}
if (GuiComponent::input(config, input))
return true;
return false;
}
void GuiTextEditKeyboardPopup::update(int deltaTime)
{
updateNavigationRepeat(deltaTime);
updateDeleteRepeat(deltaTime);
GuiComponent::update(deltaTime);
}
std::vector<HelpPrompt> GuiTextEditKeyboardPopup::getHelpPrompts()
{
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
if (!mText->isEditing()) {
prompts.push_back(HelpPrompt("lt", "shift"));
prompts.push_back(HelpPrompt("rt", "alt"));
}
else {
prompts.push_back(HelpPrompt("a", mAcceptBtnHelpText));
}
prompts.push_back(HelpPrompt("l", "backspace"));
prompts.push_back(HelpPrompt("r", "space"));
prompts.push_back(HelpPrompt("b", "back"));
if (prompts.size() > 0 && prompts.front().second == OK_SYMBOL)
prompts.front().second = mAcceptBtnHelpText;
if (prompts.size() > 0 && prompts.front().second == " ")
prompts.front().second = "SPACE";
if (prompts.size() > 0 && prompts.front().second == "CLEAR")
prompts.front().second = mClearBtnHelpText;
if (prompts.size() > 0 && prompts.front().second == "LOAD")
prompts.front().second = mLoadBtnHelpText;
if (prompts.size() > 0 && prompts.front().second == "CANCEL")
prompts.front().second = mCancelBtnHelpText;
// If a prompt has no value set, then remove it.
if (prompts.size() > 0 && prompts.front().second == "")
prompts.erase(prompts.begin(), prompts.begin() + 1);
return prompts;
}
void GuiTextEditKeyboardPopup::updateDeleteRepeat(int deltaTime)
{
if (!mDeleteRepeat)
return;
mDeleteRepeatTimer += deltaTime;
while (mDeleteRepeatTimer >= DELETE_REPEAT_SPEED) {
bool editing = mText->isEditing();
if (!editing)
mText->startEditing();
mText->textInput("\b");
if (!editing)
mText->stopEditing();
mDeleteRepeatTimer -= DELETE_REPEAT_SPEED;
}
}
void GuiTextEditKeyboardPopup::updateNavigationRepeat(int deltaTime)
{
if (mNavigationRepeatDirX == 0 && mNavigationRepeatDirY == 0)
return;
mNavigationRepeatTimer += deltaTime;
while (mNavigationRepeatTimer >= NAVIGATION_REPEAT_SPEED) {
if (mNavigationRepeatDirX != 0) {
mKeyboardGrid.get()->moveCursor({mNavigationRepeatDirX, 0});
// If replacing the line above with this code, the keyboard will wrap around the
// edges also when key repeat is active.
// if (!mKeyboardGrid.get()->moveCursor({mNavigationRepeatDirX, 0})) {
// if (mNavigationRepeatDirX < 0)
// mKeyboardGrid->moveCursorTo(mHorizontalKeyCount - 1, -1);
// else
// mKeyboardGrid->moveCursorTo(0, -1);
// }
}
if (mNavigationRepeatDirY != 0)
mKeyboardGrid.get()->moveCursor({0, mNavigationRepeatDirY});
mNavigationRepeatTimer -= NAVIGATION_REPEAT_SPEED;
}
}
void GuiTextEditKeyboardPopup::shiftKeys()
{
mShift = !mShift;
if (mShift) {
mShiftButton->setFlatColorFocused(0xFF2222FF);
mShiftButton->setFlatColorUnfocused(0xFF2222FF);
}
else {
mShiftButton->setFlatColorFocused(0x878787FF);
mShiftButton->setFlatColorUnfocused(0x60606025);
}
if (mAlt && mShift) {
altShiftKeys();
return;
}
// This only happens when Alt was deselected while both Shift and Alt were active.
if (mAlt) {
altKeys();
altKeys();
}
else {
for (auto& kb : mKeyboardButtons) {
const std::string& text = mShift ? kb.shiftedKey : kb.key;
auto sz = kb.button->getSize();
kb.button->setText(text, text, false);
kb.button->setSize(sz);
}
}
}
void GuiTextEditKeyboardPopup::altKeys()
{
mAlt = !mAlt;
if (mAlt) {
mAltButton->setFlatColorFocused(0xFF2222FF);
mAltButton->setFlatColorUnfocused(0xFF2222FF);
}
else {
mAltButton->setFlatColorFocused(0x878787FF);
mAltButton->setFlatColorUnfocused(0x60606025);
}
if (mShift && mAlt) {
altShiftKeys();
return;
}
// This only happens when Shift was deselected while both Shift and Alt were active.
if (mShift) {
shiftKeys();
shiftKeys();
}
else {
for (auto& kb : mKeyboardButtons) {
const std::string& text = mAlt ? kb.altedKey : kb.key;
auto sz = kb.button->getSize();
kb.button->setText(text, text, false);
kb.button->setSize(sz);
}
}
}
void GuiTextEditKeyboardPopup::altShiftKeys()
{
for (auto& kb : mKeyboardButtons) {
const std::string& text = kb.altshiftedKey;
auto sz = kb.button->getSize();
kb.button->setText(text, text, false);
kb.button->setSize(sz);
}
}
std::shared_ptr<ButtonComponent> GuiTextEditKeyboardPopup::makeButton(
const std::string& key,
const std::string& shiftedKey,
const std::string& altedKey,
const std::string& altshiftedKey)
{
std::shared_ptr<ButtonComponent> button = std::make_shared<ButtonComponent>(
mWindow, key, key,
[this, key, shiftedKey, altedKey, altshiftedKey] {
if (key == (OK_SYMBOL) || key.find("OK") != std::string::npos) {
mOkCallback(mText->getValue());
delete this;
return;
}
else if (key == (DELETE_SYMBOL) || key == "DEL") {
mText->startEditing();
mText->textInput("\b");
mText->stopEditing();
return;
}
else if (key == "SPACE" || key == " ") {
mText->startEditing();
mText->textInput(" ");
mText->stopEditing();
return;
}
else if (key == "LOAD") {
mText->setValue(mDefaultValue->getValue());
mText->setCursor(mDefaultValue->getValue().size());
return;
}
else if (key == "CLEAR") {
mText->setValue("");
return;
}
else if (key == "CANCEL") {
delete this;
return;
}
if (mAlt && altedKey.empty())
return;
mText->startEditing();
if (mShift && mAlt)
mText->textInput(altshiftedKey.c_str());
else if (mAlt)
mText->textInput(altedKey.c_str());
else if (mShift)
mText->textInput(shiftedKey.c_str());
else
mText->textInput(key.c_str());
mText->stopEditing();
},
false, true);
KeyboardButton kb(button, key, shiftedKey, altedKey, altshiftedKey);
mKeyboardButtons.push_back(kb);
return button;
}

View file

@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GuiTextEditKeyboardPopup.h
//
// Text edit popup with a virtual keyboard.
// Has a default mode and a complex mode, both with various options passed as arguments.
//
#ifndef ES_CORE_GUIS_GUI_TEXT_EDIT_KEYBOARD_POPUP_H
#define ES_CORE_GUIS_GUI_TEXT_EDIT_KEYBOARD_POPUP_H
#include "GuiComponent.h"
#include "components/ButtonComponent.h"
#include "components/ComponentGrid.h"
#include "components/TextEditComponent.h"
class GuiTextEditKeyboardPopup : public GuiComponent
{
public:
GuiTextEditKeyboardPopup(Window* window,
const HelpStyle& helpstyle,
const std::string& title,
const std::string& initValue,
const std::function<void(const std::string&)>& okCallback,
bool multiLine,
const std::string& acceptBtnHelpText = "OK",
const std::string& saveConfirmationText = "SAVE CHANGES?",
const std::string& infoString = "",
const std::string& defaultValue = "",
const std::string& loadBtnHelpText = "LOAD DEFAULT",
const std::string& clearBtnHelpText = "CLEAR",
const std::string& cancelBtnHelpText = "DISCARD CHANGES");
void onSizeChanged() override;
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override { return mHelpStyle; }
private:
class KeyboardButton
{
public:
std::shared_ptr<ButtonComponent> button;
const std::string key;
const std::string shiftedKey;
const std::string altedKey;
const std::string altshiftedKey;
KeyboardButton(const std::shared_ptr<ButtonComponent> b,
const std::string& k,
const std::string& sk,
const std::string& ak,
const std::string& ask)
: button{b}
, key{k}
, shiftedKey{sk}
, altedKey{ak}
, altshiftedKey{ask} {};
};
void updateDeleteRepeat(int deltaTime);
void updateNavigationRepeat(int deltaTime);
void shiftKeys();
void altKeys();
void altShiftKeys();
std::shared_ptr<ButtonComponent> makeButton(const std::string& key,
const std::string& shiftedKey,
const std::string& altedKey,
const std::string& altshiftedKey);
std::vector<KeyboardButton> mKeyboardButtons;
std::shared_ptr<ButtonComponent> mShiftButton;
std::shared_ptr<ButtonComponent> mAltButton;
NinePatchComponent mBackground;
ComponentGrid mGrid;
HelpStyle mHelpStyle;
std::shared_ptr<TextComponent> mTitle;
std::shared_ptr<TextComponent> mInfoString;
std::shared_ptr<TextComponent> mDefaultValue;
std::shared_ptr<TextEditComponent> mText;
std::shared_ptr<ComponentGrid> mKeyboardGrid;
std::string mInitValue;
std::string mAcceptBtnHelpText;
std::string mSaveConfirmationText;
std::string mLoadBtnHelpText;
std::string mClearBtnHelpText;
std::string mCancelBtnHelpText;
std::function<void(const std::string&)> mOkCallback;
bool mMultiLine;
bool mComplexMode;
bool mDeleteRepeat;
bool mShift;
bool mAlt;
int mHorizontalKeyCount;
int mDeleteRepeatTimer;
int mNavigationRepeatTimer;
int mNavigationRepeatDirX;
int mNavigationRepeatDirY;
};
#endif // ES_CORE_GUIS_GUI_TEXT_EDIT_KEYBOARD_POPUP_H

View file

@ -3,15 +3,16 @@
// EmulationStation Desktop Edition // EmulationStation Desktop Edition
// GuiTextEditPopup.cpp // GuiTextEditPopup.cpp
// //
// Simple text edit popup with a title, a text input box and OK and Cancel buttons. // Text edit popup.
// Has a default mode and a complex mode, both with various options passed as arguments.
// //
#define DELETE_REPEAT_START_DELAY 600
#define DELETE_REPEAT_SPEED 90 // Lower is faster.
#include "guis/GuiTextEditPopup.h" #include "guis/GuiTextEditPopup.h"
#include "Window.h"
#include "components/ButtonComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "components/TextEditComponent.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
GuiTextEditPopup::GuiTextEditPopup(Window* window, GuiTextEditPopup::GuiTextEditPopup(Window* window,
@ -21,15 +22,27 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window,
const std::function<void(const std::string&)>& okCallback, const std::function<void(const std::string&)>& okCallback,
bool multiLine, bool multiLine,
const std::string& acceptBtnText, const std::string& acceptBtnText,
const std::string& saveConfirmationText) const std::string& saveConfirmationText,
: GuiComponent(window) const std::string& infoString,
, mHelpStyle(helpstyle) const std::string& defaultValue,
, mBackground(window, ":/graphics/frame.svg") const std::string& loadBtnHelpText,
, mGrid(window, glm::ivec2{1, 3}) const std::string& clearBtnHelpText,
, mMultiLine(multiLine) const std::string& cancelBtnHelpText)
, mInitValue(initValue) : GuiComponent{window}
, mOkCallback(okCallback) , mBackground{window, ":/graphics/frame.svg"}
, mSaveConfirmationText(saveConfirmationText) , mGrid{window, glm::ivec2{1, (infoString != "" && defaultValue != "" ? 5 : 3)}}
, mHelpStyle{helpstyle}
, mInitValue{initValue}
, mAcceptBtnText{acceptBtnText}
, mSaveConfirmationText{saveConfirmationText}
, mLoadBtnHelpText{loadBtnHelpText}
, mClearBtnHelpText{clearBtnHelpText}
, mCancelBtnHelpText{cancelBtnHelpText}
, mOkCallback{okCallback}
, mMultiLine{multiLine}
, mComplexMode{(infoString != "" && defaultValue != "")}
, mDeleteRepeat{false}
, mDeleteRepeatTimer{0}
{ {
addChild(&mBackground); addChild(&mBackground);
addChild(&mGrid); addChild(&mGrid);
@ -37,6 +50,13 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window,
mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title), mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title),
Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER); Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER);
if (mComplexMode) {
mInfoString = std::make_shared<TextComponent>(
mWindow, infoString, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
mDefaultValue = std::make_shared<TextComponent>(
mWindow, defaultValue, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
}
mText = std::make_shared<TextEditComponent>(mWindow); mText = std::make_shared<TextEditComponent>(mWindow);
mText->setValue(initValue); mText->setValue(initValue);
@ -46,55 +66,116 @@ GuiTextEditPopup::GuiTextEditPopup(Window* window,
okCallback(mText->getValue()); okCallback(mText->getValue());
delete this; delete this;
})); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear", if (mComplexMode) {
buttons.push_back(std::make_shared<ButtonComponent>(
mWindow, "load", loadBtnHelpText, [this, defaultValue] {
mText->setValue(defaultValue);
mText->setCursor(0);
mText->setCursor(defaultValue.size());
}));
}
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "clear", clearBtnHelpText,
[this] { mText->setValue(""); })); [this] { mText->setValue(""); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes", buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes",
[this] { delete this; })); [this] { delete this; }));
mButtonGrid = makeButtonGrid(mWindow, buttons); mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true); mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true);
mGrid.setEntry(mText, glm::ivec2{0, 1}, true, false, glm::ivec2{1, 1},
int yPos = 1;
if (mComplexMode) {
mGrid.setEntry(mInfoString, glm::ivec2{0, yPos}, false, true);
mGrid.setEntry(mDefaultValue, glm::ivec2{0, yPos + 1}, false, false);
yPos += 2;
}
mGrid.setEntry(mText, glm::ivec2{0, yPos}, true, false, glm::ivec2{1, 1},
GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM); GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
mGrid.setEntry(mButtonGrid, glm::ivec2{0, 2}, true, false); mGrid.setEntry(mButtonGrid, glm::ivec2{0, yPos + 1}, true, false);
float textHeight = mText->getFont()->getHeight(); float textHeight = mText->getFont()->getHeight();
if (multiLine) if (multiLine)
textHeight *= 6.0f; textHeight *= 6.0f;
mText->setSize(0, textHeight); mText->setSize(0, textHeight);
// Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent // Adapt width to the geometry of the display. The 1.778 aspect ratio is the 16:9 reference.
// regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = glm::clamp(0.50f * aspectValue, 0.40f, 0.70f) * Renderer::getScreenWidth();
setSize(width, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y + if (mComplexMode) {
mButtonGrid->getSize().y / 2.0f); float infoWidth =
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, glm::clamp(0.70f * aspectValue, 0.34f, 0.85f) * Renderer::getScreenWidth();
(Renderer::getScreenHeight() - mSize.y) / 2.0f); float windowWidth =
glm::clamp(0.75f * aspectValue, 0.40f, 0.90f) * Renderer::getScreenWidth();
mDefaultValue->setSize(infoWidth, mDefaultValue->getFont()->getHeight());
setSize(windowWidth, mTitle->getFont()->getHeight() + textHeight +
mButtonGrid->getSize().y + mButtonGrid->getSize().y * 1.85f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
(Renderer::getScreenHeight() - mSize.y) / 2.0f);
}
else {
float width = glm::clamp(0.54f * aspectValue, 0.20f, 0.70f) * Renderer::getScreenWidth();
setSize(width, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y +
mButtonGrid->getSize().y / 2.0f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
(Renderer::getScreenHeight() - mSize.y) / 2.0f);
}
if (!multiLine)
mText->setCursor(initValue.size());
mText->startEditing(); mText->startEditing();
} }
void GuiTextEditPopup::onSizeChanged() void GuiTextEditPopup::onSizeChanged()
{ {
mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f}); mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f});
mText->setSize(mSize.x - 40.0f * Renderer::getScreenHeightModifier(), mText->getSize().y);
mText->setSize(mSize.x - 40.0f, mText->getSize().y);
// Update grid. // Update grid.
mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y); mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y);
if (mComplexMode)
mGrid.setRowHeightPerc(1, 0.15f);
mGrid.setRowHeightPerc(2, mButtonGrid->getSize().y / mSize.y); mGrid.setRowHeightPerc(2, mButtonGrid->getSize().y / mSize.y);
mGrid.setSize(mSize); mGrid.setSize(mSize);
} }
bool GuiTextEditPopup::input(InputConfig* config, Input input) bool GuiTextEditPopup::input(InputConfig* config, Input input)
{ {
if (GuiComponent::input(config, input)) // Enter key (main key or via numpad) accepts the changes.
if (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() && !mMultiLine &&
input.value && (input.id == SDLK_RETURN || input.id == SDLK_KP_ENTER)) {
this->mOkCallback(mText->getValue());
delete this;
return true; return true;
}
// Dito for the A button if using a controller.
else if (config->getDeviceId() != DEVICE_KEYBOARD && mText->isEditing() &&
config->isMappedTo("a", input) && input.value) {
this->mOkCallback(mText->getValue());
delete this;
return true;
}
// Pressing back when not text editing closes us. // If the keyboard has been configured with backspace as the back button (which is the default
if (config->isMappedTo("b", input) && input.value) { // configuration) then ignore this key if we're currently editing or otherwise it would be
// impossible to erase characters using this key.
bool keyboardBackspace = (config->getDeviceId() == DEVICE_KEYBOARD && mText->isEditing() &&
input.id == SDLK_BACKSPACE);
// Pressing back (or the escape key if using keyboard input) closes us.
if ((config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_ESCAPE) ||
(!keyboardBackspace && input.value && config->isMappedTo("b", input))) {
if (mText->getValue() != mInitValue) { if (mText->getValue() != mInitValue) {
// 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->pushGui(new GuiMsgBox(
@ -107,19 +188,97 @@ bool GuiTextEditPopup::input(InputConfig* config, Input input)
"NO", "NO",
[this] { [this] {
delete this; delete this;
return false; return true;
})); }));
} }
else { else {
delete this; delete this;
return true;
} }
} }
if (mText->isEditing() && config->isMappedLike("down", input) && input.value) {
mText->stopEditing();
mGrid.setCursorTo(mGrid.getSelectedComponent());
}
// Left shoulder button deletes a character (backspace).
if (config->isMappedTo("leftshoulder", input)) {
if (input.value) {
mDeleteRepeat = true;
mDeleteRepeatTimer = -(DELETE_REPEAT_START_DELAY - DELETE_REPEAT_SPEED);
bool editing = mText->isEditing();
if (!editing)
mText->startEditing();
mText->textInput("\b");
if (!editing)
mText->stopEditing();
}
else {
mDeleteRepeat = false;
}
return true;
}
// Right shoulder button inserts a blank space.
if (config->isMappedTo("rightshoulder", input) && input.value) {
bool editing = mText->isEditing();
if (!editing)
mText->startEditing();
mText->textInput(" ");
if (!editing)
mText->stopEditing();
return true;
}
if (GuiComponent::input(config, input))
return true;
return false; return false;
} }
void GuiTextEditPopup::update(int deltaTime)
{
updateDeleteRepeat(deltaTime);
GuiComponent::update(deltaTime);
}
std::vector<HelpPrompt> GuiTextEditPopup::getHelpPrompts() std::vector<HelpPrompt> GuiTextEditPopup::getHelpPrompts()
{ {
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts(); std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
if (mText->isEditing())
prompts.push_back(HelpPrompt("a", mAcceptBtnText));
prompts.push_back(HelpPrompt("l", "backspace"));
prompts.push_back(HelpPrompt("r", "space"));
prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("b", "back"));
return prompts; return prompts;
} }
void GuiTextEditPopup::updateDeleteRepeat(int deltaTime)
{
if (!mDeleteRepeat)
return;
mDeleteRepeatTimer += deltaTime;
while (mDeleteRepeatTimer >= DELETE_REPEAT_SPEED) {
bool editing = mText->isEditing();
if (!editing)
mText->startEditing();
mText->textInput("\b");
if (!editing)
mText->stopEditing();
mDeleteRepeatTimer -= DELETE_REPEAT_SPEED;
}
}

View file

@ -3,18 +3,17 @@
// EmulationStation Desktop Edition // EmulationStation Desktop Edition
// GuiTextEditPopup.h // GuiTextEditPopup.h
// //
// Simple text edit popup with a title, a text input box and OK and Cancel buttons. // Text edit popup.
// Has a default mode and a complex mode, both with various options passed as arguments.
// //
#ifndef ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H #ifndef ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H
#define ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H #define ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H
#include "GuiComponent.h" #include "GuiComponent.h"
#include "components/ButtonComponent.h"
#include "components/ComponentGrid.h" #include "components/ComponentGrid.h"
#include "components/NinePatchComponent.h" #include "components/TextEditComponent.h"
class TextComponent;
class TextEditComponent;
class GuiTextEditPopup : public GuiComponent class GuiTextEditPopup : public GuiComponent
{ {
@ -26,27 +25,47 @@ public:
const std::function<void(const std::string&)>& okCallback, const std::function<void(const std::string&)>& okCallback,
bool multiLine, bool multiLine,
const std::string& acceptBtnText = "OK", const std::string& acceptBtnText = "OK",
const std::string& saveConfirmationText = "SAVE CHANGES?"); const std::string& saveConfirmationText = "SAVE CHANGES?",
const std::string& infoString = "",
const std::string& defaultValue = "",
const std::string& loadBtnHelpText = "LOAD DEFAULT",
const std::string& clearBtnHelpText = "CLEAR",
const std::string& cancelBtnHelpText = "DISCARD CHANGES");
bool input(InputConfig* config, Input input) override;
void onSizeChanged() override; void onSizeChanged() override;
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
std::vector<HelpPrompt> getHelpPrompts() override; std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override { return mHelpStyle; } HelpStyle getHelpStyle() override { return mHelpStyle; }
private: private:
void updateDeleteRepeat(int deltaTime);
NinePatchComponent mBackground; NinePatchComponent mBackground;
ComponentGrid mGrid; ComponentGrid mGrid;
HelpStyle mHelpStyle;
std::shared_ptr<TextComponent> mTitle; std::shared_ptr<TextComponent> mTitle;
std::shared_ptr<TextComponent> mInfoString;
std::shared_ptr<TextComponent> mDefaultValue;
std::shared_ptr<TextEditComponent> mText; std::shared_ptr<TextEditComponent> mText;
std::shared_ptr<ComponentGrid> mButtonGrid; std::shared_ptr<ComponentGrid> mButtonGrid;
HelpStyle mHelpStyle;
bool mMultiLine;
std::string mInitValue; std::string mInitValue;
std::function<void(const std::string&)> mOkCallback; std::string mAcceptBtnText;
std::string mSaveConfirmationText; std::string mSaveConfirmationText;
std::string mLoadBtnHelpText;
std::string mClearBtnHelpText;
std::string mCancelBtnHelpText;
std::function<void(const std::string&)> mOkCallback;
bool mMultiLine;
bool mComplexMode;
bool mDeleteRepeat;
int mDeleteRepeatTimer;
}; };
#endif // ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H #endif // ES_CORE_GUIS_GUI_TEXT_EDIT_POPUP_H

View file

@ -25,6 +25,7 @@ namespace Renderer
{ {
static std::stack<Rect> clipStack; static std::stack<Rect> clipStack;
static SDL_Window* sdlWindow = nullptr; static SDL_Window* sdlWindow = nullptr;
static glm::mat4 mProjectionMatrix;
static int windowWidth = 0; static int windowWidth = 0;
static int windowHeight = 0; static int windowHeight = 0;
static int screenWidth = 0; static int screenWidth = 0;
@ -150,7 +151,7 @@ namespace Renderer
#if defined(__unix__) #if defined(__unix__)
// Disabling desktop composition can lead to better framerates and a more fluid user // Disabling desktop composition can lead to better framerates and a more fluid user
// interface, but with some drivers it can cause strange behaviours when returning to // interface, but with some drivers it can cause strange behaviors when returning to
// the desktop. // the desktop.
if (Settings::getInstance()->getBool("DisableComposition")) if (Settings::getInstance()->getBool("DisableComposition"))
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "1"); SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "1");
@ -539,7 +540,7 @@ namespace Renderer
return sShaderProgramVector[index - 1]; return sShaderProgramVector[index - 1];
else else
return nullptr; return nullptr;
}; }
const glm::mat4 getProjectionMatrix() { return mProjectionMatrix; } const glm::mat4 getProjectionMatrix() { return mProjectionMatrix; }
SDL_Window* getSDLWindow() { return sdlWindow; } SDL_Window* getSDLWindow() { return sdlWindow; }

View file

@ -48,13 +48,15 @@ namespace Renderer
static std::vector<Shader*> sShaderProgramVector; static std::vector<Shader*> sShaderProgramVector;
static GLuint shaderFBO; static GLuint shaderFBO;
static glm::mat4 mProjectionMatrix; // This is simply to get rid of a GCC false positive -Wunused-variable compiler warning.
static GLuint shaderFBODummy = shaderFBO;
static constexpr glm::mat4 getIdentity() { return glm::mat4{1.0f}; } static constexpr glm::mat4 getIdentity() { return glm::mat4{1.0f}; }
#if !defined(NDEBUG) #if !defined(NDEBUG)
#define GL_CHECK_ERROR(Function) (Function, _GLCheckError(#Function)) #define GL_CHECK_ERROR(Function) (Function, _GLCheckError(#Function))
static void _GLCheckError(const std::string& _funcName) static inline void _GLCheckError(const std::string& _funcName)
{ {
const GLenum errorCode = glGetError(); const GLenum errorCode = glGetError();
@ -162,7 +164,7 @@ namespace Renderer
const Renderer::shaderParameters& parameters = shaderParameters(), const Renderer::shaderParameters& parameters = shaderParameters(),
unsigned char* textureRGBA = nullptr); unsigned char* textureRGBA = nullptr);
static unsigned int getWindowFlags() { return SDL_WINDOW_OPENGL; } static inline unsigned int getWindowFlags() { return SDL_WINDOW_OPENGL; }
void setupWindow(); void setupWindow();
bool createContext(); bool createContext();

View file

@ -456,7 +456,7 @@ namespace Renderer
GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)); GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));
for (int i = 0; i < shaderList.size(); i++) { for (size_t i = 0; i < shaderList.size(); i++) {
vertices[0].shaders = shaderList[i]; vertices[0].shaders = shaderList[i];
int shaderPasses = 1; int shaderPasses = 1;
// For the blur shaders there is an optional variable to set the number of passes // For the blur shaders there is an optional variable to set the number of passes

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