Merge branch 'master' into 1208_new_emulators

This commit is contained in:
Leon Styhre 2022-12-16 17:43:05 +01:00
commit db7aed1075
86 changed files with 4772 additions and 1785 deletions

View file

@ -13,6 +13,7 @@
* Made fundamental changes to the application logic by removing most view styles and replacing them with a new theme variants concept
* Added theme support for defining and applying different layouts for various display aspect ratios such as 16:9 and 4:3
* Added theme support for defining and applying different color schemes
* Added a new grid component that is usable in both the system and gamelist views
* Made gamelist theming much more flexible by allowing any number of elements of any types to be defined
* Deprecated multiple older theming concepts like features, extras and hardcoded metadata attributes
* Renamed the default theme set from rbsimple-DE to slate-DE
@ -21,10 +22,15 @@
* Added support for caching of SVG images
* Added support for sizing SVG images arbitrarily (overriding the image aspect ratio by stretching and squashing)
* (Windows) Made game launching more seamless by making the application window one pixel wider instead of one pixel less in height
* Expanded the quick system select menu option from an on/off entry to a selection of different button combinations
* Changed the order of the help system entries Y, X, B and A to instead be listed as A, B, X and Y
* Changed the start button for the screensaver from "Back" to "X"
* Changed the help system description for the "A" button in the gamelist view from "Launch" to "Select"
* Changed the menu header for the gamelist options menu from "Options" to "Gamelist options"
* Added support for the Nintendo Wii U (wiiu) game system on Linux and macOS by adding the Cemu standalone emulator
* Added ares standalone as an alternative emulator for many systems
* Added MAME standalone as an alternative emulator for the gameandwatch system
* Added openMSX standalone as an alternative emulator for the colecovision, msx, msx1, msx2 and msxturbor systems
* (Linux) Added support for the Nintendo Wii U (wiiu) game system by adding the Cemu standalone emulator
* (Linux) Added support for the Sega Model 3 (model3) game system by adding the Supermodel standalone emulator
* (Linux) Added Supermodel standalone as an alternative emulator for the arcade and mame systems
* Added support for the Sega Model 2 (model2) game system on Linux on macOS by adding the MAME - Current RetroArch core
@ -34,7 +40,7 @@
* Added a %GAMEDIR% variable to the -rompath option for all MAME standalone entries to allow launching games from subdirectories
* Added Triforce (Dolphin fork) standalone as an alternative emulator for the gc system on Linux and Windows
* Added simple64 standalone as an alternative emulator for the n64 system on Linux and Windows
* (Linux) Added Rosalie's Mupen GUI standalone as an alternative emulator for the n64 system
* Added Rosalie's Mupen GUI standalone as an alternative emulator for the n64 system on Linux and Windows
* Added VICE standalone as an alternative emulator for the c64 (x64sc only) and vic20 systems
* (Windows) Added PrimeHack as an alternative emulator for the gc and wii systems
* (Windows) Added Project64 as an alternative emulator for the n64 system
@ -43,6 +49,7 @@
* Added support for the mugen system on Linux and macOS using the Ikemen GO game engine
* Added CPCemu standalone as an alternative emulator for the amstradcpc system
* Added MAME standalone as an alternative emulator for the gx4000 system
* Added the . (dot) file extension to the xbox360 system on Windows to support extensionless XBLA games
* Added the .car and .rom file extensions to the a5200 system
* Added the .car file extension to the atari800 system
* Added the .bin file extension to the gx4000 system
@ -72,6 +79,7 @@
* Greatly improved application startup speed by avoiding a lot of unnecessary SVG rasterizations
* Implemented dynamic texture allocation to the font code to reduce memory usage and avoid missing glyphs
* Large optimizations to the text wrapping code (generallly 300-400% faster)
* Added support for linear interpolation for font texture magnifications
* Added support for texture mipmapping with trilinear filtering
* Added on-demand texture loading to the carousel
* Improved the renderer scaling accuracy
@ -102,11 +110,13 @@
* Added support for defining the scrollable container speed, start delay and reset delay from the theme configuration
* Added support for arbitrary aspect ratios for RatingComponent images and also added an overlay property
* Added theme support for defining the opacity for most element types
* Added theme support for defining relative brightness for images, videos and animations
* Added theme support for defining color saturation for images, videos and animations
* Added theme support for defining the video fade-in time
* Added theme support for enabling and disabling video pillarboxes and scanline rendering
* Added theme support for defining the threshold for when pillarboxes should be applied to a video
* Added theme support for enabling or disabling audio playback for videos
* Added theme support for color shifting videos (and the static image)
* Added theme support for setting separate textColorDimmed and iconColorDimmed properties for the system and gamelist views
* Added support for nesting of theme variables
* Added support for defining multiple theme "variables" tags in the same XML file
@ -120,6 +130,7 @@
* Improved theme element placement by replacing the "alignment" and "logoAlignment" properties with specific horizontal and vertical properties
* Made it possible to use almost all game metadata field when theming text elements
* Made it possible to set the image interpolation method (nearest neighbor or linear filtering) per image from the theme configuration
* Added support for resizing and cropping images to fill the entire defined area (cover fitting)
* Changed the helpsystem properties entrySpacing and iconTextSpacing from fixed pixel values to relative values
* Added support for using unsigned integers for theme properties
* Added a metadataElement theme property to the image, video, animation and text element types to control fading and hiding of arbitrary elements
@ -129,6 +140,7 @@
* Added scraping of fan art and updated the media viewer to display these images
* Added scraping of box back covers when using TheGamesDB
* If a wheel (marquee) image on ScreenScraper falls back to another region, then the wheel-hd image is now used instead if it matches the set region
* Removed scraping of arcade controller information using ScreenScraper as they have ruined this functionality
* Added a ScreenScraper-specific option to remove dots from game name searches when using the multi-scraper in automatic mode
* Moved the option "Scrape actual folders" higher up in the scraper options menu
* Added the ability to set a manual sortname specifically for custom collections using the metadata editor
@ -148,7 +160,7 @@
* Added opacity support to the scanline shader
* Added the rlottie library as a Git subtree
* Updated to build correctly with FFmpeg 5.1
* Updated FFmpeg to 5.1.2, SDL to 2.24.1, FreeType to 2.12.1 and pugixml to 1.12.1 on Windows and macOS
* Updated SDL to 2.26.1, FFmpeg to 5.1.2, FreeType to 2.12.1 and pugixml to 1.12.1 on Windows and macOS
* Updated curl to 7.86.0 on Windows
* Added a workaround for playing broken video files with invalid PTS values
* Refactored the rendering code from a shared namespace into proper classes
@ -166,9 +178,12 @@
* Made the logging thread safe
* (Windows) Changed many logging entries to use backslashes instead of forward slashes as directory separators
* Added the build date to to main menu for alpha and dev builds
* Added a left trigger + right trigger help system icon and removed the deprecated hotkey icon
* Added an arcade twin stick controller badge icon
* Moved all Platform functions to the utility namespace instead of using the global namespace
* Implemented proper XML attribute support in ThemeData that eliminates the risk of name collisions
* Added size restrictions to images and fonts so incorrect theme configuration would not lead to crashes or excessive memory utilization
* Made animations on the carousel better looking by using a new non-linear interpolation method
* Migrated the carousel code from SystemView to a separate new CarouselComponent
* Changed the carousel properties to be more generic by renaming "logo" to "item", e.g. itemSize, maxItemCount etc.
* Added the properties "itemsBeforeCenter" and "itemsAfterCenter" to define entries for carousels of the wheel type
@ -176,7 +191,11 @@
* Added reflections support to the carousel
* Added a new itemAxisHorizontal property to the carousel to keep wheel items horizontal at all times
* Added carousel theme support for setting the opacity for unfocused entries
* Added carousel theme support for applying image color shifts
* Added carousel theme support for defining image brightness
* Added carousel theme support for defining image saturation
* Added carousel theme support for setting item transitions to "slide" or "instant"
* Added carousel theme support for controlling item stacking for overlapping items
* Added a fadeAbovePrimary property to control whether elements above the system view carousel and textlist should be rendered during fade transitions
* Removed support for the thumbnail game media type
* Changed all occurances of "GameList" to "Gamelist" throughout the codebase
@ -206,11 +225,14 @@
### Bug fixes
* Multiple levels of symlinking in the ROMs directory tree could crash the application on startup
* Adding a dot (.) to the es_systems.xml extension tag (to setup extensionless entries) lead to a crash if the system contained folders
* For the cps system, MAME standalone was configured with the wrong system directory for the -rompath option, pointing to "arcade" instead of "cps"
* During some menu operations that reloaded the gamelist view, the cached background could miss some components as they were not rendered in time
* Text wrapping did not work correctly for text that typically does not contain spaces, like Japanese
* Changing some values using the metadata editor could lead to an incorrect sort order if the changes were done from within a grouped custom collection
* Changing the setting "Group unthemed custom collections" could lead to incorrect custom collections sorting under some circumstances
* For gamelists which mixed files and folders, the folder sorting was sometimes incorrect
* Incorrect folder paths were displayed in the metadata editor if the setting "Only show ROMs from gamelist.xml files" was enabled
* Games located in subdirectories were not added back to custom collections when disabling the "Exclude from game counter" metadata option
* Enabling and then disabling the "Exclude from game counter" metadata option would remove a game from all currently disabled custom collections
* Navigation sounds for the trigger buttons would play when repeatedly pressed at the start or end of text lists
@ -226,6 +248,7 @@
* The video player output frame width was not set correctly which made some videos render as garbled when using FFmpeg 5.1 and later
* If a gamelist scroll fade-in animation was playing when opening a menu, it would continue to play after closing the menu
* The gamelist quick list scrolling overlay would not disappear as intended under some circumstances
* Using the trigger buttons to jump to the start or end of a gamelist did not reset any currently held navigation buttons
* When a legacy theme set had a video view style but did not have a valid md_video entry then the video player would still start (and play the audio)
* Clearing a game in the metadata editor would sometimes not remove all media files (if there were both a .jpg and a .png for a certain file type)
* The tile property for the image element did not work correctly with SVG images

View file

@ -401,7 +401,7 @@ elseif(WIN32)
${CMAKE_CURRENT_SOURCE_DIR}/external/FreeImage/Dist/x64
${CMAKE_CURRENT_SOURCE_DIR}/external/freetype/include
${CMAKE_CURRENT_SOURCE_DIR}/external/pugixml/src
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL2-2.24.1)
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL2-2.26.1)
elseif(EMSCRIPTEN)
set(COMMON_INCLUDE_DIRS ${COMMON_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/external/curl/include
@ -450,7 +450,7 @@ if(APPLE)
${PROJECT_SOURCE_DIR}/libfreeimage.a
${PROJECT_SOURCE_DIR}/libfreetype.6.dylib
${PROJECT_SOURCE_DIR}/libpugixml.a
${PROJECT_SOURCE_DIR}/libSDL2-2.0.dylib)
${PROJECT_SOURCE_DIR}/libSDL2-2.0.0.dylib)
elseif(WIN32)
if(CMAKE_CXX_COMPILER_ID MATCHES MSVC)
set(COMMON_LIBRARIES ${PROJECT_SOURCE_DIR}/avcodec.lib

View file

@ -8,7 +8,7 @@ Table of contents:
[[_TOC_]]
## Development Environment
## Development environment
ES-DE is developed and compiled using Clang/LLVM and GCC on Unix, Clang/LLVM on macOS and MSVC and GCC (MinGW) on Windows.
@ -1165,6 +1165,8 @@ Wildcards are supported for emulator binaries, but not for directories:
<command>~/App*/yuzu*.AppImage %ROM%</command>
```
There is a special case when it comes to file extensions where it's possible to use extensionless files if required. To accomplish this simply add a dot (.) to the list of extensions in the `<extension>` tag. Obviously this makes it impossible to use the _directories interpreted as files_ functionality as there is no file extension, but apart from that everything should work the same as for regular files.
Keep in mind that you have to set up your emulators separately from ES-DE as the es_systems.xml file assumes that your emulator environment is properly configured.
Below is an overview of the file layout with various examples. For the command tag, the newer es_find_rules.xml logic described later in this document removes the need for most of the legacy options, but they are still supported for special configurations and for backward compatibility with old configuration files.
@ -1195,8 +1197,8 @@ Below is an overview of the file layout with various examples. For the command t
All subdirectories (and non-recursive links) will be included. -->
<path>%ROMPATH%/snes</path>
<!-- A list of extensions to search for, delimited by any of the whitespace characters (", \r\n\t").
The extensions are case sensitive and they must begin with a dot. -->
<!-- A list of extensions to search for, delimited by any of the whitespace characters (", \r\n\t"). Extensions are
case sensitive and they must begin with a dot. It's also possible to add just a dot to include extensionless files. -->
<extension>.smc .SMC .sfc .SFC .swc .SWC .fig .FIG .bs .BS .bin .BIN .mgd .MGD .7z .7Z .zip .ZIP</extension>
<!-- The command executed when a game is launched. Various variables are replaced if found for a command tag as explained below.
@ -1804,7 +1806,7 @@ In addition to this extra logging, a few key combinations are enabled when in de
**Ctrl + i**
This will draw a semi-transparent red frame behind all image and animation components. It will also draw a green frame around the carousel.
This will draw a semi-transparent red frame behind all image and animation components. It will also draw a green frame around the carousel and grid.
**Ctrl + t**

File diff suppressed because it is too large Load diff

View file

@ -481,8 +481,8 @@ ES-DE supports three separate modes, **Full**, **Kiosk** and **Kid**.
These modes mandate the functionalty provided by the application in the following way:
* Full - This is the default mode which enables all functionality.
* Kiosk - The main menu will be severely restricted, only displaying the entry to change the audio volume. The game options menu will be restricted as well, removing the metadata editor and the ability to modify custom game collections. And finally the ability to flag or unflag games as favorites will be removed. Apart from this all games will be playable.
* Kid - Only games marked as being suitable for children will be displayed (this flag is set manually per game using the metadata editor). Additionally, the game options menu is disabled as is the ability to flag and unflag games as favorites. There is also a separate option available to enable or disable the main menu when in Kid mode, see _Enable menu in kid mode_ for additional information.
* Kiosk - The main menu will be severely restricted, only displaying the entry to change the audio volume. The gamelist options menu will be restricted as well, removing the metadata editor and the ability to modify custom game collections. And finally the ability to flag or unflag games as favorites will be removed. Apart from this all games will be playable.
* Kid - Only games marked as being suitable for children will be displayed (this flag is set manually per game using the metadata editor). Additionally, the gamelist options menu is disabled as is the ability to flag and unflag games as favorites. There is also a separate option available to enable or disable the main menu when in Kid mode, see _Enable menu in kid mode_ for additional information.
There is an unlock code available to revert to the Full mode from the Kiosk or Kid mode, as is described when changing this setting from the main menu. By default the button sequence is **Up, Up, Down, Down, Left, Right, Left, Right, B, A** (or equivalent buttons if an Xbox controller is not used). Either the keyboard or a controller can be used to input the passkey sequence, but it can't be entered when a menu is open.
@ -506,12 +506,12 @@ Default keyboard mappings are shown in brackets.
**Up and down**\
_(Arrow up / Arrow down)_
Navigate up and down in gamelists, between systems in the system view (if the theme has a vertical carousel) and in menus.
Navigates between system and game entries where these buttons are applicable, such as for textlists and vertical carousels. Also used in menus for general navigation.
**Left and right**\
_(Arrow left / Arrow right)_
Navigate between gamelists (if the _Quick system select_ option has been enabled), between systems in the system view (if the theme has a horizontal carousel) and between media files in the media viewer. If the _Enable screensaver controls_ option has been enabled, either button also randomly selects a new game when using the Video or Slideshow screensavers.
Navigates between system and game entries where these buttons are applicable, such as for grids and horizontal carousels. Navigates between gamelists if the _Quick system select_ option has been set to use these buttons and the primary element supports it. Navigates between media files in the media viewer and selects a random game when using the _Video_ or _Slideshow_ screensavers if the _Enable screensaver controls_ option has been enabled. Also used in menus for general navigation.
**Start button**\
_(Escape)_
@ -521,17 +521,17 @@ Opens and closes the main menu.
**Back button**\
_(F1)_
Opens and closes the game options menu in the gamelist view, or toggles the screensaver in the system view (if the _Enable screensaver controls_ setting is enabled).
Opens and closes the gamelist options menu in the gamelist view.
**Left and right shoulder buttons**\
_(Page up / Page down)_
Provides quick jumping in gamelists and menus, jumps 10 games in the gamelists and 6 entries in the menus. Also jumps forward in text edit dialogs.
Provides quick jumping in textlists and menus, jumps 10 games in the gamelists and 6 entries in the menus. Navigates between gamelists if the _Quick system select_ option has been set to use these buttons and the primary element supports it. Also used as back and blankspace keys in text edit dialogs.
**Left and right trigger buttons**\
_(Home / End)_
Jumps to the first and last entry of the gamelists, menus and text edit dialogs.
Jumps to the first or last entries in carousels, grids and textlists as well as in menus and text edit dialogs. Navigates between gamelists if the _Quick system select_ option has been set to use these buttons and the primary element supports it.
**Left and right thumbstick click**\
_(F2 / F3)_
@ -541,7 +541,7 @@ Jumps to a random game or system depending on whether pressed when in the system
**A button**\
_(Enter)_
Opens gamelists from the system view, launches games, enters folders, selects menu entries etc.
Select button which opens gamelists from the system view, launches games, enters folders, selects menu entries etc.
**B button**\
_(Back key)_
@ -551,7 +551,7 @@ Back button, self explanatory.
**X button**\
_(Delete)_
Starts the game media viewer (which is accessible from the gamelist views). Used by some other minor functions as explained by the help system and/or this guide.
Starts the media viewer in the gamelist view or the screensaver in the system view (if the _Enable screensaver controls_ setting is enabled). Used by some other minor functions as explained by the help system and/or this guide.
**Y button**\
_(Insert on Unix and Windows, F13 on macOS)_
@ -1000,11 +1000,9 @@ Apart from the potential difficulty in locating the emulator binary, there are s
#### Nintendo Wii U
Cemu is available for Windows and Linux.
The .wua archive format is the preferred method to use for Wii U games, but the method of using unpacked games is also documented here for reference.
Some time ago Cemu added support for the .wua archive format which is much easier to use than the unpacked game format. Therefore this is now the recommended approach, but both this and the traditional method of adding unpacked games are covered here.
.wud and .wux files are also supported, but these two formats are not discussed here as the .wua format is clearly the way to go in the future.
.wud and .wux files are supported as well, but these two formats are not discussed here as the .wua format is clearly the way to go in the future.
**Method 1, using .wua files**
@ -1014,6 +1012,8 @@ Following this just start ES-DE and the game should be shown as a single entry t
**Method 2, unpacked games**
Only the setup on Windows is covered here, but it's the same principle in Linux and macOS.
Using this unpacked approach, the content of each game is divided into the three directories _code, content_ and _meta_.
The first step is to prepare the target directory in the `wiiu` ROMs directory, for this example we'll go for the game _Super Mario 3D World_. So simply create a directory with this name inside the wiiu folder. It should look something like the following:
@ -1915,7 +1915,6 @@ Here's an overview of what's supported when using these scrapers:
| Multi-language | Yes | No |
| Game names | Yes | Yes |
| Ratings | Yes | No |
| Controllers (arcade systems only) | Yes | No |
| Other game metadata | Yes | Yes |
| Videos | Yes | No |
| Screenshots | Yes | Yes |
@ -1931,8 +1930,6 @@ The category **Other game metadata** includes Description, Release date, Develop
The **Multi-language** support includes translated game genres and game descriptions for a number of languages.
**Controllers** is metadata indicating the controller type (it's not images of controllers).
**Physical media** means images of cartridges, diskettes, tapes, CD-ROMs etc. that were used to distribute the games.
There are two approaches to scraping, either for a single game from the metadata editor, or for multiple games and systems using the multi-scraper.
@ -1942,11 +1939,11 @@ _Here's an example of the multi-scraper running in interactive mode, asking the
### Single-game scraper
The single-game scraper is launched from the metadata editor. You navigate to a game, open the game options menu, choose **Edit this game's metadata** and then select the **Scrape** button (alternatively the "Y" button shortcut can be used to start the scraper).
The single-game scraper is launched from the metadata editor. You navigate to a game, open the gamelist options menu, choose **Edit this game's metadata** and then select the **Scrape** button (alternatively the _Y_ button shortcut can be used to start the scraper).
### Multi-scraper
The multi-scraper is accessed from the main menu by entering the **Scraper** menu and then selecting the **Start** button (or the "Y" button shortcut can be used).
The multi-scraper is accessed from the main menu by entering the **Scraper** menu and then selecting the **Start** button (or the _Y_ button shortcut can be used).
### Scraping process
@ -2103,10 +2100,6 @@ Whether to scrape the names of the games. This does not affect the actual files
Downloads game ratings.
**Controllers (arcade systems only)** _(ScreenScraper only)_
Scrapes controller metadata which is used to set the correct controller badge. This is only available for MAME arcade games, for systems such as _arcade_, _mame_, _neogeo_, _fba_ etc. This is so because ScreenScraper does not seem to provide controller information for other platforms. The type of controllers that are scraped are _joystick_ (separated into entries from no buttons up to 6 buttons), _steering wheel_, _flight stick_, _spinner_, _trackball_ and _lightgun_.
**Other metadata**
This includes the game description, release date, developer, publisher, genre and the number of players.
@ -2295,17 +2288,21 @@ Sets the view style to _Automatic, Basic, Detailed or Video_ for legacy themes.
Transition animation when navigating between gamelists, or between systems on the System view carousel. Can be set to _Slide, Fade_ or _Instant_. Only applicable for legacy themes as the newer type of theme sets lets the theme author define the transition animations in a more fine-grained manner. Therefore this option will be grayed out if a modern theme set has been selected.
**Quick system select**
The buttons to use to jump between systems in the gamelist view. The options are _Left/right or shoulders_, _Left/right or triggers_, _Shoulders_, _Triggers_, _Left/right_ or _Disabled_. The first two options will apply either left/right or shoulder/trigger buttons depending on the type of primary element used for the gamelist. For example a textlist or a vertical carousel will allow the use of the left and right buttons, but for horizontal carousels and grids these buttons are reserved for navigating the entries so instead the secondary buttons will be used, i.e. the shoulder or trigger buttons. Using these two options therefore leads to a slight inconsistency as different buttons will be used depending on the theme configuration. If instead using any of the single button pair options, i.e. _Shoulders_, _Triggers_ or _Left/right_, the navigation will be consistent regardless of theme configuration but you'll sacrifice the ability to use the selected buttons if the gamelist supports it, such as the ability to jump rows in a textlist using the shoulder and trigger buttons.
**Gamelist on startup**
If set to _None_, the system view will be displayed. Any other value will jump to that game system automatically on startup.
**Default sort order**
The order in which to sort your gamelists. This can be overriden per game system using the game options menu, but that override will only be persistent during the application session. The _System_ sorting can not be selected here as it's only applicable to collection systems.
The order in which to sort your gamelists. This can be overriden per game system using the gamelist options menu, but that override will only be persistent during the application session. The _System_ sorting can not be selected here as it's only applicable to collection systems.
**Menu opening effect**
Animation to play when opening the main menu or the game options menu. Also sets the animation for the game launch screen. Can be set to _Scale-up_ or _None_.
Animation to play when opening the main menu or the gamelist options menu. Also sets the animation for the game launch screen. Can be set to _Scale-up_ or _None_.
**Launch screen duration**
@ -2367,10 +2364,6 @@ This enables or disables the ability to jump to a random system or game. It's ma
Activating or deactivating the ability to filter your gamelists. This can normally be left enabled.
**Enable quick system select**
If enabled, it's possible to navigate between gamelists using the _Left_ and _Right_ buttons without having to first go back to the System view.
**Display on-screen help**
Activates or deactivates the built-in help system that provides contextual information regarding button usage.
@ -2663,14 +2656,14 @@ Self explanatory.
Self explanatory.
## Game options menu
## Gamelist options menu
This menu is opened from the gamelist view, and can't be accessed from the system view. The menu changes slightly depending on the context, for example if a game file or a folder is selected, or whether the current system is a collection or a normal game system.
You open the menu using the **Back** button, and by pressing **B** or selecting the **Apply** button any settings such as letter jumping using the quick selector or sorting changes are applied. If you instead press the Back button again or select the **Cancel** button, the menu is closed without applying any changes.
![alt text](images/es-de_game_options_menu.png "ES-DE Game Options Menu")
_The game options menu as laid out when opening it from within a custom collection, which adds the menu entry to add or remove games from the collection._
![alt text](images/es-de_game_options_menu.png "ES-DE Gamelist Options Menu")
_The gamelist options menu as laid out when opening it from within a custom collection, which adds the menu entry to add or remove games from the collection._
Here's a summary of the menu entries:
@ -2680,7 +2673,7 @@ This provides a quick selector for jumping to a certain letter. If the setting t
### Sort games by
This is the sort order for the gamelist. The default sorting shown here is taken from the setting _Default sort order_ under _UI settings_ in the main menu. Any sorting that is applied via the game options menu will be persistent throughout the program session, and it can be set individually per game system and per collection.
This is the sort order for the gamelist. The default sorting shown here is taken from the setting _Default sort order_ under _UI settings_ in the main menu. Any sorting that is applied via the gamelist options menu will be persistent throughout the program session, and it can be set individually per game system and per collection.
Sorting can be applied using the following metadata, in either ascending or descending order:
@ -2702,7 +2695,7 @@ The secondary sorting is always in ascending filename order.
Choosing this entry opens a separate screen where it's possible to apply a filter to the gamelist. The filter is persistent throughout the program session, or until it's manually reset. The option to reset all filters is shown on the same screen.
![alt text](images/es-de_gamelist_filters.png "ES-DE Gamelist Filters")
_The gamelist filter screen, accessed from the game options menu._
_The gamelist filter screen, accessed from the gamelist options menu._
The following filters can be applied:
@ -2775,7 +2768,7 @@ This is the name that will be shown when browsing the gamelist. If no sortname h
**Sortname** _(files only)_
This entry makes it possible to change the sorting of a game without having to change its name. For instance it can be used to sort _Mille Miglia_ as _1000 Miglia_ or _The Punisher_ as _Punisher, The_. Note that the _Jump to..._ quick selector on the game options menu will base its index on the first character of the sortname if it exists for a game, which could be slightly confusing in some instances when quick jumping in the gamelist. The _sortname_ entry also affects custom collections, although for these it's possible to override the value as described below. This entry only applies if the sort order has been set to _Filename, Ascending_ or _Filename, Descending_.
This entry makes it possible to change the sorting of a game without having to change its name. For instance it can be used to sort _Mille Miglia_ as _1000 Miglia_ or _The Punisher_ as _Punisher, The_. Note that the _Jump to..._ quick selector on the gamelist options menu will base its index on the first character of the sortname if it exists for a game, which could be slightly confusing in some instances when quick jumping in the gamelist. The _sortname_ entry also affects custom collections, although for these it's possible to override the value as described below. This entry only applies if the sort order has been set to _Filename, Ascending_ or _Filename, Descending_.
**Custom collections sortname** _(only visible when editing a game from within a custom collection)_
@ -2823,7 +2816,7 @@ A flag to mark whether the game is suitable for children. This will be applied a
**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-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.
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 files 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 for the entry will be lowered significantly to make it clear that it's a hidden entry.
**Broken/not working**
@ -2855,7 +2848,7 @@ If the option _Enable alternative emulators per game_ has been enabled, there wi
**Folder link** _(folders only)_
Using this option it's possible to link a specific file inside the game's folder structure that will be launched directly instead of entering the folder when pressing the _A_ button. This is very useful for systems where there are multiple files per game, such as multi-disc games where an .m3u file is used to launch the game. As the name implies this is only a link, and as folders can't be part of collections (the automatic collections _All games, Favorites_ and _Last played_ as well as any custom collections) it's the linked file inside the folder that is included in such collections. So for instance, launching a game via a linked folder will have the linked file show up in the _Last played_ collection rather than the folder itself. This also means that you should scrape the linked file in addition to the folder to be able to see game media and metadata throughout the application. To override the folder link and enter the directory, there is an entry available in the game options menu.
Using this option it's possible to link a specific file inside the game's folder structure that will be launched directly instead of entering the folder when pressing the _A_ button. This is very useful for systems where there are multiple files per game, such as multi-disc games where an .m3u file is used to launch the game. As the name implies this is only a link, and as folders can't be part of collections (the automatic collections _All games, Favorites_ and _Last played_ as well as any custom collections) it's the linked file inside the folder that is included in such collections. So for instance, launching a game via a linked folder will have the linked file show up in the _Last played_ collection rather than the folder itself. This also means that you should scrape the linked file in addition to the folder to be able to see game media and metadata throughout the application. To override the folder link and enter the directory, there is an entry available in the gamelist options menu.
### Buttons
@ -2901,7 +2894,7 @@ Numerous options can be set for these screensavers, as detailed [here](USERGUIDE
The Dim screensaver simply dims and desaturates the current view and Black will show a black screen. The Slideshow and Video screensavers are more interesting as they can display images and videos from your game collection. In addition to this, the Slideshow screensaver can be configured to only show images from a specified directory.
If the option _Enable screensaver controls_ has been activated, you can manually toggle the screensaver from the system view by pressing the _Back_ button. In addition to this, for the Slideshow and Video screensavers, the controls will allow you to jump to a new random image or video by using the _Left_ and _Right_ buttons on your keyboard or controller. It's also possible to launch the game currently displayed using the _A_ button, and the _Y_ button will jump to the game in its gamelist without starting it.
If the option _Enable screensaver controls_ has been activated, you can manually toggle the screensaver from the system view by pressing the _X_ button. In addition to this, for the Slideshow and Video screensavers, the controls will allow you to jump to a new random image or video by using the _Left_ and _Right_ buttons on your keyboard or controller. It's also possible to launch the game currently displayed using the _A_ button, and the _Y_ button will jump to the game in its gamelist without starting it.
For the video and slideshow screensavers, an overlay can be enabled via the screensaver options that displays the game name and the game system as well as a star symbol if the game is marked as a favorite.
@ -2944,9 +2937,9 @@ During the time that the collection is being edited, any game that is part of th
As well, when editing custom collections the _folder link_ configuration is disabled, making it possible to enter folders with such configuration just as if there were no folder links configured.
When you are done adding games, you can either open the main menu and go to **Game collection settings** and select the **Finish editing 'Platform' collection** or you can open the game options menu and select the same option there. The latter works from within any gamelist, so you don't need to first navigate back to the collection that you're editing.
When you are done adding games, you can either open the main menu and go to **Game collection settings** and select the **Finish editing 'Platform' collection** or you can open the gamelist options menu and select the same option there. The latter works from within any gamelist, so you don't need to first navigate back to the collection that you're editing.
You can later add additional games to the collection by navigating to it, bringing up the game options menu and choosing **Add/remove games to this game collection**.
You can later add additional games to the collection by navigating to it, bringing up the gamelist options menu and choosing **Add/remove games to this game collection**.
![alt text](images/es-de_custom_collections.png "ES-DE Custom Collections")
_Example of custom collections, here configured as genres._
@ -3071,7 +3064,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| android | Google Android | BlueStacks **(Standalone)** [W] | | No | Shortcut (.lnk) file in root folder |
| apple2 | Apple II | LinApple **(Standalone)** [U],<br>Mednafen **(Standalone)** [M],<br>AppleWin **(Standalone)** [W*] | Mednafen **(Standalone)** [UW*],<br>MAME **(Standalone)** [UMW*] | Yes for Mednafen and MAME | See the specific _Apple II_ section elsewhere in this guide |
| apple2gs | Apple IIGS | MAME **(Standalone)** [UMW*] | | Yes | See the specific _Apple IIGS_ section elsewhere in this guide |
| arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2000,<br>MAME **(Standalone)** [UMW*],<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Flycast,<br>Flycast **(Standalone)** [UMW*],<br>Kronos [UW],<br>Model 2 Emulator **(Standalone)** [W*],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],<br>Supermodel **(Standalone)** [UW*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| arcade | Arcade | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2000,<br>MAME **(Standalone)** [UMW*],<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Flycast,<br>Flycast **(Standalone)** [UMW*],<br>Kronos [UW],<br>Model 2 Emulator **(Standalone)** [W*],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],<br>Supermodel **(Standalone)** [UW*],<br>Supermodel [Fullscreen] **(Standalone)** [UW*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| astrocde | Bally Astrocade | MAME - Current | MAME **(Standalone)** [UMW*] | | See the specific _Bally Astrocade_ section elsewhere in this guide |
| atari2600 | Atari 2600 | Stella | Stella 2014,<br>ares **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder |
| atari5200 | Atari 5200 | a5200 | Atari800,<br>Atari800 **(Standalone)** [UMW*] | Yes | |
@ -3121,7 +3114,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| lutris | Lutris Open Gaming Platform | Lutris application **(Standalone)** [U] | | No | See the specific _Lutris_ section elsewhere in this guide |
| lutro | Lutro Game Engine | Lutro | | | |
| macintosh | Apple Macintosh | Basilisk II **(Standalone)** [UMW*] | SheepShaver **(Standalone)** [UMW*] | Yes | See the specific _Apple Macintosh_ section elsewhere in this guide |
| mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2000,<br>MAME **(Standalone)** [UMW*],<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Flycast,<br>Flycast **(Standalone)** [UMW*],<br>Kronos [UW],<br>Model 2 Emulator **(Standalone)** [W*],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],<br>Supermodel **(Standalone)** [UW*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| mame | Multiple Arcade Machine Emulator | MAME - Current | MAME 2010,<br>MAME 2003-Plus,<br>MAME 2000,<br>MAME **(Standalone)** [UMW*],<br>FinalBurn Neo,<br>FB Alpha 2012,<br>Flycast,<br>Flycast **(Standalone)** [UMW*],<br>Kronos [UW],<br>Model 2 Emulator **(Standalone)** [W*],<br>Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],<br>Supermodel **(Standalone)** [UW*],<br>Supermodel [Fullscreen] **(Standalone)** [UW*] | Depends | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| mame-advmame | AdvanceMAME | _Placeholder_ | | Depends | |
| mame-mame4all | MAME4ALL | _Placeholder_ | | Depends | |
| mastersystem | Sega Master System | Genesis Plus GX | Genesis Plus GX Wide,<br>SMS Plus GX,<br>Gearsystem,<br>PicoDrive,<br>Mednafen **(Standalone)** [UMW*],<br>ares **(Standalone)** [UMW*] | No | Single archive or ROM file in root folder |
@ -3131,7 +3124,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| megaduck | Creatronic Mega Duck | SameDuck | | No | Single archive or ROM file in root folder |
| mess | Multi Emulator Super System | MESS 2015 | | | |
| model2 | Sega Model 2 | Model 2 Emulator **(Standalone)** [W*],<br>MAME - Current [UM] | Model 2 Emulator [Suspend ES-DE] **(Standalone)** [W*],<br>MAME - Current [W],<br>MAME **(Standalone)** [UMW*] | Yes for MAME | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| model3 | Sega Model 3 | Supermodel **(Standalone)** [UW*] | | No | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| model3 | Sega Model 3 | Supermodel **(Standalone)** [UW*] | Supermodel [Fullscreen] **(Standalone)** [UW*] | No | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
| moonlight | Moonlight Game Streaming | _Placeholder_ | | | |
| moto | Thomson MO/TO Series | Theodore | | | |
| msx | MSX | blueMSX | fMSX,<br>openMSX **(Standalone)** [UMW*],<br>openMSX No Machine **(Standalone)** [UMW*],<br>ares **(Standalone)** [UMW*] | Yes | |
@ -3143,7 +3136,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| naomi | Sega NAOMI | Flycast | Flycast **(Standalone)** [UMW*] | | |
| naomigd | Sega NAOMI GD-ROM | Flycast | Flycast **(Standalone)** [UMW*] | | |
| n3ds | Nintendo 3DS | Citra [UW],<br>Citra **(Standalone)** [M] | Citra 2018 [UW],<br>Citra **(Standalone)** [UW*] | No | Single ROM file in root folder |
| n64 | Nintendo 64 | Mupen64Plus-Next [UW],<br>ParaLLEl N64 [M] | Mupen64Plus **(Standalone)** [UMW*],<br>ParaLLEl N64 [UW],<br>simple64 **(Standalone)** [UW*],<br>Rosalie's Mupen GUI **(Standalone)** [U],<br>Project64 **(Standalone)** [W*],<br>ares **(Standalone)** [UMW*],<br>sixtyforce **(Standalone)** [M] | No | Single archive or ROM file in root folder |
| n64 | Nintendo 64 | Mupen64Plus-Next [UW],<br>ParaLLEl N64 [M] | Mupen64Plus **(Standalone)** [UMW*],<br>ParaLLEl N64 [UW],<br>simple64 **(Standalone)** [UW*],<br>Rosalie's Mupen GUI **(Standalone)** [UW],<br>Project64 **(Standalone)** [W*],<br>ares **(Standalone)** [UMW*],<br>sixtyforce **(Standalone)** [M] | No | Single archive or ROM file in root folder |
| n64dd | Nintendo 64DD | ParaLLEl N64 | Mupen64Plus-Next [UW] | Yes | See the specific _Nintendo 64DD_ section elsewhere in this guide |
| nds | Nintendo DS | DeSmuME | DeSmuME 2015,<br>DeSmuME **(Standalone)** [U],<br>melonDS,<br>melonDS **(Standalone)** [UMW*] | No | |
| neogeo | SNK Neo Geo | FinalBurn Neo | FinalBurn Neo **(Standalone)** [UW*],<br>MAME **(Standalone)** [UMW*] | Yes | See the specific _Arcade and Neo Geo_ section elsewhere in this guide |
@ -3207,7 +3200,7 @@ The **@** symbol indicates that the emulator is _deprecated_ and will be removed
| videopac | Philips Videopac G7000 | O2EM | | | |
| virtualboy | Nintendo Virtual Boy | Beetle VB | Mednafen **(Standalone)** [UMW*] | No | |
| wii | Nintendo Wii | Dolphin | Dolphin **(Standalone)** [UMW*],<br>PrimeHack **(Standalone)** [UW*] | No | |
| wiiu | Nintendo Wii U | Cemu **(Standalone)** [UW*] | | No | See the specific _Nintendo Wii U_ section elsewhere in this guide |
| wiiu | Nintendo Wii U | Cemu **(Standalone)** [UMW*] | | No | See the specific _Nintendo Wii U_ section elsewhere in this guide |
| wonderswan | Bandai WonderSwan | Beetle Cygne | Mednafen **(Standalone)** [UMW*],<br>ares **(Standalone)** [UMW*] | No | |
| wonderswancolor | Bandai WonderSwan Color | Beetle Cygne | Mednafen **(Standalone)** [UMW*],<br>ares **(Standalone)** [UMW*] | No | |
| x1 | Sharp X1 | x1 | | | Single archive or ROM file in root folder |

View file

@ -209,7 +209,7 @@ elseif(APPLE)
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libfreetype.6.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/libSDL2-2.0.dylib
install(FILES ${CMAKE_SOURCE_DIR}/libSDL2-2.0.0.dylib
PERMISSIONS ${APPLE_DYLIB_PERMISSIONS} DESTINATION ../MacOS)
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ../Resources)

View file

@ -64,6 +64,7 @@ Emulators\PPSSPP\PPSSPPWindows64.exe
Emulators\PrimeHack\Dolphin.exe
Emulators\Project64\Project64.exe
Emulators\redream\redream.exe
Emulators\RMG\RMG.exe
Emulators\RPCS3\rpcs3.exe
Emulators\ruffle\ruffle.exe
Emulators\ryujinx\Ryujinx.exe

View file

@ -40,6 +40,7 @@ FileData::FileData(FileType type,
, mEnvData {envData}
, mSystem {system}
, mOnlyFolders {false}
, mHasFolders {false}
, mUpdateChildrenLastPlayed {false}
, mUpdateChildrenMostPlayed {false}
, mDeletionFlag {false}
@ -76,7 +77,7 @@ FileData::~FileData()
std::string FileData::getDisplayName() const
{
std::string stem {Utils::FileSystem::getStem(mPath)};
const std::string& stem {Utils::FileSystem::getStem(mPath)};
return stem;
}
@ -167,7 +168,7 @@ const std::vector<FileData*> FileData::getChildrenRecursive() const
const std::string FileData::getROMDirectory()
{
std::string romDirSetting {Settings::getInstance()->getString("ROMDirectory")};
const std::string& romDirSetting {Settings::getInstance()->getString("ROMDirectory")};
std::string romDirPath;
if (romDirSetting == "") {
@ -196,7 +197,7 @@ const std::string FileData::getROMDirectory()
const std::string FileData::getMediaDirectory()
{
std::string mediaDirSetting {Settings::getInstance()->getString("MediaDirectory")};
const std::string& mediaDirSetting {Settings::getInstance()->getString("MediaDirectory")};
std::string mediaDirPath;
if (mediaDirSetting == "") {
@ -233,7 +234,7 @@ const std::string FileData::getMediafilePath(const std::string& subdirectory) co
subFolders + "/" + getDisplayName()};
// Look for an image file in the media directory.
for (size_t i = 0; i < extList.size(); ++i) {
for (size_t i {0}; i < extList.size(); ++i) {
std::string mediaPath {tempPath + extList[i]};
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
@ -332,7 +333,7 @@ const std::string FileData::getVideoPath() const
getDisplayName()};
// Look for media in the media directory.
for (size_t i = 0; i < extList.size(); ++i) {
for (size_t i {0}; i < extList.size(); ++i) {
std::string mediaPath {tempPath + extList[i]};
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
@ -424,7 +425,7 @@ std::vector<FileData*> FileData::getScrapeFilesRecursive(bool includeFolders,
const bool FileData::isArcadeAsset() const
{
const std::string stem {Utils::FileSystem::getStem(mPath)};
const std::string& stem {Utils::FileSystem::getStem(mPath)};
return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) &&
(MameNames::getInstance().isBios(stem) || MameNames::getInstance().isDevice(stem)));
@ -432,7 +433,7 @@ const bool FileData::isArcadeAsset() const
const bool FileData::isArcadeGame() const
{
const std::string stem {Utils::FileSystem::getStem(mPath)};
const std::string& stem {Utils::FileSystem::getStem(mPath)};
return ((mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
mSystem->hasPlatformId(PlatformIds::SNK_NEO_GEO))) &&
(!MameNames::getInstance().isBios(stem) && !MameNames::getInstance().isDevice(stem)));
@ -444,7 +445,7 @@ void FileData::addChild(FileData* file)
if (!mSystem->getFlattenFolders())
assert(file->getParent() == nullptr);
const std::string key = file->getKey();
const std::string& key {file->getKey()};
if (mChildrenByFilename.find(key) == mChildrenByFilename.cend()) {
mChildrenByFilename[key] = file;
mChildren.emplace_back(file);
@ -481,7 +482,7 @@ void FileData::sort(ComparisonFunction& comparator,
std::vector<FileData*> mChildrenOthers;
if (mSystem->isGroupedCustomCollection())
gameCount = {};
gameCount = {0, 0};
if (!showHiddenGames) {
for (auto it = mChildren.begin(); it != mChildren.end();) {
@ -505,20 +506,20 @@ void FileData::sort(ComparisonFunction& comparator,
// The main custom collections view is sorted during startup in CollectionSystemsManager.
// The individual collections are however sorted as any normal systems/folders.
if (mSystem->isCollection() && mSystem->getFullName() == "collections") {
std::pair<unsigned int, unsigned int> tempGameCount {};
std::pair<unsigned int, unsigned int> tempGameCount {0, 0};
for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it) {
if ((*it)->getChildren().size() > 0)
(*it)->sort(comparator, gameCount);
tempGameCount.first += gameCount.first;
tempGameCount.second += gameCount.second;
gameCount = {};
gameCount = {0, 0};
}
gameCount = tempGameCount;
return;
}
if (foldersOnTop) {
for (unsigned int i = 0; i < mChildren.size(); ++i) {
for (unsigned int i {0}; i < mChildren.size(); ++i) {
if (mChildren[i]->getType() == FOLDER) {
mChildrenFolders.emplace_back(mChildren[i]);
}
@ -538,7 +539,7 @@ void FileData::sort(ComparisonFunction& comparator,
getSortTypeFromString("filename, ascending").comparisonFunction);
}
if (foldersOnTop && mOnlyFolders)
if (foldersOnTop)
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
@ -597,24 +598,24 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator,
std::vector<FileData*> mChildrenOthers;
if (mSystem->isGroupedCustomCollection())
gameCount = {};
gameCount = {0, 0};
// The main custom collections view is sorted during startup in CollectionSystemsManager.
// The individual collections are however sorted as any normal systems/folders.
if (mSystem->isCollection() && mSystem->getFullName() == "collections") {
std::pair<unsigned int, unsigned int> tempGameCount = {};
std::pair<unsigned int, unsigned int> tempGameCount = {0, 0};
for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it) {
if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, gameCount);
tempGameCount.first += gameCount.first;
tempGameCount.second += gameCount.second;
gameCount = {};
gameCount = {0, 0};
}
gameCount = tempGameCount;
return;
}
for (unsigned int i = 0; i < mChildren.size(); ++i) {
for (unsigned int i {0}; i < mChildren.size(); ++i) {
// If the option to hide hidden games has been set and the game is hidden,
// then skip it. Normally games are hidden during loading of the gamelists in
// Gamelist::parseGamelist() and this code should only run when a user has marked
@ -685,7 +686,7 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator,
}
// Sort favorite games and the other games separately.
if (foldersOnTop && mOnlyFolders) {
if (foldersOnTop) {
std::stable_sort(mChildrenFavoritesFolders.begin(), mChildrenFavoritesFolders.end(),
comparator);
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
@ -745,7 +746,7 @@ void FileData::countGames(std::pair<unsigned int, unsigned int>& gameCount)
bool isKidMode {(Settings::getInstance()->getString("UIMode") == "kid" ||
Settings::getInstance()->getBool("ForceKid"))};
for (unsigned int i = 0; i < mChildren.size(); ++i) {
for (unsigned int i {0}; i < mChildren.size(); ++i) {
if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) {
if (!isKidMode || (isKidMode && mChildren[i]->getKidgame())) {
++gameCount.first;
@ -798,9 +799,9 @@ void FileData::updateMostPlayedList()
const FileData::SortType& FileData::getSortTypeFromString(const std::string& desc) const
{
std::vector<FileData::SortType> SortTypes = FileSorts::SortTypes;
std::vector<FileData::SortType> SortTypes {FileSorts::SortTypes};
for (unsigned int i = 0; i < FileSorts::SortTypes.size(); ++i) {
for (unsigned int i {0}; i < FileSorts::SortTypes.size(); ++i) {
const FileData::SortType& sort {FileSorts::SortTypes.at(i)};
if (sort.description == desc)
return sort;
@ -983,11 +984,11 @@ void FileData::launchGame()
command = Utils::FileSystem::expandHomePath(command);
// Check that the emulator binary actually exists, and if so, get its path.
std::string binaryPath {findEmulatorPath(command)};
const std::string& binaryPath {findEmulatorPath(command)};
// Hack to show an error message if there was no emulator entry in es_find_rules.xml.
if (binaryPath.substr(0, 18) == "NO EMULATOR RULE: ") {
std::string emulatorEntry {binaryPath.substr(18, binaryPath.size() - 18)};
const std::string& emulatorEntry {binaryPath.substr(18, binaryPath.size() - 18)};
LOG(LogError) << "Couldn't launch game, either there is no emulator entry for \""
<< emulatorEntry << "\" in es_find_rules.xml or there are no rules defined";
LOG(LogError) << "Raw emulator launch command:";
@ -1051,7 +1052,7 @@ void FileData::launchGame()
quotationMarkPos =
static_cast<unsigned int>(command.find("\"", emuPathPos + 9) - emuPathPos);
}
size_t spacePos {command.find(" ", emuPathPos + quotationMarkPos)};
const size_t spacePos {command.find(" ", emuPathPos + quotationMarkPos)};
std::string coreRaw;
std::string coreFile;
if (spacePos != std::string::npos) {
@ -1120,7 +1121,7 @@ void FileData::launchGame()
// If a %CORE_ find rule entry is used in es_systems.xml for this system, then try to find
// the emulator core using the rules defined in es_find_rules.xml.
for (std::string path : emulatorCorePaths) {
for (std::string& path : emulatorCorePaths) {
// The position of the %CORE_ variable could have changed as there may have been an
// %EMULATOR_ variable that was substituted for the actual emulator binary.
coreEntryPos = command.find("%CORE_");
@ -1210,7 +1211,7 @@ void FileData::launchGame()
}
std::string startDirectory;
size_t startDirPos {command.find("%STARTDIR%")};
const size_t startDirPos {command.find("%STARTDIR%")};
if (startDirPos != std::string::npos) {
bool invalidEntry {false};
@ -1233,7 +1234,7 @@ void FileData::launchGame()
}
}
else if (!invalidEntry) {
size_t spacePos {command.find(" ", startDirPos)};
const size_t spacePos {command.find(" ", startDirPos)};
if (spacePos != std::string::npos) {
startDirectory = command.substr(startDirPos + 11, spacePos - startDirPos - 11);
command = command.replace(startDirPos, spacePos - startDirPos + 1, "");
@ -1331,7 +1332,7 @@ void FileData::launchGame()
}
}
else if (!invalidEntry) {
size_t spacePos {command.find(" ", injectPos)};
const size_t spacePos {command.find(" ", injectPos)};
if (spacePos != std::string::npos) {
injectFile = command.substr(injectPos + 9, spacePos - injectPos - 9);
command = command.replace(injectPos, spacePos - injectPos + 1, "");
@ -1407,8 +1408,8 @@ void FileData::launchGame()
// The special characters need to be procesed in this order.
std::string specialCharacters {"^&()=;,"};
for (size_t i = 0; i < specialCharacters.size(); ++i) {
std::string special(1, specialCharacters[i]);
for (size_t i {0}; i < specialCharacters.size(); ++i) {
const std::string& special {1, specialCharacters[i]};
if (romPath.find(special) != std::string::npos) {
romPath = Utils::String::replace(romPath, special, "^" + special);
foundSpecial = true;
@ -1701,15 +1702,15 @@ const std::string FileData::findEmulatorPath(std::string& command)
return "NO EMULATOR RULE: " + emulatorEntry;
#if defined(_WIN64)
for (std::string path : emulatorWinRegistryPaths) {
for (std::string& path : emulatorWinRegistryPaths) {
// Search for the emulator using the App Paths keys in the Windows Registry.
std::string registryKeyPath {"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +
path};
const std::string& registryKeyPath {
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + path};
HKEY registryKey;
LSTATUS keyStatus {-1};
LSTATUS pathStatus {-1};
char registryPath[1024] {};
char registryPath[1024] {0};
DWORD pathSize {1024};
// First look in HKEY_CURRENT_USER.
@ -1746,11 +1747,11 @@ const std::string FileData::findEmulatorPath(std::string& command)
RegCloseKey(registryKey);
}
for (std::string value : emulatorWinRegistryValues) {
for (std::string& value : emulatorWinRegistryValues) {
// If the pipe character is found, then the string following this should be appended
// to the key value, assuming the key is found.
std::string appendString;
size_t pipePos {value.find('|')};
const size_t pipePos {value.find('|')};
if (pipePos != std::string::npos) {
appendString = value.substr(pipePos + 1, std::string::npos);
@ -1758,14 +1759,14 @@ const std::string FileData::findEmulatorPath(std::string& command)
}
// Search for the defined value in the Windows Registry.
std::string registryValueKey {
const std::string& registryValueKey {
Utils::String::replace(Utils::FileSystem::getParent(value), "/", "\\")};
std::string registryValue {Utils::FileSystem::getFileName(value)};
const std::string& registryValue {Utils::FileSystem::getFileName(value)};
HKEY registryKey;
LSTATUS keyStatus {-1};
LSTATUS pathStatus {-1};
char path[1024] {};
char path[1024] {0};
DWORD pathSize {1024};
// First look in HKEY_CURRENT_USER.
@ -1811,7 +1812,7 @@ const std::string FileData::findEmulatorPath(std::string& command)
}
#endif
for (std::string path : emulatorSystemPaths) {
for (std::string& path : emulatorSystemPaths) {
#if defined(_WIN64)
std::wstring pathWide {Utils::String::stringToWideString(path)};
// Search for the emulator using the PATH environment variable.
@ -1846,11 +1847,11 @@ const std::string FileData::findEmulatorPath(std::string& command)
#endif
}
for (std::string path : emulatorStaticPaths) {
for (std::string& path : emulatorStaticPaths) {
// If a pipe character is present in the staticpath entry it means we should substitute
// the emulator binary with whatever is defined after the pipe character.
std::string replaceCommand;
size_t pipePos {path.find('|')};
const size_t pipePos {path.find('|')};
if (pipePos != std::string::npos) {
replaceCommand = path.substr(pipePos + 1);
@ -1899,7 +1900,7 @@ const std::string FileData::findEmulatorPath(std::string& command)
// If the first character is a quotation mark, then we need to extract up to the
// next quotation mark, otherwise we'll only extract up to the first space character.
if (command.front() == '\"') {
std::string emuTemp {command.substr(1, std::string::npos)};
const std::string& emuTemp {command.substr(1, std::string::npos)};
emuExecutable = emuTemp.substr(0, emuTemp.find('"'));
}
else {
@ -1917,7 +1918,8 @@ const std::string FileData::findEmulatorPath(std::string& command)
#if defined(_WIN64)
std::wstring emuExecutableWide {Utils::String::stringToWideString(emuExecutable)};
// Search for the emulator using the PATH environment variable.
DWORD size {SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", 0, nullptr, nullptr)};
const DWORD size {
SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", 0, nullptr, nullptr)};
if (size) {
std::vector<wchar_t> pathBuffer(static_cast<size_t>(size) + 1);

View file

@ -32,20 +32,33 @@ namespace GamelistFileParser
return nullptr;
}
Utils::FileSystem::StringList pathList = Utils::FileSystem::getPathList(relative);
const Utils::FileSystem::StringList& pathList {Utils::FileSystem::getPathList(relative)};
auto path_it = pathList.begin();
FileData* treeNode = root;
bool found = false;
while (path_it != pathList.end()) {
const std::unordered_map<std::string, FileData*>& children =
treeNode->getChildrenByFilename();
FileData* treeNode {root};
bool found {false};
std::string key = *path_it;
found = children.find(key) != children.cend();
if (found) {
treeNode = children.at(key);
while (path_it != pathList.end()) {
// Workaround for an extremely rare issue that can basically only happen if a dot (.)
// has been defined as a valid extension for the system (meaning extensionless files
// are loaded), in combination with the "Only show ROMs from gamelist.xml files" option
// being enabled and a stale entry being present in the gamelist.xml file that perfectly
// matches a folder which is actually in use. The workaround is not a perfect solution
// but it at least prevents the application from crashing.
if (treeNode->getType() != FOLDER) {
LOG(LogWarning)
<< "Invalid gamelist entry caused by folder having the same name as a stale "
<< "extensionless game file (this may cause undefined behavior):";
return nullptr;
}
const std::unordered_map<std::string, FileData*>& children {
treeNode->getChildrenByFilename()};
const std::string key {*path_it};
found = children.find(key) != children.cend();
if (found)
treeNode = children.at(key);
// This is the end.
if (path_it == --pathList.end()) {
if (found)
@ -58,8 +71,8 @@ namespace GamelistFileParser
// Handle the special situation where a file exists and has an entry in the
// gamelist.xml file but the file extension is not configured in es_systems.xml.
const std::vector<std::string> extensions =
system->getSystemEnvData()->mSearchExtensions;
const std::vector<std::string>& extensions {
system->getSystemEnvData()->mSearchExtensions};
if (std::find(extensions.cbegin(), extensions.cend(),
Utils::FileSystem::getExtension(path)) == extensions.cend()) {
@ -69,7 +82,7 @@ namespace GamelistFileParser
return nullptr;
}
FileData* file = new FileData(type, path, system->getSystemEnvData(), system);
FileData* file {new FileData(type, path, system->getSystemEnvData(), system)};
// Skipping arcade assets from gamelist.
if (!file->isArcadeAsset())
@ -87,9 +100,8 @@ namespace GamelistFileParser
if (!system->getFlattenFolders()) {
// Create missing folder.
FileData* folder {new FileData(
FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it,
system->getSystemEnvData(), system)};
FileData* folder {new FileData(FOLDER, treeNode->getPath() + "/" + *path_it,
system->getSystemEnvData(), system)};
treeNode->addChild(folder);
treeNode = folder;
}
@ -103,8 +115,8 @@ namespace GamelistFileParser
void parseGamelist(SystemData* system)
{
bool trustGamelist = Settings::getInstance()->getBool("ParseGamelistOnly");
std::string xmlpath = system->getGamelistPath(false);
const bool trustGamelist {Settings::getInstance()->getBool("ParseGamelistOnly")};
const std::string& xmlpath {system->getGamelistPath(false)};
if (!Utils::FileSystem::exists(xmlpath)) {
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \"" << system->getName()
@ -121,10 +133,10 @@ namespace GamelistFileParser
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result result =
doc.load_file(Utils::String::stringToWideString(xmlpath).c_str());
const pugi::xml_parse_result& result {
doc.load_file(Utils::String::stringToWideString(xmlpath).c_str())};
#else
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
const pugi::xml_parse_result& result {doc.load_file(xmlpath.c_str())};
#endif
if (!result) {
@ -133,18 +145,18 @@ namespace GamelistFileParser
return;
}
pugi::xml_node root = doc.child("gameList");
const pugi::xml_node& root {doc.child("gameList")};
if (!root) {
LOG(LogError) << "Couldn't find <gameList> node in gamelist \"" << xmlpath << "\"";
return;
}
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
const pugi::xml_node& alternativeEmulator {doc.child("alternativeEmulator")};
if (alternativeEmulator) {
std::string label = alternativeEmulator.child("label").text().get();
const std::string& label {alternativeEmulator.child("label").text().get()};
if (label != "") {
bool validLabel = false;
for (auto command : system->getSystemEnvData()->mLaunchCommands) {
bool validLabel {false};
for (auto& command : system->getSystemEnvData()->mLaunchCommands) {
if (command.second == label)
validLabel = true;
}
@ -165,18 +177,19 @@ namespace GamelistFileParser
}
}
std::string relativeTo = system->getStartPath();
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
const std::string& relativeTo {system->getStartPath()};
const bool showHiddenFiles {Settings::getInstance()->getBool("ShowHiddenFiles")};
std::vector<std::string> tagList = {"game", "folder"};
FileType typeList[2] = {GAME, FOLDER};
for (int i = 0; i < 2; ++i) {
std::string tag = tagList[i];
FileType type = typeList[i];
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
const std::vector<std::string> tagList {"game", "folder"};
const FileType typeList[2] = {GAME, FOLDER};
for (int i {0}; i < 2; ++i) {
std::string tag {tagList[i]};
FileType type {typeList[i]};
for (pugi::xml_node fileNode {root.child(tag.c_str())}; fileNode;
fileNode = fileNode.next_sibling(tag.c_str())) {
const std::string path = Utils::FileSystem::resolveRelativePath(
fileNode.child("path").text().get(), relativeTo, false);
const std::string& path {Utils::FileSystem::resolveRelativePath(
fileNode.child("path").text().get(), relativeTo, false)};
if (!trustGamelist && !Utils::FileSystem::exists(path)) {
#if defined(_WIN64)
@ -199,7 +212,7 @@ namespace GamelistFileParser
continue;
}
FileData* file = findOrCreateFile(system, path, type);
FileData* file {findOrCreateFile(system, path, type)};
// Don't load entries with the wrong type. This should very rarely (if ever) happen.
if (file != nullptr && ((tag == "game" && file->getType() == FOLDER) ||
@ -214,7 +227,7 @@ namespace GamelistFileParser
continue;
}
else if (!file->isArcadeAsset()) {
std::string defaultName = file->metadata.get("name");
const std::string& defaultName {file->metadata.get("name")};
if (file->getType() == FOLDER) {
file->metadata =
MetaDataList::createFromXML(FOLDER_METADATA, fileNode, relativeTo);
@ -265,7 +278,7 @@ namespace GamelistFileParser
SystemData* system)
{
// Create game and add to parent node.
pugi::xml_node newNode = parent.append_child(tag.c_str());
pugi::xml_node newNode {parent.append_child(tag.c_str())};
// Write metadata.
file->metadata.appendToXML(newNode, true, system->getStartPath());
@ -303,17 +316,17 @@ namespace GamelistFileParser
pugi::xml_document doc;
pugi::xml_node root;
std::string xmlReadPath = system->getGamelistPath(false);
bool hasAlternativeEmulatorTag = false;
const std::string& xmlReadPath {system->getGamelistPath(false)};
bool hasAlternativeEmulatorTag {false};
if (Utils::FileSystem::exists(xmlReadPath)) {
// Parse an existing file first.
#if defined(_WIN64)
pugi::xml_parse_result result =
doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str());
const pugi::xml_parse_result& result {
doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str())};
#else
pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str());
const pugi::xml_parse_result& result {doc.load_file(xmlReadPath.c_str())};
#endif
if (!result) {
@ -329,7 +342,7 @@ namespace GamelistFileParser
return;
}
if (updateAlternativeEmulator) {
pugi::xml_node alternativeEmulator = doc.child("alternativeEmulator");
pugi::xml_node alternativeEmulator {doc.child("alternativeEmulator")};
if (alternativeEmulator)
hasAlternativeEmulatorTag = true;
@ -340,7 +353,7 @@ namespace GamelistFileParser
alternativeEmulator = doc.child("alternativeEmulator");
}
pugi::xml_node label = alternativeEmulator.child("label");
const pugi::xml_node& label {alternativeEmulator.child("label")};
if (label && system->getAlternativeEmulator() !=
alternativeEmulator.child("label").text().get()) {
@ -360,7 +373,7 @@ namespace GamelistFileParser
}
else {
if (updateAlternativeEmulator && system->getAlternativeEmulator() != "") {
pugi::xml_node alternativeEmulator = doc.prepend_child("alternativeEmulator");
pugi::xml_node alternativeEmulator {doc.prepend_child("alternativeEmulator")};
alternativeEmulator.prepend_child("label").text().set(
system->getAlternativeEmulator().c_str());
}
@ -372,14 +385,14 @@ namespace GamelistFileParser
// through all our games and add the information from there.
FileData* rootFolder {system->getRootFolder()};
if (rootFolder != nullptr) {
int numUpdated = 0;
int numUpdated {0};
// Get only files, no folders.
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
std::vector<FileData*> files {rootFolder->getFilesRecursive(GAME | FOLDER)};
// Iterate through all files, checking if they're already in the XML file.
for (std::vector<FileData*>::const_iterator fit = files.cbegin(); // Line break.
for (std::vector<FileData*>::const_iterator fit {files.cbegin()}; // Line break.
fit != files.cend(); ++fit) {
const std::string tag = ((*fit)->getType() == GAME) ? "game" : "folder";
const std::string& tag {((*fit)->getType() == GAME) ? "game" : "folder"};
// Do not touch if it wasn't changed and is not flagged for deletion.
if (!(*fit)->metadata.wasChanged() && !(*fit)->getDeletionFlag())
@ -387,18 +400,19 @@ namespace GamelistFileParser
// Check if the file already exists in the XML file.
// If it does, remove the entry before adding it back.
for (pugi::xml_node fileNode = root.child(tag.c_str()); fileNode;
for (pugi::xml_node fileNode {root.child(tag.c_str())}; fileNode;
fileNode = fileNode.next_sibling(tag.c_str())) {
pugi::xml_node pathNode = fileNode.child("path");
const pugi::xml_node& pathNode {fileNode.child("path")};
if (!pathNode) {
LOG(LogError) << "<" << tag << "> node contains no <path> child";
continue;
}
std::string nodePath =
const std::string& nodePath {
Utils::FileSystem::getCanonicalPath(Utils::FileSystem::resolveRelativePath(
pathNode.text().get(), system->getStartPath(), true));
std::string gamePath = Utils::FileSystem::getCanonicalPath((*fit)->getPath());
pathNode.text().get(), system->getStartPath(), true))};
const std::string& gamePath {
Utils::FileSystem::getCanonicalPath((*fit)->getPath())};
if (nodePath == gamePath) {
// Found it
@ -420,7 +434,7 @@ namespace GamelistFileParser
// Now write the file.
if (numUpdated > 0 || updateAlternativeEmulator) {
// Make sure the folders leading up to this path exist (or the write will fail).
std::string xmlWritePath(system->getGamelistPath(true));
const std::string& xmlWritePath {system->getGamelistPath(true)};
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
if (updateAlternativeEmulator) {

View file

@ -40,8 +40,8 @@ FindRules::FindRules()
void FindRules::loadFindRules()
{
std::string customSystemsDirectory {Utils::FileSystem::getHomePath() +
"/.emulationstation/custom_systems"};
const std::string& customSystemsDirectory {Utils::FileSystem::getHomePath() +
"/.emulationstation/custom_systems"};
std::string path {customSystemsDirectory + "/es_find_rules.xml"};
@ -75,9 +75,10 @@ void FindRules::loadFindRules()
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str());
const pugi::xml_parse_result& res {
doc.load_file(Utils::String::stringToWideString(path).c_str())};
#else
pugi::xml_parse_result res = doc.load_file(path.c_str());
const pugi::xml_parse_result& res {doc.load_file(path.c_str())};
#endif
if (!res) {
@ -86,7 +87,7 @@ void FindRules::loadFindRules()
}
// Actually read the file.
pugi::xml_node ruleList = doc.child("ruleList");
const pugi::xml_node& ruleList {doc.child("ruleList")};
if (!ruleList) {
LOG(LogError) << "es_find_rules.xml is missing the <ruleList> tag";
@ -96,9 +97,9 @@ void FindRules::loadFindRules()
EmulatorRules emulatorRules;
CoreRules coreRules;
for (pugi::xml_node emulator = ruleList.child("emulator"); emulator;
for (pugi::xml_node emulator {ruleList.child("emulator")}; emulator;
emulator = emulator.next_sibling("emulator")) {
std::string emulatorName = emulator.attribute("name").as_string();
const std::string& emulatorName {emulator.attribute("name").as_string()};
if (emulatorName.empty()) {
LOG(LogWarning) << "Found emulator tag without name attribute, skipping entry";
continue;
@ -109,7 +110,7 @@ void FindRules::loadFindRules()
continue;
}
for (pugi::xml_node rule = emulator.child("rule"); rule; rule = rule.next_sibling("rule")) {
std::string ruleType = rule.attribute("type").as_string();
const std::string& ruleType {rule.attribute("type").as_string()};
if (ruleType.empty()) {
LOG(LogWarning) << "Found rule tag without type attribute for emulator \""
<< emulatorName << "\", skipping entry";
@ -125,18 +126,18 @@ void FindRules::loadFindRules()
<< emulatorName << "\", skipping entry";
continue;
}
for (pugi::xml_node entry = rule.child("entry"); entry;
for (pugi::xml_node entry {rule.child("entry")}; entry;
entry = entry.next_sibling("entry")) {
std::string entryValue = entry.text().get();
const std::string& entryValue {entry.text().get()};
if (ruleType == "systempath")
emulatorRules.systemPaths.push_back(entryValue);
emulatorRules.systemPaths.emplace_back(entryValue);
else if (ruleType == "staticpath")
emulatorRules.staticPaths.push_back(entryValue);
emulatorRules.staticPaths.emplace_back(entryValue);
#if defined(_WIN64)
else if (ruleType == "winregistrypath")
emulatorRules.winRegistryPaths.push_back(entryValue);
emulatorRules.winRegistryPaths.emplace_back(entryValue);
else if (ruleType == "winregistryvalue")
emulatorRules.winRegistryValues.push_back(entryValue);
emulatorRules.winRegistryValues.emplace_back(entryValue);
#endif
}
}
@ -149,8 +150,8 @@ void FindRules::loadFindRules()
#endif
}
for (pugi::xml_node core = ruleList.child("core"); core; core = core.next_sibling("core")) {
std::string coreName = core.attribute("name").as_string();
for (pugi::xml_node core {ruleList.child("core")}; core; core = core.next_sibling("core")) {
const std::string& coreName {core.attribute("name").as_string()};
if (coreName.empty()) {
LOG(LogWarning) << "Found core tag without name attribute, skipping entry";
continue;
@ -159,8 +160,8 @@ void FindRules::loadFindRules()
LOG(LogWarning) << "Found repeating core tag \"" << coreName << "\", skipping entry";
continue;
}
for (pugi::xml_node rule = core.child("rule"); rule; rule = rule.next_sibling("rule")) {
std::string ruleType = rule.attribute("type").as_string();
for (pugi::xml_node rule {core.child("rule")}; rule; rule = rule.next_sibling("rule")) {
const std::string& ruleType {rule.attribute("type").as_string()};
if (ruleType.empty()) {
LOG(LogWarning) << "Found rule tag without type attribute for core \"" << coreName
<< "\", skipping entry";
@ -171,11 +172,11 @@ void FindRules::loadFindRules()
<< coreName << "\", skipping entry";
continue;
}
for (pugi::xml_node entry = rule.child("entry"); entry;
for (pugi::xml_node entry {rule.child("entry")}; entry;
entry = entry.next_sibling("entry")) {
std::string entryValue = entry.text().get();
const std::string& entryValue {entry.text().get()};
if (ruleType == "corepath")
coreRules.corePaths.push_back(entryValue);
coreRules.corePaths.emplace_back(entryValue);
}
}
mCores[coreName] = coreRules;
@ -265,13 +266,12 @@ void SystemData::setIsGameSystemStatus()
bool SystemData::populateFolder(FileData* folder)
{
const std::string& folderPath = folder->getPath();
std::string filePath;
std::string extension;
bool isGame;
bool showHiddenFiles = Settings::getInstance()->getBool("ShowHiddenFiles");
Utils::FileSystem::StringList dirContent = Utils::FileSystem::getDirContent(folderPath);
const std::string& folderPath {folder->getPath()};
const bool showHiddenFiles {Settings::getInstance()->getBool("ShowHiddenFiles")};
const Utils::FileSystem::StringList& dirContent {Utils::FileSystem::getDirContent(folderPath)};
bool isGame {false};
// If system directory exists but contains no games, return as error.
if (dirContent.size() == 0)
@ -290,9 +290,10 @@ bool SystemData::populateFolder(FileData* folder)
mFlattenFolders = true;
}
for (Utils::FileSystem::StringList::const_iterator it = dirContent.cbegin();
for (Utils::FileSystem::StringList::const_iterator it {dirContent.cbegin()};
it != dirContent.cend(); ++it) {
filePath = *it;
const bool isDirectory {Utils::FileSystem::isDirectory(filePath)};
// Skip any recursive symlinks as those would hang the application at various places.
if (Utils::FileSystem::isSymlink(filePath)) {
@ -306,8 +307,7 @@ bool SystemData::populateFolder(FileData* folder)
// Skip hidden files and folders.
if (!showHiddenFiles && Utils::FileSystem::isHidden(filePath)) {
LOG(LogDebug) << "SystemData::populateFolder(): Skipping hidden "
<< (Utils::FileSystem::isDirectory(filePath) ? "directory \"" : "file \"")
<< filePath << "\"";
<< (isDirectory ? "directory \"" : "file \"") << filePath << "\"";
continue;
}
@ -317,17 +317,19 @@ bool SystemData::populateFolder(FileData* folder)
extension = Utils::FileSystem::getExtension(filePath);
isGame = false;
if (std::find(mEnvData->mSearchExtensions.cbegin(), mEnvData->mSearchExtensions.cend(),
extension) != mEnvData->mSearchExtensions.cend()) {
FileData* newGame = new FileData(GAME, filePath, mEnvData, this);
extension) != mEnvData->mSearchExtensions.cend() &&
!(isDirectory && extension == ".")) {
FileData* newGame {new FileData(GAME, filePath, mEnvData, this)};
// If adding a configured file extension to a directory it will get interpreted as
// a regular file. This is useful for displaying multi-file/multi-disc games as single
// entries or for emulators that can get directories passed to them as command line
// parameters instead of regular files. In these instances we remove the extension
// from the metadata name so it does not show up in the gamelists and similar.
if (Utils::FileSystem::isDirectory(filePath)) {
const std::string folderName = newGame->metadata.get("name");
if (isDirectory && extension != ".") {
const std::string& folderName {newGame->metadata.get("name")};
newGame->metadata.set(
"name", folderName.substr(0, folderName.length() - extension.length()));
}
@ -343,15 +345,15 @@ bool SystemData::populateFolder(FileData* folder)
}
// Add directories that also do not match an extension as folders.
if (!isGame && Utils::FileSystem::isDirectory(filePath)) {
if (!isGame && isDirectory) {
// Make sure that it's not a recursive symlink pointing to a location higher in the
// hierarchy as the application would run forever trying to resolve the link.
if (Utils::FileSystem::isSymlink(filePath)) {
const std::string canonicalPath {Utils::FileSystem::getCanonicalPath(filePath)};
const std::string canonicalStartPath {
const std::string& canonicalPath {Utils::FileSystem::getCanonicalPath(filePath)};
const std::string& canonicalStartPath {
Utils::FileSystem::getCanonicalPath(mEnvData->mStartPath)};
if (canonicalPath.size() >= canonicalStartPath.size()) {
const std::string combinedPath {
const std::string& combinedPath {
mEnvData->mStartPath +
canonicalPath.substr(canonicalStartPath.size(),
canonicalStartPath.size() - canonicalPath.size())};
@ -384,9 +386,9 @@ bool SystemData::populateFolder(FileData* folder)
void SystemData::indexAllGameFilters(const FileData* folder)
{
const std::vector<FileData*>& children = folder->getChildren();
const std::vector<FileData*>& children {folder->getChildren()};
for (std::vector<FileData*>::const_iterator it = children.cbegin(); // Line break.
for (std::vector<FileData*>::const_iterator it {children.cbegin()}; // Line break.
it != children.cend(); ++it) {
switch ((*it)->getType()) {
case GAME:
@ -405,10 +407,10 @@ std::vector<std::string> readList(const std::string& str, const std::string& del
{
std::vector<std::string> ret;
size_t prevOff = str.find_first_not_of(delims, 0);
size_t off = str.find_first_of(delims, prevOff);
size_t prevOff {str.find_first_not_of(delims, 0)};
size_t off {str.find_first_of(delims, prevOff)};
while (off != std::string::npos || prevOff != std::string::npos) {
ret.push_back(str.substr(prevOff, off - prevOff));
ret.emplace_back(str.substr(prevOff, off - prevOff));
prevOff = str.find_first_not_of(delims, off);
off = str.find_first_of(delims, prevOff);
@ -430,12 +432,12 @@ bool SystemData::loadConfig()
LOG(LogInfo) << "Only parsing the gamelist.xml files, not scanning system directories";
}
std::vector<std::string> configPaths = getConfigPath(true);
const std::string rompath = FileData::getROMDirectory();
const std::vector<std::string>& configPaths {getConfigPath(true)};
const std::string& rompath {FileData::getROMDirectory()};
bool onlyProcessCustomFile = false;
bool onlyProcessCustomFile {false};
for (auto configPath : configPaths) {
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)
@ -450,10 +452,10 @@ bool SystemData::loadConfig()
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPath).c_str());
const pugi::xml_parse_result& res {
doc.load_file(Utils::String::stringToWideString(configPath).c_str())};
#else
pugi::xml_parse_result res = doc.load_file(configPath.c_str());
const pugi::xml_parse_result& res {doc.load_file(configPath.c_str())};
#endif
if (!res) {
@ -461,7 +463,7 @@ bool SystemData::loadConfig()
return true;
}
pugi::xml_node loadExclusive = doc.child("loadExclusive");
const pugi::xml_node& loadExclusive {doc.child("loadExclusive")};
if (loadExclusive) {
if (configPath == configPaths.front() && configPaths.size() > 1) {
LOG(LogInfo) << "Only loading custom file as the <loadExclusive> tag is present";
@ -475,14 +477,14 @@ bool SystemData::loadConfig()
}
// Actually read the file.
pugi::xml_node systemList = doc.child("systemList");
const pugi::xml_node& systemList {doc.child("systemList")};
if (!systemList) {
LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
return true;
}
for (pugi::xml_node system = systemList.child("system"); system;
for (pugi::xml_node system {systemList.child("system")}; system;
system = system.next_sibling("system")) {
std::string name;
std::string fullname;
@ -545,7 +547,7 @@ bool SystemData::loadConfig()
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);
const 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 \""
@ -561,7 +563,7 @@ bool SystemData::loadConfig()
// the label attribute needs to be set on all entries as it's a requirement for the
// alternative emulator logic.
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")) {
if (!entry.attribute("label")) {
if (commands.size() == 1) {
@ -589,29 +591,29 @@ bool SystemData::loadConfig()
<< name << "\"";
break;
}
commands.push_back(
commands.emplace_back(
std::make_pair(entry.text().get(), entry.attribute("label").as_string()));
}
// Platform ID list
const std::string platformList =
Utils::String::toLower(system.child("platform").text().get());
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);
const 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);
std::string str {*it};
const 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);
platformIds.emplace_back(platformId);
break;
}
@ -621,7 +623,7 @@ bool SystemData::loadConfig()
LOG(LogWarning) << "Unknown platform \"" << str << "\" defined for system \""
<< name << "\", scraper searches will be inaccurate";
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId);
platformIds.emplace_back(platformId);
}
// Theme folder.
@ -662,20 +664,20 @@ bool SystemData::loadConfig()
#endif
// Create the system runtime environment data.
SystemEnvironmentData* envData = new SystemEnvironmentData;
SystemEnvironmentData* envData {new SystemEnvironmentData};
envData->mStartPath = path;
envData->mSearchExtensions = extensions;
envData->mLaunchCommands = commands;
envData->mPlatformIds = platformIds;
SystemData* newSys = new SystemData(name, fullname, sortName, envData, themeFolder);
bool onlyHidden = false;
SystemData* newSys {new SystemData(name, fullname, sortName, envData, themeFolder)};
bool onlyHidden {false};
// 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.
if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
std::vector<FileData*> recursiveGames =
newSys->getRootFolder()->getChildrenRecursive();
std::vector<FileData*> recursiveGames {
newSys->getRootFolder()->getChildrenRecursive()};
onlyHidden = true;
for (auto it = recursiveGames.cbegin(); it != recursiveGames.cend(); ++it) {
if ((*it)->getType() != FOLDER) {
@ -692,7 +694,7 @@ bool SystemData::loadConfig()
delete newSys;
}
else {
sSystemVector.push_back(newSys);
sSystemVector.emplace_back(newSys);
}
}
}
@ -723,7 +725,7 @@ std::string SystemData::getLaunchCommandFromLabel(const std::string& label)
void SystemData::deleteSystems()
{
for (unsigned int i = 0; i < sSystemVector.size(); ++i)
for (unsigned int i {0}; i < sSystemVector.size(); ++i)
delete sSystemVector.at(i);
sSystemVector.clear();
@ -734,8 +736,8 @@ std::vector<std::string> SystemData::getConfigPath(bool legacyWarning)
std::vector<std::string> paths;
if (legacyWarning) {
std::string legacyConfigFile =
Utils::FileSystem::getHomePath() + "/.emulationstation/es_systems.cfg";
const std::string& legacyConfigFile {Utils::FileSystem::getHomePath() +
"/.emulationstation/es_systems.cfg"};
if (Utils::FileSystem::exists(legacyConfigFile)) {
#if defined(_WIN64)
@ -751,8 +753,8 @@ std::vector<std::string> SystemData::getConfigPath(bool legacyWarning)
}
}
std::string customSystemsDirectory =
Utils::FileSystem::getHomePath() + "/.emulationstation/custom_systems";
const std::string& customSystemsDirectory {Utils::FileSystem::getHomePath() +
"/.emulationstation/custom_systems"};
if (!Utils::FileSystem::exists(customSystemsDirectory)) {
LOG(LogInfo) << "Creating custom systems directory \"" << customSystemsDirectory << "\"...";
@ -766,7 +768,7 @@ std::vector<std::string> SystemData::getConfigPath(bool legacyWarning)
if (Utils::FileSystem::exists(path)) {
LOG(LogInfo) << "Found custom systems configuration file";
paths.push_back(path);
paths.emplace_back(path);
}
#if defined(_WIN64)
@ -777,14 +779,14 @@ std::vector<std::string> SystemData::getConfigPath(bool legacyWarning)
path = ResourceManager::getInstance().getResourcePath(":/systems/unix/es_systems.xml", true);
#endif
paths.push_back(path);
paths.emplace_back(path);
return paths;
}
bool SystemData::createSystemDirectories()
{
std::vector<std::string> configPaths = getConfigPath(true);
const std::string rompath = FileData::getROMDirectory();
std::vector<std::string> configPaths {getConfigPath(true)};
const std::string& rompath {FileData::getROMDirectory()};
bool onlyProcessCustomFile = false;
@ -831,13 +833,13 @@ bool SystemData::createSystemDirectories()
// processing of the bundled configuration file.
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPaths.front()).c_str());
const pugi::xml_parse_result& res {
doc.load_file(Utils::String::stringToWideString(configPaths.front()).c_str())};
#else
pugi::xml_parse_result res = doc.load_file(configPaths.front().c_str());
const pugi::xml_parse_result& res {doc.load_file(configPaths.front().c_str())};
#endif
if (res) {
pugi::xml_node loadExclusive = doc.child("loadExclusive");
const pugi::xml_node& loadExclusive {doc.child("loadExclusive")};
if (loadExclusive)
onlyProcessCustomFile = true;
}
@ -849,7 +851,7 @@ bool SystemData::createSystemDirectories()
std::vector<std::pair<std::string, std::string>> systemsVector;
for (auto configPath : configPaths) {
for (auto& configPath : configPaths) {
// If the loadExclusive tag is present.
if (onlyProcessCustomFile && configPath == configPaths.front())
continue;
@ -863,10 +865,10 @@ bool SystemData::createSystemDirectories()
pugi::xml_document doc;
#if defined(_WIN64)
pugi::xml_parse_result res =
doc.load_file(Utils::String::stringToWideString(configPath).c_str());
const pugi::xml_parse_result& res {
doc.load_file(Utils::String::stringToWideString(configPath).c_str())};
#else
pugi::xml_parse_result res = doc.load_file(configPath.c_str());
const pugi::xml_parse_result& res {doc.load_file(configPath.c_str())};
#endif
if (!res) {
@ -876,14 +878,14 @@ bool SystemData::createSystemDirectories()
}
// Actually read the file.
pugi::xml_node systemList = doc.child("systemList");
const pugi::xml_node& systemList {doc.child("systemList")};
if (!systemList) {
LOG(LogError) << "es_systems.xml is missing the <systemList> tag";
return true;
}
for (pugi::xml_node system = systemList.child("system"); system;
for (pugi::xml_node system {systemList.child("system")}; system;
system = system.next_sibling("system")) {
std::string systemDir;
std::string name;
@ -893,7 +895,7 @@ bool SystemData::createSystemDirectories()
std::vector<std::string> commands;
std::string platform;
std::string themeFolder;
const std::string systemInfoFileName = "/systeminfo.txt";
const std::string systemInfoFileName {"/systeminfo.txt"};
bool replaceInfoFile = false;
std::ofstream systemInfoFile;
@ -901,9 +903,9 @@ bool SystemData::createSystemDirectories()
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;
for (pugi::xml_node entry {system.child("command")}; entry;
entry = entry.next_sibling("command")) {
commands.push_back(entry.text().get());
commands.emplace_back(entry.text().get());
}
platform = Utils::String::toLower(system.child("platform").text().get());
themeFolder = system.child("theme").text().as_string(name.c_str());
@ -1000,9 +1002,10 @@ bool SystemData::createSystemDirectories()
systemsVector.erase(systemIter);
if (configPaths.size() != 1 && configPath == configPaths.back())
systemsVector.push_back(std::make_pair(systemDir + " (custom system)", fullname));
systemsVector.emplace_back(
std::make_pair(systemDir + " (custom system)", fullname));
else
systemsVector.push_back(std::make_pair(systemDir, fullname));
systemsVector.emplace_back(std::make_pair(systemDir, fullname));
if (replaceInfoFile) {
LOG(LogInfo) << "Replaced existing system information file \""
@ -1019,8 +1022,8 @@ bool SystemData::createSystemDirectories()
// mappings between the system directory names and the full system names. This makes it
// easier for the users to identify the correct directories for their games.
if (!systemsVector.empty()) {
const std::string systemsFileName = "/systems.txt";
bool systemsFileSuccess = true;
const std::string& systemsFileName {"/systems.txt"};
bool systemsFileSuccess {true};
if (Utils::FileSystem::exists(rompath + systemsFileName)) {
if (Utils::FileSystem::removeFile(rompath + systemsFileName))
@ -1133,7 +1136,7 @@ std::string SystemData::getThemePath() const
SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
{
unsigned int total = 0;
int total {0};
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); ++it) {
if ((*it)->isGameSystem())
++total;
@ -1142,15 +1145,15 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
if (total < 2)
return nullptr;
SystemData* randomSystem = nullptr;
SystemData* randomSystem {nullptr};
do {
// Get a random number in range.
std::random_device randDev;
// Mersenne Twister pseudorandom number generator.
std::mt19937 engine {randDev()};
std::uniform_int_distribution<int> uniform_dist(0, total - 1);
int target = uniform_dist(engine);
std::uniform_int_distribution<int> uniform_dist {0, total - 1};
int target {uniform_dist(engine)};
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); ++it) {
if ((*it)->isGameSystem()) {
@ -1171,8 +1174,8 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
FileData* SystemData::getRandomGame(const FileData* currentGame, bool gameSelectorMode)
{
std::vector<FileData*> gameList;
bool onlyFolders = false;
bool hasFolders = false;
bool onlyFolders {false};
bool hasFolders {false};
// If we're in the custom collection group list, then get the list of collections,
// otherwise get a list of all the folder and file entries in the view.
@ -1203,7 +1206,7 @@ FileData* SystemData::getRandomGame(const FileData* currentGame, bool gameSelect
// If this is a mixed view of folders and files, then remove all the folder entries
// as we want to exclude them from the random selection.
if (!onlyFolders && hasFolders) {
unsigned int i = 0;
unsigned int i {0};
do {
if (gameList[i]->getType() == FOLDER)
gameList.erase(gameList.begin() + i);
@ -1222,8 +1225,8 @@ FileData* SystemData::getRandomGame(const FileData* currentGame, bool gameSelect
if (currentGame && currentGame->getType() == PLACEHOLDER)
return nullptr;
unsigned int total = static_cast<int>(gameList.size());
int target = 0;
int total {static_cast<int>(gameList.size())};
int target {0};
if (total < 2)
return nullptr;
@ -1233,7 +1236,7 @@ FileData* SystemData::getRandomGame(const FileData* currentGame, bool gameSelect
std::random_device randDev;
// Mersenne Twister pseudorandom number generator.
std::mt19937 engine {randDev()};
std::uniform_int_distribution<int> uniform_dist(0, total - 1);
std::uniform_int_distribution<int> uniform_dist {0, total - 1};
target = uniform_dist(engine);
} while (currentGame && gameList.at(target) == currentGame);
@ -1245,7 +1248,7 @@ void SystemData::sortSystem(bool reloadGamelist, bool jumpToFirstRow)
if (getName() == "recent")
return;
bool favoritesSorting;
bool favoritesSorting {false};
if (this->isCustomCollection() ||
(this->isCollection() && this->getFullName() == "collections")) {
@ -1255,7 +1258,7 @@ void SystemData::sortSystem(bool reloadGamelist, bool jumpToFirstRow)
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
}
FileData* rootFolder = getRootFolder();
FileData* rootFolder {getRootFolder()};
// Assign the sort type to all grouped custom collections.
if (mIsCollectionSystem && mFullName == "collections") {
for (auto it = rootFolder->getChildren().begin(); // Line break.
@ -1289,7 +1292,7 @@ void SystemData::loadTheme()
{
mTheme = std::make_shared<ThemeData>();
std::string path {getThemePath()};
const std::string& path {getThemePath()};
if (!Utils::FileSystem::exists(path)) {
// No theme available for this platform.
@ -1364,7 +1367,7 @@ void SystemData::setupSystemSortType(FileData* rootFolder)
{
// If DefaultSortOrder is set to something, check that it is actually a valid value.
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 ==
Settings::getInstance()->getString("DefaultSortOrder")) {
rootFolder->setSortTypeString(

View file

@ -32,7 +32,7 @@
#include <SDL2/SDL.h>
GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
: mMenu {"OPTIONS"}
: mMenu {"GAMELIST OPTIONS"}
, mSystem {system}
, mFiltersChanged {false}
, mCancelled {false}
@ -403,29 +403,29 @@ void GuiGamelistOptions::openMetaDataEd()
clearGameBtnFunc = [this, file] {
#if defined(_WIN64)
if (file->getType() == FOLDER) {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder \""
LOG(LogInfo) << "Deleting media files and gamelist.xml entry for the folder \""
<< Utils::String::replace(file->getFullPath(), "/", "\\") << "\"";
}
else if (file->getType() == GAME && Utils::FileSystem::isDirectory(file->getFullPath())) {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the "
LOG(LogInfo) << "Deleting media files and gamelist.xml entry for the "
"file-interpreted folder \""
<< Utils::String::replace(file->getFullPath(), "/", "\\") << "\"";
}
else {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file \""
LOG(LogInfo) << "Deleting media files and gamelist.xml entry for the file \""
<< Utils::String::replace(file->getFullPath(), "/", "\\") << "\"";
#else
if (file->getType() == FOLDER) {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder \""
LOG(LogInfo) << "Deleting media files and gamelist.xml entry for the folder \""
<< file->getFullPath() << "\"";
}
else if (file->getType() == GAME && Utils::FileSystem::isDirectory(file->getFullPath())) {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the "
LOG(LogInfo) << "Deleting media files and gamelist.xml entry for the "
"file-interpreted folder \""
<< file->getFullPath() << "\"";
}
else {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file \""
LOG(LogInfo) << "Deleting media files and gamelist.xml entry for the file \""
<< file->getFullPath() << "\"";
#endif
}
@ -473,8 +473,8 @@ void GuiGamelistOptions::openMetaDataEd()
};
deleteGameBtnFunc = [this, file] {
LOG(LogInfo) << "Deleting the game file \"" << file->getFullPath()
<< "\", all its media files and its gamelist.xml entry.";
LOG(LogInfo) << "Deleting game file \"" << file->getFullPath()
<< "\", all its media files and its gamelist.xml entry";
CollectionSystemsManager::getInstance()->deleteCollectionFiles(file);
ViewController::getInstance()->getGamelistView(file->getSystem()).get()->removeMedia(file);
ViewController::getInstance()->getGamelistView(file->getSystem()).get()->remove(file, true);

View file

@ -119,7 +119,7 @@ void GuiMenu::openUIOptions()
for (auto it = themeSets.cbegin(); it != themeSets.cend(); ++it) {
// If required, abbreviate the theme set name so it doesn't overlap the setting name.
float maxNameLength = mSize.x * 0.62f;
const float maxNameLength = mSize.x * 0.62f;
themeSet->add(it->first, it->first, it == selectedSet, maxNameLength);
}
s->addWithLabel("THEME SET", themeSet);
@ -176,7 +176,7 @@ void GuiMenu::openUIOptions()
if (variant.selectable) {
// If required, abbreviate the variant name so it doesn't overlap the
// setting name.
float maxNameLength {mSize.x * 0.62f};
const float maxNameLength {mSize.x * 0.62f};
themeVariant->add(variant.label, variant.name, variant.name == selectedVariant,
maxNameLength);
}
@ -226,7 +226,7 @@ void GuiMenu::openUIOptions()
for (auto& colorScheme : currentSet->second.capabilities.colorSchemes) {
// If required, abbreviate the color scheme name so it doesn't overlap the
// setting name.
float maxNameLength {mSize.x * 0.52f};
const float maxNameLength {mSize.x * 0.52f};
themeColorScheme->add(colorScheme.label, colorScheme.name,
colorScheme.name == selectedColorScheme, maxNameLength);
}
@ -298,12 +298,12 @@ void GuiMenu::openUIOptions()
auto gamelistViewStyle = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "LEGACY GAMELIST VIEW STYLE", false);
std::string selectedViewStyle {Settings::getInstance()->getString("GamelistViewStyle")};
gamelistViewStyle->add("automatic", "automatic", selectedViewStyle == "automatic");
gamelistViewStyle->add("basic", "basic", selectedViewStyle == "basic");
gamelistViewStyle->add("detailed", "detailed", selectedViewStyle == "detailed");
gamelistViewStyle->add("video", "video", selectedViewStyle == "video");
gamelistViewStyle->add("AUTOMATIC", "automatic", selectedViewStyle == "automatic");
gamelistViewStyle->add("BASIC", "basic", selectedViewStyle == "basic");
gamelistViewStyle->add("DETAILED", "detailed", selectedViewStyle == "detailed");
gamelistViewStyle->add("VIDEO", "video", selectedViewStyle == "video");
// If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the view style to Automatic in this case.
// configuration file. Simply set the view style to "automatic" in this case.
if (gamelistViewStyle->getSelectedObjects().size() == 0)
gamelistViewStyle->selectEntry(0);
s->addWithLabel("LEGACY GAMELIST VIEW STYLE", gamelistViewStyle);
@ -337,20 +337,43 @@ void GuiMenu::openUIOptions()
}
});
// Quick system select (navigate between systems in the gamelist view).
auto quickSystemSelect = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "QUICK SYSTEM SELECT", false);
std::string selectedQuickSelect {Settings::getInstance()->getString("QuickSystemSelect")};
quickSystemSelect->add("LEFT/RIGHT OR SHOULDERS", "leftrightshoulders",
selectedQuickSelect == "leftrightshoulders");
quickSystemSelect->add("LEFT/RIGHT OR TRIGGERS", "leftrighttriggers",
selectedQuickSelect == "leftrighttriggers");
quickSystemSelect->add("SHOULDERS", "shoulders", selectedQuickSelect == "shoulders");
quickSystemSelect->add("TRIGGERS", "triggers", selectedQuickSelect == "triggers");
quickSystemSelect->add("LEFT/RIGHT", "leftright", selectedQuickSelect == "leftright");
quickSystemSelect->add("DISABLED", "disabled", selectedQuickSelect == "disabled");
// If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the quick system select to "leftrightshoulders" in this case.
if (quickSystemSelect->getSelectedObjects().size() == 0)
quickSystemSelect->selectEntry(0);
s->addWithLabel("QUICK SYSTEM SELECT", quickSystemSelect);
s->addSaveFunc([quickSystemSelect, s] {
if (quickSystemSelect->getSelected() !=
Settings::getInstance()->getString("QuickSystemSelect")) {
Settings::getInstance()->setString("QuickSystemSelect",
quickSystemSelect->getSelected());
s->setNeedsSaving();
}
});
// Optionally start in selected system/gamelist.
auto startupSystem = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "GAMELIST ON STARTUP", false);
startupSystem->add("NONE", "", Settings::getInstance()->getString("StartupSystem") == "");
for (auto it = SystemData::sSystemVector.cbegin(); // Line break.
it != SystemData::sSystemVector.cend(); ++it) {
if ((*it)->getName() != "retropie") {
// If required, abbreviate the system name so it doesn't overlap the setting name.
float maxNameLength {mSize.x * 0.51f};
startupSystem->add((*it)->getFullName(), (*it)->getName(),
Settings::getInstance()->getString("StartupSystem") ==
(*it)->getName(),
maxNameLength);
}
// If required, abbreviate the system name so it doesn't overlap the setting name.
float maxNameLength {mSize.x * 0.51f};
startupSystem->add((*it)->getFullName(), (*it)->getName(),
Settings::getInstance()->getString("StartupSystem") == (*it)->getName(),
maxNameLength);
}
// This can probably not happen but as an extra precaution select the "NONE" entry if no
// entry is selected.
@ -686,18 +709,6 @@ void GuiMenu::openUIOptions()
}
});
// Quick system select (navigate left/right in gamelist view).
auto quickSystemSelect = std::make_shared<SwitchComponent>();
quickSystemSelect->setState(Settings::getInstance()->getBool("QuickSystemSelect"));
s->addWithLabel("ENABLE QUICK SYSTEM SELECT", quickSystemSelect);
s->addSaveFunc([quickSystemSelect, s] {
if (Settings::getInstance()->getBool("QuickSystemSelect") !=
quickSystemSelect->getState()) {
Settings::getInstance()->setBool("QuickSystemSelect", quickSystemSelect->getState());
s->setNeedsSaving();
}
});
// On-screen help prompts.
auto showHelpPrompts = std::make_shared<SwitchComponent>();
showHelpPrompts->setState(Settings::getInstance()->getBool("ShowHelpPrompts"));

View file

@ -149,8 +149,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
case MD_BOOL: {
ed = std::make_shared<SwitchComponent>();
// Make the switches slightly smaller.
glm::vec2 switchSize {ed->getSize() * 0.9f};
ed->setResize(ceilf(switchSize.x), switchSize.y);
ed->setSize(glm::ceil(ed->getSize() * 0.9f));
ed->setChangedColor(ICONCOLOR_USERMARKED);
row.addElement(ed, false, true);

View file

@ -139,7 +139,7 @@ GuiScraperMenu::GuiScraperMenu(std::string title)
addChild(&mMenu);
mMenu.addButton("START", "start", std::bind(&GuiScraperMenu::pressedStart, this));
mMenu.addButton("START", "start scraper", std::bind(&GuiScraperMenu::pressedStart, this));
mMenu.addButton("BACK", "back", [&] { delete this; });
setSize(mMenu.getSize());
@ -252,27 +252,28 @@ void GuiScraperMenu::openContentOptions()
->setOpacity(DISABLED_OPACITY);
}
// Scrape controllers (arcade systems only).
auto scrapeControllers = std::make_shared<SwitchComponent>();
scrapeControllers->setState(Settings::getInstance()->getBool("ScrapeControllers"));
s->addWithLabel("CONTROLLERS (ARCADE SYSTEMS ONLY)", scrapeControllers);
s->addSaveFunc([scrapeControllers, s] {
if (scrapeControllers->getState() !=
Settings::getInstance()->getBool("ScrapeControllers")) {
Settings::getInstance()->setBool("ScrapeControllers", scrapeControllers->getState());
s->setNeedsSaving();
}
});
// ScreenScraper controller scraping is currently broken, it's unclear if they will fix it.
// // Scrape controllers (arcade systems only).
// auto scrapeControllers = std::make_shared<SwitchComponent>();
// scrapeControllers->setState(Settings::getInstance()->getBool("ScrapeControllers"));
// s->addWithLabel("CONTROLLERS (ARCADE SYSTEMS ONLY)", scrapeControllers);
// s->addSaveFunc([scrapeControllers, s] {
// if (scrapeControllers->getState() !=
// Settings::getInstance()->getBool("ScrapeControllers")) {
// Settings::getInstance()->setBool("ScrapeControllers", scrapeControllers->getState());
// s->setNeedsSaving();
// }
// });
// Controllers are not supported by TheGamesDB, so gray out the option if this scraper is
// selected.
if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
scrapeControllers->setEnabled(false);
scrapeControllers->setOpacity(DISABLED_OPACITY);
scrapeControllers->getParent()
->getChild(scrapeControllers->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
// // Controllers are not supported by TheGamesDB, so gray out the option if this scraper is
// // selected.
// if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
// scrapeControllers->setEnabled(false);
// scrapeControllers->setOpacity(DISABLED_OPACITY);
// scrapeControllers->getParent()
// ->getChild(scrapeControllers->getChildIndex() - 1)
// ->setOpacity(DISABLED_OPACITY);
// }
// Scrape other metadata.
auto scrapeMetadata = std::make_shared<SwitchComponent>();
@ -1059,11 +1060,12 @@ void GuiScraperMenu::start()
contentToScrape = true;
break;
}
if (scraperService == "screenscraper" &&
Settings::getInstance()->getBool("ScrapeControllers")) {
contentToScrape = true;
break;
}
// ScreenScraper controller scraping is currently broken, it's unclear if they will fix it.
// if (scraperService == "screenscraper" &&
// Settings::getInstance()->getBool("ScrapeControllers")) {
// contentToScrape = true;
// break;
// }
if (Settings::getInstance()->getBool("ScrapeMetadata")) {
contentToScrape = true;
break;
@ -1192,6 +1194,6 @@ std::vector<HelpPrompt> GuiScraperMenu::getHelpPrompts()
{
std::vector<HelpPrompt> prompts {mMenu.getHelpPrompts()};
prompts.push_back(HelpPrompt("b", "back"));
prompts.push_back(HelpPrompt("y", "start"));
prompts.push_back(HelpPrompt("y", "start scraper"));
return prompts;
}

View file

@ -966,9 +966,10 @@ bool GuiScraperSearch::saveMetadata(const ScraperSearchResult& result,
if (key == "rating" && !Settings::getInstance()->getBool("ScrapeRatings"))
continue;
// Skip saving of controller metadata if the corresponding option has been set to false.
if (key == "controller" && !Settings::getInstance()->getBool("ScrapeControllers"))
continue;
// ScreenScraper controller scraping is currently broken, it's unclear if they will fix it.
// // Skip saving of controller metadata if the corresponding option has been set to false.
// if (key == "controller" && !Settings::getInstance()->getBool("ScrapeControllers"))
// continue;
// Skip saving of game name if the corresponding option has been set to false.
if (key == "name" && !Settings::getInstance()->getBool("ScrapeGameNames"))

View file

@ -471,93 +471,97 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc,
if (result.platformIDs.empty())
result.platformIDs.push_back(PlatformId::PLATFORM_UNKNOWN);
// Controller (only for the Arcade and SNK Neo Geo systems).
if (parentPlatformID == 75 || parentPlatformID == 142) {
std::string controller = Utils::String::toLower(game.child("controles").text().get());
if (!controller.empty()) {
std::string controllerDescription = "Other";
// Place the steering wheel entry first as some games support both joysticks and
// and steering wheels and it's likely more interesting to capture the steering
// wheel option in this case.
if (controller.find("steering wheel") != std::string::npos ||
controller.find("paddle") != std::string::npos ||
controller.find("pedal") != std::string::npos) {
result.mdl.set("controller", "steering_wheel_generic");
controllerDescription = "Steering wheel";
}
else if (controller.find("control type=\"joy") != std::string::npos ||
controller.find("joystick") != std::string::npos) {
std::string buttonEntry;
std::string buttonCount;
if (controller.find("p1numbuttons=") != std::string::npos)
buttonEntry = controller.substr(controller.find("p1numbuttons=") + 13, 4);
else if (controller.find("buttons=") != std::string::npos)
buttonEntry = controller.substr(controller.find("buttons=") + 8, 5);
bool foundDigit = false;
for (unsigned char character : buttonEntry) {
if (std::isdigit(character)) {
buttonCount.push_back(character);
foundDigit = true;
}
else if (foundDigit == true) {
break;
}
}
if (buttonCount == "0") {
result.mdl.set("controller", "joystick_arcade_no_buttons");
controllerDescription = "Joystick (no buttons)";
}
else if (buttonCount == "1") {
result.mdl.set("controller", "joystick_arcade_1_button");
controllerDescription = "Joystick (1 button)";
}
else if (buttonCount == "2") {
result.mdl.set("controller", "joystick_arcade_2_buttons");
controllerDescription = "Joystick (2 buttons)";
}
else if (buttonCount == "3") {
result.mdl.set("controller", "joystick_arcade_3_buttons");
controllerDescription = "Joystick (3 buttons)";
}
else if (buttonCount == "4") {
result.mdl.set("controller", "joystick_arcade_4_buttons");
controllerDescription = "Joystick (4 buttons)";
}
else if (buttonCount == "5") {
result.mdl.set("controller", "joystick_arcade_5_buttons");
controllerDescription = "Joystick (5 buttons)";
}
else if (buttonCount == "6") {
result.mdl.set("controller", "joystick_arcade_6_buttons");
controllerDescription = "Joystick (6 buttons)";
}
else {
controllerDescription = "Joystick (other)";
}
}
else if (controller.find("spinner") != std::string::npos) {
result.mdl.set("controller", "spinner_generic");
controllerDescription = "Spinner";
}
else if (controller.find("trackball") != std::string::npos) {
result.mdl.set("controller", "trackball_generic");
controllerDescription = "Trackball";
}
else if (controller.find("gun") != std::string::npos) {
result.mdl.set("controller", "lightgun_generic");
controllerDescription = "Lightgun";
}
else if (controller.find("stick") != std::string::npos) {
result.mdl.set("controller", "flight_stick_generic");
controllerDescription = "Flight stick";
}
LOG(LogDebug) << "ScreenScraperRequest::processGame(): Controller: "
<< controllerDescription;
}
}
// ScreenScraper controller scraping is currently broken, it's unclear if they will fix it.
// // Controller (only for the Arcade and SNK Neo Geo systems).
// if (parentPlatformID == 75 || parentPlatformID == 142) {
// std::string controller {Utils::String::toLower(game.child("controles").text().get())};
//
// LOG(LogError) << controller;
//
// if (!controller.empty()) {
// std::string controllerDescription = "Other";
// // Place the steering wheel entry first as some games support both joysticks and
// // and steering wheels and it's likely more interesting to capture the steering
// // wheel option in this case.
// if (controller.find("steering wheel") != std::string::npos ||
// controller.find("paddle") != std::string::npos ||
// controller.find("pedal") != std::string::npos) {
// result.mdl.set("controller", "steering_wheel_generic");
// controllerDescription = "Steering wheel";
// }
// else if (controller.find("control type=\"joy") != std::string::npos ||
// controller.find("joystick") != std::string::npos) {
// std::string buttonEntry;
// std::string buttonCount;
// if (controller.find("p1numbuttons=") != std::string::npos)
// buttonEntry = controller.substr(controller.find("p1numbuttons=") + 13, 4);
// else if (controller.find("buttons=") != std::string::npos)
// buttonEntry = controller.substr(controller.find("buttons=") + 8, 5);
//
// bool foundDigit = false;
// for (unsigned char character : buttonEntry) {
// if (std::isdigit(character)) {
// buttonCount.push_back(character);
// foundDigit = true;
// }
// else if (foundDigit == true) {
// break;
// }
// }
//
// if (buttonCount == "0") {
// result.mdl.set("controller", "joystick_arcade_no_buttons");
// controllerDescription = "Joystick (no buttons)";
// }
// else if (buttonCount == "1") {
// result.mdl.set("controller", "joystick_arcade_1_button");
// controllerDescription = "Joystick (1 button)";
// }
// else if (buttonCount == "2") {
// result.mdl.set("controller", "joystick_arcade_2_buttons");
// controllerDescription = "Joystick (2 buttons)";
// }
// else if (buttonCount == "3") {
// result.mdl.set("controller", "joystick_arcade_3_buttons");
// controllerDescription = "Joystick (3 buttons)";
// }
// else if (buttonCount == "4") {
// result.mdl.set("controller", "joystick_arcade_4_buttons");
// controllerDescription = "Joystick (4 buttons)";
// }
// else if (buttonCount == "5") {
// result.mdl.set("controller", "joystick_arcade_5_buttons");
// controllerDescription = "Joystick (5 buttons)";
// }
// else if (buttonCount == "6") {
// result.mdl.set("controller", "joystick_arcade_6_buttons");
// controllerDescription = "Joystick (6 buttons)";
// }
// else {
// controllerDescription = "Joystick (other)";
// }
// }
// else if (controller.find("spinner") != std::string::npos) {
// result.mdl.set("controller", "spinner_generic");
// controllerDescription = "Spinner";
// }
// else if (controller.find("trackball") != std::string::npos) {
// result.mdl.set("controller", "trackball_generic");
// controllerDescription = "Trackball";
// }
// else if (controller.find("gun") != std::string::npos) {
// result.mdl.set("controller", "lightgun_generic");
// controllerDescription = "Lightgun";
// }
// else if (controller.find("stick") != std::string::npos) {
// result.mdl.set("controller", "flight_stick_generic");
// controllerDescription = "Flight stick";
// }
//
// LOG(LogDebug) << "ScreenScraperRequest::processGame(): Controller: "
// << controllerDescription;
// }
// }
// Media super-node.
pugi::xml_node media_list = game.child("medias");

View file

@ -196,8 +196,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
}
}
else if (config->isMappedLike(getQuickSystemSelectRightButton(), input)) {
if (mLeftRightAvailable && Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1) {
if (SystemData::sSystemVector.size() > 1) {
muteViewVideos();
onFocusLost();
stopListScrolling();
@ -207,8 +206,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
}
}
else if (config->isMappedLike(getQuickSystemSelectLeftButton(), input)) {
if (mLeftRightAvailable && Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1) {
if (SystemData::sSystemVector.size() > 1) {
muteViewVideos();
onFocusLost();
stopListScrolling();
@ -435,11 +433,18 @@ bool GamelistBase::input(InputConfig* config, Input input)
}
else if (CollectionSystemsManager::getInstance()->toggleGameInCollection(
entryToUpdate)) {
// Needed to avoid some minor transition animation glitches.
auto grid =
ViewController::getInstance()->getGamelistView(system).get()->mGrid.get();
if (grid != nullptr)
grid->setSuppressTransitions(true);
// As the toggling of the game destroyed this object, we need to get the view
// from ViewController instead of using the reference that existed before the
// destruction. Otherwise we get random crashes.
GamelistView* view {
ViewController::getInstance()->getGamelistView(system).get()};
// Jump to the first entry in the gamelist if the last favorite was unmarked.
if (foldersOnTop && removedLastFavorite &&
!entryToUpdate->getSystem()->isCustomCollection()) {
@ -457,6 +462,10 @@ bool GamelistBase::input(InputConfig* config, Input input)
else if (selectLastEntry && view->getPrimary()->size() > 0) {
view->setCursor(view->getLastEntry());
}
if (grid != nullptr)
grid->setSuppressTransitions(false);
// Display the indication icons which show what games are part of the
// custom collection currently being edited. This is done cheaply using
// onFileChanged() which will trigger populateList().
@ -568,15 +577,19 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
auto theme = mRoot->getSystem()->getTheme();
std::string name;
std::string carouselItemType;
std::string carouselDefaultItem;
std::string defaultImage;
if (mCarousel != nullptr) {
carouselItemType = mCarousel->getItemType();
carouselDefaultItem = mCarousel->getDefaultItem();
if (!ResourceManager::getInstance().fileExists(carouselDefaultItem))
carouselDefaultItem = "";
defaultImage = mCarousel->getDefaultImage();
if (!ResourceManager::getInstance().fileExists(defaultImage))
defaultImage = "";
}
else if (mGrid != nullptr) {
defaultImage = mGrid->getDefaultImage();
if (!ResourceManager::getInstance().fileExists(defaultImage))
defaultImage = "";
}
if (files.size() > 0) {
for (auto it = files.cbegin(); it != files.cend(); ++it) {
@ -593,8 +606,6 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
}
if (mCarousel != nullptr) {
assert(carouselItemType != "");
CarouselComponent<FileData*>::Entry carouselEntry;
carouselEntry.name = (*it)->getName();
carouselEntry.object = *it;
@ -606,13 +617,29 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
else if (letterCase == LetterCase::CAPITALIZED)
carouselEntry.name = Utils::String::toCapitalized(carouselEntry.name);
if (carouselDefaultItem != "")
carouselEntry.data.defaultItemPath = carouselDefaultItem;
if (defaultImage != "")
carouselEntry.data.defaultImagePath = defaultImage;
mCarousel->addEntry(carouselEntry, theme);
}
else if (mGrid != nullptr) {
GridComponent<FileData*>::Entry gridEntry;
gridEntry.name = (*it)->getName();
gridEntry.object = *it;
if (mTextList != nullptr) {
if (letterCase == LetterCase::UPPERCASE)
gridEntry.name = Utils::String::toUpper(gridEntry.name);
else if (letterCase == LetterCase::LOWERCASE)
gridEntry.name = Utils::String::toLower(gridEntry.name);
else if (letterCase == LetterCase::CAPITALIZED)
gridEntry.name = Utils::String::toCapitalized(gridEntry.name);
if (defaultImage != "")
gridEntry.data.defaultImagePath = defaultImage;
mGrid->addEntry(gridEntry, theme);
}
else if (mTextList != nullptr) {
TextListComponent<FileData*>::Entry textListEntry;
std::string indicators {mTextList->getIndicators()};
std::string collectionIndicators {mTextList->getCollectionIndicators()};
@ -685,6 +712,9 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
addPlaceholder(firstEntry);
}
if (mGrid != nullptr)
mGrid->calculateLayout();
generateGamelistInfo(getCursor(), firstEntry);
generateFirstLetterIndex(files);
}
@ -717,13 +747,20 @@ void GamelistBase::addPlaceholder(FileData* firstEntry)
textListEntry.data.entryType = TextListEntryType::SECONDARY;
mTextList->addEntry(textListEntry);
}
if (mCarousel != nullptr) {
else if (mCarousel != nullptr) {
CarouselComponent<FileData*>::Entry carouselEntry;
carouselEntry.name = placeholder->getName();
letterCaseFunc(carouselEntry.name);
carouselEntry.object = placeholder;
mCarousel->addEntry(carouselEntry, mRoot->getSystem()->getTheme());
}
else if (mGrid != nullptr) {
GridComponent<FileData*>::Entry gridEntry;
gridEntry.name = placeholder->getName();
letterCaseFunc(gridEntry.name);
gridEntry.object = placeholder;
mGrid->addEntry(gridEntry, mRoot->getSystem()->getTheme());
}
}
void GamelistBase::generateFirstLetterIndex(const std::vector<FileData*>& files)
@ -961,3 +998,79 @@ void GamelistBase::removeMedia(FileData* game)
removeEmptyDirFunc(systemMediaDir, mediaType, path);
}
}
std::string GamelistBase::getQuickSystemSelectLeftButton()
{
if (Settings::getInstance()->getString("QuickSystemSelect") == "leftrightshoulders") {
if (mLeftRightAvailable)
return "left";
else
return "leftshoulder";
}
if (Settings::getInstance()->getString("QuickSystemSelect") == "leftrighttriggers") {
if (mLeftRightAvailable)
return "left";
else
return "lefttrigger";
}
if (Settings::getInstance()->getString("QuickSystemSelect") == "shoulders")
return "leftshoulder";
if (Settings::getInstance()->getString("QuickSystemSelect") == "triggers")
return "lefttrigger";
if (Settings::getInstance()->getString("QuickSystemSelect") == "leftright") {
if (mLeftRightAvailable)
return "left";
else
return "";
}
if (Settings::getInstance()->getString("QuickSystemSelect") == "disabled")
return "";
// This should only happen if there is an invalid value in es_settings.xml.
if (mLeftRightAvailable)
return "left";
else
return "leftshoulder";
}
std::string GamelistBase::getQuickSystemSelectRightButton()
{
if (Settings::getInstance()->getString("QuickSystemSelect") == "leftrightshoulders") {
if (mLeftRightAvailable)
return "right";
else
return "rightshoulder";
}
if (Settings::getInstance()->getString("QuickSystemSelect") == "leftrighttriggers") {
if (mLeftRightAvailable)
return "right";
else
return "righttrigger";
}
if (Settings::getInstance()->getString("QuickSystemSelect") == "shoulders")
return "rightshoulder";
if (Settings::getInstance()->getString("QuickSystemSelect") == "triggers")
return "righttrigger";
if (Settings::getInstance()->getString("QuickSystemSelect") == "leftright") {
if (mLeftRightAvailable)
return "right";
else
return "";
}
if (Settings::getInstance()->getString("QuickSystemSelect") == "disabled")
return "";
if (mLeftRightAvailable)
return "right";
else
return "rightshoulder";
}

View file

@ -23,6 +23,7 @@
#include "components/TextComponent.h"
#include "components/VideoFFmpegComponent.h"
#include "components/primary/CarouselComponent.h"
#include "components/primary/GridComponent.h"
#include "components/primary/TextListComponent.h"
#include <stack>
@ -85,11 +86,12 @@ protected:
bool isListScrolling() override { return mPrimary->isScrolling(); }
std::string getQuickSystemSelectRightButton() { return "right"; }
std::string getQuickSystemSelectLeftButton() { return "left"; }
std::string getQuickSystemSelectLeftButton();
std::string getQuickSystemSelectRightButton();
FileData* mRoot;
std::unique_ptr<CarouselComponent<FileData*>> mCarousel;
std::unique_ptr<GridComponent<FileData*>> mGrid;
std::unique_ptr<TextListComponent<FileData*>> mTextList;
PrimaryComponent<FileData*>* mPrimary;

View file

@ -49,7 +49,12 @@ void GamelistView::onFileChanged(FileData* file, bool reloadGamelist)
FileData* cursor {getCursor()};
if (!cursor->isPlaceHolder()) {
populateList(cursor->getParent()->getChildrenListToDisplay(), cursor->getParent());
// Needed to avoid some minor transition animation glitches.
if (mGrid != nullptr)
mGrid->setSuppressTransitions(true);
setCursor(cursor);
if (mGrid != nullptr)
mGrid->setSuppressTransitions(false);
}
else {
populateList(mRoot->getChildrenListToDisplay(), mRoot);
@ -114,15 +119,24 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
if (mTheme->hasView("gamelist")) {
for (auto& element : mTheme->getViewElements("gamelist").elements) {
if (element.second.type == "textlist" || element.second.type == "carousel") {
if (element.second.type == "carousel" && mTextList != nullptr) {
if (element.second.type == "carousel" || element.second.type == "grid" ||
element.second.type == "textlist") {
if (element.second.type == "carousel" &&
(mGrid != nullptr || mTextList != nullptr)) {
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
<< "defined, skipping <carousel> configuration entry";
<< "defined, skipping carousel configuration entry";
continue;
}
if (element.second.type == "textlist" && mCarousel != nullptr) {
if (element.second.type == "grid" &&
(mCarousel != nullptr || mTextList != nullptr)) {
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
<< "defined, skipping <textlist> configuration entry";
<< "defined, skipping grid configuration entry";
continue;
}
if (element.second.type == "textlist" &&
(mCarousel != nullptr || mGrid != nullptr)) {
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
<< "defined, skipping textlist configuration entry";
continue;
}
}
@ -131,9 +145,6 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mTextList = std::make_unique<TextListComponent<FileData*>>();
mPrimary = mTextList.get();
}
mPrimary->setPosition(0.0f, mSize.y * 0.1f);
mPrimary->setSize(mSize.x, mSize.y * 0.8f);
mPrimary->setAlignment(TextListComponent<FileData*>::PrimaryAlignment::ALIGN_LEFT);
mPrimary->setCursorChangedCallback(
[&](const CursorState& state) { updateView(state); });
mPrimary->setDefaultZIndex(50.0f);
@ -144,28 +155,51 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
if (element.second.type == "carousel") {
if (mCarousel == nullptr) {
mCarousel = std::make_unique<CarouselComponent<FileData*>>();
if (element.second.has("itemType")) {
if (element.second.has("imageType")) {
const std::string imageType {element.second.get<std::string>("imageType")};
if (imageType == "marquee" || imageType == "cover" ||
imageType == "backcover" || imageType == "3dbox" ||
imageType == "physicalmedia" || imageType == "screenshot" ||
imageType == "titlescreen" || imageType == "miximage" ||
imageType == "fanart" || imageType == "none") {
mCarousel->setImageType(imageType);
}
else {
LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme "
"configuration, carousel property \"imageType\" "
"for element \""
<< element.first.substr(9) << "\" defined as \""
<< imageType << "\"";
mCarousel->setImageType("marquee");
}
}
else if (element.second.has("itemType")) {
// TEMPORARY: Backward compatiblity due to property name changes.
const std::string itemType {element.second.get<std::string>("itemType")};
if (itemType == "marquee" || itemType == "cover" ||
itemType == "backcover" || itemType == "3dbox" ||
itemType == "physicalmedia" || itemType == "screenshot" ||
itemType == "titlescreen" || itemType == "miximage" ||
itemType == "fanart" || itemType == "none") {
mCarousel->setItemType(itemType);
mCarousel->setImageType(itemType);
}
else {
LOG(LogWarning)
<< "GamelistView::onThemeChanged(): Invalid theme configuration, "
"<itemType> property defined as \""
<< itemType << "\"";
mCarousel->setItemType("marquee");
LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme "
"configuration, carousel property \"itemType\" "
"for element \""
<< element.first.substr(9) << "\" defined as \""
<< itemType << "\"";
mCarousel->setImageType("marquee");
}
}
else {
mCarousel->setItemType("marquee");
mCarousel->setImageType("marquee");
}
// TEMPORARY: Backward compatiblity due to property name changes.
if (element.second.has("defaultItem"))
mCarousel->setDefaultItem(element.second.get<std::string>("defaultItem"));
mCarousel->setDefaultImage(element.second.get<std::string>("defaultItem"));
if (element.second.has("defaultImage"))
mCarousel->setDefaultImage(element.second.get<std::string>("defaultImage"));
mPrimary = mCarousel.get();
}
mPrimary->setCursorChangedCallback(
@ -174,6 +208,40 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mPrimary->applyTheme(theme, "gamelist", element.first, ALL);
addChild(mPrimary);
}
if (element.second.type == "grid") {
if (mGrid == nullptr) {
mGrid = std::make_unique<GridComponent<FileData*>>();
if (element.second.has("imageType")) {
const std::string imageType {element.second.get<std::string>("imageType")};
if (imageType == "marquee" || imageType == "cover" ||
imageType == "backcover" || imageType == "3dbox" ||
imageType == "physicalmedia" || imageType == "screenshot" ||
imageType == "titlescreen" || imageType == "miximage" ||
imageType == "fanart" || imageType == "none") {
mGrid->setImageType(imageType);
}
else {
LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme "
"configuration, grid property \"imageType\" "
"for element \""
<< element.first.substr(5) << "\" defined as \""
<< imageType << "\"";
mGrid->setImageType("marquee");
}
}
else {
mGrid->setImageType("marquee");
}
if (element.second.has("defaultImage"))
mGrid->setDefaultImage(element.second.get<std::string>("defaultImage"));
mPrimary = mGrid.get();
}
mPrimary->setCursorChangedCallback(
[&](const CursorState& state) { updateView(state); });
mPrimary->setDefaultZIndex(50.0f);
mPrimary->applyTheme(theme, "gamelist", element.first, ALL);
addChild(mPrimary);
}
if (element.second.type == "image") {
// If this is the startup system, then forceload the images to avoid texture pop-in.
if (isStartupSystem)
@ -313,9 +381,6 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
if (mPrimary == nullptr) {
mTextList = std::make_unique<TextListComponent<FileData*>>();
mPrimary = mTextList.get();
mPrimary->setPosition(0.0f, mSize.y * 0.1f);
mPrimary->setSize(mSize.x, mSize.y * 0.8f);
mPrimary->setAlignment(TextListComponent<FileData*>::PrimaryAlignment::ALIGN_LEFT);
mPrimary->setCursorChangedCallback([&](const CursorState& state) { updateView(state); });
mPrimary->setDefaultZIndex(50.0f);
mPrimary->setZIndex(50.0f);
@ -325,12 +390,15 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
populateList(mRoot->getChildrenListToDisplay(), mRoot);
// Disable quick system select if the primary component uses the left and right buttons.
// Check whether the primary component uses the left and right buttons for its navigation.
if (mCarousel != nullptr) {
if (mCarousel->getType() == CarouselComponent<FileData*>::CarouselType::HORIZONTAL ||
mCarousel->getType() == CarouselComponent<FileData*>::CarouselType::HORIZONTAL_WHEEL)
mLeftRightAvailable = false;
}
else if (mGrid != nullptr) {
mLeftRightAvailable = false;
}
for (auto& video : mStaticVideoComponents) {
if (video->hasStaticVideo())
@ -386,15 +454,20 @@ std::vector<HelpPrompt> GamelistView::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
if (Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1 && mLeftRightAvailable)
prompts.push_back(HelpPrompt("left/right", "system"));
if (Settings::getInstance()->getString("QuickSystemSelect") != "disabled") {
if (getQuickSystemSelectLeftButton() == "leftshoulder")
prompts.push_back(HelpPrompt("lr", "system"));
else if (getQuickSystemSelectLeftButton() == "lefttrigger")
prompts.push_back(HelpPrompt("ltrt", "system"));
else if (getQuickSystemSelectLeftButton() == "left")
prompts.push_back(HelpPrompt("left/right", "system"));
}
if (mRoot->getSystem()->getThemeFolder() == "custom-collections" && mCursorStack.empty() &&
ViewController::getInstance()->getState().viewing == ViewController::GAMELIST)
prompts.push_back(HelpPrompt("a", "enter"));
prompts.push_back(HelpPrompt("a", "select"));
else
prompts.push_back(HelpPrompt("a", "launch"));
prompts.push_back(HelpPrompt("a", "select"));
prompts.push_back(HelpPrompt("b", "back"));
prompts.push_back(HelpPrompt("x", "view media"));

View file

@ -128,7 +128,7 @@ bool SystemView::input(InputConfig* config, Input input)
return true;
}
if (config->isMappedTo("back", input) &&
if (config->isMappedTo("x", input) &&
Settings::getInstance()->getBool("ScreensaverControls")) {
if (!mWindow->isScreensaverActive()) {
ViewController::getInstance()->stopScrolling();
@ -145,9 +145,6 @@ bool SystemView::input(InputConfig* config, Input input)
void SystemView::update(int deltaTime)
{
if (!mPrimary->isAnimationPlaying(0))
mMaxFade = false;
mPrimary->update(deltaTime);
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents) {
@ -208,6 +205,9 @@ std::vector<HelpPrompt> SystemView::getHelpPrompts()
else
prompts.push_back(HelpPrompt("left/right", "choose"));
}
else if (mGrid != nullptr) {
prompts.push_back(HelpPrompt("up/down/left/right", "choose"));
}
else if (mTextList != nullptr) {
prompts.push_back(HelpPrompt("up/down", "choose"));
}
@ -218,14 +218,50 @@ std::vector<HelpPrompt> SystemView::getHelpPrompts()
prompts.push_back(HelpPrompt("thumbstickclick", "random"));
if (Settings::getInstance()->getBool("ScreensaverControls"))
prompts.push_back(HelpPrompt("back", "screensaver"));
prompts.push_back(HelpPrompt("x", "screensaver"));
return prompts;
}
void SystemView::onCursorChanged(const CursorState& state)
{
int cursor {mPrimary->getCursor()};
const int cursor {mPrimary->getCursor()};
const int scrollVelocity {mPrimary->getScrollingVelocity()};
const std::string& transitionStyle {Settings::getInstance()->getString("TransitionStyle")};
mFadeTransitions = transitionStyle == "fade";
// Some logic needed to avoid various navigation glitches with GridComponent and
// TextListComponent.
if (state == CursorState::CURSOR_STOPPED && mCarousel == nullptr) {
const int numEntries {static_cast<int>(mPrimary->getNumEntries())};
bool doStop {false};
if (cursor == 0 && mLastCursor == numEntries - 1 && std::abs(scrollVelocity) == 1)
doStop = false;
else if (cursor == 0)
doStop = true;
else if (cursor == numEntries - 1 && mLastCursor == 0 && std::abs(scrollVelocity) == 1)
doStop = false;
else if (cursor == numEntries - 1)
doStop = true;
if (!doStop && mGrid != nullptr && std::abs(scrollVelocity) == mGrid->getColumnCount()) {
const int columns {mGrid->getColumnCount()};
const int columnModulus {numEntries % columns};
if (cursor < columns)
doStop = true;
else if (cursor >= numEntries - (columnModulus == 0 ? columns : columnModulus))
doStop = true;
}
if (doStop) {
if (mGrid != nullptr)
mGrid->setScrollVelocity(0);
mPrimary->stopScrolling();
mNavigated = false;
}
}
// Avoid double updates.
if (cursor != mLastCursor) {
@ -240,6 +276,31 @@ void SystemView::onCursorChanged(const CursorState& state)
video->stopVideoPlayer();
}
// This is needed to avoid erratic camera movements during extreme navigation input when using
// slide transitions. This should very rarely occur during normal application usage.
if (transitionStyle == "slide") {
bool resetCamOffset {false};
if (scrollVelocity == -1 && mPreviousScrollVelocity == 1) {
if (mLastCursor > cursor && mCamOffset > static_cast<float>(mLastCursor))
resetCamOffset = true;
else if (mLastCursor > cursor && mCamOffset < static_cast<float>(cursor))
resetCamOffset = true;
else if (mLastCursor < cursor && mCamOffset <= static_cast<float>(cursor) &&
mCamOffset != static_cast<float>(mLastCursor))
resetCamOffset = true;
}
else if (scrollVelocity == 1 && mPreviousScrollVelocity == -1) {
if (mLastCursor > cursor && mCamOffset < static_cast<float>(mLastCursor))
resetCamOffset = true;
else if (mLastCursor < cursor && mCamOffset > static_cast<float>(cursor))
resetCamOffset = true;
}
if (resetCamOffset)
mCamOffset = static_cast<float>(cursor);
}
mLastCursor = cursor;
for (auto& video : mSystemElements[cursor].videoComponents)
@ -255,10 +316,9 @@ void SystemView::onCursorChanged(const CursorState& state)
startViewVideos();
updateHelpPrompts();
int scrollVelocity {mPrimary->getScrollingVelocity()};
const float posMax {static_cast<float>(mPrimary->getNumEntries())};
const float target {static_cast<float>(cursor)};
float startPos {mCamOffset};
float posMax {static_cast<float>(mPrimary->getNumEntries())};
float target {static_cast<float>(cursor)};
float endPos {target};
if (mPreviousScrollVelocity > 0 && scrollVelocity == 0 && mCamOffset > posMax - 1.0f)
@ -290,24 +350,32 @@ void SystemView::onCursorChanged(const CursorState& state)
if (scrollVelocity != 0)
mPreviousScrollVelocity = scrollVelocity;
std::string transitionStyle {Settings::getInstance()->getString("TransitionStyle")};
mFadeTransitions = transitionStyle == "fade";
Animation* anim;
float animTime {400.0f};
float timeMin {200.0f};
float timeDiff {1.0f};
if (mGrid != nullptr) {
animTime = 300.0f;
timeMin = 100.0f;
}
// If startPos is inbetween two positions then reduce the time slightly as the distance will
// be shorter meaning the animation would play for too long if not compensated for.
if (scrollVelocity == 1)
timeDiff = endPos - startPos;
else if (scrollVelocity == -1)
timeDiff = startPos - endPos;
if (timeDiff != 1.0f)
animTime =
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), timeMin, animTime);
if (transitionStyle == "fade") {
float startFade {mFadeOpacity};
anim = new LambdaAnimation(
[this, startFade, startPos, endPos, posMax](float t) {
t -= 1;
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
if (f < 0.0f)
f += posMax;
if (f >= posMax)
f -= posMax;
t += 1;
[this, startFade, endPos](float t) {
if (t < 0.3f)
mFadeOpacity =
glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f));
@ -319,7 +387,7 @@ void SystemView::onCursorChanged(const CursorState& state)
if (t > 0.5f)
mCamOffset = endPos;
if (t >= 0.7f && t != 1.0f)
if (mNavigated && t >= 0.7f && t != 1.0f)
mMaxFade = true;
// Update the game count when the entire animation has been completed.
@ -328,15 +396,17 @@ void SystemView::onCursorChanged(const CursorState& state)
updateGameCount();
}
},
500);
static_cast<int>(animTime * 1.3f));
}
else if (transitionStyle == "slide") {
mUpdatedGameCount = false;
anim = new LambdaAnimation(
[this, startPos, endPos, posMax](float t) {
t -= 1;
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
if (f < 0.0f)
// Non-linear interpolation.
t = 1.0f - (1.0f - t) * (1.0f - t);
float f {(endPos * t) + (startPos * (1.0f - t))};
if (f < 0)
f += posMax;
if (f >= posMax)
f -= posMax;
@ -362,23 +432,13 @@ void SystemView::onCursorChanged(const CursorState& state)
updateGameCount();
}
},
500);
static_cast<int>(animTime));
}
else {
// Instant.
updateGameCount();
anim = new LambdaAnimation(
[this, startPos, endPos, posMax](float t) {
t -= 1;
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
if (f < 0.0f)
f += posMax;
if (f >= posMax)
f -= posMax;
mCamOffset = endPos;
},
500);
anim = new LambdaAnimation([this, endPos](float t) { mCamOffset = endPos; },
static_cast<int>(animTime));
}
setAnimation(anim, 0, nullptr, false, 0);
@ -389,7 +449,7 @@ void SystemView::populate()
if (SystemData::sSystemVector.size() == 0)
return;
LOG(LogDebug) << "SystemView::populate(): Populating carousel";
LOG(LogDebug) << "SystemView::populate(): Populating primary element...";
auto themeSets = ThemeData::getThemeSets();
std::map<std::string, ThemeData::ThemeSet, ThemeData::StringComparator>::const_iterator
@ -405,8 +465,9 @@ void SystemView::populate()
for (auto it : SystemData::sSystemVector) {
const std::shared_ptr<ThemeData>& theme {it->getTheme()};
std::string itemPath;
std::string defaultItemPath;
std::string imagePath;
std::string defaultImagePath;
std::string itemText;
if (mLegacyMode && mViewNeedsReload) {
if (mCarousel == nullptr) {
@ -458,17 +519,27 @@ void SystemView::populate()
ThemeFlags::ALL);
elements.gameSelectors.back()->setNeedsRefresh();
}
if (element.second.type == "textlist" || element.second.type == "carousel") {
if (element.second.type == "carousel" && mTextList != nullptr) {
if (element.second.type == "carousel" || element.second.type == "grid" ||
element.second.type == "textlist") {
if (element.second.type == "carousel" &&
(mGrid != nullptr || mTextList != nullptr)) {
LOG(LogWarning)
<< "SystemView::populate(): Multiple primary components "
<< "defined, skipping <carousel> configuration entry";
<< "defined, skipping carousel configuration entry";
continue;
}
if (element.second.type == "textlist" && mCarousel != nullptr) {
if (element.second.type == "grid" &&
(mCarousel != nullptr || mTextList != nullptr)) {
LOG(LogWarning)
<< "SystemView::populate(): Multiple primary components "
<< "defined, skipping <textlist> configuration entry";
<< "defined, skipping grid configuration entry";
continue;
}
if (element.second.type == "textlist" &&
(mCarousel != nullptr || mGrid != nullptr)) {
LOG(LogWarning)
<< "SystemView::populate(): Multiple primary components "
<< "defined, skipping textlist configuration entry";
continue;
}
if (element.second.type == "carousel" && mCarousel == nullptr) {
@ -476,6 +547,11 @@ void SystemView::populate()
mPrimary = mCarousel.get();
mPrimaryType = PrimaryType::CAROUSEL;
}
else if (element.second.type == "grid" && mGrid == nullptr) {
mGrid = std::make_unique<GridComponent<SystemData*>>();
mPrimary = mGrid.get();
mPrimaryType = PrimaryType::GRID;
}
else if (element.second.type == "textlist" && mTextList == nullptr) {
mTextList = std::make_unique<TextListComponent<SystemData*>>();
mPrimary = mTextList.get();
@ -497,11 +573,21 @@ void SystemView::populate()
anim->setPauseAnimation(true);
}
});
if (mCarousel != nullptr) {
if (element.second.has("staticItem"))
itemPath = element.second.get<std::string>("staticItem");
if (element.second.has("defaultItem"))
defaultItemPath = element.second.get<std::string>("defaultItem");
if (mCarousel != nullptr || mGrid != nullptr) {
if (mCarousel != nullptr) {
// TEMPORARY: Backward compatiblity due to property name changes.
if (element.second.has("staticItem"))
imagePath = element.second.get<std::string>("staticItem");
if (element.second.has("defaultItem"))
defaultImagePath =
element.second.get<std::string>("defaultItem");
}
if (element.second.has("staticImage"))
imagePath = element.second.get<std::string>("staticImage");
if (element.second.has("defaultImage"))
defaultImagePath = element.second.get<std::string>("defaultImage");
if (element.second.has("text"))
itemText = element.second.get<std::string>("text");
}
}
else if (element.second.type == "image") {
@ -663,18 +749,35 @@ void SystemView::populate()
if (mCarousel != nullptr) {
CarouselComponent<SystemData*>::Entry entry;
// Keep showing only the short name for legacy themes to maintain maximum
// backward compatibility. This also applies to unreadable theme sets.
if (mLegacyMode)
if (mLegacyMode) {
// Keep showing only the short name for legacy themes to maintain maximum
// backward compatibility. This also applies to unreadable theme sets.
entry.name = it->getName();
else
entry.name = it->getFullName();
}
else {
if (itemText == "")
entry.name = it->getFullName();
else
entry.name = itemText;
}
letterCaseFunc(entry.name);
entry.object = it;
entry.data.itemPath = itemPath;
entry.data.defaultItemPath = defaultItemPath;
entry.data.imagePath = imagePath;
entry.data.defaultImagePath = defaultImagePath;
mCarousel->addEntry(entry, theme);
}
else if (mGrid != nullptr) {
GridComponent<SystemData*>::Entry entry;
if (itemText == "")
entry.name = it->getFullName();
else
entry.name = itemText;
letterCaseFunc(entry.name);
entry.object = it;
entry.data.imagePath = imagePath;
entry.data.defaultImagePath = defaultImagePath;
mGrid->addEntry(entry, theme);
}
else if (mTextList != nullptr) {
TextListComponent<SystemData*>::Entry entry;
entry.name = it->getFullName();
@ -685,6 +788,9 @@ void SystemView::populate()
}
}
if (mGrid != nullptr)
mGrid->calculateLayout();
for (auto& elements : mSystemElements) {
for (auto& text : elements.textComponents) {
if (text->getThemeSystemdata() != "") {
@ -772,12 +878,12 @@ void SystemView::updateGameSelectors()
if (mLegacyMode)
return;
int cursor {mPrimary->getCursor()};
const int cursor {mPrimary->getCursor()};
if (mSystemElements[cursor].gameSelectors.size() == 0)
return;
bool multipleSelectors {mSystemElements[cursor].gameSelectors.size() > 1};
const bool multipleSelectors {mSystemElements[cursor].gameSelectors.size() > 1};
for (auto& image : mSystemElements[cursor].imageComponents) {
if (image->getThemeImageTypes().size() == 0)
@ -809,76 +915,79 @@ void SystemView::updateGameSelectors()
else {
gameSelector = mSystemElements[cursor].gameSelectors.front().get();
}
const size_t gameSelectorEntry {static_cast<size_t>(
glm::clamp(image->getThemeGameSelectorEntry(), 0u,
static_cast<unsigned int>(gameSelector->getGameCount() - 1)))};
gameSelector->refreshGames();
std::vector<FileData*> games {gameSelector->getGames()};
if (!games.empty()) {
if (games.size() > gameSelectorEntry) {
std::string path;
for (auto& imageType : image->getThemeImageTypes()) {
if (imageType == "image") {
path = games.front()->getImagePath();
path = games.at(gameSelectorEntry)->getImagePath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "miximage") {
path = games.front()->getMiximagePath();
path = games.at(gameSelectorEntry)->getMiximagePath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "marquee") {
path = games.front()->getMarqueePath();
path = games.at(gameSelectorEntry)->getMarqueePath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "screenshot") {
path = games.front()->getScreenshotPath();
path = games.at(gameSelectorEntry)->getScreenshotPath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "titlescreen") {
path = games.front()->getTitleScreenPath();
path = games.at(gameSelectorEntry)->getTitleScreenPath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "cover") {
path = games.front()->getCoverPath();
path = games.at(gameSelectorEntry)->getCoverPath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "backcover") {
path = games.front()->getBackCoverPath();
path = games.at(gameSelectorEntry)->getBackCoverPath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "3dbox") {
path = games.front()->get3DBoxPath();
path = games.at(gameSelectorEntry)->get3DBoxPath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "physicalmedia") {
path = games.front()->getPhysicalMediaPath();
path = games.at(gameSelectorEntry)->getPhysicalMediaPath();
if (path != "") {
image->setImage(path);
break;
}
}
else if (imageType == "fanart") {
path = games.front()->getFanArtPath();
path = games.at(gameSelectorEntry)->getFanArtPath();
if (path != "") {
image->setImage(path);
break;
@ -925,10 +1034,13 @@ void SystemView::updateGameSelectors()
else {
gameSelector = mSystemElements[cursor].gameSelectors.front().get();
}
const size_t gameSelectorEntry {static_cast<size_t>(
glm::clamp(video->getThemeGameSelectorEntry(), 0u,
static_cast<unsigned int>(gameSelector->getGameCount() - 1)))};
gameSelector->refreshGames();
std::vector<FileData*> games {gameSelector->getGames()};
if (!games.empty()) {
if (!video->setVideo(games.front()->getVideoPath()))
if (games.size() > gameSelectorEntry) {
if (!video->setVideo(games.at(gameSelectorEntry)->getVideoPath()))
video->setDefaultVideo();
}
}
@ -963,76 +1075,79 @@ void SystemView::updateGameSelectors()
else {
gameSelector = mSystemElements[cursor].gameSelectors.front().get();
}
const size_t gameSelectorEntry {static_cast<size_t>(
glm::clamp(video->getThemeGameSelectorEntry(), 0u,
static_cast<unsigned int>(gameSelector->getGameCount() - 1)))};
gameSelector->refreshGames();
std::vector<FileData*> games {gameSelector->getGames()};
if (!games.empty()) {
if (games.size() > gameSelectorEntry) {
std::string path;
for (auto& imageType : video->getThemeImageTypes()) {
if (imageType == "image") {
path = games.front()->getImagePath();
path = games.at(gameSelectorEntry)->getImagePath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "miximage") {
path = games.front()->getMiximagePath();
path = games.at(gameSelectorEntry)->getMiximagePath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "marquee") {
path = games.front()->getMarqueePath();
path = games.at(gameSelectorEntry)->getMarqueePath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "screenshot") {
path = games.front()->getScreenshotPath();
path = games.at(gameSelectorEntry)->getScreenshotPath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "titlescreen") {
path = games.front()->getTitleScreenPath();
path = games.at(gameSelectorEntry)->getTitleScreenPath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "cover") {
path = games.front()->getCoverPath();
path = games.at(gameSelectorEntry)->getCoverPath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "backcover") {
path = games.front()->getBackCoverPath();
path = games.at(gameSelectorEntry)->getBackCoverPath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "3dbox") {
path = games.front()->get3DBoxPath();
path = games.at(gameSelectorEntry)->get3DBoxPath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "physicalmedia") {
path = games.front()->getPhysicalMediaPath();
path = games.at(gameSelectorEntry)->getPhysicalMediaPath();
if (path != "") {
video->setImage(path);
break;
}
}
else if (imageType == "fanart") {
path = games.front()->getFanArtPath();
path = games.at(gameSelectorEntry)->getFanArtPath();
if (path != "") {
video->setImage(path);
break;
@ -1078,37 +1193,45 @@ void SystemView::updateGameSelectors()
else {
gameSelector = mSystemElements[cursor].gameSelectors.front().get();
}
const size_t gameSelectorEntry {static_cast<size_t>(
glm::clamp(text->getThemeGameSelectorEntry(), 0u,
static_cast<unsigned int>(gameSelector->getGameCount() - 1)))};
gameSelector->refreshGames();
std::vector<FileData*> games {gameSelector->getGames()};
if (!games.empty()) {
if (games.size() > gameSelectorEntry) {
const std::string metadata {text->getThemeMetadata()};
if (metadata == "name")
text->setValue(games.front()->metadata.get("name"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("name"));
if (metadata == "description")
text->setValue(games.front()->metadata.get("desc"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("desc"));
if (metadata == "rating")
text->setValue(
RatingComponent::getRatingValue(games.front()->metadata.get("rating")));
text->setValue(RatingComponent::getRatingValue(
games.at(gameSelectorEntry)->metadata.get("rating")));
if (metadata == "developer")
text->setValue(games.front()->metadata.get("developer"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("developer"));
if (metadata == "publisher")
text->setValue(games.front()->metadata.get("publisher"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("publisher"));
if (metadata == "genre")
text->setValue(games.front()->metadata.get("genre"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("genre"));
if (metadata == "players")
text->setValue(games.front()->metadata.get("players"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("players"));
if (metadata == "favorite")
text->setValue(games.front()->metadata.get("favorite") == "true" ? "yes" : "no");
text->setValue(
games.at(gameSelectorEntry)->metadata.get("favorite") == "true" ? "yes" : "no");
if (metadata == "completed")
text->setValue(games.front()->metadata.get("completed") == "true" ? "yes" : "no");
text->setValue(games.at(gameSelectorEntry)->metadata.get("completed") == "true" ?
"yes" :
"no");
if (metadata == "kidgame")
text->setValue(games.front()->metadata.get("kidgame") == "true" ? "yes" : "no");
text->setValue(
games.at(gameSelectorEntry)->metadata.get("kidgame") == "true" ? "yes" : "no");
if (metadata == "broken")
text->setValue(games.front()->metadata.get("broken") == "true" ? "yes" : "no");
text->setValue(
games.at(gameSelectorEntry)->metadata.get("broken") == "true" ? "yes" : "no");
if (metadata == "playcount")
text->setValue(games.front()->metadata.get("playcount"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("playcount"));
if (metadata == "altemulator")
text->setValue(games.front()->metadata.get("altemulator"));
text->setValue(games.at(gameSelectorEntry)->metadata.get("altemulator"));
}
else {
text->setValue("");
@ -1145,15 +1268,18 @@ void SystemView::updateGameSelectors()
else {
gameSelector = mSystemElements[cursor].gameSelectors.front().get();
}
const size_t gameSelectorEntry {static_cast<size_t>(
glm::clamp(dateTime->getThemeGameSelectorEntry(), 0u,
static_cast<unsigned int>(gameSelector->getGameCount() - 1)))};
gameSelector->refreshGames();
std::vector<FileData*> games {gameSelector->getGames()};
if (!games.empty()) {
if (games.size() > gameSelectorEntry) {
dateTime->setVisible(true);
const std::string metadata {dateTime->getThemeMetadata()};
if (metadata == "releasedate")
dateTime->setValue(games.front()->metadata.get("releasedate"));
dateTime->setValue(games.at(gameSelectorEntry)->metadata.get("releasedate"));
if (metadata == "lastplayed")
dateTime->setValue(games.front()->metadata.get("lastplayed"));
dateTime->setValue(games.at(gameSelectorEntry)->metadata.get("lastplayed"));
}
else {
dateTime->setVisible(false);
@ -1189,11 +1315,14 @@ void SystemView::updateGameSelectors()
else {
gameSelector = mSystemElements[cursor].gameSelectors.front().get();
}
const size_t gameSelectorEntry {static_cast<size_t>(
glm::clamp(rating->getThemeGameSelectorEntry(), 0u,
static_cast<unsigned int>(gameSelector->getGameCount() - 1)))};
gameSelector->refreshGames();
std::vector<FileData*> games {gameSelector->getGames()};
if (!games.empty()) {
if (games.size() > gameSelectorEntry) {
rating->setVisible(true);
rating->setValue(games.front()->metadata.get("rating"));
rating->setValue(games.at(gameSelectorEntry)->metadata.get("rating"));
}
else {
rating->setVisible(false);
@ -1241,7 +1370,7 @@ void SystemView::renderElements(const glm::mat4& parentTrans, bool abovePrimary)
int renderAfter {static_cast<int>(mCamOffset)};
// If we're transitioning then also render the previous and next systems.
if (mPrimary->isAnimationPlaying(0)) {
if (isAnimationPlaying(0)) {
renderBefore -= 1;
renderAfter += 1;
}
@ -1253,7 +1382,7 @@ void SystemView::renderElements(const glm::mat4& parentTrans, bool abovePrimary)
while (index >= static_cast<int>(mPrimary->getNumEntries()))
index -= static_cast<int>(mPrimary->getNumEntries());
if (mPrimary->isAnimationPlaying(0) || index == mPrimary->getCursor()) {
if (isAnimationPlaying(0) || index == mPrimary->getCursor()) {
glm::mat4 elementTrans {trans};
if (mCarousel != nullptr) {
if (mCarousel->getType() ==
@ -1268,6 +1397,10 @@ void SystemView::renderElements(const glm::mat4& parentTrans, bool abovePrimary)
elementTrans,
glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
}
else if (mGrid != nullptr) {
elementTrans = glm::translate(
elementTrans, glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
}
else if (mTextList != nullptr) {
elementTrans = glm::translate(
elementTrans, glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));

View file

@ -21,6 +21,7 @@
#include "components/TextComponent.h"
#include "components/VideoFFmpegComponent.h"
#include "components/primary/CarouselComponent.h"
#include "components/primary/GridComponent.h"
#include "components/primary/TextListComponent.h"
#include "resources/Font.h"
@ -125,6 +126,7 @@ private:
Renderer* mRenderer;
std::unique_ptr<CarouselComponent<SystemData*>> mCarousel;
std::unique_ptr<GridComponent<SystemData*>> mGrid;
std::unique_ptr<TextListComponent<SystemData*>> mTextList;
std::unique_ptr<TextComponent> mLegacySystemInfo;
std::vector<SystemViewElements> mSystemElements;

View file

@ -33,6 +33,7 @@ set(CORE_HEADERS
# Primary GUI components
${CMAKE_CURRENT_SOURCE_DIR}/src/components/primary/CarouselComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/primary/GridComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/primary/PrimaryComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/primary/TextListComponent.h

View file

@ -19,6 +19,7 @@
GuiComponent::GuiComponent()
: mWindow {Window::getInstance()}
, mParent {nullptr}
, mThemeGameSelectorEntry {0}
, mColor {0}
, mColorShift {0}
, mColorShiftEnd {0}
@ -29,6 +30,7 @@ GuiComponent::GuiComponent()
, mOrigin {0.0f, 0.0f}
, mRotationOrigin {0.5f, 0.5f}
, mSize {0.0f, 0.0f}
, mBrightness {0.0f}
, mOpacity {1.0f}
, mSaturation {1.0f}
, mDimming {1.0f}
@ -183,6 +185,16 @@ const int GuiComponent::getChildIndex() const
return -1;
}
void GuiComponent::setBrightness(float brightness)
{
if (mBrightness == brightness)
return;
mBrightness = brightness;
for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it)
(*it)->setBrightness(brightness);
}
void GuiComponent::setOpacity(float opacity)
{
if (mOpacity == opacity)
@ -371,6 +383,9 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else
setZIndex(getDefaultZIndex());
if (properties & ThemeFlags::BRIGHTNESS && elem->has("brightness"))
mBrightness = glm::clamp(elem->get<float>("brightness"), -2.0f, 2.0f);
if (properties & ThemeFlags::OPACITY && elem->has("opacity"))
mThemeOpacity = glm::clamp(elem->get<float>("opacity"), 0.0f, 1.0f);
@ -384,6 +399,9 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (properties && elem->has("gameselector"))
mThemeGameSelector = elem->get<std::string>("gameselector");
if (properties && elem->has("gameselectorEntry"))
mThemeGameSelectorEntry = elem->get<unsigned int>("gameselectorEntry");
}
void GuiComponent::updateHelpPrompts()

View file

@ -104,8 +104,8 @@ public:
virtual glm::vec2 getSize() const { return mSize; }
void setSize(const glm::vec2& size) { setSize(size.x, size.y); }
void setSize(const float w, const float h);
virtual void setResize(float width, float height) {}
virtual void setResize(float width, float height, bool rasterize) {}
virtual void setResize(const float width, const float height) {}
virtual void setResize(const glm::vec2& size, bool rasterize = true) {}
virtual void onSizeChanged() {}
virtual glm::vec2 getRotationSize() const { return getSize(); }
@ -222,8 +222,10 @@ public:
virtual void stopGamelistFadeAnimations() {}
virtual bool isListScrolling() { return false; }
virtual void stopListScrolling() {}
virtual const float getBrightness() const { return mBrightness; }
virtual const float getOpacity() const { return mOpacity; }
virtual const float getColorOpacity() const { return 1.0f; }
virtual void setBrightness(float brightness);
virtual void setOpacity(float opacity);
virtual float getSaturation() const { return static_cast<float>(mColor); }
virtual void setSaturation(float saturation) { mSaturation = saturation; }
@ -257,7 +259,8 @@ public:
const std::string& getThemeMetadata() { return mThemeMetadata; }
void setThemeMetadata(const std::string& text) { mThemeMetadata = text; }
const std::vector<std::string>& getThemeImageTypes() { return mThemeImageTypes; }
const std::string& getThemeGameSelector() { return mThemeGameSelector; }
const std::string& getThemeGameSelector() const { return mThemeGameSelector; }
const unsigned int getThemeGameSelectorEntry() const { return mThemeGameSelectorEntry; }
virtual std::shared_ptr<Font> getFont() const { return nullptr; }
@ -323,6 +326,7 @@ protected:
std::string mThemeSystemdata;
std::string mThemeMetadata;
std::string mThemeGameSelector;
unsigned int mThemeGameSelectorEntry;
unsigned int mColor;
unsigned int mColorShift;
@ -336,6 +340,7 @@ protected:
glm::vec2 mRotationOrigin;
glm::vec2 mSize;
float mBrightness;
float mOpacity;
float mSaturation;
float mDimming;

View file

@ -63,7 +63,7 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
iconColorDimmed = iconColor;
if (elem->has("fontPath") || elem->has("fontSize"))
font = Font::getFromTheme(elem, ThemeFlags::ALL, font, 0.0f, theme->isLegacyTheme());
font = Font::getFromTheme(elem, ThemeFlags::ALL, font, 0.0f, false, theme->isLegacyTheme());
if (elem->has("entrySpacing"))
entrySpacing = glm::clamp(elem->get<float>("entrySpacing"), 0.0f, 0.04f);
@ -100,6 +100,8 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
mCustomButtons.button_lt = elem->get<std::string>(PREFIX "button_lt");
if (elem->has(PREFIX "button_rt"))
mCustomButtons.button_rt = elem->get<std::string>(PREFIX "button_rt");
if (elem->has(PREFIX "button_ltrt"))
mCustomButtons.button_ltrt = elem->get<std::string>(PREFIX "button_ltrt");
// SNES.
if (elem->has(PREFIX "button_a_SNES"))

View file

@ -42,6 +42,7 @@ struct HelpStyle {
std::string button_lr;
std::string button_lt;
std::string button_rt;
std::string button_ltrt;
// SNES
std::string button_a_SNES;

View file

@ -88,7 +88,8 @@ void Settings::setDefaults()
mBoolMap["ScrapeGameNames"] = {true, true};
mBoolMap["ScrapeRatings"] = {true, true};
mBoolMap["ScrapeControllers"] = {true, true};
// ScreenScraper controller scraping is currently broken, it's unclear if they will fix it.
// mBoolMap["ScrapeControllers"] = {true, true};
mBoolMap["ScrapeMetadata"] = {true, true};
mBoolMap["ScrapeVideos"] = {true, true};
mBoolMap["ScrapeScreenshots"] = {true, true};
@ -136,6 +137,7 @@ void Settings::setDefaults()
mStringMap["ThemeAspectRatio"] = {"", ""};
mStringMap["GamelistViewStyle"] = {"automatic", "automatic"};
mStringMap["TransitionStyle"] = {"slide", "slide"};
mStringMap["QuickSystemSelect"] = {"leftrightshoulders", "leftrightshoulders"};
mStringMap["StartupSystem"] = {"", ""};
mStringMap["DefaultSortOrder"] = {"filename, ascending", "filename, ascending"};
mStringMap["MenuOpeningEffect"] = {"scale-up", "scale-up"};
@ -190,7 +192,6 @@ void Settings::setDefaults()
mBoolMap["FavoritesAddButton"] = {true, true};
mBoolMap["RandomAddButton"] = {false, false};
mBoolMap["GamelistFilters"] = {true, true};
mBoolMap["QuickSystemSelect"] = {true, true};
mBoolMap["ShowHelpPrompts"] = {true, true};
// Sound settings.

View file

@ -43,7 +43,7 @@ std::vector<std::string> ThemeData::sLegacySupportedFeatures {
{"z-index"},
{"visible"}};
std::vector<std::string> ThemeData::sLegacyElements {
std::vector<std::string> ThemeData::sLegacyProperties {
{"showSnapshotNoVideo"},
{"showSnapshotDelay"},
{"forceUppercase"},
@ -56,6 +56,12 @@ std::vector<std::string> ThemeData::sLegacyElements {
{"logoAlignment"},
{"maxLogoCount"}};
std::vector<std::string> ThemeData::sDeprecatedProperties {
{"staticItem"},
{"itemType"},
{"defaultItem"},
{"itemInterpolation"}};
std::vector<std::pair<std::string, std::string>> ThemeData::sSupportedAspectRatios {
{"automatic", "automatic"},
{"16:9", "16:9"},
@ -101,6 +107,140 @@ std::map<std::string, std::map<std::string, std::string>> ThemeData::sPropertyAt
std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
ThemeData::sElementMap {
{"carousel",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"type", STRING},
{"staticImage", PATH},
{"imageType", STRING},
{"defaultImage", PATH},
{"staticItem", PATH}, // TEMPORARY: For backward compatibility.
{"itemType", STRING}, // TEMPORARY: For backward compatibility.
{"defaultItem", PATH}, // TEMPORARY: For backward compatibility.
{"maxItemCount", FLOAT},
{"maxLogoCount", FLOAT}, // For backward compatibility with legacy themes.
{"itemsBeforeCenter", UNSIGNED_INTEGER},
{"itemsAfterCenter", UNSIGNED_INTEGER},
{"itemStacking", STRING},
{"itemSize", NORMALIZED_PAIR},
{"itemScale", FLOAT},
{"itemRotation", FLOAT},
{"itemRotationOrigin", NORMALIZED_PAIR},
{"itemAxisHorizontal", BOOLEAN},
{"itemInterpolation", STRING}, // TEMPORARY: For backward compatibility.
{"imageInterpolation", STRING},
{"imageColor", COLOR},
{"imageColorEnd", COLOR},
{"imageGradientType", STRING},
{"imageBrightness", FLOAT},
{"imageSaturation", FLOAT},
{"itemTransitions", STRING},
{"itemHorizontalAlignment", STRING},
{"itemVerticalAlignment", STRING},
{"wheelHorizontalAlignment", STRING},
{"horizontalOffset", FLOAT},
{"verticalOffset", FLOAT},
{"reflections", BOOLEAN},
{"reflectionsOpacity", FLOAT},
{"reflectionsFalloff", FLOAT},
{"unfocusedItemOpacity", FLOAT},
{"defaultLogo", PATH}, // For backward compatibility with legacy themes.
{"logoSize", NORMALIZED_PAIR}, // For backward compatibility with legacy themes.
{"logoScale", FLOAT}, // For backward compatibility with legacy themes.
{"logoRotation", FLOAT}, // For backward compatibility with legacy themes.
{"logoRotationOrigin", NORMALIZED_PAIR}, // For backward compatibility with legacy themes.
{"logoAlignment", STRING}, // For backward compatibility with legacy themes.
{"color", COLOR},
{"colorEnd", COLOR},
{"gradientType", STRING},
{"text", STRING},
{"textColor", COLOR},
{"textBackgroundColor", COLOR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"letterCase", STRING},
{"letterCaseCollections", STRING},
{"letterCaseGroupedCollections", STRING},
{"lineSpacing", FLOAT},
{"fadeAbovePrimary", BOOLEAN},
{"zIndex", FLOAT},
{"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes.
{"grid",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"staticImage", PATH},
{"imageType", STRING},
{"defaultImage", PATH},
{"itemSize", NORMALIZED_PAIR},
{"itemScale", FLOAT},
{"itemSpacing", NORMALIZED_PAIR},
{"fractionalRows", BOOLEAN},
{"itemTransitions", STRING},
{"rowTransitions", STRING},
{"unfocusedItemOpacity", FLOAT},
{"edgeScaleInwards", BOOLEAN}, // TODO
{"imageFit", STRING},
{"imageRelativeScale", FLOAT},
{"imageColor", COLOR},
{"imageColorEnd", COLOR},
{"imageGradientType", STRING},
{"imageBrightness", FLOAT},
{"imageSaturation", FLOAT},
{"backgroundImage", PATH},
{"backgroundRelativeScale", FLOAT},
{"backgroundColor", COLOR},
{"backgroundColorEnd", COLOR},
{"backgroundGradientType", STRING},
{"selectorImage", PATH},
{"selectorRelativeScale", FLOAT},
{"selectorLayer", STRING},
{"selectorColor", COLOR},
{"selectorColorEnd", COLOR},
{"selectorGradientType", STRING},
{"text", STRING},
{"textRelativeScale", FLOAT},
{"textColor", COLOR},
{"textBackgroundColor", COLOR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"letterCase", STRING},
{"letterCaseCollections", STRING},
{"letterCaseGroupedCollections", STRING},
{"lineSpacing", FLOAT},
{"fadeAbovePrimary", BOOLEAN},
{"zIndex", FLOAT}}},
{"textlist",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"selectorHeight", FLOAT},
{"selectorOffsetY", FLOAT},
{"selectorColor", COLOR},
{"selectorColorEnd", COLOR},
{"selectorGradientType", STRING},
{"selectorImagePath", PATH},
{"selectorImageTile", BOOLEAN},
{"primaryColor", COLOR},
{"secondaryColor", COLOR},
{"selectedColor", COLOR},
{"selectedSecondaryColor", COLOR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"scrollSound", PATH}, // For backward compatibility with legacy themes.
{"horizontalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"horizontalMargin", FLOAT},
{"letterCase", STRING},
{"letterCaseCollections", STRING},
{"letterCaseGroupedCollections", STRING},
{"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
{"lineSpacing", FLOAT},
{"indicators", STRING},
{"collectionIndicators", STRING},
{"fadeAbovePrimary", BOOLEAN},
{"zIndex", FLOAT}}},
{"image",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
@ -113,6 +253,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"imageType", STRING},
{"metadataElement", BOOLEAN},
{"gameselector", STRING},
{"gameselectorEntry", UNSIGNED_INTEGER},
{"tile", BOOLEAN},
{"tileSize", NORMALIZED_PAIR},
{"tileHorizontalAlignment", STRING},
@ -122,6 +263,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"colorEnd", COLOR},
{"gradientType", STRING},
{"scrollFadeIn", BOOLEAN},
{"brightness", FLOAT},
{"opacity", FLOAT},
{"saturation", FLOAT},
{"visible", BOOLEAN},
@ -137,14 +279,19 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"imageType", STRING},
{"metadataElement", BOOLEAN},
{"gameselector", STRING},
{"gameselectorEntry", UNSIGNED_INTEGER},
{"audio", BOOLEAN},
{"interpolation", STRING},
{"color", COLOR},
{"colorEnd", COLOR},
{"gradientType", STRING},
{"pillarboxes", BOOLEAN},
{"pillarboxThreshold", NORMALIZED_PAIR},
{"scanlines", BOOLEAN},
{"delay", FLOAT},
{"fadeInTime", FLOAT},
{"scrollFadeIn", BOOLEAN},
{"brightness", FLOAT},
{"opacity", FLOAT},
{"saturation", FLOAT},
{"visible", BOOLEAN},
@ -163,6 +310,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"direction", STRING},
{"keepAspectRatio", BOOLEAN},
{"interpolation", STRING},
{"brightness", FLOAT},
{"opacity", FLOAT},
{"saturation", FLOAT},
{"visible", BOOLEAN},
@ -201,6 +349,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"metadata", STRING},
{"metadataElement", BOOLEAN},
{"gameselector", STRING},
{"gameselectorEntry", UNSIGNED_INTEGER},
{"container", BOOLEAN},
{"containerVerticalSnap", BOOLEAN},
{"containerScrollSpeed", FLOAT},
@ -227,6 +376,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"rotationOrigin", NORMALIZED_PAIR},
{"metadata", STRING},
{"gameselector", STRING},
{"gameselectorEntry", UNSIGNED_INTEGER},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"horizontalAlignment", STRING},
@ -265,6 +415,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"rotation", FLOAT},
{"rotationOrigin", NORMALIZED_PAIR},
{"gameselector", STRING},
{"gameselectorEntry", UNSIGNED_INTEGER},
{"interpolation", STRING},
{"color", COLOR},
{"filledPath", PATH},
@ -273,85 +424,6 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"carousel",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"type", STRING},
{"staticItem", PATH},
{"itemType", STRING},
{"defaultItem", PATH},
{"maxItemCount", FLOAT},
{"maxLogoCount", FLOAT}, // For backward compatibility with legacy themes.
{"itemsBeforeCenter", UNSIGNED_INTEGER},
{"itemsAfterCenter", UNSIGNED_INTEGER},
{"itemSize", NORMALIZED_PAIR},
{"itemScale", FLOAT},
{"itemTransitions", STRING},
{"itemInterpolation", STRING},
{"itemRotation", FLOAT},
{"itemRotationOrigin", NORMALIZED_PAIR},
{"itemAxisHorizontal", BOOLEAN},
{"itemHorizontalAlignment", STRING},
{"itemVerticalAlignment", STRING},
{"wheelHorizontalAlignment", STRING},
{"horizontalOffset", FLOAT},
{"verticalOffset", FLOAT},
{"reflections", BOOLEAN},
{"reflectionsOpacity", FLOAT},
{"reflectionsFalloff", FLOAT},
{"unfocusedItemOpacity", FLOAT},
{"defaultLogo", PATH}, // For backward compatibility with legacy themes.
{"logoSize", NORMALIZED_PAIR}, // For backward compatibility with legacy themes.
{"logoScale", FLOAT}, // For backward compatibility with legacy themes.
{"logoRotation", FLOAT}, // For backward compatibility with legacy themes.
{"logoRotationOrigin", NORMALIZED_PAIR}, // For backward compatibility with legacy themes.
{"logoAlignment", STRING}, // For backward compatibility with legacy themes.
{"color", COLOR},
{"colorEnd", COLOR},
{"gradientType", STRING},
{"text", STRING},
{"textColor", COLOR},
{"textBackgroundColor", COLOR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"letterCase", STRING},
{"letterCaseCollections", STRING},
{"letterCaseGroupedCollections", STRING},
{"lineSpacing", FLOAT},
{"fadeAbovePrimary", BOOLEAN},
{"zIndex", FLOAT},
{"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes.
{"textlist",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"selectorHeight", FLOAT},
{"selectorOffsetY", FLOAT},
{"selectorColor", COLOR},
{"selectorColorEnd", COLOR},
{"selectorGradientType", STRING},
{"selectorImagePath", PATH},
{"selectorImageTile", BOOLEAN},
{"primaryColor", COLOR},
{"secondaryColor", COLOR},
{"selectedColor", COLOR},
{"selectedSecondaryColor", COLOR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"scrollSound", PATH}, // For backward compatibility with legacy themes.
{"horizontalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"horizontalMargin", FLOAT},
{"letterCase", STRING},
{"letterCaseCollections", STRING},
{"letterCaseGroupedCollections", STRING},
{"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
{"lineSpacing", FLOAT},
{"indicators", STRING},
{"collectionIndicators", STRING},
{"fadeAbovePrimary", BOOLEAN},
{"zIndex", FLOAT}}},
{"gameselector",
{{"selection", STRING},
{"gameCount", UNSIGNED_INTEGER}}},
@ -370,8 +442,6 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"textStyle", STRING}, // For backward compatibility with legacy themes.
{"opacity", FLOAT},
{"customButtonIcon", PATH}}},
{"sound",
{{"path", PATH}}},
{"navigationsounds",
{{"systembrowseSound", PATH},
{"quicksysselectSound", PATH},
@ -381,6 +451,8 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"favoriteSound", PATH},
{"launchSound", PATH}}},
// Legacy components below, not in use any longer but needed for backward compatibility.
{"sound",
{{"path", PATH}}},
{"imagegrid",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
@ -1415,17 +1487,29 @@ void ThemeData::parseElement(const pugi::xml_node& root,
std::string nodeName = node.name();
// Strictly enforce removal of legacy elements for non-legacy theme sets by creating
// Strictly enforce removal of legacy properties for non-legacy theme sets by creating
// an unthemed system if they're present in the configuration.
if (!mLegacyTheme) {
for (auto& legacyElement : sLegacyElements) {
if (nodeName == legacyElement) {
for (auto& legacyProperty : sLegacyProperties) {
if (nodeName == legacyProperty) {
throw error << ": Legacy <" << nodeName
<< "> property found for non-legacy theme set";
}
}
}
// Print a warning if a deprecated property is used for a non-legacy theme set.
if (!mLegacyTheme) {
for (auto& deprecatedProperty : sDeprecatedProperties) {
if (nodeName == deprecatedProperty) {
LOG(LogWarning)
<< "ThemeData::parseElement(): Property \"" << deprecatedProperty
<< "\" is deprecated and support for it will be removed in a future "
"version";
}
}
}
// If an attribute exists, then replace nodeName with its name.
auto attributeEntry = sPropertyAttributeMap.find(element.type);
if (attributeEntry != sPropertyAttributeMap.end()) {

View file

@ -57,9 +57,10 @@ namespace ThemeFlags
DELAY = 0x00002000,
Z_INDEX = 0x00004000,
ROTATION = 0x00008000,
OPACITY = 0x00010000,
SATURATION = 0x00020000,
VISIBLE = 0x00040000,
BRIGHTNESS = 0x00010000,
OPACITY = 0x00020000,
SATURATION = 0x00040000,
VISIBLE = 0x00080000,
ALL = 0xFFFFFFFF
};
// clang-format on
@ -281,7 +282,8 @@ private:
static std::vector<std::string> sSupportedViews;
static std::vector<std::string> sLegacySupportedViews;
static std::vector<std::string> sLegacySupportedFeatures;
static std::vector<std::string> sLegacyElements;
static std::vector<std::string> sLegacyProperties;
static std::vector<std::string> sDeprecatedProperties;
static std::vector<std::pair<std::string, std::string>> sSupportedAspectRatios;
static std::map<std::string, float> sAspectRatioMap;

View file

@ -680,14 +680,14 @@ void Window::setHelpPrompts(const std::vector<HelpPrompt>& prompts, const HelpSt
static const std::vector<std::string> map = {"up/down/left/right",
"up/down",
"left/right",
"a",
"b",
"x",
"y",
"r",
"l",
"rt",
"lt",
"r",
"l",
"y",
"x",
"b",
"a",
"start",
"back"};
int i = 0;

View file

@ -9,6 +9,7 @@
#include "animations/AnimationController.h"
#include "animations/Animation.h"
#include "utils/MathUtil.h"
AnimationController::AnimationController(Animation* anim,
int delay,
@ -37,16 +38,10 @@ bool AnimationController::update(int deltaTime)
if (mTime < 0) // Are we still in delay?
return false;
float t = static_cast<float>(mTime) / mAnimation->getDuration();
float animTime {glm::clamp(static_cast<float>(mTime) / mAnimation->getDuration(), 0.0f, 1.0f)};
mAnimation->apply(mReverse ? 1.0f - animTime : animTime);
if (t > 1.0f)
t = 1.0f;
else if (t < 0.0f)
t = 0.0f;
mAnimation->apply(mReverse ? 1.0f - t : t);
if (t == 1.0f)
if (animTime == 1.0f)
return true;
return false;

View file

@ -32,7 +32,7 @@ void AnimatedImageComponent::load(const AnimationDef* def)
}
auto img = std::unique_ptr<ImageComponent>(new ImageComponent);
img->setResize(mSize.x, mSize.y);
img->setResize(mSize);
img->setImage(std::string(def->frames[i].path), false);
mFrames.push_back(ImageFrame(std::move(img), def->frames[i].time));
@ -53,9 +53,8 @@ void AnimatedImageComponent::reset()
void AnimatedImageComponent::onSizeChanged()
{
for (auto it = mFrames.cbegin(); it != mFrames.cend(); ++it) {
it->first->setResize(mSize.x, mSize.y);
}
for (auto it = mFrames.cbegin(); it != mFrames.cend(); ++it)
it->first->setResize(mSize);
}
void AnimatedImageComponent::update(int deltaTime)

View file

@ -40,6 +40,7 @@ namespace
{"gamepad_xbox", "Gamepad (Xbox)", ":/graphics/controllers/gamepad_xbox.svg"},
{"joystick_generic", "Joystick (Generic)", ":/graphics/controllers/joystick_generic.svg"},
{"joystick_arcade_no_buttons", "Joystick (Arcade No Buttons)", ":/graphics/controllers/joystick_arcade_no_buttons.svg"},
{"joystick_arcade_no_buttons_twin", "Joystick (Arcade No Buttons Twin Stick)", ":/graphics/controllers/joystick_arcade_no_buttons_twin.svg"},
{"joystick_arcade_1_button", "Joystick (Arcade 1 Button)", ":/graphics/controllers/joystick_arcade_1_button.svg"},
{"joystick_arcade_2_buttons", "Joystick (Arcade 2 Buttons)", ":/graphics/controllers/joystick_arcade_2_buttons.svg"},
{"joystick_arcade_3_buttons", "Joystick (Arcade 3 Buttons)", ":/graphics/controllers/joystick_arcade_3_buttons.svg"},

View file

@ -224,5 +224,5 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (properties & LINE_SPACING && elem->has("lineSpacing"))
setLineSpacing(glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f));
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, theme->isLegacyTheme()));
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, false, theme->isLegacyTheme()));
}

View file

@ -38,7 +38,7 @@ void FlexboxComponent::render(const glm::mat4& parentTrans)
return;
if (!mLayoutValid)
computeLayout();
calculateLayout();
glm::mat4 trans {parentTrans * getTransform()};
mRenderer->setMatrix(trans);
@ -82,7 +82,7 @@ void FlexboxComponent::setItemMargin(glm::vec2 value)
mLayoutValid = false;
}
void FlexboxComponent::computeLayout()
void FlexboxComponent::calculateLayout()
{
// If we're not clamping itemMargin to a reasonable value, all kinds of weird rendering
// issues could occur.

View file

@ -78,7 +78,7 @@ public:
private:
// Calculate flexbox layout.
void computeLayout();
void calculateLayout();
Renderer* mRenderer;
std::vector<FlexboxItem>& mItems;

View file

@ -495,6 +495,7 @@ void GIFAnimComponent::render(const glm::mat4& parentTrans)
for (int i = 0; i < 4; ++i)
vertices[i].position = glm::round(vertices[i].position);
vertices->brightness = mBrightness;
vertices->saturation = mSaturation * mThemeSaturation;
vertices->opacity = mOpacity * mThemeOpacity;
vertices->dimming = mDimming;

View file

@ -45,6 +45,7 @@ public:
const bool getNeedsRefresh() { return mNeedsRefresh; }
const GameSelection getGameSelection() const { return mGameSelection; }
const std::string& getSelectorName() const { return mSelectorName; }
const int getGameCount() const { return mGameCount; }
void refreshGames()
{
@ -63,9 +64,12 @@ public:
Settings::getInstance()->getBool("ForceKid"))};
if (mGameSelection == GameSelection::RANDOM) {
for (int i = 0; i < mGameCount; ++i) {
int tries {8};
for (int i {0}; i < mGameCount; ++i) {
if (mSystem->getRootFolder()->getGameCount().first == 0)
break;
if (mSystem->getRootFolder()->getGameCount().first == mGames.size())
break;
FileData* randomGame {nullptr};
if (mGameCount > 1 || lastGame == nullptr ||
@ -74,6 +78,14 @@ public:
else
randomGame = mSystem->getRandomGame(lastGame, true);
if (std::find(mGames.begin(), mGames.end(), randomGame) != mGames.end()) {
if (tries > 0) {
--i;
--tries;
}
continue;
}
if (randomGame != nullptr)
mGames.emplace_back(randomGame);
}

View file

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

View file

@ -3,7 +3,7 @@
// EmulationStation Desktop Edition
// IList.h
//
// List base class, used by both the gamelist views and the menu.
// List base class, used by the system view, gamelist view and menu system.
//
#ifndef ES_CORE_COMPONENTS_ILIST_H
@ -73,6 +73,9 @@ protected:
const ScrollTierList& mTierList;
const ListLoopType mLoopType;
int mCursor;
int mLastCursor;
int mColumns;
int mRows;
int mScrollTier;
int mScrollVelocity;
int mScrollTierAccumulator;
@ -88,6 +91,9 @@ public:
, mTierList {tierList}
, mLoopType {loopType}
, mCursor {0}
, mLastCursor {0}
, mColumns {0}
, mRows {0}
, mScrollTier {0}
, mScrollVelocity {0}
, mScrollTierAccumulator {0}
@ -117,6 +123,7 @@ public:
{
mEntries.clear();
mCursor = 0;
mLastCursor = 0;
listInput(0);
onCursorChanged(CursorState::CURSOR_STOPPED);
}
@ -213,7 +220,10 @@ protected:
bool listFirstRow()
{
mLastCursor = mCursor;
mCursor = 0;
mScrollVelocity = 0;
mScrollTier = 0;
onCursorChanged(CursorState::CURSOR_STOPPED);
onScroll();
return true;
@ -221,7 +231,10 @@ protected:
bool listLastRow()
{
mLastCursor = mCursor;
mCursor = static_cast<int>(mEntries.size()) - 1;
mScrollVelocity = 0;
mScrollTier = 0;
onCursorChanged(CursorState::CURSOR_STOPPED);
onScroll();
return true;
@ -271,7 +284,7 @@ protected:
}
// Actually perform the scrolling.
for (int i = 0; i < scrollCount; ++i)
for (int i {0}; i < scrollCount; ++i)
scroll(mScrollVelocity);
}
@ -287,7 +300,7 @@ protected:
}
std::string titleIndex;
bool favoritesSorting;
bool favoritesSorting {true};
if (getSelected()->getSystem()->isCustomCollection())
favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom");
@ -323,6 +336,29 @@ protected:
if (mScrollVelocity == 0 || size() < 2)
return;
bool doScroll {true};
// This is only needed for GridComponent.
if (mColumns > 1 && mScrollVelocity == -mColumns && mCursor < mColumns) {
doScroll = false;
}
else if (mColumns != 0 && mScrollVelocity == mColumns) {
if (size() - mCursor <= size() % mColumns)
doScroll = false;
else if (mColumns != 1 && mCursor >= (mColumns * mRows) - mColumns &&
size() - mCursor <= mColumns && size() % mColumns == 0)
doScroll = false;
else if (size() < mColumns)
doScroll = false;
}
mLastCursor = mCursor;
if (!doScroll) {
onCursorChanged(CursorState::CURSOR_STOPPED);
return;
}
int cursor {mCursor + amt};
int absAmt {amt < 0 ? -amt : amt};

View file

@ -22,6 +22,7 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic)
, mFlipX {false}
, mFlipY {false}
, mTargetIsMax {false}
, mTargetIsCrop {false}
, mTileWidth {0.0f}
, mTileHeight {0.0f}
, mColorShift {0xFFFFFFFF}
@ -93,6 +94,8 @@ void ImageComponent::setImage(const std::string& path, bool tile)
mMipmapping, static_cast<size_t>(mSize.x),
static_cast<size_t>(mSize.y), mTileWidth, mTileHeight);
mTexture->rasterizeAt(mSize.x, mSize.y);
if (mTargetIsCrop)
coverFitCrop();
onSizeChanged();
}
}
@ -123,17 +126,19 @@ void ImageComponent::setImage(const std::shared_ptr<TextureResource>& texture, b
resize();
}
void ImageComponent::setResize(float width, float height)
void ImageComponent::setResize(const float width, const float height)
{
mTargetSize = glm::vec2 {width, height};
mTargetIsMax = false;
mTargetIsCrop = false;
resize();
}
void ImageComponent::setResize(float width, float height, bool rasterize)
void ImageComponent::setResize(const glm::vec2& size, bool rasterize)
{
mTargetSize = glm::vec2 {width, height};
mTargetSize = size;
mTargetIsMax = false;
mTargetIsCrop = false;
resize(rasterize);
}
@ -141,39 +146,48 @@ void ImageComponent::setMaxSize(const float width, const float height)
{
mTargetSize = glm::vec2 {width, height};
mTargetIsMax = true;
mTargetIsCrop = false;
resize();
}
void ImageComponent::cropLeft(const float percent)
void ImageComponent::setCroppedSize(const glm::vec2& size)
{
assert(percent >= 0.0f && percent <= 1.0f);
mTopLeftCrop.x = percent;
mTargetSize = size;
mTargetIsMax = false;
mTargetIsCrop = true;
resize();
}
void ImageComponent::cropTop(const float percent)
void ImageComponent::cropLeft(const float value)
{
assert(percent >= 0.0f && percent <= 1.0f);
mTopLeftCrop.y = percent;
assert(value >= 0.0f && value <= 1.0f);
mTopLeftCrop.x = value;
}
void ImageComponent::cropRight(const float percent)
void ImageComponent::cropTop(const float value)
{
assert(percent >= 0.0f && percent <= 1.0f);
mBottomRightCrop.x = 1.0f - percent;
assert(value >= 0.0f && value <= 1.0f);
mTopLeftCrop.y = value;
}
void ImageComponent::cropBot(const float percent)
void ImageComponent::cropRight(const float value)
{
assert(percent >= 0.0f && percent <= 1.0f);
mBottomRightCrop.y = 1.0f - percent;
assert(value >= 0.0f && value <= 1.0f);
mBottomRightCrop.x = 1.0f - value;
}
void ImageComponent::crop(const float left, const float top, const float right, const float bot)
void ImageComponent::cropBottom(const float value)
{
assert(value >= 0.0f && value <= 1.0f);
mBottomRightCrop.y = 1.0f - value;
}
void ImageComponent::crop(const float left, const float top, const float right, const float bottom)
{
cropLeft(left);
cropTop(top);
cropRight(right);
cropBot(bot);
cropBottom(bottom);
}
void ImageComponent::uncrop()
@ -182,6 +196,24 @@ void ImageComponent::uncrop()
crop(0.0f, 0.0f, 0.0f, 0.0f);
}
void ImageComponent::coverFitCrop()
{
assert(mTargetIsCrop);
if (std::round(mSize.y) > std::round(mTargetSize.y)) {
const float cropSize {1.0f - (mTargetSize.y / mSize.y)};
cropTop(cropSize / 2.0f);
cropBottom(cropSize / 2.0f);
mSize.y = mSize.y - (mSize.y * cropSize);
}
else {
const float cropSize {1.0f - (mTargetSize.x / mSize.x)};
cropLeft(cropSize / 2.0f);
cropRight(cropSize / 2.0f);
mSize.x = mSize.x - (mSize.x * cropSize);
}
}
void ImageComponent::cropTransparentPadding(const float maxSizeX, const float maxSizeY)
{
if (mSize == glm::vec2 {0.0f, 0.0f})
@ -362,8 +394,9 @@ void ImageComponent::render(const glm::mat4& parentTrans)
else
fadeIn(mTexture->bind());
mVertices->saturation = mSaturation * mThemeSaturation;
mVertices->brightness = mBrightness;
mVertices->opacity = mThemeOpacity;
mVertices->saturation = mSaturation * mThemeSaturation;
mVertices->dimming = mDimming;
mVertices->reflectionsFalloff = mReflectionsFalloff;
@ -562,7 +595,7 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (elem->has("colorEnd"))
setColorShiftEnd(elem->get<unsigned int>("colorEnd"));
if (elem->has("gradientType")) {
const std::string gradientType {elem->get<std::string>("gradientType")};
const std::string& gradientType {elem->get<std::string>("gradientType")};
if (gradientType == "horizontal") {
setColorGradientHorizontal(true);
}
@ -617,6 +650,12 @@ void ImageComponent::resize(bool rasterize)
mSize.x = std::min((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x);
}
}
else if (mTargetIsCrop) {
// Size texture to allow for cropped image to fill the entire area.
const float cropFactor {
std::max(mTargetSize.x / textureSize.x, mTargetSize.y / textureSize.y)};
mSize = textureSize * cropFactor;
}
else {
// If both axes are set we just stretch or squash, if no axes are set we do nothing.
mSize = mTargetSize == glm::vec2 {0.0f, 0.0f} ? textureSize : mTargetSize;
@ -640,6 +679,8 @@ void ImageComponent::resize(bool rasterize)
if (rasterize) {
mTexture->rasterizeAt(mSize.x, mSize.y);
if (mTargetIsCrop)
coverFitCrop();
onSizeChanged();
}
}

View file

@ -39,11 +39,7 @@ public:
// Can be set before or after an image is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(const float width, const float height) override;
void setResize(const glm::vec2& size, bool rasterize = true)
{
setResize(size.x, size.y, rasterize);
}
void setResize(const float width, const float height, bool rasterize) override;
void setResize(const glm::vec2& size, bool rasterize = true) override;
// Resize the image to be as large as possible but fit within a box of this size.
// Can be set before or after an image is loaded.
@ -51,6 +47,9 @@ public:
void setMaxSize(const float width, const float height);
void setMaxSize(const glm::vec2& size) { setMaxSize(size.x, size.y); }
// Resize and crop image so it fills the entire area defined by the size parameter.
void setCroppedSize(const glm::vec2& size);
void setTileSize(const float width, const float height)
{
mTileWidth = width;
@ -59,14 +58,16 @@ public:
glm::vec2 getRotationSize() const override { return mRotateByTargetSize ? mTargetSize : mSize; }
// Applied AFTER image positioning and sizing.
// cropTop(0.2) will crop 20% of the top of the image.
void cropLeft(const float percent);
void cropTop(const float percent);
void cropRight(const float percent);
void cropBot(const float percent);
void crop(const float left, const float top, const float right, const float bot);
// Applied after image positioning and sizing.
void cropLeft(const float value);
void cropTop(const float value);
void cropRight(const float value);
void cropBottom(const float value);
void crop(const float left, const float top, const float right, const float bottom);
void uncrop();
// This essentially implements CSS "object-fit: cover" and has nothing to do with the
// cover image type (as the name may seem to imply).
void coverFitCrop();
// This crops any entirely transparent areas around the actual image.
// The arguments restrict how much the end result is allowed to be scaled.
@ -119,6 +120,7 @@ private:
bool mFlipX;
bool mFlipY;
bool mTargetIsMax;
bool mTargetIsCrop;
float mTileWidth;
float mTileHeight;

View file

@ -484,6 +484,7 @@ void LottieAnimComponent::render(const glm::mat4& parentTrans)
for (int i = 0; i < 4; ++i)
vertices[i].position = glm::round(vertices[i].position);
vertices->brightness = mBrightness;
vertices->saturation = mSaturation * mThemeSaturation;
vertices->opacity = mOpacity * mThemeOpacity;
vertices->dimming = mDimming;

View file

@ -22,7 +22,6 @@ public:
void render(const glm::mat4& parentTrans) override;
void onSizeChanged() override { mImage.setSize(mSize); }
void setResize(float width, float height) override { setSize(width, height); }
bool getState() const { return mState; }
void setState(bool state);
std::string getValue() const override;

View file

@ -486,5 +486,5 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (properties & LINE_SPACING && elem->has("lineSpacing"))
setLineSpacing(glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f));
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, theme->isLegacyTheme()));
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, false, theme->isLegacyTheme()));
}

View file

@ -22,6 +22,9 @@
VideoComponent::VideoComponent()
: mVideoWidth {0}
, mVideoHeight {0}
, mColorShift {0xFFFFFFFF}
, mColorShiftEnd {0xFFFFFFFF}
, mColorGradientHorizontal {true}
, mTargetSize {0.0f, 0.0f}
, mVideoAreaPos {0.0f, 0.0f}
, mVideoAreaSize {0.0f, 0.0f}
@ -254,6 +257,29 @@ void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
}
}
if (elem->has("color")) {
mColorShift = elem->get<unsigned int>("color");
mColorShiftEnd = mColorShift;
}
if (elem->has("colorEnd"))
mColorShiftEnd = elem->get<unsigned int>("colorEnd");
if (elem->has("gradientType")) {
const std::string& gradientType {elem->get<std::string>("gradientType")};
if (gradientType == "horizontal") {
mColorGradientHorizontal = true;
}
else if (gradientType == "vertical") {
mColorGradientHorizontal = false;
}
else {
mColorGradientHorizontal = true;
LOG(LogWarning) << "VideoComponent: Invalid theme configuration, property "
"\"gradientType\" for element \""
<< element.substr(6) << "\" defined as \"" << gradientType << "\"";
}
}
if (elem->has("pillarboxes"))
mDrawPillarboxes = elem->get<bool>("pillarboxes");
@ -355,6 +381,14 @@ void VideoComponent::renderSnapshot(const glm::mat4& parentTrans)
if (mStaticImagePath != "") {
mStaticImage.setOpacity(mOpacity * mThemeOpacity);
mStaticImage.setSaturation(mSaturation * mThemeSaturation);
if (mBrightness != 0.0f)
mStaticImage.setBrightness(mBrightness);
if (mColorShift != 0xFFFFFFFF)
mStaticImage.setColorShift(mColorShift);
if (mColorShift != mColorShiftEnd)
mStaticImage.setColorShiftEnd(mColorShiftEnd);
if (!mColorGradientHorizontal)
mStaticImage.setColorGradientHorizontal(mColorGradientHorizontal);
mStaticImage.setDimming(mDimming);
mStaticImage.render(parentTrans);
}

View file

@ -76,12 +76,6 @@ public:
void update(int deltaTime) override;
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain
// aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
// zero, no resizing. This can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
virtual void setResize(float width, float height) override = 0;
// Resize the video to be as large as possible but fit within a box of this size.
// This can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
@ -107,6 +101,9 @@ protected:
unsigned mVideoWidth;
unsigned mVideoHeight;
unsigned int mColorShift;
unsigned int mColorShiftEnd;
bool mColorGradientHorizontal;
glm::vec2 mTargetSize;
glm::vec2 mVideoAreaPos;
glm::vec2 mVideoAreaSize;

View file

@ -60,12 +60,12 @@ VideoFFmpegComponent::VideoFFmpegComponent()
{
}
void VideoFFmpegComponent::setResize(float width, float height)
void VideoFFmpegComponent::setResize(const float width, const float height)
{
// This resize function is used when stretching videos to full screen in the video screensaver.
mTargetSize = glm::vec2 {width, height};
mTargetIsMax = false;
mStaticImage.setResize(width, height);
mStaticImage.setResize(mTargetSize);
resize();
}
@ -164,6 +164,11 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
vertices[3] = {{mSize.x + mRectangleOffset.x, mSize.y + + mRectangleOffset.y}, {1.0f, 1.0f}, 0xFFFFFFFF};
// clang-format on
vertices[0].color = mColorShift;
vertices[1].color = mColorGradientHorizontal ? mColorShift : mColorShiftEnd;
vertices[2].color = mColorGradientHorizontal ? mColorShiftEnd : mColorShift;
vertices[3].color = mColorShiftEnd;
// Round vertices.
for (int i = 0; i < 4; ++i)
vertices[i].position = glm::round(vertices[i].position);
@ -171,6 +176,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
if (mFadeIn < 1.0f || mThemeOpacity < 1.0f)
vertices->opacity = mFadeIn * mThemeOpacity;
vertices->brightness = mBrightness;
vertices->saturation = mSaturation * mThemeSaturation;
vertices->dimming = mDimming;

View file

@ -40,7 +40,7 @@ public:
// aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
// zero, no resizing. This can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height) override;
void setResize(const float width, const float height) override;
// Resize the video to be as large as possible but fit within a box of this size.
// This can be set before or after a video is loaded.

View file

@ -3,11 +3,11 @@
// EmulationStation Desktop Edition
// CarouselComponent.h
//
// Carousel.
// Carousel, usable in both the system and gamelist views.
//
#ifndef ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
#define ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
#ifndef ES_CORE_COMPONENTS_PRIMARY_CAROUSEL_COMPONENT_H
#define ES_CORE_COMPONENTS_PRIMARY_CAROUSEL_COMPONENT_H
#include "Sound.h"
#include "animations/LambdaAnimation.h"
@ -17,16 +17,15 @@
struct CarouselEntry {
std::shared_ptr<GuiComponent> item;
std::string itemPath;
std::string defaultItemPath;
std::string imagePath;
std::string defaultImagePath;
};
template <typename T>
class CarouselComponent : public PrimaryComponent<T>, protected IList<CarouselEntry, T>
{
using List = IList<CarouselEntry, T>;
protected:
using List = IList<CarouselEntry, T>;
using List::mCursor;
using List::mEntries;
using List::mScrollVelocity;
@ -47,6 +46,14 @@ public:
NO_CAROUSEL
};
enum class ItemStacking {
CENTERED,
ASCENDING,
ASCENDING_RAISED,
DESCENDING,
DESCENDING_RAISED
};
CarouselComponent();
void addEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme);
@ -54,10 +61,10 @@ public:
Entry& getEntry(int index) { return mEntries.at(index); }
void onDemandTextureLoad() override;
const CarouselType getType() { return mType; }
const std::string& getItemType() { return mItemType; }
void setItemType(std::string itemType) { mItemType = itemType; }
const std::string& getDefaultItem() { return mDefaultItem; }
void setDefaultItem(std::string defaultItem) { mDefaultItem = defaultItem; }
const std::string& getImageType() { return mImageType; }
void setImageType(std::string imageType) { mImageType = imageType; }
const std::string& getDefaultImage() { return mDefaultImage; }
void setDefaultImage(std::string defaultImage) { mDefaultImage = defaultImage; }
bool isScrolling() const override { return List::isScrolling(); }
const LetterCase getLetterCase() const override { return mLetterCase; }
const LetterCase getLetterCaseCollections() const override { return mLetterCaseCollections; }
@ -66,14 +73,14 @@ public:
return mLetterCaseGroupedCollections;
}
void setCursorChangedCallback(const std::function<void(CursorState state)>& func) override
{
mCursorChangedCallback = func;
}
void setCancelTransitionsCallback(const std::function<void()>& func) override
{
mCancelTransitionsCallback = func;
}
void setCursorChangedCallback(const std::function<void(CursorState state)>& func) override
{
mCursorChangedCallback = func;
}
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
@ -122,41 +129,47 @@ private:
bool mPositiveDirection;
bool mTriggerJump;
bool mGamelistView;
bool mLegacyMode;
CarouselType mType;
std::string mItemType;
std::string mDefaultItem;
bool mLegacyMode;
std::shared_ptr<Font> mFont;
unsigned int mTextColor;
unsigned int mTextBackgroundColor;
float mLineSpacing;
Alignment mItemHorizontalAlignment;
Alignment mItemVerticalAlignment;
Alignment mWheelHorizontalAlignment;
float mUnfocusedItemOpacity;
std::string mImageType;
std::string mDefaultImage;
float mMaxItemCount;
int mItemsBeforeCenter;
int mItemsAfterCenter;
ItemStacking mItemStacking;
glm::vec2 mItemSize;
bool mLinearInterpolation;
bool mInstantItemTransitions;
bool mItemAxisHorizontal;
bool mFadeAbovePrimary;
LetterCase mLetterCase;
LetterCase mLetterCaseCollections;
LetterCase mLetterCaseGroupedCollections;
float mItemScale;
float mItemRotation;
glm::vec2 mItemRotationOrigin;
unsigned int mCarouselColor;
unsigned int mCarouselColorEnd;
bool mColorGradientHorizontal;
bool mItemAxisHorizontal;
bool mLinearInterpolation;
unsigned int mImageColorShift;
unsigned int mImageColorShiftEnd;
bool mImageColorGradientHorizontal;
float mImageBrightness;
float mImageSaturation;
bool mInstantItemTransitions;
Alignment mItemHorizontalAlignment;
Alignment mItemVerticalAlignment;
Alignment mWheelHorizontalAlignment;
float mHorizontalOffset;
float mVerticalOffset;
bool mReflections;
float mReflectionsOpacity;
float mReflectionsFalloff;
float mHorizontalOffset;
float mVerticalOffset;
float mUnfocusedItemOpacity;
unsigned int mCarouselColor;
unsigned int mCarouselColorEnd;
bool mColorGradientHorizontal;
unsigned int mTextColor;
unsigned int mTextBackgroundColor;
std::shared_ptr<Font> mFont;
LetterCase mLetterCase;
LetterCase mLetterCaseCollections;
LetterCase mLetterCaseGroupedCollections;
float mLineSpacing;
bool mFadeAbovePrimary;
};
template <typename T>
@ -172,39 +185,45 @@ CarouselComponent<T>::CarouselComponent()
, mPositiveDirection {false}
, mTriggerJump {false}
, mGamelistView {std::is_same_v<T, FileData*> ? true : false}
, mType {CarouselType::HORIZONTAL}
, mLegacyMode {false}
, mFont {Font::get(FONT_SIZE_LARGE)}
, mTextColor {0x000000FF}
, mTextBackgroundColor {0xFFFFFF00}
, mLineSpacing {1.5f}
, mItemHorizontalAlignment {ALIGN_CENTER}
, mItemVerticalAlignment {ALIGN_CENTER}
, mWheelHorizontalAlignment {ALIGN_CENTER}
, mUnfocusedItemOpacity {0.5f}
, mType {CarouselType::HORIZONTAL}
, mMaxItemCount {3.0f}
, mItemsBeforeCenter {8}
, mItemsAfterCenter {8}
, mItemStacking {ItemStacking::CENTERED}
, mItemSize {glm::vec2 {Renderer::getScreenWidth() * 0.25f,
Renderer::getScreenHeight() * 0.155f}}
, mLinearInterpolation {false}
, mInstantItemTransitions {false}
, mItemAxisHorizontal {false}
, mFadeAbovePrimary {false}
, mLetterCase {LetterCase::NONE}
, mLetterCaseCollections {LetterCase::NONE}
, mLetterCaseGroupedCollections {LetterCase::NONE}
, mItemScale {1.2f}
, mItemRotation {7.5f}
, mItemRotationOrigin {-3.0f, 0.5f}
, mCarouselColor {0}
, mCarouselColorEnd {0}
, mColorGradientHorizontal {true}
, mItemAxisHorizontal {false}
, mLinearInterpolation {false}
, mImageColorShift {0xFFFFFFFF}
, mImageColorShiftEnd {0xFFFFFFFF}
, mImageColorGradientHorizontal {true}
, mImageBrightness {0.0f}
, mImageSaturation {1.0f}
, mInstantItemTransitions {false}
, mItemHorizontalAlignment {ALIGN_CENTER}
, mItemVerticalAlignment {ALIGN_CENTER}
, mWheelHorizontalAlignment {ALIGN_CENTER}
, mHorizontalOffset {0.0f}
, mVerticalOffset {0.0f}
, mReflections {false}
, mReflectionsOpacity {0.5f}
, mReflectionsFalloff {1.0f}
, mHorizontalOffset {0.0f}
, mVerticalOffset {0.0f}
, mUnfocusedItemOpacity {0.5f}
, mCarouselColor {0}
, mCarouselColorEnd {0}
, mColorGradientHorizontal {true}
, mTextColor {0x000000FF}
, mTextBackgroundColor {0xFFFFFF00}
, mFont {Font::get(FONT_SIZE_LARGE)}
, mLetterCase {LetterCase::NONE}
, mLetterCaseCollections {LetterCase::NONE}
, mLetterCaseGroupedCollections {LetterCase::NONE}
, mLineSpacing {1.5f}
, mFadeAbovePrimary {false}
{
}
@ -240,34 +259,55 @@ void CarouselComponent<T>::addEntry(Entry& entry, const std::shared_ptr<ThemeDat
}
}
else {
if (entry.data.itemPath != "" &&
ResourceManager::getInstance().fileExists(entry.data.itemPath)) {
if (entry.data.imagePath != "" &&
ResourceManager::getInstance().fileExists(entry.data.imagePath)) {
auto item = std::make_shared<ImageComponent>(false, dynamic);
item->setLinearInterpolation(mLinearInterpolation);
item->setMipmapping(true);
item->setMaxSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f)));
item->setImage(entry.data.itemPath);
item->setImage(entry.data.imagePath);
item->applyTheme(theme, "system", "", ThemeFlags::ALL);
if (mImageBrightness != 0.0)
item->setBrightness(mImageBrightness);
if (mImageSaturation != 1.0)
item->setSaturation(mImageSaturation);
if (mImageColorShift != 0xFFFFFFFF)
item->setColorShift(mImageColorShift);
if (mImageColorShiftEnd != mImageColorShift)
item->setColorShiftEnd(mImageColorShiftEnd);
if (!mImageColorGradientHorizontal)
item->setColorGradientHorizontal(false);
item->setRotateByTargetSize(true);
entry.data.item = item;
}
else if (entry.data.defaultItemPath != "" &&
ResourceManager::getInstance().fileExists(entry.data.defaultItemPath)) {
auto defaultItem = std::make_shared<ImageComponent>(false, dynamic);
defaultItem->setLinearInterpolation(mLinearInterpolation);
defaultItem->setMipmapping(true);
defaultItem->setMaxSize(
else if (entry.data.defaultImagePath != "" &&
ResourceManager::getInstance().fileExists(entry.data.defaultImagePath)) {
auto defaultImage = std::make_shared<ImageComponent>(false, dynamic);
defaultImage->setLinearInterpolation(mLinearInterpolation);
defaultImage->setMipmapping(true);
defaultImage->setMaxSize(
glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f)));
defaultItem->setImage(entry.data.defaultItemPath);
defaultItem->applyTheme(theme, "system", "", ThemeFlags::ALL);
defaultItem->setRotateByTargetSize(true);
entry.data.item = defaultItem;
defaultImage->setImage(entry.data.defaultImagePath);
defaultImage->applyTheme(theme, "system", "", ThemeFlags::ALL);
if (mImageBrightness != 0.0)
defaultImage->setBrightness(mImageBrightness);
if (mImageSaturation != 1.0)
defaultImage->setSaturation(mImageSaturation);
if (mImageColorShift != 0xFFFFFFFF)
defaultImage->setColorShift(mImageColorShift);
if (mImageColorShiftEnd != mImageColorShift)
defaultImage->setColorShiftEnd(mImageColorShiftEnd);
if (!mImageColorGradientHorizontal)
defaultImage->setColorGradientHorizontal(false);
defaultImage->setRotateByTargetSize(true);
// For the gamelist view the default image is applied in onDemandTextureLoad().
if (!mGamelistView)
entry.data.item = defaultImage;
}
}
if (!entry.data.item) {
// If no item image is present, add item text as fallback.
auto text = std::make_shared<TextComponent>(
entry.name, mFont, 0x000000FF, mItemHorizontalAlignment, mItemVerticalAlignment,
glm::vec3 {0.0f, 0.0f, 0.0f},
@ -313,13 +353,23 @@ void CarouselComponent<T>::addEntry(Entry& entry, const std::shared_ptr<ThemeDat
template <typename T>
void CarouselComponent<T>::updateEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme)
{
if (entry.data.itemPath != "") {
if (entry.data.imagePath != "") {
auto item = std::make_shared<ImageComponent>(false, true);
item->setLinearInterpolation(mLinearInterpolation);
item->setMipmapping(true);
item->setMaxSize(glm::round(mItemSize * (mItemScale >= 1.0f ? mItemScale : 1.0f)));
item->setImage(entry.data.itemPath);
item->setImage(entry.data.imagePath);
item->applyTheme(theme, "system", "", ThemeFlags::ALL);
if (mImageBrightness != 0.0)
item->setBrightness(mImageBrightness);
if (mImageSaturation != 1.0)
item->setSaturation(mImageSaturation);
if (mImageColorShift != 0xFFFFFFFF)
item->setColorShift(mImageColorShift);
if (mImageColorShiftEnd != mImageColorShift)
item->setColorShiftEnd(mImageColorShiftEnd);
if (!mImageColorGradientHorizontal)
item->setColorGradientHorizontal(false);
item->setRotateByTargetSize(true);
entry.data.item = item;
}
@ -349,7 +399,7 @@ void CarouselComponent<T>::updateEntry(Entry& entry, const std::shared_ptr<Theme
template <typename T> void CarouselComponent<T>::onDemandTextureLoad()
{
if constexpr (std::is_same_v<T, FileData*>) {
const int numEntries {static_cast<int>(mEntries.size())};
const int numEntries {size()};
const int center {getCursor()};
const bool isWheel {mType == CarouselType::VERTICAL_WHEEL ||
mType == CarouselType::HORIZONTAL_WHEEL};
@ -396,6 +446,7 @@ template <typename T> void CarouselComponent<T>::onDemandTextureLoad()
if (mVerticalOffset > 0.0f)
centerOffset = -centerOffset;
}
itemInclusion += 1;
}
for (int i = center - itemInclusion - itemInclusionBefore;
@ -409,30 +460,33 @@ template <typename T> void CarouselComponent<T>::onDemandTextureLoad()
auto& entry = mEntries.at(cursor);
if (entry.data.itemPath == "") {
if (entry.data.imagePath == "") {
FileData* game {entry.object};
if (mItemType == "" || mItemType == "marquee")
entry.data.itemPath = game->getMarqueePath();
else if (mItemType == "cover")
entry.data.itemPath = game->getCoverPath();
else if (mItemType == "backcover")
entry.data.itemPath = game->getBackCoverPath();
else if (mItemType == "3dbox")
entry.data.itemPath = game->get3DBoxPath();
else if (mItemType == "physicalmedia")
entry.data.itemPath = game->getPhysicalMediaPath();
else if (mItemType == "screenshot")
entry.data.itemPath = game->getScreenshotPath();
else if (mItemType == "titlescreen")
entry.data.itemPath = game->getTitleScreenPath();
else if (mItemType == "miximage")
entry.data.itemPath = game->getMiximagePath();
else if (mItemType == "fanart")
entry.data.itemPath = game->getFanArtPath();
else if (mItemType == "none") // Display the game name as text.
if (mImageType == "" || mImageType == "marquee")
entry.data.imagePath = game->getMarqueePath();
else if (mImageType == "cover")
entry.data.imagePath = game->getCoverPath();
else if (mImageType == "backcover")
entry.data.imagePath = game->getBackCoverPath();
else if (mImageType == "3dbox")
entry.data.imagePath = game->get3DBoxPath();
else if (mImageType == "physicalmedia")
entry.data.imagePath = game->getPhysicalMediaPath();
else if (mImageType == "screenshot")
entry.data.imagePath = game->getScreenshotPath();
else if (mImageType == "titlescreen")
entry.data.imagePath = game->getTitleScreenPath();
else if (mImageType == "miximage")
entry.data.imagePath = game->getMiximagePath();
else if (mImageType == "fanart")
entry.data.imagePath = game->getFanArtPath();
else if (mImageType == "none") // Display the game name as text.
return;
if (entry.data.imagePath == "")
entry.data.imagePath = entry.data.defaultImagePath;
auto theme = game->getSystem()->getTheme();
updateEntry(entry, theme);
}
@ -527,7 +581,8 @@ template <typename T> bool CarouselComponent<T>::input(InputConfig* config, Inpu
config->isMappedLike("rightshoulder", input) ||
config->isMappedLike("lefttrigger", input) ||
config->isMappedLike("righttrigger", input)) {
onCursorChanged(CursorState::CURSOR_STOPPED);
if (isScrolling())
onCursorChanged(CursorState::CURSOR_STOPPED);
List::listInput(0);
mTriggerJump = false;
}
@ -812,6 +867,33 @@ template <typename T> void CarouselComponent<T>::render(const glm::mat4& parentT
if (renderItems.size() == 1) {
renderItemsSorted.emplace_back(renderItems.front());
}
else if (!isWheel && mItemStacking != ItemStacking::CENTERED) {
if (mItemStacking == ItemStacking::ASCENDING) {
renderItemsSorted.insert(renderItemsSorted.begin(),
std::make_move_iterator(renderItems.begin()),
std::make_move_iterator(renderItems.end()));
}
else if (mItemStacking == ItemStacking::ASCENDING_RAISED) {
for (size_t i {0}; i < renderItems.size(); ++i) {
if (i == static_cast<size_t>(belowCenter))
continue;
renderItemsSorted.emplace_back(std::move(renderItems[i]));
}
renderItemsSorted.emplace_back(std::move(renderItems[belowCenter]));
}
else if (mItemStacking == ItemStacking::DESCENDING) {
for (size_t i {renderItems.size()}; i > 0; --i)
renderItemsSorted.emplace_back(std::move(renderItems[i - 1]));
}
else if (mItemStacking == ItemStacking::DESCENDING_RAISED) {
for (size_t i {renderItems.size()}; i > 0; --i) {
if (i - 1 == static_cast<size_t>(belowCenter))
continue;
renderItemsSorted.emplace_back(std::move(renderItems[i - 1]));
}
renderItemsSorted.emplace_back(std::move(renderItems[belowCenter]));
}
}
else {
// Make sure that overlapping items are rendered in the correct order.
size_t zeroDistanceEntry {0};
@ -886,8 +968,8 @@ template <typename T> void CarouselComponent<T>::render(const glm::mat4& parentT
// TODO: Rewrite to use "real" reflections instead of this hack.
// Don't attempt to add reflections for text entries.
if (mReflections && (mEntries.at(renderItem.index).data.itemPath != "" ||
mEntries.at(renderItem.index).data.defaultItemPath != "")) {
if (mReflections && (mEntries.at(renderItem.index).data.imagePath != "" ||
mEntries.at(renderItem.index).data.defaultImagePath != "")) {
glm::mat4 reflectionTrans {glm::translate(
renderItem.trans, glm::vec3 {0.0f, comp->getSize().y * renderItem.scale, 0.0f})};
float falloff {glm::clamp(mReflectionsFalloff, 0.0f, 1.0f)};
@ -913,9 +995,6 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& element,
unsigned int properties)
{
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "carousel")};
mSize.x = Renderer::getScreenWidth();
mSize.y = Renderer::getScreenHeight() * 0.23240f;
GuiComponent::mPosition.x = 0.0f;
@ -924,6 +1003,9 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
mCarouselColorEnd = 0xFFFFFFD8;
mZIndex = mDefaultZIndex;
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "carousel")};
if (!elem)
return;
@ -1004,27 +1086,36 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
}
if (elem->has("itemScale"))
mItemScale = glm::clamp(elem->get<float>("itemScale"), 0.2f, 3.0f);
if (elem->has("itemTransitions")) {
const std::string& itemTransitions {elem->get<std::string>("itemTransitions")};
if (itemTransitions == "slide") {
mInstantItemTransitions = false;
if (elem->has("itemStacking")) {
const std::string& itemStacking {elem->get<std::string>("itemStacking")};
if (itemStacking == "ascending") {
mItemStacking = ItemStacking::ASCENDING;
}
else if (itemTransitions == "instant") {
mInstantItemTransitions = true;
else if (itemStacking == "ascendingRaised") {
mItemStacking = ItemStacking::ASCENDING_RAISED;
}
else if (itemStacking == "descending") {
mItemStacking = ItemStacking::DESCENDING;
}
else if (itemStacking == "descendingRaised") {
mItemStacking = ItemStacking::DESCENDING_RAISED;
}
else if (itemStacking == "centered") {
mItemStacking = ItemStacking::CENTERED;
}
else {
mInstantItemTransitions = false;
mItemStacking = ItemStacking::CENTERED;
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"\"itemTransitions\" for element \""
<< element.substr(9) << "\" defined as \"" << itemTransitions
<< "\"";
"\"itemStacking\" for element \""
<< element.substr(9) << "\" defined as \"" << itemStacking << "\"";
}
}
if (elem->has("itemScale"))
mItemScale = glm::clamp(elem->get<float>("itemScale"), 0.2f, 3.0f);
if (elem->has("itemInterpolation")) {
// TEMPORARY: Backward compatiblity due to property name changes.
const std::string& itemInterpolation {elem->get<std::string>("itemInterpolation")};
if (itemInterpolation == "linear") {
mLinearInterpolation = true;
@ -1041,6 +1132,46 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
}
}
if (elem->has("imageBrightness"))
mImageBrightness = glm::clamp(elem->get<float>("imageBrightness"), -2.0f, 2.0f);
if (elem->has("imageSaturation"))
mImageSaturation = glm::clamp(elem->get<float>("imageSaturation"), 0.0f, 1.0f);
if (elem->has("imageInterpolation")) {
const std::string& imageInterpolation {elem->get<std::string>("imageInterpolation")};
if (imageInterpolation == "linear") {
mLinearInterpolation = true;
}
else if (imageInterpolation == "nearest") {
mLinearInterpolation = false;
}
else {
mLinearInterpolation = true;
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"\"imageInterpolation\" for element \""
<< element.substr(9) << "\" defined as \"" << imageInterpolation
<< "\"";
}
}
if (elem->has("itemTransitions")) {
const std::string& itemTransitions {elem->get<std::string>("itemTransitions")};
if (itemTransitions == "animate") {
mInstantItemTransitions = false;
}
else if (itemTransitions == "instant") {
mInstantItemTransitions = true;
}
else {
mInstantItemTransitions = false;
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"\"itemTransitions\" for element \""
<< element.substr(9) << "\" defined as \"" << itemTransitions
<< "\"";
}
}
if (elem->has("itemRotation"))
mItemRotation = elem->get<float>("itemRotation");
@ -1050,6 +1181,29 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
mItemAxisHorizontal =
(elem->has("itemAxisHorizontal") && elem->get<bool>("itemAxisHorizontal"));
if (elem->has("imageColor")) {
mImageColorShift = elem->get<unsigned int>("imageColor");
mImageColorShiftEnd = mImageColorShift;
}
if (elem->has("imageColorEnd"))
mImageColorShiftEnd = elem->get<unsigned int>("imageColorEnd");
if (elem->has("imageGradientType")) {
const std::string& gradientType {elem->get<std::string>("imageGradientType")};
if (gradientType == "horizontal") {
mImageColorGradientHorizontal = true;
}
else if (gradientType == "vertical") {
mImageColorGradientHorizontal = false;
}
else {
mImageColorGradientHorizontal = true;
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"\"imageGradientType\" for element \""
<< element.substr(9) << "\" defined as \"" << gradientType << "\"";
}
}
if (elem->has("itemHorizontalAlignment")) {
const std::string& alignment {elem->get<std::string>("itemHorizontalAlignment")};
if (alignment == "left" && mType != CarouselType::HORIZONTAL) {
@ -1203,7 +1357,7 @@ void CarouselComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
}
// For non-legacy themes, scale the font size with the itemScale property value.
mFont = Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode,
mFont = Font::getFromTheme(elem, properties, mFont, 0.0f, false, mLegacyMode,
(mLegacyMode ? 1.0f : (mItemScale >= 1.0f ? mItemScale : 1.0f)));
if (elem->has("textColor"))
@ -1326,11 +1480,25 @@ template <typename T> void CarouselComponent<T>::onCursorChanged(const CursorSta
mPositiveDirection = false;
mEntryCamTarget = endPos;
float animTime {400.0f};
float timeDiff {1.0f};
// If startPos is inbetween two positions then reduce the time slightly as the distance will
// be shorter meaning the animation would play for too long if not compensated for.
if (mScrollVelocity == 1)
timeDiff = endPos - startPos;
else if (mScrollVelocity == -1)
timeDiff = startPos - endPos;
if (timeDiff != 1.0f)
animTime =
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), 200.0f, animTime);
Animation* anim {new LambdaAnimation(
[this, startPos, endPos, posMax](float t) {
t -= 1;
float f {glm::mix(startPos, endPos, t * t * t + 1)};
// Non-linear interpolation.
t = 1.0f - (1.0f - t) * (1.0f - t);
float f {(endPos * t) + (startPos * (1.0f - t))};
if (f < 0)
f += posMax;
if (f >= posMax)
@ -1338,7 +1506,7 @@ template <typename T> void CarouselComponent<T>::onCursorChanged(const CursorSta
mEntryCamOffset = f;
},
500)};
static_cast<int>(animTime))};
GuiComponent::setAnimation(anim, 0, nullptr, false, 0);
@ -1346,4 +1514,4 @@ template <typename T> void CarouselComponent<T>::onCursorChanged(const CursorSta
mCursorChangedCallback(state);
}
#endif // ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
#endif // ES_CORE_COMPONENTS_PRIMARY_CAROUSEL_COMPONENT_H

File diff suppressed because it is too large Load diff

View file

@ -3,17 +3,18 @@
// EmulationStation Desktop Edition
// PrimaryComponent.h
//
// Base class for the primary components (carousel and textlist).
// Base class for the primary components (carousel, grid and textlist).
//
#ifndef ES_CORE_COMPONENTS_PRIMARY_COMPONENT_H
#define ES_CORE_COMPONENTS_PRIMARY_COMPONENT_H
#ifndef ES_CORE_COMPONENTS_PRIMARY_PRIMARY_COMPONENT_H
#define ES_CORE_COMPONENTS_PRIMARY_PRIMARY_COMPONENT_H
template <typename T> class PrimaryComponent : public virtual GuiComponent
{
public:
enum class PrimaryType {
CAROUSEL,
GRID,
TEXTLIST
};
@ -52,4 +53,4 @@ public:
virtual void setAlignment(PrimaryAlignment align) {}
};
#endif // ES_CORE_COMPONENTS_PRIMARY_COMPONENT_H
#endif // ES_CORE_COMPONENTS_PRIMARY_PRIMARY_COMPONENT_H

View file

@ -3,11 +3,11 @@
// EmulationStation Desktop Edition
// TextListComponent.h
//
// Text list used for displaying and navigating the gamelist views.
// Text list, usable in both the system and gamelist views.
//
#ifndef ES_CORE_COMPONENTS_TEXT_LIST_COMPONENT_H
#define ES_CORE_COMPONENTS_TEXT_LIST_COMPONENT_H
#ifndef ES_CORE_COMPONENTS_PRIMARY_TEXT_LIST_COMPONENT_H
#define ES_CORE_COMPONENTS_PRIMARY_TEXT_LIST_COMPONENT_H
#include "Log.h"
#include "Sound.h"
@ -57,14 +57,14 @@ public:
void setAlignment(PrimaryAlignment align) override { mAlignment = align; }
void setCursorChangedCallback(const std::function<void(CursorState state)>& func) override
{
mCursorChangedCallback = func;
}
void setCancelTransitionsCallback(const std::function<void()>& func) override
{
mCancelTransitionsCallback = func;
}
void setCursorChangedCallback(const std::function<void(CursorState state)>& func) override
{
mCursorChangedCallback = func;
}
void setFont(const std::shared_ptr<Font>& font)
{
@ -82,7 +82,7 @@ public:
return mLetterCaseGroupedCollections;
}
protected:
private:
void onShow() override { mLoopTime = 0; }
void onScroll() override
{
@ -90,8 +90,6 @@ protected:
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
}
void onCursorChanged(const CursorState& state) override;
private:
bool isScrolling() const override { return List::isScrolling(); }
void stopScrolling() override { List::stopScrolling(); }
const int getScrollingVelocity() override { return List::getScrollingVelocity(); }
@ -111,6 +109,7 @@ private:
Renderer* mRenderer;
std::function<void()> mCancelTransitionsCallback;
std::function<void(CursorState state)> mCursorChangedCallback;
float mCamOffset;
int mPreviousScrollVelocity;
@ -122,7 +121,6 @@ private:
PrimaryAlignment mAlignment;
float mHorizontalMargin;
std::function<void(CursorState state)> mCursorChangedCallback;
ImageComponent mSelectorImage;
std::shared_ptr<Font> mFont;
@ -203,15 +201,19 @@ template <typename T> bool TextListComponent<T>::input(InputConfig* config, Inpu
return true;
}
if (config->isMappedLike("leftshoulder", input)) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
List::listInput(-10);
if (mCursor != 0) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
List::listInput(-10);
}
return true;
}
if (config->isMappedLike("rightshoulder", input)) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
List::listInput(10);
if (mCursor != size() - 1) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
List::listInput(10);
}
return true;
}
if (config->isMappedLike("lefttrigger", input)) {
@ -235,14 +237,9 @@ template <typename T> bool TextListComponent<T>::input(InputConfig* config, Inpu
config->isMappedLike("rightshoulder", input) ||
config->isMappedLike("lefttrigger", input) ||
config->isMappedLike("righttrigger", input)) {
if constexpr (std::is_same_v<T, SystemData*>) {
if (isScrolling())
onCursorChanged(CursorState::CURSOR_STOPPED);
List::listInput(0);
}
else {
List::stopScrolling();
}
if (isScrolling())
onCursorChanged(CursorState::CURSOR_STOPPED);
List::listInput(0);
}
}
}
@ -480,7 +477,14 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& element,
unsigned int properties)
{
mSize.x = Renderer::getScreenWidth();
mSize.y = Renderer::getScreenHeight() * 0.8f;
GuiComponent::mPosition.x = 0.0f;
GuiComponent::mPosition.y = Renderer::getScreenHeight() * 0.1f;
setAlignment(PrimaryAlignment::ALIGN_LEFT);
GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "textlist")};
@ -525,7 +529,7 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
mSelectedSecondaryColor = mSelectedColor;
}
setFont(Font::getFromTheme(elem, properties, mFont, 0.0f, mLegacyMode));
setFont(Font::getFromTheme(elem, properties, mFont, 0.0f, false, mLegacyMode));
if (mLegacyMode)
mFont->useLegacyMaxGlyphHeight();
const float selectorHeight {mFont->getHeight(mLineSpacing)};
@ -550,8 +554,8 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
<< "\"";
}
}
// Legacy themes only.
else if (elem->has("alignment")) {
// Legacy themes only.
const std::string& alignment {elem->get<std::string>("alignment")};
if (alignment == "left") {
setAlignment(PrimaryAlignment::ALIGN_LEFT);
@ -703,10 +707,26 @@ template <typename T> void TextListComponent<T>::onCursorChanged(const CursorSta
float posMax {static_cast<float>(mEntries.size())};
float endPos {static_cast<float>(mCursor)};
float animTime {400.0f};
float timeDiff {1.0f};
// If startPos is inbetween two positions then reduce the time slightly as the distance will
// be shorter meaning the animation would play for too long if not compensated for.
if (mScrollVelocity == 1)
timeDiff = endPos - startPos;
else if (mScrollVelocity == -1)
timeDiff = startPos - endPos;
if (timeDiff != 1.0f)
animTime =
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), 200.0f, animTime);
Animation* anim {new LambdaAnimation(
[this, startPos, endPos, posMax](float t) {
t -= 1;
float f {glm::mix(startPos, endPos, t * t * t + 1)};
// Non-linear interpolation.
t = 1.0f - (1.0f - t) * (1.0f - t);
float f {(endPos * t) + (startPos * (1.0f - t))};
if (f < 0)
f += posMax;
if (f >= posMax)
@ -714,7 +734,7 @@ template <typename T> void TextListComponent<T>::onCursorChanged(const CursorSta
mCamOffset = f;
},
500)};
static_cast<int>(animTime))};
GuiComponent::setAnimation(anim, 0, nullptr, false, 0);
}
@ -723,4 +743,4 @@ template <typename T> void TextListComponent<T>::onCursorChanged(const CursorSta
mCursorChangedCallback(state);
}
#endif // ES_CORE_COMPONENTS_TEXT_LIST_COMPONENT_H
#endif // ES_CORE_COMPONENTS_PRIMARY_TEXT_LIST_COMPONENT_H

View file

@ -61,6 +61,7 @@ public:
glm::vec2 texcoord;
unsigned int color;
glm::vec4 clipregion;
float brightness;
float opacity;
float saturation;
float dimming;
@ -69,7 +70,8 @@ public:
unsigned int shaderFlags;
Vertex()
: opacity {1.0f}
: brightness {0.0f}
, opacity {1.0f}
, saturation {1.0f}
, dimming {1.0f}
, reflectionsFalloff {0.0f}
@ -86,6 +88,7 @@ public:
, texcoord(textureCoord)
, color(color)
, clipregion(clipRegion)
, brightness {0.0f}
, opacity {1.0f}
, saturation {1.0f}
, dimming {1.0f}

View file

@ -443,6 +443,7 @@ void RendererOpenGL::drawTriangleStrips(const Vertex* vertices,
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices,
GL_DYNAMIC_DRAW));
mCoreShader->setClipRegion(vertices->clipregion);
mCoreShader->setBrightness(vertices->brightness);
mCoreShader->setOpacity(vertices->opacity);
mCoreShader->setSaturation(vertices->saturation);
mCoreShader->setDimming(vertices->dimming);
@ -517,6 +518,7 @@ void RendererOpenGL::drawTriangleStrips(const Vertex* vertices,
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices,
GL_DYNAMIC_DRAW));
mScanlinelShader->setOpacity(vertices->opacity);
mScanlinelShader->setBrightness(vertices->brightness);
mScanlinelShader->setSaturation(vertices->saturation);
mScanlinelShader->setTextureSize({shaderWidth, shaderHeight});
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
@ -548,7 +550,7 @@ void RendererOpenGL::shaderPostprocessing(unsigned int shaders,
vertices->opacity = parameters.opacity;
vertices->saturation = parameters.saturation;
vertices->dimming = parameters.dimming;
vertices->shaderFlags = ShaderFlags::POST_PROCESSING;
vertices->shaderFlags = ShaderFlags::POST_PROCESSING | ShaderFlags::PREMULTIPLIED;
if (shaders & Shader::CORE)
shaderList.push_back(Shader::CORE);

View file

@ -19,6 +19,7 @@ ShaderOpenGL::ShaderOpenGL()
, mShaderTextureCoord {0}
, mShaderColor {0}
, mShaderTextureSize {0}
, mShaderBrightness {0}
, mShaderOpacity {0}
, mShaderSaturation {0}
, mShaderDimming {0}
@ -122,6 +123,7 @@ void ShaderOpenGL::getVariableLocations(GLuint programID)
mShaderColor = glGetAttribLocation(mProgramID, "colorVertex");
mShaderTextureSize = glGetUniformLocation(mProgramID, "textureSize");
mShaderClipRegion = glGetUniformLocation(mProgramID, "clipRegion");
mShaderBrightness = glGetUniformLocation(mProgramID, "brightness");
mShaderOpacity = glGetUniformLocation(mProgramID, "opacity");
mShaderSaturation = glGetUniformLocation(mProgramID, "saturation");
mShaderDimming = glGetUniformLocation(mProgramID, "dimming");
@ -166,6 +168,12 @@ void ShaderOpenGL::setClipRegion(glm::vec4 clipRegion)
clipRegion[3]));
}
void ShaderOpenGL::setBrightness(GLfloat brightness)
{
if (mShaderBrightness != -1)
GL_CHECK_ERROR(glUniform1f(mShaderBrightness, brightness));
}
void ShaderOpenGL::setOpacity(GLfloat opacity)
{
if (mShaderOpacity != -1)

View file

@ -69,6 +69,7 @@ public:
void setAttribPointers();
void setTextureSize(std::array<GLfloat, 2> shaderVec2);
void setClipRegion(glm::vec4 clipRegion);
void setBrightness(GLfloat brightness);
void setOpacity(GLfloat opacity);
void setSaturation(GLfloat saturation);
void setDimming(GLfloat dimming);
@ -95,6 +96,7 @@ private:
GLint mShaderColor;
GLint mShaderClipRegion;
GLint mShaderTextureSize;
GLint mShaderBrightness;
GLint mShaderOpacity;
GLint mShaderSaturation;
GLint mShaderDimming;

View file

@ -15,10 +15,11 @@
#include "utils/PlatformUtil.h"
#include "utils/StringUtil.h"
Font::Font(float size, const std::string& path)
Font::Font(float size, const std::string& path, const bool linearMagnify)
: mRenderer {Renderer::getInstance()}
, mPath(path)
, mFontSize {size}
, mLinearMagnify {linearMagnify}
, mLetterHeight {0.0f}
, mMaxGlyphHeight {static_cast<int>(std::round(size))}
, mLegacyMaxGlyphHeight {0}
@ -46,7 +47,8 @@ Font::~Font()
{
unload(ResourceManager::getInstance());
auto fontEntry = sFontMap.find(std::pair<std::string, float>(mPath, mFontSize));
auto fontEntry =
sFontMap.find(std::tuple<float, std::string, bool>(mFontSize, mPath, mLinearMagnify));
if (fontEntry != sFontMap.cend())
sFontMap.erase(fontEntry);
@ -57,11 +59,11 @@ Font::~Font()
}
}
std::shared_ptr<Font> Font::get(float size, const std::string& path)
std::shared_ptr<Font> Font::get(float size, const std::string& path, const bool linearMagnify)
{
const std::string canonicalPath {Utils::FileSystem::getCanonicalPath(path)};
const std::pair<std::string, float> def {
canonicalPath.empty() ? getDefaultPath() : canonicalPath, size};
const std::tuple<float, std::string, bool> def {
size, canonicalPath.empty() ? getDefaultPath() : canonicalPath, linearMagnify};
auto foundFont = sFontMap.find(def);
if (foundFont != sFontMap.cend()) {
@ -69,7 +71,7 @@ std::shared_ptr<Font> Font::get(float size, const std::string& path)
return foundFont->second.lock();
}
std::shared_ptr<Font> font {new Font(def.second, def.first)};
std::shared_ptr<Font> font {new Font(std::get<0>(def), std::get<1>(def), std::get<2>(def))};
sFontMap[def] = std::weak_ptr<Font>(font);
ResourceManager::getInstance().addReloadable(font);
return font;
@ -423,6 +425,7 @@ std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
unsigned int properties,
const std::shared_ptr<Font>& orig,
const float maxHeight,
const bool linearMagnify,
const bool legacyTheme,
const float sizeMultiplier)
{
@ -459,9 +462,9 @@ std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
}
if (mLegacyTheme)
return get(std::floor(size), path);
return get(std::floor(size), path, false);
else
return get(size, path);
return get(size, path, linearMagnify);
}
size_t Font::getMemUsage() const
@ -522,11 +525,12 @@ std::vector<std::string> Font::getFallbackFontPaths()
return fontPaths;
}
Font::FontTexture::FontTexture(const int mFontSize)
Font::FontTexture::FontTexture(const int mFontSize, const bool linearMagnifyArg)
{
textureId = 0;
rowHeight = 0;
writePos = glm::ivec2 {0, 0};
linearMagnify = linearMagnifyArg;
// Set the texture to a reasonable size, if we run out of space for adding glyphs then
// more textures will be created dynamically.
@ -573,9 +577,9 @@ void Font::FontTexture::initTexture()
// glyphs will not be visible. That would otherwise lead to edge artifacts as these pixels
// would get sampled during scaling.
std::vector<uint8_t> texture(textureSize.x * textureSize.y * 4, 0);
textureId =
Renderer::getInstance()->createTexture(Renderer::TextureType::RED, true, false, false,
false, textureSize.x, textureSize.y, &texture[0]);
textureId = Renderer::getInstance()->createTexture(Renderer::TextureType::RED, true,
linearMagnify, false, false, textureSize.x,
textureSize.y, &texture[0]);
}
void Font::FontTexture::deinitTexture()
@ -664,7 +668,8 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize,
return; // Yes.
}
mTextures.emplace_back(std::make_unique<FontTexture>(static_cast<int>(std::round(mFontSize))));
mTextures.emplace_back(
std::make_unique<FontTexture>(static_cast<int>(std::round(mFontSize)), mLinearMagnify));
tex_out = mTextures.back().get();
tex_out->initTexture();

View file

@ -36,7 +36,9 @@ class Font : public IReloadable
{
public:
virtual ~Font();
static std::shared_ptr<Font> get(float size, const std::string& path = getDefaultPath());
static std::shared_ptr<Font> get(float size,
const std::string& path = getDefaultPath(),
const bool linearMagnify = false);
// Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis.
glm::vec2 sizeText(std::string text, float lineSpacing = 1.5f);
@ -93,6 +95,7 @@ public:
unsigned int properties,
const std::shared_ptr<Font>& orig,
const float maxHeight = 0.0f,
const bool linearMagnify = false,
const bool legacyTheme = false,
const float sizeMultiplier = 1.0f);
@ -102,7 +105,7 @@ public:
static size_t getTotalMemUsage();
private:
Font(float size, const std::string& path);
Font(float size, const std::string& path, const bool linearMagnify);
static void initLibrary();
struct FontTexture {
@ -110,8 +113,9 @@ private:
glm::ivec2 textureSize;
glm::ivec2 writePos;
int rowHeight;
bool linearMagnify;
FontTexture(const int mFontSize);
FontTexture(const int mFontSize, const bool linearMagnifyArg);
~FontTexture();
bool findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out);
@ -161,7 +165,7 @@ private:
void clearFaceCache() { mFaceCache.clear(); }
static inline FT_Library sLibrary {nullptr};
static inline std::map<std::pair<std::string, float>, std::weak_ptr<Font>> sFontMap;
static inline std::map<std::tuple<float, std::string, bool>, std::weak_ptr<Font>> sFontMap;
static inline bool mLegacyTheme {false};
Renderer* mRenderer;
@ -171,6 +175,7 @@ private:
const std::string mPath;
float mFontSize;
const bool mLinearMagnify;
float mLetterHeight;
int mMaxGlyphHeight;
int mLegacyMaxGlyphHeight;

View file

@ -47,14 +47,14 @@
// build environment is broken.
#if defined(__unix__)
#if defined(ES_INSTALL_PREFIX)
std::string installPrefix = ES_INSTALL_PREFIX;
const std::string installPrefix {ES_INSTALL_PREFIX};
#else
#if defined(__linux__)
std::string installPrefix = "/usr";
const std::string installPrefix {"/usr"};
#elif defined(__NetBSD__)
std::string installPrefix = "/usr/pkg";
const std::string installPrefix {"/usr/pkg"};
#else
std::string installPrefix = "/usr/local";
const std::string installPrefix {"/usr/local"};
#endif
#endif
#endif
@ -63,12 +63,12 @@ namespace Utils
{
namespace FileSystem
{
static std::string homePath = "";
static std::string exePath = "";
static std::string homePath;
static std::string exePath;
StringList getDirContent(const std::string& path, const bool recursive)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
StringList contentList;
// Only parse the directory, if it's a directory.
@ -76,17 +76,19 @@ namespace Utils
#if defined(_WIN64)
WIN32_FIND_DATAW findData;
std::wstring wildcard = Utils::String::stringToWideString(genericPath) + L"/*";
HANDLE hFind = FindFirstFileW(wildcard.c_str(), &findData);
const std::wstring& wildcard {Utils::String::stringToWideString(genericPath) +
L"/*"};
const HANDLE hFind {FindFirstFileW(wildcard.c_str(), &findData)};
if (hFind != INVALID_HANDLE_VALUE) {
// Loop over all files in the directory.
do {
std::string name = Utils::String::wideStringToString(findData.cFileName);
const std::string& name {
Utils::String::wideStringToString(findData.cFileName)};
// Ignore "." and ".."
if ((name != ".") && (name != "..")) {
std::string fullName(getGenericPath(genericPath + "/" + name));
contentList.push_back(fullName);
const std::string& fullName {getGenericPath(genericPath + "/" + name)};
contentList.emplace_back(fullName);
if (recursive && isDirectory(fullName)) {
contentList.sort();
@ -97,18 +99,18 @@ namespace Utils
FindClose(hFind);
}
#else
DIR* dir = opendir(genericPath.c_str());
DIR* dir {opendir(genericPath.c_str())};
if (dir != nullptr) {
struct dirent* entry;
// Loop over all files in the directory.
while ((entry = readdir(dir)) != nullptr) {
std::string name(entry->d_name);
const std::string& name(entry->d_name);
// Ignore "." and ".."
if ((name != ".") && (name != "..")) {
std::string fullName(getGenericPath(genericPath + "/" + name));
contentList.push_back(fullName);
const std::string& fullName {getGenericPath(genericPath + "/" + name)};
contentList.emplace_back(fullName);
if (recursive && isDirectory(fullName)) {
contentList.sort();
@ -132,13 +134,13 @@ namespace Utils
if (entry == std::string::npos)
return files;
std::string parent {getParent(pattern)};
const std::string& parent {getParent(pattern)};
// Don't allow wildcard matching for the parent directory.
if (entry <= parent.size())
return files;
StringList dirContent {getDirContent(parent)};
const StringList& dirContent {getDirContent(parent)};
if (dirContent.size() == 0)
return files;
@ -172,19 +174,19 @@ namespace Utils
StringList getPathList(const std::string& path)
{
StringList pathList;
std::string genericPath = getGenericPath(path);
size_t start = 0;
size_t end = 0;
const std::string& genericPath {getGenericPath(path)};
size_t start {0};
size_t end {0};
// Split at '/'
while ((end = genericPath.find("/", start)) != std::string::npos) {
if (end != start)
pathList.push_back(std::string(genericPath, start, end - start));
pathList.emplace_back(std::string {genericPath, start, end - start});
start = end + 1;
}
// Add last folder / file to pathList.
if (start != genericPath.size())
pathList.push_back(std::string(genericPath, start, genericPath.size() - start));
pathList.emplace_back(std::string {genericPath, start, genericPath.size() - start});
return pathList;
}
@ -223,7 +225,7 @@ namespace Utils
#else
if (!homePath.length()) {
std::string envHome = getenv("HOME");
const std::string& envHome {getenv("HOME")};
if (envHome.length())
homePath = getGenericPath(envHome);
}
@ -260,17 +262,18 @@ namespace Utils
// Ugly hack to compensate for the Flatpak sandbox restrictions. We traverse
// this hardcoded list of paths and use the "which" command to check outside the
// sandbox if the emulator binary exists.
std::string pathVariable {"/var/lib/flatpak/exports/bin:/usr/bin:/usr/local/"
"bin:/usr/local/sbin:/usr/sbin:/sbin:/bin:/usr/games:/usr/"
"local/games:/snap/bin:/var/lib/snapd/snap/bin"};
const std::string& pathVariable {
"/var/lib/flatpak/exports/bin:/usr/bin:/usr/local/"
"bin:/usr/local/sbin:/usr/sbin:/sbin:/bin:/usr/games:/usr/"
"local/games:/snap/bin:/var/lib/snapd/snap/bin"};
std::vector<std::string> pathList {
const std::vector<std::string>& pathList {
Utils::String::delimitedStringToVector(pathVariable, ":")};
// Using a temporary file is the only viable solution I've found to communicate
// between the sandbox and the outside world.
std::string tempFile {Utils::FileSystem::getHomePath() + "/.emulationstation/" +
".flatpak_emulator_binary_path.tmp"};
const std::string& tempFile {Utils::FileSystem::getHomePath() + "/.emulationstation/" +
".flatpak_emulator_binary_path.tmp"};
std::string emulatorPath;
@ -293,9 +296,9 @@ namespace Utils
return emulatorPath;
#else
std::string pathVariable {std::string(getenv("PATH"))};
const std::string& pathVariable {std::string {getenv("PATH")}};
std::vector<std::string> pathList {
const std::vector<std::string>& pathList {
Utils::String::delimitedStringToVector(pathVariable, ":")};
std::string pathTest;
@ -313,7 +316,7 @@ namespace Utils
void setExePath(const std::string& path)
{
constexpr int pathMax = 32767;
constexpr int pathMax {32767};
#if defined(_WIN64)
std::wstring result(pathMax, 0);
if (GetModuleFileNameW(nullptr, &result[0], pathMax) != 0)
@ -349,20 +352,22 @@ namespace Utils
std::string getPreferredPath(const std::string& path)
{
std::string preferredPath = path;
#if defined(_WIN64)
size_t offset = std::string::npos;
std::string preferredPath {path};
size_t offset {std::string::npos};
// Convert '/' to '\\'
while ((offset = preferredPath.find('/')) != std::string::npos)
preferredPath.replace(offset, 1, "\\");
#else
const std::string& preferredPath {path};
#endif
return preferredPath;
}
std::string getGenericPath(const std::string& path)
{
std::string genericPath = path;
size_t offset = std::string::npos;
std::string genericPath {path};
size_t offset {std::string::npos};
// Remove "\\\\?\\"
if ((genericPath.find("\\\\?\\")) == 0)
@ -386,7 +391,7 @@ namespace Utils
std::string getEscapedPath(const std::string& path)
{
std::string escapedPath = getGenericPath(path);
std::string escapedPath {getGenericPath(path)};
#if defined(_WIN64)
// Windows escapes stuff by just putting everything in quotes.
@ -396,12 +401,12 @@ namespace Utils
return getPreferredPath(escapedPath);
#else
// Insert a backslash before most characters that would mess up a bash path.
const char* invalidChars = "\\ '\"!$^&*(){}[]?;<>";
const char* invalidChar = invalidChars;
const char* invalidChars {"\\ '\"!$^&*(){}[]?;<>"};
const char* invalidChar {invalidChars};
while (*invalidChar) {
size_t start = 0;
size_t offset = 0;
size_t start {0};
size_t offset {0};
while ((offset = escapedPath.find(*invalidChar, start)) != std::string::npos) {
start = offset + 1;
@ -423,17 +428,17 @@ namespace Utils
if ((path[0] == ':') && (path[1] == '/'))
return path;
std::string canonicalPath = exists(path) ? getAbsolutePath(path) : getGenericPath(path);
std::string canonicalPath {exists(path) ? getAbsolutePath(path) : getGenericPath(path)};
// Cleanup path.
bool scan = true;
bool scan {true};
while (scan) {
StringList pathList = getPathList(canonicalPath);
const StringList& pathList {getPathList(canonicalPath)};
canonicalPath.clear();
scan = false;
for (StringList::const_iterator it = pathList.cbegin(); it != pathList.cend();
for (StringList::const_iterator it {pathList.cbegin()}; it != pathList.cend();
++it) {
// Ignore empty.
if ((*it).empty())
@ -458,7 +463,7 @@ namespace Utils
#endif
if (isSymlink(canonicalPath)) {
std::string resolved = resolveSymlink(canonicalPath);
const std::string& resolved {resolveSymlink(canonicalPath)};
if (resolved.empty())
return "";
@ -481,8 +486,9 @@ namespace Utils
std::string getAbsolutePath(const std::string& path, const std::string& base)
{
std::string absolutePath = getGenericPath(path);
std::string baseVar = isAbsolute(base) ? getGenericPath(base) : getAbsolutePath(base);
const std::string& absolutePath {getGenericPath(path)};
const std::string& baseVar {isAbsolute(base) ? getGenericPath(base) :
getAbsolutePath(base)};
return isAbsolute(absolutePath) ? absolutePath :
getGenericPath(baseVar + "/" + absolutePath);
@ -490,8 +496,8 @@ namespace Utils
std::string getParent(const std::string& path)
{
std::string genericPath = getGenericPath(path);
size_t offset = std::string::npos;
std::string genericPath {getGenericPath(path)};
size_t offset {std::string::npos};
// Find last '/' and erase it.
if ((offset = genericPath.find_last_of('/')) != std::string::npos)
@ -503,13 +509,13 @@ namespace Utils
std::string getFileName(const std::string& path)
{
std::string genericPath = getGenericPath(path);
size_t offset = std::string::npos;
const std::string& genericPath {getGenericPath(path)};
size_t offset {std::string::npos};
// Find last '/' and return the filename.
if ((offset = genericPath.find_last_of('/')) != std::string::npos)
return ((genericPath[offset + 1] == 0) ? "." :
std::string(genericPath, offset + 1));
std::string {genericPath, offset + 1});
// No '/' found, entire path is a filename.
return genericPath;
@ -517,8 +523,8 @@ namespace Utils
std::string getStem(const std::string& path)
{
std::string fileName = getFileName(path);
size_t offset = std::string::npos;
std::string fileName {getFileName(path)};
size_t offset {std::string::npos};
// Empty fileName.
if (fileName == ".")
@ -536,8 +542,8 @@ namespace Utils
std::string getExtension(const std::string& path)
{
std::string fileName = getFileName(path);
size_t offset = std::string::npos;
const std::string& fileName {getFileName(path)};
size_t offset {std::string::npos};
// Empty fileName.
if (fileName == ".")
@ -545,7 +551,7 @@ namespace Utils
// Find last '.' and return the extension.
if ((offset = fileName.find_last_of('.')) != std::string::npos)
return std::string(fileName, offset);
return std::string {fileName, offset};
// No '.' found, filename has no extension.
return ".";
@ -561,9 +567,9 @@ namespace Utils
const std::string& relativeTo,
const bool allowHome)
{
std::string genericPath = getGenericPath(path);
std::string relativeToVar =
isDirectory(relativeTo) ? getGenericPath(relativeTo) : getParent(relativeTo);
const std::string& genericPath {getGenericPath(path)};
const std::string& relativeToVar {isDirectory(relativeTo) ? getGenericPath(relativeTo) :
getParent(relativeTo)};
// Nothing to resolve.
if (!genericPath.length())
@ -585,8 +591,8 @@ namespace Utils
const std::string& relativeTo,
const bool allowHome)
{
bool contains = false;
std::string relativePath = removeCommonPath(path, relativeTo, contains);
bool contains {false};
std::string relativePath {removeCommonPath(path, relativeTo, contains)};
if (contains)
return ("./" + relativePath);
@ -604,9 +610,9 @@ namespace Utils
const std::string& commonArg,
bool& contains)
{
std::string genericPath = getGenericPath(path);
std::string common =
isDirectory(commonArg) ? getGenericPath(commonArg) : getParent(commonArg);
const std::string& genericPath {getGenericPath(path)};
const std::string& common {isDirectory(commonArg) ? getGenericPath(commonArg) :
getParent(commonArg)};
if (genericPath.find(common) == 0) {
contains = true;
@ -619,7 +625,7 @@ namespace Utils
std::string resolveSymlink(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
std::string resolved;
#if defined(_WIN64)
@ -675,10 +681,10 @@ namespace Utils
}
#if defined(_WIN64)
std::ifstream sourceFile(Utils::String::stringToWideString(sourcePath).c_str(),
std::ios::binary);
std::ifstream sourceFile {Utils::String::stringToWideString(sourcePath).c_str(),
std::ios::binary};
#else
std::ifstream sourceFile(sourcePath, std::ios::binary);
std::ifstream sourceFile {sourcePath, std::ios::binary};
#endif
if (sourceFile.fail()) {
@ -689,10 +695,10 @@ namespace Utils
}
#if defined(_WIN64)
std::ofstream targetFile(Utils::String::stringToWideString(destinationPath).c_str(),
std::ios::binary);
std::ofstream targetFile {Utils::String::stringToWideString(destinationPath).c_str(),
std::ios::binary};
#else
std::ofstream targetFile(destinationPath, std::ios::binary);
std::ofstream targetFile {destinationPath, std::ios::binary};
#endif
if (targetFile.fail()) {
@ -744,7 +750,7 @@ namespace Utils
bool removeFile(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
// Don't remove if it doesn't exists.
if (!exists(genericPath))
@ -798,7 +804,7 @@ namespace Utils
bool createDirectory(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
if (exists(genericPath))
return true;
@ -812,7 +818,7 @@ namespace Utils
#endif
// Failed to create directory, try to create the parent.
std::string parent = getParent(genericPath);
const std::string& parent {getParent(genericPath)};
// Only try to create parent if it's not identical to genericPath.
if (parent != genericPath)
@ -829,7 +835,7 @@ namespace Utils
bool exists(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
struct stat info;
@ -846,7 +852,7 @@ namespace Utils
bool driveExists(const std::string& path)
{
#if defined(_WIN64)
std::string genericPath = getGenericPath(path);
std::string genericPath {getGenericPath(path)};
// Try to add a dot or a backslash and a dot depending on how the drive
// letter was defined by the user.
if (genericPath.length() == 2 && genericPath.at(1) == ':')
@ -864,7 +870,7 @@ namespace Utils
bool isAbsolute(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
#if defined(_WIN64)
return ((genericPath.size() > 1) && (genericPath[1] == ':'));
@ -875,7 +881,7 @@ namespace Utils
bool isRegularFile(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
struct stat info;
@ -897,7 +903,7 @@ namespace Utils
bool isDirectory(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
struct stat info;
@ -919,12 +925,12 @@ namespace Utils
bool isSymlink(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
#if defined(_WIN64)
// Check for symlink attribute.
const DWORD Attributes =
GetFileAttributesW(Utils::String::stringToWideString(genericPath).c_str());
const DWORD Attributes {
GetFileAttributesW(Utils::String::stringToWideString(genericPath).c_str())};
if ((Attributes != INVALID_FILE_ATTRIBUTES) &&
(Attributes & FILE_ATTRIBUTE_REPARSE_POINT))
return true;
@ -952,12 +958,12 @@ namespace Utils
bool isHidden(const std::string& path)
{
std::string genericPath = getGenericPath(path);
const std::string& genericPath {getGenericPath(path)};
#if defined(_WIN64)
// Check for hidden attribute.
const DWORD Attributes =
GetFileAttributesW(Utils::String::stringToWideString(genericPath).c_str());
const DWORD Attributes {
GetFileAttributesW(Utils::String::stringToWideString(genericPath).c_str())};
if ((Attributes != INVALID_FILE_ATTRIBUTES) && (Attributes & FILE_ATTRIBUTE_HIDDEN))
return true;
#endif

View file

@ -0,0 +1,363 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733333 67.733333"
version="1.1"
id="svg4925"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="joystick_arcade_twin.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4919" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.6601232"
inkscape:cx="-20.675734"
inkscape:cy="97.551873"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2065"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata4922">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-229.26665)">
<rect
style="opacity:1;vector-effect:none;fill:#373737;fill-opacity:1;stroke:#000000;stroke-width:0.26458333;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect5985-6-0"
width="56.947788"
height="33.415451"
x="5.3927727"
y="246.4256"
ry="3.286572"
rx="3.1865375" />
<g
id="g4928"
transform="matrix(0.99990441,0,0,1.0164462,0.00195324,-4.327527)">
<ellipse
ry="10.483982"
rx="10.657423"
cx="20.499538"
cy="263.1333"
id="circle34-2-3-3-0-2"
style="opacity:1;fill:#7b7b7b;fill-opacity:1;stroke:#080701;stroke-width:0.24870832;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
ry="6.1186433"
rx="6.2198658"
cx="20.499538"
cy="263.1333"
id="circle34-2-3-39-9"
style="opacity:1;fill:#424242;fill-opacity:1;stroke-width:0.14515407" />
<g
transform="matrix(0.83301628,0,0,0.81945968,4.5251315,59.03631)"
id="g5882-1-9">
<g
id="g5815-9-6">
<g
id="g5786-6-0">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.1078754"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-9-2"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.10787541"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-3-7"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
<g
transform="rotate(90,19.176585,249.06284)"
id="g5786-9-3-6">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.1078754"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-1-8-1"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.10787541"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-9-0-3"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
</g>
<g
id="g5815-6-5-2"
transform="rotate(45,19.238276,249.03729)">
<g
id="g5786-4-6-1">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.1078754"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-0-6-59"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.10787541"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-0-4-9"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
<g
transform="rotate(90,19.176585,249.06284)"
id="g5786-9-4-0-1">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.1078754"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-1-6-0-4"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.10787541"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-9-2-4-9"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
</g>
</g>
<circle
id="ellipse8-1"
transform="rotate(-166.691)"
cy="-251.3472"
cx="-80.522942"
r="3.860163"
style="fill:#9e9e9b;stroke-width:0.13708451" />
<circle
transform="rotate(-90)"
id="circle10-0"
r="3.0756288"
cy="20.498316"
cx="-263.13339"
style="fill:#565656;stroke-width:0.13708451" />
<path
id="path12-7"
d="m 20.507225,259.29748 c -2.123437,0 -3.844811,1.72138 -3.844811,3.84482 0,2.12343 1.721374,3.84481 3.844811,3.84481 2.123441,0 3.84481,-1.72138 3.84481,-3.84481 0,-2.12344 -1.721369,-3.84482 -3.84481,-3.84482 z m -0.04741,7.48263 c -2.035291,0 -3.685245,-1.64995 -3.685245,-3.68525 0,-2.03529 1.649954,-3.68524 3.685245,-3.68524 2.035292,0 3.685242,1.64995 3.685242,3.68524 0,2.0353 -1.64995,3.68525 -3.685242,3.68525 z"
inkscape:connector-curvature="0"
style="fill:#c9c9c7;stroke-width:0.13708451" />
<path
id="path14-5"
d="m 20.507093,266.98697 c 2.123437,0 3.844806,-1.72124 3.844806,-3.84481 0,-2.12344 -1.721369,-3.84481 -3.844806,-3.84481 -2.123442,0 -3.844811,1.72137 -3.844811,3.84481 1.28e-4,2.12357 1.721506,3.84481 3.844811,3.84481 z m 0.04741,-7.48249 c 2.035292,0 3.685241,1.64995 3.685241,3.68525 0,2.03529 -1.649949,3.68524 -3.685241,3.68524 -2.035155,0 -3.685241,-1.64995 -3.685241,-3.68524 1.28e-4,-2.0353 1.650086,-3.68525 3.685241,-3.68525 z"
inkscape:connector-curvature="0"
style="fill:#9a9a9b;stroke-width:0.13708451" />
<ellipse
ry="5.0897107"
rx="5.1739116"
cx="20.06336"
cy="260.785"
id="circle34-2-3-1-4-8"
style="opacity:1;fill:#f4d400;fill-opacity:1;stroke:#080701;stroke-width:0.25320625;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
inkscape:transform-center-y="-2.1261916"
inkscape:transform-center-x="10.630944"
transform="matrix(0.6842207,0.729275,-0.6842207,0.729275,0,0)"
ry="0.88702333"
rx="0.65102625"
cy="164.61426"
cx="190.09605"
id="path4910-2-7"
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.01910845;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.84313725;paint-order:normal" />
</g>
<g
id="g4928-3"
transform="matrix(0.99990441,0,0,1.0164462,26.419465,-4.327527)">
<ellipse
ry="10.483982"
rx="10.657423"
cx="20.499538"
cy="263.1333"
id="circle34-2-3-3-0-2-6"
style="opacity:1;fill:#7b7b7b;fill-opacity:1;stroke:#080701;stroke-width:0.248708;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
ry="6.1186433"
rx="6.2198658"
cx="20.499538"
cy="263.1333"
id="circle34-2-3-39-9-7"
style="opacity:1;fill:#424242;fill-opacity:1;stroke-width:0.145154" />
<g
transform="matrix(0.83301628,0,0,0.81945968,4.5251315,59.03631)"
id="g5882-1-9-5">
<g
id="g5815-9-6-3">
<g
id="g5786-6-0-5">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-9-2-6"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-3-7-2"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
<g
transform="rotate(90,19.176585,249.06284)"
id="g5786-9-3-6-9">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-1-8-1-1"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-9-0-3-2"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
</g>
<g
id="g5815-6-5-2-7"
transform="rotate(45,19.238276,249.03729)">
<g
id="g5786-4-6-1-0">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-0-6-59-9"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-0-4-9-3"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
<g
transform="rotate(90,19.176585,249.06284)"
id="g5786-9-4-0-1-6">
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176687,237.11862 -1.677879,1.91267 h 0.81948 v 1.74035 h 1.7166 v -1.74022 h 0.81948 z"
id="path4-0-1-6-0-4-0"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="2.11451"
inkscape:transform-center-y="-0.31364665" />
<path
style="fill:#b7b7b9;fill-opacity:1;stroke-width:0.107875"
inkscape:connector-curvature="0"
d="m 19.176489,261.00707 1.677879,-1.91267 h -0.81948 v -1.74035 h -1.7166 v 1.74022 h -0.819481 z"
id="path4-0-4-9-2-4-9-6"
sodipodi:nodetypes="cccccccc"
inkscape:transform-center-x="-2.11451"
inkscape:transform-center-y="0.31364335" />
</g>
</g>
</g>
<circle
id="ellipse8-1-2"
transform="rotate(-166.691)"
cy="-251.3472"
cx="-80.522942"
r="3.860163"
style="fill:#9e9e9b;stroke-width:0.137085" />
<circle
transform="rotate(-90)"
id="circle10-0-6"
r="3.0756288"
cy="20.498316"
cx="-263.13339"
style="fill:#565656;stroke-width:0.137085" />
<path
id="path12-7-1"
d="m 20.507225,259.29748 c -2.123437,0 -3.844811,1.72138 -3.844811,3.84482 0,2.12343 1.721374,3.84481 3.844811,3.84481 2.123441,0 3.84481,-1.72138 3.84481,-3.84481 0,-2.12344 -1.721369,-3.84482 -3.84481,-3.84482 z m -0.04741,7.48263 c -2.035291,0 -3.685245,-1.64995 -3.685245,-3.68525 0,-2.03529 1.649954,-3.68524 3.685245,-3.68524 2.035292,0 3.685242,1.64995 3.685242,3.68524 0,2.0353 -1.64995,3.68525 -3.685242,3.68525 z"
inkscape:connector-curvature="0"
style="fill:#c9c9c7;stroke-width:0.137085" />
<path
id="path14-5-8"
d="m 20.507093,266.98697 c 2.123437,0 3.844806,-1.72124 3.844806,-3.84481 0,-2.12344 -1.721369,-3.84481 -3.844806,-3.84481 -2.123442,0 -3.844811,1.72137 -3.844811,3.84481 1.28e-4,2.12357 1.721506,3.84481 3.844811,3.84481 z m 0.04741,-7.48249 c 2.035292,0 3.685241,1.64995 3.685241,3.68525 0,2.03529 -1.649949,3.68524 -3.685241,3.68524 -2.035155,0 -3.685241,-1.64995 -3.685241,-3.68524 1.28e-4,-2.0353 1.650086,-3.68525 3.685241,-3.68525 z"
inkscape:connector-curvature="0"
style="fill:#9a9a9b;stroke-width:0.137085" />
<ellipse
ry="5.0897107"
rx="5.1739116"
cx="20.06336"
cy="260.785"
id="circle34-2-3-1-4-8-7"
style="opacity:1;fill:#f4d400;fill-opacity:1;stroke:#080701;stroke-width:0.253206;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
inkscape:transform-center-y="-2.1261916"
inkscape:transform-center-x="10.630944"
transform="matrix(0.6842207,0.729275,-0.6842207,0.729275,0,0)"
ry="0.88702333"
rx="0.65102625"
cy="164.61426"
cx="190.09605"
id="path4910-2-7-9"
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.0191085;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.843137;paint-order:normal" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path id="outline" d="M12,4 L52,4 A8,8 90 0,1 60,12 L60,52 A8,8 90 0,1 52,60 L12,60 A8,8 90 0,1 4,52 L4,12 A8,8 90 0,1 12,4Z" fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="2"/>
<path id="button_hotkey" d="m12 4a8 8 0 0 0-8 8v40a8 8 0 0 0 8 8h40a8 8 0 0 0 8-8v-40a8 8 0 0 0-8-8h-40zm21.039062 12.302734c2.205868 0 3.894744 0.634451 5.066407 1.904297 1.171662 1.269847 1.757812 3.095918 1.757812 5.478516 0 2.376053-0.58937 4.204077-1.767578 5.480469s-2.867085 1.914062-5.066406 1.914062c-2.199322 0-3.888198-0.63767-5.066406-1.914062s-1.767579-3.110857-1.767579-5.5c0-2.389144 0.589371-4.211995 1.767579-5.46875 1.184753-1.263301 2.87685-1.894532 5.076171-1.894532zm-21.039062 0.226563h3.042969v5.625h5.685547v-5.625h3.033203v14.353515h-3.033203v-6.195312h-5.685547v6.195312h-3.042969v-14.353515zm29.160156 0h10.839844v2.533203h-3.898438v11.820312h-3.042968v-11.820312h-3.898438v-2.533203zm-8.111328 2.306641c-1.210936 0-2.122995 0.410315-2.738281 1.228515-0.615286 0.811654-0.923828 2.017422-0.923828 3.621094 0 3.22698 1.21416 4.841797 3.642578 4.841797 2.428417 0 3.642578-1.614817 3.642578-4.841797 0-3.233526-1.207721-4.84961-3.623047-4.849609zm-21.048828 14.505859h3.425781v6.568359l1.34961-1.6875 4.376953-4.880859h3.800781l-5.636719 6.353515 5.681641 8.001954h-3.890625l-4.232422-6.048828-1.449219 0.923828v5.125h-3.425781v-14.355469zm15.125 0h9.306641v2.494141h-5.878907v3.152343h5.458985v2.494141h-5.458985v3.701172h5.878907v2.513672h-9.306641v-14.355469zm10.75 0h3.712891l3.349609 5.912109 3.371094-5.912109h3.691406l-5.361328 8.767578v5.587891h-3.404297v-5.488282l-5.359375-8.867187z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="64"
height="64"
version="1.1"
viewBox="0 0 64 64"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
id="button_lt"
d="M 2,3 V 16 A 13,13 0 0 0 15,29 H 49 A 13,13 0 0 0 62,16 V 3 Z M 21.939453,7.4335941 H 26.04102 V 21.355469 h 4.28906 v 3.210937 h -8.390627 z m 9.667967,0 H 42.06055 V 10.65625 h -3.1875 V 24.566406 H 34.79492 V 10.65625 h -3.1875 z"
fill="#ffffff" />
<path
id="button_rt"
d="m 2.000002,35 v 13 a 13,13 0 0 0 13,13 h 33.999996 a 13,13 0 0 0 13,-13 V 35 Z m 19.119141,4.433594 h 4.546875 c 1.882812,0 3.273437,0.425781 4.171875,1.277344 0.898437,0.851562 1.347655,2.15625 1.347655,3.914062 0,2.039062 -0.699218,3.519531 -2.097655,4.441406 l 3.281245,7.5 h -4.324214 l -2.625,-6.46875 h -0.210938 v 6.46875 h -4.089843 z m 11.308595,0 h 10.45312 v 3.222656 h -3.1875 v 13.910156 h -4.07812 V 42.65625 h -3.1875 z m -7.218752,3.175781 v 4.324219 h 0.304688 c 0.539062,0 0.929687,-0.183594 1.171875,-0.550782 0.25,-0.367187 0.375,-0.921874 0.375,-1.664062 0,-0.75 -0.128906,-1.289062 -0.386719,-1.617188 -0.25,-0.328124 -0.644531,-0.492187 -1.183594,-0.492187 z"
fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -4,7 +4,7 @@
// core.glsl
//
// Core shader functionality:
// Clipping, opacity, saturation, dimming and reflections falloff.
// Clipping, brightness, saturation, opacity, dimming and reflections falloff.
//
// Vertex section of code:
@ -39,8 +39,9 @@ in vec2 texCoord;
in vec4 color;
uniform vec4 clipRegion;
uniform float opacity;
uniform float brightness;
uniform float saturation;
uniform float opacity;
uniform float dimming;
uniform float reflectionsFalloff;
uniform uint shaderFlags;
@ -70,6 +71,25 @@ void main()
vec4 sampledColor = texture(textureSampler, texCoord);
// Brightness.
if (brightness != 0.0) {
sampledColor.rgb /= sampledColor.a;
sampledColor.rgb += 0.3 * brightness;
sampledColor.rgb *= sampledColor.a;
}
// Saturation.
if (saturation != 1.0) {
vec3 grayscale;
// Premultiplied textures are all in BGRA format.
if (0x0u != (shaderFlags & 0x01u))
grayscale = vec3(dot(sampledColor.bgr, vec3(0.114, 0.587, 0.299)));
else
grayscale = vec3(dot(sampledColor.rgb, vec3(0.299, 0.587, 0.114)));
vec3 blendedColor = mix(grayscale, sampledColor.rgb, saturation);
sampledColor = vec4(blendedColor, sampledColor.a);
}
// For fonts the alpha information is stored in the red channel.
if (0x0u != (shaderFlags & 0x2u))
sampledColor = vec4(1.0, 1.0, 1.0, sampledColor.r);
@ -97,18 +117,6 @@ void main()
sampledColor *= opacity;
}
// Saturation.
if (saturation != 1.0) {
vec3 grayscale;
// Premultiplied textures are all in BGRA format.
if (0x0u != (shaderFlags & 0x01u))
grayscale = vec3(dot(sampledColor.bgr, vec3(0.34, 0.55, 0.11)));
else
grayscale = vec3(dot(sampledColor.rgb, vec3(0.34, 0.55, 0.11)));
vec3 blendedColor = mix(grayscale, sampledColor.rgb, saturation);
sampledColor = vec4(blendedColor, sampledColor.a);
}
// Dimming.
if (dimming != 1.0) {
vec4 dimColor = vec4(dimming, dimming, dimming, 1.0);

View file

@ -30,10 +30,13 @@ precision mediump float;
uniform mat4 MVPMatrix;
in vec2 positionVertex;
in vec2 texCoordVertex;
in vec4 colorVertex;
uniform vec2 textureSize;
out vec2 texCoord;
out vec2 onex;
out vec2 oney;
out vec4 colorShift;
#define SourceSize vec4(textureSize, 1.0 / textureSize)
@ -43,6 +46,7 @@ void main()
texCoord = texCoordVertex;
onex = vec2(SourceSize.z, 0.0);
oney = vec2(0.0, SourceSize.w);
colorShift.abgr = colorVertex.rgba;
}
// Fragment section of code:
@ -54,11 +58,13 @@ precision mediump float;
uniform vec2 textureSize;
uniform float opacity;
uniform float brightness;
uniform float saturation;
uniform sampler2D textureSampler;
in vec2 texCoord;
in vec2 onex;
in vec2 oney;
in vec4 colorShift;
out vec4 FragColor;
#define SourceSize vec4(textureSize, 1.0 / textureSize)
@ -101,13 +107,6 @@ void main()
float h_weight_00 = dx / SPOT_WIDTH;
WEIGHT(h_weight_00);
// Saturation.
if (saturation != 1.0) {
vec3 grayscale = vec3(dot(color.rgb, vec3(0.34, 0.55, 0.11)));
vec3 blendedColor = mix(grayscale, color.rgb, saturation);
color = vec4(blendedColor, color.a);
}
color *= vec4(h_weight_00, h_weight_00, h_weight_00, h_weight_00);
// Get closest horizontal neighbour to blend.
@ -122,13 +121,6 @@ void main()
}
vec4 colorNB = TEX2D(texture_coords + coords01);
// Saturation.
if (saturation != 1.0) {
vec3 grayscale = vec3(dot(colorNB.rgb, vec3(0.34, 0.55, 0.11)));
vec3 blendedColor = mix(grayscale, colorNB.rgb, saturation);
colorNB = vec4(blendedColor, colorNB.a);
}
float h_weight_01 = dx / SPOT_WIDTH;
WEIGHT(h_weight_01);
@ -152,13 +144,6 @@ void main()
}
colorNB = TEX2D(texture_coords + coords10);
// Saturation.
if (saturation != 1.0) {
vec3 grayscale = vec3(dot(colorNB.rgb, vec3(0.34, 0.55, 0.11)));
vec3 blendedColor = mix(grayscale, colorNB.rgb, saturation);
colorNB = vec4(blendedColor, colorNB.a);
}
float v_weight_10 = dy / SPOT_HEIGHT;
WEIGHT(v_weight_10);
@ -170,6 +155,26 @@ void main()
color *= vec4(COLOR_BOOST);
vec4 colorTemp = clamp(GAMMA_OUT(color), 0.0, 1.0);
// Brightness.
if (brightness != 0.0) {
colorTemp.rgb /= colorTemp.a;
colorTemp.rgb += 0.3 * brightness;
colorTemp.rgb *= colorTemp.a;
}
// Saturation.
if (saturation != 1.0) {
vec3 grayscale;
grayscale = vec3(dot(colorTemp.bgr, vec3(0.114, 0.587, 0.299)));
vec3 blendedColor = mix(grayscale, colorTemp.rgb, saturation);
colorTemp = vec4(blendedColor, colorTemp.a);
}
// Color shift.
colorTemp.rgb *= colorShift.rgb;
colorTemp.a *= colorShift.a;
FragColor = vec4(colorTemp.rgb, colorTemp.a * opacity);
}
#endif

View file

@ -46,6 +46,12 @@
<entry>/Applications/BasiliskII.app/Contents/MacOS/BasiliskII</entry>
</rule>
</emulator>
<emulator name="CEMU">
<!-- Nintendo Wii U emulator Cemu -->
<rule type="staticpath">
<entry>/Applications/Cemu.app/Contents/MacOS/Cemu</entry>
</rule>
</emulator>
<emulator name="CITRA">
<!-- Nintendo 3DS emulator Citra -->
<rule type="staticpath">
@ -218,6 +224,12 @@
<entry>/Applications/Ruffle.app/Contents/MacOS/ruffle</entry>
</rule>
</emulator>
<emulator name="RYUJINX">
<!-- Nintendo Switch emulator Ryujinx -->
<rule type="staticpath">
<entry>/Applications/Ryujinx.app/Contents/MacOS/Ryujinx</entry>
</rule>
</emulator>
<emulator name="SAMEBOY">
<!-- Nintendo Game Boy / Color emulator SameBoy -->
<rule type="staticpath">

View file

@ -1440,8 +1440,8 @@
<name>switch</name>
<fullname>Nintendo Switch</fullname>
<path>%ROMPATH%/switch</path>
<extension>.7z .7Z .zip .ZIP</extension>
<command>PLACEHOLDER %ROM%</command>
<extension>.nca .NCA .nro .NRO .nso .NSO .nsp .NSP .xci .XCI .7z .7Z .zip .ZIP</extension>
<command label="Ryujinx (Standalone)">%EMULATOR_RYUJINX% %ROM%</command>
<platform>switch</platform>
<theme>switch</theme>
</system>
@ -1584,8 +1584,8 @@
<name>wiiu</name>
<fullname>Nintendo Wii U</fullname>
<path>%ROMPATH%/wiiu</path>
<extension>.7z .7Z .zip .ZIP</extension>
<command>PLACEHOLDER %ROM%</command>
<extension>.rpx .RPX .wua .WUA .wud .WUD .wux .WUX</extension>
<command label="Cemu (Standalone)">%EMULATOR_CEMU% -g %ROM%</command>
<platform>wiiu</platform>
<theme>wiiu</theme>
</system>

View file

@ -326,6 +326,7 @@
<!-- Nintendo Game Boy Advance emulator mGBA -->
<rule type="systempath">
<entry>mgba</entry>
<entry>mgba-qt</entry>
<entry>io.mgba.mGBA</entry>
</rule>
<rule type="staticpath">
@ -591,6 +592,7 @@
<!-- Super Nintendo emulator Snes9x -->
<rule type="systempath">
<entry>snes9x</entry>
<entry>snes9x-gtk</entry>
</rule>
<rule type="staticpath">
<entry>/var/lib/flatpak/exports/bin/com.snes9x.Snes9x</entry>

View file

@ -115,6 +115,7 @@
<command label="Flycast (Standalone)">%EMULATOR_FLYCAST% %ROM%</command>
<command label="Kronos">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/kronos_libretro.so %ROM%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel [Fullscreen] (Standalone)">%STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<platform>arcade</platform>
<theme>arcade</theme>
</system>
@ -682,6 +683,7 @@
<command label="Flycast (Standalone)">%EMULATOR_FLYCAST% %ROM%</command>
<command label="Kronos">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/kronos_libretro.so %ROM%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel [Fullscreen] (Standalone)">%STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<platform>arcade</platform>
<theme>mame</theme>
</system>
@ -791,6 +793,7 @@
<path>%ROMPATH%/model3</path>
<extension>.7z .7Z .zip .ZIP</extension>
<command label="Supermodel (Standalone)">%STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel [Fullscreen] (Standalone)">%STARTDIR%=%GAMEDIR% %EMULATOR_SUPERMODEL% -log-output=%GAMEDIR%/Config/Supermodel.log -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<platform>arcade</platform>
<theme>model3</theme>
</system>
@ -1684,7 +1687,7 @@
<name>xbox360</name>
<fullname>Microsoft Xbox 360</fullname>
<path>%ROMPATH%/xbox360</path>
<extension>.iso .ISO .xex .XEX</extension>
<extension>. .iso .ISO .xex .XEX</extension>
<command>PLACEHOLDER %ROM%</command>
<platform>xbox360</platform>
<theme>xbox360</theme>

View file

@ -502,6 +502,18 @@
<entry>%ESPATH%\..\redream\redream.exe</entry>
</rule>
</emulator>
<emulator name="ROSALIES-MUPEN-GUI">
<!-- Nintendo 64 emulator Rosalie's Mupen GUI -->
<rule type="systempath">
<entry>RMG.exe</entry>
</rule>
<rule type="staticpath">
<entry>~\AppData\Local\Programs\Rosalie's Mupen GUI\RMG.exe</entry>
<entry>%ESPATH%\Emulators\RMG\RMG.exe</entry>
<entry>%ESPATH%\RMG\RMG.exe</entry>
<entry>%ESPATH%\..\RMG\RMG.exe</entry>
</rule>
</emulator>
<emulator name="RPCS3">
<!-- Sony PlayStation 3 emulator RPCS3 -->
<rule type="systempath">

View file

@ -339,6 +339,14 @@
<entry>%ESPATH%\..\redream\redream.exe</entry>
</rule>
</emulator>
<emulator name="ROSALIES-MUPEN-GUI">
<!-- Nintendo 64 emulator Rosalie's Mupen GUI -->
<rule type="staticpath">
<entry>%ESPATH%\Emulators\RMG\RMG.exe</entry>
<entry>%ESPATH%\RMG\RMG.exe</entry>
<entry>%ESPATH%\..\RMG\RMG.exe</entry>
</rule>
</emulator>
<emulator name="RPCS3">
<!-- Sony PlayStation 3 emulator RPCS3 -->
<rule type="staticpath">

View file

@ -116,7 +116,8 @@
<command label="Kronos">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%\kronos_libretro.dll %ROM%</command>
<command label="Model 2 Emulator (Standalone)">%RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME%</command>
<command label="Model 2 Emulator [Suspend ES-DE] (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel [Fullscreen] (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<platform>arcade</platform>
<theme>arcade</theme>
</system>
@ -682,7 +683,8 @@
<command label="Kronos">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%\kronos_libretro.dll %ROM%</command>
<command label="Model 2 Emulator (Standalone)">%RUNINBACKGROUND% %STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME%</command>
<command label="Model 2 Emulator [Suspend ES-DE] (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_M2EMULATOR% %BASENAME%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel [Fullscreen] (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<platform>arcade</platform>
<theme>mame</theme>
</system>
@ -792,7 +794,8 @@
<fullname>Sega Model 3</fullname>
<path>%ROMPATH%\model3</path>
<extension>.7z .7Z .zip .ZIP</extension>
<command label="Supermodel (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<command label="Supermodel [Fullscreen] (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_SUPERMODEL% -fullscreen -force-feedback %INJECT%=%BASENAME%.commands %ROM%</command>
<platform>arcade</platform>
<theme>model3</theme>
</system>
@ -902,6 +905,7 @@
<command label="Mupen64Plus (Standalone)">%EMULATOR_MUPEN64PLUS% --fullscreen %ROM%</command>
<command label="ParaLLEl N64">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%\parallel_n64_libretro.dll %ROM%</command>
<command label="simple64 (Standalone)">%EMULATOR_SIMPLE64% --nogui %ROM%</command>
<command label="Rosalie's Mupen GUI (Standalone)">%EMULATOR_ROSALIES-MUPEN-GUI% %ROM%</command>
<command label="Project64 (Standalone)">%EMULATOR_PROJECT64% %ROM%</command>
<command label="ares (Standalone)">%EMULATOR_ARES% --fullscreen --system "Nintendo 64" %ROM%</command>
<platform>n64</platform>
@ -1686,7 +1690,7 @@
<name>xbox360</name>
<fullname>Microsoft Xbox 360</fullname>
<path>%ROMPATH%\xbox360</path>
<extension>.iso .ISO .xex .XEX</extension>
<extension>. .iso .ISO .xex .XEX</extension>
<command label="xenia (Standalone)">%STARTDIR%=%EMUDIR% %EMULATOR_XENIA% %ROM%</command>
<platform>xbox360</platform>
<theme>xbox360</theme>

View file

@ -60,7 +60,7 @@
<pos>0 0.3</pos>
<size>1 0.4</size>
<type>horizontal</type>
<staticItem>./art/${system.theme}.jpg</staticItem>
<staticImage>./art/${system.theme}.jpg</staticImage>
<itemScale>1</itemScale>
<itemVerticalAlignment>center</itemVerticalAlignment>
<unfocusedItemOpacity>1</unfocusedItemOpacity>

View file

@ -47,6 +47,11 @@
<selectable>true</selectable>
</variant>
<variant name="themeEngineTest_4">
<label>Theme engine test 4</label>
<selectable>true</selectable>
</variant>
<variant name="noMedia">
<label>No game media</label>
<selectable>false</selectable>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="400"
height="400"
viewBox="0 0 105.83333 105.83333"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<g
id="layer1">
<rect
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.0467413;stroke-linejoin:round;stroke-miterlimit:2;-inkscape-stroke:none;stop-color:#000000"
id="rect846"
width="105.83334"
height="105.83334"
x="-5.0862632e-06"
y="-5.0862632e-06"
rx="7.7669764"
ry="7.7669764" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 709 B

View file

@ -27,11 +27,11 @@
<pos>0 0.383796</pos>
<size>1 0.232407</size>
<type>horizontal</type>
<staticItem>./${system.theme}/images/logo.svg</staticItem>
<staticImage>./${system.theme}/images/logo.svg</staticImage>
<maxItemCount>3</maxItemCount>
<itemSize>0.25 0.125</itemSize>
<itemScale>1.23</itemScale>
<itemInterpolation>linear</itemInterpolation>
<imageInterpolation>linear</imageInterpolation>
<unfocusedItemOpacity>0.5</unfocusedItemOpacity>
<color>${systemCarouselColor}</color>
<text>${system.fullName}</text>
@ -431,4 +431,8 @@
<variant name="themeEngineTest_3">
<include>./theme_engine_test_3.xml</include>
</variant>
<variant name="themeEngineTest_4">
<include>./theme_engine_test_4.xml</include>
</variant>
</theme>

View file

@ -6,7 +6,7 @@
<pos>0 0</pos>
<size>0.28 1.0</size>
<type>vertical_wheel</type>
<staticItem>./${system.theme}/images/logo.svg</staticItem>
<staticImage>./${system.theme}/images/logo.svg</staticImage>
<itemsBeforeCenter>4</itemsBeforeCenter>
<itemsAfterCenter>4</itemsAfterCenter>
<itemSize>0.15 0.125</itemSize>
@ -28,7 +28,7 @@
</carousel>
<gameselector name="selector_recent">
<selection>lastplayed</selection>
<gameCount>3</gameCount>
<gameCount>1</gameCount>
</gameselector>
<gameselector name="selector_random">
<selection>random</selection>
@ -40,7 +40,6 @@
<imageType>fanart, titlescreen</imageType>
<gameselector>selector_random</gameselector>
<interpolation>nearest</interpolation>
<scrollFadeIn>true</scrollFadeIn>
<opacity>0.8</opacity>
<saturation>0.8</saturation>
<zIndex>3</zIndex>
@ -52,7 +51,6 @@
<imageType>cover</imageType>
<gameselector>selector_recent</gameselector>
<interpolation>nearest</interpolation>
<scrollFadeIn>true</scrollFadeIn>
<zIndex>3</zIndex>
</image>
<video name="random_game_video">
@ -118,7 +116,7 @@
<letterCase>none</letterCase>
<zIndex>50</zIndex>
</text>
<datetime name="game_release_date">
<datetime name="game_lastplayed_date">
<pos>0.775 0.64</pos>
<size>0.2 0.05</size>
<metadata>lastplayed</metadata>

View file

@ -110,11 +110,11 @@
<pos>0.025 0.201</pos>
<size>0.39 0.711</size>
<type>vertical</type>
<itemType>marquee</itemType>
<imageType>marquee</imageType>
<maxItemCount>5</maxItemCount>
<itemSize>0.26 0.105</itemSize>
<itemScale>1.23</itemScale>
<itemInterpolation>linear</itemInterpolation>
<imageInterpolation>linear</imageInterpolation>
<itemHorizontalAlignment>center</itemHorizontalAlignment>
<horizontalOffset>0</horizontalOffset>
<verticalOffset>0</verticalOffset>

View file

@ -110,11 +110,11 @@
<pos>0.025 0.231</pos>
<size>0.95 0.692</size>
<type>horizontal</type>
<itemType>cover</itemType>
<imageType>cover</imageType>
<maxItemCount>5</maxItemCount>
<itemSize>0.105 0.235</itemSize>
<itemScale>1.53</itemScale>
<itemInterpolation>linear</itemInterpolation>
<imageInterpolation>linear</imageInterpolation>
<itemVerticalAlignment>center</itemVerticalAlignment>
<verticalOffset>0.1</verticalOffset>
<reflections>true</reflections>

View file

@ -0,0 +1,188 @@
<theme>
<!-- Test of grid element for both the system view and gamelist view -->
<view name="system">
<grid name="system_grid">
<pos>0.5 0.2</pos>
<size>0.86 0.715</size>
<origin>0.5 0</origin>
<staticImage>./${system.theme}/images/logo.svg</staticImage>
<itemSize>0.151 -1</itemSize>
<itemScale>1.1</itemScale>
<itemSpacing>0.022 -1</itemSpacing>
<fractionalRows>true</fractionalRows>
<itemTransitions>animate</itemTransitions>
<rowTransitions>animate</rowTransitions>
<unfocusedItemOpacity>1.0</unfocusedItemOpacity>
<imageRelativeScale>0.9</imageRelativeScale>
<imageFit>contain</imageFit>
<backgroundImage>./core/images/grid_frame.svg</backgroundImage>
<backgroundRelativeScale>1.0</backgroundRelativeScale>
<backgroundColor>424242</backgroundColor>
<selectorImage>./core/images/grid_frame.svg</selectorImage>
<selectorRelativeScale>1.0</selectorRelativeScale>
<selectorLayer>middle</selectorLayer>
<selectorColor>FF3333</selectorColor>
<textRelativeScale>0.8</textRelativeScale>
<textColor>F0F0F0</textColor>
<fontPath>./core/fonts/Exo2-RegularCondensed.otf</fontPath>
<fontSize>0.032</fontSize>
<letterCase>uppercase</letterCase>
<lineSpacing>1.2</lineSpacing>
<fadeAbovePrimary>true</fadeAbovePrimary>
</grid>
<image name="consolegame">
<pos>0.02 0.17</pos>
<maxSize>0.4 0.15</maxSize>
<origin>0 1</origin>
</image>
<image name="controller">
<pos>0.78 0.17</pos>
<maxSize>0.1 0.15</maxSize>
<origin>1 1</origin>
</image>
<image name="backframe2">
<pos>0.5 0.19</pos>
<size>0.96 0.735</size>
<origin>0.5 0</origin>
<path>./core/images/frame.png</path>
<color>181818</color>
<zIndex>10</zIndex>
</image>
<image name="logo">
<pos>0.02 0.17</pos>
<maxSize>0.32 0.13</maxSize>
<origin>0 1</origin>
</image>
<text name="game_counter">
<pos>0.8 0.145</pos>
<size>0.117 0.056</size>
<origin>0 0.5</origin>
<systemdata>gamecount_games</systemdata>
<fontPath>./core/fonts/Exo2-RegularCondensed.otf</fontPath>
<fontSize>0.035</fontSize>
<horizontalAlignment>center</horizontalAlignment>
<color>DDDDDD</color>
<backgroundColor>262626DD</backgroundColor>
<letterCase>uppercase</letterCase>
<zIndex>50</zIndex>
</text>
<helpsystem name="help">
<pos>0.012 0.955</pos>
<textColor>${gamelistHelpColor}</textColor>
<iconColor>${gamelistHelpColor}</iconColor>
<textColorDimmed>${gamelistHelpColorDimmed}</textColorDimmed>
<iconColorDimmed>${gamelistHelpColorDimmed}</iconColorDimmed>
</helpsystem>
<!-- Hide some unwanted elements -->
<text name="info1, info2, info3, info4, info5, info6, info7, info8, info9, info10">
<visible>false</visible>
</text>
</view>
<view name="gamelist">
<grid name="gamelist_grid">
<pos>0.5 0.3</pos>
<size>0.86 0.623</size>
<origin>0.5 0</origin>
<imageType>cover</imageType>
<itemSize>0.151 -1</itemSize>
<itemScale>1.1</itemScale>
<itemSpacing>0.022 -1</itemSpacing>
<fractionalRows>false</fractionalRows>
<itemTransitions>animate</itemTransitions>
<rowTransitions>animate</rowTransitions>
<unfocusedItemOpacity>1.0</unfocusedItemOpacity>
<imageRelativeScale>0.9</imageRelativeScale>
<imageFit>contain</imageFit>
<backgroundImage>./core/images/grid_frame.svg</backgroundImage>
<backgroundRelativeScale>1.0</backgroundRelativeScale>
<backgroundColor>424242</backgroundColor>
<selectorImage>./core/images/grid_frame.svg</selectorImage>
<selectorRelativeScale>1.0</selectorRelativeScale>
<selectorLayer>middle</selectorLayer>
<selectorColor>FF3333</selectorColor>
<textRelativeScale>0.8</textRelativeScale>
<textColor>F0F0F0</textColor>
<fontPath>./core/fonts/Exo2-RegularCondensed.otf</fontPath>
<fontSize>0.032</fontSize>
<letterCase>uppercase</letterCase>
<lineSpacing>1.2</lineSpacing>
</grid>
<image name="backframe2">
<pos>0.5 0.19</pos>
<size>0.96 0.735</size>
<origin>0.5 0</origin>
<path>./core/images/frame.png</path>
<color>181818</color>
<zIndex>10</zIndex>
</image>
<text name="game_name">
<pos>0.92 0.225</pos>
<size>0.55 0.08</size>
<origin>1 0</origin>
<metadata>name</metadata>
<gameselector>selector_recent</gameselector>
<fontPath>./core/fonts/Exo2-RegularCondensed.otf</fontPath>
<fontSize>0.045</fontSize>
<horizontalAlignment>right</horizontalAlignment>
<verticalAlignment>center</verticalAlignment>
<color>D6D6D6</color>
<letterCase>none</letterCase>
<zIndex>80</zIndex>
</text>
<text name="md_description">
<pos>0.025 0.205</pos>
<size>0.446 0.185</size>
<fontSize>0.02</fontSize>
<lineSpacing>1.2</lineSpacing>
</text>
<rating name="md_rating">
<pos>0.815 0.203</pos>
<size>0.04 0.03</size>
</rating>
<gamelistinfo name="gamelistInfo">
<color>888888</color>
</gamelistinfo>
<badges name="md_badges">
<pos>0.03 0.205</pos>
<size>0.38 0.08</size>
<lines>1</lines>
<itemsPerLine>10</itemsPerLine>
</badges>
<helpsystem name="help">
<pos>0.012 0.955</pos>
<textColor>${gamelistHelpColor}</textColor>
<iconColor>${gamelistHelpColor}</iconColor>
<textColorDimmed>${gamelistHelpColorDimmed}</textColorDimmed>
<iconColorDimmed>${gamelistHelpColorDimmed}</iconColorDimmed>
</helpsystem>
<!-- Hide some unwanted elements -->
<image name="backframe3">
<visible>false</visible>
</image>
<text name="md_lbl_publisher, md_lbl_releasedate, md_lbl_developer, md_lbl_genre,
md_lbl_players, md_lbl_lastplayed, md_developer, md_publisher, md_genre,
md_players, md_description">
<visible>false</visible>
</text>
<datetime name="md_releasedate md_lastplayed">
<visible>false</visible>
</datetime>
<rating name="md_rating">
<visible>false</visible>
</rating>
</view>
<aspectRatio name="4:3">
<view name="system, gamelist">
<grid name="gamelist_grid">
<itemSize>0.192 -1</itemSize>
</grid>
<helpsystem name="help">
<fontSize>0.034</fontSize>
</helpsystem>
</view>
<view name="gamelist"></view>
</aspectRatio>
</theme>

View file

@ -140,26 +140,26 @@ cd ..
echo:
echo Setting up SDL
if exist SDL2-2.24.1\ (
rmdir /S /Q SDL2-2.24.1
if exist SDL2-2.26.1\ (
rmdir /S /Q SDL2-2.26.1
)
curl -LO https://libsdl.org/release/SDL2-devel-2.24.1-VC.zip
curl -LO https://libsdl.org/release/SDL2-devel-2.26.1-VC.zip
7z x SDL2-devel-2.24.1-VC.zip
7z x SDL2-devel-2.26.1-VC.zip
if not exist SDL2-2.24.1\ (
if not exist SDL2-2.26.1\ (
echo SDL directory is missing, aborting.
cd ..
goto end
)
cd SDL2-2.24.1
cd SDL2-2.26.1
rename include SDL2
cd ..
copy /Y SDL2-2.24.1\lib\x64\SDL2.dll ..
copy /Y SDL2-2.24.1\lib\x64\SDL2.lib ..
copy /Y SDL2-2.24.1\lib\x64\SDL2main.lib ..
copy /Y SDL2-2.26.1\lib\x64\SDL2.dll ..
copy /Y SDL2-2.26.1\lib\x64\SDL2.lib ..
copy /Y SDL2-2.26.1\lib\x64\SDL2main.lib ..
echo:
echo Setting up FFmpeg

View file

@ -38,6 +38,11 @@ rm -rf glew-*
curl -LO https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.zip
unzip glew-2.1.0.zip
if [ ! -d glew-2.1.0 ]; then
echo "GLEW directory is missing, aborting."
exit
fi
echo -e "\nSetting up FreeType"
rm -rf freetype
@ -83,20 +88,20 @@ cd ..
echo -e "\nSetting up SDL"
rm -rf SDL2-*
curl -O https://libsdl.org/release/SDL2-devel-2.24.1-mingw.tar.gz
curl -O https://libsdl.org/release/SDL2-devel-2.26.1-mingw.tar.gz
tar xvzf SDL2-devel-2.24.1-mingw.tar.gz
tar xvzf SDL2-devel-2.26.1-mingw.tar.gz
# Needed due to some kind of file system race condition that sometimes occurs on Windows.
sleep 1
if [ ! -d SDL2-2.24.1 ]; then
if [ ! -d SDL2-2.26.1 ]; then
echo "SDL directory is missing, aborting."
exit
fi
mv SDL2-2.24.1/x86_64-w64-mingw32/include/SDL2 SDL2-2.24.1/
cp -p SDL2-2.24.1/x86_64-w64-mingw32/lib/libSDL2main.a ..
cp -p SDL2-2.24.1/x86_64-w64-mingw32/bin/SDL2.dll ..
mv SDL2-2.26.1/x86_64-w64-mingw32/include/SDL2 SDL2-2.26.1/
cp -p SDL2-2.26.1/x86_64-w64-mingw32/lib/libSDL2main.a ..
cp -p SDL2-2.26.1/x86_64-w64-mingw32/bin/SDL2.dll ..
echo -e "\nSetting up FFmpeg"
rm -rf ffmpeg-*

View file

@ -17,8 +17,8 @@
# How many CPU threads to use for the compilation.
JOBS=4
SDL_RELEASE_TAG=release-2.24.1
SDL_SHARED_LIBRARY=libSDL2-2.0.so.0.2400.1
SDL_RELEASE_TAG=release-2.26.1
SDL_SHARED_LIBRARY=libSDL2-2.0.so.0.2600.1
echo "Building AppImage..."

View file

@ -13,8 +13,8 @@
# How many CPU threads to use for the compilation.
JOBS=4
SDL_RELEASE_TAG=release-2.24.1
SDL_SHARED_LIBRARY=libSDL2-2.0.so.0.2400.1
SDL_RELEASE_TAG=release-2.26.1
SDL_SHARED_LIBRARY=libSDL2-2.0.so.0.2600.1
echo "Building Steam Deck AppImage..."

View file

@ -107,7 +107,7 @@ rm -f CMakeCache.txt
cmake -DCMAKE_BUILD_TYPE=Release -S .. -B .
make clean
make -j${JOBS}
cp libSDL2-2.0.dylib ../../..
cp libSDL2-2.0.0.dylib ../../..
cd ../..
echo "\nBuilding libvpx"

View file

@ -187,7 +187,7 @@ if [ ! -d SDL ]; then
fi
cd SDL
git checkout release-2.24.1
git checkout release-2.26.1
ln -s include SDL2
mkdir build
cd ..