Merge branch 'new-theme-engine-phase-three'

This commit is contained in:
Leon Styhre 2022-02-20 19:35:54 +01:00
commit deeb97ccb2
104 changed files with 4475 additions and 2420 deletions

View file

@ -15,11 +15,25 @@
* 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
* Reorganized the UI Settings menu a bit and added entries to set the variant and aspect ratio for newer theme sets
* Added support for defining what type of image metadata to use for all image elements (and also for the video component static image)
* Removed the "Play videos immediately (override theme)" setting
* Renamed the sound menu option "Play audio for videos in the gamelist view" to "Play audio for gamelist and system view videos"
* Added support for defining which types of game media to use for all image elements (and also for the video component static image)
* Added a legacy (backward compatibility) mode for still supporting older RetroPie EmulationStation themes
* Added theme support for Lottie animations (vector graphics)
* Added a GameSelectorComponent for displaying game media and metadata in the system view
* Added support for displaying videos, Lottie animations and date/time elements to the system view
* Replaced the forceUppercase theme property with a more versatile letterCase property (forceUppercase is retained for legacy theme compatibility)
* Made it possible to set any text element as a scrollable container using either metadata values or literal strings
* Added support for defining the scrollable container speed, start delay and reset delay from the theme configuration
* Added theme support for defining the opacity for most element types
* 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 enabling or disabling audio playback for videos
* Disabled the pillarboxes and scanline rendering menu options when using a non-legacy theme set
* 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 using unsigned integers for theme properties
* Added scraper support for displaying the returned platform if it does not match the game platform, or if multiple platforms are defined for the system
* Added scraping of fan art and updated the media viewer to display these images
* Added scraping of box back covers when using TheGamesDB
@ -27,13 +41,17 @@
* Set the option "Play audio for screensaver videos" as enabled by default
* Added the ability to set a manual sortname specifically for custom collections using the metadata editor
* When scraping in semi-automatic mode, horizontal scrolling of long game names are no longer reset when automatically selecting the result
* Added support for using the tilde (~) symbol in the es_systems.xml path entries to expand to the user home directory
* Reduced CPU usage significantly when a menu is open by not rendering the bottom of the stack
* Reduced CPU usage significantly by only rendering the necessary systems in SystemView
* Added an OpenGL ES 2.0 renderer (borrowed from the RetroPie fork of EmulationStation)
* Added logging of the display refresh rate on startup
* Improved the theme loading error logging to make it consistent and easier to understand
* Added a log warning for unthemed systems during theme set loading
* Changed the warning log level for missing theme files to debug level if the paths are set using variables
* Added new theme system variables for differentiating between collections and non-collection systems
* Added a color model conversion shader for converting from BGRA to RGBA
* Added renderer support for supplying a separate format than internalFormat when creating textures (although not really supported by the OpenGL standard)
* Added opacity support to the scanline shader
* Added the rlottie library as a Git subtree
* On Windows all dependencies were moved in-tree to the "external" directory to greatly simplify the build environment
* Updated the build scripts to support native M1/ARM builds on macOS
@ -41,8 +59,10 @@
* Large refactoring to improve thread safety and improve singleton pattern usage
* Moved all Platform functions to the utility namespace instead of using the global namespace
* Implemented proper XML attribute support in ThemeData that eliminated the risk of name collisions
* Migrated the carousel code from SystemView to a separate new CarouselComponent
* Changed all occurances of "GameList" to "Gamelist" throughout the codebase
* Removed a huge amount of unnecessary Window* function parameters throughout the codebase
* Changed the opacity data type and functions from unsigned char to float throughout the codebase
* Refactored the six gamelist classes into two new classes; GamelistBase and GamelistView
* Rewrote the gamelist logic to handle an arbitrary amount of components per type and split the legacy code into a separate file
* Renamed Gamelist.cpp to GamelistFileParser.cpp and moved it to its own namespace instead of using the global namespace
@ -51,6 +71,7 @@
* Moved UIModeController.cpp from the es-app/views directory to es-app
* Set the clang-format option SpaceBeforeCpp11BracedList to true and reformatted the codebase
* Removed some unnecessary typedefs and replaced the remaining ones with the more modern "using" keyword
* Greatly simplified the video controls code (play/stop/pause etc.)
* Removed the deprecated VideoVlcComponent
* Lots of general code cleanup and refactoring
* Updated and improved the theming documentation
@ -61,12 +82,20 @@
* 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
* When multi-scraping in semi-automatic mode and a long game name was scrolling, the start position was not reset when scraping the next game
* Slide and fade transitions would sometimes stop working after changing theme sets
* Using fade transitions, when holding a direction button to scroll the system view carousel, the key repeat would cause an unwanted background rendering
* Horizontal and vertical gradients were mixed up (showing the opposite gradient type if set in a theme)
* The VideoComponent static images were not fading out smoothly on gamelist fast-scrolling
* Rating icon outlines would not fade out correctly when fast-scrolling in a gamelist
* If setting an origin other than 0.5 for a video with pillarboxes enabled, the video would not get centered on the black rectangle
* 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 ScummVM platform entry was missing for TheGamesDB which resulted in very inaccurate scraper searches
* During multi-scraping the busy indicator was not displayed after a result was acquired but before the thumbnail was completely downloaded
* Text opacity did not work correctly in some places, such as for the help prompts
* ScrollableContainer faded semi-transparent text to fully opaque when resetting
* ScrollableContainer faded in the background text color in addition to the text color when resetting
* The device text flickered in GuiDetectDevice when configuring a controller
* The selector bar was not aligned correctly during menu scale-up animations
## Version 1.2.0

File diff suppressed because it is too large Load diff

View file

@ -364,8 +364,6 @@ Below are the default zIndex values per element type:
* System Logo/Text - 50
* `image name="logo"`
* `text name="logoText"`
* `image name="logoPlaceholderImage"`
* `text name="logoPlaceholderText"`
* Gamelist information - 50
* `text name="gamelistInfo"`
* Badges - 50
@ -418,15 +416,8 @@ or to specify only a portion of the value of a theme property:
- The system logo carousel
* `image name="logo"` - PATH | COLOR
- A logo image, to be displayed in the system logo carousel.
* `image name="logoPlaceholderImage"` - ALL
- A logo image, to be displayed system name in the system logo carousel when no logo is available. Set the position
to `0.5 0.5` to center the image.
* `text name="logoPlaceholderText"` - ALL
- Logo text, to be displayed system name in the system logo carousel when no logo is available. The logo text is
displayed on top of `logoPlaceholderImage`. Set the position to `0.5 0.5` to center the text.
* `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT
- **Deprecated:** A logo text, to be displayed system name in the system logo carousel when no logo is available.
Ignored when `logoPlaceholderImage` or `logoPlaceholderText` are set.
- A logo text, to be displayed system name in the system logo carousel when no logo is available.
* `text name="systemInfo"` - ALL
- Displays details of the system currently selected in the carousel.
* You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed
@ -909,8 +900,6 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren
- Spacing in pixels between the help system components.
* `iconTextSpacing` - type: FLOAT. Default is 8.0.
- Spacing in pixels within a help system component between it's icon and text.
* `textStyle` - type: STRING. Default is `uppercase`.
- The style of the text. Options: `uppercase`, `lowercase`, `camelcase`.
* `customButtonIcon` - type: PATH.
- A button icon override. Specify the button type in the attribute `button`. The available buttons are:
`dpad_updown`,
@ -959,6 +948,8 @@ It's strongly recommended to use the same image dimensions for all badges as var
- angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`.
* `rotationOrigin` - type: NORMALIZED_PAIR.
- Point around which the image will be rotated. Default is `0.5 0.5`.
* `alignment` - type: STRING.
- Valid values are `left` or `right`
* `direction` - type: STRING.
- Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. Default is `row`.
* `lines` - type: FLOAT.
@ -1023,9 +1014,7 @@ It's strongly recommended to use the same image dimensions for all badges as var
- Sets the number of logos to display in the carousel.
- Default is 3
* `zIndex` - type: FLOAT.
- z-index value for component. Components will be rendered in order of z-index value from low to high.
* `legacyZIndexMode` - type: BOOLEAN
- If true, the carousel will ignore zIndex and always render on top of other components. Default is `true`.
- z-index value for component. Components will be rendered in order of z-index value from low to high with the carousel above all other components.
The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu.

View file

@ -362,8 +362,6 @@ Below are the default zIndex values per element type:
* System Logo/Text - 50
* `image name="logo"`
* `text name="logoText"`
* `image name="logoPlaceholderImage"`
* `text name="logoPlaceholderText"`
* Gamelist information - 50
* `text name="gamelistInfo"`
* Badges - 50
@ -416,15 +414,8 @@ or to specify only a portion of the value of a theme property:
- The system logo carousel
* `image name="logo"` - PATH | COLOR
- A logo image, to be displayed in the system logo carousel.
* `image name="logoPlaceholderImage"` - ALL
- A logo image, to be displayed system name in the system logo carousel when no logo is available. Set the position
to `0.5 0.5` to center the image.
* `text name="logoPlaceholderText"` - ALL
- Logo text, to be displayed system name in the system logo carousel when no logo is available. The logo text is
displayed on top of `logoPlaceholderImage`. Set the position to `0.5 0.5` to center the text.
* `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT
- **Deprecated:** A logo text, to be displayed system name in the system logo carousel when no logo is available.
Ignored when `logoPlaceholderImage` or `logoPlaceholderText` are set.
- A logo text, to be displayed system name in the system logo carousel when no logo is available.
* `text name="systemInfo"` - ALL
- Displays details of the system currently selected in the carousel.
* You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed
@ -881,8 +872,6 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren
- Spacing in pixels between the help system components.
* `iconTextSpacing` - type: FLOAT. Default is 8.0.
- Spacing in pixels within a help system component between it's icon and text.
* `textStyle` - type: STRING. Default is `uppercase`.
- The style of the text. Options: `uppercase`, `lowercase`, `camelcase`.
* `customButtonIcon` - type: PATH.
- A button icon override. Specify the button type in the attribute `button`. The available buttons are:
`dpad_updown`,
@ -931,6 +920,8 @@ It's strongly recommended to use the same image dimensions for all badges as var
- angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`.
* `rotationOrigin` - type: NORMALIZED_PAIR.
- Point around which the image will be rotated. Default is `0.5 0.5`.
* `alignment` - type: STRING.
- Valid values are `left` or `right`
* `direction` - type: STRING.
- Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. Default is `row`.
* `lines` - type: FLOAT.
@ -996,8 +987,6 @@ It's strongly recommended to use the same image dimensions for all badges as var
- Default is 3
* `zIndex` - type: FLOAT.
- z-index value for component. Components will be rendered in order of z-index value from low to high.
* `legacyZIndexMode` - type: BOOLEAN
- If true, the carousel will ignore zIndex and always render on top of other components. Default is `true`.
The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu.

View file

@ -150,6 +150,29 @@ There will be a lot of directories created if using the es_systems.xml file bund
![alt text](images/es-de_ui_easy_setup.png "ES-DE Easy Setup")
_This is the dialog shown if no game files were found. It lets you configure the ROM directory if you don't want to use the default one, and you can also generate the game systems directory structure. Note that the directory is the physical path, and that your operating system may present this as a localized path if you are using a language other than English._
## Placing games into non-standard directories
As explained above, the basic logic for how ES-DE works is that it expects game files to be placed into a standardized directory structure under the ROMs directory. The location of this directory is configurable so it could for instance be placed on an external storage device or on a file share served by a NAS. The way it's implemented is via the %ROMPATH% variable in the es_systems.xml file which will always point to this ROM directory. For example this is an entry for the Super Nintendo system:
```
<path>%ROMPATH%/snes</path>
```
In theory it's possible to make a custom system entry and hardcode the path to a specific directory instead of using the %ROMPATH% variable, but this is not really supported and it also makes custom collections non-portable as the path to every game will be an absolute path rather than a path relative to the %ROMPATH% variable. So if you move your games to a different directory, you would manually need to modify all your custom collections configuration files as well as your custom es_systems.xml file.
If you really insist on not placing your games into the ES-DE standard directory structure, a much better solution is to symlink the game directories into the standard directory. In this way you don't need to make a custom es_systems.xml file and any additional emulators and other configuration added to future ES-DE releases will just work after upgrading.
This is an example of symlinking the Super Nintendo game directory on Unix and macOS:
```
cd ~/ROMs
ln -s ~/my_games/super_nintendo/ snes
```
And on Windows (you need to run this as Administrator):
```
cd C:\Users\Myusername\ROMs
mklink /D snes "C:\My Games\Super Nintendo\"
```
## Disabling game systems
The way ES-DE works is that it will always try to load any system for which there are game files available, so to disable a system it needs to be hidden from ES-DE. This is easily accomplished by renaming the game directory to something that is not recognized, for example changing `~/ROMs/c64` to `~/ROMs/c64_DISABLED`. Another approach is to create a subdirectory named DISABLED (or whatever name you prefer that is not matching a supported system) in the ROMs directory and move the game folder there, such as `~/ROMs/DISABLED/c64`. This makes it easy to disable and re-enable game systems in ES-DE. Note that the gamelist.xml file and any game media files are retained while the system is disabled so this is an entirely safe thing to do.
@ -1081,7 +1104,7 @@ Images of cartridges, diskettes, tapes, CD-ROMs etc. that were used to distribut
**Fan art images**
Fan art. Disabled by default as not everyone may want these images, and because they slow down the scraping.
Fan art. These can get quite large so if you don't need them then disable this option to speed up the scraping process.
#### Miximage settings
@ -1247,13 +1270,13 @@ Submenu containing all the settings for the screensaver. These are described in
This option will blur the background behind the menu slightly. Normally this can be left enabled, but if you have a really slow GPU, disabling this option may make the application feel a bit more responsive.
**Display pillarboxes for gamelist videos**
**Display pillarboxes for gamelist videos** _Only for legacy theme sets_
With this option enabled, there are black pillarboxes (and to a lesser extent letterboxes) displayed around videos with non-standard aspect ratios. This will probably be most commonly used for vertical arcade shooters, or for game systems that has a screen in portrait orientation. For wider than normal videos, letterboxes are added, but this is quite rare compared to videos in portrait orientation. This option looks good with some theme sets such as rbsimple-DE, but on others it may be more visually pleasing to disable it. On less wide displays such as those in 4:3 aspect ratio this option should probably be disabled as it may otherwise add quite excessive letterboxing.
With this option enabled, there are black pillarboxes (and to a lesser extent letterboxes) displayed around videos with non-standard aspect ratios. This will probably be most commonly used for vertical arcade shooters, or for game systems that has a screen in portrait orientation. For wider than normal videos, letterboxes are added, but this is quite rare compared to videos in portrait orientation. This option looks good with some theme sets such as rbsimple-DE, but on others it may be more visually pleasing to disable it. On less wide displays such as those in 4:3 aspect ratio this option should probably be disabled as it may otherwise add quite excessive letterboxing. This option is only available for legacy theme sets as it's otherwise managed by the theme author.
**Render scanlines for gamelist videos**
**Render scanlines for gamelist videos** _Only for legacy theme sets_
Whether to use a shader to render scanlines for videos in the gamelist view. The effect is usually pretty subtle as the video is normally renderered in a limited size in the GUI and the scanlines are sized relative to the video window size.
Whether to use a shader to render scanlines for videos in the gamelist view. The effect is usually pretty subtle as the video is normally renderered in a limited size in the GUI and the scanlines are sized relative to the video window size. This option is only available for legacy theme sets as it's otherwise managed by the theme author.
**Sort folders on top of gamelists**
@ -1303,10 +1326,6 @@ If enabled, it's possible to navigate between gamelists using the _Left_ and _Ri
Activates or deactivates the built-in help system that provides contextual information regarding button usage.
**Play videos immediately (override theme)**
Some themes (including rbsimple-DE) display the game images briefly before playing the game videos. This setting forces the videos to be played immediately, regardless of the configuration in the theme. Note though that if there is a video available for a game, but no images, the video will always start to play immediately no matter the theme configuration or whether this settings has been enabled or not.
#### Media viewer settings
Settings for the media viewer that is accessible from the gamelist views.
@ -1419,9 +1438,9 @@ Sets the volume for the navigation sounds.
Sets the volume for the video player. This applies to the gamelist view, the media viewer and the video screensaver.
**Play audio for videos in the gamelist view**
**Play audio for gamelist and system view videos**
With this turned off, audio won't play for videos in the gamelists.
With this turned off, audio won't play for videos in the gamelist or system views. Note that even with this option enabled videos may be muted as the audio can be disabled per video element from the theme configuration.
**Play audio for media viewer videos**
@ -1473,7 +1492,7 @@ If the theme set in use provides themes for custom collections, then this entry
**Create new custom collection**
This lets you create a completely custom collection with a name of your choice.
This lets you create a completely custom collection with a name of your choice. If the selected name collides with an existing name, a sequence number inside brackets will be appended to the collection name, such as _fighting (1)_ if a _fighting_ collection already existed. Note that custom collection names are always converted to lowercase.
**Delete custom collection**

View file

@ -1443,6 +1443,10 @@ void CollectionSystemsManager::trimCollectionCount(FileData* rootFolder, int lim
(CollectionFileData*)rootFolder->getChildrenListToDisplay().back();
ViewController::getInstance()->getGamelistView(curSys).get()->remove(gameToRemove, false);
}
// Also update the lists of last played and most played games as these could otherwise
// contain dangling pointers.
rootFolder->updateLastPlayedList();
rootFolder->updateMostPlayedList();
}
const bool CollectionSystemsManager::themeFolderExists(const std::string& folder)

View file

@ -39,6 +39,8 @@ FileData::FileData(FileType type,
, mEnvData {envData}
, mSystem {system}
, mOnlyFolders {false}
, mUpdateChildrenLastPlayed {false}
, mUpdateChildrenMostPlayed {false}
, mDeletionFlag {false}
{
// Metadata needs at least a name field (since that's what getName() will return).
@ -736,6 +738,9 @@ void FileData::sort(const SortType& type, bool mFavoritesOnTop)
sortFavoritesOnTop(*type.comparisonFunction, mGameCount);
else
sort(*type.comparisonFunction, mGameCount);
updateLastPlayedList();
updateMostPlayedList();
}
void FileData::countGames(std::pair<unsigned int, unsigned int>& gameCount)
@ -743,9 +748,6 @@ void FileData::countGames(std::pair<unsigned int, unsigned int>& gameCount)
bool isKidMode = (Settings::getInstance()->getString("UIMode") == "kid" ||
Settings::getInstance()->getBool("ForceKid"));
(Settings::getInstance()->getString("UIMode") == "kid" ||
Settings::getInstance()->getBool("ForceKid"));
for (unsigned int i = 0; i < mChildren.size(); ++i) {
if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) {
if (!isKidMode || (isKidMode && mChildren[i]->getKidgame())) {
@ -761,6 +763,42 @@ void FileData::countGames(std::pair<unsigned int, unsigned int>& gameCount)
mGameCount = gameCount;
}
void FileData::updateLastPlayedList()
{
if (mUpdateListCallback)
mUpdateListCallback();
if (!mUpdateChildrenLastPlayed)
return;
mChildrenLastPlayed.clear();
mChildrenLastPlayed = getChildrenRecursive();
std::stable_sort(mChildrenLastPlayed.begin(), mChildrenLastPlayed.end());
std::sort(std::begin(mChildrenLastPlayed), std::end(mChildrenLastPlayed),
[](FileData* a, FileData* b) {
return a->metadata.get("lastplayed") > b->metadata.get("lastplayed");
});
}
void FileData::updateMostPlayedList()
{
if (mUpdateListCallback)
mUpdateListCallback();
if (!mUpdateChildrenMostPlayed)
return;
mChildrenMostPlayed.clear();
mChildrenMostPlayed = getChildrenRecursive();
std::stable_sort(mChildrenMostPlayed.begin(), mChildrenMostPlayed.end());
std::sort(std::begin(mChildrenMostPlayed), std::end(mChildrenMostPlayed),
[](FileData* a, FileData* b) {
return a->metadata.getInt("playcount") > b->metadata.getInt("playcount");
});
}
const FileData::SortType& FileData::getSortTypeFromString(const std::string& desc) const
{
std::vector<FileData::SortType> SortTypes = FileSorts::SortTypes;
@ -1148,7 +1186,7 @@ void FileData::launchGame()
// been set for the specific launch command, then block the video player, stop scrolling
// game names and descriptions and keep the screensaver from getting activated.
if (runInBackground)
window->setLaunchedGame();
window->setLaunchedGame(true);
else
// Normalize deltaTime so that the screensaver does not start immediately
// when returning from the game.
@ -1158,7 +1196,7 @@ void FileData::launchGame()
// This blocks the video player, stops the scrolling of game names and descriptions and
// keeps the screensaver from getting activated.
if (runInBackground)
window->setLaunchedGame();
window->setLaunchedGame(true);
// Normalize deltaTime so that the screensaver does not start immediately
// when returning from the game.
window->normalizeNextUpdate();

View file

@ -14,6 +14,7 @@
#include "MetaData.h"
#include "utils/FileSystemUtil.h"
#include <functional>
#include <unordered_map>
class SystemData;
@ -56,6 +57,14 @@ public:
const std::vector<FileData*>& getChildren() const { return mChildren; }
SystemData* getSystem() const { return mSystem; }
SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
// These functions are used by GameSelectorComponent.
const std::vector<FileData*>& getChildrenLastPlayed() const { return mChildrenLastPlayed; }
const std::vector<FileData*>& getChildrenMostPlayed() const { return mChildrenMostPlayed; }
void setUpdateChildrenLastPlayed(bool state) { mUpdateChildrenLastPlayed = state; }
void setUpdateChildrenMostPlayed(bool state) { mUpdateChildrenMostPlayed = state; }
void setUpdateListCallback(const std::function<void()>& func) { mUpdateListCallback = func; }
const bool getOnlyFoldersFlag() const { return mOnlyFolders; }
const bool getHasFoldersFlag() const { return mHasFolders; }
static const std::string getROMDirectory();
@ -127,7 +136,8 @@ public:
MetaDataList metadata;
// Only count the games, a cheaper alternative to a full sort when that is not required.
void countGames(std::pair<unsigned int, unsigned int>& gameCount);
void updateLastPlayedList();
void updateMostPlayedList();
void setSortTypeString(std::string typestring) { mSortTypeString = typestring; }
const std::string& getSortTypeString() const { return mSortTypeString; }
const FileData::SortType& getSortTypeFromString(const std::string& desc) const;
@ -146,10 +156,15 @@ private:
std::unordered_map<std::string, FileData*> mChildrenByFilename;
std::vector<FileData*> mChildren;
std::vector<FileData*> mFilteredChildren;
std::vector<FileData*> mChildrenLastPlayed;
std::vector<FileData*> mChildrenMostPlayed;
std::function<void()> mUpdateListCallback;
// The pair includes all games, and favorite games.
std::pair<unsigned int, unsigned int> mGameCount;
bool mOnlyFolders;
bool mHasFolders;
bool mUpdateChildrenLastPlayed;
bool mUpdateChildrenMostPlayed;
// Used for flagging a game for deletion from its gamelist.xml file.
bool mDeletionFlag;
};

View file

@ -105,7 +105,7 @@ namespace GamelistFileParser
std::string xmlpath = system->getGamelistPath(false);
if (!Utils::FileSystem::exists(xmlpath)) {
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \"" << system->getName()
<< "\" does not have a gamelist.xml file";
return;
}
@ -143,7 +143,8 @@ namespace GamelistFileParser
}
if (validLabel) {
system->setAlternativeEmulator(label);
LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName()
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \""
<< system->getName()
<< "\" has a valid alternativeEmulator entry: \"" << label
<< "\"";
}
@ -181,8 +182,8 @@ namespace GamelistFileParser
if (!showHiddenFiles &&
(Utils::FileSystem::isHidden(path) ||
Utils::FileSystem::isHidden(Utils::FileSystem::getParent(path)))) {
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden file \"" << path
<< "\"";
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): Skipping hidden file \""
<< path << "\"";
continue;
}
@ -205,7 +206,7 @@ namespace GamelistFileParser
else {
// Skip arcade asset entries as these will not be used in any way inside
// the application.
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping arcade asset \""
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): Skipping arcade asset \""
<< file->getName() << "\"";
delete file;
continue;
@ -216,7 +217,7 @@ namespace GamelistFileParser
// application restart.
if (!Settings::getInstance()->getBool("ShowHiddenGames")) {
if (file->getHidden()) {
LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden "
LOG(LogDebug) << "GamelistFileParser::parseGamelist(): Skipping hidden "
<< (type == GAME ? "file" : "folder") << " entry \""
<< file->getName() << "\""
<< " (\"" << file->getPath() << "\")";
@ -397,21 +398,22 @@ namespace GamelistFileParser
if (updateAlternativeEmulator) {
if (hasAlternativeEmulatorTag && system->getAlternativeEmulator() == "") {
LOG(LogDebug) << "Gamelist::updateGamelist(): Removed the "
LOG(LogDebug) << "GamelistFileParser::updateGamelist(): Removed the "
"alternativeEmulator tag for system \""
<< system->getName() << "\" as the default emulator \""
<< system->getSystemEnvData()->mLaunchCommands.front().second
<< "\" was selected";
}
else if (system->getAlternativeEmulator() != "") {
LOG(LogDebug) << "Gamelist::updateGamelist(): "
LOG(LogDebug) << "GamelistFileParser::updateGamelist(): "
"Added/updated the alternativeEmulator tag for system \""
<< system->getName() << "\" to \""
<< system->getAlternativeEmulator() << "\"";
}
}
if (numUpdated > 0) {
LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated
LOG(LogDebug) << "GamelistFileParser::updateGamelist(): Added/updated "
<< numUpdated
<< (numUpdated == 1 ? " entity in \"" : " entities in \"")
<< xmlWritePath << "\"";
}

View file

@ -43,9 +43,6 @@ bool MediaViewer::startMediaViewer(FileData* game)
initiateViewer();
if (mHasVideo)
ViewController::getInstance()->onPauseVideo();
if (mHasVideo || mHasImages)
return true;
else
@ -55,7 +52,7 @@ bool MediaViewer::startMediaViewer(FileData* game)
void MediaViewer::stopMediaViewer()
{
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
ViewController::getInstance()->onStopVideo();
ViewController::getInstance()->stopViewVideos();
if (mVideo) {
delete mVideo;
@ -83,8 +80,8 @@ void MediaViewer::render(const glm::mat4& /*parentTrans*/)
Renderer::setMatrix(trans);
// Render a black background below the game media.
Renderer::drawRect(0.0f, 0.0f, static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
0x000000FF, 0x000000FF);
if (mVideo && !mDisplayingImage) {
mVideo->render(trans);
@ -96,7 +93,7 @@ void MediaViewer::render(const glm::mat4& /*parentTrans*/)
shaders = Renderer::SHADER_SCANLINES;
if (Settings::getInstance()->getBool("MediaViewerVideoBlur")) {
shaders |= Renderer::SHADER_BLUR_HORIZONTAL;
float heightModifier = Renderer::getScreenHeightModifier();
float heightModifier {Renderer::getScreenHeightModifier()};
// clang-format off
if (heightModifier < 1)
videoParameters.blurPasses = 2; // Below 1080
@ -257,23 +254,20 @@ void MediaViewer::playVideo()
return;
mDisplayingImage = false;
ViewController::getInstance()->onStopVideo();
ViewController::getInstance()->pauseViewVideos();
mVideo = new VideoFFmpegComponent;
mVideo->topWindow(true);
mVideo->setOrigin(0.5f, 0.5f);
mVideo->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f);
if (Settings::getInstance()->getBool("MediaViewerStretchVideos"))
mVideo->setResize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mVideo->setResize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
else
mVideo->setMaxSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mVideo->setMaxSize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
mVideo->setVideo(mVideoFile);
mVideo->setMediaViewerMode(true);
mVideo->onShow();
mVideo->startVideoPlayer();
}
void MediaViewer::showImage(int index)
@ -288,7 +282,6 @@ void MediaViewer::showImage(int index)
mImage->setImage(mImageFiles[index]);
mImage->setOrigin(0.5f, 0.5f);
mImage->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f);
mImage->setMaxSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mImage->setMaxSize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
}
}

View file

@ -27,7 +27,7 @@
#include <cstring>
#endif
#define FADE_TIME 300
#define FADE_TIME 300.0f
Screensaver::Screensaver()
: mWindow {Window::getInstance()}
@ -59,6 +59,8 @@ Screensaver::~Screensaver()
void Screensaver::startScreensaver(bool generateMediaList)
{
ViewController::getInstance()->pauseViewVideos();
std::string path = "";
std::string screensaverType = Settings::getInstance()->getString("ScreensaverType");
mHasMediaFiles = false;
@ -126,11 +128,11 @@ void Screensaver::startScreensaver(bool generateMediaList)
Renderer::getScreenHeight() / 2.0f);
if (Settings::getInstance()->getBool("ScreensaverStretchImages"))
mImageScreensaver->setResize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mImageScreensaver->setResize(Renderer::getScreenWidth(),
Renderer::getScreenHeight());
else
mImageScreensaver->setMaxSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mImageScreensaver->setMaxSize(Renderer::getScreenWidth(),
Renderer::getScreenHeight());
}
mTimer = 0;
return;
@ -157,21 +159,20 @@ void Screensaver::startScreensaver(bool generateMediaList)
generateOverlayInfo();
mVideoScreensaver = new VideoFFmpegComponent;
mVideoScreensaver->topWindow(true);
mVideoScreensaver->setOrigin(0.5f, 0.5f);
mVideoScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f,
Renderer::getScreenHeight() / 2.0f);
if (Settings::getInstance()->getBool("ScreensaverStretchVideos"))
mVideoScreensaver->setResize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mVideoScreensaver->setResize(Renderer::getScreenWidth(),
Renderer::getScreenHeight());
else
mVideoScreensaver->setMaxSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mVideoScreensaver->setMaxSize(Renderer::getScreenWidth(),
Renderer::getScreenHeight());
mVideoScreensaver->setVideo(path);
mVideoScreensaver->setScreensaverMode(true);
mVideoScreensaver->onShow();
mVideoScreensaver->startVideoPlayer();
mTimer = 0;
return;
}
@ -197,6 +198,8 @@ void Screensaver::stopScreensaver()
if (mGameOverlay)
mGameOverlay.reset();
ViewController::getInstance()->startViewVideos();
}
void Screensaver::nextGame()
@ -215,6 +218,7 @@ void Screensaver::launchGame()
ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get();
view->setCursor(mCurrentGame);
ViewController::getInstance()->cancelViewTransitions();
ViewController::getInstance()->pauseViewVideos();
}
}
@ -237,8 +241,8 @@ void Screensaver::renderScreensaver()
if (mVideoScreensaver && screensaverType == "video") {
// Render a black background below the video.
Renderer::setMatrix(Renderer::getIdentity());
Renderer::drawRect(0.0f, 0.0f, static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
0x000000FF, 0x000000FF);
// Only render the video if the state requires it.
if (static_cast<int>(mState) >= STATE_FADE_IN_VIDEO) {
@ -249,13 +253,13 @@ void Screensaver::renderScreensaver()
else if (mImageScreensaver && screensaverType == "slideshow") {
// Render a black background below the image.
Renderer::setMatrix(Renderer::getIdentity());
Renderer::drawRect(0.0f, 0.0f, static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
0x000000FF, 0x000000FF);
// Only render the image if the state requires it.
if (static_cast<int>(mState) >= STATE_FADE_IN_VIDEO) {
if (mImageScreensaver->hasImage()) {
mImageScreensaver->setOpacity(255 - static_cast<unsigned char>(mOpacity * 255));
mImageScreensaver->setOpacity(1.0f - mOpacity);
glm::mat4 trans {Renderer::getIdentity()};
mImageScreensaver->render(trans);
}
@ -581,8 +585,8 @@ void Screensaver::generateOverlayInfo()
if (mGameName == "" || mSystemName == "")
return;
float posX = static_cast<float>(Renderer::getWindowWidth()) * 0.023f;
float posY = static_cast<float>(Renderer::getWindowHeight()) * 0.02f;
float posX {Renderer::getWindowWidth() * 0.023f};
float posY {Renderer::getWindowHeight() * 0.02f};
std::string favoriteChar;
if (mCurrentGame && mCurrentGame->getFavorite())
@ -609,7 +613,7 @@ void Screensaver::generateOverlayInfo()
else
textSizeX = mGameOverlayFont[0].get()->sizeText(systemName).x;
float marginX = Renderer::getWindowWidth() * 0.01f;
float marginX {Renderer::getWindowWidth() * 0.01f};
mGameOverlayRectangleCoords.clear();
mGameOverlayRectangleCoords.push_back(posX - marginX);

View file

@ -483,6 +483,9 @@ bool SystemData::loadConfig()
#endif
path = Utils::String::replace(path, "//", "/");
// In case ~ is used, expand it to the home directory path.
path = Utils::FileSystem::expandHomePath(path);
// Check that the ROM directory for the system is valid or otherwise abort the
// processing.
if (!Utils::FileSystem::exists(path)) {
@ -1096,7 +1099,7 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
return randomSystem;
}
FileData* SystemData::getRandomGame(const FileData* currentGame)
FileData* SystemData::getRandomGame(const FileData* currentGame, bool gameSelectorMode)
{
std::vector<FileData*> gameList;
bool onlyFolders = false;
@ -1109,12 +1112,17 @@ FileData* SystemData::getRandomGame(const FileData* currentGame)
gameList = mRootFolder->getParent()->getChildrenListToDisplay();
}
else {
gameList = ViewController::getInstance()
->getGamelistView(mRootFolder->getSystem())
.get()
->getCursor()
->getParent()
->getChildrenListToDisplay();
if (gameSelectorMode) {
gameList = mRootFolder->getFilesRecursive(GAME, false, false);
}
else {
gameList = ViewController::getInstance()
->getGamelistView(mRootFolder->getSystem())
.get()
->getCursor()
->getParent()
->getChildrenListToDisplay();
}
}
if (gameList.size() > 0 && gameList.front()->getParent()->getOnlyFoldersFlag())
@ -1231,6 +1239,22 @@ void SystemData::loadTheme()
sysData.insert(std::pair<std::string, std::string>("system.name", getName()));
sysData.insert(std::pair<std::string, std::string>("system.theme", getThemeFolder()));
sysData.insert(std::pair<std::string, std::string>("system.fullName", getFullName()));
if (isCollection()) {
sysData.insert(
std::pair<std::string, std::string>("system.name.collections", getName()));
sysData.insert(
std::pair<std::string, std::string>("system.fullName.collections", getFullName()));
sysData.insert(
std::pair<std::string, std::string>("system.theme.collections", getThemeFolder()));
}
else {
sysData.insert(
std::pair<std::string, std::string>("system.name.noCollections", getName()));
sysData.insert(std::pair<std::string, std::string>("system.fullName.noCollections",
getFullName()));
sysData.insert(std::pair<std::string, std::string>("system.theme.noCollections",
getThemeFolder()));
}
mTheme->loadFile(sysData, path);
}

View file

@ -128,7 +128,7 @@ public:
SystemData* getNext() const;
SystemData* getPrev() const;
static SystemData* getRandomSystem(const SystemData* currentSystem);
FileData* getRandomGame(const FileData* currentGame = nullptr);
FileData* getRandomGame(const FileData* currentGame = nullptr, bool gameSelectorMode = false);
FileData* getPlaceholder() { return mPlaceholder; }
void sortSystem(bool reloadGamelist = true, bool jumpToFirstRow = false);

View file

@ -193,19 +193,19 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system)
// Set a maximum width depending on the aspect ratio of the screen, to make the screen look
// somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9
// reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float maxWidthModifier = glm::clamp(0.72f * aspectValue, 0.50f, 0.92f);
float maxWidth = static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
float maxWidthModifier {glm::clamp(0.72f * aspectValue, 0.50f, 0.92f)};
float maxWidth {Renderer::getScreenWidth() * maxWidthModifier};
// Set the width of the selector window to the menu width, unless the system full name is
// too large to fit. If so, allow the size to be exceeded up to the maximum size calculated
// above.
float systemTextWidth =
float systemTextWidth {
Font::get(FONT_SIZE_LARGE)->sizeText(Utils::String::toUpper(system->getFullName())).x *
1.05f;
1.05f};
float width = 0.0f;
float menuWidth = mMenu.getSize().x;
float width {0.0f};
float menuWidth {mMenu.getSize().x};
if (systemTextWidth <= menuWidth)
width = menuWidth;

View file

@ -304,7 +304,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title)
glm::vec2 menuSize {ss->getMenuSize()};
glm::vec3 menuPos {ss->getMenuPosition()};
ss->setMenuSize(glm::vec2 {menuSize.x * 1.08f, menuSize.y});
menuPos.x = static_cast<float>((Renderer::getScreenWidth()) - ss->getMenuSize().x) / 2.0f;
menuPos.x = (Renderer::getScreenWidth() - ss->getMenuSize().x) / 2.0f;
ss->setMenuPosition(menuPos);
mWindow->pushGui(ss);
});

View file

@ -243,8 +243,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system)
}
// Center the menu.
setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
mMenu.setPosition((mSize.x - mMenu.getSize().x) / 2.0f, (mSize.y - mMenu.getSize().y) / 2.0f);
}
@ -255,6 +254,8 @@ GuiGamelistOptions::~GuiGamelistOptions()
// the menu has been closed.
ViewController::getInstance()->stopScrolling();
ViewController::getInstance()->startViewVideos();
if (mFiltersChanged) {
if (!mCustomCollectionSystem) {
ViewController::getInstance()->reloadGamelistView(mSystem);

View file

@ -99,29 +99,29 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
// Adjust the width depending on the aspect ratio of the screen, to make the screen look
// somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9
// reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
float maxWidthModifier = glm::clamp(0.78f * aspectValue, 0.78f, 0.90f);
float minWidthModifier = glm::clamp(0.50f * aspectValue, 0.50f, 0.65f);
float maxWidthModifier {glm::clamp(0.78f * aspectValue, 0.78f, 0.90f)};
float minWidthModifier {glm::clamp(0.50f * aspectValue, 0.50f, 0.65f)};
float maxWidth = static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
float minWidth = static_cast<float>(Renderer::getScreenWidth()) * minWidthModifier;
float maxWidth {Renderer::getScreenWidth() * maxWidthModifier};
float minWidth {Renderer::getScreenWidth() * minWidthModifier};
float fontWidth =
float fontWidth {
Font::get(static_cast<int>(gameNameFontSize * std::min(Renderer::getScreenHeight(),
Renderer::getScreenWidth())))
->sizeText(Utils::String::toUpper(game->getName()))
.x;
.x};
// Add a bit of width to compensate for the left and right spacers.
fontWidth += static_cast<float>(Renderer::getScreenWidth()) * 0.05f;
fontWidth += Renderer::getScreenWidth() * 0.05f;
float width = glm::clamp(fontWidth, minWidth, maxWidth);
float width {glm::clamp(fontWidth, minWidth, maxWidth)};
if (mImagePath != "")
setSize(width, static_cast<float>(Renderer::getScreenHeight()) * 0.60f);
setSize(width, Renderer::getScreenHeight() * 0.60f);
else
setSize(width, static_cast<float>(Renderer::getScreenHeight()) * 0.38f);
setSize(width, Renderer::getScreenHeight() * 0.38f);
// Set row heights.
if (mImagePath != "")
@ -160,8 +160,9 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
// width so that the sizes look somewhat consistent regardless of the aspect ratio
// of the images.
if (mImagePath != "") {
mMarquee->setImage(game->getMarqueePath(), false, true);
mMarquee->cropTransparentPadding(static_cast<float>(Renderer::getScreenWidth()) *
mMarquee->setLinearInterpolation(true);
mMarquee->setImage(game->getMarqueePath(), false);
mMarquee->cropTransparentPadding(Renderer::getScreenWidth() *
(0.25f * (1.778f / Renderer::getScreenAspectRatio())),
mGrid->getRowHeight(3));
@ -178,8 +179,7 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game)
setOrigin({0.5f, 0.5f});
// Center on the X axis and keep slightly off-center on the Y axis.
setPosition(static_cast<float>(Renderer::getScreenWidth()) / 2.0f,
static_cast<float>(Renderer::getScreenHeight()) / 2.25f);
setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.25f);
mBackground.fitTo(mSize, glm::vec3 {}, glm::vec2 {-32.0f, -32.0f});
mBackground.setEdgeColor(0xEEEEEEFF);

View file

@ -87,6 +87,8 @@ GuiMenu::~GuiMenu()
// was openened. Without this, the scrolling would run until manually stopped after
// the menu has been closed.
ViewController::getInstance()->stopScrolling();
ViewController::getInstance()->startViewVideos();
}
void GuiMenu::openScraperOptions()
@ -283,79 +285,6 @@ void GuiMenu::openUIOptions()
}
});
// When the theme set entries are scrolled or selected, update the relevant rows.
auto scrollThemeSetFunc = [=](const std::string& themeName, bool firstRun = false) {
auto selectedSet = themeSets.find(themeName);
if (selectedSet == themeSets.cend())
return;
if (!firstRun) {
themeVariantsFunc(themeName, themeVariant->getSelected());
themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected());
}
int selectableVariants {0};
for (auto& variant : selectedSet->second.capabilities.variants) {
if (variant.selectable)
++selectableVariants;
}
if (!selectedSet->second.capabilities.legacyTheme && selectableVariants > 0) {
themeVariant->setEnabled(true);
themeVariant->setOpacity(255);
themeVariant->getParent()->getChild(themeVariant->getChildIndex() - 1)->setOpacity(255);
}
else {
themeVariant->setEnabled(false);
themeVariant->setOpacity(DISABLED_OPACITY);
themeVariant->getParent()
->getChild(themeVariant->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
if (!selectedSet->second.capabilities.legacyTheme &&
selectedSet->second.capabilities.aspectRatios.size() > 0) {
themeAspectRatio->setEnabled(true);
themeAspectRatio->setOpacity(255);
themeAspectRatio->getParent()
->getChild(themeAspectRatio->getChildIndex() - 1)
->setOpacity(255);
}
else {
themeAspectRatio->setEnabled(false);
themeAspectRatio->setOpacity(DISABLED_OPACITY);
themeAspectRatio->getParent()
->getChild(themeAspectRatio->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
if (!selectedSet->second.capabilities.legacyTheme) {
gamelist_view_style->setEnabled(false);
gamelist_view_style->setOpacity(DISABLED_OPACITY);
gamelist_view_style->getParent()
->getChild(gamelist_view_style->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
// TEMPORARY
// transition_style->setEnabled(false);
transition_style->setOpacity(DISABLED_OPACITY);
transition_style->getParent()
->getChild(transition_style->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
else {
gamelist_view_style->setEnabled(true);
gamelist_view_style->setOpacity(255);
gamelist_view_style->getParent()
->getChild(gamelist_view_style->getChildIndex() - 1)
->setOpacity(255);
transition_style->setEnabled(true);
transition_style->setOpacity(255);
transition_style->getParent()
->getChild(transition_style->getChildIndex() - 1)
->setOpacity(255);
}
};
scrollThemeSetFunc(selectedSet->first, true);
theme_set->setCallback(scrollThemeSetFunc);
// Optionally start in selected system/gamelist.
auto startupSystem = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "GAMELIST ON STARTUP", false);
@ -585,28 +514,28 @@ void GuiMenu::openUIOptions()
#endif
// Display pillarboxes (and letterboxes) for videos in the gamelists.
auto gamelist_video_pillarbox = std::make_shared<SwitchComponent>();
gamelist_video_pillarbox->setState(Settings::getInstance()->getBool("GamelistVideoPillarbox"));
s->addWithLabel("DISPLAY PILLARBOXES FOR GAMELIST VIDEOS", gamelist_video_pillarbox);
s->addSaveFunc([gamelist_video_pillarbox, s] {
if (gamelist_video_pillarbox->getState() !=
auto gamelistVideoPillarbox = std::make_shared<SwitchComponent>();
gamelistVideoPillarbox->setState(Settings::getInstance()->getBool("GamelistVideoPillarbox"));
s->addWithLabel("DISPLAY PILLARBOXES FOR GAMELIST VIDEOS", gamelistVideoPillarbox);
s->addSaveFunc([gamelistVideoPillarbox, s] {
if (gamelistVideoPillarbox->getState() !=
Settings::getInstance()->getBool("GamelistVideoPillarbox")) {
Settings::getInstance()->setBool("GamelistVideoPillarbox",
gamelist_video_pillarbox->getState());
gamelistVideoPillarbox->getState());
s->setNeedsSaving();
}
});
#if defined(USE_OPENGL_21)
// Render scanlines for videos in the gamelists.
auto gamelist_video_scanlines = std::make_shared<SwitchComponent>();
gamelist_video_scanlines->setState(Settings::getInstance()->getBool("GamelistVideoScanlines"));
s->addWithLabel("RENDER SCANLINES FOR GAMELIST VIDEOS", gamelist_video_scanlines);
s->addSaveFunc([gamelist_video_scanlines, s] {
if (gamelist_video_scanlines->getState() !=
auto gamelistVideoScanlines = std::make_shared<SwitchComponent>();
gamelistVideoScanlines->setState(Settings::getInstance()->getBool("GamelistVideoScanlines"));
s->addWithLabel("RENDER SCANLINES FOR GAMELIST VIDEOS", gamelistVideoScanlines);
s->addSaveFunc([gamelistVideoScanlines, s] {
if (gamelistVideoScanlines->getState() !=
Settings::getInstance()->getBool("GamelistVideoScanlines")) {
Settings::getInstance()->setBool("GamelistVideoScanlines",
gamelist_video_scanlines->getState());
gamelistVideoScanlines->getState());
s->setNeedsSaving();
}
});
@ -761,18 +690,110 @@ void GuiMenu::openUIOptions()
}
});
// Play videos immediately (overrides theme setting).
auto play_videos_immediately = std::make_shared<SwitchComponent>();
play_videos_immediately->setState(Settings::getInstance()->getBool("PlayVideosImmediately"));
s->addWithLabel("PLAY VIDEOS IMMEDIATELY (OVERRIDE THEME)", play_videos_immediately);
s->addSaveFunc([play_videos_immediately, s] {
if (Settings::getInstance()->getBool("PlayVideosImmediately") !=
play_videos_immediately->getState()) {
Settings::getInstance()->setBool("PlayVideosImmediately",
play_videos_immediately->getState());
s->setNeedsSaving();
// When the theme set entries are scrolled or selected, update the relevant rows.
auto scrollThemeSetFunc = [=](const std::string& themeName, bool firstRun = false) {
auto selectedSet = themeSets.find(themeName);
if (selectedSet == themeSets.cend())
return;
if (!firstRun) {
themeVariantsFunc(themeName, themeVariant->getSelected());
themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected());
}
});
int selectableVariants {0};
for (auto& variant : selectedSet->second.capabilities.variants) {
if (variant.selectable)
++selectableVariants;
}
if (!selectedSet->second.capabilities.legacyTheme && selectableVariants > 0) {
themeVariant->setEnabled(true);
themeVariant->setOpacity(1.0f);
themeVariant->getParent()
->getChild(themeVariant->getChildIndex() - 1)
->setOpacity(1.0f);
}
else {
themeVariant->setEnabled(false);
themeVariant->setOpacity(DISABLED_OPACITY);
themeVariant->getParent()
->getChild(themeVariant->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
if (!selectedSet->second.capabilities.legacyTheme &&
selectedSet->second.capabilities.aspectRatios.size() > 0) {
themeAspectRatio->setEnabled(true);
themeAspectRatio->setOpacity(1.0f);
themeAspectRatio->getParent()
->getChild(themeAspectRatio->getChildIndex() - 1)
->setOpacity(1.0f);
}
else {
themeAspectRatio->setEnabled(false);
themeAspectRatio->setOpacity(DISABLED_OPACITY);
themeAspectRatio->getParent()
->getChild(themeAspectRatio->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
if (!selectedSet->second.capabilities.legacyTheme) {
gamelist_view_style->setEnabled(false);
gamelist_view_style->setOpacity(DISABLED_OPACITY);
gamelist_view_style->getParent()
->getChild(gamelist_view_style->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
// TEMPORARY
// transition_style->setEnabled(false);
transition_style->setOpacity(DISABLED_OPACITY);
transition_style->getParent()
->getChild(transition_style->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
// Pillarboxes are theme-controlled for newer themes.
gamelistVideoPillarbox->setEnabled(false);
gamelistVideoPillarbox->setOpacity(DISABLED_OPACITY);
gamelistVideoPillarbox->getParent()
->getChild(gamelistVideoPillarbox->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
// Scanlines are theme-controlled for newer themes.
#if defined(USE_OPENGL_21)
gamelistVideoScanlines->setEnabled(false);
gamelistVideoScanlines->setOpacity(DISABLED_OPACITY);
gamelistVideoScanlines->getParent()
->getChild(gamelistVideoScanlines->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
#endif
}
else {
gamelist_view_style->setEnabled(true);
gamelist_view_style->setOpacity(1.0f);
gamelist_view_style->getParent()
->getChild(gamelist_view_style->getChildIndex() - 1)
->setOpacity(1.0f);
transition_style->setEnabled(true);
transition_style->setOpacity(1.0f);
transition_style->getParent()
->getChild(transition_style->getChildIndex() - 1)
->setOpacity(1.0f);
gamelistVideoPillarbox->setEnabled(true);
gamelistVideoPillarbox->setOpacity(1.0f);
gamelistVideoPillarbox->getParent()
->getChild(gamelistVideoPillarbox->getChildIndex() - 1)
->setOpacity(1.0f);
#if defined(USE_OPENGL_21)
gamelistVideoScanlines->setEnabled(true);
gamelistVideoScanlines->setOpacity(1.0f);
gamelistVideoScanlines->getParent()
->getChild(gamelistVideoScanlines->getChildIndex() - 1)
->setOpacity(1.0f);
#endif
}
};
scrollThemeSetFunc(selectedSet->first, true);
theme_set->setCallback(scrollThemeSetFunc);
s->setSize(mSize);
mWindow->pushGui(s);
@ -835,14 +856,13 @@ void GuiMenu::openSoundOptions()
if (UIModeController::getInstance()->isUIModeFull()) {
// Play audio for gamelist videos.
auto gamelist_video_audio = std::make_shared<SwitchComponent>();
gamelist_video_audio->setState(Settings::getInstance()->getBool("GamelistVideoAudio"));
s->addWithLabel("PLAY AUDIO FOR VIDEOS IN THE GAMELIST VIEW", gamelist_video_audio);
s->addSaveFunc([gamelist_video_audio, s] {
if (gamelist_video_audio->getState() !=
Settings::getInstance()->getBool("GamelistVideoAudio")) {
Settings::getInstance()->setBool("GamelistVideoAudio",
gamelist_video_audio->getState());
auto viewsVideoAudio = std::make_shared<SwitchComponent>();
viewsVideoAudio->setState(Settings::getInstance()->getBool("ViewsVideoAudio"));
s->addWithLabel("PLAY AUDIO FOR GAMELIST AND SYSTEM VIEW VIDEOS", viewsVideoAudio);
s->addSaveFunc([viewsVideoAudio, s] {
if (viewsVideoAudio->getState() !=
Settings::getInstance()->getBool("ViewsVideoAudio")) {
Settings::getInstance()->setBool("ViewsVideoAudio", viewsVideoAudio->getState());
s->setNeedsSaving();
}
});

View file

@ -256,10 +256,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
s->addRow(row, false);
}
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f);
float maxWidth =
static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
float maxWidthModifier {glm::clamp(0.64f * aspectValue, 0.42f, 0.92f)};
float maxWidth {Renderer::getScreenWidth() * maxWidthModifier};
s->setMenuSize(glm::vec2 {maxWidth, s->getMenuSize().y});
s->setMenuPosition(
@ -388,10 +387,11 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
s->addRow(row, false);
}
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f);
float maxWidth =
static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
const float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
const float maxWidthModifier {
glm::clamp(0.64f * aspectValue, 0.42f, 0.92f)};
const float maxWidth {static_cast<float>(Renderer::getScreenWidth()) *
maxWidthModifier};
s->setMenuSize(glm::vec2 {maxWidth, s->getMenuSize().y});
s->setMenuPosition(glm::vec3 {(s->getSize().x - maxWidth) / 2.0f,
@ -514,7 +514,6 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
buttons.push_back(std::make_shared<ButtonComponent>("SAVE", "save metadata", [&] {
save();
ViewController::getInstance()->onPauseVideo();
delete this;
}));
buttons.push_back(
@ -581,9 +580,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
mGrid.setEntry(mButtons, glm::ivec2 {0, 5}, true, false, glm::ivec2 {2, 1});
// Resize + center.
float width =
static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * 1.05f),
static_cast<int>(Renderer::getScreenWidth() * 0.90f)));
float width = std::min(Renderer::getScreenHeight() * 1.05f, Renderer::getScreenWidth() * 0.90f);
// Set height explicitly to ten rows for the component list.
float height = mList->getRowHeight(0) * 10.0f + mTitle->getSize().y + mSubtitle->getSize().y +
@ -843,7 +840,6 @@ void GuiMetaDataEd::close()
CollectionSystemsManager::getInstance()->refreshCollectionSystems(mScraperParams.game);
mWindow->invalidateCachedBackground();
}
ViewController::getInstance()->onPauseVideo();
delete this;
};

View file

@ -176,8 +176,8 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue<FileData*>& gameQueue)
// For narrower displays (e.g. in 4:3 ratio), allow the window to fill 95% of the screen
// width rather than the 85% allowed for wider displays.
float width =
Renderer::getScreenWidth() * ((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f);
float width {Renderer::getScreenWidth() *
((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f)};
setSize(width, Renderer::getScreenHeight() * 0.75f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,

View file

@ -915,10 +915,10 @@ void GuiScraperMenu::openOtherOptions()
}
else {
scraper_semiautomatic->setEnabled(true);
scraper_semiautomatic->setOpacity(255);
scraper_semiautomatic->setOpacity(1.0f);
scraper_semiautomatic->getParent()
->getChild(scraper_semiautomatic->getChildIndex() - 1)
->setOpacity(255);
->setOpacity(1.0f);
}
};
@ -932,10 +932,10 @@ void GuiScraperMenu::openOtherOptions()
}
else {
scraper_exclude_recursively->setEnabled(true);
scraper_exclude_recursively->setOpacity(255);
scraper_exclude_recursively->setOpacity(1.0f);
scraper_exclude_recursively->getParent()
->getChild(scraper_exclude_recursively->getChildIndex() - 1)
->setOpacity(255);
->setOpacity(1.0f);
}
};

View file

@ -70,8 +70,8 @@ GuiScraperMulti::GuiScraperMulti(const std::queue<ScraperSearchParams>& searches
mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this));
mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this));
mSearchComp->setRefineCallback([&] {
mScrollUp->setOpacity(0);
mScrollDown->setOpacity(0);
mScrollUp->setOpacity(0.0f);
mScrollDown->setOpacity(0.0f);
mResultList->resetScrollIndicatorStatus();
});
@ -151,8 +151,7 @@ GuiScraperMulti::GuiScraperMulti(const std::queue<ScraperSearchParams>& searches
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth();
float height = (mTitle->getFont()->getLetterHeight() +
static_cast<float>(Renderer::getScreenHeight()) * 0.0637f) +
float height = (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) +
mSystem->getFont()->getLetterHeight() +
mSubtitle->getFont()->getHeight() * 1.75f + mButtonGrid->getSize().y +
Font::get(FONT_SIZE_MEDIUM)->getHeight() * 7.0f;
@ -176,7 +175,6 @@ GuiScraperMulti::~GuiScraperMulti()
(*it)->sortSystem();
}
}
ViewController::getInstance()->onPauseVideo();
}
void GuiScraperMulti::onSizeChanged()
@ -230,8 +228,8 @@ void GuiScraperMulti::doNextSearch()
scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath());
}
mScrollUp->setOpacity(0);
mScrollDown->setOpacity(0);
mScrollUp->setOpacity(0.0f);
mScrollDown->setOpacity(0.0f);
mResultList->resetScrollIndicatorStatus();
// Extract possible subfolders from the path.

View file

@ -557,7 +557,7 @@ void GuiScraperSearch::updateInfoPane()
// Metadata.
if (mScrapeRatings) {
mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating")));
mMD_Rating->setOpacity(255);
mMD_Rating->setOpacity(1.0f);
}
mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate")));
mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer")));
@ -574,7 +574,7 @@ void GuiScraperSearch::updateInfoPane()
// Metadata.
if (mScrapeRatings) {
mMD_Rating->setValue("");
mMD_Rating->setOpacity(0);
mMD_Rating->setOpacity(0.0f);
}
// Set the release date to this value to force DateTimeEditComponent to put a
// blank instead of the text 'unknown' prior to the scrape result being returned.

View file

@ -113,21 +113,20 @@ GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params,
});
mSearch->setCancelCallback([&] { delete this; });
mSearch->setRefineCallback([&] {
mScrollUp->setOpacity(0);
mScrollDown->setOpacity(0);
mScrollUp->setOpacity(0.0f);
mScrollDown->setOpacity(0.0f);
mResultList->resetScrollIndicatorStatus();
});
// Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is
// the 16:9 reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth();
float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
float width {glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth()};
float height = (mGameName->getFont()->getLetterHeight() +
static_cast<float>(Renderer::getScreenHeight()) * 0.0637f) +
mSystemName->getFont()->getLetterHeight() +
static_cast<float>(Renderer::getScreenHeight()) * 0.04f +
mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f;
float height {
(mGameName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) +
mSystemName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.04f +
mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f};
setSize(width, height);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,

View file

@ -39,8 +39,7 @@ GuiSettings::GuiSettings(std::string title)
addChild(&mMenu);
mMenu.addButton("BACK", "back", [this] { delete this; });
setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
mMenu.setPosition((mSize.x - mMenu.getSize().x) / 2.0f, Renderer::getScreenHeight() * 0.13f);
}

View file

@ -261,7 +261,7 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
scrapeFiles.push_back(mediaFileInfo);
#if defined(_WIN64)
// Required due to the idiotic file locking that exists on this operating system.
ViewController::getInstance()->onStopVideo();
ViewController::getInstance()->stopViewVideos();
#endif
}

View file

@ -26,8 +26,7 @@ GamelistBase::GamelistBase(FileData* root)
, mIsFolder {false}
, mVideoPlaying {false}
{
setSize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
mList.setSize(mSize.x, mSize.y * 0.8f);
mList.setPosition(0.0f, mSize.y * 0.2f);
@ -75,7 +74,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
if (config->isMappedTo("a", input)) {
FileData* cursor = getCursor();
if (cursor->getType() == GAME) {
onPauseVideo();
pauseViewVideos();
ViewController::getInstance()->cancelViewTransitions();
stopListScrolling();
launch(cursor);
@ -132,7 +131,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
}
else {
NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND);
onPauseVideo();
muteViewVideos();
onFocusLost();
stopListScrolling();
SystemData* systemToView = getCursor()->getSystem();
@ -176,7 +175,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
else if (config->isMappedLike(getQuickSystemSelectRightButton(), input)) {
if (Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1) {
onPauseVideo();
muteViewVideos();
onFocusLost();
stopListScrolling();
ViewController::getInstance()->goToNextGamelist();
@ -186,7 +185,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
else if (config->isMappedLike(getQuickSystemSelectLeftButton(), input)) {
if (Settings::getInstance()->getBool("QuickSystemSelect") &&
SystemData::sSystemVector.size() > 1) {
onPauseVideo();
muteViewVideos();
onFocusLost();
stopListScrolling();
ViewController::getInstance()->goToPrevGamelist();
@ -429,7 +428,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
setCursor(getFirstEntry());
view->setCursor(view->getFirstEntry());
}
else if (selectLastEntry) {
else if (selectLastEntry && mList.size() > 0) {
setCursor(getLastEntry());
view->setCursor(view->getLastEntry());
}
@ -458,6 +457,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
config->isMappedTo("back", input) && input.value) {
ViewController::getInstance()->cancelViewTransitions();
stopListScrolling();
pauseViewVideos();
mWindow->pushGui(new GuiGamelistOptions(this->mRoot->getSystem()));
return true;
}
@ -622,7 +622,7 @@ void GamelistBase::generateFirstLetterIndex(const std::vector<FileData*>& files)
void GamelistBase::generateGamelistInfo(FileData* cursor, FileData* firstEntry)
{
// Generate data needed for the gamelistInfo field, which is displayed from the
// gamelist interfaces (Detailed/Video/Grid).
// gamelist interfaces.
mIsFiltered = false;
mIsFolder = false;
FileData* rootFolder {firstEntry->getSystem()->getRootFolder()};
@ -705,7 +705,7 @@ void GamelistBase::removeMedia(FileData* game)
std::string path;
// Stop the video player, especially important on Windows as the file would otherwise be locked.
onStopVideo();
stopViewVideos();
// If there are no media files left in the directory after the deletion, then remove
// the directory too. Remove any empty parent directories as well.

View file

@ -27,6 +27,7 @@ void GamelistView::legacyPopulateFields()
mTextComponents.back()->setHorizontalAlignment(ALIGN_CENTER);
mTextComponents.back()->setColor(0xFFFFFFFF);
mTextComponents.back()->setDefaultZIndex(50.0f);
mTextComponents.back()->setVisible(false);
addChild(mTextComponents.back().get());
// Logo.
@ -45,16 +46,16 @@ void GamelistView::legacyPopulateFields()
// Thumbnails.
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setMetadataField("image_md_thumbnail");
mImageComponents.back()->setThemeMetadata("image_md_thumbnail");
mImageComponents.back()->setOrigin(0.5f, 0.5f);
mImageComponents.back()->setVisible(false);
mImageComponents.back()->setMaxSize(mSize.x * (0.25f - 2.0f * padding), mSize.y * 0.10f);
mImageComponents.back()->setDefaultZIndex(25.0f);
addChild(mImageComponents.back().get());
// Marquee.
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setMetadataField("image_md_marquee");
mImageComponents.back()->setThemeMetadata("image_md_marquee");
mImageComponents.back()->setLinearInterpolation(true);
mImageComponents.back()->setOrigin(0.5f, 0.5f);
mImageComponents.back()->setVisible(false);
mImageComponents.back()->setMaxSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.18f);
@ -63,7 +64,7 @@ void GamelistView::legacyPopulateFields()
// Image.
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setMetadataField("image_md_image");
mImageComponents.back()->setThemeMetadata("image_md_image");
mImageComponents.back()->setOrigin(0.5f, 0.5f);
mImageComponents.back()->setPosition(mSize.x * 0.25f,
mList.getPosition().y + mSize.y * 0.2125f);
@ -74,7 +75,7 @@ void GamelistView::legacyPopulateFields()
if (mViewStyle == ViewController::VIDEO) {
// Video.
mVideoComponents.push_back(std::make_unique<VideoFFmpegComponent>());
mVideoComponents.back()->setMetadataField("video_md_video");
mVideoComponents.back()->setThemeMetadata("video_md_video");
mVideoComponents.back()->setOrigin(0.5f, 0.5f);
mVideoComponents.back()->setPosition(mSize.x * 0.25f,
mList.getPosition().y + mSize.y * 0.2125f);
@ -91,80 +92,80 @@ void GamelistView::legacyPopulateFields()
// Metadata labels + values.
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Rating: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_rating");
mTextComponents.back()->setThemeMetadata("text_md_lbl_rating");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Released: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_releasedate");
mTextComponents.back()->setThemeMetadata("text_md_lbl_releasedate");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Developer: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_developer");
mTextComponents.back()->setThemeMetadata("text_md_lbl_developer");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Publisher: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_publisher");
mTextComponents.back()->setThemeMetadata("text_md_lbl_publisher");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Genre: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_genre");
mTextComponents.back()->setThemeMetadata("text_md_lbl_genre");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Players: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_players");
mTextComponents.back()->setThemeMetadata("text_md_lbl_players");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Last played: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_lastplayed");
mTextComponents.back()->setThemeMetadata("text_md_lbl_lastplayed");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Times played: ", false);
mTextComponents.back()->setMetadataField("text_md_lbl_playcount");
mTextComponents.back()->setThemeMetadata("text_md_lbl_playcount");
addChild(mTextComponents.back().get());
mRatingComponents.push_back(std::make_unique<RatingComponent>());
mRatingComponents.back()->setMetadataField("rating_md_rating");
mRatingComponents.back()->setThemeMetadata("rating_md_rating");
mRatingComponents.back()->setDefaultZIndex(40.0f);
addChild(mRatingComponents.back().get());
mDateTimeComponents.push_back(std::make_unique<DateTimeComponent>());
mDateTimeComponents.back()->setMetadataField("datetime_md_releasedate");
mDateTimeComponents.back()->setThemeMetadata("datetime_md_releasedate");
addChild(mDateTimeComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("text_md_developer");
mTextComponents.back()->setThemeMetadata("text_md_developer");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("text_md_publisher");
mTextComponents.back()->setThemeMetadata("text_md_publisher");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("text_md_genre");
mTextComponents.back()->setThemeMetadata("text_md_genre");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("text_md_players");
mTextComponents.back()->setThemeMetadata("text_md_players");
addChild(mTextComponents.back().get());
mDateTimeComponents.push_back(std::make_unique<DateTimeComponent>());
mDateTimeComponents.back()->setMetadataField("datetime_md_lastplayed");
mDateTimeComponents.back()->setThemeMetadata("datetime_md_lastplayed");
mDateTimeComponents.back()->setDisplayRelative(true);
addChild(mDateTimeComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("text_md_playcount");
mTextComponents.back()->setThemeMetadata("text_md_playcount");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("text_md_name");
mTextComponents.back()->setThemeMetadata("text_md_name");
mTextComponents.back()->setPosition(mSize.x, mSize.y);
mTextComponents.back()->setFont(Font::get(FONT_SIZE_MEDIUM));
mTextComponents.back()->setHorizontalAlignment(ALIGN_CENTER);
@ -174,7 +175,7 @@ void GamelistView::legacyPopulateFields()
// Badges.
mBadgeComponents.push_back(std::make_unique<BadgeComponent>());
mBadgeComponents.back()->setMetadataField("badges_md_badges");
mBadgeComponents.back()->setThemeMetadata("badges_md_badges");
mBadgeComponents.back()->setOrigin(0.5f, 0.5f);
mBadgeComponents.back()->setPosition(mSize.x * 0.8f, mSize.y * 0.7f);
mBadgeComponents.back()->setSize(mSize.x * 0.15f, mSize.y * 0.2f);
@ -183,7 +184,7 @@ void GamelistView::legacyPopulateFields()
// Scrollable container (game description).
mContainerComponents.push_back(std::make_unique<ScrollableContainer>());
mContainerComponents.back()->setMetadataField("text_md_description");
mContainerComponents.back()->setThemeMetadata("text_md_description");
mContainerComponents.back()->setSize(mSize.x * (0.50f - 2.0f * padding),
mSize.y - mContainerComponents.back()->getPosition().y);
mContainerComponents.back()->setAutoScroll(true);
@ -196,7 +197,7 @@ void GamelistView::legacyPopulateFields()
mContainerComponents.back()->addChild(mTextComponents.back().get());
mGamelistInfoComponents.push_back(std::make_unique<TextComponent>());
mGamelistInfoComponents.back()->setMetadataField("text_gamelistInfo");
mGamelistInfoComponents.back()->setThemeMetadata("text_gamelistInfo");
mGamelistInfoComponents.back()->setOrigin(0.5f, 0.5f);
mGamelistInfoComponents.back()->setFont(Font::get(FONT_SIZE_SMALL));
mGamelistInfoComponents.back()->setDefaultZIndex(50.0f);
@ -229,19 +230,19 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
mList.applyTheme(theme, getName(), "textlist_gamelist", ALL);
mImageComponents[LegacyImage::MD_THUMBNAIL]->applyTheme(
theme, getName(), mImageComponents[LegacyImage::MD_THUMBNAIL]->getMetadataField(), ALL);
theme, getName(), mImageComponents[LegacyImage::MD_THUMBNAIL]->getThemeMetadata(), ALL);
mImageComponents[LegacyImage::MD_MARQUEE]->applyTheme(theme, getName(), "image_md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX |
ROTATION | VISIBLE);
if (mViewStyle == ViewController::DETAILED) {
mImageComponents[LegacyImage::MD_IMAGE]->applyTheme(
theme, getName(), mImageComponents[LegacyImage::MD_IMAGE]->getMetadataField(),
theme, getName(), mImageComponents[LegacyImage::MD_IMAGE]->getThemeMetadata(),
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
}
else if (mViewStyle == ViewController::VIDEO) {
mVideoComponents.front()->applyTheme(
theme, getName(), mVideoComponents.front()->getMetadataField(),
theme, getName(), mVideoComponents.front()->getThemeMetadata(),
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE);
}
@ -249,37 +250,31 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
legacyInitMDValues();
mTextComponents[LegacyText::MD_NAME]->applyTheme(
theme, getName(), mTextComponents[LegacyText::MD_NAME]->getMetadataField(), ALL);
theme, getName(), mTextComponents[LegacyText::MD_NAME]->getThemeMetadata(), ALL);
for (size_t i = 0; i < mBadgeComponents.size(); ++i)
mBadgeComponents[i]->applyTheme(theme, getName(), mBadgeComponents[i]->getMetadataField(),
mBadgeComponents[i]->applyTheme(theme, getName(), mBadgeComponents[i]->getThemeMetadata(),
ALL);
for (size_t i = 0; i < mRatingComponents.size(); ++i)
mRatingComponents[i]->applyTheme(theme, getName(), mRatingComponents[i]->getMetadataField(),
mRatingComponents[i]->applyTheme(theme, getName(), mRatingComponents[i]->getThemeMetadata(),
ALL);
mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->applyTheme(
theme, getName(), mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->getMetadataField(),
theme, getName(), mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->getThemeMetadata(),
ALL);
mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->applyTheme(
theme, getName(), mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->getMetadataField(),
theme, getName(), mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->getThemeMetadata(),
ALL);
if (mLegacyMode) {
for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i)
mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getMetadataField(),
ALL ^ ThemeFlags::TEXT);
}
else {
for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i)
mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getMetadataField(),
ALL);
for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i) {
mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getThemeMetadata(),
ALL ^ ThemeFlags::TEXT);
}
for (auto& container : mContainerComponents) {
container->applyTheme(theme, getName(), container->getMetadataField(),
container->applyTheme(theme, getName(), container->getThemeMetadata(),
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
}
@ -290,7 +285,7 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
for (auto& gamelistInfo : mGamelistInfoComponents)
gamelistInfo->applyTheme(theme, getName(), gamelistInfo->getMetadataField(),
gamelistInfo->applyTheme(theme, getName(), gamelistInfo->getThemeMetadata(),
ALL ^ ThemeFlags::TEXT);
// If there is no position defined in the theme for gamelistInfo, then hide it.
@ -371,6 +366,12 @@ void GamelistView::legacyUpdateInfoPanel()
bool fadingOut = false;
if (file == nullptr) {
if (mViewStyle == ViewController::VIDEO) {
mVideoComponents.front()->stopVideoPlayer();
mVideoComponents.front()->setVideo("");
if (!mVideoComponents.front()->hasStartDelay())
mVideoComponents.front()->setImage("");
}
mVideoPlaying = false;
fadingOut = true;
}
@ -385,19 +386,15 @@ void GamelistView::legacyUpdateInfoPanel()
if (mRandomGame) {
mImageComponents[LegacyImage::MD_THUMBNAIL]->setImage(
mRandomGame->getThumbnailPath());
mImageComponents[LegacyImage::MD_MARQUEE]->setImage(mRandomGame->getMarqueePath(),
false, true);
mImageComponents[LegacyImage::MD_MARQUEE]->setImage(mRandomGame->getMarqueePath());
if (mViewStyle == ViewController::VIDEO) {
mVideoComponents.front()->setImage(mRandomGame->getImagePath());
// Always stop the video before setting a new video as it will otherwise
// continue to play if it has the same path (i.e. it is the same physical
// video file) as the previously set video. That may happen when entering a
// folder with the same name as the first game file inside, or as in this
// case, when entering a custom collection.
mVideoComponents.front()->onHide();
mVideoComponents.front()->stopVideoPlayer();
if (!mVideoComponents.front()->setVideo(mRandomGame->getVideoPath()))
mVideoComponents.front()->setDefaultVideo();
mVideoComponents.front()->startVideoPlayer();
}
else {
mImageComponents[LegacyImage::MD_IMAGE]->setImage(mRandomGame->getImagePath());
@ -416,14 +413,15 @@ void GamelistView::legacyUpdateInfoPanel()
}
else {
mImageComponents[LegacyImage::MD_THUMBNAIL]->setImage(file->getThumbnailPath());
mImageComponents[LegacyImage::MD_MARQUEE]->setImage(file->getMarqueePath(), false,
true);
mImageComponents[LegacyImage::MD_MARQUEE]->setImage(file->getMarqueePath());
if (mViewStyle == ViewController::VIDEO) {
mVideoComponents.front()->setImage(file->getImagePath());
mVideoComponents.front()->onHide();
mVideoComponents.front()->stopVideoPlayer();
if (!mVideoComponents.front()->setVideo(file->getVideoPath()))
mVideoComponents.front()->setDefaultVideo();
mVideoComponents.front()->startVideoPlayer();
}
else {
mImageComponents[LegacyImage::MD_IMAGE]->setImage(file->getImagePath());
@ -472,8 +470,8 @@ void GamelistView::legacyUpdateInfoPanel()
if (mViewStyle == ViewController::DETAILED) {
// Fade in the game image.
auto func = [this](float t) {
mImageComponents[LegacyImage::MD_IMAGE]->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
mImageComponents[LegacyImage::MD_IMAGE]->setOpacity(
glm::mix(FADE_IN_START_OPACITY, 1.0f, t));
};
mImageComponents[LegacyImage::MD_IMAGE]->setAnimation(
new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
@ -481,8 +479,7 @@ void GamelistView::legacyUpdateInfoPanel()
else if (mViewStyle == ViewController::VIDEO) {
// Fade in the static image.
auto func = [this](float t) {
mVideoComponents.front()->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
mVideoComponents.front()->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t));
};
mVideoComponents.front()->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0,
nullptr, false);
@ -509,17 +506,17 @@ void GamelistView::legacyUpdateInfoPanel()
BadgeComponent::BadgeInfo badgeInfo;
badgeInfo.badgeType = badge;
if (badge == "controller") {
if (file->metadata.get("controller").compare("") != 0) {
if (file->metadata.get("controller") != "") {
badgeInfo.gameController = file->metadata.get("controller");
badgeSlots.push_back(badgeInfo);
}
}
else if (badge == "altemulator") {
if (file->metadata.get(badge).compare("") != 0)
if (file->metadata.get(badge) != "")
badgeSlots.push_back(badgeInfo);
}
else {
if (file->metadata.get(badge).compare("true") == 0)
if (file->metadata.get(badge) == "true")
badgeSlots.push_back(badgeInfo);
}
}
@ -550,51 +547,30 @@ void GamelistView::legacyUpdateInfoPanel()
std::vector<GuiComponent*> comps;
if (!mLegacyMode) {
for (auto& text : mTextComponents) {
if (text->getScrollHide())
comps.emplace_back(text.get());
}
for (auto& date : mDateTimeComponents) {
if (date->getScrollHide())
comps.emplace_back(date.get());
}
for (auto& image : mImageComponents) {
if (image->getScrollHide())
comps.emplace_back(image.get());
}
for (auto& badge : mBadgeComponents) {
if (badge->getScrollHide())
comps.emplace_back(badge.get());
}
for (auto& rating : mRatingComponents) {
if (rating->getScrollHide())
comps.emplace_back(rating.get());
}
}
if (mLegacyMode) {
for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::END; ++i)
comps.emplace_back(mTextComponents[i].get());
comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE].get());
comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED].get());
comps.emplace_back(mTextComponents[LegacyText::MD_NAME].get());
comps.emplace_back(mImageComponents[LegacyImage::MD_THUMBNAIL].get());
comps.emplace_back(mImageComponents[LegacyImage::MD_MARQUEE].get());
comps.emplace_back(mImageComponents[LegacyImage::MD_IMAGE].get());
comps.push_back(mBadgeComponents.front().get());
comps.push_back(mRatingComponents.front().get());
}
for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::END; ++i)
comps.emplace_back(mTextComponents[i].get());
comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE].get());
comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED].get());
comps.emplace_back(mTextComponents[LegacyText::MD_NAME].get());
comps.emplace_back(mImageComponents[LegacyImage::MD_THUMBNAIL].get());
comps.emplace_back(mImageComponents[LegacyImage::MD_MARQUEE].get());
comps.emplace_back(mImageComponents[LegacyImage::MD_IMAGE].get());
if (mVideoComponents.size() > 0)
comps.emplace_back(mVideoComponents.front().get());
comps.push_back(mBadgeComponents.front().get());
comps.push_back(mRatingComponents.front().get());
for (auto it = comps.cbegin(); it != comps.cend(); ++it) {
GuiComponent* comp = *it;
GuiComponent* comp {*it};
if (!fadingOut && !comp->isAnimationPlaying(0)) {
comp->setOpacity(1.0f);
continue;
}
// An animation is playing, then animate if reverse != fadingOut.
// An animation is not playing, then animate if opacity != our target opacity.
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
};
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0.0f : 1.0f))) {
auto func = [comp](float t) { comp->setOpacity(glm::mix(0.0f, 1.0f, t)); };
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
}
}
@ -607,12 +583,6 @@ void GamelistView::legacyUpdate(int deltaTime)
mImageComponents[LegacyImage::MD_IMAGE]->finishAnimation(0);
if (mViewStyle == ViewController::VIDEO) {
if (!mVideoPlaying)
mVideoComponents.front()->onHide();
else if (mVideoPlaying && !mVideoComponents.front()->isVideoPaused() &&
!mWindow->isScreensaverActive())
mVideoComponents.front()->onShow();
if (ViewController::getInstance()->getGameLaunchTriggered() &&
mVideoComponents.front()->isAnimationPlaying(0))
mVideoComponents.front()->finishAnimation(0);
@ -669,9 +639,7 @@ void GamelistView::legacyInitMDValues()
}
mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->setFont(defaultFont);
// mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->setColor(0xFFFFFFFF);
mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->setFont(defaultFont);
// mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->setColor(0xFFFFFFFF);
values.emplace_back(mRatingComponents.front().get());
values.emplace_back(mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE].get());

View file

@ -85,9 +85,20 @@ void GamelistView::onShow()
updateInfoPanel();
}
void GamelistView::onTransition()
{
for (auto& animation : mLottieAnimComponents)
animation->setPauseAnimation(true);
}
void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
mLegacyMode = mTheme->isLegacyTheme();
auto themeSets = ThemeData::getThemeSets();
std::map<std::string, ThemeData::ThemeSet>::const_iterator selectedSet {
themeSets.find(Settings::getInstance()->getString("ThemeSet"))};
assert(selectedSet != themeSets.cend());
mLegacyMode = selectedSet->second.capabilities.legacyTheme;
if (mLegacyMode) {
legacyOnThemeChanged(theme);
@ -102,7 +113,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setDefaultZIndex(30.0f);
mImageComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mImageComponents.back()->getMetadataField() != "")
if (mImageComponents.back()->getThemeImageTypes().size() != 0)
mImageComponents.back()->setScrollHide(true);
addChild(mImageComponents.back().get());
}
@ -111,7 +122,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mVideoComponents.back()->setDefaultZIndex(30.0f);
addChild(mVideoComponents.back().get());
mVideoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mVideoComponents.back()->getMetadataField() != "")
if (mVideoComponents.back()->getThemeImageTypes().size() != 0)
mVideoComponents.back()->setScrollHide(true);
}
else if (element.second.type == "animation") {
@ -130,7 +141,6 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
else if (element.second.type == "text") {
if (element.second.has("container") && element.second.get<bool>("container")) {
mContainerComponents.push_back(std::make_unique<ScrollableContainer>());
mContainerComponents.back()->setAutoScroll(true);
mContainerComponents.back()->setDefaultZIndex(40.0f);
addChild(mContainerComponents.back().get());
mContainerTextComponents.push_back(std::make_unique<TextComponent>());
@ -139,18 +149,19 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mContainerComponents.back()->applyTheme(theme, "gamelist", element.first,
POSITION | ThemeFlags::SIZE | Z_INDEX |
VISIBLE);
mContainerComponents.back()->setAutoScroll(true);
mContainerTextComponents.back()->setSize(
mContainerComponents.back()->getSize().x, 0.0f);
mContainerTextComponents.back()->applyTheme(
theme, "gamelist", element.first,
(ALL ^ POSITION ^ Z_INDEX ^ ThemeFlags::SIZE ^ VISIBLE ^ ROTATION) | COLOR);
ALL ^ POSITION ^ Z_INDEX ^ ThemeFlags::SIZE ^ VISIBLE ^ ROTATION);
mContainerComponents.back()->setScrollHide(true);
}
else {
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setDefaultZIndex(40.0f);
mTextComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mTextComponents.back()->getMetadataField() != "")
if (mTextComponents.back()->getThemeMetadata() != "")
mTextComponents.back()->setScrollHide(true);
addChild(mTextComponents.back().get());
}
@ -159,7 +170,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mDateTimeComponents.push_back(std::make_unique<DateTimeComponent>());
mDateTimeComponents.back()->setDefaultZIndex(40.0f);
mDateTimeComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mDateTimeComponents.back()->getMetadataField() != "")
if (mDateTimeComponents.back()->getThemeMetadata() != "")
mDateTimeComponents.back()->setScrollHide(true);
addChild(mDateTimeComponents.back().get());
}
@ -199,16 +210,6 @@ void GamelistView::update(int deltaTime)
}
}
for (auto& video : mVideoComponents) {
if (!mVideoPlaying)
video->onHide();
else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive())
video->onShow();
if (ViewController::getInstance()->getGameLaunchTriggered() && video->isAnimationPlaying(0))
video->finishAnimation(0);
}
updateChildren(deltaTime);
}
@ -330,7 +331,7 @@ void GamelistView::updateInfoPanel()
if (hideMetaDataFields) {
for (auto& text : mTextComponents) {
if (text->getMetadataField() != "")
if (text->getThemeMetadata() != "")
text->setVisible(false);
}
for (auto& date : mDateTimeComponents)
@ -340,13 +341,13 @@ void GamelistView::updateInfoPanel()
for (auto& rating : mRatingComponents)
rating->setVisible(false);
for (auto& cText : mContainerTextComponents) {
if (cText->getMetadataField() != "md_description")
if (cText->getThemeMetadata() != "description")
cText->setVisible(false);
}
}
else {
for (auto& text : mTextComponents) {
if (text->getMetadataField() != "")
if (text->getThemeMetadata() != "")
text->setVisible(true);
}
for (auto& date : mDateTimeComponents)
@ -356,13 +357,21 @@ void GamelistView::updateInfoPanel()
for (auto& rating : mRatingComponents)
rating->setVisible(true);
for (auto& cText : mContainerTextComponents) {
if (cText->getMetadataField() != "md_description")
if (cText->getThemeMetadata() != "description")
cText->setVisible(true);
}
}
bool fadingOut = false;
if (file == nullptr) {
if (mVideoPlaying) {
for (auto& video : mVideoComponents) {
video->stopVideoPlayer();
video->setVideo("");
if (!video->hasStartDelay())
video->setImage("");
}
}
mVideoPlaying = false;
fadingOut = true;
}
@ -375,75 +384,33 @@ void GamelistView::updateInfoPanel()
mRandomGame = CollectionSystemsManager::getInstance()->updateCollectionFolderMetadata(
file->getSystem());
if (mRandomGame) {
for (auto& image : mImageComponents) {
if (image->getMetadataField() == "md_image")
image->setImage(mRandomGame->getImagePath());
else if (image->getMetadataField() == "md_miximage")
image->setImage(mRandomGame->getMiximagePath());
else if (image->getMetadataField() == "md_marquee")
image->setImage(mRandomGame->getMarqueePath(), false, true);
else if (image->getMetadataField() == "md_screenshot")
image->setImage(mRandomGame->getScreenshotPath());
else if (image->getMetadataField() == "md_titlescreen")
image->setImage(mRandomGame->getTitleScreenPath());
else if (image->getMetadataField() == "md_cover")
image->setImage(mRandomGame->getCoverPath());
else if (image->getMetadataField() == "md_backcover")
image->setImage(mRandomGame->getBackCoverPath());
else if (image->getMetadataField() == "md_3dbox")
image->setImage(mRandomGame->get3DBoxPath());
else if (image->getMetadataField() == "md_fanart")
image->setImage(mRandomGame->getFanArtPath());
else if (image->getMetadataField() == "md_thumbnail")
image->setImage(mRandomGame->getThumbnailPath());
}
for (auto& image : mImageComponents)
setGameImage(mRandomGame, image.get());
for (auto& video : mVideoComponents) {
if (video->getMetadataField() == "md_image")
video->setImage(mRandomGame->getImagePath());
else if (video->getMetadataField() == "md_miximage")
video->setImage(mRandomGame->getMiximagePath());
else if (video->getMetadataField() == "md_marquee")
video->setImage(mRandomGame->getMarqueePath(), false, true);
else if (video->getMetadataField() == "md_screenshot")
video->setImage(mRandomGame->getScreenshotPath());
else if (video->getMetadataField() == "md_titlescreen")
video->setImage(mRandomGame->getTitleScreenPath());
else if (video->getMetadataField() == "md_cover")
video->setImage(mRandomGame->getCoverPath());
else if (video->getMetadataField() == "md_backcover")
video->setImage(mRandomGame->getBackCoverPath());
else if (video->getMetadataField() == "md_3dbox")
video->setImage(mRandomGame->get3DBoxPath());
else if (video->getMetadataField() == "md_fanart")
video->setImage(mRandomGame->getFanArtPath());
else if (video->getMetadataField() == "md_thumbnail")
video->setImage(mRandomGame->getThumbnailPath());
setGameImage(mRandomGame, video.get());
// Always stop the video before setting a new video as it will otherwise
// continue to play if it has the same path (i.e. it is the same physical
// video file) as the previously set video. That may happen when entering a
// folder with the same name as the first game file inside, or as in this
// case, when entering a custom collection.
video->onHide();
video->stopVideoPlayer();
if (video->hasStaticVideo())
video->setStaticVideo();
else if (!video->setVideo(mRandomGame->getVideoPath()))
video->setDefaultVideo();
video->startVideoPlayer();
}
}
else {
for (auto& image : mImageComponents) {
if (image->getMetadataField() != "")
if (image->getThemeImageTypes().size() != 0)
image->setImage("");
}
for (auto& video : mVideoComponents) {
video->stopVideoPlayer();
video->setImage("");
video->setVideo("");
if (video->hasStaticVideo()) {
video->onStopVideo();
video->setStaticVideo();
}
else {
@ -454,56 +421,19 @@ void GamelistView::updateInfoPanel()
}
else {
for (auto& image : mImageComponents) {
if (image->getMetadataField() == "md_image")
image->setImage(file->getImagePath());
else if (image->getMetadataField() == "md_miximage")
image->setImage(file->getMiximagePath());
else if (image->getMetadataField() == "md_marquee")
image->setImage(file->getMarqueePath(), false, true);
else if (image->getMetadataField() == "md_screenshot")
image->setImage(file->getScreenshotPath());
else if (image->getMetadataField() == "md_titlescreen")
image->setImage(file->getTitleScreenPath());
else if (image->getMetadataField() == "md_cover")
image->setImage(file->getCoverPath());
else if (image->getMetadataField() == "md_backcover")
image->setImage(file->getBackCoverPath());
else if (image->getMetadataField() == "md_3dbox")
image->setImage(file->get3DBoxPath());
else if (image->getMetadataField() == "md_fanart")
image->setImage(file->getFanArtPath());
else if (image->getMetadataField() == "md_thumbnail")
image->setImage(file->getThumbnailPath());
setGameImage(file, image.get());
}
for (auto& video : mVideoComponents) {
if (video->getMetadataField() == "md_image")
video->setImage(file->getImagePath());
else if (video->getMetadataField() == "md_miximage")
video->setImage(file->getMiximagePath());
else if (video->getMetadataField() == "md_marquee")
video->setImage(file->getMarqueePath(), false, true);
else if (video->getMetadataField() == "md_screenshot")
video->setImage(file->getScreenshotPath());
else if (video->getMetadataField() == "md_titlescreen")
video->setImage(file->getTitleScreenPath());
else if (video->getMetadataField() == "md_cover")
video->setImage(file->getCoverPath());
else if (video->getMetadataField() == "md_backcover")
video->setImage(file->getBackCoverPath());
else if (video->getMetadataField() == "md_3dbox")
video->setImage(file->get3DBoxPath());
else if (video->getMetadataField() == "md_fanart")
video->setImage(file->getFanArtPath());
else if (video->getMetadataField() == "md_thumbnail")
video->setImage(file->getThumbnailPath());
video->onHide();
setGameImage(file, video.get());
video->stopVideoPlayer();
if (video->hasStaticVideo())
video->setStaticVideo();
else if (!video->setVideo(file->getVideoPath()))
video->setDefaultVideo();
video->startVideoPlayer();
}
}
@ -550,8 +480,7 @@ void GamelistView::updateInfoPanel()
for (auto& image : mImageComponents) {
if (image->getScrollFadeIn()) {
auto func = [&image](float t) {
image->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
image->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t));
};
image->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
}
@ -561,8 +490,7 @@ void GamelistView::updateInfoPanel()
for (auto& video : mVideoComponents) {
if (video->getScrollFadeIn()) {
auto func = [&video](float t) {
video->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
video->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t));
};
video->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
}
@ -581,17 +509,17 @@ void GamelistView::updateInfoPanel()
BadgeComponent::BadgeInfo badgeInfo;
badgeInfo.badgeType = badge;
if (badge == "controller") {
if (file->metadata.get("controller").compare("") != 0) {
if (file->metadata.get("controller") != "") {
badgeInfo.gameController = file->metadata.get("controller");
badgeSlots.push_back(badgeInfo);
}
}
else if (badge == "altemulator") {
if (file->metadata.get(badge).compare("") != 0)
if (file->metadata.get(badge) != "")
badgeSlots.push_back(badgeInfo);
}
else {
if (file->metadata.get(badge).compare("true") == 0)
if (file->metadata.get(badge) == "true")
badgeSlots.push_back(badgeInfo);
}
}
@ -599,23 +527,23 @@ void GamelistView::updateInfoPanel()
}
for (auto& text : mTextComponents) {
if (text->getMetadataField() == "md_name")
if (text->getThemeMetadata() == "name")
text->setText(file->metadata.get("name"));
}
if (file->getType() == GAME) {
if (!hideMetaDataFields) {
for (auto& date : mDateTimeComponents) {
if (date->getMetadataField() == "md_lastplayed")
if (date->getThemeMetadata() == "lastplayed")
date->setValue(file->metadata.get("lastplayed"));
else if (date->getMetadataField() == "md_playcount")
else if (date->getThemeMetadata() == "playcount")
date->setValue(file->metadata.get("playcount"));
}
}
else if (file->getType() == FOLDER) {
if (!hideMetaDataFields) {
for (auto& date : mDateTimeComponents) {
if (date->getMetadataField() == "md_lastplayed") {
if (date->getThemeMetadata() == "lastplayed") {
date->setValue(file->metadata.get("lastplayed"));
date->setVisible(false);
date->setVisible(false);
@ -628,46 +556,46 @@ void GamelistView::updateInfoPanel()
std::string metadata;
auto getMetadataValue = [&file, &metadata]() -> std::string {
if (metadata == "md_name")
if (metadata == "name")
return file->metadata.get("name");
else if (metadata == "md_description")
else if (metadata == "description")
return file->metadata.get("desc");
else if (metadata == "md_developer")
else if (metadata == "developer")
return file->metadata.get("developer");
else if (metadata == "md_publisher")
else if (metadata == "publisher")
return file->metadata.get("publisher");
else if (metadata == "md_genre")
else if (metadata == "genre")
return file->metadata.get("genre");
else if (metadata == "md_players")
else if (metadata == "players")
return file->metadata.get("players");
else if (metadata == "md_favorite")
return file->metadata.get("favorite") == "true" ? "Yes" : "No";
else if (metadata == "md_completed")
return file->metadata.get("completed") == "true" ? "Yes" : "No";
else if (metadata == "md_kidgame")
return file->metadata.get("kidgame") == "true" ? "Yes" : "No";
else if (metadata == "md_broken")
return file->metadata.get("broken") == "true" ? "Yes" : "No";
else if (metadata == "md_playcount")
else if (metadata == "favorite")
return file->metadata.get("favorite") == "true" ? "yes" : "no";
else if (metadata == "completed")
return file->metadata.get("completed") == "true" ? "yes" : "no";
else if (metadata == "kidgame")
return file->metadata.get("kidgame") == "true" ? "yes" : "no";
else if (metadata == "broken")
return file->metadata.get("broken") == "true" ? "yes" : "no";
else if (metadata == "playcount")
return file->metadata.get("playcount");
else if (metadata == "md_altemulator")
else if (metadata == "altemulator")
return file->metadata.get("altemulator");
else
return metadata;
};
for (auto& text : mContainerTextComponents) {
metadata = text->getMetadataField();
metadata = text->getThemeMetadata();
if (metadata == "")
continue;
if (metadata == "md_rating") {
if (metadata == "rating") {
text->setValue(mRatingComponents.front()->getRatingValue());
continue;
}
else if (metadata == "md_controller") {
std::string controller =
BadgeComponent::getDisplayName(file->metadata.get("controller"));
else if (metadata == "controller") {
std::string controller {
BadgeComponent::getDisplayName(file->metadata.get("controller"))};
text->setValue(controller == "unknown" ? "" : controller);
continue;
}
@ -676,15 +604,15 @@ void GamelistView::updateInfoPanel()
}
for (auto& text : mTextComponents) {
metadata = text->getMetadataField();
metadata = text->getThemeMetadata();
if (metadata == "")
continue;
if (metadata == "md_rating") {
if (metadata == "rating") {
text->setValue(mRatingComponents.front()->getRatingValue());
continue;
}
else if (metadata == "md_controller") {
else if (metadata == "controller") {
std::string controller =
BadgeComponent::getDisplayName(file->metadata.get("controller"));
text->setValue(controller == "unknown" ? "" : controller);
@ -695,23 +623,20 @@ void GamelistView::updateInfoPanel()
}
for (auto& date : mDateTimeComponents) {
std::string metadata = date->getMetadataField();
std::string metadata = date->getThemeMetadata();
if (metadata == "")
continue;
if (metadata == "md_releasedate") {
if (metadata == "releasedate") {
date->setValue(file->metadata.get("releasedate"));
}
else if (metadata == "md_lastplayed") {
else if (metadata == "lastplayed") {
date->setValue(file->metadata.get("lastplayed"));
date->setDisplayRelative(true);
}
else {
date->setValue("19700101T000000");
}
}
fadingOut = false;
}
std::vector<GuiComponent*> comps;
@ -728,6 +653,10 @@ void GamelistView::updateInfoPanel()
if (image->getScrollHide())
comps.emplace_back(image.get());
}
for (auto& video : mVideoComponents) {
if (video->getScrollHide())
comps.emplace_back(video.get());
}
for (auto& badge : mBadgeComponents) {
if (badge->getScrollHide())
comps.emplace_back(badge.get());
@ -742,15 +671,97 @@ void GamelistView::updateInfoPanel()
}
for (auto it = comps.cbegin(); it != comps.cend(); ++it) {
GuiComponent* comp = *it;
GuiComponent* comp {*it};
if (!fadingOut && !comp->isAnimationPlaying(0)) {
comp->setOpacity(1.0f);
continue;
}
// An animation is playing, then animate if reverse != fadingOut.
// An animation is not playing, then animate if opacity != our target opacity.
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
};
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0.0f : 1.0f))) {
auto func = [comp](float t) { comp->setOpacity(glm::mix(0.0f, 1.0f, t)); };
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
}
}
}
void GamelistView::setGameImage(FileData* file, GuiComponent* comp)
{
std::string path;
for (auto& imageType : comp->getThemeImageTypes()) {
if (imageType == "image") {
path = file->getImagePath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "miximage") {
path = file->getMiximagePath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "marquee") {
path = file->getMarqueePath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "screenshot") {
path = file->getScreenshotPath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "titlescreen") {
path = file->getTitleScreenPath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "cover") {
path = file->getCoverPath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "backcover") {
path = file->getBackCoverPath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "3dbox") {
path = file->get3DBoxPath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "fanart") {
path = file->getFanArtPath();
if (path != "") {
comp->setImage(path);
break;
}
}
else if (imageType == "thumbnail") {
path = file->getThumbnailPath();
if (path != "") {
comp->setImage(path);
break;
}
}
}
// This is needed so the default image is set if no game media was found.
if (path == "" && comp->getThemeImageTypes().size() > 0)
comp->setImage("");
}

View file

@ -23,6 +23,7 @@ public:
// Called when a FileData* is added, has its metadata changed, or is removed.
void onFileChanged(FileData* file, bool reloadGamelist) override;
void onShow() override;
void onTransition() override;
void preloadGamelist() { updateInfoPanel(); }
void launch(FileData* game) override { ViewController::getInstance()->triggerGameLaunch(game); }
@ -41,6 +42,28 @@ public:
}
}
void startViewVideos() override
{
for (auto& video : mVideoComponents)
video->startVideoPlayer();
}
void stopViewVideos() override
{
for (auto& video : mVideoComponents)
video->stopVideoPlayer();
}
void pauseViewVideos() override
{
for (auto& video : mVideoComponents) {
video->pauseVideoPlayer();
}
}
void muteViewVideos() override
{
for (auto& video : mVideoComponents)
video->muteVideoPlayer();
}
const std::shared_ptr<ThemeData> getTheme() const { return mTheme; }
void setTheme(const std::shared_ptr<ThemeData>& theme)
{
@ -57,6 +80,7 @@ public:
private:
void updateInfoPanel();
void setGameImage(FileData* file, GuiComponent* comp);
// Legacy (backward compatibility) functions.
void legacyPopulateFields();

File diff suppressed because it is too large Load diff

View file

@ -9,14 +9,14 @@
#ifndef ES_APP_VIEWS_SYSTEM_VIEW_H
#define ES_APP_VIEWS_SYSTEM_VIEW_H
#include "FileData.h"
#include "GuiComponent.h"
#include "Sound.h"
#include "components/BadgeComponent.h"
#include "SystemData.h"
#include "components/CarouselComponent.h"
#include "components/DateTimeComponent.h"
#include "components/IList.h"
#include "components/GameSelectorComponent.h"
#include "components/LottieComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "components/TextComponent.h"
#include "components/TextListComponent.h"
#include "components/VideoFFmpegComponent.h"
@ -26,106 +26,99 @@
class SystemData;
enum CarouselType : unsigned int {
HORIZONTAL = 0,
VERTICAL = 1,
VERTICAL_WHEEL = 2,
HORIZONTAL_WHEEL = 3
struct SystemViewElements {
std::string name;
std::string fullName;
std::vector<std::unique_ptr<GameSelectorComponent>> gameSelectors;
std::vector<GuiComponent*> legacyExtras;
std::vector<GuiComponent*> children;
std::vector<std::unique_ptr<TextComponent>> gameCountComponents;
std::vector<std::unique_ptr<TextComponent>> textComponents;
std::vector<std::unique_ptr<DateTimeComponent>> dateTimeComponents;
std::vector<std::unique_ptr<ImageComponent>> imageComponents;
std::vector<std::unique_ptr<VideoFFmpegComponent>> videoComponents;
std::vector<std::unique_ptr<LottieComponent>> lottieAnimComponents;
};
struct SystemViewData {
std::shared_ptr<GuiComponent> logo;
std::shared_ptr<GuiComponent> logoPlaceholderText;
std::vector<GuiComponent*> backgroundExtras;
std::vector<std::shared_ptr<TextComponent>> textComponents;
std::vector<std::shared_ptr<DateTimeComponent>> dateTimeComponents;
std::vector<std::shared_ptr<ImageComponent>> imageComponents;
std::vector<std::shared_ptr<VideoFFmpegComponent>> videoComponents;
std::vector<std::shared_ptr<LottieComponent>> lottieAnimComponents;
std::vector<std::shared_ptr<BadgeComponent>> badgeComponents;
std::vector<std::shared_ptr<RatingComponent>> ratingComponents;
std::vector<std::shared_ptr<ScrollableContainer>> containerComponents;
std::vector<std::shared_ptr<TextComponent>> containerTextComponents;
std::vector<std::shared_ptr<TextComponent>> gamelistInfoComponents;
};
struct SystemViewCarousel {
CarouselType type;
glm::vec2 pos;
glm::vec2 size;
glm::vec2 origin;
float logoScale;
float logoRotation;
glm::vec2 logoRotationOrigin;
Alignment logoAlignment;
unsigned int color;
unsigned int colorEnd;
bool colorGradientHorizontal;
int maxLogoCount; // Number of logos shown on the carousel.
glm::vec2 logoSize;
float zIndex;
bool legacyZIndexMode;
};
class SystemView : public IList<SystemViewData, SystemData*>
class SystemView : public GuiComponent
{
public:
SystemView();
~SystemView();
void onShow() override { mShowing = true; }
void onHide() override { mShowing = false; }
void onTransition() override;
void goToSystem(SystemData* system, bool animate);
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void render(const glm::mat4& parentTrans) override;
bool isScrolling() { return mCarousel->isScrolling(); }
void stopScrolling() { mCarousel->stopScrolling(); }
bool isSystemAnimationPlaying(unsigned char slot)
{
return mCarousel->isAnimationPlaying(slot);
}
void finishSystemAnimation(unsigned char slot)
{
finishAnimation(slot);
mCarousel->finishAnimation(slot);
}
CarouselComponent::CarouselType getCarouselType() { return mCarousel->getType(); }
SystemData* getFirstSystem() { return mCarousel->getFirst(); }
void startViewVideos() override
{
for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents)
video->startVideoPlayer();
}
void stopViewVideos() override
{
for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents)
video->stopVideoPlayer();
}
void pauseViewVideos() override
{
for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) {
video->pauseVideoPlayer();
}
}
void muteViewVideos() override
{
for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents)
video->muteVideoPlayer();
}
void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
std::vector<HelpPrompt> getHelpPrompts() override;
HelpStyle getHelpStyle() override;
CarouselType getCarouselType() { return mCarousel.type; }
protected:
void onCursorChanged(const CursorState& state) override;
void onScroll() override
{
NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
}
void onCursorChanged(const CursorState& state);
private:
void populate();
void updateGameCount();
// Get the ThemeElements that make up the SystemView.
void getViewElements(const std::shared_ptr<ThemeData>& theme);
// Populate the system carousel with the legacy values.
void getDefaultElements(void);
void getCarouselFromTheme(const ThemeData::ThemeElement* elem);
void updateGameSelectors();
void legacyApplyTheme(const std::shared_ptr<ThemeData>& theme);
void renderElements(const glm::mat4& parentTrans, bool abovePrimary);
// Render system carousel.
void renderCarousel(const glm::mat4& parentTrans);
// Draw background extras.
void renderExtras(const glm::mat4& parentTrans, float lower, float upper);
void renderFade(const glm::mat4& trans);
std::unique_ptr<CarouselComponent> mCarousel;
std::unique_ptr<TextComponent> mLegacySystemInfo;
std::vector<SystemViewElements> mSystemElements;
SystemViewCarousel mCarousel;
TextComponent mSystemInfo;
// Unit is list index.
float mCamOffset;
float mExtrasCamOffset;
float mExtrasFadeOpacity;
float mFadeOpacity;
int mPreviousScrollVelocity;
bool mUpdatedGameCount;
bool mViewNeedsReload;
bool mShowing;
bool mLegacyMode;
bool mHoldingKey;
bool mNavigated;
};
#endif // ES_APP_VIEWS_SYSTEM_VIEW_H

View file

@ -233,7 +233,7 @@ void ViewController::goToStart(bool playTransition)
Settings::getInstance()->setString("StartupSystem", "");
}
// Get the first system entry.
goToSystemView(getSystemListView()->getFirst(), false);
goToSystemView(getSystemListView()->getFirstSystem(), false);
}
void ViewController::ReloadAndGoToStart()
@ -293,8 +293,8 @@ void ViewController::stopScrolling()
mSystemListView->stopScrolling();
mCurrentView->stopListScrolling();
if (mSystemListView->isAnimationPlaying(0))
mSystemListView->finishAnimation(0);
if (mSystemListView->isSystemAnimationPlaying(0))
mSystemListView->finishSystemAnimation(0);
}
int ViewController::getSystemId(SystemData* system)
@ -330,6 +330,9 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition)
mPreviousView = nullptr;
}
if (mCurrentView != nullptr)
mCurrentView->onTransition();
mPreviousView = mCurrentView;
if (system->isGroupedCustomCollection())
@ -340,7 +343,7 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition)
mSystemViewTransition = true;
auto systemList = getSystemListView();
systemList->setPosition(getSystemId(system) * static_cast<float>(Renderer::getScreenWidth()),
systemList->setPosition(getSystemId(system) * Renderer::getScreenWidth(),
systemList->getPosition().y);
systemList->goToSystem(system, false);
@ -351,19 +354,19 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition)
if (applicationStartup) {
mCamera = glm::translate(mCamera, -mCurrentView->getPosition());
if (Settings::getInstance()->getString("TransitionStyle") == "slide") {
if (getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL ||
getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL_WHEEL)
mCamera[3].y += static_cast<float>(Renderer::getScreenHeight());
if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL ||
getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL)
mCamera[3].y += Renderer::getScreenHeight();
else
mCamera[3].x -= static_cast<float>(Renderer::getScreenWidth());
mCamera[3].x -= Renderer::getScreenWidth();
updateHelpPrompts();
}
else if (Settings::getInstance()->getString("TransitionStyle") == "fade") {
if (getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL ||
getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL_WHEEL)
mCamera[3].y += static_cast<float>(Renderer::getScreenHeight());
if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL ||
getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL)
mCamera[3].y += Renderer::getScreenHeight();
else
mCamera[3].x += static_cast<float>(Renderer::getScreenWidth());
mCamera[3].x += Renderer::getScreenWidth();
}
else {
updateHelpPrompts();
@ -415,6 +418,9 @@ void ViewController::goToGamelist(SystemData* system)
bool wrapLastToFirst = false;
bool slideTransitions = false;
if (mCurrentView != nullptr)
mCurrentView->onTransition();
if (Settings::getInstance()->getString("TransitionStyle") == "slide")
slideTransitions = true;
@ -463,8 +469,8 @@ void ViewController::goToGamelist(SystemData* system)
// Stop any scrolling, animations and camera movements.
if (mState.viewing == SYSTEM_SELECT) {
mSystemListView->stopScrolling();
if (mSystemListView->isAnimationPlaying(0))
mSystemListView->finishAnimation(0);
if (mSystemListView->isSystemAnimationPlaying(0))
mSystemListView->finishSystemAnimation(0);
}
if (slideTransitions)
@ -476,8 +482,7 @@ void ViewController::goToGamelist(SystemData* system)
float offsetX = sysList->getPosition().x;
int sysId = getSystemId(system);
sysList->setPosition(sysId * static_cast<float>(Renderer::getScreenWidth()),
sysList->getPosition().y);
sysList->setPosition(sysId * Renderer::getScreenWidth(), sysList->getPosition().y);
offsetX = sysList->getPosition().x - offsetX;
mCamera[3].x -= offsetX;
}
@ -520,11 +525,11 @@ void ViewController::goToGamelist(SystemData* system)
if (mState.viewing == NOTHING) {
mCamera = glm::translate(mCamera, -mCurrentView->getPosition());
if (Settings::getInstance()->getString("TransitionStyle") == "slide") {
mCamera[3].y -= static_cast<float>(Renderer::getScreenHeight());
mCamera[3].y -= Renderer::getScreenHeight();
updateHelpPrompts();
}
else if (Settings::getInstance()->getString("TransitionStyle") == "fade") {
mCamera[3].y += static_cast<float>(Renderer::getScreenHeight() * 2);
mCamera[3].y += Renderer::getScreenHeight() * 2.0f;
}
else {
updateHelpPrompts();
@ -649,11 +654,6 @@ void ViewController::launch(FileData* game)
return;
}
// If the video view style is used, pause the video currently playing or block the
// video from starting to play if the static image is still shown.
if (mCurrentView)
mCurrentView->onPauseVideo();
// Disable text scrolling and stop any Lottie animations. These will be enabled again in
// FileData upon returning from the game.
mWindow->setAllowTextScrolling(false);
@ -728,11 +728,11 @@ std::shared_ptr<GamelistView> ViewController::getGamelistView(SystemData* system
GamelistViewStyle selectedViewStyle = AUTOMATIC;
std::string viewPreference {Settings::getInstance()->getString("GamelistViewStyle")};
if (viewPreference.compare("basic") == 0)
if (viewPreference == "basic")
selectedViewStyle = BASIC;
if (viewPreference.compare("detailed") == 0)
else if (viewPreference == "detailed")
selectedViewStyle = DETAILED;
if (viewPreference.compare("video") == 0)
else if (viewPreference == "video")
selectedViewStyle = VIDEO;
if (selectedViewStyle == AUTOMATIC) {
@ -773,8 +773,7 @@ std::shared_ptr<GamelistView> ViewController::getGamelistView(SystemData* system
std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
int id {static_cast<int>(std::find(sysVec.cbegin(), sysVec.cend(), system) - sysVec.cbegin())};
view->setPosition(id * static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight() * 2));
view->setPosition(id * Renderer::getScreenWidth(), Renderer::getScreenHeight() * 2.0f);
addChild(view.get());
@ -790,7 +789,7 @@ std::shared_ptr<SystemView> ViewController::getSystemListView()
mSystemListView = std::shared_ptr<SystemView>(new SystemView);
addChild(mSystemListView.get());
mSystemListView->setPosition(0, static_cast<float>(Renderer::getScreenHeight()));
mSystemListView->setPosition(0, Renderer::getScreenHeight());
return mSystemListView;
}
@ -808,7 +807,7 @@ bool ViewController::input(InputConfig* config, Input input)
if (mWindow->getGameLaunchedState()) {
mWindow->setAllowTextScrolling(true);
mWindow->setAllowFileAnimation(true);
mWindow->unsetLaunchedGame();
mWindow->setLaunchedGame(false);
// Filter out the "a" button so the game is not restarted if there was such a button press
// queued when leaving the game.
if (config->isMappedTo("a", input) && input.value != 0)
@ -825,11 +824,14 @@ bool ViewController::input(InputConfig* config, Input input)
mSystemListView->stopScrolling();
// Finish the animation too, so that it doesn't continue
// to play when we've closed the menu.
if (mSystemListView->isAnimationPlaying(0))
mSystemListView->finishAnimation(0);
// Stop the gamelist scrolling as well as it would otherwise
// also continue to run after closing the menu.
if (mSystemListView->isSystemAnimationPlaying(0))
mSystemListView->finishSystemAnimation(0);
// Stop the gamelist scrolling as well as it would otherwise continue to run after
// closing the menu.
mCurrentView->stopListScrolling();
// Pause all videos as they would otherwise continue to play beneath the menu.
mCurrentView->pauseViewVideos();
// Finally, if the camera is currently moving, reset its position.
cancelViewTransitions();
@ -870,9 +872,8 @@ void ViewController::render(const glm::mat4& parentTrans)
// Camera position, position + size.
glm::vec3 viewStart {transInverse[3]};
glm::vec3 viewEnd {std::fabs(trans[3].x) + static_cast<float>(Renderer::getScreenWidth()),
std::fabs(trans[3].y) + static_cast<float>(Renderer::getScreenHeight()),
0.0f};
glm::vec3 viewEnd {std::fabs(trans[3].x) + Renderer::getScreenWidth(),
std::fabs(trans[3].y) + Renderer::getScreenHeight(), 0.0f};
// Keep track of UI mode changes.
UIModeController::getInstance()->monitorUIMode();
@ -904,8 +905,8 @@ void ViewController::render(const glm::mat4& parentTrans)
if (mFadeOpacity) {
unsigned int fadeColor = 0x00000000 | static_cast<unsigned char>(mFadeOpacity * 255);
Renderer::setMatrix(parentTrans);
Renderer::drawRect(0.0f, 0.0f, static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()), fadeColor, fadeColor);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
fadeColor, fadeColor);
}
}
@ -988,7 +989,7 @@ void ViewController::reloadGamelistView(GamelistView* view, bool reloadTheme)
// video player, prevent scrolling of game names and game descriptions and prevent the
// screensaver from starting on schedule.
if (mWindow->getGameLaunchedState())
mWindow->setLaunchedGame();
mWindow->setLaunchedGame(true);
// Redisplay the current view.
if (mCurrentView)

View file

@ -66,6 +66,12 @@ public:
void cancelViewTransitions();
void stopScrolling();
// Basic video controls.
void startViewVideos() override { mCurrentView->startViewVideos(); }
void stopViewVideos() override { mCurrentView->stopViewVideos(); }
void pauseViewVideos() override { mCurrentView->pauseViewVideos(); }
void muteViewVideos() override { mCurrentView->muteViewVideos(); }
void onFileChanged(FileData* file, bool reloadGamelist);
void triggerGameLaunch(FileData* game)
{

View file

@ -36,11 +36,13 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgeComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/CarouselComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GameSelectorComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h
@ -114,6 +116,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgeComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/CarouselComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp

View file

@ -19,7 +19,6 @@
GuiComponent::GuiComponent()
: mWindow {Window::getInstance()}
, mParent {nullptr}
, mOpacity {255}
, mColor {0}
, mSaturation {1.0f}
, mColorShift {0}
@ -31,6 +30,8 @@ GuiComponent::GuiComponent()
, mOrigin {0.0f, 0.0f}
, mRotationOrigin {0.5f, 0.5f}
, mSize {0.0f, 0.0f}
, mOpacity {1.0f}
, mThemeOpacity {1.0f}
, mRotation {0.0f}
, mScale {1.0f}
, mDefaultZIndex {0.0f}
@ -180,7 +181,7 @@ const int GuiComponent::getChildIndex() const
return -1;
}
void GuiComponent::setOpacity(unsigned char opacity)
void GuiComponent::setOpacity(float opacity)
{
if (mOpacity == opacity)
return;
@ -321,9 +322,9 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& element,
unsigned int properties)
{
glm::vec2 scale {getParent() ? getParent()->getSize() :
glm::vec2 {static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())}};
glm::vec2 scale {getParent() ?
getParent()->getSize() :
glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}};
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "");
if (!elem)
@ -341,7 +342,7 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
// Position + size also implies origin.
if ((properties & ORIGIN || (properties & POSITION && properties & ThemeFlags::SIZE)) &&
elem->has("origin")) {
setOrigin(elem->get<glm::vec2>("origin"));
setOrigin(glm::clamp(elem->get<glm::vec2>("origin"), 0.0f, 1.0f));
}
if (properties & ThemeFlags::ROTATION) {
@ -356,10 +357,16 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else
setZIndex(getDefaultZIndex());
if (properties & ThemeFlags::VISIBLE && elem->has("visible"))
setVisible(elem->get<bool>("visible"));
if (properties & ThemeFlags::OPACITY && elem->has("opacity"))
mThemeOpacity = glm::clamp(elem->get<float>("opacity"), 0.0f, 1.0f);
if (properties & ThemeFlags::VISIBLE && elem->has("visible") && !elem->get<bool>("visible"))
mThemeOpacity = 0.0f;
else
setVisible(true);
if (properties && elem->has("gameselector"))
mThemeGameSelector = elem->get<std::string>("gameselector");
}
void GuiComponent::updateHelpPrompts()
@ -386,51 +393,3 @@ void GuiComponent::onHide()
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onHide();
}
void GuiComponent::onStopVideo()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onStopVideo();
}
void GuiComponent::onPauseVideo()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onPauseVideo();
}
void GuiComponent::onUnpauseVideo()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onUnpauseVideo();
}
void GuiComponent::onScreensaverActivate()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onScreensaverActivate();
}
void GuiComponent::onScreensaverDeactivate()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onScreensaverDeactivate();
}
void GuiComponent::onGameLaunchedActivate()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onGameLaunchedActivate();
}
void GuiComponent::onGameLaunchedDeactivate()
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->onGameLaunchedDeactivate();
}
void GuiComponent::topWindow(bool isTop)
{
for (unsigned int i = 0; i < getChildCount(); ++i)
getChild(i)->topWindow(isTop);
}

View file

@ -25,7 +25,7 @@
#define ICONCOLOR_USERMARKED 0x7777FFFF
#define TEXTCOLOR_SCRAPERMARKED 0x992222FF
#define TEXTCOLOR_USERMARKED 0x222299FF
#define DISABLED_OPACITY 80
#define DISABLED_OPACITY 0.314f
class Animation;
class AnimationController;
@ -191,8 +191,8 @@ public:
virtual bool isListScrolling() { return false; }
virtual void stopListScrolling() {}
virtual unsigned char getOpacity() const { return mOpacity; }
virtual void setOpacity(unsigned char opacity);
virtual const float getOpacity() const { return mOpacity; }
virtual void setOpacity(float opacity);
virtual unsigned int getColor() const { return mColor; }
virtual unsigned int getColorShift() const { return mColorShift; }
virtual float getLineSpacing() { return 0.0f; }
@ -207,12 +207,18 @@ public:
virtual void setOriginalColor(unsigned int color) { mColorOriginalValue = color; }
virtual void setChangedColor(unsigned int color) { mColorChangedValue = color; }
virtual void setImage(const std::string& path, bool tile = false) {}
// These functions are used to enable and disable options in menus, i.e. switches and similar.
virtual bool getEnabled() { return mEnabled; }
virtual void setEnabled(bool state) { mEnabled = state; }
std::string getMetadataField() { return mMetadataField; }
void setMetadataField(const std::string& text) { mMetadataField = text; }
const std::string& getThemeSystemdata() { return mThemeSystemdata; }
void setThemeSystemdata(const std::string& text) { mThemeSystemdata = text; }
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; }
virtual std::shared_ptr<Font> getFont() const { return nullptr; }
@ -232,19 +238,17 @@ public:
virtual void onShow();
virtual void onHide();
virtual void onStopVideo();
virtual void onPauseVideo();
virtual void onUnpauseVideo();
virtual bool isVideoPaused() { return false; }
virtual void onTransition() {}
// System view and gamelist view video controls.
virtual void startViewVideos() {}
virtual void stopViewVideos() {}
virtual void pauseViewVideos() {}
virtual void muteViewVideos() {}
// For Lottie animations.
virtual void resetFileAnimation() {};
virtual void onScreensaverActivate();
virtual void onScreensaverDeactivate();
virtual void onGameLaunchedActivate();
virtual void onGameLaunchedDeactivate();
virtual void topWindow(bool isTop);
// Default implementation just handles <pos> and <size> tags as normalized float pairs.
// You probably want to keep this behavior for any derived classes as well as add your own.
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
@ -275,9 +279,11 @@ protected:
GuiComponent* mParent;
std::vector<GuiComponent*> mChildren;
std::string mMetadataField;
std::vector<std::string> mThemeImageTypes;
std::string mThemeSystemdata;
std::string mThemeMetadata;
std::string mThemeGameSelector;
unsigned char mOpacity;
unsigned int mColor;
float mSaturation;
unsigned int mColorShift;
@ -291,6 +297,8 @@ protected:
glm::vec2 mRotationOrigin;
glm::vec2 mSize;
float mOpacity;
float mThemeOpacity;
float mRotation;
float mScale;
float mDefaultZIndex;

View file

@ -24,7 +24,8 @@ HelpStyle::HelpStyle()
iconColorDimmed = 0x777777FF;
entrySpacing = 16.0f;
iconTextSpacing = 8.0f;
textStyle = "uppercase";
letterCase = "uppercase";
opacity = 1.0f;
if (FONT_SIZE_SMALL != 0)
font = Font::get(FONT_SIZE_SMALL);
@ -40,8 +41,7 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
if (elem->has("pos"))
position = elem->get<glm::vec2>("pos") *
glm::vec2 {static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())};
glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()};
if (elem->has("origin"))
origin = elem->get<glm::vec2>("origin");
@ -71,8 +71,11 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
if (elem->has("iconTextSpacing"))
iconTextSpacing = elem->get<float>("iconTextSpacing");
if (elem->has("textStyle"))
textStyle = elem->get<std::string>("textStyle");
if (elem->has("letterCase"))
letterCase = elem->get<std::string>("letterCase");
if (elem->has("opacity"))
opacity = glm::clamp(elem->get<float>("opacity"), 0.2f, 1.0f);
// Load custom button icons.
// The names may look a bit strange when combined with the PREFIX string "button_" but it's

View file

@ -28,7 +28,8 @@ struct HelpStyle {
std::shared_ptr<Font> font;
float entrySpacing;
float iconTextSpacing;
std::string textStyle;
float opacity;
std::string letterCase;
struct CustomButtonIcons {

View file

@ -100,7 +100,7 @@ void Settings::setDefaults()
mBoolMap["ScrapeMarquees"] = {true, true};
mBoolMap["Scrape3DBoxes"] = {true, true};
mBoolMap["ScrapePhysicalMedia"] = {true, true};
mBoolMap["ScrapeFanArt"] = {false, false};
mBoolMap["ScrapeFanArt"] = {true, true};
mStringMap["MiximageResolution"] = {"1280x960", "1280x960"};
mStringMap["MiximageScreenshotScaling"] = {"sharp", "sharp"};
@ -192,13 +192,11 @@ void Settings::setDefaults()
mBoolMap["GamelistFilters"] = {true, true};
mBoolMap["QuickSystemSelect"] = {true, true};
mBoolMap["ShowHelpPrompts"] = {true, true};
mBoolMap["PlayVideosImmediately"] = {false, false};
mBoolMap["EnableMenuKidMode"] = {false, false};
// Sound settings.
mIntMap["SoundVolumeNavigation"] = {70, 70};
mIntMap["SoundVolumeVideos"] = {80, 80};
mBoolMap["GamelistVideoAudio"] = {true, true};
mBoolMap["ViewsVideoAudio"] = {true, true};
mBoolMap["MediaViewerVideoAudio"] = {true, true};
mBoolMap["ScreensaverVideoAudio"] = {true, true};
mBoolMap["NavigationSounds"] = {true, true};
@ -243,6 +241,7 @@ void Settings::setDefaults()
mBoolMap["DisableComposition"] = {true, true};
#endif
mBoolMap["DisplayGPUStatistics"] = {false, false};
mBoolMap["EnableMenuKidMode"] = {false, false};
// macOS requires root privileges to reboot and power off so it doesn't make much
// sense to enable this setting and menu entry for that operating system.
#if !defined(__APPLE__)

View file

@ -44,6 +44,13 @@ std::vector<std::string> ThemeData::sLegacySupportedFeatures {
{"z-index"},
{"visible"}};
std::vector<std::string> ThemeData::sLegacyElements {
{"showSnapshotNoVideo"},
{"showSnapshotDelay"},
{"forceUppercase"},
{"alignment"},
{"logoAlignment"}};
std::vector<std::pair<std::string, std::string>> ThemeData::sSupportedAspectRatios {
{"16:9", "16:9"},
{"16:9_vertical", "16:9 vertical"},
@ -81,12 +88,15 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"rotationOrigin", NORMALIZED_PAIR},
{"path", PATH},
{"default", PATH},
{"imageType", STRING},
{"gameselector", STRING},
{"tile", BOOLEAN},
{"metadata", STRING},
{"interpolation", STRING},
{"color", COLOR},
{"colorEnd", COLOR},
{"gradientType", STRING},
{"scrollFadeIn", BOOLEAN},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"video",
@ -94,14 +104,19 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"size", NORMALIZED_PAIR},
{"maxSize", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"rotation", FLOAT},
{"rotationOrigin", NORMALIZED_PAIR},
{"path", PATH},
{"default", PATH},
{"defaultImage", PATH},
{"imageMetadata", STRING},
{"imageType", STRING},
{"gameselector", STRING},
{"audio", BOOLEAN},
{"interpolation", STRING},
{"pillarboxes", BOOLEAN},
{"scanlines", BOOLEAN},
{"delay", FLOAT},
{"fadeInTime", FLOAT},
{"scrollFadeIn", BOOLEAN},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT},
{"showSnapshotNoVideo", BOOLEAN}, // For backward compatibility with legacy themes.
@ -116,6 +131,7 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"speed", FLOAT},
{"direction", STRING},
{"keepAspectRatio", BOOLEAN},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"badges",
@ -124,16 +140,18 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"origin", NORMALIZED_PAIR},
{"rotation", FLOAT},
{"rotationOrigin", NORMALIZED_PAIR},
{"alignment", STRING},
{"horizontalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"direction", STRING},
{"lines", FLOAT},
{"itemsPerLine", FLOAT},
{"lines", UNSIGNED_INTEGER},
{"itemsPerLine", UNSIGNED_INTEGER},
{"itemMargin", NORMALIZED_PAIR},
{"slots", STRING},
{"controllerPos", NORMALIZED_PAIR},
{"controllerSize", FLOAT},
{"customBadgeIcon", PATH},
{"customControllerIcon", PATH},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"text",
@ -143,15 +161,24 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"rotation", FLOAT},
{"rotationOrigin", NORMALIZED_PAIR},
{"text", STRING},
{"systemdata", STRING},
{"metadata", STRING},
{"gameselector", STRING},
{"container", BOOLEAN},
{"containerScrollSpeed", FLOAT},
{"containerStartDelay", FLOAT},
{"containerResetDelay", FLOAT},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"alignment", STRING},
{"horizontalAlignment", STRING},
{"verticalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"color", COLOR},
{"backgroundColor", COLOR},
{"forceUppercase", BOOLEAN},
{"letterCase", STRING},
{"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
{"lineSpacing", FLOAT},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"datetime",
@ -161,15 +188,20 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"rotation", FLOAT},
{"rotationOrigin", NORMALIZED_PAIR},
{"metadata", STRING},
{"gameselector", STRING},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"alignment", STRING},
{"horizontalAlignment", STRING},
{"verticalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"color", COLOR},
{"backgroundColor", COLOR},
{"forceUppercase", BOOLEAN},
{"letterCase", STRING},
{"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
{"lineSpacing", FLOAT},
{"format", STRING},
{"displayRelative", BOOLEAN},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"gamelistinfo",
@ -182,7 +214,10 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"fontSize", FLOAT},
{"color", COLOR},
{"backgroundColor", COLOR},
{"alignment", STRING},
{"horizontalAlignment", STRING},
{"verticalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"rating",
@ -194,6 +229,8 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"color", COLOR},
{"filledPath", PATH},
{"unfilledPath", PATH},
{"opacity", FLOAT},
{"visible", BOOLEAN},
{"zIndex", FLOAT}}},
{"carousel",
{{"type", STRING},
@ -203,14 +240,25 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"color", COLOR},
{"colorEnd", COLOR},
{"gradientType", STRING},
{"logo", PATH},
{"defaultLogo", PATH},
{"logoSize", NORMALIZED_PAIR},
{"logoScale", FLOAT},
{"logoRotation", FLOAT},
{"logoRotationOrigin", NORMALIZED_PAIR},
{"logoSize", NORMALIZED_PAIR},
{"logoAlignment", STRING},
{"maxLogoCount", FLOAT},
{"logoHorizontalAlignment", STRING},
{"logoVerticalAlignment", STRING},
{"logoAlignment", STRING}, // For backward compatibility with legacy themes.
{"maxLogoCount", UNSIGNED_INTEGER},
{"text", STRING},
{"textColor", COLOR},
{"textBackgroundColor", COLOR},
{"letterCase", STRING},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"lineSpacing", FLOAT},
{"zIndex", FLOAT},
{"legacyZIndexMode", STRING}}},
{"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes.
{"textlist",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
@ -227,13 +275,17 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"secondaryColor", COLOR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"scrollHide", BOOLEAN},
{"scrollSound", PATH}, // For backward compatibility with legacy themes.
{"alignment", STRING},
{"horizontalAlignment", STRING},
{"alignment", STRING}, // For backward compatibility with legacy themes.
{"horizontalMargin", FLOAT},
{"forceUppercase", BOOLEAN},
{"letterCase", STRING},
{"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes.
{"lineSpacing", FLOAT},
{"zIndex", FLOAT}}},
{"gameselector",
{{"selection", STRING},
{"gameCount", UNSIGNED_INTEGER}}},
{"helpsystem",
{{"pos", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
@ -245,7 +297,9 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"fontSize", FLOAT},
{"entrySpacing", FLOAT},
{"iconTextSpacing", FLOAT},
{"textStyle", STRING},
{"letterCase", STRING},
{"textStyle", STRING}, // For backward compatibility with legacy themes.
{"opacity", FLOAT},
{"customButtonIcon", PATH}}},
{"sound",
{{"path", PATH}}},
@ -545,29 +599,6 @@ const std::string ThemeData::getAspectRatioLabel(const std::string& aspectRatio)
return "invalid ratio";
}
const std::shared_ptr<ThemeData> ThemeData::getDefault()
{
static std::shared_ptr<ThemeData> theme = nullptr;
if (theme == nullptr) {
theme = std::shared_ptr<ThemeData>(new ThemeData());
const std::string path {Utils::FileSystem::getHomePath() +
"/.emulationstation/es_theme_default.xml"};
if (Utils::FileSystem::exists(path)) {
try {
std::map<std::string, std::string> emptyMap;
theme->loadFile(emptyMap, path);
}
catch (ThemeException& e) {
LOG(LogError) << e.what();
theme = std::shared_ptr<ThemeData>(new ThemeData()); // Reset to empty.
}
}
}
return theme;
}
unsigned int ThemeData::getHexColor(const std::string& str)
{
ThemeException error;
@ -1076,10 +1107,15 @@ void ThemeData::parseElement(const pugi::xml_node& root,
std::string nodeName = node.name();
if (!mLegacyTheme && element.type == "video") {
if (nodeName == "showSnapshotNoVideo" || nodeName == "showSnapshotDelay")
throw error << ": Legacy <" << nodeName
<< "> property found for non-legacy theme set";
// Strictly enforce removal of legacy elements 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) {
throw error << ": Legacy <" << nodeName
<< "> property found for non-legacy theme set";
}
}
}
// If an attribute exists, then replace nodeName with its name.
@ -1150,10 +1186,22 @@ void ThemeData::parseElement(const pugi::xml_node& root,
if (!ResourceManager::getInstance().fileExists(path)) {
std::stringstream ss;
LOG(LogWarning)
<< error.message << ": Couldn't find file \"" << node.text().get() << "\" "
<< ((node.text().get() != path) ? "which resolves to \"" + path + "\"" :
"");
// For explicits paths, print a warning if the file couldn't be found, but
// only print a debug message if it was set using a variable.
if (str == node.text().as_string()) {
LOG(LogWarning)
<< error.message << ": Couldn't find file \"" << node.text().get()
<< "\" "
<< ((node.text().get() != path) ? "which resolves to \"" + path + "\"" :
"");
}
else {
LOG(LogDebug)
<< error.message << ": Couldn't find file \"" << node.text().get()
<< "\" "
<< ((node.text().get() != path) ? "which resolves to \"" + path + "\"" :
"");
}
}
element.properties[nodeName] = path;
break;
@ -1167,6 +1215,11 @@ void ThemeData::parseElement(const pugi::xml_node& root,
}
break;
}
case UNSIGNED_INTEGER: {
unsigned int integerVal {static_cast<unsigned int>(strtoul(str.c_str(), 0, 0))};
element.properties[node.name()] = integerVal;
break;
}
case FLOAT: {
float floatVal {static_cast<float>(strtod(str.c_str(), 0))};
element.properties[node.name()] = floatVal;
@ -1175,9 +1228,11 @@ void ThemeData::parseElement(const pugi::xml_node& root,
case BOOLEAN: {
bool boolVal = false;
// Only look at the first character.
if (str.front() == '1' || str.front() == 't' || str.front() == 'T' ||
str.front() == 'y' || str.front() == 'Y')
boolVal = true;
if (str.size() > 0) {
if (str.front() == '1' || str.front() == 't' || str.front() == 'T' ||
str.front() == 'y' || str.front() == 'Y')
boolVal = true;
}
element.properties[node.name()] = boolVal;
break;

View file

@ -50,12 +50,14 @@ namespace ThemeFlags
ALIGNMENT = 0x00000080,
TEXT = 0x00000100,
METADATA = 0x00000200,
FORCE_UPPERCASE = 0x00000400,
LINE_SPACING = 0x00000800,
DELAY = 0x00001000,
Z_INDEX = 0x00002000,
ROTATION = 0x00004000,
VISIBLE = 0x00008000,
LETTER_CASE = 0x00000400,
FORCE_UPPERCASE = 0x00000800, // For backward compatibility with legacy themes.
LINE_SPACING = 0x00001000,
DELAY = 0x00002000,
Z_INDEX = 0x00004000,
ROTATION = 0x00008000,
OPACITY = 0x00010000,
VISIBLE = 0x00020000,
ALL = 0xFFFFFFFF
};
// clang-format on
@ -215,6 +217,7 @@ public:
PATH,
STRING,
COLOR,
UNSIGNED_INTEGER,
FLOAT,
BOOLEAN
};
@ -222,7 +225,6 @@ public:
std::map<std::string, std::string> mVariables;
private:
static const std::shared_ptr<ThemeData> getDefault();
unsigned int getHexColor(const std::string& str);
std::string resolvePlaceholders(const std::string& in);
@ -239,14 +241,15 @@ private:
const std::map<std::string, ElementPropertyType>& typeMap,
ThemeElement& element);
static std::map<std::string, std::map<std::string, ElementPropertyType>> sElementMap;
static std::map<std::string, std::map<std::string, std::string>> sPropertyAttributeMap;
static std::vector<std::string> sLegacySupportedFeatures;
static std::vector<std::string> sLegacySupportedViews;
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::pair<std::string, std::string>> sSupportedAspectRatios;
static std::map<std::string, std::map<std::string, std::string>> sPropertyAttributeMap;
static std::map<std::string, std::map<std::string, ElementPropertyType>> sElementMap;
static inline std::map<std::string, ThemeSet> mThemeSets;
std::map<std::string, ThemeData::ThemeSet>::iterator mCurrentThemeSet;

View file

@ -29,7 +29,7 @@ Window::Window() noexcept
, mMediaViewer {nullptr}
, mLaunchScreen {nullptr}
, mInfoPopup {nullptr}
, mListScrollOpacity {0}
, mListScrollOpacity {0.0f}
, mFrameTimeElapsed {0}
, mFrameCountElapsed {0}
, mAverageDeltaTime {10}
@ -75,10 +75,6 @@ Window* Window::getInstance()
void Window::pushGui(GuiComponent* gui)
{
if (mGuiStack.size() > 0) {
auto& top = mGuiStack.back();
top->topWindow(false);
}
mGuiStack.push_back(gui);
gui->updateHelpPrompts();
}
@ -90,10 +86,8 @@ void Window::removeGui(GuiComponent* gui)
it = mGuiStack.erase(it);
// We just popped the stack and the stack is not empty.
if (it == mGuiStack.cend() && mGuiStack.size()) {
if (it == mGuiStack.cend() && mGuiStack.size())
mGuiStack.back()->updateHelpPrompts();
mGuiStack.back()->topWindow(true);
}
return;
}
@ -121,7 +115,7 @@ bool Window::init()
mHelp = new HelpComponent;
mBackgroundOverlay = new ImageComponent;
mBackgroundOverlayOpacity = 0;
mBackgroundOverlayOpacity = 0.0f;
// Keep a reference to the default fonts, so they don't keep getting destroyed/recreated.
if (mDefaultFonts.empty()) {
@ -131,8 +125,7 @@ bool Window::init()
}
mBackgroundOverlay->setImage(":/graphics/screen_gradient.png");
mBackgroundOverlay->setResize(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()));
mBackgroundOverlay->setResize(Renderer::getScreenWidth(), Renderer::getScreenHeight());
#if defined(USE_OPENGL_21)
mPostprocessedBackground = TextureResource::get("");
@ -380,7 +373,7 @@ void Window::update(int deltaTime)
// will be moved. This is required as theme set changes always makes a transition to
// the system view. If we wouldn't make this update, the camera movement would take
// place once the menu has been closed.
if (mChangedThemeSet && mGuiStack.size() > 1) {
if (mChangedThemeSet) {
mGuiStack.front()->update(deltaTime);
mChangedThemeSet = false;
}
@ -443,7 +436,7 @@ void Window::render()
// a new cached background has been generated.
if (mGuiStack.size() > 1 && mCachedBackground) {
if ((Settings::getInstance()->getString("MenuOpeningEffect") == "scale-up" &&
mBackgroundOverlayOpacity == 255) ||
mBackgroundOverlayOpacity == 1.0f) ||
Settings::getInstance()->getString("MenuOpeningEffect") != "scale-up")
renderBottom = false;
}
@ -460,8 +453,9 @@ void Window::render()
#if (CLOCK_BACKGROUND_CREATION)
const auto backgroundStartTime = std::chrono::system_clock::now();
#endif
unsigned char* processedTexture =
new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * 4];
unsigned char* processedTexture {
new unsigned char[static_cast<size_t>(Renderer::getScreenWidth()) *
static_cast<size_t>(Renderer::getScreenHeight()) * 4]};
// De-focus the background using multiple passes of gaussian blur, with the number
// of iterations relative to the screen resolution.
@ -502,18 +496,19 @@ void Window::render()
}
mPostprocessedBackground->initFromPixels(
processedTexture, Renderer::getScreenWidth(), Renderer::getScreenHeight());
processedTexture, static_cast<size_t>(Renderer::getScreenWidth()),
static_cast<size_t>(Renderer::getScreenHeight()));
mBackgroundOverlay->setImage(mPostprocessedBackground);
// The following is done to avoid fading in if the cached image was
// invalidated (rather than the menu being opened).
if (mInvalidatedCachedBackground) {
mBackgroundOverlayOpacity = 255;
mBackgroundOverlayOpacity = 1.0f;
mInvalidatedCachedBackground = false;
}
else {
mBackgroundOverlayOpacity = 25;
mBackgroundOverlayOpacity = 0.1f;
}
delete[] processedTexture;
@ -530,8 +525,9 @@ void Window::render()
// Fade in the cached background if the menu opening effect has been set to scale-up.
if (Settings::getInstance()->getString("MenuOpeningEffect") == "scale-up") {
mBackgroundOverlay->setOpacity(mBackgroundOverlayOpacity);
if (mBackgroundOverlayOpacity < 255)
mBackgroundOverlayOpacity = glm::clamp(mBackgroundOverlayOpacity + 30, 0, 255);
if (mBackgroundOverlayOpacity < 1.0f)
mBackgroundOverlayOpacity =
glm::clamp(mBackgroundOverlayOpacity + 0.118f, 0.0f, 1.0f);
}
#endif // USE_OPENGL_21
@ -558,18 +554,19 @@ void Window::render()
}
// Render the quick list scrolling overlay, which is triggered in IList.
if (mListScrollOpacity != 0) {
if (mListScrollOpacity != 0.0f) {
Renderer::setMatrix(Renderer::getIdentity());
Renderer::drawRect(0.0f, 0.0f, static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()),
0x00000000 | mListScrollOpacity, 0x00000000 | mListScrollOpacity);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
0x00000000 | static_cast<unsigned char>(mListScrollOpacity * 255.0f),
0x00000000 | static_cast<unsigned char>(mListScrollOpacity * 255.0f));
glm::vec2 offset {mListScrollFont->sizeText(mListScrollText)};
offset.x = (Renderer::getScreenWidth() - offset.x) * 0.5f;
offset.y = (Renderer::getScreenHeight() - offset.y) * 0.5f;
TextCache* cache = mListScrollFont->buildTextCache(mListScrollText, offset.x, offset.y,
0xFFFFFF00 | mListScrollOpacity);
TextCache* cache {mListScrollFont->buildTextCache(
mListScrollText, offset.x, offset.y,
0xFFFFFF00 | static_cast<unsigned char>(mListScrollOpacity * 255.0f))};
mListScrollFont->renderTextCache(cache);
delete cache;
}
@ -625,8 +622,8 @@ void Window::renderLoadingScreen(std::string text)
{
glm::mat4 trans {Renderer::getIdentity()};
Renderer::setMatrix(trans);
Renderer::drawRect(0.0f, 0.0f, static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
0x000000FF, 0x000000FF);
ImageComponent splash(true);
splash.setImage(":/graphics/splash.svg");
@ -638,8 +635,8 @@ void Window::renderLoadingScreen(std::string text)
auto& font = mDefaultFonts.at(1);
TextCache* cache = font->buildTextCache(text, 0.0f, 0.0f, 0x656565FF);
float x = std::round((Renderer::getScreenWidth() - cache->metrics.size.x) / 2.0f);
float y = std::round(Renderer::getScreenHeight() * 0.835f);
float x {std::round((Renderer::getScreenWidth() - cache->metrics.size.x) / 2.0f)};
float y {std::round(Renderer::getScreenHeight() * 0.835f)};
trans = glm::translate(trans, glm::vec3 {x, y, 0.0f});
Renderer::setMatrix(trans);
font->renderTextCache(cache);
@ -648,9 +645,9 @@ void Window::renderLoadingScreen(std::string text)
Renderer::swapBuffers();
}
void Window::renderListScrollOverlay(unsigned char opacity, const std::string& text)
void Window::renderListScrollOverlay(const float opacity, const std::string& text)
{
mListScrollOpacity = static_cast<unsigned char>(opacity * 0.6f);
mListScrollOpacity = opacity * 0.6f;
mListScrollText = text;
}
@ -749,10 +746,6 @@ void Window::stopInfoPopup()
void Window::startScreensaver()
{
if (mScreensaver && !mRenderScreensaver) {
// Tell the GUI components the screensaver is starting.
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it)
(*it)->onScreensaverActivate();
setAllowTextScrolling(false);
setAllowFileAnimation(false);
mScreensaver->startScreensaver(true);
@ -768,14 +761,6 @@ bool Window::stopScreensaver()
setAllowTextScrolling(true);
setAllowFileAnimation(true);
// Tell the GUI components the screensaver has stopped.
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it) {
(*it)->onScreensaverDeactivate();
// If the menu is open, pause the video so it won't start playing beneath the menu.
if (mGuiStack.front() != mGuiStack.back())
(*it)->onPauseVideo();
}
return true;
}
@ -827,10 +812,6 @@ void Window::closeLaunchScreen()
mRenderLaunchScreen = false;
}
void Window::increaseVideoPlayerCount() { ++mVideoPlayerCount; }
void Window::decreaseVideoPlayerCount() { --mVideoPlayerCount; }
int Window::getVideoPlayerCount()
{
int videoPlayerCount;
@ -838,24 +819,6 @@ int Window::getVideoPlayerCount()
return videoPlayerCount;
}
void Window::setLaunchedGame()
{
// Tell the GUI components that a game has been launched.
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it)
(*it)->onGameLaunchedActivate();
mGameLaunchedState = true;
}
void Window::unsetLaunchedGame()
{
// Tell the GUI components that the user is back in ES-DE again.
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it)
(*it)->onGameLaunchedDeactivate();
mGameLaunchedState = false;
}
void Window::invalidateCachedBackground()
{
mCachedBackground = false;

View file

@ -100,7 +100,7 @@ public:
void renderLoadingScreen(std::string text);
// The list scroll overlay is triggered from IList when the highest scrolling tier is reached.
void renderListScrollOverlay(unsigned char opacity, const std::string& text);
void renderListScrollOverlay(const float opacity, const std::string& text);
void renderHelpPromptsEarly(); // Used to render HelpPrompts before a fade.
void setHelpPrompts(const std::vector<HelpPrompt>& prompts, const HelpStyle& style);
@ -130,12 +130,11 @@ public:
void setLaunchScreen(GuiLaunchScreen* launchScreen) { mLaunchScreen = launchScreen; }
bool isLaunchScreenDisplayed() { return mRenderLaunchScreen; }
void increaseVideoPlayerCount();
void decreaseVideoPlayerCount();
void increaseVideoPlayerCount() { ++mVideoPlayerCount; }
void decreaseVideoPlayerCount() { --mVideoPlayerCount; }
int getVideoPlayerCount();
void setLaunchedGame();
void unsetLaunchedGame();
void setLaunchedGame(bool state) { mGameLaunchedState = state; }
void invalidateCachedBackground();
bool isInvalidatingCachedBackground() { return mInvalidateCacheTimer > 0; }
@ -162,7 +161,7 @@ private:
HelpComponent* mHelp;
ImageComponent* mBackgroundOverlay;
unsigned char mBackgroundOverlayOpacity;
float mBackgroundOverlayOpacity;
std::vector<GuiComponent*> mGuiStack;
std::vector<std::shared_ptr<Font>> mDefaultFonts;
std::unique_ptr<TextCache> mFrameDataText;
@ -180,7 +179,7 @@ private:
std::string mListScrollText;
std::shared_ptr<Font> mListScrollFont;
unsigned char mListScrollOpacity;
float mListScrollOpacity;
int mFrameTimeElapsed;
int mFrameCountElapsed;

View file

@ -104,7 +104,6 @@ void BadgeComponent::setBadges(const std::vector<BadgeInfo>& badges)
[badge](FlexboxComponent::FlexboxItem item) { return item.label == badge.badgeType; });
if (it != mFlexboxItems.end()) {
// Don't show the alternative emulator badge if the corresponding setting has been
// disabled.
if (badge.badgeType == "altemulator" &&
@ -169,16 +168,16 @@ const std::string BadgeComponent::getDisplayName(const std::string& shortName)
void BadgeComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible())
if (!isVisible() || mThemeOpacity == 0.0f)
return;
if (mOpacity == 255) {
if (mOpacity * mThemeOpacity == 1.0f) {
mFlexboxComponent.render(parentTrans);
}
else {
mFlexboxComponent.setOpacity(mOpacity);
mFlexboxComponent.setOpacity(mOpacity * mThemeOpacity);
mFlexboxComponent.render(parentTrans);
mFlexboxComponent.setOpacity(255);
mFlexboxComponent.setOpacity(1.0f);
}
}
@ -195,7 +194,19 @@ void BadgeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (!elem)
return;
if (elem->has("alignment")) {
if (elem->has("horizontalAlignment")) {
const std::string horizontalAlignment {elem->get<std::string>("horizontalAlignment")};
if (horizontalAlignment != "left" && horizontalAlignment != "right") {
LOG(LogWarning)
<< "BadgeComponent: Invalid theme configuration, <horizontalAlignment> set to \""
<< horizontalAlignment << "\"";
}
else {
mFlexboxComponent.setAlignment(horizontalAlignment);
}
}
// Legacy themes only.
else if (elem->has("alignment")) {
const std::string alignment {elem->get<std::string>("alignment")};
if (alignment != "left" && alignment != "right") {
LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, <alignment> set to \""
@ -218,25 +229,25 @@ void BadgeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
}
if (elem->has("lines")) {
const float lines {elem->get<float>("lines")};
if (lines < 1.0f || lines > 10.0f) {
const unsigned int lines {elem->get<unsigned int>("lines")};
if (lines < 1 || lines > 10) {
LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, <lines> set to \""
<< lines << "\"";
}
else {
mFlexboxComponent.setLines(static_cast<unsigned int>(lines));
mFlexboxComponent.setLines(lines);
}
}
if (elem->has("itemsPerLine")) {
const float itemsPerLine {elem->get<float>("itemsPerLine")};
if (itemsPerLine < 1.0f || itemsPerLine > 10.0f) {
const unsigned int itemsPerLine {elem->get<unsigned int>("itemsPerLine")};
if (itemsPerLine < 1 || itemsPerLine > 10) {
LOG(LogWarning)
<< "BadgeComponent: Invalid theme configuration, <itemsPerLine> set to \""
<< itemsPerLine << "\"";
}
else {
mFlexboxComponent.setItemsPerLine(static_cast<unsigned int>(itemsPerLine));
mFlexboxComponent.setItemsPerLine(itemsPerLine);
}
}

View file

@ -45,10 +45,10 @@ void BusyComponent::onSizeChanged()
if (mSize.x == 0.0f || mSize.y == 0.0f)
return;
const float middleSpacerWidth = 0.01f * Renderer::getScreenWidth();
const float textHeight = mText->getFont()->getLetterHeight();
const float middleSpacerWidth {0.01f * Renderer::getScreenWidth()};
const float textHeight {mText->getFont()->getLetterHeight()};
mText->setSize(0, textHeight);
const float textWidth = mText->getSize().x + (4.0f * Renderer::getScreenWidthModifier());
const float textWidth {mText->getSize().x + (4.0f * Renderer::getScreenWidthModifier())};
mGrid.setColWidthPerc(1, textHeight / mSize.x); // Animation is square.
mGrid.setColWidthPerc(2, middleSpacerWidth / mSize.x);

View file

@ -0,0 +1,549 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// CarouselComponent.cpp
//
// Carousel.
//
#include "components/CarouselComponent.h"
#include "Log.h"
#include "animations/LambdaAnimation.h"
namespace
{
// Buffer values for scrolling velocity (left, stopped, right).
const int logoBuffersLeft[] {-5, -2, -1};
const int logoBuffersRight[] {1, 2, 5};
} // namespace
CarouselComponent::CarouselComponent()
: IList<CarouselElement, SystemData*> {LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP}
, mCamOffset {0.0f}
, mPreviousScrollVelocity {0}
, mType {HORIZONTAL}
, mFont {Font::get(FONT_SIZE_LARGE)}
, mTextColor {0x000000FF}
, mTextBackgroundColor {0xFFFFFF00}
, mLineSpacing {1.5f}
, mLogoHorizontalAlignment {ALIGN_CENTER}
, mLogoVerticalAlignment {ALIGN_CENTER}
, mMaxLogoCount {3}
, mLogoSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f}
, mLogoScale {1.2f}
, mLogoRotation {7.5f}
, mLogoRotationOrigin {-3.0f, 0.5f}
, mCarouselColor {0}
, mCarouselColorEnd {0}
, mColorGradientHorizontal {true}
{
}
void CarouselComponent::addEntry(const std::shared_ptr<ThemeData>& theme,
Entry& entry,
bool legacyMode)
{
// Make logo.
if (legacyMode) {
const ThemeData::ThemeElement* logoElem {
theme->getElement("system", "image_logo", "image")};
if (logoElem) {
std::string path;
if (logoElem->has("path"))
path = logoElem->get<std::string>("path");
std::string defaultPath {
logoElem->has("default") ? logoElem->get<std::string>("default") : ""};
if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) ||
(!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) {
auto logo = std::make_shared<ImageComponent>(false, false);
logo->setMaxSize(glm::round(mLogoSize * mLogoScale));
logo->applyTheme(theme, "system", "image_logo",
ThemeFlags::PATH | ThemeFlags::COLOR);
logo->setRotateByTargetSize(true);
entry.data.logo = logo;
}
}
}
else {
if (entry.data.logoPath != "" &&
ResourceManager::getInstance().fileExists(entry.data.logoPath)) {
auto logo = std::make_shared<ImageComponent>(false, false);
logo->setImage(entry.data.logoPath);
logo->setMaxSize(glm::round(mLogoSize * mLogoScale));
logo->applyTheme(theme, "system", "", ThemeFlags::ALL);
logo->setRotateByTargetSize(true);
entry.data.logo = logo;
}
else if (entry.data.defaultLogoPath != "" &&
ResourceManager::getInstance().fileExists(entry.data.defaultLogoPath)) {
auto defaultLogo = std::make_shared<ImageComponent>(false, false);
defaultLogo->setImage(entry.data.defaultLogoPath);
defaultLogo->setMaxSize(glm::round(mLogoSize * mLogoScale));
defaultLogo->applyTheme(theme, "system", "", ThemeFlags::ALL);
defaultLogo->setRotateByTargetSize(true);
entry.data.logo = defaultLogo;
}
}
if (!entry.data.logo) {
// If no logo image is present, add logo text as fallback.
auto text = std::make_shared<TextComponent>(entry.name, mFont, 0x000000FF, ALIGN_CENTER);
text->setSize(mLogoSize * mLogoScale);
if (legacyMode) {
text->applyTheme(theme, "system", "text_logoText",
ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR |
ThemeFlags::LETTER_CASE | ThemeFlags::FORCE_UPPERCASE |
ThemeFlags::LINE_SPACING | ThemeFlags::TEXT);
}
if (!legacyMode) {
text->setLineSpacing(mLineSpacing);
if (mText != "")
text->setValue(mText);
text->setColor(mTextColor);
text->setBackgroundColor(mTextBackgroundColor);
text->setRenderBackground(true);
}
entry.data.logo = text;
text->setHorizontalAlignment(mLogoHorizontalAlignment);
text->setVerticalAlignment(mLogoVerticalAlignment);
}
// Set origin for the logos based on their alignment so they line up properly.
if (mLogoHorizontalAlignment == ALIGN_LEFT)
entry.data.logo->setOrigin(0, 0.5);
else if (mLogoHorizontalAlignment == ALIGN_RIGHT)
entry.data.logo->setOrigin(1.0, 0.5);
else
entry.data.logo->setOrigin(0.5, 0.5);
if (mLogoVerticalAlignment == ALIGN_TOP)
entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 0);
else if (mLogoVerticalAlignment == ALIGN_BOTTOM)
entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 1);
else
entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 0.5);
glm::vec2 denormalized {mLogoSize * entry.data.logo->getOrigin()};
entry.data.logo->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f});
add(entry);
}
bool CarouselComponent::input(InputConfig* config, Input input)
{
if (input.value != 0) {
switch (mType) {
case VERTICAL:
case VERTICAL_WHEEL:
if (config->isMappedLike("up", input)) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
listInput(-1);
return true;
}
if (config->isMappedLike("down", input)) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
listInput(1);
return true;
}
break;
case HORIZONTAL:
case HORIZONTAL_WHEEL:
default:
if (config->isMappedLike("left", input)) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
listInput(-1);
return true;
}
if (config->isMappedLike("right", input)) {
if (mCancelTransitionsCallback)
mCancelTransitionsCallback();
listInput(1);
return true;
}
break;
}
}
else {
if (config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
config->isMappedLike("up", input) || config->isMappedLike("down", input)) {
listInput(0);
}
}
return GuiComponent::input(config, input);
}
void CarouselComponent::update(int deltaTime)
{
listUpdate(deltaTime);
GuiComponent::update(deltaTime);
}
void CarouselComponent::render(const glm::mat4& parentTrans)
{
glm::mat4 carouselTrans {parentTrans};
carouselTrans = glm::translate(carouselTrans, glm::vec3 {mPosition.x, mPosition.y, 0.0f});
carouselTrans = glm::translate(
carouselTrans, glm::vec3 {mOrigin.x * mSize.x * -1.0f, mOrigin.y * mSize.y * -1.0f, 0.0f});
glm::vec2 clipPos {carouselTrans[3].x, carouselTrans[3].y};
Renderer::setMatrix(carouselTrans);
// Background box behind logos.
Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, mCarouselColor, mCarouselColorEnd,
mColorGradientHorizontal);
// Draw logos.
// logoSpacing will also include the size of the logo itself.
glm::vec2 logoSpacing {};
float xOff {0.0f};
float yOff {0.0f};
switch (mType) {
case HORIZONTAL_WHEEL:
case VERTICAL_WHEEL:
xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.y));
yOff = (mSize.y - mLogoSize.y) / 2.0f;
break;
case VERTICAL:
logoSpacing.y =
((mSize.y - (mLogoSize.y * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.y;
yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y);
if (mLogoHorizontalAlignment == ALIGN_LEFT)
xOff = mLogoSize.x / 10.0f;
else if (mLogoHorizontalAlignment == ALIGN_RIGHT)
xOff = mSize.x - (mLogoSize.x * 1.1f);
else
xOff = (mSize.x - mLogoSize.x) / 2.0f;
break;
case HORIZONTAL:
default:
logoSpacing.x =
((mSize.x - (mLogoSize.x * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.x;
xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.x));
if (mLogoVerticalAlignment == ALIGN_TOP)
yOff = mLogoSize.y / 10.0f;
else if (mLogoVerticalAlignment == ALIGN_BOTTOM)
yOff = mSize.y - (mLogoSize.y * 1.1f);
else
yOff = (mSize.y - mLogoSize.y) / 2.0f;
break;
}
int center {static_cast<int>(mCamOffset)};
int logoCount {std::min(mMaxLogoCount, static_cast<int>(mEntries.size()))};
// Adding texture loading buffers depending on scrolling speed and status.
int bufferIndex {getScrollingVelocity() + 1};
int bufferLeft {logoBuffersLeft[bufferIndex]};
int bufferRight {logoBuffersRight[bufferIndex]};
if (logoCount == 1) {
bufferLeft = 0;
bufferRight = 0;
}
for (int i = center - logoCount / 2 + bufferLeft; // Line break.
i <= center + logoCount / 2 + bufferRight; ++i) {
int index {i};
while (index < 0)
index += static_cast<int>(mEntries.size());
while (index >= static_cast<int>(mEntries.size()))
index -= static_cast<int>(mEntries.size());
glm::mat4 logoTrans {carouselTrans};
logoTrans = glm::translate(
logoTrans, glm::vec3 {i * logoSpacing.x + xOff, i * logoSpacing.y + yOff, 0.0f});
float distance = i - mCamOffset;
float scale {1.0f + ((mLogoScale - 1.0f) * (1.0f - fabs(distance)))};
scale = std::min(mLogoScale, std::max(1.0f, scale));
scale /= mLogoScale;
int opacity {
static_cast<int>(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabs(distance)))))};
opacity = std::max(static_cast<int>(0x80), opacity);
const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo;
if (comp == nullptr)
continue;
if (mType == VERTICAL_WHEEL || mType == HORIZONTAL_WHEEL) {
comp->setRotationDegrees(mLogoRotation * distance);
comp->setRotationOrigin(mLogoRotationOrigin);
}
// When running at lower resolutions, prevent the scale-down to go all the way to the
// minimum value. This avoids potential single-pixel alignment issues when the logo
// can't be vertically placed exactly in the middle of the carousel. Although the
// problem theoretically exists at all resolutions, it's not visble at around 1080p
// and above.
if (std::min(Renderer::getScreenWidth(), Renderer::getScreenHeight()) < 1080.0f)
scale = glm::clamp(scale, 1.0f / mLogoScale + 0.01f, 1.0f);
comp->setScale(scale);
comp->setOpacity(static_cast<float>(opacity) / 255.0f);
comp->render(logoTrans);
}
}
void CarouselComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
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.2325f;
mPosition.x = 0.0f;
mPosition.y = floorf(0.5f * (Renderer::getScreenHeight() - mSize.y));
mCarouselColor = 0xFFFFFFD8;
mCarouselColorEnd = 0xFFFFFFD8;
mDefaultZIndex = 50.0f;
mText = "";
if (!elem)
return;
if (elem->has("type")) {
const std::string type {elem->get<std::string>("type")};
if (type == "horizontal") {
mType = HORIZONTAL;
}
else if (type == "horizontal_wheel") {
mType = HORIZONTAL_WHEEL;
}
else if (type == "vertical") {
mType = VERTICAL;
}
else if (type == "vertical_wheel") {
mType = VERTICAL_WHEEL;
}
else {
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"<type> set to \""
<< type << "\"";
mType = HORIZONTAL;
}
}
if (elem->has("color")) {
mCarouselColor = elem->get<unsigned int>("color");
mCarouselColorEnd = mCarouselColor;
}
if (elem->has("colorEnd"))
mCarouselColorEnd = 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) << "CarouselComponent: Invalid theme configuration, property "
"<gradientType> set to \""
<< gradientType << "\"";
}
}
if (elem->has("logoScale"))
mLogoScale = glm::clamp(elem->get<float>("logoScale"), 0.5f, 3.0f);
if (elem->has("logoSize")) {
// Keep size within a 0.05 and 1.0 multiple of the screen size.
glm::vec2 logoSize {elem->get<glm::vec2>("logoSize")};
if (std::max(logoSize.x, logoSize.y) > 1.0f) {
logoSize /= std::max(logoSize.x, logoSize.y);
}
else if (std::min(logoSize.x, logoSize.y) < 0.005f) {
float ratio {std::min(logoSize.x, logoSize.y) / 0.005f};
logoSize /= ratio;
// Just an extra precaution if a crazy ratio was used.
logoSize.x = glm::clamp(logoSize.x, 0.005f, 1.0f);
logoSize.y = glm::clamp(logoSize.y, 0.005f, 1.0f);
}
mLogoSize = logoSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
}
if (elem->has("maxLogoCount"))
mMaxLogoCount =
glm::clamp(static_cast<int>(elem->get<unsigned int>("maxLogoCount")), 2, 30);
if (elem->has("logoRotation"))
mLogoRotation = elem->get<float>("logoRotation");
if (elem->has("logoRotationOrigin"))
mLogoRotationOrigin = elem->get<glm::vec2>("logoRotationOrigin");
if (elem->has("logoHorizontalAlignment")) {
const std::string alignment {elem->get<std::string>("logoHorizontalAlignment")};
if (alignment == "left" && mType != HORIZONTAL) {
mLogoHorizontalAlignment = ALIGN_LEFT;
}
else if (alignment == "right" && mType != HORIZONTAL) {
mLogoHorizontalAlignment = ALIGN_RIGHT;
}
else if (alignment == "center") {
mLogoHorizontalAlignment = ALIGN_CENTER;
}
else {
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"<logoHorizontalAlignment> set to \""
<< alignment << "\"";
mLogoHorizontalAlignment = ALIGN_CENTER;
}
}
if (elem->has("logoVerticalAlignment")) {
const std::string alignment {elem->get<std::string>("logoVerticalAlignment")};
if (alignment == "top" && mType != VERTICAL) {
mLogoVerticalAlignment = ALIGN_TOP;
}
else if (alignment == "bottom" && mType != VERTICAL) {
mLogoVerticalAlignment = ALIGN_BOTTOM;
}
else if (alignment == "center") {
mLogoVerticalAlignment = ALIGN_CENTER;
}
else {
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"<logoVerticalAlignment> set to \""
<< alignment << "\"";
mLogoVerticalAlignment = ALIGN_CENTER;
}
}
// Legacy themes only.
if (elem->has("logoAlignment")) {
const std::string alignment {elem->get<std::string>("logoAlignment")};
if (alignment == "left" && mType != HORIZONTAL) {
mLogoHorizontalAlignment = ALIGN_LEFT;
mLogoVerticalAlignment = ALIGN_CENTER;
}
else if (alignment == "right" && mType != HORIZONTAL) {
mLogoHorizontalAlignment = ALIGN_RIGHT;
mLogoVerticalAlignment = ALIGN_CENTER;
}
else if (alignment == "top" && mType != VERTICAL) {
mLogoVerticalAlignment = ALIGN_TOP;
mLogoHorizontalAlignment = ALIGN_CENTER;
}
else if (alignment == "bottom" && mType != VERTICAL) {
mLogoVerticalAlignment = ALIGN_BOTTOM;
mLogoHorizontalAlignment = ALIGN_CENTER;
}
else if (alignment == "center") {
mLogoHorizontalAlignment = ALIGN_CENTER;
mLogoVerticalAlignment = ALIGN_CENTER;
}
else {
LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property "
"<logoAlignment> set to \""
<< alignment << "\"";
mLogoHorizontalAlignment = ALIGN_CENTER;
mLogoVerticalAlignment = ALIGN_CENTER;
}
}
mFont = Font::getFromTheme(elem, properties, mFont);
if (elem->has("textColor"))
mTextColor = elem->get<unsigned int>("textColor");
if (elem->has("textBackgroundColor"))
mTextBackgroundColor = elem->get<unsigned int>("textBackgroundColor");
if (elem->has("lineSpacing"))
mLineSpacing = glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f);
std::string letterCase;
if (elem->has("letterCase"))
letterCase = elem->get<std::string>("letterCase");
if (elem->has("text")) {
if (letterCase == "uppercase") {
mText = Utils::String::toUpper(elem->get<std::string>("text"));
}
else if (letterCase == "lowercase") {
mText = Utils::String::toLower(elem->get<std::string>("text"));
}
else if (letterCase == "capitalize") {
mText = Utils::String::toCapitalized(elem->get<std::string>("text"));
}
else if (letterCase == "none") {
mText = elem->get<std::string>("text");
}
else {
LOG(LogWarning)
<< "CarouselComponent: Invalid theme configuration, property <letterCase> set to \""
<< letterCase << "\"";
mText = elem->get<std::string>("text");
}
}
GuiComponent::applyTheme(theme, view, element, ALL);
}
void CarouselComponent::onCursorChanged(const CursorState& state)
{
float startPos {mCamOffset};
float posMax {static_cast<float>(mEntries.size())};
float target {static_cast<float>(mCursor)};
// Find the shortest path to the target.
float endPos {target}; // Directly.
float dist {fabs(endPos - startPos)};
if (fabs(target + posMax - startPos - mScrollVelocity) < dist)
endPos = target + posMax; // Loop around the end (0 -> max).
if (fabs(target - posMax - startPos - mScrollVelocity) < dist)
endPos = target - posMax; // Loop around the start (max - 1 -> -1).
// Make sure there are no reverse jumps between logos.
bool changedDirection {false};
if (mPreviousScrollVelocity != 0 && mPreviousScrollVelocity != mScrollVelocity)
changedDirection = true;
if (!changedDirection && mScrollVelocity > 0 && endPos < startPos)
endPos = endPos + posMax;
if (!changedDirection && mScrollVelocity < 0 && endPos > startPos)
endPos = endPos - posMax;
if (mScrollVelocity != 0)
mPreviousScrollVelocity = mScrollVelocity;
// No need to animate transition, we're not going anywhere (probably mEntries.size() == 1).
if (endPos == mCamOffset)
return;
Animation* anim = new LambdaAnimation(
[this, startPos, endPos, posMax](float t) {
t -= 1;
float f {glm::mix(startPos, endPos, t * t * t + 1)};
if (f < 0)
f += posMax;
if (f >= posMax)
f -= posMax;
mCamOffset = f;
},
500);
setAnimation(anim, 0, nullptr, false, 0);
if (mCursorChangedCallback)
mCursorChangedCallback(state);
}

View file

@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// CarouselComponent.h
//
// Carousel.
//
#include "GuiComponent.h"
#include "Sound.h"
#include "components/IList.h"
#include "components/ImageComponent.h"
#include "components/TextComponent.h"
#include "resources/Font.h"
class SystemData;
#ifndef ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
#define ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H
struct CarouselElement {
std::shared_ptr<GuiComponent> logo;
std::string logoPath;
std::string defaultLogoPath;
};
class CarouselComponent : public IList<CarouselElement, SystemData*>
{
public:
CarouselComponent();
void addEntry(const std::shared_ptr<ThemeData>& theme, Entry& entry, bool legacyMode);
Entry& getEntry(int index) { return mEntries.at(index); }
enum CarouselType {
HORIZONTAL, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
VERTICAL,
VERTICAL_WHEEL,
HORIZONTAL_WHEEL
};
int getCursor() { return mCursor; }
const CarouselType getType() { return mType; }
size_t getNumEntries() { return mEntries.size(); }
void setCursorChangedCallback(const std::function<void(CursorState state)>& func)
{
mCursorChangedCallback = func;
}
void setCancelTransitionsCallback(const std::function<void()>& func)
{
mCancelTransitionsCallback = func;
}
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void render(const glm::mat4& parentTrans) override;
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties) override;
protected:
void onCursorChanged(const CursorState& state) override;
void onScroll() override
{
NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
}
private:
std::function<void(CursorState state)> mCursorChangedCallback;
std::function<void()> mCancelTransitionsCallback;
float mCamOffset;
int mPreviousScrollVelocity;
CarouselType mType;
std::shared_ptr<Font> mFont;
unsigned int mTextColor;
unsigned int mTextBackgroundColor;
std::string mText;
float mLineSpacing;
Alignment mLogoHorizontalAlignment;
Alignment mLogoVerticalAlignment;
int mMaxLogoCount;
glm::vec2 mLogoSize;
float mLogoScale;
float mLogoRotation;
glm::vec2 mLogoRotationOrigin;
unsigned int mCarouselColor;
unsigned int mCarouselColorEnd;
bool mColorGradientHorizontal;
};
#endif // ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H

View file

@ -289,7 +289,7 @@ void ComponentList::render(const glm::mat4& parentTrans)
dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x;
dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y;
const int clipRectPosX {static_cast<int>(std::ceil(trans[3].x))};
const int clipRectPosX {static_cast<int>(std::round(trans[3].x))};
const int clipRectPosY {static_cast<int>(std::round(trans[3].y))};
const int clipRectSizeX {static_cast<int>(std::round(dim.x))};
const int clipRectSizeY {static_cast<int>(std::round(dim.y))};
@ -376,19 +376,17 @@ void ComponentList::render(const glm::mat4& parentTrans)
// Custom rendering.
Renderer::setMatrix(trans);
float opacity = mOpacity / 255.0f;
// Draw selector bar.
if (mFocused) {
const float selectedRowHeight = getRowHeight(mEntries.at(mCursor).data);
if (opacity == 1) {
if (mOpacity == 1.0f) {
Renderer::drawRect(0.0f, mSelectorBarOffset, std::ceil(mSize.x), selectedRowHeight,
0xFFFFFFFF, 0xFFFFFFFF, false, opacity, trans,
0xFFFFFFFF, 0xFFFFFFFF, false, mOpacity, trans,
Renderer::Blend::ONE_MINUS_DST_COLOR, Renderer::Blend::ZERO);
Renderer::drawRect(0.0f, mSelectorBarOffset, std::ceil(mSize.x), selectedRowHeight,
0x777777FF, 0x777777FF, false, opacity, trans, Renderer::Blend::ONE,
0x777777FF, 0x777777FF, false, mOpacity, trans, Renderer::Blend::ONE,
Renderer::Blend::ONE);
}
@ -404,12 +402,12 @@ void ComponentList::render(const glm::mat4& parentTrans)
float y = 0;
for (unsigned int i = 0; i < mEntries.size(); ++i) {
Renderer::drawRect(0.0f, y, std::ceil(mSize.x), 1.0f * Renderer::getScreenHeightModifier(),
0xC6C7C6FF, 0xC6C7C6FF, false, opacity, trans);
0xC6C7C6FF, 0xC6C7C6FF, false, mOpacity, trans);
y += getRowHeight(mEntries.at(i).data);
}
Renderer::drawRect(0.0f, y, std::ceil(mSize.x), 1.0f * Renderer::getScreenHeightModifier(),
0xC6C7C6FF, 0xC6C7C6FF, false, opacity, trans);
0xC6C7C6FF, 0xC6C7C6FF, false, mOpacity, trans);
Renderer::popClipRect();
}

View file

@ -117,9 +117,6 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (!elem)
return;
if (elem->has("displayRelative"))
setDisplayRelative(elem->get<bool>("displayRelative"));
if (elem->has("format"))
setFormat(elem->get<std::string>("format"));
@ -132,8 +129,8 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setRenderBackground(true);
}
if (properties & ALIGNMENT && elem->has("alignment")) {
std::string str = elem->get<std::string>("alignment");
if (properties & ALIGNMENT && elem->has("horizontalAlignment")) {
std::string str {elem->get<std::string>("horizontalAlignment")};
if (str == "left")
setHorizontalAlignment(ALIGN_LEFT);
else if (str == "center")
@ -141,17 +138,73 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else if (str == "right")
setHorizontalAlignment(ALIGN_RIGHT);
else
LOG(LogError) << "Unknown text alignment string: " << str;
LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property "
"<horizontalAlignment> set to \""
<< str << "\"";
}
if (properties & ALIGNMENT && elem->has("verticalAlignment")) {
std::string str {elem->get<std::string>("verticalAlignment")};
if (str == "top")
setVerticalAlignment(ALIGN_TOP);
else if (str == "center")
setVerticalAlignment(ALIGN_CENTER);
else if (str == "bottom")
setVerticalAlignment(ALIGN_BOTTOM);
else
LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property "
"<verticalAlignment> set to \""
<< str << "\"";
}
// Legacy themes only.
if (properties & ALIGNMENT && elem->has("alignment")) {
std::string str {elem->get<std::string>("alignment")};
if (str == "left")
setHorizontalAlignment(ALIGN_LEFT);
else if (str == "center")
setHorizontalAlignment(ALIGN_CENTER);
else if (str == "right")
setHorizontalAlignment(ALIGN_RIGHT);
else
LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property "
"<alignment> set to \""
<< str << "\"";
}
if (properties & METADATA && elem->has("metadata"))
setMetadataField(elem->get<std::string>("metadata"));
mThemeMetadata = elem->get<std::string>("metadata");
if (mThemeMetadata == "lastplayed")
setDisplayRelative(true);
if (elem->has("displayRelative"))
setDisplayRelative(elem->get<bool>("displayRelative"));
if (properties & LETTER_CASE && elem->has("letterCase")) {
std::string letterCase {elem->get<std::string>("letterCase")};
if (letterCase == "uppercase") {
setUppercase(true);
}
else if (letterCase == "lowercase") {
setLowercase(true);
}
else if (letterCase == "capitalize") {
setCapitalize(true);
}
else if (letterCase != "none") {
LOG(LogWarning)
<< "DateTimeComponent: Invalid theme configuration, property <letterCase> set to \""
<< letterCase << "\"";
}
}
// Legacy themes only.
if (properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase"));
if (properties & LINE_SPACING && elem->has("lineSpacing"))
setLineSpacing(elem->get<float>("lineSpacing"));
setLineSpacing(glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f));
setFont(Font::getFromTheme(elem, properties, mFont));
}

View file

@ -193,7 +193,7 @@ void DateTimeEditComponent::render(const glm::mat4& parentTrans)
0x00000033, 0x00000033);
}
mTextCache->setColor((mColor & 0xFFFFFF00) | getOpacity());
mTextCache->setColor((mColor & 0xFFFFFF00) | static_cast<int>(getOpacity() * 255.0f));
font->renderTextCache(mTextCache.get());
if (mEditing && mTime != 0) {

View file

@ -50,7 +50,7 @@ void FlexboxComponent::render(const glm::mat4& parentTrans)
for (auto& item : mItems) {
if (!item.visible)
continue;
if (mOpacity == 255) {
if (mOpacity == 1.0f) {
item.baseImage.render(trans);
if (item.overlayImage.getTexture() != nullptr)
item.overlayImage.render(trans);
@ -58,11 +58,11 @@ void FlexboxComponent::render(const glm::mat4& parentTrans)
else {
item.baseImage.setOpacity(mOpacity);
item.baseImage.render(trans);
item.baseImage.setOpacity(255);
item.baseImage.setOpacity(1.0f);
if (item.overlayImage.getTexture() != nullptr) {
item.overlayImage.setOpacity(mOpacity);
item.overlayImage.render(trans);
item.overlayImage.setOpacity(255);
item.overlayImage.setOpacity(1.0f);
}
}
}
@ -91,10 +91,8 @@ void FlexboxComponent::computeLayout()
mItemMargin.y = glm::clamp(mItemMargin.y, 0.0f, mSize.y / 2.0f);
// Also keep the size within reason.
mSize.x = glm::clamp(mSize.x, static_cast<float>(Renderer::getScreenWidth()) * 0.03f,
static_cast<float>(Renderer::getScreenWidth()));
mSize.y = glm::clamp(mSize.y, static_cast<float>(Renderer::getScreenHeight()) * 0.03f,
static_cast<float>(Renderer::getScreenHeight()));
mSize.x = glm::clamp(mSize.x, Renderer::getScreenWidth() * 0.03f, Renderer::getScreenWidth());
mSize.y = glm::clamp(mSize.y, Renderer::getScreenHeight() * 0.03f, Renderer::getScreenHeight());
if (mItemsPerLine * mLines < mItems.size()) {
LOG(LogWarning)

View file

@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// GameSelectorComponent.h
//
// Makes a selection of games based on theme-controlled criteria.
//
#ifndef ES_CORE_COMPONENTS_GAME_SELECTOR_COMPONENT_H
#define ES_CORE_COMPONENTS_GAME_SELECTOR_COMPONENT_H
#include "GuiComponent.h"
#include "Log.h"
#include "ThemeData.h"
class GameSelectorComponent : public GuiComponent
{
public:
GameSelectorComponent(SystemData* system)
: mSystem {system}
, mGameSelection {GameSelection::RANDOM}
, mNeedsRefresh {false}
, mGameCount {1}
{
mSystem->getRootFolder()->setUpdateListCallback([&]() { mNeedsRefresh = true; });
}
~GameSelectorComponent()
{
if (std::find(SystemData::sSystemVector.cbegin(), SystemData::sSystemVector.cend(),
mSystem) != SystemData::sSystemVector.cend())
mSystem->getRootFolder()->setUpdateListCallback(nullptr);
}
enum class GameSelection {
RANDOM, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0).
LAST_PLAYED,
MOST_PLAYED
};
const std::vector<FileData*>& getGames() const { return mGames; }
void setNeedsRefresh() { mNeedsRefresh = true; }
const bool getNeedsRefresh() { return mNeedsRefresh; }
const GameSelection getGameSelection() const { return mGameSelection; }
const std::string& getSelectorName() const { return mSelectorName; }
void refreshGames()
{
if (!mNeedsRefresh)
return;
mGames.clear();
mNeedsRefresh = false;
bool isKidMode {(Settings::getInstance()->getString("UIMode") == "kid" ||
Settings::getInstance()->getBool("ForceKid"))};
if (mGameSelection == GameSelection::RANDOM) {
for (int i = 0; i < mGameCount; ++i) {
if (mSystem->getRootFolder()->getGameCount().first == 0)
break;
FileData* randomGame {mSystem->getRandomGame(nullptr, true)};
if (randomGame != nullptr)
mGames.emplace_back(randomGame);
}
}
else if (mGameSelection == GameSelection::LAST_PLAYED) {
for (auto& child : mSystem->getRootFolder()->getChildrenLastPlayed()) {
if (child->getType() != GAME)
continue;
if (!child->getCountAsGame())
continue;
if (isKidMode && !child->getKidgame())
continue;
if (child->metadata.get("lastplayed") == "0")
continue;
mGames.emplace_back(child);
if (static_cast<int>(mGames.size()) == mGameCount)
break;
}
}
else if (mGameSelection == GameSelection::MOST_PLAYED) {
for (auto& child : mSystem->getRootFolder()->getChildrenMostPlayed()) {
if (child->getType() != GAME)
continue;
if (!child->getCountAsGame())
continue;
if (isKidMode && !child->getKidgame())
continue;
if (child->metadata.get("playcount") == "0")
continue;
mGames.emplace_back(child);
if (static_cast<int>(mGames.size()) == mGameCount)
break;
}
}
}
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "gameselector")};
if (!elem)
return;
// Remove the leading "gameselector_" part of the element string in order to directly
// match with the optional "gameselector" property used in other elements.
mSelectorName = element.substr(13, std::string::npos);
if (elem->has("selection")) {
const std::string selection {elem->get<std::string>("selection")};
if (selection == "random") {
mGameSelection = GameSelection::RANDOM;
}
else if (selection == "lastplayed") {
mGameSelection = GameSelection::LAST_PLAYED;
mSystem->getRootFolder()->setUpdateChildrenLastPlayed(true);
mSystem->getRootFolder()->updateLastPlayedList();
}
else if (selection == "mostplayed") {
mGameSelection = GameSelection::MOST_PLAYED;
mSystem->getRootFolder()->setUpdateChildrenMostPlayed(true);
mSystem->getRootFolder()->updateMostPlayedList();
}
else {
mGameSelection = GameSelection::RANDOM;
LOG(LogWarning) << "GameSelectorComponent: Invalid theme configuration, property "
"<selection> set to \""
<< selection << "\"";
}
}
if (elem->has("gameCount"))
mGameCount = glm::clamp(static_cast<int>(elem->get<unsigned int>("gameCount")), 1, 30);
}
private:
SystemData* mSystem;
std::vector<FileData*> mGames;
std::string mSelectorName;
GameSelection mGameSelection;
bool mNeedsRefresh;
int mGameCount;
};
#endif // ES_CORE_COMPONENTS_GAME_SELECTOR_COMPONENT_H

View file

@ -75,8 +75,7 @@ void GridTileComponent::update(int deltaTime)
void applyThemeToProperties(const ThemeData::ThemeElement* elem, GridTileProperties* properties)
{
glm::vec2 screen {static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())};
glm::vec2 screen {Renderer::getScreenWidth(), Renderer::getScreenHeight()};
if (elem->has("size"))
properties->mSize = elem->get<glm::vec2>("size") * screen;
@ -149,7 +148,7 @@ bool GridTileComponent::isSelected() const
return mSelected;
}
void GridTileComponent::setImage(const std::string& path)
void GridTileComponent::setImageOLD(const std::string& path)
{
mImage->setImage(path);
@ -157,7 +156,7 @@ void GridTileComponent::setImage(const std::string& path)
resize();
}
void GridTileComponent::setImage(const std::shared_ptr<TextureResource>& texture)
void GridTileComponent::setImageOLD(const std::shared_ptr<TextureResource>& texture)
{
mImage->setImage(texture);

View file

@ -39,10 +39,10 @@ public:
glm::vec2 getSelectedTileSize() const;
bool isSelected() const;
void reset() { setImage(""); }
void reset() { setImageOLD(""); }
void setImage(const std::string& path);
void setImage(const std::shared_ptr<TextureResource>& texture);
void setImageOLD(const std::string& path);
void setImageOLD(const std::shared_ptr<TextureResource>& texture);
void setSelected(bool selected,
bool allowAnimation = true,
glm::vec3* pPosition = nullptr,

View file

@ -209,29 +209,31 @@ void HelpComponent::updateGrid()
std::vector<std::shared_ptr<ImageComponent>> icons;
std::vector<std::shared_ptr<TextComponent>> labels;
float width = 0;
const float height = std::round(font->getLetterHeight() * 1.25f);
float width {0.0f};
const float height {std::round(font->getLetterHeight() * 1.25f)};
// State variable indicating whether gui is dimmed.
bool isDimmed = mWindow->isBackgroundDimmed();
bool isDimmed {mWindow->isBackgroundDimmed()};
for (auto it = mPrompts.cbegin(); it != mPrompts.cend(); ++it) {
auto icon = std::make_shared<ImageComponent>();
icon->setImage(getIconTexture(it->first.c_str()), false);
icon->setColorShift(isDimmed ? mStyle.iconColorDimmed : mStyle.iconColor);
icon->setResize(0, height);
icon->setOpacity(mStyle.opacity);
icons.push_back(icon);
// Apply text style and color from the theme to the label and add it to the label list.
std::string lblInput = it->second;
if (mStyle.textStyle == "lowercase")
std::string lblInput {it->second};
if (mStyle.letterCase == "lowercase")
lblInput = Utils::String::toLower(lblInput);
else if (mStyle.textStyle == "camelcase")
lblInput = Utils::String::toCamelCase(lblInput);
else if (mStyle.letterCase == "capitalize")
lblInput = Utils::String::toCapitalized(lblInput);
else
lblInput = Utils::String::toUpper(lblInput);
auto lbl = std::make_shared<TextComponent>(
lblInput, font, isDimmed ? mStyle.textColorDimmed : mStyle.textColor);
lbl->setOpacity(mStyle.opacity);
labels.push_back(lbl);
width +=
@ -274,18 +276,18 @@ std::shared_ptr<TextureResource> HelpComponent::getIconTexture(const char* name)
return nullptr;
}
std::shared_ptr<TextureResource> tex =
TextureResource::get(pathLookup->second, false, false, false);
std::shared_ptr<TextureResource> tex {
TextureResource::get(pathLookup->second, false, false, false)};
mIconCache[std::string(name)] = tex;
return tex;
}
void HelpComponent::setOpacity(unsigned char opacity)
void HelpComponent::setOpacity(float opacity)
{
GuiComponent::setOpacity(opacity);
GuiComponent::setOpacity(opacity * mStyle.opacity);
for (unsigned int i = 0; i < mGrid->getChildCount(); ++i)
mGrid->getChild(i)->setOpacity(opacity);
mGrid->getChild(i)->setOpacity(opacity * mStyle.opacity);
}
void HelpComponent::render(const glm::mat4& parentTrans)

View file

@ -27,7 +27,7 @@ public:
void setPrompts(const std::vector<HelpPrompt>& prompts);
void render(const glm::mat4& parent) override;
void setOpacity(unsigned char opacity) override;
void setOpacity(float opacity) override;
void setStyle(const HelpStyle& style);

View file

@ -73,7 +73,7 @@ protected:
int mScrollTierAccumulator;
int mScrollCursorAccumulator;
unsigned char mTitleOverlayOpacity;
float mTitleOverlayOpacity;
unsigned int mTitleOverlayColor;
const ScrollTierList& mTierList;
@ -95,7 +95,7 @@ public:
mScrollTierAccumulator = 0;
mScrollCursorAccumulator = 0;
mTitleOverlayOpacity = 0x00;
mTitleOverlayOpacity = 0.0f;
mTitleOverlayColor = 0xFFFFFF00;
}
@ -105,7 +105,7 @@ public:
void stopScrolling()
{
mTitleOverlayOpacity = 0;
mTitleOverlayOpacity = 0.0f;
listInput(0);
if (mScrollVelocity == 0)
@ -243,15 +243,10 @@ protected:
{
// Update the title overlay opacity.
// Fade in if scroll tier is >= 1, otherwise fade out.
const int dir = (mScrollTier >= mTierList.count - 1) ? 1 : -1;
const float dir {(mScrollTier >= mTierList.count - 1) ? 1.0f : -1.0f};
// We simply translate the time directly to opacity, i.e. no scaling is performed.
int op = mTitleOverlayOpacity + deltaTime * dir;
if (op >= 255)
mTitleOverlayOpacity = 255;
else if (op <= 0)
mTitleOverlayOpacity = 0;
else
mTitleOverlayOpacity = static_cast<unsigned char>(op);
mTitleOverlayOpacity = glm::clamp(
mTitleOverlayOpacity + (static_cast<float>(deltaTime) / 255.0f) * dir, 0.0f, 1.0f);
if (mScrollVelocity == 0 || size() < 2)
return;
@ -285,8 +280,8 @@ protected:
if (!Settings::getInstance()->getBool("ListScrollOverlay"))
return;
if (size() == 0 || mTitleOverlayOpacity == 0) {
mWindow->renderListScrollOverlay(0, "");
if (size() == 0 || mTitleOverlayOpacity == 0.0f) {
mWindow->renderListScrollOverlay(0.0f, "");
return;
}

View file

@ -14,6 +14,7 @@
#include "Window.h"
#include "resources/TextureResource.h"
#include "utils/CImgUtil.h"
#include "utils/StringUtil.h"
glm::ivec2 ImageComponent::getTextureSize() const
{
@ -37,11 +38,12 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic)
, mColorShift {0xFFFFFFFF}
, mColorShiftEnd {0xFFFFFFFF}
, mColorGradientHorizontal {true}
, mFadeOpacity {0}
, mFadeOpacity {0.0f}
, mFading {false}
, mForceLoad {forceLoad}
, mDynamic {dynamic}
, mRotateByTargetSize {false}
, mLinearInterpolation {false}
, mTopLeftCrop {0.0f, 0.0f}
, mBottomRightCrop {1.0f, 1.0f}
{
@ -132,7 +134,7 @@ void ImageComponent::resize()
onSizeChanged();
}
void ImageComponent::setImage(const std::string& path, bool tile, bool linearMagnify)
void ImageComponent::setImage(const std::string& path, bool tile)
{
// Always load bundled graphic resources statically, unless mForceLoad has been set.
// This eliminates annoying texture pop-in problems that would otherwise occur.
@ -144,11 +146,11 @@ void ImageComponent::setImage(const std::string& path, bool tile, bool linearMag
if (mDefaultPath.empty() || !ResourceManager::getInstance().fileExists(mDefaultPath))
mTexture.reset();
else
mTexture =
TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify);
mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic,
mLinearInterpolation);
}
else {
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify);
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation);
}
resize();
@ -319,7 +321,7 @@ void ImageComponent::setColorGradientHorizontal(bool horizontal)
updateColors();
}
void ImageComponent::setOpacity(unsigned char opacity)
void ImageComponent::setOpacity(float opacity)
{
mOpacity = opacity;
updateColors();
@ -369,7 +371,7 @@ void ImageComponent::updateVertices()
void ImageComponent::updateColors()
{
const float opacity = (mOpacity * (mFading ? mFadeOpacity / 255.0f : 1.0f)) / 255.0f;
const float opacity = (mOpacity * mThemeOpacity * (mFading ? mFadeOpacity : 1.0f));
const unsigned int color = Renderer::convertRGBAToABGR(
(mColorShift & 0xFFFFFF00) | static_cast<unsigned char>((mColorShift & 0xFF) * opacity));
const unsigned int colorEnd =
@ -377,21 +379,21 @@ void ImageComponent::updateColors()
static_cast<unsigned char>((mColorShiftEnd & 0xFF) * opacity));
mVertices[0].col = color;
mVertices[1].col = mColorGradientHorizontal ? colorEnd : color;
mVertices[2].col = mColorGradientHorizontal ? color : colorEnd;
mVertices[1].col = mColorGradientHorizontal ? color : colorEnd;
mVertices[2].col = mColorGradientHorizontal ? colorEnd : color;
mVertices[3].col = colorEnd;
}
void ImageComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible() || mTexture == nullptr || mTargetSize == glm::vec2 {0.0f, 0.0f} ||
mSize == glm::vec2 {0.0f, 0.0f})
if (!isVisible() || mThemeOpacity == 0.0f || mTexture == nullptr ||
mTargetSize == glm::vec2 {0.0f, 0.0f} || mSize == glm::vec2 {0.0f, 0.0f})
return;
glm::mat4 trans {parentTrans * getTransform()};
Renderer::setMatrix(trans);
if (mTexture && mOpacity > 0) {
if (mTexture && mOpacity > 0.0f) {
if (Settings::getInstance()->getBool("DebugImage")) {
glm::vec2 targetSizePos {(mTargetSize - mSize) * mOrigin * glm::vec2 {-1.0f}};
Renderer::drawRect(targetSizePos.x, targetSizePos.y, mTargetSize.x, mTargetSize.y,
@ -447,7 +449,7 @@ void ImageComponent::fadeIn(bool textureLoaded)
// Start the fade if this is the first time we've encountered the unloaded texture.
if (!mFading) {
// Start with a zero opacity and flag it as fading.
mFadeOpacity = 0;
mFadeOpacity = 0.0f;
mFading = true;
updateColors();
}
@ -456,14 +458,14 @@ void ImageComponent::fadeIn(bool textureLoaded)
// The texture is loaded and we need to fade it in. The fade is based on the frame
// rate and is 1/4 second if running at 60 frames per second although the actual
// value is not that important.
int opacity = mFadeOpacity + 255 / 15;
float opacity {mFadeOpacity + 1.0f / 15.0f};
// See if we've finished fading.
if (opacity >= 255) {
mFadeOpacity = 255;
if (opacity >= 1.0f) {
mFadeOpacity = 1.0f;
mFading = false;
}
else {
mFadeOpacity = static_cast<unsigned char>(opacity);
mFadeOpacity = opacity;
}
updateColors();
}
@ -484,9 +486,9 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (!elem)
return;
glm::vec2 scale {getParent() ? getParent()->getSize() :
glm::vec2(static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight()))};
glm::vec2 scale {getParent() ?
getParent()->getSize() :
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())};
if (properties & ThemeFlags::SIZE) {
if (elem->has("size"))
@ -497,6 +499,22 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setMinSize(elem->get<glm::vec2>("minSize") * scale);
}
if (elem->has("interpolation")) {
const std::string interpolation {elem->get<std::string>("interpolation")};
if (interpolation == "linear") {
mLinearInterpolation = true;
}
else if (interpolation == "nearest") {
mLinearInterpolation = false;
}
else {
mLinearInterpolation = false;
LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property "
"<interpolation> set to \""
<< interpolation << "\"";
}
}
if (elem->has("default"))
setDefaultImage(elem->get<std::string>("default"));
@ -505,17 +523,36 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setImage(elem->get<std::string>("path"), tile);
}
if (properties & METADATA && elem->has("metadata"))
setMetadataField(elem->get<std::string>("metadata"));
if (properties && elem->has("imageType")) {
std::string imageTypes {elem->get<std::string>("imageType")};
for (auto& character : imageTypes) {
if (std::isspace(character))
character = ',';
}
imageTypes = Utils::String::replace(imageTypes, ",,", ",");
mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ",");
}
if (properties & COLOR) {
if (elem->has("color"))
setColorShift(elem->get<unsigned int>("color"));
if (elem->has("colorEnd"))
setColorShiftEnd(elem->get<unsigned int>("colorEnd"));
if (elem->has("gradientType"))
setColorGradientHorizontal(
!(elem->get<std::string>("gradientType").compare("horizontal")));
if (elem->has("gradientType")) {
const std::string gradientType {elem->get<std::string>("gradientType")};
if (gradientType == "horizontal") {
setColorGradientHorizontal(true);
}
else if (gradientType == "vertical") {
setColorGradientHorizontal(false);
}
else {
setColorGradientHorizontal(true);
LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property "
"<gradientType> set to \""
<< gradientType << "\"";
}
}
}
if (elem->has("scrollFadeIn") && elem->get<bool>("scrollFadeIn"))

View file

@ -24,7 +24,7 @@ public:
// Loads the image at the given filepath. Will tile if tile is true (retrieves texture
// as tiling, creates vertices accordingly).
void setImage(const std::string& path, bool tile = false, bool linearMagnify = false);
void setImage(const std::string& path, bool tile = false) override;
// Loads an image from memory.
void setImage(const char* data, size_t length, bool tile = false);
// Use an already existing texture.
@ -71,7 +71,7 @@ public:
unsigned int getColorShift() const override { return mColorShift; }
void setOpacity(unsigned char opacity) override;
void setOpacity(float opacity) override;
void setSaturation(float saturation) override;
void setFlipX(bool flip); // Mirror on the X axis.
@ -79,6 +79,8 @@ public:
// Flag indicating if rotation should be based on target size vs. actual size.
void setRotateByTargetSize(bool rotate) { mRotateByTargetSize = rotate; }
// Whether to use smooth texture magnification by utilizing linear interpolation.
void setLinearInterpolation(bool state) { mLinearInterpolation = state; }
// Returns the size of the current texture, or (0, 0) if none is loaded.
// May be different than drawn size (use getSize() for that).
@ -124,11 +126,12 @@ private:
std::string mDefaultPath;
std::shared_ptr<TextureResource> mTexture;
unsigned char mFadeOpacity;
float mFadeOpacity;
bool mFading;
bool mForceLoad;
bool mDynamic;
bool mRotateByTargetSize;
bool mLinearInterpolation;
glm::vec2 mTopLeftCrop;
glm::vec2 mBottomRightCrop;

View file

@ -124,8 +124,7 @@ private:
template <typename T> ImageGridComponent<T>::ImageGridComponent()
{
glm::vec2 screen {static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())};
glm::vec2 screen {Renderer::getScreenWidth(), Renderer::getScreenHeight()};
mCamera = 0.0f;
mCameraDirection = 1.0f;

View file

@ -36,6 +36,7 @@ LottieComponent::LottieComponent()
, mSkippedFrames {0}
, mHoldFrame {false}
, mPause {false}
, mExternalPause {false}
, mAlternate {false}
, mKeepAspectRatio {true}
{
@ -190,6 +191,7 @@ void LottieComponent::setAnimation(const std::string& path)
void LottieComponent::resetFileAnimation()
{
mExternalPause = false;
mTimeAccumulator = 0;
mFrameNum = mStartDirection == "reverse" ? mTotalFrames - 1 : 0;
@ -330,16 +332,13 @@ void LottieComponent::update(int deltaTime)
void LottieComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible())
return;
if (mAnimation == nullptr)
if (!isVisible() || mThemeOpacity == 0.0f || mAnimation == nullptr)
return;
glm::mat4 trans {parentTrans * getTransform()};
// This is necessary as there may otherwise be no texture to render when paused.
if (mPause && mTexture->getSize().x == 0.0f) {
if ((mExternalPause || mPause) && mTexture->getSize().x == 0.0f) {
mTexture->initFromPixels(&mPictureRGBA.at(0), static_cast<size_t>(mSize.x),
static_cast<size_t>(mSize.y));
}
@ -355,7 +354,7 @@ void LottieComponent::render(const glm::mat4& parentTrans)
}
// Don't render any new frames if paused or if a menu is open (unless invalidating background).
if (!mPause && doRender) {
if ((!mPause && !mExternalPause) && doRender) {
if ((mDirection == "normal" && mFrameNum >= mTotalFrames) ||
(mDirection == "reverse" && mFrameNum > mTotalFrames)) {
if (DEBUG_ANIMATION) {
@ -472,6 +471,7 @@ void LottieComponent::render(const glm::mat4& parentTrans)
#if defined(USE_OPENGL_21)
// Perform color space conversion from BGRA to RGBA.
vertices[0].opacity = mThemeOpacity;
vertices[0].shaders = Renderer::SHADER_BGRA_TO_RGBA;
#endif

View file

@ -33,6 +33,7 @@ public:
{
mMaxCacheSize = static_cast<size_t>(glm::clamp(value, 0, 1024) * 1024 * 1024);
}
void setPauseAnimation(bool state) { mExternalPause = state; }
void resetFileAnimation() override;
void onSizeChanged() override;
@ -42,8 +43,9 @@ public:
const std::string& element,
unsigned int properties) override;
private:
void update(int deltaTime) override;
private:
void render(const glm::mat4& parentTrans) override;
std::shared_ptr<TextureResource> mTexture;
@ -76,6 +78,7 @@ private:
bool mHoldFrame;
bool mPause;
bool mExternalPause;
bool mAlternate;
bool mKeepAspectRatio;
};

View file

@ -108,9 +108,7 @@ void MenuComponent::updateSize()
}
}
float width =
static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * 1.05f),
static_cast<int>(Renderer::getScreenWidth() * 0.90f)));
float width {std::min(Renderer::getScreenHeight() * 1.05f, Renderer::getScreenWidth() * 0.90f)};
setSize(width, height);
}

View file

@ -135,9 +135,9 @@ void NinePatchComponent::render(const glm::mat4& parentTrans)
if (mTexture && mVertices != nullptr) {
Renderer::setMatrix(trans);
if (mOpacity < 255) {
if (mOpacity < 1.0f) {
mVertices[0].shaders = Renderer::SHADER_OPACITY;
mVertices[0].opacity = mOpacity / 255.0f;
mVertices[0].opacity = mOpacity;
}
else if (mVertices[0].shaders & Renderer::SHADER_OPACITY) {
// We have reached full opacity, so disable the opacity shader and set

View file

@ -500,8 +500,8 @@ private:
else {
mEnabled = true;
list->getChild(i)->setEnabled(true);
list->getChild(i)->setOpacity(255);
list->getChild(i + 1)->setOpacity(255);
list->getChild(i)->setOpacity(1.0f);
list->getChild(i + 1)->setOpacity(1.0f);
}
}
}
@ -545,8 +545,8 @@ private:
mParent->mEntries.at(i).selected = false;
checkBoxes.at(i)->setImage(UNCHECKED_PATH);
if (mParent->mMultiExclusiveSelect) {
checkBoxes.at(i)->setOpacity(255);
textEntries.at(i)->setOpacity(255);
checkBoxes.at(i)->setOpacity(1.0f);
textEntries.at(i)->setOpacity(1.0f);
textEntries.at(i)->setEnabled(true);
}
}

View file

@ -82,10 +82,11 @@ std::string RatingComponent::getRatingValue() const
return ss.str();
}
void RatingComponent::setOpacity(unsigned char opacity)
void RatingComponent::setOpacity(float opacity)
{
mOpacity = opacity;
mColorShift = (mColorShift >> 8 << 8) | mOpacity;
mColorShift =
(mColorShift >> 8 << 8) | static_cast<unsigned char>(mOpacity * mThemeOpacity * 255.0f);
updateColors();
}
@ -96,7 +97,7 @@ void RatingComponent::setColorShift(unsigned int color)
// Grab the opacity from the color shift because we may need
// to apply it if fading in textures.
mOpacity = color & 0xff;
mOpacity = static_cast<float>(color & 0xff) / 255.0f;
updateColors();
}
@ -119,11 +120,11 @@ void RatingComponent::onSizeChanged()
void RatingComponent::updateVertices()
{
const float numStars = NUM_RATING_STARS;
const float h = getSize().y; // Ss the same as a single star's width.
const float w = getSize().y * mValue * numStars;
const float fw = getSize().y * numStars;
const unsigned int color = Renderer::convertRGBAToABGR(mColorShift);
const float numStars {NUM_RATING_STARS};
const float h {getSize().y}; // Ss the same as a single star's width.
const float w {getSize().y * mValue * numStars};
const float fw {getSize().y * numStars};
const unsigned int color {Renderer::convertRGBAToABGR(mColorShift)};
// clang-format off
mVertices[0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color};
@ -140,7 +141,7 @@ void RatingComponent::updateVertices()
void RatingComponent::updateColors()
{
const unsigned int color = Renderer::convertRGBAToABGR(mColorShift);
const unsigned int color {Renderer::convertRGBAToABGR(mColorShift)};
for (int i = 0; i < 8; ++i)
mVertices[i].col = color;
@ -148,14 +149,15 @@ void RatingComponent::updateColors()
void RatingComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible() || mFilledTexture == nullptr || mUnfilledTexture == nullptr)
if (!isVisible() || mFilledTexture == nullptr || mUnfilledTexture == nullptr ||
mThemeOpacity == 0.0f)
return;
glm::mat4 trans {parentTrans * getTransform()};
Renderer::setMatrix(trans);
if (mOpacity > 0) {
if (mOpacity > 0.0f) {
if (Settings::getInstance()->getBool("DebugImage")) {
Renderer::drawRect(0.0f, 0.0f, mSize.y * NUM_RATING_STARS, mSize.y, 0xFF000033,
0xFF000033);
@ -165,7 +167,7 @@ void RatingComponent::render(const glm::mat4& parentTrans)
if (mUnfilledColor != mColorShift) {
const unsigned int color = Renderer::convertRGBAToABGR(mUnfilledColor);
for (int i = 0; i < 8; ++i)
mVertices[i].col = color;
mVertices[i].col = (color & 0x00FFFFFF) + (mVertices[i].col & 0xFF000000);
}
Renderer::drawTriangleStrips(&mVertices[4], 4);

View file

@ -32,7 +32,7 @@ public:
void onSizeChanged() override;
void setOpacity(unsigned char opacity) override;
void setOpacity(float opacity) override;
// Multiply all pixels in the image by this color when rendering.
void setColorShift(unsigned int color) override;

View file

@ -27,8 +27,8 @@ public:
scrollUp->setImage(":/graphics/scroll_up.svg");
scrollDown->setImage(":/graphics/scroll_down.svg");
scrollUp->setOpacity(0);
scrollDown->setOpacity(0);
scrollUp->setOpacity(0.0f);
scrollDown->setOpacity(0.0f);
if (!Settings::getInstance()->getBool("ScrollIndicators")) {
// If the scroll indicators setting is disabled, then show a permanent down arrow
@ -38,7 +38,7 @@ public:
if (state == ComponentList::SCROLL_UP ||
state == ComponentList::SCROLL_UP_DOWN ||
state == ComponentList::SCROLL_DOWN) {
scrollDown->setOpacity(255);
scrollDown->setOpacity(1.0f);
}
});
}
@ -60,7 +60,7 @@ public:
if (state == ComponentList::SCROLL_UP &&
mPreviousScrollState == ComponentList::SCROLL_NONE) {
scrollUp->setOpacity(255);
scrollUp->setOpacity(1.0f);
}
else if (state == ComponentList::SCROLL_UP &&
mPreviousScrollState == ComponentList::SCROLL_UP_DOWN) {
@ -70,12 +70,12 @@ public:
mPreviousScrollState == ComponentList::SCROLL_DOWN) {
upFadeIn = true;
fadeTime *= 2.0f;
scrollDown->setOpacity(0);
scrollDown->setOpacity(0.0f);
}
else if (state == ComponentList::SCROLL_UP_DOWN &&
mPreviousScrollState == ComponentList::SCROLL_NONE) {
scrollUp->setOpacity(255);
scrollDown->setOpacity(255);
scrollUp->setOpacity(1.0f);
scrollDown->setOpacity(1.0f);
}
else if (state == ComponentList::SCROLL_UP_DOWN &&
mPreviousScrollState == ComponentList::SCROLL_DOWN) {
@ -87,7 +87,7 @@ public:
}
else if (state == ComponentList::SCROLL_DOWN &&
mPreviousScrollState == ComponentList::SCROLL_NONE) {
scrollDown->setOpacity(255);
scrollDown->setOpacity(1.0f);
}
else if (state == ComponentList::SCROLL_DOWN &&
mPreviousScrollState == ComponentList::SCROLL_UP_DOWN) {
@ -97,7 +97,7 @@ public:
mPreviousScrollState == ComponentList::SCROLL_UP) {
downFadeIn = true;
fadeTime *= 2.0f;
scrollUp->setOpacity(0);
scrollUp->setOpacity(0.0f);
}
// If jumping more than one row using the shoulder or trigger buttons, then
@ -107,8 +107,7 @@ public:
if (upFadeIn) {
auto upFadeInFunc = [scrollUp](float t) {
scrollUp->setOpacity(
static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
scrollUp->setOpacity(glm::mix(0.0f, 1.0f, t));
};
scrollUp->setAnimation(
new LambdaAnimation(upFadeInFunc, static_cast<int>(fadeTime)), 0,
@ -117,8 +116,7 @@ public:
if (upFadeOut) {
auto upFadeOutFunc = [scrollUp](float t) {
scrollUp->setOpacity(
static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
scrollUp->setOpacity(glm::mix(0.0f, 1.0f, t));
};
scrollUp->setAnimation(
new LambdaAnimation(upFadeOutFunc, static_cast<int>(fadeTime)), 0,
@ -127,8 +125,7 @@ public:
if (downFadeIn) {
auto downFadeInFunc = [scrollDown](float t) {
scrollDown->setOpacity(
static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
scrollDown->setOpacity(glm::mix(0.0f, 1.0f, t));
};
scrollDown->setAnimation(
new LambdaAnimation(downFadeInFunc, static_cast<int>(fadeTime)), 0,
@ -137,8 +134,7 @@ public:
if (downFadeOut) {
auto downFadeOutFunc = [scrollDown](float t) {
scrollDown->setOpacity(
static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
scrollDown->setOpacity(glm::mix(0.0f, 1.0f, t));
};
scrollDown->setAnimation(
new LambdaAnimation(downFadeOutFunc, static_cast<int>(fadeTime)), 0,

View file

@ -77,6 +77,34 @@ void ScrollableContainer::reset()
}
}
void ScrollableContainer::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element, properties);
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "text")};
if (!elem || !elem->has("container"))
return;
if (elem->has("containerScrollSpeed")) {
mAutoScrollSpeedConstant =
AUTO_SCROLL_SPEED / glm::clamp(elem->get<float>("containerScrollSpeed"), 0.1f, 10.0f);
}
if (elem->has("containerStartDelay")) {
mAutoScrollDelayConstant =
glm::clamp(elem->get<float>("containerStartDelay"), 0.0f, 10.0f) * 1000.0f;
}
if (elem->has("containerResetDelay")) {
mAutoScrollResetDelayConstant =
glm::clamp(elem->get<float>("containerResetDelay"), 0.0f, 20.0f) * 1000.0f;
}
}
void ScrollableContainer::update(int deltaTime)
{
if (mSize == glm::vec2 {0.0f, 0.0f})
@ -169,9 +197,8 @@ void ScrollableContainer::update(int deltaTime)
255.0f};
auto func = [this, maxOpacity](float t) {
unsigned int color {mChildren.front()->getColor()};
unsigned int opacity {
static_cast<unsigned int>(glm::mix(0.0f, maxOpacity, t) * 255)};
color = (color & 0xFFFFFF00) + opacity;
float opacity {glm::mix(0.0f, maxOpacity, t)};
color = (color & 0xFFFFFF00) + static_cast<unsigned char>(opacity * 255.0f);
this->mChildren.front()->setColor(color);
mScrollPos = glm::vec2 {};
mAutoScrollResetAccumulator = 0;
@ -187,7 +214,7 @@ void ScrollableContainer::update(int deltaTime)
void ScrollableContainer::render(const glm::mat4& parentTrans)
{
if (!isVisible())
if (!isVisible() || mThemeOpacity == 0.0f || mChildren.front()->getValue() == "")
return;
glm::mat4 trans {parentTrans * getTransform()};

View file

@ -33,6 +33,11 @@ public:
float autoScrollSpeedConstant) override;
void reset();
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties) override;
void update(int deltaTime) override;
void render(const glm::mat4& parentTrans) override;

View file

@ -32,8 +32,8 @@ public:
void setChangedColor(unsigned int color) override { mColorChangedValue = color; }
void setCallback(const std::function<void()>& callbackFunc) { mToggleCallback = callbackFunc; }
unsigned char getOpacity() const override { return mImage.getOpacity(); }
void setOpacity(unsigned char opacity) override { mImage.setOpacity(opacity); }
float const getOpacity() const override { return mImage.getOpacity(); }
void setOpacity(float opacity) override { mImage.setOpacity(opacity); }
// Multiply all pixels in the image by this color when rendering.
void setColorShift(unsigned int color) override { mImage.setColorShift(color); }

View file

@ -16,10 +16,12 @@ TextComponent::TextComponent()
: mFont {Font::get(FONT_SIZE_MEDIUM)}
, mColor {0x000000FF}
, mBgColor {0x00000000}
, mColorOpacity {0x000000FF}
, mBgColorOpacity {0x00000000}
, mColorOpacity {1.0f}
, mBgColorOpacity {0.0f}
, mRenderBackground {false}
, mUppercase {false}
, mLowercase {false}
, mCapitalize {false}
, mAutoCalcExtent {1, 1}
, mHorizontalAlignment {ALIGN_LEFT}
, mVerticalAlignment {ALIGN_CENTER}
@ -39,10 +41,12 @@ TextComponent::TextComponent(const std::string& text,
: mFont {nullptr}
, mColor {0x000000FF}
, mBgColor {0x00000000}
, mColorOpacity {0x000000FF}
, mBgColorOpacity {0x00000000}
, mColorOpacity {1.0f}
, mBgColorOpacity {0.0f}
, mRenderBackground {false}
, mUppercase {false}
, mLowercase {false}
, mCapitalize {false}
, mAutoCalcExtent {1, 1}
, mHorizontalAlignment {align}
, mVerticalAlignment {ALIGN_CENTER}
@ -77,7 +81,7 @@ void TextComponent::setFont(const std::shared_ptr<Font>& font)
void TextComponent::setColor(unsigned int color)
{
mColor = color;
mColorOpacity = mColor & 0x000000FF;
mColorOpacity = static_cast<float>(mColor & 0x000000FF) / 255.0f;
onColorChanged();
}
@ -85,22 +89,17 @@ void TextComponent::setColor(unsigned int color)
void TextComponent::setBackgroundColor(unsigned int color)
{
mBgColor = color;
mBgColorOpacity = mBgColor & 0x000000FF;
mBgColorOpacity = static_cast<float>(mBgColor & 0x000000FF) / 255.0f;
}
// Scale the opacity.
void TextComponent::setOpacity(unsigned char opacity)
void TextComponent::setOpacity(float opacity)
{
// This function is mostly called to do fade in and fade out of the text component element.
// Therefore we assume here that opacity is a fractional value (expressed as an unsigned
// char 0 - 255) of the opacity originally set with setColor() or setBackgroundColor().
unsigned char o = static_cast<unsigned char>(static_cast<float>(opacity) / 255.0f *
static_cast<float>(mColorOpacity));
mColor = (mColor & 0xFFFFFF00) | static_cast<unsigned char>(o);
float textOpacity {opacity * mColorOpacity * mThemeOpacity};
mColor = (mColor & 0xFFFFFF00) | static_cast<unsigned char>(textOpacity * 255.0f);
unsigned char bgo = static_cast<unsigned char>(static_cast<float>(opacity) / 255.0f *
static_cast<float>(mBgColorOpacity));
mBgColor = (mBgColor & 0xFFFFFF00) | static_cast<unsigned char>(bgo);
float textBackgroundOpacity {opacity * mBgColorOpacity * mThemeOpacity};
mBgColor = (mBgColor & 0xFFFFFF00) | static_cast<unsigned char>(textBackgroundOpacity * 255.0f);
onColorChanged();
GuiComponent::setOpacity(opacity);
@ -120,12 +119,36 @@ void TextComponent::setText(const std::string& text, bool update)
void TextComponent::setUppercase(bool uppercase)
{
mUppercase = uppercase;
if (uppercase) {
mLowercase = false;
mCapitalize = false;
}
onTextChanged();
}
void TextComponent::setLowercase(bool lowercase)
{
mLowercase = lowercase;
if (lowercase) {
mUppercase = false;
mCapitalize = false;
}
onTextChanged();
}
void TextComponent::setCapitalize(bool capitalize)
{
mCapitalize = capitalize;
if (capitalize) {
mUppercase = false;
mLowercase = false;
}
onTextChanged();
}
void TextComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible())
if (!isVisible() || mThemeOpacity == 0.0f)
return;
glm::mat4 trans {parentTrans * getTransform()};
@ -198,14 +221,37 @@ void TextComponent::render(const glm::mat4& parentTrans)
void TextComponent::calculateExtent()
{
if (mAutoCalcExtent.x) {
mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing);
if (mUppercase)
mSize = mFont->sizeText(Utils::String::toUpper(mText), mLineSpacing);
else if (mLowercase)
mSize = mFont->sizeText(Utils::String::toLower(mText), mLineSpacing);
else if (mCapitalize)
mSize = mFont->sizeText(Utils::String::toCapitalized(mText), mLineSpacing);
else
mSize = mFont->sizeText(mText, mLineSpacing); // Original case.
}
else {
if (mAutoCalcExtent.y)
mSize.y = mFont
->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText) : mText,
getSize().x, mLineSpacing)
.y;
if (mAutoCalcExtent.y) {
if (mUppercase) {
mSize.y =
mFont->sizeWrappedText(Utils::String::toUpper(mText), getSize().x, mLineSpacing)
.y;
}
else if (mLowercase) {
mSize.y =
mFont->sizeWrappedText(Utils::String::toLower(mText), getSize().x, mLineSpacing)
.y;
}
else if (mCapitalize) {
mSize.y = mFont
->sizeWrappedText(Utils::String::toCapitalized(mText), getSize().x,
mLineSpacing)
.y;
}
else {
mSize.y = mFont->sizeWrappedText(mText, getSize().x, mLineSpacing).y;
}
}
}
}
@ -218,7 +264,16 @@ void TextComponent::onTextChanged()
return;
}
std::string text = mUppercase ? Utils::String::toUpper(mText) : mText;
std::string text;
if (mUppercase)
text = Utils::String::toUpper(mText);
else if (mLowercase)
text = Utils::String::toLower(mText);
else if (mCapitalize)
text = Utils::String::toCapitalized(mText);
else
text = mText; // Original case.
std::shared_ptr<Font> f = mFont;
const bool isMultiline = (mSize.y == 0.0f || mSize.y > f->getHeight() * 1.2f);
@ -246,17 +301,18 @@ void TextComponent::onTextChanged()
}
text.append(abbrev);
mTextCache = std::shared_ptr<TextCache>(
f->buildTextCache(text, glm::vec2 {}, (mColor >> 8 << 8) | mOpacity, mSize.x,
mHorizontalAlignment, mLineSpacing, mNoTopMargin));
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(
text, glm::vec2 {}, mColor, mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin));
}
else {
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(
f->wrapText(text, mSize.x), glm::vec2 {}, (mColor >> 8 << 8) | mOpacity, mSize.x,
mHorizontalAlignment, mLineSpacing, mNoTopMargin));
mTextCache = std::shared_ptr<TextCache>(
f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {}, mColor, mSize.x,
mHorizontalAlignment, mLineSpacing, mNoTopMargin));
}
if (mOpacity != 1.0f || mThemeOpacity != 1.0f)
setOpacity(mOpacity);
// This is required to set the color transparency.
onColorChanged();
}
@ -302,9 +358,12 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
GuiComponent::applyTheme(theme, view, element, properties);
std::string elementType {"text"};
std::string componentName {"TextComponent"};
if (element.substr(0, 13) == "gamelistinfo_")
if (element.substr(0, 13) == "gamelistinfo_") {
elementType = "gamelistinfo";
componentName = "gamelistInfoComponent";
}
const ThemeData::ThemeElement* elem = theme->getElement(view, element, elementType);
if (!elem)
@ -319,8 +378,8 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setRenderBackground(true);
}
if (properties & ALIGNMENT && elem->has("alignment")) {
std::string str = elem->get<std::string>("alignment");
if (properties & ALIGNMENT && elem->has("horizontalAlignment")) {
std::string str {elem->get<std::string>("horizontalAlignment")};
if (str == "left")
setHorizontalAlignment(ALIGN_LEFT);
else if (str == "center")
@ -328,20 +387,76 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else if (str == "right")
setHorizontalAlignment(ALIGN_RIGHT);
else
LOG(LogError) << "Unknown text alignment string: " << str;
LOG(LogWarning) << componentName
<< ": Invalid theme configuration, property "
"<horizontalAlignment> set to \""
<< str << "\"";
}
if (properties & ALIGNMENT && elem->has("verticalAlignment")) {
std::string str {elem->get<std::string>("verticalAlignment")};
if (str == "top")
setVerticalAlignment(ALIGN_TOP);
else if (str == "center")
setVerticalAlignment(ALIGN_CENTER);
else if (str == "bottom")
setVerticalAlignment(ALIGN_BOTTOM);
else
LOG(LogWarning) << componentName
<< ": Invalid theme configuration, property "
"<verticalAlignment> set to \""
<< str << "\"";
}
// Legacy themes only.
if (properties & ALIGNMENT && elem->has("alignment")) {
std::string str {elem->get<std::string>("alignment")};
if (str == "left")
setHorizontalAlignment(ALIGN_LEFT);
else if (str == "center")
setHorizontalAlignment(ALIGN_CENTER);
else if (str == "right")
setHorizontalAlignment(ALIGN_RIGHT);
else
LOG(LogWarning) << componentName
<< ": Invalid theme configuration, property "
"<alignment> set to \""
<< str << "\"";
}
if (properties & TEXT && elem->has("text"))
setText(elem->get<std::string>("text"));
if (properties & METADATA && elem->has("metadata"))
setMetadataField(elem->get<std::string>("metadata"));
if (properties & METADATA && elem->has("systemdata"))
mThemeSystemdata = elem->get<std::string>("systemdata");
if (properties & METADATA && elem->has("metadata"))
mThemeMetadata = elem->get<std::string>("metadata");
if (properties & LETTER_CASE && elem->has("letterCase")) {
std::string letterCase {elem->get<std::string>("letterCase")};
if (letterCase == "uppercase") {
setUppercase(true);
}
else if (letterCase == "lowercase") {
setLowercase(true);
}
else if (letterCase == "capitalize") {
setCapitalize(true);
}
else if (letterCase != "none") {
LOG(LogWarning)
<< "TextComponent: Invalid theme configuration, property <letterCase> set to \""
<< letterCase << "\"";
}
}
// Legacy themes only.
if (properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase"));
if (properties & LINE_SPACING && elem->has("lineSpacing"))
setLineSpacing(elem->get<float>("lineSpacing"));
setLineSpacing(glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f));
setFont(Font::getFromTheme(elem, properties, mFont));
}

View file

@ -35,6 +35,8 @@ public:
void setFont(const std::shared_ptr<Font>& font);
void setUppercase(bool uppercase);
void setLowercase(bool lowercase);
void setCapitalize(bool capitalize);
void onSizeChanged() override;
void setText(const std::string& text, bool update = true);
void setHiddenText(const std::string& text) { mHiddenText = text; }
@ -55,8 +57,11 @@ public:
std::string getHiddenValue() const override { return mHiddenText; }
void setHiddenValue(const std::string& value) override { setHiddenText(value); }
unsigned char getOpacity() const override { return mColor & 0x000000FF; }
void setOpacity(unsigned char opacity) override;
float const getOpacity() const override
{
return static_cast<float>((mColor & 0x000000FF) / 255.0f);
}
void setOpacity(float opacity) override;
void setSelectable(bool status) { mSelectable = status; }
@ -85,11 +90,13 @@ private:
unsigned int mColor;
unsigned int mBgColor;
unsigned char mColorOpacity;
unsigned char mBgColorOpacity;
float mColorOpacity;
float mBgColorOpacity;
bool mRenderBackground;
bool mUppercase;
bool mLowercase;
bool mCapitalize;
glm::ivec2 mAutoCalcExtent;
std::shared_ptr<TextCache> mTextCache;
Alignment mHorizontalAlignment;

View file

@ -258,8 +258,8 @@ void TextEditComponent::setCursor(size_t pos)
void TextEditComponent::onTextChanged()
{
std::string wrappedText = (isMultiline() ? mFont->wrapText(mText, getTextAreaSize().x) : mText);
mTextCache = std::unique_ptr<TextCache>(
mFont->buildTextCache(wrappedText, 0.0f, 0.0f, 0x77777700 | getOpacity()));
mTextCache = std::unique_ptr<TextCache>(mFont->buildTextCache(
wrappedText, 0.0f, 0.0f, 0x77777700 | static_cast<unsigned char>(mOpacity * 255.0f)));
if (mCursor > static_cast<int>(mText.length()))
mCursor = static_cast<int>(mText.length());

View file

@ -78,6 +78,38 @@ public:
void setUppercase(bool uppercase)
{
mUppercase = uppercase;
if (uppercase) {
mLowercase = false;
mCapitalize = false;
}
for (auto it = mEntries.begin(); it != mEntries.end(); ++it)
it->data.textCache.reset();
}
void setLowercase(bool lowercase)
{
mLowercase = lowercase;
if (lowercase) {
mUppercase = false;
mCapitalize = false;
}
for (auto it = mEntries.begin(); it != mEntries.end(); ++it)
it->data.textCache.reset();
}
void setCapitalize(bool capitalize)
{
mCapitalize = capitalize;
if (capitalize) {
mUppercase = false;
mLowercase = false;
}
for (auto it = mEntries.begin(); it != mEntries.end(); ++it)
it->data.textCache.reset();
}
@ -115,6 +147,8 @@ private:
std::shared_ptr<Font> mFont;
bool mUppercase;
bool mLowercase;
bool mCapitalize;
float mLineSpacing;
float mSelectorHeight;
float mSelectorOffsetY;
@ -140,6 +174,8 @@ template <typename T> TextListComponent<T>::TextListComponent()
mFont = Font::get(FONT_SIZE_MEDIUM);
mUppercase = false;
mLowercase = false;
mCapitalize = false;
mLineSpacing = 1.5f;
mSelectorHeight = mFont->getSize() * 1.5f;
mSelectorOffsetY = 0;
@ -229,9 +265,20 @@ template <typename T> void TextListComponent<T>::render(const glm::mat4& parentT
else
color = mColors[entry.data.colorId];
if (!entry.data.textCache)
entry.data.textCache = std::unique_ptr<TextCache>(font->buildTextCache(
mUppercase ? Utils::String::toUpper(entry.name) : entry.name, 0, 0, 0x000000FF));
if (!entry.data.textCache) {
if (mUppercase)
entry.data.textCache = std::unique_ptr<TextCache>(
font->buildTextCache(Utils::String::toUpper(entry.name), 0, 0, 0x000000FF));
else if (mLowercase)
entry.data.textCache = std::unique_ptr<TextCache>(
font->buildTextCache(Utils::String::toLower(entry.name), 0, 0, 0x000000FF));
else if (mCapitalize)
entry.data.textCache = std::unique_ptr<TextCache>(font->buildTextCache(
Utils::String::toCapitalized(entry.name), 0, 0, 0x000000FF));
else
entry.data.textCache =
std::unique_ptr<TextCache>(font->buildTextCache(entry.name, 0, 0, 0x000000FF));
}
// If a game is marked as hidden, lower the text opacity a lot.
// If a game is marked to not be counted, lower the opacity a moderate amount.
@ -427,9 +474,21 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
}
if (elem->has("selectorColorEnd"))
setSelectorColorEnd(elem->get<unsigned int>("selectorColorEnd"));
if (elem->has("selectorGradientType"))
setSelectorColorGradientHorizontal(
!(elem->get<std::string>("selectorGradientType").compare("horizontal")));
if (elem->has("selectorGradientType")) {
const std::string gradientType {elem->get<std::string>("selectorGradientType")};
if (gradientType == "horizontal") {
setSelectorColorGradientHorizontal(true);
}
else if (gradientType == "vertical") {
setSelectorColorGradientHorizontal(false);
}
else {
setSelectorColorGradientHorizontal(true);
LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property "
"<selectorGradientType> set to \""
<< gradientType << "\"";
}
}
if (elem->has("selectedColor"))
setSelectedColor(elem->get<unsigned int>("selectedColor"));
if (elem->has("primaryColor"))
@ -444,8 +503,8 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
setSelectorHeight(selectorHeight);
if (properties & ALIGNMENT) {
if (elem->has("alignment")) {
const std::string& str = elem->get<std::string>("alignment");
if (elem->has("horizontalAlignment")) {
const std::string& str {elem->get<std::string>("horizontalAlignment")};
if (str == "left")
setAlignment(ALIGN_LEFT);
else if (str == "center")
@ -453,21 +512,56 @@ void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
else if (str == "right")
setAlignment(ALIGN_RIGHT);
else
LOG(LogError) << "Unknown TextListComponent alignment \"" << str << "\"!";
LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property "
"<horizontalAlignment> set to \""
<< str << "\"";
}
// Legacy themes only.
else if (elem->has("alignment")) {
const std::string& str {elem->get<std::string>("alignment")};
if (str == "left")
setAlignment(ALIGN_LEFT);
else if (str == "center")
setAlignment(ALIGN_CENTER);
else if (str == "right")
setAlignment(ALIGN_RIGHT);
else
LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property "
"<alignment> set to \""
<< str << "\"";
}
if (elem->has("horizontalMargin")) {
mHorizontalMargin = elem->get<float>("horizontalMargin") *
(this->mParent ? this->mParent->getSize().x :
static_cast<float>(Renderer::getScreenWidth()));
mHorizontalMargin =
elem->get<float>("horizontalMargin") *
(this->mParent ? this->mParent->getSize().x : Renderer::getScreenWidth());
}
}
if (properties & LETTER_CASE && elem->has("letterCase")) {
std::string letterCase {elem->get<std::string>("letterCase")};
if (letterCase == "uppercase") {
setUppercase(true);
}
else if (letterCase == "lowercase") {
setLowercase(true);
}
else if (letterCase == "capitalize") {
setCapitalize(true);
}
else if (letterCase != "none") {
LOG(LogWarning)
<< "TextListComponent: Invalid theme configuration, property <letterCase> set to \""
<< letterCase << "\"";
}
}
// Legacy themes only.
if (properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase"));
if (properties & LINE_SPACING) {
if (elem->has("lineSpacing"))
setLineSpacing(elem->get<float>("lineSpacing"));
setLineSpacing(glm::clamp(elem->get<float>("lineSpacing"), 0.5f, 3.0f));
if (elem->has("selectorHeight"))
setSelectorHeight(elem->get<float>("selectorHeight") * Renderer::getScreenHeight());
if (elem->has("selectorOffsetY")) {

View file

@ -12,6 +12,7 @@
#include "Window.h"
#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include <SDL2/SDL_timer.h>
@ -24,39 +25,37 @@ VideoComponent::VideoComponent()
, mTargetSize {0.0f, 0.0f}
, mVideoAreaPos {0.0f, 0.0f}
, mVideoAreaSize {0.0f, 0.0f}
, mStartDelayed {false}
, mStartTime {0}
, mIsPlaying {false}
, mIsActuallyPlaying {false}
, mPause {false}
, mShowing {false}
, mDisable {false}
, mPaused {false}
, mMediaViewerMode {false}
, mScreensaverActive {false}
, mScreensaverMode {false}
, mGameLaunched {false}
, mBlockPlayer {false}
, mTargetIsMax {false}
, mPlayAudio {true}
, mDrawPillarboxes {true}
, mRenderScanlines {false}
, mLegacyTheme {false}
, mHasVideo {false}
, mFadeIn {1.0f}
, mFadeInTime {1000.0f}
{
// Setup the default configuration.
// Setup default configuration.
mConfig.showSnapshotDelay = false;
mConfig.showSnapshotNoVideo = false;
mConfig.startDelay = 0;
if (mWindow->getGuiStackSize() > 1)
topWindow(false);
mConfig.startDelay = 1500;
}
VideoComponent::~VideoComponent()
{
// Stop any currently running video.
stopVideo();
stopVideoPlayer();
}
bool VideoComponent::setVideo(std::string path)
{
// Convert the path into a generic format.
std::string fullPath = Utils::FileSystem::getCanonicalPath(path);
std::string fullPath {Utils::FileSystem::getCanonicalPath(path)};
// Check that it's changed.
if (fullPath == mVideoPath)
@ -67,143 +66,34 @@ bool VideoComponent::setVideo(std::string path)
// If the file exists then set the new video.
if (!fullPath.empty() && ResourceManager::getInstance().fileExists(fullPath)) {
mHasVideo = true;
// Return true to show that we are going to attempt to play a video.
return true;
}
if (!mVideoPath.empty() || !mConfig.defaultVideoPath.empty() ||
!mConfig.staticVideoPath.empty())
mHasVideo = true;
else
mHasVideo = false;
// Return false to show that no video will be displayed.
return false;
}
void VideoComponent::setImage(const std::string& path, bool tile, bool linearMagnify)
void VideoComponent::setImage(const std::string& path, bool tile)
{
// Check that the image has changed.
if (path == mStaticImagePath)
std::string imagePath {path};
if (imagePath == "")
imagePath = mDefaultImagePath;
// Check if the image has changed.
if (imagePath == mStaticImagePath)
return;
mStaticImage.setImage(path, tile, linearMagnify);
mStaticImagePath = path;
}
void VideoComponent::onShow()
{
mBlockPlayer = false;
mPause = false;
mShowing = true;
manageState();
}
void VideoComponent::onHide()
{
mShowing = false;
manageState();
}
void VideoComponent::onStopVideo()
{
stopVideo();
manageState();
}
void VideoComponent::onPauseVideo()
{
mBlockPlayer = true;
mPause = true;
manageState();
}
void VideoComponent::onUnpauseVideo()
{
mBlockPlayer = false;
mPause = false;
manageState();
}
void VideoComponent::onScreensaverActivate()
{
mBlockPlayer = true;
mPause = true;
if (Settings::getInstance()->getString("ScreensaverType") == "dim")
stopVideo();
else
pauseVideo();
manageState();
}
void VideoComponent::onScreensaverDeactivate()
{
mBlockPlayer = false;
// Stop video when deactivating the screensaver to force a reload of the
// static image (if the theme is configured as such).
stopVideo();
manageState();
}
void VideoComponent::onGameLaunchedActivate()
{
mGameLaunched = true;
manageState();
}
void VideoComponent::onGameLaunchedDeactivate()
{
mGameLaunched = false;
stopVideo();
manageState();
}
void VideoComponent::topWindow(bool isTop)
{
if (isTop) {
mBlockPlayer = false;
mPause = false;
// Stop video when closing the menu to force a reload of the
// static image (if the theme is configured as such).
stopVideo();
}
else {
mBlockPlayer = true;
mPause = true;
}
manageState();
}
void VideoComponent::render(const glm::mat4& parentTrans)
{
if (!isVisible())
return;
glm::mat4 trans {parentTrans * getTransform()};
GuiComponent::renderChildren(trans);
Renderer::setMatrix(trans);
// Handle the case where the video is delayed.
handleStartDelay();
// Handle looping of the video.
handleLooping();
// Pause video in case a game has been launched.
pauseVideo();
}
void VideoComponent::renderSnapshot(const glm::mat4& parentTrans)
{
// This function is called when the video is not currently being played. We need to
// work out if we should display a static image. If the menu is open, then always render
// the static image as the metadata may have been changed. In that case the gamelist
// was reloaded and there would just be a blank space unless we render the image here.
// The side effect of this is that a static image is displayed even for themes that are
// set to start playing the video immediately. Although this may seem a bit inconsistent it
// simply looks better than leaving an empty space where the video would have been located.
if (mWindow->getGuiStackSize() > 1 || (mConfig.showSnapshotNoVideo && mVideoPath.empty()) ||
(mStartDelayed && mConfig.showSnapshotDelay)) {
mStaticImage.setOpacity(mOpacity);
mStaticImage.render(parentTrans);
}
mStaticImage.setImage(imagePath, tile);
mStaticImagePath = imagePath;
}
void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
@ -218,12 +108,14 @@ void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "video");
mLegacyTheme = theme->isLegacyTheme();
if (!elem)
return;
glm::vec2 scale {getParent() ? getParent()->getSize() :
glm::vec2 {static_cast<float>(Renderer::getScreenWidth()),
static_cast<float>(Renderer::getScreenHeight())}};
glm::vec2 scale {getParent() ?
getParent()->getSize() :
glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}};
if (properties & ThemeFlags::SIZE) {
if (elem->has("size")) {
@ -241,19 +133,40 @@ void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
mVideoAreaPos = elem->get<glm::vec2>("pos") * scale;
}
if (elem->has("audio"))
mPlayAudio = elem->get<bool>("audio");
if (elem->has("interpolation")) {
const std::string interpolation {elem->get<std::string>("interpolation")};
if (interpolation == "linear") {
mStaticImage.setLinearInterpolation(true);
}
else if (interpolation == "nearest") {
mStaticImage.setLinearInterpolation(false);
}
else {
mStaticImage.setLinearInterpolation(false);
LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property "
"<interpolation> set to \""
<< interpolation << "\"";
}
}
if (elem->has("default"))
mConfig.defaultVideoPath = elem->get<std::string>("default");
if (elem->has("defaultImage")) {
mStaticImage.setDefaultImage(elem->get<std::string>("defaultImage"));
mStaticImage.setImage(mStaticImagePath);
mDefaultImagePath = elem->get<std::string>("defaultImage");
}
if (elem->has("path"))
mConfig.staticVideoPath = elem->get<std::string>("path");
if ((properties & ThemeFlags::DELAY) && elem->has("delay"))
mConfig.startDelay = static_cast<unsigned>((elem->get<float>("delay") * 1000.0f));
mConfig.startDelay =
static_cast<unsigned int>(glm::clamp(elem->get<float>("delay"), 0.0f, 15.0f) * 1000.0f);
if (!theme->isLegacyTheme())
mConfig.showSnapshotNoVideo = true;
@ -265,8 +178,24 @@ void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else if (elem->has("showSnapshotDelay"))
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay");
if (properties & METADATA && elem->has("imageMetadata"))
setMetadataField(elem->get<std::string>("imageMetadata"));
if (properties && elem->has("fadeInTime"))
mFadeInTime = glm::clamp(elem->get<float>("fadeInTime"), 0.0f, 8.0f) * 1000.0f;
if (properties && elem->has("imageType")) {
std::string imageTypes {elem->get<std::string>("imageType")};
for (auto& character : imageTypes) {
if (std::isspace(character))
character = ',';
}
imageTypes = Utils::String::replace(imageTypes, ",,", ",");
mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ",");
}
if (elem->has("pillarboxes"))
mDrawPillarboxes = elem->get<bool>("pillarboxes");
if (elem->has("scanlines"))
mRenderScanlines = elem->get<bool>("scanlines");
if (elem->has("scrollFadeIn") && elem->get<bool>("scrollFadeIn"))
mComponentThemeFlags |= ComponentThemeFlags::SCROLL_FADE_IN;
@ -281,15 +210,33 @@ std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
void VideoComponent::update(int deltaTime)
{
if (mBlockPlayer) {
setImage(mStaticImagePath);
if (!mHasVideo) {
// We need this update so the static image gets updated (e.g. used for fade animations).
GuiComponent::update(deltaTime);
return;
}
manageState();
// Hack to prevent the video from starting to play if the static image was shown when paused.
if (mPaused)
mStartTime = SDL_GetTicks() + mConfig.startDelay;
// Fade in videos, the time period is a bit different between the screensaver,
// media viewer and gamelist view.
if (mWindow->getGameLaunchedState())
return;
bool playVideo {false};
if (!mIsPlaying && mConfig.startDelay == 0) {
startVideoStream();
}
else if (mStartTime == 0 || SDL_GetTicks() > mStartTime) {
mStartTime = 0;
playVideo = true;
startVideoStream();
}
// Fade in videos, the time period is a bit different between the screensaver and media viewer.
// For the theme controlled videos in the gamelist and system views, the fade-in time is set
// via the theme configuration.
if (mScreensaverMode && mFadeIn < 1.0f) {
mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast<float>(SCREENSAVER_FADE_IN_TIME)),
0.0f, 1.0f);
@ -299,93 +246,40 @@ void VideoComponent::update(int deltaTime)
0.0f, 1.0f);
}
else if (mFadeIn < 1.0f) {
mFadeIn = glm::clamp(mFadeIn + 0.01f, 0.0f, 1.0f);
mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast<float>(mFadeInTime)), 0.0f, 1.0f);
}
if (mIsPlaying)
updatePlayer();
handleLooping();
GuiComponent::update(deltaTime);
}
void VideoComponent::startVideoWithDelay()
void VideoComponent::startVideoPlayer()
{
mPause = false;
if (mIsPlaying)
stopVideoPlayer();
// If not playing then either start the video or initiate the delay.
if (!mIsPlaying) {
// Set the video that we are going to be playing so we don't attempt to restart it.
mPlayingVideoPath = mVideoPath;
if (mConfig.startDelay == 0) {
// No delay. Just start the video.
mStartDelayed = false;
startVideo();
}
else {
// Configure the start delay.
mStartDelayed = true;
mStartTime = SDL_GetTicks() + mConfig.startDelay;
}
mIsPlaying = true;
if (mConfig.startDelay != 0 && mStaticImagePath != "") {
mStartTime = SDL_GetTicks() + mConfig.startDelay;
setImage(mStaticImagePath);
}
mPaused = false;
}
void VideoComponent::handleStartDelay()
void VideoComponent::renderSnapshot(const glm::mat4& parentTrans)
{
if (mBlockPlayer || mGameLaunched)
if (mLegacyTheme && !mHasVideo && !mConfig.showSnapshotNoVideo)
return;
// Only play if any delay has timed out.
if (mStartDelayed) {
// If the setting to override the theme-supplied video delay setting has been enabled,
// then play the video immediately.
if (!Settings::getInstance()->getBool("PlayVideosImmediately")) {
// If there is a video file available but no static image, then start playing the
// video immediately regardless of theme configuration or settings.
if (mStaticImagePath != "") {
if (mStartTime > SDL_GetTicks()) {
// Timeout not yet completed.
return;
}
}
}
// Completed.
mStartDelayed = false;
// Clear the playing flag so startVideo works.
mIsPlaying = false;
startVideo();
if (mHasVideo && (!mConfig.showSnapshotDelay || mConfig.startDelay == 0))
return;
if (mStaticImagePath != "") {
mStaticImage.setOpacity(mOpacity * mThemeOpacity);
mStaticImage.render(parentTrans);
}
}
void VideoComponent::manageState()
{
// We will only show the video if the component is on display and the screensaver
// is not active.
bool show = mShowing && !mScreensaverActive && !mDisable;
// See if we're already playing.
if (mIsPlaying) {
// If we are not on display then stop the video from playing.
if (!show) {
stopVideo();
}
else {
if (mVideoPath != mPlayingVideoPath) {
// Path changed. Stop the video. We will start it again below because
// mIsPlaying will be modified by stopVideo to be false.
stopVideo();
}
}
updatePlayer();
}
// Need to recheck variable rather than 'else' because it may be modified above.
if (!mIsPlaying) {
// If we are on display then see if we should start the video.
if (show && !mVideoPath.empty())
startVideoWithDelay();
}
// If a game has just been launched and a video is actually shown, then request a
// pause of the video so it doesn't continue to play in the background while the
// game is running.
if (mGameLaunched && show && !mPause)
mPause = true;
}

View file

@ -40,36 +40,31 @@ public:
// Configures the component to show the static video.
void setStaticVideo() { setVideo(mConfig.staticVideoPath); }
// Loads a static image that is displayed if the video cannot be played.
void setImage(const std::string& path, bool tile = false, bool linearMagnify = false);
void setImage(const std::string& path, bool tile = false) override;
// Sets whether we're in media viewer mode.
void setMediaViewerMode(bool isMediaViewer) { mMediaViewerMode = isMediaViewer; }
// Sets whether we're in screensaver mode.
void setScreensaverMode(bool isScreensaver) { mScreensaverMode = isScreensaver; }
// Set the opacity for the embedded static image.
void setOpacity(unsigned char opacity) override { mOpacity = opacity; }
void setOpacity(float opacity) override { mOpacity = opacity; }
// Set whether to draw black pillarboxes/letterboxes behind videos.
void setDrawPillarboxes(bool state) { mDrawPillarboxes = state; }
bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); }
void onShow() override;
void onHide() override;
void onStopVideo() override;
void onPauseVideo() override;
void onUnpauseVideo() override;
bool isVideoPaused() override { return mPause; }
void onScreensaverActivate() override;
void onScreensaverDeactivate() override;
void onGameLaunchedActivate() override;
void onGameLaunchedDeactivate() override;
void topWindow(bool isTop) override;
bool hasStaticImage() { return mStaticImage.getTextureSize() != glm::ivec2 {0, 0}; }
bool hasStartDelay()
{
if (mLegacyTheme)
return mConfig.showSnapshotDelay && mConfig.startDelay > 0;
else
return mConfig.startDelay > 0;
}
// These functions update the embedded static image.
void onOriginChanged() override { mStaticImage.setOrigin(mOrigin); }
void onPositionChanged() override { mStaticImage.setPosition(mPosition); }
void onSizeChanged() override { mStaticImage.onSizeChanged(); }
void render(const glm::mat4& parentTrans) override;
void renderSnapshot(const glm::mat4& parentTrans);
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
@ -92,27 +87,21 @@ public:
virtual void setMaxSize(float width, float height) = 0;
void setMaxSize(const glm::vec2& size) { setMaxSize(size.x, size.y); }
private:
// Start the video immediately.
virtual void startVideo() {}
// Stop the video.
virtual void stopVideo() {}
// Pause the video when a game has been launched.
virtual void pauseVideo() {}
// Basic video controls.
void startVideoPlayer();
virtual void stopVideoPlayer() {}
virtual void pauseVideoPlayer() {}
// Handle looping of the video. Must be called periodically.
virtual void handleLooping() {}
// Used to immediately mute audio even if there are still samples to play in the buffer.
virtual void muteVideoPlayer() {}
virtual void updatePlayer() {}
// Start the video after any configured delay.
void startVideoWithDelay();
// Handle any delay to the start of playing the video clip. Must be called periodically.
void handleStartDelay();
// Manage the playing state of the component.
void manageState();
friend MediaViewer;
protected:
virtual void startVideoStream() {}
void renderSnapshot(const glm::mat4& parentTrans);
ImageComponent mStaticImage;
unsigned mVideoWidth;
@ -122,23 +111,23 @@ protected:
glm::vec2 mVideoAreaSize;
std::shared_ptr<TextureResource> mTexture;
std::string mStaticImagePath;
std::string mDefaultImagePath;
std::string mVideoPath;
std::string mPlayingVideoPath;
unsigned mStartTime;
bool mStartDelayed;
std::atomic<bool> mIsPlaying;
std::atomic<bool> mIsActuallyPlaying;
std::atomic<bool> mPause;
bool mShowing;
bool mDisable;
std::atomic<bool> mPaused;
bool mMediaViewerMode;
bool mScreensaverActive;
bool mScreensaverMode;
bool mGameLaunched;
bool mBlockPlayer;
bool mTargetIsMax;
float mFadeIn; // Used for fading in the video screensaver.
bool mPlayAudio;
bool mDrawPillarboxes;
bool mRenderScanlines;
bool mLegacyTheme;
bool mHasVideo;
float mFadeIn;
float mFadeInTime;
Configuration mConfig;
};

View file

@ -24,7 +24,8 @@ std::vector<std::string> VideoFFmpegComponent::sHWDecodedVideos;
std::vector<std::string> VideoFFmpegComponent::sSWDecodedVideos;
VideoFFmpegComponent::VideoFFmpegComponent()
: mFrameProcessingThread {nullptr}
: mRectangleOffset {0.0f, 0.0f}
, mFrameProcessingThread {nullptr}
, mFormatContext {nullptr}
, mVideoStream {nullptr}
, mAudioStream {nullptr}
@ -54,8 +55,6 @@ VideoFFmpegComponent::VideoFFmpegComponent()
{
}
VideoFFmpegComponent::~VideoFFmpegComponent() { stopVideo(); }
void VideoFFmpegComponent::setResize(float width, float height)
{
// This resize function is used when stretching videos to full screen in the video screensaver.
@ -123,7 +122,12 @@ void VideoFFmpegComponent::resize()
void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
{
VideoComponent::render(parentTrans);
if (!isVisible() || mThemeOpacity == 0.0f)
return;
if (!mHasVideo && mStaticImagePath == "")
return;
glm::mat4 trans {parentTrans * getTransform()};
GuiComponent::renderChildren(trans);
@ -141,18 +145,25 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
Renderer::Vertex vertices[4];
Renderer::setMatrix(parentTrans);
unsigned int rectColor {0x000000FF};
if (mThemeOpacity != 1.0f) {
color = (static_cast<int>(mThemeOpacity * mFadeIn * 255.0f) << 24) + 0x00FFFFFF;
rectColor = static_cast<int>(mThemeOpacity * mFadeIn * 255.0f);
}
// Render the black rectangle behind the video.
if (mVideoRectangleCoords.size() == 4) {
Renderer::drawRect(mVideoRectangleCoords[0], mVideoRectangleCoords[1],
mVideoRectangleCoords[2], mVideoRectangleCoords[3], // Line break.
0x000000FF, 0x000000FF);
rectColor, rectColor);
}
// clang-format off
vertices[0] = {{0.0f, 0.0f }, {0.0f, 0.0f}, color};
vertices[1] = {{0.0f, mSize.y}, {0.0f, 1.0f}, color};
vertices[2] = {{mSize.x, 0.0f }, {1.0f, 0.0f}, color};
vertices[3] = {{mSize.x, mSize.y}, {1.0f, 1.0f}, color};
vertices[0] = {{0.0f + mRectangleOffset.x, 0.0f + mRectangleOffset.y }, {0.0f, 0.0f}, color};
vertices[1] = {{0.0f + mRectangleOffset.x, mSize.y + mRectangleOffset.y }, {0.0f, 1.0f}, color};
vertices[2] = {{mSize.x + mRectangleOffset.x, 0.0f + + mRectangleOffset.y }, {1.0f, 0.0f}, color};
vertices[3] = {{mSize.x + mRectangleOffset.x, mSize.y + + mRectangleOffset.y}, {1.0f, 1.0f}, color};
// clang-format on
// Round vertices.
@ -199,15 +210,23 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
pictureLock.unlock();
}
mTexture->bind();
if (mTexture != nullptr)
mTexture->bind();
#if defined(USE_OPENGL_21)
// Render scanlines if this option is enabled. However, if this is the media viewer
// or the video screensaver, then skip this as the scanline rendering is then handled
// in those modules as a postprocessing step.
if ((!mScreensaverMode && !mMediaViewerMode) &&
Settings::getInstance()->getBool("GamelistVideoScanlines"))
vertices[0].shaders = Renderer::SHADER_SCANLINES;
if (!mScreensaverMode && !mMediaViewerMode) {
vertices[0].opacity = mFadeIn * mThemeOpacity;
if ((mLegacyTheme && Settings::getInstance()->getBool("GamelistVideoScanlines")) ||
(!mLegacyTheme && mRenderScanlines)) {
vertices[0].shaders = Renderer::SHADER_SCANLINES;
}
else {
vertices[0].shaders = Renderer::SHADER_OPACITY;
}
}
#endif
// Render it.
@ -222,7 +241,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
void VideoFFmpegComponent::updatePlayer()
{
if (mPause || !mFormatContext)
if (mPaused || !mFormatContext)
return;
// Output any audio that has been added by the processing thread.
@ -265,7 +284,7 @@ void VideoFFmpegComponent::frameProcessing()
if (mAudioCodecContext)
audioFilter = setupAudioFilters();
while (mIsPlaying && !mPause && videoFilter && (!mAudioCodecContext || audioFilter)) {
while (mIsPlaying && !mPaused && videoFilter && (!mAudioCodecContext || audioFilter)) {
readFrames();
if (!mIsPlaying)
break;
@ -767,7 +786,7 @@ void VideoFFmpegComponent::outputFrames()
bool outputSound = false;
if ((!mScreensaverMode && !mMediaViewerMode) &&
Settings::getInstance()->getBool("GamelistVideoAudio"))
Settings::getInstance()->getBool("ViewsVideoAudio"))
outputSound = true;
else if (mScreensaverMode && Settings::getInstance()->getBool("ScreensaverVideoAudio"))
outputSound = true;
@ -864,10 +883,12 @@ void VideoFFmpegComponent::calculateBlackRectangle()
// otherwise it will exactly match the video size. The reason to add a black rectangle
// behind videos in this second instance is that the scanline rendering will make the
// video partially transparent so this may avoid some unforseen issues with some themes.
if (mVideoAreaPos != glm::vec2 {} && mVideoAreaSize != glm::vec2 {}) {
if (mVideoAreaPos != glm::vec2 {0.0f, 0.0f} && mVideoAreaSize != glm::vec2 {0.0f, 0.0f}) {
mVideoRectangleCoords.clear();
mRectangleOffset = {0.0f, 0.0f};
if (Settings::getInstance()->getBool("GamelistVideoPillarbox")) {
if ((mLegacyTheme && Settings::getInstance()->getBool("GamelistVideoPillarbox")) ||
(!mLegacyTheme && mDrawPillarboxes)) {
float rectHeight;
float rectWidth;
// Video is in landscape orientation.
@ -900,6 +921,15 @@ void VideoFFmpegComponent::calculateBlackRectangle()
std::round(mVideoAreaPos.y - rectHeight * mOrigin.y));
mVideoRectangleCoords.emplace_back(std::round(rectWidth));
mVideoRectangleCoords.emplace_back(std::round(rectHeight));
// If an origin value other than 0.5 is used, then create an offset for centering
// the video inside the rectangle.
if (mOrigin != glm::vec2 {0.5f, 0.5f}) {
if (rectWidth > mSize.x)
mRectangleOffset.x -= (rectWidth - mSize.x) * (mOrigin.x - 0.5f);
else if (rectHeight > mSize.y)
mRectangleOffset.y -= (rectHeight - mSize.y) * (mOrigin.y - 0.5f);
}
}
// If the option to display pillarboxes is disabled, then make the rectangle equivalent
// to the size of the video.
@ -1055,7 +1085,7 @@ bool VideoFFmpegComponent::decoderInitHW()
AVCodecContext* checkCodecContext = avcodec_alloc_context3(mHardwareCodec);
if (avcodec_parameters_to_context(checkCodecContext, mVideoStream->codecpar)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
"Couldn't fill the video codec context parameters for file \""
<< mVideoPath << "\"";
avcodec_free_context(&checkCodecContext);
@ -1068,7 +1098,7 @@ bool VideoFFmpegComponent::decoderInitHW()
checkCodecContext->hw_device_ctx = av_buffer_ref(mHwContext);
if (avcodec_open2(checkCodecContext, mHardwareCodec, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
"Couldn't initialize the video codec context for file \""
<< mVideoPath << "\"";
}
@ -1156,7 +1186,7 @@ bool VideoFFmpegComponent::decoderInitHW()
mVideoCodecContext = avcodec_alloc_context3(mHardwareCodec);
if (!mVideoCodecContext) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
"Couldn't allocate video codec context for file \""
<< mVideoPath << "\"";
avcodec_free_context(&mVideoCodecContext);
@ -1164,7 +1194,7 @@ bool VideoFFmpegComponent::decoderInitHW()
}
if (avcodec_parameters_to_context(mVideoCodecContext, mVideoStream->codecpar)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
"Couldn't fill the video codec context parameters for file \""
<< mVideoPath << "\"";
avcodec_free_context(&mVideoCodecContext);
@ -1175,7 +1205,7 @@ bool VideoFFmpegComponent::decoderInitHW()
mVideoCodecContext->hw_device_ctx = av_buffer_ref(mHwContext);
if (avcodec_open2(mVideoCodecContext, mHardwareCodec, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
"Couldn't initialize the video codec context for file \""
<< mVideoPath << "\"";
avcodec_free_context(&mVideoCodecContext);
@ -1185,8 +1215,13 @@ bool VideoFFmpegComponent::decoderInitHW()
return false;
}
void VideoFFmpegComponent::startVideo()
void VideoFFmpegComponent::startVideoStream()
{
if (mThemeOpacity == 0.0f)
return;
mIsPlaying = true;
if (!mFormatContext) {
mHardwareCodec = nullptr;
mHwContext = nullptr;
@ -1222,14 +1257,14 @@ void VideoFFmpegComponent::startVideo()
// File operations and basic setup.
if (avformat_open_input(&mFormatContext, filePath.c_str(), nullptr, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't open video file \""
<< mVideoPath << "\"";
return;
}
if (avformat_find_stream_info(mFormatContext, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't read stream information from video file \""
<< mVideoPath << "\"";
return;
@ -1250,7 +1285,7 @@ void VideoFFmpegComponent::startVideo()
av_find_best_stream(mFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &mHardwareCodec, 0);
if (mVideoStreamIndex < 0) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't retrieve video stream for file \""
<< mVideoPath << "\"";
avformat_close_input(&mFormatContext);
@ -1262,7 +1297,7 @@ void VideoFFmpegComponent::startVideo()
mVideoWidth = mFormatContext->streams[mVideoStreamIndex]->codecpar->width;
mVideoHeight = mFormatContext->streams[mVideoStreamIndex]->codecpar->height;
LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): "
LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): "
<< "Playing video \"" << mVideoPath << "\" (codec: "
<< avcodec_get_name(
mFormatContext->streams[mVideoStreamIndex]->codecpar->codec_id)
@ -1276,15 +1311,16 @@ void VideoFFmpegComponent::startVideo()
if (mSWDecoder) {
// The hardware decoder initialization failed, which can happen for a number of reasons.
if (hwDecoding) {
LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): Hardware decoding failed, "
"falling back to software decoder";
LOG(LogDebug)
<< "VideoFFmpegComponent::startVideoStream(): Hardware decoding failed, "
"falling back to software decoder";
}
mVideoCodec =
const_cast<AVCodec*>(avcodec_find_decoder(mVideoStream->codecpar->codec_id));
if (!mVideoCodec) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't find a suitable video codec for file \""
<< mVideoPath << "\"";
return;
@ -1293,7 +1329,7 @@ void VideoFFmpegComponent::startVideo()
mVideoCodecContext = avcodec_alloc_context3(mVideoCodec);
if (!mVideoCodecContext) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't allocate video codec context for file \""
<< mVideoPath << "\"";
return;
@ -1303,14 +1339,14 @@ void VideoFFmpegComponent::startVideo()
mVideoCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED;
if (avcodec_parameters_to_context(mVideoCodecContext, mVideoStream->codecpar)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't fill the video codec context parameters for file \""
<< mVideoPath << "\"";
return;
}
if (avcodec_open2(mVideoCodecContext, mVideoCodec, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't initialize the video codec context for file \""
<< mVideoPath << "\"";
return;
@ -1318,47 +1354,50 @@ void VideoFFmpegComponent::startVideo()
}
// Audio stream setup, optional as some videos do not have any audio tracks.
// Audio can also be disabled per video via the theme configuration.
mAudioStreamIndex =
av_find_best_stream(mFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (mPlayAudio) {
mAudioStreamIndex =
av_find_best_stream(mFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (mAudioStreamIndex < 0) {
LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): "
"File does not seem to contain any audio streams";
}
if (mAudioStreamIndex >= 0) {
mAudioStream = mFormatContext->streams[mAudioStreamIndex];
mAudioCodec =
const_cast<AVCodec*>(avcodec_find_decoder(mAudioStream->codecpar->codec_id));
if (!mAudioCodec) {
LOG(LogError) << "Couldn't find a suitable audio codec for file \"" << mVideoPath
<< "\"";
return;
if (mAudioStreamIndex < 0) {
LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): "
"File does not seem to contain any audio streams";
}
mAudioCodecContext = avcodec_alloc_context3(mAudioCodec);
if (mAudioStreamIndex >= 0) {
mAudioStream = mFormatContext->streams[mAudioStreamIndex];
mAudioCodec =
const_cast<AVCodec*>(avcodec_find_decoder(mAudioStream->codecpar->codec_id));
if (mAudioCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
mAudioCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED;
if (!mAudioCodec) {
LOG(LogError) << "Couldn't find a suitable audio codec for file \""
<< mVideoPath << "\"";
return;
}
// Some formats want separate stream headers.
if (mAudioCodecContext->flags & AVFMT_GLOBALHEADER)
mAudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
mAudioCodecContext = avcodec_alloc_context3(mAudioCodec);
if (avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
"Couldn't fill the audio codec context parameters for file \""
<< mVideoPath << "\"";
return;
}
if (mAudioCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
mAudioCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED;
if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
"Couldn't initialize the audio codec context for file \""
<< mVideoPath << "\"";
return;
// Some formats want separate stream headers.
if (mAudioCodecContext->flags & AVFMT_GLOBALHEADER)
mAudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
if (avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar)) {
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't fill the audio codec context parameters for file \""
<< mVideoPath << "\"";
return;
}
if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr)) {
LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
"Couldn't initialize the audio codec context for file \""
<< mVideoPath << "\"";
return;
}
}
}
@ -1384,17 +1423,17 @@ void VideoFFmpegComponent::startVideo()
// Calculate pillarbox/letterbox sizes.
calculateBlackRectangle();
mIsPlaying = true;
mFadeIn = 0.0f;
}
}
void VideoFFmpegComponent::stopVideo()
void VideoFFmpegComponent::stopVideoPlayer()
{
muteVideoPlayer();
mIsPlaying = false;
mIsActuallyPlaying = false;
mStartDelayed = false;
mPause = false;
mPaused = false;
mEndOfVideo = false;
mTexture.reset();
@ -1433,10 +1472,10 @@ void VideoFFmpegComponent::stopVideo()
}
}
void VideoFFmpegComponent::pauseVideo()
void VideoFFmpegComponent::pauseVideoPlayer()
{
if (mPause && mWindow->getVideoPlayerCount() == 0)
AudioManager::getInstance().muteStream();
muteVideoPlayer();
mPaused = true;
}
void VideoFFmpegComponent::handleLooping()
@ -1449,8 +1488,16 @@ void VideoFFmpegComponent::handleLooping()
mWindow->screensaverTriggerNextGame();
}
else {
stopVideo();
startVideo();
stopVideoPlayer();
startVideoStream();
}
}
}
void VideoFFmpegComponent::muteVideoPlayer()
{
if (AudioManager::sAudioDevice != 0) {
AudioManager::getInstance().clearStream();
AudioManager::getInstance().muteStream();
}
}

View file

@ -33,7 +33,7 @@ class VideoFFmpegComponent : public VideoComponent
{
public:
VideoFFmpegComponent();
virtual ~VideoFFmpegComponent();
virtual ~VideoFFmpegComponent() { stopVideoPlayer(); }
// 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
@ -45,8 +45,17 @@ public:
// This can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
void setMaxSize(float width, float height) override;
// Basic video controls.
void stopVideoPlayer() override;
void pauseVideoPlayer() override;
// Handle looping of the video. Must be called periodically.
void handleLooping() override;
// Used to immediately mute audio even if there are samples to play in the buffer.
void muteVideoPlayer() override;
private:
void startVideoStream() override;
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change.
void resize();
@ -74,15 +83,6 @@ private:
static void detectHWDecoder();
bool decoderInitHW();
// Start the video immediately.
void startVideo() override;
// Stop the video.
void stopVideo() override;
// Pause the video when a game has been launched.
void pauseVideo() override;
// Handle looping the video. Must be called periodically.
void handleLooping() override;
static enum AVHWDeviceType sDeviceType;
static enum AVPixelFormat sPixelFormat;
static std::vector<std::string> sSWDecodedVideos;
@ -90,6 +90,7 @@ private:
std::shared_ptr<TextureResource> mTexture;
std::vector<float> mVideoRectangleCoords;
glm::vec2 mRectangleOffset;
std::unique_ptr<std::thread> mFrameProcessingThread;
std::mutex mPictureMutex;

View file

@ -15,7 +15,7 @@
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#define HOLD_TIME 1000
#define HOLD_TIME 1000.0f
GuiDetectDevice::GuiDetectDevice(bool firstRun,
bool forcedConfig,
@ -68,8 +68,8 @@ GuiDetectDevice::GuiDetectDevice(bool firstRun,
mGrid.setEntry(mMsg1, glm::ivec2 {0, 2}, false, true);
const std::string msg2str =
firstRun ? "PRESS ESC TO SKIP (OR F4 TO QUIT AT ANY TIME)" : "PRESS ESC TO CANCEL";
const std::string msg2str {firstRun ? "PRESS ESC TO SKIP (OR F4 TO QUIT AT ANY TIME)" :
"PRESS ESC TO CANCEL"};
mMsg2 = std::make_shared<TextComponent>(msg2str, Font::get(FONT_SIZE_SMALL), 0x777777FF,
ALIGN_CENTER);
mGrid.setEntry(mMsg2, glm::ivec2 {0, 3}, false, true);
@ -81,8 +81,8 @@ GuiDetectDevice::GuiDetectDevice(bool firstRun,
// Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent
// regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference.
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
float width = glm::clamp(0.60f * aspectValue, 0.50f, 0.80f) * Renderer::getScreenWidth();
float aspectValue {1.778f / Renderer::getScreenAspectRatio()};
float width {glm::clamp(0.60f * aspectValue, 0.50f, 0.80f) * Renderer::getScreenWidth()};
setSize(width, Renderer::getScreenHeight() * 0.5f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
@ -96,10 +96,8 @@ void GuiDetectDevice::onSizeChanged()
// Grid.
mGrid.setSize(mSize);
mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y);
// mGrid.setRowHeightPerc(1, mDeviceInfo->getFont()->getHeight() / mSize.y());
mGrid.setRowHeightPerc(2, mMsg1->getFont()->getHeight() / mSize.y);
mGrid.setRowHeightPerc(3, mMsg2->getFont()->getHeight() / mSize.y);
// mGrid.setRowHeightPerc(4, mDeviceHeld->getFont()->getHeight() / mSize.y());
}
bool GuiDetectDevice::input(InputConfig* config, Input input)
@ -125,7 +123,7 @@ bool GuiDetectDevice::input(InputConfig* config, Input input)
if (input.value && mHoldingConfig == nullptr) {
// Started holding.
mHoldingConfig = config;
mHoldTime = HOLD_TIME;
mHoldTime = static_cast<int>(HOLD_TIME);
mDeviceHeld->setText(Utils::String::toUpper(config->getDeviceName()));
}
else if (!input.value && mHoldingConfig == config) {
@ -140,9 +138,9 @@ bool GuiDetectDevice::input(InputConfig* config, Input input)
void GuiDetectDevice::update(int deltaTime)
{
if (mHoldingConfig) {
// If ES starts and if a known device is connected after startup skip controller
// configuration unless the flag to force the configuration was passed on the
// command line.
// If ES-DE starts and if a known device is connected after startup, then skip
// controller configuration unless the flag to force the configuration was passed
// on the command line.
if (!mForcedConfig && mFirstRun &&
Utils::FileSystem::exists(InputManager::getConfigPath()) &&
InputManager::getInstance().getNumConfiguredDevices() > 0) {
@ -152,11 +150,11 @@ void GuiDetectDevice::update(int deltaTime)
}
else {
mHoldTime -= deltaTime;
const float t = static_cast<float>(mHoldTime) / HOLD_TIME;
unsigned int c = static_cast<unsigned char>(t * 255);
mDeviceHeld->setColor((c << 24) | (c << 16) | (c << 8) | 0xFF);
// Fade in device name.
const float t {std::fabs((static_cast<float>(mHoldTime) / HOLD_TIME) - 1.0f)};
mDeviceHeld->setColor(0x44444400 | static_cast<unsigned char>(t * 255.0f));
if (mHoldTime <= 0) {
// Picked one!
// A device was selected.
mWindow->pushGui(new GuiInputConfig(mHoldingConfig, true, mDoneCallback));
delete this;
}

View file

@ -17,6 +17,7 @@
GuiInfoPopup::GuiInfoPopup(std::string message, int duration)
: mMessage {message}
, mDuration {duration}
, mAlpha {1.0f}
, mRunning {true}
{
mFrame = new NinePatchComponent;
@ -102,18 +103,18 @@ bool GuiInfoPopup::updateState()
return false;
}
else if (curTime - mStartTime <= 500) {
mAlpha = ((curTime - mStartTime) * 255 / 500);
mAlpha = static_cast<float>((curTime - mStartTime) / 500.0f);
}
else if (curTime - mStartTime < mDuration - 500) {
mAlpha = 255;
mAlpha = 1.0f;
}
else {
mAlpha = ((-(curTime - mStartTime - mDuration) * 255) / 500);
mAlpha = static_cast<float>((-(curTime - mStartTime - mDuration)) / 500.0f);
}
mGrid->setOpacity(static_cast<unsigned char>(mAlpha));
mGrid->setOpacity(mAlpha);
// Apply fade-in effect to popup frame.
mFrame->setEdgeColor(0xFFFFFF00 | static_cast<unsigned char>(mAlpha));
mFrame->setCenterColor(0xFFFFFF00 | static_cast<unsigned char>(mAlpha));
mFrame->setEdgeColor(0xFFFFFF00 | static_cast<unsigned char>(mAlpha * 255.0f));
mFrame->setCenterColor(0xFFFFFF00 | static_cast<unsigned char>(mAlpha * 255.0f));
return true;
}

View file

@ -32,7 +32,7 @@ private:
std::string mMessage;
int mDuration;
int mAlpha;
float mAlpha;
int mStartTime;
bool mRunning;
};

View file

@ -71,7 +71,7 @@ GuiInputConfig::GuiInputConfig(InputConfig* target,
mSubtitle2 = std::make_shared<TextComponent>(
"HOLD ANY BUTTON 1 SECOND TO SKIP", Font::get(FONT_SIZE_SMALL), 0x999999FF, ALIGN_CENTER);
// The opacity will be set to visible for any row that is skippable.
mSubtitle2->setOpacity(0);
mSubtitle2->setOpacity(0.0f);
mGrid.setEntry(mSubtitle2, glm::ivec2 {0, 3}, false, true);
@ -157,8 +157,10 @@ GuiInputConfig::GuiInputConfig(InputConfig* target,
// Only show "HOLD TO SKIP" if this input is skippable.
mList->setCursorChangedCallback([this](CursorState) {
bool skippable = sGuiInputConfigList[mList->getCursorId()].skippable;
mSubtitle2->setOpacity(skippable * 255);
if (sGuiInputConfigList[mList->getCursorId()].skippable)
mSubtitle2->setOpacity(1.0f);
else
mSubtitle2->setOpacity(0.0f);
});
// Make the first one say "PRESS ANYTHING" if we're re-configuring everything.

View file

@ -272,8 +272,8 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup(
if (mMultiLine) {
setSize(width, KEYBOARD_HEIGHT + textHeight - mText->getFont()->getHeight());
setPosition((static_cast<float>(Renderer::getScreenWidth()) - mSize.x) / 2.0f,
(static_cast<float>(Renderer::getScreenHeight()) - mSize.y) / 2.0f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
(Renderer::getScreenHeight() - mSize.y) / 2.0f);
}
else {
if (mComplexMode)
@ -281,8 +281,8 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup(
else
setSize(width, KEYBOARD_HEIGHT);
setPosition((static_cast<float>(Renderer::getScreenWidth()) - mSize.x) / 2.0f,
(static_cast<float>(Renderer::getScreenHeight()) - mSize.y) / 2.0f);
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
(Renderer::getScreenHeight() - mSize.y) / 2.0f);
}
if (!multiLine)

View file

@ -477,8 +477,8 @@ namespace Renderer
// clang-format off
vertices[0] = {{x, y }, {0.0f, 0.0f}, rColor};
vertices[1] = {{x, y + hL}, {0.0f, 0.0f}, horizontalGradient ? rColorEnd : rColor};
vertices[2] = {{x + wL, y }, {0.0f, 0.0f}, horizontalGradient ? rColor : rColorEnd};
vertices[1] = {{x, y + hL}, {0.0f, 0.0f}, horizontalGradient ? rColor : rColorEnd};
vertices[2] = {{x + wL, y }, {0.0f, 0.0f}, horizontalGradient ? rColorEnd : rColor};
vertices[3] = {{x + wL, y + hL}, {0.0f, 0.0f}, rColorEnd};
// clang-format on
@ -535,12 +535,12 @@ namespace Renderer
const glm::mat4& getProjectionMatrix() { return mProjectionMatrix; }
SDL_Window* getSDLWindow() { return sdlWindow; }
const int getWindowWidth() { return windowWidth; }
const int getWindowHeight() { return windowHeight; }
const int getScreenWidth() { return screenWidth; }
const int getScreenHeight() { return screenHeight; }
const int getScreenOffsetX() { return screenOffsetX; }
const int getScreenOffsetY() { return screenOffsetY; }
const float getWindowWidth() { return static_cast<float>(windowWidth); }
const float getWindowHeight() { return static_cast<float>(windowHeight); }
const float getScreenWidth() { return static_cast<float>(screenWidth); }
const float getScreenHeight() { return static_cast<float>(screenHeight); }
const float getScreenOffsetX() { return static_cast<float>(screenOffsetX); }
const float getScreenOffsetY() { return static_cast<float>(screenOffsetY); }
const int getScreenRotate() { return screenRotate; }
const float getScreenWidthModifier() { return screenWidthModifier; }
const float getScreenHeightModifier() { return screenHeightModifier; }

View file

@ -146,12 +146,12 @@ namespace Renderer
const Blend::Factor srcBlendFactor = Blend::SRC_ALPHA,
const Blend::Factor dstBlendFactor = Blend::ONE_MINUS_SRC_ALPHA);
SDL_Window* getSDLWindow();
const int getWindowWidth();
const int getWindowHeight();
const int getScreenWidth();
const int getScreenHeight();
const int getScreenOffsetX();
const int getScreenOffsetY();
const float getWindowWidth();
const float getWindowHeight();
const float getScreenWidth();
const float getScreenHeight();
const float getScreenOffsetX();
const float getScreenOffsetY();
const int getScreenRotate();
const float getScreenWidthModifier();
const float getScreenHeightModifier();

View file

@ -352,6 +352,7 @@ namespace Renderer
if (runShader) {
runShader->activateShaders();
runShader->setModelViewProjectionMatrix(getProjectionMatrix() * trans);
runShader->setOpacity(vertices->opacity);
runShader->setTextureSize({shaderWidth, shaderHeight});
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
runShader->deactivateShaders();
@ -363,6 +364,7 @@ namespace Renderer
if (runShader) {
runShader->activateShaders();
runShader->setModelViewProjectionMatrix(getProjectionMatrix() * trans);
runShader->setOpacity(vertices->opacity);
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
runShader->deactivateShaders();
}
@ -389,7 +391,8 @@ namespace Renderer
void setViewport(const Rect& viewport)
{
// glViewport starts at the bottom left of the window.
GL_CHECK_ERROR(glViewport(viewport.x, getWindowHeight() - viewport.y - viewport.h,
GL_CHECK_ERROR(glViewport(viewport.x,
static_cast<GLint>(getWindowHeight()) - viewport.y - viewport.h,
viewport.w, viewport.h));
}
@ -400,7 +403,8 @@ namespace Renderer
}
else {
// glScissor starts at the bottom left of the window.
GL_CHECK_ERROR(glScissor(scissor.x, getWindowHeight() - scissor.y - scissor.h,
GL_CHECK_ERROR(glScissor(scissor.x,
static_cast<GLint>(getWindowHeight()) - scissor.y - scissor.h,
scissor.w, scissor.h));
GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST));
}
@ -451,10 +455,10 @@ namespace Renderer
{
Vertex vertices[4];
std::vector<unsigned int> shaderList;
GLuint width = getScreenWidth();
GLuint height = getScreenHeight();
float widthf = static_cast<float>(width);
float heightf = static_cast<float>(height);
GLuint width {static_cast<GLuint>(getScreenWidth())};
GLuint height {static_cast<GLuint>(getScreenHeight())};
float widthf {static_cast<float>(width)};
float heightf {static_cast<float>(height)};
// Set vertex positions and texture coordinates to full screen as all
// postprocessing is applied to the complete screen area.

View file

@ -233,7 +233,8 @@ namespace Renderer
void setViewport(const Rect& viewport)
{
// glViewport starts at the bottom left of the window.
GL_CHECK_ERROR(glViewport(viewport.x, getWindowHeight() - viewport.y - viewport.h,
GL_CHECK_ERROR(glViewport(viewport.x,
static_cast<GLint>(getWindowHeight()) - viewport.y - viewport.h,
viewport.w, viewport.h));
}
@ -244,7 +245,8 @@ namespace Renderer
}
else {
// glScissor starts at the bottom left of the window.
GL_CHECK_ERROR(glScissor(scissor.x, getWindowHeight() - scissor.y - scissor.h,
GL_CHECK_ERROR(glScissor(scissor.x,
static_cast<GLint>(getWindowHeight()) - scissor.y - scissor.h,
scissor.w, scissor.h));
GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST));
}

View file

@ -434,7 +434,8 @@ namespace Renderer
void setViewport(const Rect& viewport)
{
// glViewport starts at the bottom left of the window.
GL_CHECK_ERROR(glViewport(viewport.x, getWindowHeight() - viewport.y - viewport.h,
GL_CHECK_ERROR(glViewport(viewport.x,
static_cast<GLint>(getWindowHeight()) - viewport.y - viewport.h,
viewport.w, viewport.h));
}
@ -445,7 +446,8 @@ namespace Renderer
}
else {
// glScissor starts at the bottom left of the window.
GL_CHECK_ERROR(glScissor(scissor.x, getWindowHeight() - scissor.y - scissor.h,
GL_CHECK_ERROR(glScissor(scissor.x,
static_cast<GLint>(getWindowHeight()) - scissor.y - scissor.h,
scissor.w, scissor.h));
GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST));
}

View file

@ -84,7 +84,7 @@ Font::Font(int size, const std::string& path)
LOG(LogWarning) << "Requested font size too small, changing to minimum supported size";
}
else if (mSize > Renderer::getScreenHeight()) {
mSize = Renderer::getScreenHeight();
mSize = static_cast<int>(Renderer::getScreenHeight());
LOG(LogWarning) << "Requested font size too large, changing to maximum supported size";
}

View file

@ -37,7 +37,7 @@ class TextCache;
enum Alignment {
ALIGN_LEFT,
ALIGN_CENTER, // Centers both horizontally and vertically.
ALIGN_CENTER, // Used for both horizontal and vertical alignments.
ALIGN_RIGHT,
ALIGN_TOP,
ALIGN_BOTTOM

View file

@ -464,10 +464,7 @@ namespace Utils
std::string expandHomePath(const std::string& path)
{
// Expand home path if ~ is used.
std::string expandedPath = path;
expandedPath = Utils::String::replace(path, "~", Utils::FileSystem::getHomePath());
return expandedPath;
return Utils::String::replace(path, "~", Utils::FileSystem::getHomePath());
}
std::string resolveRelativePath(const std::string& path,

View file

@ -538,22 +538,27 @@ namespace Utils
return stringUpper;
}
std::string toCamelCase(const std::string& stringArg)
std::string toCapitalized(const std::string& stringArg)
{
std::string line = stringArg;
bool active = true;
if (stringArg == "")
return stringArg;
for (int i = 0; line[i] != '\0'; ++i) {
if (std::isalpha(line[i])) {
std::string line {stringArg};
bool active {true};
for (auto& chr : line) {
if (std::isalnum(chr)) {
if (active) {
line[i] = Utils::String::toUpper(std::string(1, line[i]))[0];
chr = std::toupper(chr);
active = false;
}
else
line[i] = Utils::String::toLower(std::string(1, line[i]))[0];
else {
chr = std::tolower(chr);
}
}
else if (line[i] == ' ')
else if (chr == ' ' || chr == '-' || chr == '\n' || chr == '\r' || chr == '\t') {
active = true;
}
}
return line;

View file

@ -27,7 +27,7 @@ namespace Utils
size_t moveCursor(const std::string& stringArg, const size_t cursor, const int amount);
std::string toLower(const std::string& stringArg);
std::string toUpper(const std::string& stringArg);
std::string toCamelCase(const std::string& stringArg);
std::string toCapitalized(const std::string& stringArg);
std::string trim(const std::string& stringArg);
std::string replace(const std::string& stringArg,
const std::string& replace,

View file

@ -21,13 +21,14 @@ void main(void)
#elif defined(FRAGMENT)
// Fragment section of code:
uniform float opacity = 1.0;
uniform sampler2D myTexture;
varying vec2 vTexCoord;
void main()
{
vec4 color = texture2D(myTexture, vTexCoord);
gl_FragColor = vec4(color.bgra);
gl_FragColor = vec4(color.bgr, color.a * opacity);
}
#endif

View file

@ -88,10 +88,10 @@ COMPAT_VARYING vec4 TEX0;
void main()
{
vec2 texcoord = vTexCoord;
vec2 texcoord = vTexCoord;
vec2 PIXEL_SIZE = vec2(SourceSize.z, SourceSize.w);
#if __VERSION__ < 130
#if __VERSION__ < 130
float sampleOffsets1 = 0.0;
float sampleOffsets2 = 1.4347826;
float sampleOffsets3 = 3.3478260;
@ -108,28 +108,40 @@ void main()
vec4 color = COMPAT_TEXTURE(Source, texcoord);
color = vec4(color.rgb, 1.0) * sampleWeights1;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights5;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights5;
#else
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights5;
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights5;
#else
float sampleOffsets[5] = { 0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130 };
float sampleWeights[5] = { 0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196 };
float sampleOffsets[5] = {0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130};
float sampleWeights[5] = {0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196};
vec4 color = COMPAT_TEXTURE(Source, texcoord) * sampleWeights[0];
for (int i = 1; i < 5; i++) {
color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights[i];
color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights[i];
color +=
COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights[i];
color +=
COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) *
sampleWeights[i];
}
#endif
#endif
FragColor = vec4(color);
}

View file

@ -88,10 +88,10 @@ COMPAT_VARYING vec4 TEX0;
void main()
{
vec2 texcoord = vTexCoord;
vec2 texcoord = vTexCoord;
vec2 PIXEL_SIZE = vec2(SourceSize.z, SourceSize.w);
#if __VERSION__ < 130
#if __VERSION__ < 130
float sampleOffsets1 = 0.0;
float sampleOffsets2 = 1.4347826;
float sampleOffsets3 = 3.3478260;
@ -108,28 +108,40 @@ void main()
vec4 color = COMPAT_TEXTURE(Source, texcoord);
color = vec4(color.rgb, 1.0) * sampleWeights1;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) * sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) * sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) *
sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) *
sampleWeights2;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) * sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) * sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) *
sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) *
sampleWeights3;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) * sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) * sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) *
sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) *
sampleWeights4;
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) * sampleWeights5;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) * sampleWeights5;
#else
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) *
sampleWeights5;
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) *
sampleWeights5;
#else
float sampleOffsets[5] = { 0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130 };
float sampleWeights[5] = { 0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196 };
float sampleOffsets[5] = {0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130};
float sampleWeights[5] = {0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196};
vec4 color = COMPAT_TEXTURE(Source, texcoord) * sampleWeights[0];
for (int i = 1; i < 5; i++) {
color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) * sampleWeights[i];
color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) * sampleWeights[i];
color +=
COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) *
sampleWeights[i];
color +=
COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) *
sampleWeights[i];
}
#endif
#endif
FragColor = vec4(color);
}

View file

@ -93,6 +93,7 @@ uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform COMPAT_PRECISION float opacity = 1.0;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING vec2 onex;
@ -126,10 +127,11 @@ uniform COMPAT_PRECISION float OutputGamma;
#define TEX2D(coords) GAMMA_IN(COMPAT_TEXTURE(Source, coords))
// Macro for weights computing.
#define WEIGHT(w) \
if (w > 1.0) w = 1.0; \
w = 1.0 - w * w; \
w = w * w;
#define WEIGHT(w) \
if (w > 1.0) \
w = 1.0; \
w = 1.0 - w * w; \
w = w * w;
void main()
{
@ -143,7 +145,7 @@ void main()
float h_weight_00 = dx / SPOT_WIDTH;
WEIGHT(h_weight_00);
color *= vec4( h_weight_00, h_weight_00, h_weight_00, h_weight_00 );
color *= vec4(h_weight_00, h_weight_00, h_weight_00, h_weight_00);
// Get closest horizontal neighbour to blend.
vec2 coords01;
@ -184,12 +186,13 @@ void main()
WEIGHT(v_weight_10);
color = color + colorNB * vec4(v_weight_10 * h_weight_00, v_weight_10 * h_weight_00,
v_weight_10 * h_weight_00, v_weight_10 * h_weight_00);
v_weight_10 * h_weight_00, v_weight_10 * h_weight_00);
colorNB = TEX2D(texture_coords + coords01 + coords10);
color = color + colorNB * vec4(v_weight_10 * h_weight_01, v_weight_10 * h_weight_01,
v_weight_10 * h_weight_01, v_weight_10 * h_weight_01);
v_weight_10 * h_weight_01, v_weight_10 * h_weight_01);
color *= vec4(COLOR_BOOST);
FragColor = clamp(GAMMA_OUT(color), 0.0, 1.0);
vec4 colorTemp = clamp(GAMMA_OUT(color), 0.0, 1.0);
FragColor = vec4(colorTemp.rgb, colorTemp.a * opacity);
}
#endif

View file

@ -42,6 +42,11 @@ Theme capabilities for rbsimple-DE.
</override>
</variant>
<variant name="themeEngineTest">
<label>Theme engine test</label>
<selectable>true</selectable>
</variant>
<variant name="noMedia">
<label>No game media</label>
<selectable>false</selectable>

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