mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 07:35:38 +00:00
Themes mostly stable, documentation updated
This commit is contained in:
parent
8bfde96966
commit
a7359a2d08
|
@ -154,10 +154,11 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimationComponent.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.h
|
||||
|
@ -172,24 +173,28 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextListComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ThemeComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiDetectDevice.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiFastSelect.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMetaDataEd.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMsgBoxOk.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMsgBoxYesNo.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiGameList.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiGameScraper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiInputConfig.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMenu.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiSettingsMenu.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiScraperStart.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiScraperLog.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBScraper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/TheArchiveScraper.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugiconfig.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugixml.hpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/BasicGameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/DetailedGameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/GameListView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h
|
||||
|
@ -215,10 +220,11 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Sound.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/XMLReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimationComponent.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.cpp
|
||||
|
@ -231,27 +237,31 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ThemeComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiDetectDevice.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiFastSelect.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMetaDataEd.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMsgBoxOk.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMsgBoxYesNo.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiGameList.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiGameScraper.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiInputConfig.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiMenu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiSettingsMenu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiScraperStart.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GuiScraperLog.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/GamesDBScraper.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/TheArchiveScraper.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/pugiXML/pugixml.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/BasicGameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/DetailedGameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/GameListView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/data/ResourceUtil.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/data/converted/ES_logo_16_png.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/data/converted/ES_logo_32_png.cpp
|
||||
|
|
35
DEVNOTES.md
Normal file
35
DEVNOTES.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
Also known as "Really Bad Technical Documentation"
|
||||
|
||||
These are mostly notes I create as I go along, marking potential gotchas for people who might try to extend my code.
|
||||
Some day I'll try and add an overview of the code structure, what each class does, etc.
|
||||
|
||||
Development Environment
|
||||
=======================
|
||||
|
||||
I personally launch ES in windowed mode with a smaller resolution than my monitor and with debug text enabled.
|
||||
|
||||
`emulationstation --windowed --debug -w 1280 -h 720`
|
||||
|
||||
|
||||
Creating a new GuiComponent
|
||||
===========================
|
||||
|
||||
You probably want to override:
|
||||
|
||||
`bool input(InputConfig* config, Input input);`
|
||||
Check if some input is mapped to some action with `config->isMappedTo("a", input);`.
|
||||
Check if an input is "pressed" with `input.value != 0` (input.value *can* be negative in the case of axes).
|
||||
|
||||
`void update(int deltaTime);`
|
||||
`deltaTime` is in milliseconds.
|
||||
|
||||
`void render(const Eigen::Affine3f& parentTrans);`
|
||||
You probably want to do `Eigen::Affine3f trans = parentTrans * getTransform();` to get your final "modelview" matrix.
|
||||
Apply the modelview matrix with `Renderer::setMatrix(const Eigen::Affine3f&)`.
|
||||
Render any children the component may have with `renderChildren(parentTrans);`.
|
||||
|
||||
|
||||
Creating a new GameListView Class
|
||||
=================================
|
||||
|
||||
1. Don't allow the user to navigate to the root node's parent. If you use a stack of some sort to keep track of past cursor states this will be a natural side effect.
|
219
THEMES.md
219
THEMES.md
|
@ -1,176 +1,114 @@
|
|||
Themes
|
||||
======
|
||||
|
||||
EmulationStation allows each system to have its own "theme." A theme is a collection of display settings and images defined in an XML document.
|
||||
EmulationStation allows each system to have its own "theme." A theme is a collection of resources defined in an XML document.
|
||||
|
||||
ES will check 3 places for a theme, in the following order:
|
||||
- a theme.xml file in the root of a system's %PATH% directory.
|
||||
- $HOME/.emulationstation/%NAME%/theme.xml
|
||||
- $HOME/.emulationstation/es_theme.xml
|
||||
|
||||
Almost all positions, dimensions, origins, etc. work in percentages - that is, they are a decimal between 0 and 1, representing the percentage of the screen on that axis to use. This ensures that themes look similar at every resolution.
|
||||
|
||||
Colors are hex values, either 6 or 8 characters, defined as RRGGBB or RRGGBBAA. If alpha is not included, a default value of FF will be assumed (not transparent).
|
||||
Themes are loaded like this:
|
||||
|
||||
1. Initialize to default values.
|
||||
2. If `$HOME/.emulationstation/es_theme_default.xml` exists, load it.
|
||||
3a. If there is a `theme.xml` present in the root of a system's `path` directory, load it.
|
||||
3b. IF NOT, If `$HOME/.emulationstation/%SYSTEMNAME%/theme.xml` exists, load it.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Here's a theme that defines some colors, displays a background, and displays a logo in the top left corner:
|
||||
```
|
||||
<theme>
|
||||
<listPrimaryColor>0000FF</listPrimaryColor>
|
||||
<listSecondaryColor>00FF00</listSecondaryColor>
|
||||
|
||||
<component>
|
||||
<type>image</type>
|
||||
<path>./theme/background.png</path>
|
||||
<pos>0 0</pos>
|
||||
<dim>1 1</dim>
|
||||
<origin>0 0</origin>
|
||||
</component>
|
||||
<listFont>
|
||||
<path>./../all_themes/font.ttf</path>
|
||||
<size>0.045</size>
|
||||
</listFont>
|
||||
|
||||
<component>
|
||||
<type>image</type>
|
||||
<descriptionFont>
|
||||
<path>./../all_themes/font.ttf</path>
|
||||
<size>0.035</size>
|
||||
</descriptionFont>
|
||||
|
||||
<backgroundImage>
|
||||
<path>./theme/background.png</path>
|
||||
<tile>true</tile>
|
||||
</backgroundImage>
|
||||
|
||||
<headerImage>
|
||||
<path>./theme/logo.png</path>
|
||||
<pos>0 0</pos>
|
||||
<dim>0.4 0</dim>
|
||||
<origin>0 0</origin>
|
||||
</component>
|
||||
<tile>false</tile>
|
||||
</headerImage>
|
||||
|
||||
<scrollSound>./../all_themes/scrollSound.wav</scrollSound>
|
||||
</theme>
|
||||
|
||||
<!-- You can also optionally define a "basic" theme, which is used instead if ES is in the "basic" view (no box art) -->
|
||||
<basicTheme>
|
||||
<listPrimaryColor>0000FF</listPrimaryColor>
|
||||
<listSecondaryColor>00FF00</listSecondaryColor>
|
||||
|
||||
<component>
|
||||
<type>image</type>
|
||||
<path>./theme/background.png</path>
|
||||
<pos>0 0</pos>
|
||||
<dim>1 1</dim>
|
||||
<origin>0 0</origin>
|
||||
</component>
|
||||
|
||||
</basicTheme>
|
||||
```
|
||||
|
||||
Themes can be defined with two tags: `<theme>` and `<themeBasic>`.
|
||||
You can define both a normal and basic theme in the same file.
|
||||
Themes must be enclosed in a `<theme>` tag.
|
||||
|
||||
If EmulationStation is running in "basic" mode, it will try to use `<themeBasic>`. If that doesn't exist or ES is running in "detailed" mode (a gamelist.xml is present), `<theme>` will be used.
|
||||
|
||||
|
||||
Components
|
||||
==========
|
||||
|
||||
A theme is made up of components, which have various types. At the moment, the only type is `image`. Components are rendered in the order they are defined - that means you'll want to define the background first, a header image second, etc.
|
||||
|
||||
|
||||
The "image" component
|
||||
=====================
|
||||
|
||||
Used to display an image.
|
||||
|
||||
`<path>` - path to the image file. Most common file types are supported, and . and ~ are properly expanded.
|
||||
|
||||
`<pos>` - the position, as two screen percentages, at which to display the image.
|
||||
|
||||
`<dim>` - the dimensions, as two screen percentages, that the image will be resized to. Make one axis 0 to keep the aspect ratio.
|
||||
|
||||
`<origin>` - the point on the image that `<pos>` defines, as an image percentage. "0.5 0.5", the center of the image, by default.
|
||||
|
||||
`<tiled />` - if present, the image is tiled instead of resized.
|
||||
|
||||
|
||||
Display tags
|
||||
============
|
||||
|
||||
Display tags define some "meta" display attributes about your theme. Display tags must be at the root of the `<theme>` tree - for example, they can't be inside a component tag. They are not required.
|
||||
|
||||
|
||||
**Game list attributes:**
|
||||
|
||||
`<listPrimaryColor>` - the hex font color to use for games on the GuiGameList.
|
||||
|
||||
`<listSecondaryColor>` - the hex font color to use for folders on the GuiGameList.
|
||||
|
||||
`<descColor>` - the hex font color to use for the description on the GuiGameList.
|
||||
|
||||
`<listSelectorColor>` - the hex color to use for the "selector bar" on the GuiGameList. Default is `000000FF`.
|
||||
|
||||
`<listSelectedColor>` - the hex color to use for selected text on the GuiGameList. Default is zero, which means no change.
|
||||
|
||||
`<listLeftAlign />` - if present, the games list names will be left aligned to the value of `<listOffsetX>` + `<listTextOffsetX>`. On by default for detailed themes.
|
||||
|
||||
`<hideHeader />` - if present, the system name header won't be displayed (useful for replacing it with an image). If you're making a complete custom theme, you probably want to use this.
|
||||
|
||||
`<hideDividers />` - if present, the divider between games on the detailed GuiGameList won't be displayed.
|
||||
|
||||
`<listOffsetX>` - the percentage to offset the list by. Default is 0.5 (half the screen). **Will also move the selector bar**.
|
||||
|
||||
`<listTextOffsetX>` - the percentage to offset the text in the list by. Default is 0.005. Only works in combination with `<listLeftAlign />`.
|
||||
|
||||
|
||||
|
||||
**Game image attributes:**
|
||||
|
||||
`<gameImagePos>` - two values for the position of the game art, in the form of `[x] [y]`, as a percentage. Default is `$infoWidth/2 $headerHeight`.
|
||||
|
||||
`<gameImageDim>` - two values for the dimensions of the game art, in the form of `[width] [height]`, as a percentage of the screen. Default is `$infoWidth 0` (width fits within the info column). The image will only be resized if at least one axis is nonzero *and* exceeded by the image's size. You should always leave at least one axis as zero to preserve the aspect ratio.
|
||||
|
||||
`<gameImageOrigin>` - two values for the origin of the game art, in the form of `[x] [y]`, as a percentage. Default is `0.5 0` (top-center of the image).
|
||||
|
||||
`<gameImageNotFound>` - path to the image to display if a game's image is missing. '.' and '~' are expanded.
|
||||
|
||||
|
||||
|
||||
**Fast Select box attributes:**
|
||||
|
||||
`<fastSelectColor>` - the hex color to use for the letter display on the fast select box.
|
||||
|
||||
`<fastSelectFrame>` - the path to a "nine patch" image to use for the "background" of the fast select box. See the "Nine Patches" section for more info.
|
||||
|
||||
`<fastSelectFont>` - font definition to use for the fast select letter. See the "Fonts" section for more info.
|
||||
All paths automatically expand `./` to the folder containing the theme.xml.
|
||||
All paths automatically expand `~/` to the home directory ($HOME on Linux, %HOMEPATH% on Windows).
|
||||
|
||||
Stuff you can define
|
||||
====================
|
||||
|
||||
Fonts
|
||||
=====
|
||||
|
||||
Fonts are defined like so:
|
||||
|
||||
```
|
||||
<fontTag>
|
||||
<path>./path/to/font</path>
|
||||
<size>0.05</size>
|
||||
</fontTag>
|
||||
<resourceName>
|
||||
<!-- Path is optional. -->
|
||||
<path>./some/path/here.ttf</path>
|
||||
<!-- Size is a percentage of screen height. Optional. -->
|
||||
<size>0.035</size>
|
||||
</resourceName>
|
||||
```
|
||||
|
||||
You can leave off any tags you don't want to use, and they'll use the default. Size is defined as a percentage of the screen height. "." and "~" are expanded for paths.
|
||||
`<listFont>` - Default size: 0.045.
|
||||
`<descriptionFont>` - Default size: 0.035.
|
||||
|
||||
NOTE: If your font size is too big, it'll overrun the maximum OpenGL texture size. ES will attempt to rasterize it in progressively smaller sizes until one fits, then upscale it.
|
||||
Colors
|
||||
======
|
||||
|
||||
**Font tags:**
|
||||
Colors are defined in hex, like this:
|
||||
|
||||
`<listFont>` - font to use for the game list.
|
||||
`<resourceName>00FF00FF</resourceName>`
|
||||
or
|
||||
`<resourceName>00FF00</resourceName>`
|
||||
(without the alpha channel specified - will assume FF)
|
||||
|
||||
`<descriptionFont>` - font to use for description text.
|
||||
`<listPrimaryColor>` - Default: 0000FFFF.
|
||||
`<listSecondaryColor>` - Default: 00FF00FF.
|
||||
`<listSelectorColor>` - Default: 000000FF.
|
||||
`<listSelectedColor>` - Default: 00000000.
|
||||
`<descriptionColor>` - Default: 48474DFF.
|
||||
|
||||
`<fastSelectFont>` - font to use for the fast select letter.
|
||||
Images
|
||||
======
|
||||
|
||||
Images are defined like this:
|
||||
```
|
||||
<resourceName>
|
||||
<path>./some/path/here.png</path>
|
||||
<!-- Can be true or false. -->
|
||||
<tile>true</tile>
|
||||
</resourceName>
|
||||
```
|
||||
Pretty much any image format is supported.
|
||||
|
||||
Audio
|
||||
=====
|
||||
`<backgroundImage>` - No default.
|
||||
`<headerImage>` - No default.
|
||||
|
||||
Themes can also define menu sounds. These tags go in the root of the `<theme>` tree, just like Display tags. Sounds should be in the .wav format. The relative path operator (.) and home operator (~) are properly expanded.
|
||||
Sounds
|
||||
======
|
||||
|
||||
`<menuScrollSound>` - path to the sound to play when the game list or fast select menu is scrolling.
|
||||
Sounds are defined like this:
|
||||
`<resourceName>./some/path/here.wav</resourceName>`
|
||||
Only .wav files are supported.
|
||||
|
||||
`<menuSelectSound>` - path to the sound to play when the user selects something from the game list.
|
||||
|
||||
`<menuBackSound>` - path to the sound to play when the user "goes up" from a folder in the game list.
|
||||
|
||||
`<menuOpenSound>` - path to the sound to play when the user opens a menu (either the "main menu" or the fast select menu).
|
||||
`<scrollSound>` - No default.
|
||||
`<gameSelectSound>` - No default.
|
||||
`<backSound>` - No default.
|
||||
`<menuOpenSound>` - No default.
|
||||
|
||||
|
||||
Nine Patches
|
||||
|
@ -178,18 +116,5 @@ Nine Patches
|
|||
|
||||
EmulationStation borrows the concept of "nine patches" from Android (or "9-Slices"). Currently the implementation is very simple and hard-coded to only use 48x48px images (16x16px for each "patch"). Check the `data/resources` directory for some examples (button.png, frame.png).
|
||||
|
||||
|
||||
List of variables
|
||||
=================
|
||||
|
||||
Variables can be used in position and dimension definitions. They can be added, subtracted, multiplied, and divided. Parenthesis are valid. They are a percentage of the screen.
|
||||
|
||||
For example, if you wanted to place an image that covered the left half of the screen, up to the game list, you could use `<dim>$infoWidth 1</dim>`.
|
||||
|
||||
`$headerHeight` - height of the system name header.
|
||||
|
||||
`$infoWidth` - where the left of the game list begins. Will follow `<listOffsetX>`.
|
||||
|
||||
|
||||
-Aloshi
|
||||
http://www.aloshi.com
|
||||
|
|
|
@ -9,8 +9,8 @@ std::string getCleanFileName(const fs::path& path)
|
|||
}
|
||||
|
||||
|
||||
FileData::FileData(FileType type, const fs::path& path)
|
||||
: mType(type), mPath(path), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor!
|
||||
FileData::FileData(FileType type, const fs::path& path, SystemData* system)
|
||||
: mType(type), mPath(path), mSystem(system), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor!
|
||||
{
|
||||
// metadata needs at least a name field (since that's what getName() will return)
|
||||
if(metadata.get("name").empty())
|
||||
|
@ -21,6 +21,9 @@ FileData::~FileData()
|
|||
{
|
||||
if(mParent)
|
||||
mParent->removeChild(this);
|
||||
|
||||
while(mChildren.size())
|
||||
delete mChildren.back();
|
||||
}
|
||||
|
||||
const std::string& FileData::getThumbnailPath() const
|
||||
|
|
|
@ -5,12 +5,21 @@
|
|||
#include <boost/filesystem.hpp>
|
||||
#include "MetaData.h"
|
||||
|
||||
class SystemData;
|
||||
|
||||
enum FileType
|
||||
{
|
||||
GAME = 1, // Cannot have children.
|
||||
FOLDER = 2
|
||||
};
|
||||
|
||||
enum FileChangeType
|
||||
{
|
||||
FILE_ADDED,
|
||||
FILE_METADATA_CHANGED,
|
||||
FILE_REMOVED
|
||||
};
|
||||
|
||||
// Used for loading/saving gamelist.xml.
|
||||
const char* fileTypeToString(FileType type);
|
||||
FileType stringToFileType(const char* str);
|
||||
|
@ -21,7 +30,7 @@ std::string getCleanFileName(const boost::filesystem::path& path);
|
|||
class FileData
|
||||
{
|
||||
public:
|
||||
FileData(FileType type, const boost::filesystem::path& path);
|
||||
FileData(FileType type, const boost::filesystem::path& path, SystemData* system);
|
||||
virtual ~FileData();
|
||||
|
||||
inline const std::string& getName() const { return metadata.get("name"); }
|
||||
|
@ -29,6 +38,7 @@ public:
|
|||
inline const boost::filesystem::path& getPath() const { return mPath; }
|
||||
inline FileData* getParent() const { return mParent; }
|
||||
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
|
||||
inline SystemData* getSystem() const { return mSystem; }
|
||||
|
||||
virtual const std::string& getThumbnailPath() const;
|
||||
|
||||
|
@ -57,6 +67,7 @@ public:
|
|||
private:
|
||||
FileType mType;
|
||||
boost::filesystem::path mPath;
|
||||
SystemData* mSystem;
|
||||
FileData* mParent;
|
||||
std::vector<FileData*> mChildren;
|
||||
};
|
||||
|
|
|
@ -99,6 +99,9 @@ void GuiComponent::addChild(GuiComponent* cmp)
|
|||
|
||||
void GuiComponent::removeChild(GuiComponent* cmp)
|
||||
{
|
||||
if(!cmp->getParent())
|
||||
return;
|
||||
|
||||
if(cmp->getParent() != this)
|
||||
{
|
||||
LOG(LogError) << "Tried to remove child from incorrect parent!";
|
||||
|
|
|
@ -35,8 +35,8 @@ SystemData::SystemData(const std::string& name, const std::string& fullName, con
|
|||
mLaunchCommand = command;
|
||||
mPlatformId = platformId;
|
||||
|
||||
mRootFolder = new FileData(FOLDER, mStartPath);
|
||||
mRootFolder->metadata.set("name", "Search Root");
|
||||
mRootFolder = new FileData(FOLDER, mStartPath, this);
|
||||
mRootFolder->metadata.set("name", mFullName);
|
||||
|
||||
if(!Settings::getInstance()->getBool("PARSEGAMELISTONLY"))
|
||||
populateFolder(mRootFolder);
|
||||
|
@ -45,6 +45,9 @@ SystemData::SystemData(const std::string& name, const std::string& fullName, con
|
|||
parseGamelist(this);
|
||||
|
||||
mRootFolder->sort(FileSorts::SortTypes.at(0));
|
||||
|
||||
mTheme = std::make_shared<ThemeData>();
|
||||
mTheme->loadFile(getThemePath());
|
||||
}
|
||||
|
||||
SystemData::~SystemData()
|
||||
|
@ -178,7 +181,7 @@ void SystemData::populateFolder(FileData* folder)
|
|||
isGame = false;
|
||||
if(std::find(mSearchExtensions.begin(), mSearchExtensions.end(), extension) != mSearchExtensions.end())
|
||||
{
|
||||
FileData* newGame = new FileData(GAME, filePath.generic_string());
|
||||
FileData* newGame = new FileData(GAME, filePath.generic_string(), this);
|
||||
folder->addChild(newGame);
|
||||
isGame = true;
|
||||
}
|
||||
|
@ -186,7 +189,7 @@ void SystemData::populateFolder(FileData* folder)
|
|||
//add directories that also do not match an extension as folders
|
||||
if(!isGame && fs::is_directory(filePath))
|
||||
{
|
||||
FileData* newFolder = new FileData(FOLDER, filePath.generic_string());
|
||||
FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), this);
|
||||
populateFolder(newFolder);
|
||||
|
||||
//ignore folders that do not contain games
|
||||
|
@ -347,7 +350,19 @@ std::string SystemData::getGamelistPath() const
|
|||
return filePath.generic_string();
|
||||
}
|
||||
|
||||
bool SystemData::hasGamelist()
|
||||
std::string SystemData::getThemePath() const
|
||||
{
|
||||
fs::path filePath;
|
||||
|
||||
filePath = mRootFolder->getPath() / "theme.xml";
|
||||
if(fs::exists(filePath))
|
||||
return filePath.generic_string();
|
||||
|
||||
filePath = getHomePath() + "/.emulationstation/" + getName() + "/theme.xml";
|
||||
return filePath.generic_string();
|
||||
}
|
||||
|
||||
bool SystemData::hasGamelist() const
|
||||
{
|
||||
return (fs::exists(getGamelistPath()));
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Window.h"
|
||||
#include "MetaData.h"
|
||||
#include "PlatformId.h"
|
||||
#include "ThemeData.h"
|
||||
|
||||
class SystemData
|
||||
{
|
||||
|
@ -21,9 +22,11 @@ public:
|
|||
inline const std::string& getStartPath() const { return mStartPath; }
|
||||
inline const std::vector<std::string>& getExtensions() const { return mSearchExtensions; }
|
||||
inline PlatformIds::PlatformId getPlatformId() const { return mPlatformId; }
|
||||
inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; }
|
||||
|
||||
std::string getGamelistPath() const;
|
||||
bool hasGamelist();
|
||||
bool hasGamelist() const;
|
||||
std::string getThemePath() const;
|
||||
|
||||
unsigned int getGameCount() const;
|
||||
|
||||
|
@ -43,6 +46,7 @@ private:
|
|||
std::vector<std::string> mSearchExtensions;
|
||||
std::string mLaunchCommand;
|
||||
PlatformIds::PlatformId mPlatformId;
|
||||
std::shared_ptr<ThemeData> mTheme;
|
||||
|
||||
void populateFolder(FileData* folder);
|
||||
|
||||
|
|
207
src/ThemeData.cpp
Normal file
207
src/ThemeData.cpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
#include "ThemeData.h"
|
||||
#include "Renderer.h"
|
||||
#include "resources/Font.h"
|
||||
#include "Sound.h"
|
||||
#include "resources/TextureResource.h"
|
||||
#include "Log.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/assign.hpp>
|
||||
#include "pugiXML/pugixml.hpp"
|
||||
|
||||
// Defaults
|
||||
std::map<std::string, FontDef > ThemeData::sDefaultFonts = boost::assign::map_list_of
|
||||
("listFont", FontDef(0.045f, ""))
|
||||
("descriptionFont", FontDef(0.035f, ""));
|
||||
|
||||
std::map<std::string, unsigned int> ThemeData::sDefaultColors = boost::assign::map_list_of
|
||||
("listPrimaryColor", 0x0000FFFF)
|
||||
("listSecondaryColor", 0x00FF00FF)
|
||||
("listSelectorColor", 0x000000FF)
|
||||
("listSelectedColor", 0x00000000)
|
||||
("descriptionColor", 0x48474DFF);
|
||||
|
||||
std::map<std::string, ImageDef> ThemeData::sDefaultImages = boost::assign::map_list_of
|
||||
("backgroundImage", ImageDef("", true))
|
||||
("headerImage", ImageDef("", false));
|
||||
|
||||
std::map<std::string, SoundDef> ThemeData::sDefaultSounds = boost::assign::map_list_of
|
||||
("scrollSound", SoundDef(""))
|
||||
("gameSelectSound", SoundDef(""))
|
||||
("backSound", SoundDef(""))
|
||||
("menuOpenSound", SoundDef(""));
|
||||
|
||||
|
||||
|
||||
const std::shared_ptr<ThemeData>& ThemeData::getDefault()
|
||||
{
|
||||
static const std::shared_ptr<ThemeData> def = std::shared_ptr<ThemeData>(new ThemeData());
|
||||
return def;
|
||||
}
|
||||
|
||||
ThemeData::ThemeData()
|
||||
{
|
||||
setDefaults();
|
||||
|
||||
std::string defaultDir = getHomePath() + "/.emulationstation/es_theme_default.xml";
|
||||
if(boost::filesystem::exists(defaultDir))
|
||||
loadFile(defaultDir);
|
||||
}
|
||||
|
||||
void ThemeData::setDefaults()
|
||||
{
|
||||
mFontMap.clear();
|
||||
mImageMap.clear();
|
||||
mColorMap.clear();
|
||||
mSoundMap.clear();
|
||||
|
||||
mFontMap = sDefaultFonts;
|
||||
mImageMap = sDefaultImages;
|
||||
mColorMap = sDefaultColors;
|
||||
mSoundMap = sDefaultSounds;
|
||||
}
|
||||
|
||||
unsigned int getHexColor(const char* str, unsigned int defaultColor)
|
||||
{
|
||||
if(!str)
|
||||
return defaultColor;
|
||||
|
||||
size_t len = strlen(str);
|
||||
if(len != 6 && len != 8)
|
||||
{
|
||||
LOG(LogError) << "Invalid theme color \"" << str << "\" (must be 6 or 8 characters)";
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
unsigned int val;
|
||||
std::stringstream ss;
|
||||
ss << str;
|
||||
ss >> std::hex >> val;
|
||||
|
||||
if(len == 6)
|
||||
val = (val << 8) | 0xFF;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
std::string resolvePath(const char* in, const std::string& relative)
|
||||
{
|
||||
if(!in || in[0] == '\0')
|
||||
return in;
|
||||
|
||||
boost::filesystem::path relPath(relative);
|
||||
relPath = relPath.parent_path();
|
||||
|
||||
boost::filesystem::path path(in);
|
||||
|
||||
// we use boost filesystem here instead of just string checks because
|
||||
// some directories could theoretically start with ~ or .
|
||||
if(*path.begin() == "~")
|
||||
{
|
||||
path = getHomePath() + (in + 1);
|
||||
}else if(*path.begin() == ".")
|
||||
{
|
||||
path = relPath / (in + 1);
|
||||
}
|
||||
|
||||
return path.generic_string();
|
||||
}
|
||||
|
||||
void ThemeData::loadFile(const std::string& themePath)
|
||||
{
|
||||
if(themePath.empty() || !boost::filesystem::exists(themePath))
|
||||
return;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(themePath.c_str());
|
||||
if(!result)
|
||||
{
|
||||
LOG(LogWarning) << "Could not parse theme file \"" << themePath << "\":\n " << result.description();
|
||||
return;
|
||||
}
|
||||
|
||||
pugi::xml_node root = doc.child("theme");
|
||||
|
||||
// Fonts
|
||||
for(auto it = mFontMap.begin(); it != mFontMap.end(); it++)
|
||||
{
|
||||
pugi::xml_node node = root.child(it->first.c_str());
|
||||
if(node)
|
||||
{
|
||||
std::string path = resolvePath(node.child("path").text().as_string(it->second.path.c_str()), themePath);
|
||||
if(!boost::filesystem::exists(path))
|
||||
{
|
||||
LOG(LogWarning) << "Font \"" << path << "\" doesn't exist!";
|
||||
path = it->second.path;
|
||||
}
|
||||
|
||||
float size = node.child("size").text().as_float(it->second.size);
|
||||
mFontMap[it->first] = FontDef(size, path);
|
||||
root.remove_child(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Images
|
||||
for(auto it = mImageMap.begin(); it != mImageMap.end(); it++)
|
||||
{
|
||||
pugi::xml_node node = root.child(it->first.c_str());
|
||||
if(node)
|
||||
{
|
||||
std::string path = resolvePath(node.child("path").text().as_string(it->second.path.c_str()), themePath);
|
||||
if(!boost::filesystem::exists(path))
|
||||
{
|
||||
LOG(LogWarning) << "Image \"" << path << "\" doesn't exist!";
|
||||
path = it->second.path;
|
||||
}
|
||||
|
||||
bool tile = node.child("tile").text().as_bool(it->second.tile);
|
||||
mImageMap[it->first] = ImageDef(path, tile);
|
||||
root.remove_child(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Colors
|
||||
for(auto it = mColorMap.begin(); it != mColorMap.end(); it++)
|
||||
{
|
||||
pugi::xml_node node = root.child(it->first.c_str());
|
||||
if(node)
|
||||
{
|
||||
mColorMap[it->first] = getHexColor(node.text().as_string(NULL), it->second);
|
||||
root.remove_child(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Sounds
|
||||
for(auto it = mSoundMap.begin(); it != mSoundMap.end(); it++)
|
||||
{
|
||||
pugi::xml_node node = root.child(it->first.c_str());
|
||||
if(node)
|
||||
{
|
||||
std::string path = resolvePath(node.text().as_string(it->second.path.c_str()), themePath);
|
||||
if(!boost::filesystem::exists(path))
|
||||
{
|
||||
LOG(LogWarning) << "Sound \"" << path << "\" doesn't exist!";
|
||||
path = it->second.path;
|
||||
}
|
||||
|
||||
mSoundMap[it->first] = SoundDef(path);
|
||||
root.remove_child(node);
|
||||
}
|
||||
}
|
||||
|
||||
if(root.begin() != root.end())
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unused theme identifiers:\n";
|
||||
for(auto it = root.children().begin(); it != root.children().end(); it++)
|
||||
{
|
||||
ss << " " << it->name() << "\n";
|
||||
}
|
||||
|
||||
LOG(LogWarning) << ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeData::playSound(const std::string& identifier) const
|
||||
{
|
||||
mSoundMap.at(identifier).get()->play();
|
||||
}
|
78
src/ThemeData.h
Normal file
78
src/ThemeData.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "resources/Font.h"
|
||||
#include "resources/TextureResource.h"
|
||||
#include "Renderer.h"
|
||||
#include "AudioManager.h"
|
||||
#include "Sound.h"
|
||||
|
||||
struct FontDef
|
||||
{
|
||||
FontDef() {}
|
||||
FontDef(float sz, const std::string& p) : path(p), size(sz) {}
|
||||
|
||||
std::string path;
|
||||
float size;
|
||||
|
||||
inline const std::shared_ptr<Font>& get() const { if(!font) font = Font::get((int)(size * Renderer::getScreenHeight()), path); return font; }
|
||||
|
||||
private:
|
||||
mutable std::shared_ptr<Font> font;
|
||||
};
|
||||
|
||||
struct ImageDef
|
||||
{
|
||||
ImageDef() {}
|
||||
ImageDef(const std::string& p, bool t) : path(p), tile(t) {}
|
||||
|
||||
std::string path;
|
||||
bool tile;
|
||||
|
||||
inline const std::shared_ptr<TextureResource>& getTexture() const { if(!texture && !path.empty()) texture = TextureResource::get(path); return texture; }
|
||||
|
||||
private:
|
||||
mutable std::shared_ptr<TextureResource> texture;
|
||||
};
|
||||
|
||||
struct SoundDef
|
||||
{
|
||||
SoundDef() {}
|
||||
SoundDef(const std::string& p) : path(p) { sound = std::shared_ptr<Sound>(new Sound(path)); AudioManager::getInstance()->registerSound(sound); }
|
||||
|
||||
std::string path;
|
||||
|
||||
inline const std::shared_ptr<Sound>& get() const { return sound; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Sound> sound;
|
||||
};
|
||||
|
||||
class ThemeData
|
||||
{
|
||||
public:
|
||||
static const std::shared_ptr<ThemeData>& getDefault();
|
||||
|
||||
ThemeData();
|
||||
|
||||
void setDefaults();
|
||||
void loadFile(const std::string& path);
|
||||
|
||||
inline std::shared_ptr<Font> getFont(const std::string& identifier) const { return mFontMap.at(identifier).get(); }
|
||||
inline const ImageDef& getImage(const std::string& identifier) const { return mImageMap.at(identifier); }
|
||||
inline unsigned int getColor(const std::string& identifier) const { return mColorMap.at(identifier); }
|
||||
void playSound(const std::string& identifier) const;
|
||||
|
||||
private:
|
||||
static std::map<std::string, ImageDef> sDefaultImages;
|
||||
static std::map<std::string, unsigned int> sDefaultColors;
|
||||
static std::map<std::string, FontDef > sDefaultFonts;
|
||||
static std::map<std::string, SoundDef> sDefaultSounds;
|
||||
|
||||
std::map<std::string, ImageDef> mImageMap;
|
||||
std::map<std::string, unsigned int> mColorMap;
|
||||
std::map<std::string, FontDef > mFontMap;
|
||||
std::map< std::string, SoundDef > mSoundMap;
|
||||
};
|
|
@ -6,11 +6,14 @@
|
|||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include <iomanip>
|
||||
#include "views/ViewController.h"
|
||||
|
||||
Window::Window() : mNormalizeNextUpdate(false), mFrameTimeElapsed(0), mFrameCountElapsed(0), mAverageDeltaTime(10),
|
||||
mZoomFactor(1.0f), mCenterPoint(0, 0), mMatrix(Eigen::Affine3f::Identity()), mFadePercent(0.0f), mAllowSleep(true)
|
||||
{
|
||||
mInputManager = new InputManager(this);
|
||||
mViewController = new ViewController(this);
|
||||
pushGui(mViewController);
|
||||
setCenterPoint(Eigen::Vector2f(Renderer::getScreenWidth() / 2, Renderer::getScreenHeight() / 2));
|
||||
}
|
||||
|
||||
|
@ -88,9 +91,12 @@ void Window::input(InputConfig* config, Input input)
|
|||
{
|
||||
VolumeControl::getInstance()->setVolume(VolumeControl::getInstance()->getVolume() - 5);
|
||||
}
|
||||
else if(peekGui())
|
||||
else
|
||||
{
|
||||
if(peekGui())
|
||||
this->peekGui()->input(config, input);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::update(int deltaTime)
|
||||
{
|
||||
|
@ -125,10 +131,6 @@ void Window::update(int deltaTime)
|
|||
|
||||
void Window::render()
|
||||
{
|
||||
//there's nothing to render, which should pretty much never happen
|
||||
if(mGuiStack.size() == 0)
|
||||
std::cout << "guistack empty\n";
|
||||
|
||||
for(unsigned int i = 0; i < mGuiStack.size(); i++)
|
||||
{
|
||||
mGuiStack.at(i)->render(mMatrix);
|
||||
|
@ -148,11 +150,6 @@ void Window::normalizeNextUpdate()
|
|||
mNormalizeNextUpdate = true;
|
||||
}
|
||||
|
||||
InputManager* Window::getInputManager()
|
||||
{
|
||||
return mInputManager;
|
||||
}
|
||||
|
||||
void Window::setZoomFactor(const float& zoom)
|
||||
{
|
||||
mZoomFactor = zoom;
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
#include "GuiComponent.h"
|
||||
#include "InputManager.h"
|
||||
#include "resources/ResourceManager.h"
|
||||
#include <vector>
|
||||
#include "resources/Font.h"
|
||||
|
||||
class ViewController;
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
|
@ -24,8 +25,8 @@ public:
|
|||
bool init(unsigned int width = 0, unsigned int height = 0);
|
||||
void deinit();
|
||||
|
||||
InputManager* getInputManager();
|
||||
ResourceManager* getResourceManager();
|
||||
inline InputManager* getInputManager() { return mInputManager; }
|
||||
inline ViewController* getViewController() { return mViewController; }
|
||||
|
||||
void normalizeNextUpdate();
|
||||
|
||||
|
@ -39,6 +40,7 @@ public:
|
|||
|
||||
private:
|
||||
InputManager* mInputManager;
|
||||
ViewController* mViewController;
|
||||
std::vector<GuiComponent*> mGuiStack;
|
||||
|
||||
std::vector< std::shared_ptr<Font> > mDefaultFonts;
|
||||
|
|
|
@ -72,13 +72,13 @@ FileData* createGameFromPath(std::string gameAbsPath, SystemData* system)
|
|||
//the folder didn't already exist, so create it
|
||||
if(!foundFolder)
|
||||
{
|
||||
FileData* newFolder = new FileData(FOLDER, folder->getPath() / checkName);
|
||||
FileData* newFolder = new FileData(FOLDER, folder->getPath() / checkName, system);
|
||||
folder->addChild(newFolder);
|
||||
folder = newFolder;
|
||||
}
|
||||
}
|
||||
|
||||
FileData* game = new FileData(GAME, gameAbsPath);
|
||||
FileData* game = new FileData(GAME, gameAbsPath, system);
|
||||
folder->addChild(game);
|
||||
return game;
|
||||
}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
#include "AnimationComponent.h"
|
||||
|
||||
AnimationComponent::AnimationComponent()
|
||||
{
|
||||
mMoveX = 0;
|
||||
mMoveY = 0;
|
||||
mMoveSpeed = 0;
|
||||
mFadeRate = 0;
|
||||
mOpacity = 0;
|
||||
mAccumulator = 0;
|
||||
}
|
||||
|
||||
void AnimationComponent::move(int x, int y, int speed)
|
||||
{
|
||||
mMoveX = x;
|
||||
mMoveY = y;
|
||||
mMoveSpeed = speed;
|
||||
}
|
||||
|
||||
void AnimationComponent::fadeIn(int time)
|
||||
{
|
||||
mOpacity = 0;
|
||||
setChildrenOpacity(0);
|
||||
|
||||
mFadeRate = time;
|
||||
}
|
||||
|
||||
void AnimationComponent::fadeOut(int time)
|
||||
{
|
||||
mOpacity = 255;
|
||||
setChildrenOpacity(255);
|
||||
|
||||
mFadeRate = -time;
|
||||
}
|
||||
|
||||
//this should really be fixed at the system loop level...
|
||||
void AnimationComponent::update(int deltaTime)
|
||||
{
|
||||
mAccumulator += deltaTime;
|
||||
while(mAccumulator >= ANIMATION_TICK_SPEED)
|
||||
{
|
||||
mAccumulator -= ANIMATION_TICK_SPEED;
|
||||
|
||||
if(mMoveX != 0 || mMoveY != 0)
|
||||
{
|
||||
Eigen::Vector2i offset(mMoveX, mMoveY);
|
||||
if(abs(offset.x()) > mMoveSpeed)
|
||||
offset.x() = mMoveSpeed * (offset.x() > 0 ? 1 : -1);
|
||||
if(abs(offset.y()) > mMoveSpeed)
|
||||
offset.y() = mMoveSpeed * (offset.y() > 0 ? 1 : -1);
|
||||
|
||||
moveChildren(offset.x(), offset.y());
|
||||
|
||||
mMoveX -= offset.x();
|
||||
mMoveY -= offset.y();
|
||||
}
|
||||
|
||||
if(mFadeRate != 0)
|
||||
{
|
||||
int opacity = (int)mOpacity + mFadeRate;
|
||||
if(opacity > 255)
|
||||
{
|
||||
mFadeRate = 0;
|
||||
opacity = 255;
|
||||
}
|
||||
|
||||
if(opacity < 0)
|
||||
{
|
||||
mFadeRate = 0;
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
mOpacity = (unsigned char)opacity;
|
||||
setChildrenOpacity((unsigned char)opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::addChild(GuiComponent* gui)
|
||||
{
|
||||
mChildren.push_back(gui);
|
||||
}
|
||||
|
||||
void AnimationComponent::moveChildren(int offsetx, int offsety)
|
||||
{
|
||||
Eigen::Vector3f move((float)offsetx, (float)offsety, 0);
|
||||
for(unsigned int i = 0; i < mChildren.size(); i++)
|
||||
{
|
||||
GuiComponent* comp = mChildren.at(i);
|
||||
comp->setPosition(comp->getPosition() + move);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationComponent::setChildrenOpacity(unsigned char opacity)
|
||||
{
|
||||
for(unsigned int i = 0; i < mChildren.size(); i++)
|
||||
{
|
||||
mChildren.at(i)->setOpacity(opacity);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#ifndef _ANIMATIONCOMPONENT_H_
|
||||
#define _ANIMATIONCOMPONENT_H_
|
||||
|
||||
#include "../GuiComponent.h"
|
||||
#include <vector>
|
||||
|
||||
#define ANIMATION_TICK_SPEED 16
|
||||
|
||||
//just fyi, this is easily the worst animation system i've ever written.
|
||||
//it was mostly written during a single lecture and it really shows in how un-thought-out it is
|
||||
//it also hasn't been converted to use floats or vectors yet
|
||||
class AnimationComponent
|
||||
{
|
||||
public:
|
||||
AnimationComponent();
|
||||
|
||||
void move(int x, int y, int speed);
|
||||
void fadeIn(int time);
|
||||
void fadeOut(int time);
|
||||
|
||||
void update(int deltaTime);
|
||||
|
||||
void addChild(GuiComponent* gui);
|
||||
|
||||
private:
|
||||
unsigned char mOpacity;
|
||||
|
||||
std::vector<GuiComponent*> mChildren;
|
||||
|
||||
void moveChildren(int offsetx, int offsety);
|
||||
void setChildrenOpacity(unsigned char opacity);
|
||||
|
||||
int mFadeRate;
|
||||
int mMoveX, mMoveY, mMoveSpeed;
|
||||
|
||||
int mAccumulator;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,185 +0,0 @@
|
|||
#include "GuiFastSelect.h"
|
||||
#include "../Renderer.h"
|
||||
#include <iostream>
|
||||
#include "GuiGameList.h"
|
||||
#include "../FileSorts.h"
|
||||
|
||||
#define DEFAULT_FS_IMAGE ":/frame.png"
|
||||
|
||||
const std::string GuiFastSelect::LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const int GuiFastSelect::SCROLLSPEED = 100;
|
||||
const int GuiFastSelect::SCROLLDELAY = 507;
|
||||
|
||||
int GuiFastSelect::sortTypeId = 0;
|
||||
|
||||
GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent<FileData*>* list, char startLetter, ThemeComponent * theme)
|
||||
: GuiComponent(window), mParent(parent), mList(list), mTheme(theme), mBox(mWindow, "")
|
||||
{
|
||||
mLetterID = LETTERS.find(toupper(startLetter));
|
||||
if(mLetterID == std::string::npos)
|
||||
mLetterID = 0;
|
||||
|
||||
mScrollSound = mTheme->getSound("menuScroll");
|
||||
mTextColor = mTheme->getColor("fastSelect");
|
||||
|
||||
mScrolling = false;
|
||||
mScrollTimer = 0;
|
||||
mScrollOffset = 0;
|
||||
|
||||
unsigned int sw = Renderer::getScreenWidth(), sh = Renderer::getScreenHeight();
|
||||
|
||||
|
||||
if(theme->getString("fastSelectFrame").empty())
|
||||
{
|
||||
mBox.setImagePath(DEFAULT_FS_IMAGE);
|
||||
//mBox.setEdgeColor(0x0096ffFF);
|
||||
mBox.setEdgeColor(0x005493FF);
|
||||
mBox.setCenterColor(0x5e5e5eFF);
|
||||
}else{
|
||||
mBox.setImagePath(theme->getString("fastSelectFrame"));
|
||||
}
|
||||
|
||||
mBox.setPosition(sw * 0.2f, sh * 0.2f);
|
||||
mBox.setSize(sw * 0.6f, sh * 0.6f);
|
||||
}
|
||||
|
||||
GuiFastSelect::~GuiFastSelect()
|
||||
{
|
||||
mParent->updateDetailData();
|
||||
}
|
||||
|
||||
void GuiFastSelect::render(const Eigen::Affine3f& parentTrans)
|
||||
{
|
||||
Eigen::Affine3f trans = parentTrans * getTransform();
|
||||
|
||||
unsigned int sw = Renderer::getScreenWidth(), sh = Renderer::getScreenHeight();
|
||||
|
||||
mBox.render(trans);
|
||||
|
||||
Renderer::setMatrix(trans);
|
||||
std::shared_ptr<Font> letterFont = mTheme->getFastSelectFont();
|
||||
std::shared_ptr<Font> subtextFont = mTheme->getDescriptionFont();
|
||||
|
||||
letterFont->drawCenteredText(LETTERS.substr(mLetterID, 1), 0, sh * 0.5f - (letterFont->getHeight() * 0.5f), mTextColor);
|
||||
subtextFont->drawCenteredText("Sort order:", 0, sh * 0.6f - (subtextFont->getHeight() * 0.5f), mTextColor);
|
||||
|
||||
std::string sortString = "<- " + FileSorts::SortTypes.at(sortTypeId).description + " ->";
|
||||
subtextFont->drawCenteredText(sortString, 0, sh * 0.6f + (subtextFont->getHeight() * 0.5f), mTextColor);
|
||||
}
|
||||
|
||||
bool GuiFastSelect::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(config->isMappedTo("up", input) && input.value != 0)
|
||||
{
|
||||
mScrollOffset = -1;
|
||||
scroll();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedTo("down", input) && input.value != 0)
|
||||
{
|
||||
mScrollOffset = 1;
|
||||
scroll();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedTo("left", input) && input.value != 0)
|
||||
{
|
||||
sortTypeId--;
|
||||
if(sortTypeId < 0)
|
||||
sortTypeId = FileSorts::SortTypes.size() - 1;
|
||||
|
||||
mScrollSound->play();
|
||||
return true;
|
||||
}
|
||||
else if(config->isMappedTo("right", input) && input.value != 0)
|
||||
{
|
||||
sortTypeId = (sortTypeId + 1) % FileSorts::SortTypes.size();
|
||||
mScrollSound->play();
|
||||
return true;
|
||||
}
|
||||
|
||||
if((config->isMappedTo("up", input) || config->isMappedTo("down", input)) && input.value == 0)
|
||||
{
|
||||
mScrolling = false;
|
||||
mScrollTimer = 0;
|
||||
mScrollOffset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedTo("select", input) && input.value == 0)
|
||||
{
|
||||
setListPos();
|
||||
mParent->sort(FileSorts::SortTypes.at(sortTypeId));
|
||||
delete this;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GuiFastSelect::update(int deltaTime)
|
||||
{
|
||||
if(mScrollOffset != 0)
|
||||
{
|
||||
mScrollTimer += deltaTime;
|
||||
|
||||
if(!mScrolling && mScrollTimer >= SCROLLDELAY)
|
||||
{
|
||||
mScrolling = true;
|
||||
mScrollTimer = SCROLLSPEED;
|
||||
}
|
||||
|
||||
if(mScrolling && mScrollTimer >= SCROLLSPEED)
|
||||
{
|
||||
mScrollTimer = 0;
|
||||
scroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuiFastSelect::scroll()
|
||||
{
|
||||
setLetterID(mLetterID + mScrollOffset);
|
||||
mScrollSound->play();
|
||||
}
|
||||
|
||||
void GuiFastSelect::setLetterID(int id)
|
||||
{
|
||||
while(id < 0)
|
||||
id += LETTERS.length();
|
||||
while(id >= (int)LETTERS.length())
|
||||
id -= LETTERS.length();
|
||||
|
||||
mLetterID = (size_t)id;
|
||||
}
|
||||
|
||||
void GuiFastSelect::setListPos()
|
||||
{
|
||||
char letter = LETTERS[mLetterID];
|
||||
|
||||
int min = 0;
|
||||
int max = mList->getObjectCount() - 1;
|
||||
|
||||
int mid = 0;
|
||||
|
||||
while(max >= min)
|
||||
{
|
||||
mid = ((max - min) / 2) + min;
|
||||
|
||||
char checkLetter = toupper(mList->getObject(mid)->getName()[0]);
|
||||
|
||||
if(checkLetter < letter)
|
||||
{
|
||||
min = mid + 1;
|
||||
}else if(checkLetter > letter)
|
||||
{
|
||||
max = mid - 1;
|
||||
}else{
|
||||
//exact match found
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mList->setSelection(mid);
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
#ifndef _GUIFASTSELECT_H_
|
||||
#define _GUIFASTSELECT_H_
|
||||
|
||||
#include "../GuiComponent.h"
|
||||
#include "../SystemData.h"
|
||||
#include "../Sound.h"
|
||||
#include "ThemeComponent.h"
|
||||
#include "TextListComponent.h"
|
||||
#include "NinePatchComponent.h"
|
||||
|
||||
class GuiGameList;
|
||||
|
||||
|
||||
class GuiFastSelect : public GuiComponent
|
||||
{
|
||||
public:
|
||||
GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent<FileData*>* list, char startLetter, ThemeComponent* theme);
|
||||
~GuiFastSelect();
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
private:
|
||||
static const std::string LETTERS;
|
||||
static const int SCROLLSPEED;
|
||||
static const int SCROLLDELAY;
|
||||
|
||||
static int sortTypeId;
|
||||
|
||||
void setListPos();
|
||||
void scroll();
|
||||
void setLetterID(int id);
|
||||
|
||||
GuiGameList* mParent;
|
||||
TextListComponent<FileData*>* mList;
|
||||
ThemeComponent * mTheme;
|
||||
NinePatchComponent mBox;
|
||||
|
||||
size_t mLetterID;
|
||||
|
||||
unsigned int mTextColor;
|
||||
|
||||
int mScrollTimer, mScrollOffset;
|
||||
bool mScrolling;
|
||||
|
||||
std::shared_ptr<Sound> mScrollSound;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,505 +0,0 @@
|
|||
#include "GuiGameList.h"
|
||||
#include "../InputManager.h"
|
||||
#include <iostream>
|
||||
#include "GuiMenu.h"
|
||||
#include "GuiFastSelect.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "../Log.h"
|
||||
#include "../Settings.h"
|
||||
|
||||
#include "GuiMetaDataEd.h"
|
||||
#include "GuiScraperStart.h"
|
||||
|
||||
Eigen::Vector3f GuiGameList::getImagePos()
|
||||
{
|
||||
return Eigen::Vector3f(Renderer::getScreenWidth() * mTheme->getFloat("gameImageOffsetX"), Renderer::getScreenHeight() * mTheme->getFloat("gameImageOffsetY"), 0.0f);
|
||||
}
|
||||
|
||||
bool GuiGameList::isDetailed() const
|
||||
{
|
||||
if(!mFolder)
|
||||
return false;
|
||||
|
||||
//return true if any game has an image specified
|
||||
for(auto it = mFolder->getChildren().begin(); it != mFolder->getChildren().end(); it++)
|
||||
{
|
||||
if(!(*it)->getThumbnailPath().empty())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GuiGameList::GuiGameList(Window* window) : GuiComponent(window),
|
||||
mTheme(new ThemeComponent(mWindow)),
|
||||
mList(window, 0.0f, 0.0f, Font::get(FONT_SIZE_MEDIUM)),
|
||||
mScreenshot(window),
|
||||
mDescription(window),
|
||||
mRating(window),
|
||||
mReleaseDateLabel(window),
|
||||
mReleaseDate(window),
|
||||
mDescContainer(window),
|
||||
mTransitionImage(window, 0.0f, 0.0f, "", (float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight(), true),
|
||||
mHeaderText(mWindow),
|
||||
mLockInput(false),
|
||||
mEffectFunc(NULL), mEffectTime(0), mGameLaunchEffectLength(700)
|
||||
{
|
||||
mImageAnimation.addChild(&mScreenshot);
|
||||
mDescContainer.addChild(&mReleaseDateLabel);
|
||||
mDescContainer.addChild(&mReleaseDate);
|
||||
mDescContainer.addChild(&mRating);
|
||||
mDescContainer.addChild(&mDescription);
|
||||
|
||||
//scale delay with screen width (higher width = more text per line)
|
||||
//the scroll speed is automatically scaled by component size
|
||||
mDescContainer.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f);
|
||||
|
||||
mTransitionImage.setPosition((float)Renderer::getScreenWidth(), 0);
|
||||
mTransitionImage.setOrigin(0, 0);
|
||||
|
||||
mHeaderText.setColor(0xFF0000FF);
|
||||
mHeaderText.setFont(Font::get(FONT_SIZE_LARGE));
|
||||
mHeaderText.setPosition(0, 1);
|
||||
mHeaderText.setSize((float)Renderer::getScreenWidth(), 0);
|
||||
mHeaderText.setCentered(true);
|
||||
|
||||
addChild(mTheme);
|
||||
addChild(&mHeaderText);
|
||||
addChild(&mScreenshot);
|
||||
addChild(&mDescContainer);
|
||||
addChild(&mList);
|
||||
addChild(&mTransitionImage);
|
||||
|
||||
mTransitionAnimation.addChild(this);
|
||||
|
||||
setSystemId(0);
|
||||
}
|
||||
|
||||
GuiGameList::~GuiGameList()
|
||||
{
|
||||
delete mTheme;
|
||||
}
|
||||
|
||||
void GuiGameList::setSystemId(int id)
|
||||
{
|
||||
if(SystemData::sSystemVector.size() == 0)
|
||||
{
|
||||
LOG(LogError) << "Error - no systems found!";
|
||||
return;
|
||||
}
|
||||
|
||||
//make sure the id is within range
|
||||
if(id >= (int)SystemData::sSystemVector.size())
|
||||
id -= SystemData::sSystemVector.size();
|
||||
if(id < 0)
|
||||
id += SystemData::sSystemVector.size();
|
||||
|
||||
mSystemId = id;
|
||||
mSystem = SystemData::sSystemVector.at(mSystemId);
|
||||
|
||||
//clear the folder stack
|
||||
while(mFolderStack.size()){ mFolderStack.pop(); }
|
||||
|
||||
mFolder = mSystem->getRootFolder();
|
||||
|
||||
updateTheme();
|
||||
updateList();
|
||||
updateDetailData();
|
||||
mWindow->normalizeNextUpdate(); //image loading can be slow
|
||||
}
|
||||
|
||||
void GuiGameList::render(const Eigen::Affine3f& parentTrans)
|
||||
{
|
||||
Eigen::Affine3f trans = parentTrans * getTransform();
|
||||
renderChildren(trans);
|
||||
}
|
||||
|
||||
bool GuiGameList::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(mLockInput)
|
||||
return false;
|
||||
|
||||
mList.input(config, input);
|
||||
|
||||
if(input.id == SDLK_F3)
|
||||
{
|
||||
FileData* game = mList.getSelectedObject();
|
||||
if(game->getType() == GAME)
|
||||
{
|
||||
FileData* root = mSystem->getRootFolder();
|
||||
ScraperSearchParams searchParams;
|
||||
searchParams.game = game;
|
||||
searchParams.system = mSystem;
|
||||
mWindow->pushGui(new GuiMetaDataEd(mWindow, &game->metadata, game->metadata.getMDD(), searchParams, game->getPath().stem().string(),
|
||||
[&] { updateDetailData(); },
|
||||
[game, root, this] {
|
||||
boost::filesystem::remove(game->getPath());
|
||||
root->removeChild(game);
|
||||
updateList();
|
||||
}));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(input.id == SDLK_F5)
|
||||
{
|
||||
mWindow->pushGui(new GuiScraperStart(mWindow));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedTo("a", input) && input.value != 0)
|
||||
{
|
||||
//play select sound
|
||||
mTheme->getSound("menuSelect")->play();
|
||||
|
||||
FileData* file = mList.getSelectedObject();
|
||||
if(file->getType() == FOLDER) //if you selected a folder, add this directory to the stack, and use the selected one
|
||||
{
|
||||
mFolderStack.push(mFolder);
|
||||
mFolder = file;
|
||||
updateList();
|
||||
updateDetailData();
|
||||
return true;
|
||||
}else{
|
||||
mList.stopScrolling();
|
||||
|
||||
mEffectFunc = &GuiGameList::updateGameLaunchEffect;
|
||||
mEffectTime = 0;
|
||||
mGameLaunchEffectLength = (int)mTheme->getSound("menuSelect")->getLengthMS();
|
||||
if(mGameLaunchEffectLength < 800)
|
||||
mGameLaunchEffectLength = 800;
|
||||
|
||||
mLockInput = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//if there's something on the directory stack, return to it
|
||||
if(config->isMappedTo("b", input) && input.value != 0 && mFolderStack.size())
|
||||
{
|
||||
mFolder = mFolderStack.top();
|
||||
mFolderStack.pop();
|
||||
updateList();
|
||||
updateDetailData();
|
||||
|
||||
//play the back sound
|
||||
mTheme->getSound("menuBack")->play();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//only allow switching systems if more than one exists (otherwise it'll reset your position when you switch and it's annoying)
|
||||
if(SystemData::sSystemVector.size() > 1 && input.value != 0)
|
||||
{
|
||||
if(config->isMappedTo("right", input))
|
||||
{
|
||||
setSystemId(mSystemId + 1);
|
||||
doTransition(-1);
|
||||
return true;
|
||||
}
|
||||
if(config->isMappedTo("left", input))
|
||||
{
|
||||
setSystemId(mSystemId - 1);
|
||||
doTransition(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//open the "start menu"
|
||||
if(config->isMappedTo("menu", input) && input.value != 0)
|
||||
{
|
||||
mWindow->pushGui(new GuiMenu(mWindow, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
//open the fast select menu
|
||||
if(config->isMappedTo("select", input) && input.value != 0)
|
||||
{
|
||||
mWindow->pushGui(new GuiFastSelect(mWindow, this, &mList, mList.getSelectedObject()->getName()[0], mTheme));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isDetailed())
|
||||
{
|
||||
if(config->isMappedTo("up", input) || config->isMappedTo("down", input) || config->isMappedTo("pageup", input) || config->isMappedTo("pagedown", input))
|
||||
{
|
||||
if(input.value == 0)
|
||||
updateDetailData();
|
||||
else
|
||||
hideDetailData();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GuiGameList::sort(const FileData::SortType& type)
|
||||
{
|
||||
//resort list and update it
|
||||
mFolder->sort(type);
|
||||
updateList();
|
||||
updateDetailData();
|
||||
}
|
||||
|
||||
void GuiGameList::updateList()
|
||||
{
|
||||
mList.clear();
|
||||
|
||||
for(auto it = mFolder->getChildren().begin(); it != mFolder->getChildren().end(); it++)
|
||||
{
|
||||
if((*it)->getType() == FOLDER)
|
||||
mList.addObject((*it)->getName(), *it, mTheme->getColor("secondary"));
|
||||
else
|
||||
mList.addObject((*it)->getName(), *it, mTheme->getColor("primary"));
|
||||
}
|
||||
}
|
||||
|
||||
std::string GuiGameList::getThemeFile()
|
||||
{
|
||||
std::string themePath;
|
||||
|
||||
themePath = getHomePath();
|
||||
themePath += "/.emulationstation/" + mSystem->getName() + "/theme.xml";
|
||||
if(boost::filesystem::exists(themePath))
|
||||
return themePath;
|
||||
|
||||
themePath = mSystem->getStartPath() + "/theme.xml";
|
||||
if(boost::filesystem::exists(themePath))
|
||||
return themePath;
|
||||
|
||||
themePath = getHomePath();
|
||||
themePath += "/.emulationstation/es_theme.xml";
|
||||
if(boost::filesystem::exists(themePath))
|
||||
return themePath;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void GuiGameList::updateTheme()
|
||||
{
|
||||
mTheme->readXML(getThemeFile(), isDetailed());
|
||||
|
||||
mList.setSelectorColor(mTheme->getColor("selector"));
|
||||
mList.setSelectedTextColor(mTheme->getColor("selected"));
|
||||
mList.setScrollSound(mTheme->getSound("menuScroll"));
|
||||
|
||||
mList.setFont(mTheme->getListFont());
|
||||
mList.setPosition(0.0f, Font::get(FONT_SIZE_LARGE)->getHeight() + 2.0f);
|
||||
|
||||
if(!mTheme->getBool("hideHeader"))
|
||||
{
|
||||
mHeaderText.setText(mSystem->getFullName());
|
||||
}else{
|
||||
mHeaderText.setText("");
|
||||
}
|
||||
|
||||
if(isDetailed())
|
||||
{
|
||||
mList.setCentered(mTheme->getBool("listCentered"));
|
||||
|
||||
mList.setPosition(mTheme->getFloat("listOffsetX") * Renderer::getScreenWidth(), mList.getPosition().y());
|
||||
mList.setTextOffsetX((int)(mTheme->getFloat("listTextOffsetX") * Renderer::getScreenWidth()));
|
||||
|
||||
mScreenshot.setPosition(mTheme->getFloat("gameImageOffsetX") * Renderer::getScreenWidth(), mTheme->getFloat("gameImageOffsetY") * Renderer::getScreenHeight());
|
||||
mScreenshot.setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY"));
|
||||
mScreenshot.setResize(mTheme->getFloat("gameImageWidth") * Renderer::getScreenWidth(), mTheme->getFloat("gameImageHeight") * Renderer::getScreenHeight(), false);
|
||||
|
||||
mReleaseDateLabel.setColor(mTheme->getColor("description"));
|
||||
mReleaseDateLabel.setFont(mTheme->getDescriptionFont());
|
||||
mReleaseDate.setColor(mTheme->getColor("description"));
|
||||
mReleaseDate.setFont(mTheme->getDescriptionFont());
|
||||
|
||||
mDescription.setColor(mTheme->getColor("description"));
|
||||
mDescription.setFont(mTheme->getDescriptionFont());
|
||||
}else{
|
||||
mList.setCentered(true);
|
||||
mList.setPosition(0, mList.getPosition().y());
|
||||
mList.setTextOffsetX(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GuiGameList::updateDetailData()
|
||||
{
|
||||
if(!isDetailed() || !mList.getSelectedObject() || mList.getSelectedObject()->getType() == FOLDER)
|
||||
{
|
||||
hideDetailData();
|
||||
}else{
|
||||
if(mDescContainer.getParent() != this)
|
||||
addChild(&mDescContainer);
|
||||
|
||||
FileData* game = mList.getSelectedObject();
|
||||
|
||||
//set image to either "not found" image or metadata image
|
||||
if(!boost::filesystem::exists(game->metadata.get("image")))
|
||||
{
|
||||
//image doesn't exist
|
||||
if(mTheme->getString("imageNotFoundPath").empty())
|
||||
{
|
||||
//"not found" image doesn't exist
|
||||
mScreenshot.setImage("");
|
||||
mScreenshot.setSize(0, 0); //clear old size
|
||||
}else{
|
||||
mScreenshot.setImage(mTheme->getString("imageNotFoundPath"));
|
||||
}
|
||||
}else{
|
||||
mScreenshot.setImage(game->metadata.get("image"));
|
||||
}
|
||||
|
||||
Eigen::Vector3f imgOffset = Eigen::Vector3f(Renderer::getScreenWidth() * 0.10f, 0, 0);
|
||||
mScreenshot.setPosition(getImagePos() - imgOffset);
|
||||
|
||||
mImageAnimation.fadeIn(35);
|
||||
mImageAnimation.move((int)imgOffset.x(), (int)imgOffset.y(), 20);
|
||||
|
||||
mDescContainer.setPosition(Eigen::Vector3f(Renderer::getScreenWidth() * 0.03f, getImagePos().y() + mScreenshot.getSize().y() + 12, 0));
|
||||
mDescContainer.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), Renderer::getScreenHeight() - mDescContainer.getPosition().y()));
|
||||
mDescContainer.setScrollPos(Eigen::Vector2d(0, 0));
|
||||
mDescContainer.resetAutoScrollTimer();
|
||||
|
||||
const float colwidth = mDescContainer.getSize().x();
|
||||
float ratingHeight = colwidth * 0.3f / 5.0f;
|
||||
mRating.setSize(ratingHeight * 5.0f, ratingHeight);
|
||||
|
||||
mReleaseDateLabel.setPosition(0, 0);
|
||||
mReleaseDateLabel.setText("Released: ");
|
||||
mReleaseDate.setPosition(mReleaseDateLabel.getPosition().x() + mReleaseDateLabel.getSize().x(), mReleaseDateLabel.getPosition().y());
|
||||
mReleaseDate.setValue(game->metadata.get("releasedate"));
|
||||
|
||||
mRating.setPosition(colwidth - mRating.getSize().x() - 12, 0);
|
||||
mRating.setValue(game->metadata.get("rating"));
|
||||
|
||||
mDescription.setPosition(0, mRating.getSize().y());
|
||||
mDescription.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), 0));
|
||||
mDescription.setText(game->metadata.get("desc"));
|
||||
}
|
||||
}
|
||||
|
||||
void GuiGameList::hideDetailData()
|
||||
{
|
||||
if(mDescContainer.getParent() == this)
|
||||
removeChild(&mDescContainer);
|
||||
|
||||
mImageAnimation.fadeOut(35);
|
||||
}
|
||||
|
||||
GuiGameList* GuiGameList::create(Window* window)
|
||||
{
|
||||
GuiGameList* list = new GuiGameList(window);
|
||||
window->pushGui(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
void GuiGameList::update(int deltaTime)
|
||||
{
|
||||
mTransitionAnimation.update(deltaTime);
|
||||
mImageAnimation.update(deltaTime);
|
||||
|
||||
if(mEffectFunc != NULL)
|
||||
{
|
||||
mEffectTime += deltaTime;
|
||||
(this->*mEffectFunc)(mEffectTime);
|
||||
}
|
||||
|
||||
GuiComponent::update(deltaTime);
|
||||
}
|
||||
|
||||
void GuiGameList::doTransition(int dir)
|
||||
{
|
||||
mTransitionImage.copyScreen();
|
||||
mTransitionImage.setOpacity(255);
|
||||
|
||||
//put the image of what's currently onscreen at what will be (in screen coords) 0, 0
|
||||
mTransitionImage.setPosition((float)Renderer::getScreenWidth() * dir, 0);
|
||||
|
||||
//move the entire thing offscreen so we'll move into place
|
||||
setPosition((float)Renderer::getScreenWidth() * -dir, mPosition[1]);
|
||||
|
||||
mTransitionAnimation.move(Renderer::getScreenWidth() * dir, 0, 50);
|
||||
}
|
||||
|
||||
float lerpFloat(const float& start, const float& end, float t)
|
||||
{
|
||||
if(t <= 0)
|
||||
return start;
|
||||
if(t >= 1)
|
||||
return end;
|
||||
|
||||
return (start * (1 - t) + end * t);
|
||||
}
|
||||
|
||||
Eigen::Vector2f lerpVector2f(const Eigen::Vector2f& start, const Eigen::Vector2f& end, float t)
|
||||
{
|
||||
if(t <= 0)
|
||||
return start;
|
||||
if(t >= 1)
|
||||
return end;
|
||||
|
||||
return (start * (1 - t) + end * t);
|
||||
}
|
||||
|
||||
float clamp(float min, float max, float val)
|
||||
{
|
||||
if(val < min)
|
||||
val = min;
|
||||
else if(val > max)
|
||||
val = max;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
//http://en.wikipedia.org/wiki/Smoothstep
|
||||
float smoothStep(float edge0, float edge1, float x)
|
||||
{
|
||||
// Scale, and clamp x to 0..1 range
|
||||
x = clamp(0, 1, (x - edge0)/(edge1 - edge0));
|
||||
|
||||
// Evaluate polynomial
|
||||
return x*x*x*(x*(x*6 - 15) + 10);
|
||||
}
|
||||
|
||||
void GuiGameList::updateGameLaunchEffect(int t)
|
||||
{
|
||||
const int endTime = mGameLaunchEffectLength;
|
||||
|
||||
const int zoomTime = endTime;
|
||||
const int centerTime = endTime - 50;
|
||||
|
||||
const int fadeDelay = endTime - 600;
|
||||
const int fadeTime = endTime - fadeDelay - 100;
|
||||
|
||||
Eigen::Vector2f imageCenter(mScreenshot.getCenter());
|
||||
if(!isDetailed())
|
||||
{
|
||||
imageCenter << mList.getPosition().x() + mList.getSize().x() / 2, mList.getPosition().y() + mList.getSize().y() / 2;
|
||||
}
|
||||
|
||||
const Eigen::Vector2f centerStart(Renderer::getScreenWidth() / 2, Renderer::getScreenHeight() / 2);
|
||||
|
||||
//remember to clamp or zoom factor will be incorrect with a negative t because squared
|
||||
const float tNormalized = clamp(0, 1, (float)t / endTime);
|
||||
|
||||
mWindow->setCenterPoint(lerpVector2f(centerStart, imageCenter, smoothStep(0.0, 1.0, tNormalized)));
|
||||
mWindow->setZoomFactor(lerpFloat(1.0f, 3.0f, tNormalized*tNormalized));
|
||||
mWindow->setFadePercent(lerpFloat(0.0f, 1.0f, (float)(t - fadeDelay) / fadeTime));
|
||||
|
||||
if(t > endTime)
|
||||
{
|
||||
//effect done
|
||||
mTransitionImage.setImage(""); //fixes "tried to bind uninitialized texture!" since copyScreen()'d textures don't reinit
|
||||
mSystem->launchGame(mWindow, mList.getSelectedObject());
|
||||
mEffectFunc = &GuiGameList::updateGameReturnEffect;
|
||||
mEffectTime = 0;
|
||||
mGameLaunchEffectLength = 700;
|
||||
mLockInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GuiGameList::updateGameReturnEffect(int t)
|
||||
{
|
||||
updateGameLaunchEffect(mGameLaunchEffectLength - t);
|
||||
|
||||
if(t >= mGameLaunchEffectLength)
|
||||
mEffectFunc = NULL;
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
#ifndef _GUIGAMELIST_H_
|
||||
#define _GUIGAMELIST_H_
|
||||
|
||||
#include "../GuiComponent.h"
|
||||
#include "TextListComponent.h"
|
||||
#include "ImageComponent.h"
|
||||
#include "ThemeComponent.h"
|
||||
#include "AnimationComponent.h"
|
||||
#include "TextComponent.h"
|
||||
#include <string>
|
||||
#include <stack>
|
||||
#include "../SystemData.h"
|
||||
#include "ScrollableContainer.h"
|
||||
#include "RatingComponent.h"
|
||||
#include "DateTimeComponent.h"
|
||||
|
||||
//This is where the magic happens - GuiGameList is the parent of almost every graphical element in ES at the moment.
|
||||
//It has a TextListComponent child that handles the game list, a ThemeComponent that handles the theming system, and an ImageComponent for game images.
|
||||
class GuiGameList : public GuiComponent
|
||||
{
|
||||
public:
|
||||
GuiGameList(Window* window);
|
||||
virtual ~GuiGameList();
|
||||
|
||||
void setSystemId(int id);
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
void updateDetailData();
|
||||
|
||||
void sort(const FileData::SortType& type);
|
||||
|
||||
static GuiGameList* create(Window* window);
|
||||
|
||||
bool isDetailed() const;
|
||||
|
||||
static const float sInfoWidth;
|
||||
private:
|
||||
void updateList();
|
||||
void updateTheme();
|
||||
void hideDetailData();
|
||||
void doTransition(int dir);
|
||||
|
||||
std::string getThemeFile();
|
||||
|
||||
SystemData* mSystem;
|
||||
FileData* mFolder;
|
||||
std::stack<FileData*> mFolderStack;
|
||||
int mSystemId;
|
||||
|
||||
TextListComponent<FileData*> mList;
|
||||
ImageComponent mScreenshot;
|
||||
|
||||
TextComponent mDescription;
|
||||
RatingComponent mRating;
|
||||
TextComponent mReleaseDateLabel;
|
||||
DateTimeComponent mReleaseDate;
|
||||
|
||||
ScrollableContainer mDescContainer;
|
||||
AnimationComponent mImageAnimation;
|
||||
ThemeComponent* mTheme;
|
||||
TextComponent mHeaderText;
|
||||
|
||||
ImageComponent mTransitionImage;
|
||||
AnimationComponent mTransitionAnimation;
|
||||
|
||||
Eigen::Vector3f getImagePos();
|
||||
|
||||
bool mLockInput;
|
||||
|
||||
void (GuiGameList::*mEffectFunc)(int);
|
||||
int mEffectTime;
|
||||
int mGameLaunchEffectLength;
|
||||
|
||||
void updateGameLaunchEffect(int t);
|
||||
void updateGameReturnEffect(int t);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -2,8 +2,8 @@
|
|||
#include "../Window.h"
|
||||
#include "../Renderer.h"
|
||||
#include "../resources/Font.h"
|
||||
#include "GuiGameList.h"
|
||||
#include "../Log.h"
|
||||
#include "../views/ViewController.h"
|
||||
|
||||
static const int inputCount = 10;
|
||||
static std::string inputName[inputCount] = { "Up", "Down", "Left", "Right", "A", "B", "Menu", "Select", "PageUp", "PageDown"};
|
||||
|
@ -38,7 +38,7 @@ bool GuiInputConfig::input(InputConfig* config, Input input)
|
|||
mWindow->pushGui(new GuiInputConfig(mWindow, mWindow->getInputManager()->getInputConfigByPlayer(mTargetConfig->getPlayerNum() + 1)));
|
||||
}else{
|
||||
mWindow->getInputManager()->writeConfig();
|
||||
GuiGameList::create(mWindow);
|
||||
mWindow->getViewController()->goToSystemSelect();
|
||||
}
|
||||
delete this;
|
||||
return true;
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
#include "GuiMenu.h"
|
||||
#include <iostream>
|
||||
#include <SDL.h>
|
||||
#include "../Log.h"
|
||||
#include "../SystemData.h"
|
||||
#include "GuiGameList.h"
|
||||
#include "../Settings.h"
|
||||
#include "GuiSettingsMenu.h"
|
||||
|
||||
GuiMenu::GuiMenu(Window* window, GuiGameList* parent) : GuiComponent(window)
|
||||
{
|
||||
mParent = parent;
|
||||
|
||||
std::shared_ptr<Font> font = Font::get(FONT_SIZE_LARGE);
|
||||
mList = new TextListComponent<std::string>(mWindow, 0.0f, font->getHeight() + 2.0f, font);
|
||||
mList->setSelectedTextColor(0x0000FFFF);
|
||||
populateList();
|
||||
}
|
||||
|
||||
GuiMenu::~GuiMenu()
|
||||
{
|
||||
delete mList;
|
||||
}
|
||||
|
||||
bool GuiMenu::input(InputConfig* config, Input input)
|
||||
{
|
||||
mList->input(config, input);
|
||||
|
||||
if(config->isMappedTo("menu", input) && input.value != 0)
|
||||
{
|
||||
delete this;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(config->isMappedTo("a", input) && input.value != 0)
|
||||
{
|
||||
executeCommand(mList->getSelectedObject());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GuiMenu::executeCommand(std::string command)
|
||||
{
|
||||
if(command == "exit")
|
||||
{
|
||||
//push SDL quit event
|
||||
SDL_Event* event = new SDL_Event();
|
||||
event->type = SDL_QUIT;
|
||||
SDL_PushEvent(event);
|
||||
}else if(command == "es_reload")
|
||||
{
|
||||
//reload the game list
|
||||
SystemData::loadConfig(SystemData::getConfigPath(), false);
|
||||
mParent->setSystemId(0);
|
||||
}else if(command == "es_settings")
|
||||
{
|
||||
mWindow->pushGui(new GuiSettingsMenu(mWindow));
|
||||
delete this;
|
||||
}else{
|
||||
if(system(command.c_str()) != 0)
|
||||
{
|
||||
LOG(LogWarning) << "(warning: command terminated with nonzero result!)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuiMenu::populateList()
|
||||
{
|
||||
mList->clear();
|
||||
|
||||
//if you want to add your own commands to the menu, here is where you need to change!
|
||||
//commands added here are called with system() when selected (so are executed as shell commands)
|
||||
//the method is GuiList::addObject(std::string displayString, std::string commandString, unsigned int displayHexColor);
|
||||
//the list will automatically adjust as items are added to it, this should be the only area you need to change
|
||||
//if you want to do something special within ES, override your command in the executeComand() method
|
||||
|
||||
mList->addObject("Settings", "es_settings", 0x0000FFFF);
|
||||
|
||||
mList->addObject("Restart", "sudo shutdown -r now", 0x0000FFFF);
|
||||
mList->addObject("Shutdown", "sudo shutdown -h now", 0x0000FFFF);
|
||||
|
||||
mList->addObject("Reload", "es_reload", 0x0000FFFF);
|
||||
|
||||
if(!Settings::getInstance()->getBool("DONTSHOWEXIT"))
|
||||
mList->addObject("Exit", "exit", 0xFF0000FF); //a special case; pushes an SDL quit event to the event stack instead of being called by system()
|
||||
}
|
||||
|
||||
void GuiMenu::update(int deltaTime)
|
||||
{
|
||||
mList->update(deltaTime);
|
||||
}
|
||||
|
||||
void GuiMenu::render(const Eigen::Affine3f& parentTrans)
|
||||
{
|
||||
Eigen::Affine3f trans = parentTrans;
|
||||
Renderer::setMatrix(trans);
|
||||
|
||||
Renderer::drawRect(Renderer::getScreenWidth() / 4, 0, Renderer::getScreenWidth() / 2, Renderer::getScreenHeight(), 0x999999);
|
||||
mList->render(trans);
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#ifndef _GUIMENU_H_
|
||||
#define _GUIMENU_H_
|
||||
|
||||
#include "../GuiComponent.h"
|
||||
#include "TextListComponent.h"
|
||||
|
||||
class GuiGameList;
|
||||
|
||||
class GuiMenu : public GuiComponent
|
||||
{
|
||||
public:
|
||||
GuiMenu(Window* window, GuiGameList* parent);
|
||||
virtual ~GuiMenu();
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
private:
|
||||
GuiGameList* mParent;
|
||||
TextListComponent<std::string>* mList;
|
||||
|
||||
void populateList();
|
||||
void executeCommand(std::string command);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -20,7 +20,7 @@ Eigen::Vector2f ImageComponent::getCenter() const
|
|||
}
|
||||
|
||||
ImageComponent::ImageComponent(Window* window, float offsetX, float offsetY, std::string path, float targetWidth, float targetHeight, bool allowUpscale) : GuiComponent(window),
|
||||
mTiled(false), mAllowUpscale(allowUpscale), mFlipX(false), mFlipY(false), mOrigin(0.5, 0.5), mTargetSize(targetWidth, targetHeight), mColorShift(0xFFFFFFFF)
|
||||
mTiled(false), mAllowUpscale(allowUpscale), mFlipX(false), mFlipY(false), mOrigin(0.0, 0.0), mTargetSize(targetWidth, targetHeight), mColorShift(0xFFFFFFFF)
|
||||
{
|
||||
setPosition(offsetX, offsetY);
|
||||
|
||||
|
@ -70,16 +70,20 @@ void ImageComponent::resize()
|
|||
|
||||
void ImageComponent::setImage(std::string path)
|
||||
{
|
||||
mPath = path;
|
||||
|
||||
if(mPath.empty() || !ResourceManager::getInstance()->fileExists(mPath))
|
||||
if(path.empty() || !ResourceManager::getInstance()->fileExists(path))
|
||||
mTexture.reset();
|
||||
else
|
||||
mTexture = TextureResource::get(mPath);
|
||||
mTexture = TextureResource::get(path);
|
||||
|
||||
resize();
|
||||
}
|
||||
|
||||
void ImageComponent::setImage(const std::shared_ptr<TextureResource>& texture)
|
||||
{
|
||||
mTexture = texture;
|
||||
resize();
|
||||
}
|
||||
|
||||
void ImageComponent::setImage(const char* path, size_t length)
|
||||
{
|
||||
mTexture.reset();
|
||||
|
@ -227,7 +231,7 @@ void ImageComponent::drawImageArray(GLfloat* points, GLfloat* texs, GLubyte* col
|
|||
|
||||
bool ImageComponent::hasImage()
|
||||
{
|
||||
return !mPath.empty();
|
||||
return (bool)mTexture;
|
||||
}
|
||||
|
||||
void ImageComponent::copyScreen()
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
void copyScreen(); //Copy the entire screen into a texture for us to use.
|
||||
void setImage(std::string path); //Loads the image at the given filepath.
|
||||
void setImage(const char* image, size_t length); //Loads image from memory.
|
||||
void setImage(const std::shared_ptr<TextureResource>& texture); //Use an already existing texture.
|
||||
void setOrigin(float originX, float originY); //Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
|
||||
void setTiling(bool tile); //Enables or disables tiling. Must be called before loading an image or resizing will be weird.
|
||||
void setResize(float width, float height, bool allowUpscale);
|
||||
|
@ -48,8 +49,6 @@ private:
|
|||
void buildImageArray(int x, int y, GLfloat* points, GLfloat* texs, float percentageX = 1, float percentageY = 1); //writes 12 GLfloat points and 12 GLfloat texture coordinates to a given array at a given position
|
||||
void drawImageArray(GLfloat* points, GLfloat* texs, GLubyte* colors, unsigned int count = 6); //draws the given set of points and texture coordinates, number of coordinate pairs may be specified (default 6)
|
||||
|
||||
std::string mPath;
|
||||
|
||||
unsigned int mColorShift;
|
||||
|
||||
std::shared_ptr<TextureResource> mTexture;
|
||||
|
|
|
@ -10,102 +10,98 @@
|
|||
#include <memory>
|
||||
#include "../Sound.h"
|
||||
#include "../Log.h"
|
||||
#include "../ThemeData.h"
|
||||
#include <functional>
|
||||
|
||||
#define MARQUEE_DELAY 900
|
||||
#define MARQUEE_SPEED 16
|
||||
#define MARQUEE_RATE 3
|
||||
#define THEME_FONT "listFont"
|
||||
#define THEME_SELECTOR_COLOR "listSelectorColor"
|
||||
#define THEME_HIGHLIGHTED_COLOR "listSelectedColor"
|
||||
#define THEME_SCROLL_SOUND "listScrollSound"
|
||||
static const int THEME_COLOR_ID_COUNT = 2;
|
||||
static const char* const THEME_ENTRY_COLOR[THEME_COLOR_ID_COUNT] = { "listPrimaryColor", "listSecondaryColor" };
|
||||
|
||||
//A graphical list. Supports multiple colors for rows and scrolling.
|
||||
template <typename T>
|
||||
class TextListComponent : public GuiComponent
|
||||
{
|
||||
public:
|
||||
TextListComponent(Window* window, float offsetX, float offsetY, std::shared_ptr<Font> font);
|
||||
TextListComponent(Window* window);
|
||||
virtual ~TextListComponent();
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
void onPositionChanged() override;
|
||||
|
||||
void addObject(std::string name, T obj, unsigned int color = 0xFF0000);
|
||||
void clear();
|
||||
|
||||
std::string getSelectedName();
|
||||
T getSelectedObject();
|
||||
int getSelection();
|
||||
void stopScrolling();
|
||||
bool isScrolling();
|
||||
|
||||
void setSelectorColor(unsigned int selectorColor);
|
||||
void setSelectedTextColor(unsigned int selectedColor);
|
||||
void setCentered(bool centered);
|
||||
void setScrollSound(std::shared_ptr<Sound> & sound);
|
||||
void setTextOffsetX(int textoffsetx);
|
||||
|
||||
int getObjectCount();
|
||||
T getObject(int i);
|
||||
void setSelection(int i);
|
||||
|
||||
void setFont(std::shared_ptr<Font> f);
|
||||
|
||||
private:
|
||||
static const int SCROLLDELAY = 507;
|
||||
static const int SCROLLTIME = 200;
|
||||
|
||||
void scroll(); //helper method, scrolls in whatever direction scrollDir is
|
||||
void setScrollDir(int val); //helper method, set mScrollDir as well as reset marquee stuff
|
||||
|
||||
int mScrollDir, mScrollAccumulator;
|
||||
bool mScrolling;
|
||||
|
||||
int mMarqueeOffset;
|
||||
int mMarqueeTime;
|
||||
|
||||
std::shared_ptr<Font> mFont;
|
||||
unsigned int mSelectorColor, mSelectedTextColorOverride;
|
||||
bool mDrawCentered;
|
||||
|
||||
int mTextOffsetX;
|
||||
|
||||
struct ListRow
|
||||
{
|
||||
std::string name;
|
||||
T object;
|
||||
unsigned int color;
|
||||
unsigned int colorId;
|
||||
};
|
||||
|
||||
void add(const std::string& name, const T& obj, unsigned int colorId);
|
||||
void remove(const T& obj);
|
||||
void clear();
|
||||
|
||||
inline const std::string& getSelectedName() const { return mRowVector.at(mCursor).name; }
|
||||
inline T getSelected() const { return mRowVector.at(mCursor).object; }
|
||||
inline const std::vector<ListRow>& getList() const { return mRowVector; }
|
||||
|
||||
void setCursor(const T& select);
|
||||
|
||||
void stopScrolling();
|
||||
inline bool isScrolling() const { return mScrollDir != 0; }
|
||||
|
||||
inline void setTheme(const std::shared_ptr<ThemeData>& theme) { mTheme = theme; }
|
||||
inline void setCentered(bool centered) { mCentered = centered; }
|
||||
|
||||
enum CursorState {
|
||||
CURSOR_STOPPED,
|
||||
CURSOR_SCROLLING
|
||||
};
|
||||
|
||||
inline void setCursorChangedCallback(const std::function<void(CursorState state)>& func) { mCursorChangedCallback = func; }
|
||||
|
||||
private:
|
||||
static const int MARQUEE_DELAY = 900;
|
||||
static const int MARQUEE_SPEED = 16;
|
||||
static const int MARQUEE_RATE = 3;
|
||||
|
||||
static const int SCROLL_DELAY = 507;
|
||||
static const int SCROLL_TIME = 150;
|
||||
|
||||
void scroll(); //helper method, scrolls in whatever direction scrollDir is
|
||||
void setScrollDir(int val); //helper method, set mScrollDir as well as reset marquee stuff
|
||||
void onCursorChanged(CursorState state);
|
||||
|
||||
int mScrollDir, mScrollAccumulator;
|
||||
|
||||
int mMarqueeOffset;
|
||||
int mMarqueeTime;
|
||||
|
||||
std::shared_ptr<ThemeData> mTheme;
|
||||
bool mCentered;
|
||||
|
||||
std::vector<ListRow> mRowVector;
|
||||
int mSelection;
|
||||
std::shared_ptr<Sound> mScrollSound;
|
||||
int mCursor;
|
||||
|
||||
std::function<void(CursorState state)> mCursorChangedCallback;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
TextListComponent<T>::TextListComponent(Window* window, float offsetX, float offsetY, std::shared_ptr<Font> font) : GuiComponent(window)
|
||||
TextListComponent<T>::TextListComponent(Window* window) :
|
||||
GuiComponent(window)
|
||||
{
|
||||
mSelection = 0;
|
||||
mCursor = 0;
|
||||
mScrollDir = 0;
|
||||
mScrolling = 0;
|
||||
mScrollAccumulator = 0;
|
||||
|
||||
setPosition(offsetX, offsetY);
|
||||
|
||||
mMarqueeOffset = 0;
|
||||
mMarqueeTime = -MARQUEE_DELAY;
|
||||
mTextOffsetX = 0;
|
||||
|
||||
mFont = font;
|
||||
mSelectorColor = 0x000000FF;
|
||||
mSelectedTextColorOverride = 0;
|
||||
mScrollSound = NULL;
|
||||
mDrawCentered = true;
|
||||
}
|
||||
mCentered = true;
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::onPositionChanged()
|
||||
{
|
||||
setSize(Renderer::getScreenWidth() - getPosition().x(), Renderer::getScreenHeight() - getPosition().y());
|
||||
mTheme = ThemeData::getDefault();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -119,8 +115,10 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
|
|||
Eigen::Affine3f trans = parentTrans * getTransform();
|
||||
Renderer::setMatrix(trans);
|
||||
|
||||
std::shared_ptr<Font> font = mTheme->getFont(THEME_FONT);
|
||||
|
||||
const int cutoff = 0;
|
||||
const int entrySize = mFont->getHeight() + 5;
|
||||
const int entrySize = font->getHeight() + 5;
|
||||
|
||||
int startEntry = 0;
|
||||
|
||||
|
@ -129,7 +127,7 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
|
|||
|
||||
if((int)mRowVector.size() >= screenCount)
|
||||
{
|
||||
startEntry = mSelection - (int)(screenCount * 0.5);
|
||||
startEntry = mCursor - (int)(screenCount * 0.5);
|
||||
if(startEntry < 0)
|
||||
startEntry = 0;
|
||||
if(startEntry >= (int)mRowVector.size() - screenCount)
|
||||
|
@ -140,7 +138,7 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
|
|||
|
||||
if(mRowVector.size() == 0)
|
||||
{
|
||||
mFont->drawCenteredText("The list is empty.", 0, y, 0xFF0000FF);
|
||||
font->drawCenteredText("The list is empty.", 0, y, 0xFF0000FF);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -152,27 +150,28 @@ void TextListComponent<T>::render(const Eigen::Affine3f& parentTrans)
|
|||
dim = trans * dim - trans.translation();
|
||||
Renderer::pushClipRect(Eigen::Vector2i((int)trans.translation().x(), (int)trans.translation().y()), Eigen::Vector2i((int)dim.x(), (int)dim.y()));
|
||||
|
||||
//Renderer::pushClipRect(pos, dim);
|
||||
//Renderer::pushClipRect(Eigen::Vector2i((int)trans.translation().x(), (int)trans.translation().y()), Eigen::Vector2i((int)getSize().x() * trans., (int)getSize().y() * trans.scale().y()));
|
||||
//Renderer::pushClipRect(getGlobalOffset(), getSize());
|
||||
|
||||
for(int i = startEntry; i < listCutoff; i++)
|
||||
{
|
||||
//draw selector bar
|
||||
if(mSelection == i)
|
||||
if(mCursor == i)
|
||||
{
|
||||
Renderer::drawRect(0, (int)y, (int)getSize().x(), mFont->getHeight(), mSelectorColor);
|
||||
Renderer::drawRect(0, (int)y, (int)getSize().x(), font->getHeight(), mTheme->getColor(THEME_SELECTOR_COLOR));
|
||||
}
|
||||
|
||||
ListRow row = mRowVector.at((unsigned int)i);
|
||||
|
||||
float x = (float)mTextOffsetX - (mSelection == i ? mMarqueeOffset : 0);
|
||||
unsigned int color = (mSelection == i && mSelectedTextColorOverride != 0) ? mSelectedTextColorOverride : row.color;
|
||||
float x = (float)(mCursor == i ? -mMarqueeOffset : 0);
|
||||
|
||||
if(mDrawCentered)
|
||||
mFont->drawCenteredText(row.name, x, y, color);
|
||||
unsigned int color;
|
||||
if(mCursor == i && mTheme->getColor(THEME_HIGHLIGHTED_COLOR))
|
||||
color = mTheme->getColor(THEME_HIGHLIGHTED_COLOR);
|
||||
else
|
||||
mFont->drawText(row.name, Eigen::Vector2f(x, y), color);
|
||||
color = mTheme->getColor(THEME_ENTRY_COLOR[row.colorId]);
|
||||
|
||||
if(mCentered)
|
||||
font->drawCenteredText(row.name, x, y, color);
|
||||
else
|
||||
font->drawText(row.name, Eigen::Vector2f(x, y), color);
|
||||
|
||||
y += entrySize;
|
||||
}
|
||||
|
@ -216,7 +215,8 @@ bool TextListComponent<T>::input(InputConfig* config, Input input)
|
|||
return true;
|
||||
}
|
||||
}else{
|
||||
if(config->isMappedTo("down", input) || config->isMappedTo("up", input) || config->isMappedTo("pagedown", input) || config->isMappedTo("pageup", input))
|
||||
if(config->isMappedTo("down", input) || config->isMappedTo("up", input) ||
|
||||
config->isMappedTo("pagedown", input) || config->isMappedTo("pageup", input))
|
||||
{
|
||||
stopScrolling();
|
||||
}
|
||||
|
@ -232,14 +232,15 @@ void TextListComponent<T>::setScrollDir(int val)
|
|||
mScrollDir = val;
|
||||
mMarqueeOffset = 0;
|
||||
mMarqueeTime = -MARQUEE_DELAY;
|
||||
mScrollAccumulator = -SCROLL_DELAY;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::stopScrolling()
|
||||
{
|
||||
mScrollAccumulator = 0;
|
||||
mScrolling = false;
|
||||
mScrollDir = 0;
|
||||
onCursorChanged(CURSOR_STOPPED);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -249,31 +250,17 @@ void TextListComponent<T>::update(int deltaTime)
|
|||
{
|
||||
mScrollAccumulator += deltaTime;
|
||||
|
||||
if(!mScrolling)
|
||||
while(mScrollAccumulator >= SCROLL_TIME)
|
||||
{
|
||||
if(mScrollAccumulator >= SCROLLDELAY)
|
||||
{
|
||||
mScrollAccumulator = SCROLLTIME;
|
||||
mScrolling = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(mScrolling)
|
||||
{
|
||||
mScrollAccumulator += deltaTime;
|
||||
|
||||
while(mScrollAccumulator >= SCROLLTIME)
|
||||
{
|
||||
mScrollAccumulator -= SCROLLTIME;
|
||||
|
||||
mScrollAccumulator -= SCROLL_TIME;
|
||||
scroll();
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
//if we're not scrolling and this object's text goes outside our size, marquee it!
|
||||
std::string text = getSelectedName();
|
||||
|
||||
Eigen::Vector2f textSize = mFont->sizeText(text);
|
||||
Eigen::Vector2f textSize = mTheme->getFont(THEME_FONT)->sizeText(text);
|
||||
|
||||
//it's long enough to marquee
|
||||
if(textSize.x() - mMarqueeOffset > getSize().x() - 12)
|
||||
|
@ -293,126 +280,94 @@ void TextListComponent<T>::update(int deltaTime)
|
|||
template <typename T>
|
||||
void TextListComponent<T>::scroll()
|
||||
{
|
||||
mSelection += mScrollDir;
|
||||
mCursor += mScrollDir;
|
||||
|
||||
if(mSelection < 0)
|
||||
if(mCursor < 0)
|
||||
{
|
||||
if(mScrollDir < -1)
|
||||
mSelection = 0;
|
||||
mCursor = 0;
|
||||
else
|
||||
mSelection += mRowVector.size();
|
||||
mCursor += mRowVector.size();
|
||||
}
|
||||
if(mSelection >= (int)mRowVector.size())
|
||||
if(mCursor >= (int)mRowVector.size())
|
||||
{
|
||||
if(mScrollDir > 1)
|
||||
mSelection = (int)mRowVector.size() - 1;
|
||||
mCursor = (int)mRowVector.size() - 1;
|
||||
else
|
||||
mSelection -= mRowVector.size();
|
||||
mCursor -= mRowVector.size();
|
||||
}
|
||||
|
||||
if(mScrollSound)
|
||||
mScrollSound->play();
|
||||
onCursorChanged(CURSOR_SCROLLING);
|
||||
mTheme->playSound("scrollSound");
|
||||
}
|
||||
|
||||
//list management stuff
|
||||
template <typename T>
|
||||
void TextListComponent<T>::addObject(std::string name, T obj, unsigned int color)
|
||||
void TextListComponent<T>::add(const std::string& name, const T& obj, unsigned int color)
|
||||
{
|
||||
if(color >= THEME_COLOR_ID_COUNT)
|
||||
{
|
||||
LOG(LogError) << "Invalid row color Id (" << color << ")";
|
||||
color = 0;
|
||||
}
|
||||
|
||||
ListRow row = {name, obj, color};
|
||||
mRowVector.push_back(row);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::remove(const T& obj)
|
||||
{
|
||||
for(auto it = mRowVector.begin(); it != mRowVector.end(); it++)
|
||||
{
|
||||
if((*it).object == obj)
|
||||
{
|
||||
if(mCursor > 0 && it - mRowVector.begin() >= mCursor)
|
||||
{
|
||||
mCursor--;
|
||||
onCursorChanged(CURSOR_STOPPED);
|
||||
}
|
||||
|
||||
mRowVector.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(LogError) << "Tried to remove an object we couldn't find";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::clear()
|
||||
{
|
||||
mRowVector.clear();
|
||||
mSelection = 0;
|
||||
mCursor = 0;
|
||||
mScrollDir = 0;
|
||||
mMarqueeOffset = 0;
|
||||
mMarqueeTime = -MARQUEE_DELAY;
|
||||
onCursorChanged(CURSOR_STOPPED);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string TextListComponent<T>::getSelectedName()
|
||||
void TextListComponent<T>::setCursor(const T& obj)
|
||||
{
|
||||
if((int)mRowVector.size() > mSelection)
|
||||
return mRowVector.at(mSelection).name;
|
||||
else
|
||||
return "";
|
||||
for(auto it = mRowVector.begin(); it != mRowVector.end(); it++)
|
||||
{
|
||||
if((*it).object == obj)
|
||||
{
|
||||
mCursor = it - mRowVector.begin();
|
||||
onCursorChanged(CURSOR_STOPPED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(LogError) << "Tried to set cursor to object we couldn't find";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T TextListComponent<T>::getSelectedObject()
|
||||
void TextListComponent<T>::onCursorChanged(CursorState state)
|
||||
{
|
||||
if((int)mRowVector.size() > mSelection)
|
||||
return mRowVector.at(mSelection).object;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int TextListComponent<T>::getSelection()
|
||||
{
|
||||
return mSelection;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool TextListComponent<T>::isScrolling()
|
||||
{
|
||||
return mScrollDir != 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::setSelectorColor(unsigned int selectorColor)
|
||||
{
|
||||
mSelectorColor = selectorColor;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::setSelectedTextColor(unsigned int selectedColor)
|
||||
{
|
||||
mSelectedTextColorOverride = selectedColor;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void TextListComponent<T>::setCentered(bool centered)
|
||||
{
|
||||
mDrawCentered = centered;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void TextListComponent<T>::setTextOffsetX(int textoffsetx)
|
||||
{
|
||||
mTextOffsetX = textoffsetx;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int TextListComponent<T>::getObjectCount()
|
||||
{
|
||||
return mRowVector.size();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T TextListComponent<T>::getObject(int i)
|
||||
{
|
||||
return mRowVector.at(i).object;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::setSelection(int i)
|
||||
{
|
||||
mSelection = i;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::setScrollSound(std::shared_ptr<Sound> & sound)
|
||||
{
|
||||
mScrollSound = sound;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TextListComponent<T>::setFont(std::shared_ptr<Font> font)
|
||||
{
|
||||
mFont = font;
|
||||
if(mCursorChangedCallback)
|
||||
mCursorChangedCallback(state);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,393 +0,0 @@
|
|||
#include "ThemeComponent.h"
|
||||
#include "../MathExp.h"
|
||||
#include <iostream>
|
||||
#include "GuiGameList.h"
|
||||
#include "ImageComponent.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <sstream>
|
||||
#include "../Renderer.h"
|
||||
#include "../Log.h"
|
||||
|
||||
unsigned int ThemeComponent::getColor(std::string name)
|
||||
{
|
||||
return mColorMap[name];
|
||||
}
|
||||
|
||||
bool ThemeComponent::getBool(std::string name)
|
||||
{
|
||||
return mBoolMap[name];
|
||||
}
|
||||
|
||||
float ThemeComponent::getFloat(std::string name)
|
||||
{
|
||||
return mFloatMap[name];
|
||||
}
|
||||
|
||||
std::shared_ptr<Sound> & ThemeComponent::getSound(std::string name)
|
||||
{
|
||||
return mSoundMap[name];
|
||||
}
|
||||
|
||||
std::string ThemeComponent::getString(std::string name)
|
||||
{
|
||||
return mStringMap[name];
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> ThemeComponent::getListFont()
|
||||
{
|
||||
if(mListFont)
|
||||
return mListFont;
|
||||
else
|
||||
return Font::get(FONT_SIZE_MEDIUM);
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> ThemeComponent::getDescriptionFont()
|
||||
{
|
||||
if(mDescFont)
|
||||
return mDescFont;
|
||||
else
|
||||
return Font::get(FONT_SIZE_SMALL);
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> ThemeComponent::getFastSelectFont()
|
||||
{
|
||||
if(mFastSelectFont)
|
||||
return mFastSelectFont;
|
||||
else
|
||||
return Font::get(FONT_SIZE_LARGE);
|
||||
}
|
||||
|
||||
ThemeComponent::ThemeComponent(Window* window) : GuiComponent(window)
|
||||
{
|
||||
mSoundMap["menuScroll"] = std::shared_ptr<Sound>(new Sound);
|
||||
mSoundMap["menuSelect"] = std::shared_ptr<Sound>(new Sound);
|
||||
mSoundMap["menuBack"] = std::shared_ptr<Sound>(new Sound);
|
||||
mSoundMap["menuOpen"] = std::shared_ptr<Sound>(new Sound);
|
||||
|
||||
//register all sound with the audiomanager
|
||||
AudioManager::getInstance()->registerSound(mSoundMap["menuScroll"]);
|
||||
AudioManager::getInstance()->registerSound(mSoundMap["menuSelect"]);
|
||||
AudioManager::getInstance()->registerSound(mSoundMap["menuBack"]);
|
||||
AudioManager::getInstance()->registerSound(mSoundMap["menuOpen"]);
|
||||
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
ThemeComponent::~ThemeComponent()
|
||||
{
|
||||
deleteComponents();
|
||||
}
|
||||
|
||||
void ThemeComponent::setDefaults()
|
||||
{
|
||||
mColorMap["primary"] = 0x0000FFFF;
|
||||
mColorMap["secondary"] = 0x00FF00FF;
|
||||
mColorMap["selector"] = 0x000000FF;
|
||||
mColorMap["selected"] = 0x00000000;
|
||||
mColorMap["description"] = 0x0000FFFF;
|
||||
mColorMap["fastSelect"] = 0xFF0000FF;
|
||||
|
||||
mBoolMap["hideHeader"] = false;
|
||||
mBoolMap["hideDividers"] = false;
|
||||
mBoolMap["listCentered"] = false;
|
||||
|
||||
mFloatMap["listOffsetX"] = 0.5;
|
||||
mFloatMap["listTextOffsetX"] = 0.005f;
|
||||
mFloatMap["gameImageOriginX"] = 0.5;
|
||||
mFloatMap["gameImageOriginY"] = 0;
|
||||
mFloatMap["gameImageOffsetX"] = mFloatMap["listOffsetX"] / 2;
|
||||
mFloatMap["gameImageOffsetY"] = (float)FONT_SIZE_LARGE / (float)Renderer::getScreenHeight();
|
||||
mFloatMap["gameImageWidth"] = mFloatMap["listOffsetX"];
|
||||
mFloatMap["gameImageHeight"] = 0;
|
||||
|
||||
mSoundMap["menuScroll"]->loadFile("");
|
||||
mSoundMap["menuSelect"]->loadFile("");
|
||||
mSoundMap["menuBack"]->loadFile("");
|
||||
mSoundMap["menuOpen"]->loadFile("");
|
||||
|
||||
mStringMap["imageNotFoundPath"] = "";
|
||||
mStringMap["fastSelectFrame"] = "";
|
||||
|
||||
mListFont.reset();
|
||||
mDescFont.reset();
|
||||
mFastSelectFont.reset();
|
||||
}
|
||||
|
||||
void ThemeComponent::deleteComponents()
|
||||
{
|
||||
for(unsigned int i = 0; i < getChildCount(); i++)
|
||||
{
|
||||
delete getChild(i);
|
||||
}
|
||||
|
||||
clearChildren();
|
||||
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
|
||||
void ThemeComponent::readXML(std::string path, bool detailed)
|
||||
{
|
||||
if(mPath == path)
|
||||
return;
|
||||
|
||||
setDefaults();
|
||||
deleteComponents();
|
||||
|
||||
mPath = path;
|
||||
|
||||
if(path.empty())
|
||||
return;
|
||||
|
||||
LOG(LogInfo) << "Loading theme \"" << path << "\"...";
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(path.c_str());
|
||||
|
||||
if(!result)
|
||||
{
|
||||
LOG(LogError) << "Error parsing theme \"" << path << "\"!\n" << " " << result.description();
|
||||
return;
|
||||
}
|
||||
|
||||
pugi::xml_node root;
|
||||
|
||||
if(!detailed)
|
||||
{
|
||||
//if we're using the basic view, check if there's a basic version of the theme
|
||||
root = doc.child("basicTheme");
|
||||
}
|
||||
|
||||
if(!root)
|
||||
{
|
||||
root = doc.child("theme");
|
||||
}
|
||||
|
||||
if(!root)
|
||||
{
|
||||
LOG(LogError) << "No theme tag found in theme \"" << path << "\"!";
|
||||
return;
|
||||
}
|
||||
|
||||
//load non-component theme stuff
|
||||
mColorMap["primary"] = resolveColor(root.child("listPrimaryColor").text().get(), mColorMap["primary"]);
|
||||
mColorMap["secondary"] = resolveColor(root.child("listSecondaryColor").text().get(), mColorMap["secondary"]);
|
||||
mColorMap["selector"] = resolveColor(root.child("listSelectorColor").text().get(), mColorMap["selector"]);
|
||||
mColorMap["selected"] = resolveColor(root.child("listSelectedColor").text().get(), mColorMap["selected"]);
|
||||
mColorMap["description"] = resolveColor(root.child("descColor").text().get(), mColorMap["description"]);
|
||||
mColorMap["fastSelect"] = resolveColor(root.child("fastSelectColor").text().get(), mColorMap["fastSelect"]);
|
||||
|
||||
mBoolMap["hideHeader"] = root.child("hideHeader") != 0;
|
||||
mBoolMap["hideDividers"] = root.child("hideDividers") != 0;
|
||||
|
||||
//list stuff
|
||||
mBoolMap["listCentered"] = !root.child("listLeftAlign");
|
||||
mFloatMap["listOffsetX"] = strToFloat(root.child("listOffsetX").text().get(), mFloatMap["listOffsetX"]);
|
||||
mFloatMap["listTextOffsetX"] = strToFloat(root.child("listTextOffsetX").text().get(), mFloatMap["listTextOffsetX"]);
|
||||
|
||||
//game image stuff
|
||||
std::string artPos = root.child("gameImagePos").text().get();
|
||||
std::string artDim = root.child("gameImageDim").text().get();
|
||||
std::string artOrigin = root.child("gameImageOrigin").text().get();
|
||||
|
||||
std::string artPosX, artPosY, artWidth, artHeight, artOriginX, artOriginY;
|
||||
splitString(artPos, ' ', &artPosX, &artPosY);
|
||||
splitString(artDim, ' ', &artWidth, &artHeight);
|
||||
splitString(artOrigin, ' ', &artOriginX, &artOriginY);
|
||||
|
||||
mFloatMap["gameImageOffsetX"] = resolveExp(artPosX, mFloatMap["gameImageOffsetX"]);
|
||||
mFloatMap["gameImageOffsetY"] = resolveExp(artPosY, mFloatMap["gameImageOffsetY"]);
|
||||
mFloatMap["gameImageWidth"] = resolveExp(artWidth, mFloatMap["gameImageWidth"]);
|
||||
mFloatMap["gameImageHeight"] = resolveExp(artHeight, mFloatMap["gameImageHeight"]);
|
||||
mFloatMap["gameImageOriginX"] = resolveExp(artOriginX, mFloatMap["gameImageOriginX"]);
|
||||
mFloatMap["gameImageOriginY"] = resolveExp(artOriginY, mFloatMap["gameImageOriginY"]);
|
||||
|
||||
mStringMap["imageNotFoundPath"] = expandPath(root.child("gameImageNotFound").text().get());
|
||||
mStringMap["fastSelectFrame"] = expandPath(root.child("fastSelectFrame").text().get());
|
||||
|
||||
//sounds
|
||||
mSoundMap["menuScroll"]->loadFile(expandPath(root.child("menuScrollSound").text().get()));
|
||||
mSoundMap["menuSelect"]->loadFile(expandPath(root.child("menuSelectSound").text().get()));
|
||||
mSoundMap["menuBack"]->loadFile(expandPath(root.child("menuBackSound").text().get()));
|
||||
mSoundMap["menuOpen"]->loadFile(expandPath(root.child("menuOpenSound").text().get()));
|
||||
|
||||
//fonts
|
||||
mListFont = resolveFont(root.child("listFont"), Font::getDefaultPath(), FONT_SIZE_MEDIUM);
|
||||
mDescFont = resolveFont(root.child("descriptionFont"), Font::getDefaultPath(), FONT_SIZE_SMALL);
|
||||
mFastSelectFont = resolveFont(root.child("fastSelectFont"), Font::getDefaultPath(), FONT_SIZE_LARGE);
|
||||
|
||||
//actually read the components
|
||||
createComponentChildren(root, this);
|
||||
|
||||
LOG(LogInfo) << "Theme loading complete.";
|
||||
}
|
||||
|
||||
//recursively creates components
|
||||
void ThemeComponent::createComponentChildren(pugi::xml_node node, GuiComponent* parent)
|
||||
{
|
||||
for(pugi::xml_node data = node.child("component"); data; data = data.next_sibling("component"))
|
||||
{
|
||||
GuiComponent* nextComp = createElement(data, parent);
|
||||
|
||||
if(nextComp)
|
||||
createComponentChildren(data, nextComp);
|
||||
}
|
||||
}
|
||||
|
||||
//takes an XML element definition and creates an object from it
|
||||
GuiComponent* ThemeComponent::createElement(pugi::xml_node data, GuiComponent* parent)
|
||||
{
|
||||
std::string type = data.child("type").text().get();
|
||||
|
||||
if(type == "image")
|
||||
{
|
||||
std::string path = expandPath(data.child("path").text().get());
|
||||
|
||||
if(!boost::filesystem::exists(path))
|
||||
{
|
||||
LOG(LogError) << "Error - theme image \"" << path << "\" does not exist.";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::string pos = data.child("pos").text().get();
|
||||
std::string dim = data.child("dim").text().get();
|
||||
std::string origin = data.child("origin").text().get();
|
||||
|
||||
bool tiled = data.child("tiled") != 0;
|
||||
|
||||
//split position and dimension information
|
||||
std::string posX, posY;
|
||||
splitString(pos, ' ', &posX, &posY);
|
||||
|
||||
std::string dimW, dimH;
|
||||
splitString(dim, ' ', &dimW, &dimH);
|
||||
|
||||
std::string originX, originY;
|
||||
splitString(origin, ' ', &originX, &originY);
|
||||
|
||||
//resolve to pixels from percentages/variables
|
||||
float x = resolveExp(posX) * Renderer::getScreenWidth();
|
||||
float y = resolveExp(posY) * Renderer::getScreenHeight();
|
||||
float w = resolveExp(dimW) * Renderer::getScreenWidth();
|
||||
float h = resolveExp(dimH) * Renderer::getScreenHeight();
|
||||
|
||||
float ox = strToFloat(originX);
|
||||
float oy = strToFloat(originY);
|
||||
|
||||
ImageComponent* comp = new ImageComponent(mWindow, x, y, "", w, h, true);
|
||||
comp->setOrigin(ox, oy);
|
||||
comp->setTiling(tiled);
|
||||
comp->setImage(path);
|
||||
|
||||
addChild(comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
|
||||
LOG(LogError) << "Theme component type \"" << type << "\" unknown!";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//expands a file path (./ becomes the directory of this theme file, ~/ becomes $HOME/)
|
||||
std::string ThemeComponent::expandPath(std::string path)
|
||||
{
|
||||
if(path.empty())
|
||||
return "";
|
||||
|
||||
if(path[0] == '~')
|
||||
path = getHomePath() + path.substr(1, path.length() - 1);
|
||||
else if(path[0] == '.')
|
||||
path = boost::filesystem::path(mPath).parent_path().string() + path.substr(1, path.length() - 1);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
//takes a string containing a mathematical expression (possibly including variables) and resolves it to a float value
|
||||
float ThemeComponent::resolveExp(std::string str, float defaultVal)
|
||||
{
|
||||
if(str.empty())
|
||||
return defaultVal;
|
||||
|
||||
MathExp exp;
|
||||
exp.setExpression(str);
|
||||
|
||||
//set variables
|
||||
exp.setVariable("headerHeight", (float)(FONT_SIZE_LARGE / Renderer::getScreenHeight()));
|
||||
exp.setVariable("infoWidth", mFloatMap["listOffsetX"]);
|
||||
|
||||
return exp.eval();
|
||||
}
|
||||
|
||||
//takes a string of hex and resolves it to an integer
|
||||
unsigned int ThemeComponent::resolveColor(std::string str, unsigned int defaultColor)
|
||||
{
|
||||
if(str.empty())
|
||||
return defaultColor;
|
||||
|
||||
if(str.length() != 6 && str.length() != 8)
|
||||
{
|
||||
LOG(LogError) << "Color \"" << str << "\" is not a valid hex color! Must be 6 or 8 characters.";
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
//if there's no alpha specified, assume FF
|
||||
if(str.length() == 6)
|
||||
str += "FF";
|
||||
|
||||
unsigned int ret;
|
||||
std::stringstream ss;
|
||||
ss << std::hex << str;
|
||||
ss >> ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
//splits a string in two at the first instance of the delimiter
|
||||
void ThemeComponent::splitString(std::string str, char delim, std::string* before, std::string* after)
|
||||
{
|
||||
if(str.empty())
|
||||
return;
|
||||
|
||||
size_t split = str.find(delim);
|
||||
if(split != std::string::npos)
|
||||
{
|
||||
*before = str.substr(0, split);
|
||||
*after = str.substr(split + 1, str.length() - split - 1);
|
||||
}else{
|
||||
LOG(LogError) << "Tried to splt string \"" << str << "\" with delimiter '" << delim << "', but delimiter was not found!";
|
||||
}
|
||||
}
|
||||
|
||||
//converts a string to a float
|
||||
float ThemeComponent::strToFloat(std::string str, float defaultVal)
|
||||
{
|
||||
if(str.empty())
|
||||
return defaultVal;
|
||||
|
||||
float ret;
|
||||
std::stringstream ss;
|
||||
ss << str;
|
||||
ss >> ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> ThemeComponent::resolveFont(pugi::xml_node node, std::string defaultPath, unsigned int defaultSize)
|
||||
{
|
||||
if(!node)
|
||||
return NULL;
|
||||
|
||||
std::string path = expandPath(node.child("path").text().get());
|
||||
unsigned int size = (unsigned int)(strToFloat(node.child("size").text().get()) * Renderer::getScreenHeight());
|
||||
|
||||
if(!boost::filesystem::exists(path))
|
||||
{
|
||||
path = defaultPath;
|
||||
}
|
||||
|
||||
if(size == 0)
|
||||
{
|
||||
size = defaultSize;
|
||||
}
|
||||
|
||||
return Font::get(size, path);
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
#ifndef _THEMECOMPONENT_H_
|
||||
#define _THEMECOMPONENT_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../GuiComponent.h"
|
||||
#include "../pugiXML/pugixml.hpp"
|
||||
#include "../AudioManager.h"
|
||||
#include "../resources/Font.h"
|
||||
|
||||
//This class loads an XML-defined list of GuiComponents.
|
||||
class ThemeComponent : public GuiComponent
|
||||
{
|
||||
public:
|
||||
ThemeComponent(Window* window);
|
||||
virtual ~ThemeComponent();
|
||||
|
||||
void readXML(std::string path, bool detailed);
|
||||
|
||||
unsigned int getColor(std::string name);
|
||||
bool getBool(std::string name);
|
||||
float getFloat(std::string name);
|
||||
std::shared_ptr<Sound> & getSound(std::string name);
|
||||
std::string getString(std::string name);
|
||||
|
||||
std::shared_ptr<Font> getListFont();
|
||||
std::shared_ptr<Font> getDescriptionFont();
|
||||
std::shared_ptr<Font> getFastSelectFont();
|
||||
|
||||
private:
|
||||
void setDefaults();
|
||||
void deleteComponents();
|
||||
void createComponentChildren(pugi::xml_node node, GuiComponent* parent);
|
||||
GuiComponent* createElement(pugi::xml_node data, GuiComponent* parent);
|
||||
|
||||
//utility functions
|
||||
std::string expandPath(std::string path);
|
||||
float resolveExp(std::string str, float defaultVal = 0.0);
|
||||
unsigned int resolveColor(std::string str, unsigned int defaultColor = 0x000000FF);
|
||||
void splitString(std::string str, char delim, std::string* before, std::string* after);
|
||||
float strToFloat(std::string str, float defaultVal = 0.0f);
|
||||
std::shared_ptr<Font> resolveFont(pugi::xml_node node, std::string defaultPath, unsigned int defaultSize);
|
||||
|
||||
std::string mPath;
|
||||
|
||||
std::map<std::string, unsigned int> mColorMap;
|
||||
std::map<std::string, bool> mBoolMap;
|
||||
std::map<std::string, float> mFloatMap;
|
||||
std::map<std::string, std::shared_ptr<Sound>> mSoundMap;
|
||||
std::map<std::string, std::string> mStringMap;
|
||||
|
||||
std::shared_ptr<Font> mListFont;
|
||||
std::shared_ptr<Font> mDescFont;
|
||||
std::shared_ptr<Font> mFastSelectFont;
|
||||
};
|
||||
|
||||
#endif
|
22
src/main.cpp
22
src/main.cpp
|
@ -5,7 +5,7 @@
|
|||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "Renderer.h"
|
||||
#include "components/GuiGameList.h"
|
||||
#include "views/ViewController.h"
|
||||
#include "SystemData.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "components/GuiDetectDevice.h"
|
||||
|
@ -127,9 +127,16 @@ int main(int argc, char* argv[])
|
|||
Log::open();
|
||||
LOG(LogInfo) << "EmulationStation - " << PROGRAM_VERSION_STRING;
|
||||
|
||||
//always close the log and deinit the BCM library on exit
|
||||
//always close the log on exit
|
||||
atexit(&onExit);
|
||||
|
||||
Window window;
|
||||
if(!scrape_cmdline && !window.init(width, height))
|
||||
{
|
||||
LOG(LogError) << "Window failed to initialize!";
|
||||
return 1;
|
||||
}
|
||||
|
||||
//try loading the system config file
|
||||
if(!SystemData::loadConfig(SystemData::getConfigPath(), true))
|
||||
{
|
||||
|
@ -144,26 +151,19 @@ int main(int argc, char* argv[])
|
|||
return 1;
|
||||
}
|
||||
|
||||
//run the command line scraper ui then quit
|
||||
//run the command line scraper then quit
|
||||
if(scrape_cmdline)
|
||||
{
|
||||
return run_scraper_cmdline();
|
||||
}
|
||||
|
||||
Window window;
|
||||
if(!window.init(width, height))
|
||||
{
|
||||
LOG(LogError) << "Window failed to initialize!";
|
||||
return 1;
|
||||
}
|
||||
|
||||
//dont generate joystick events while we're loading (hopefully fixes "automatically started emulator" bug)
|
||||
SDL_JoystickEventState(SDL_DISABLE);
|
||||
|
||||
//choose which GUI to open depending on if an input configuration already exists
|
||||
if(fs::exists(InputManager::getConfigPath()))
|
||||
{
|
||||
GuiGameList::create(&window);
|
||||
window.getViewController()->goToSystemSelect();
|
||||
}else{
|
||||
window.pushGui(new GuiDetectDevice(&window));
|
||||
}
|
||||
|
|
|
@ -101,13 +101,7 @@ void Font::unload(std::shared_ptr<ResourceManager>& rm)
|
|||
|
||||
std::shared_ptr<Font> Font::get(int size, const std::string& path)
|
||||
{
|
||||
if(path.empty())
|
||||
{
|
||||
LOG(LogError) << "Tried to get font with no path!";
|
||||
return std::shared_ptr<Font>();
|
||||
}
|
||||
|
||||
std::pair<std::string, int> def(path, size);
|
||||
std::pair<std::string, int> def(path.empty() ? getDefaultPath() : path, size);
|
||||
auto foundFont = sFontMap.find(def);
|
||||
if(foundFont != sFontMap.end())
|
||||
{
|
||||
|
|
174
src/views/BasicGameListView.cpp
Normal file
174
src/views/BasicGameListView.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include "BasicGameListView.h"
|
||||
#include "ViewController.h"
|
||||
#include "../Renderer.h"
|
||||
#include "../Window.h"
|
||||
#include "../ThemeData.h"
|
||||
|
||||
BasicGameListView::BasicGameListView(Window* window, FileData* root)
|
||||
: GameListView(window, root),
|
||||
mHeaderText(window), mHeaderImage(window), mBackground(window), mList(window)
|
||||
{
|
||||
mHeaderText.setText("Header");
|
||||
mHeaderText.setSize(mSize.x(), 0);
|
||||
mHeaderText.setPosition(0, 0);
|
||||
mHeaderText.setCentered(true);
|
||||
|
||||
mHeaderImage.setResize(0, mSize.y() * 0.2f, false);
|
||||
mHeaderImage.setOrigin(0.5f, 0.0f);
|
||||
mHeaderImage.setPosition(mSize.x() / 2, 0);
|
||||
|
||||
mBackground.setResize(mSize.x(), mSize.y(), true);
|
||||
|
||||
mList.setSize(mSize.x(), mSize.y() * 0.8f);
|
||||
mList.setPosition(0, mSize.y() * 0.2f);
|
||||
|
||||
populateList(root);
|
||||
|
||||
addChild(&mBackground);
|
||||
addChild(&mList);
|
||||
addChild(&mHeaderText);
|
||||
}
|
||||
|
||||
void BasicGameListView::setTheme(const std::shared_ptr<ThemeData>& theme)
|
||||
{
|
||||
const ImageDef& bg = theme->getImage("backgroundImage");
|
||||
mBackground.setTiling(bg.tile);
|
||||
mBackground.setImage(bg.getTexture());
|
||||
|
||||
const ImageDef& hdr = theme->getImage("headerImage");
|
||||
mHeaderImage.setTiling(hdr.tile);
|
||||
mHeaderImage.setImage(hdr.getTexture());
|
||||
|
||||
if(mHeaderImage.hasImage())
|
||||
{
|
||||
removeChild(&mHeaderText);
|
||||
addChild(&mHeaderImage);
|
||||
}else{
|
||||
addChild(&mHeaderText);
|
||||
removeChild(&mHeaderImage);
|
||||
}
|
||||
|
||||
mList.setTheme(theme);
|
||||
}
|
||||
|
||||
void BasicGameListView::onFileChanged(FileData* file, FileChangeType change)
|
||||
{
|
||||
// we don't care about metadata changes (since we don't display metadata),
|
||||
// so we just ignore the FILE_METADATA_CHANGED case
|
||||
|
||||
// if it's immediately inside our current folder
|
||||
if(file->getParent() == getCurrentFolder())
|
||||
{
|
||||
if(change == FILE_REMOVED)
|
||||
{
|
||||
mList.remove(file); // will automatically make sure cursor ends up in a "safe" place
|
||||
}else if(change == FILE_ADDED)
|
||||
{
|
||||
FileData* cursor = getCursor();
|
||||
populateList(cursor->getParent());
|
||||
mList.setCursor(cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void buildHeader(FileData* from, std::stringstream& ss)
|
||||
{
|
||||
if(from->getParent())
|
||||
{
|
||||
buildHeader(from->getParent(), ss);
|
||||
ss << " -> ";
|
||||
}
|
||||
|
||||
ss << from->getName();
|
||||
}
|
||||
|
||||
void BasicGameListView::populateList(FileData* root)
|
||||
{
|
||||
mList.clear();
|
||||
|
||||
std::stringstream ss;
|
||||
buildHeader(root, ss);
|
||||
mHeaderText.setText(ss.str());
|
||||
|
||||
for(auto it = root->getChildren().begin(); it != root->getChildren().end(); it++)
|
||||
{
|
||||
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
|
||||
}
|
||||
}
|
||||
|
||||
void BasicGameListView::setCursor(FileData* cursor)
|
||||
{
|
||||
if(cursor->getParent() != getCursor()->getParent())
|
||||
{
|
||||
// Rebuild the folder stack
|
||||
std::stack<FileData*> path;
|
||||
FileData* cur = cursor;
|
||||
while((cur = cur->getParent()) != mRoot)
|
||||
path.push(cur);
|
||||
|
||||
while(!mCursorStack.empty())
|
||||
mCursorStack.pop();
|
||||
|
||||
while(!path.empty()) // put back in reverse order (flip)
|
||||
{
|
||||
mCursorStack.push(path.top());
|
||||
path.pop();
|
||||
}
|
||||
|
||||
populateList(cursor->getParent());
|
||||
}
|
||||
|
||||
mList.setCursor(cursor);
|
||||
}
|
||||
|
||||
bool BasicGameListView::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(input.value != 0)
|
||||
{
|
||||
if(config->isMappedTo("a", input))
|
||||
{
|
||||
if(mList.getList().size() > 0)
|
||||
{
|
||||
FileData* cursor = getCursor();
|
||||
if(cursor->getType() == GAME)
|
||||
{
|
||||
mWindow->getViewController()->launch(cursor);
|
||||
}else{
|
||||
// it's a folder
|
||||
if(cursor->getChildren().size() > 0)
|
||||
{
|
||||
mCursorStack.push(cursor);
|
||||
populateList(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}else if(config->isMappedTo("b", input))
|
||||
{
|
||||
if(mCursorStack.size())
|
||||
{
|
||||
populateList(mCursorStack.top()->getParent());
|
||||
mList.setCursor(mCursorStack.top());
|
||||
mCursorStack.pop();
|
||||
}else{
|
||||
mList.stopScrolling();
|
||||
mWindow->getViewController()->goToSystemSelect();
|
||||
}
|
||||
|
||||
return true;
|
||||
}else if(config->isMappedTo("right", input))
|
||||
{
|
||||
mList.stopScrolling();
|
||||
mWindow->getViewController()->goToNextSystem();
|
||||
return true;
|
||||
}else if(config->isMappedTo("left", input))
|
||||
{
|
||||
mList.stopScrolling();
|
||||
mWindow->getViewController()->goToPrevSystem();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return GameListView::input(config, input);
|
||||
}
|
34
src/views/BasicGameListView.h
Normal file
34
src/views/BasicGameListView.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "GameListView.h"
|
||||
#include "../components/TextListComponent.h"
|
||||
#include "../components/TextComponent.h"
|
||||
#include "../components/ImageComponent.h"
|
||||
|
||||
class BasicGameListView : public GameListView
|
||||
{
|
||||
public:
|
||||
BasicGameListView(Window* window, FileData* root);
|
||||
|
||||
// Called when a FileData* is added, has its metadata changed, or is removed
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change);
|
||||
|
||||
virtual bool input(InputConfig* config, Input input) override;
|
||||
|
||||
virtual void setTheme(const std::shared_ptr<ThemeData>& theme) override;
|
||||
|
||||
inline FileData* getCursor() { return mList.getSelected(); }
|
||||
virtual void setCursor(FileData* file) override;
|
||||
|
||||
protected:
|
||||
void populateList(FileData* root);
|
||||
|
||||
TextComponent mHeaderText;
|
||||
ImageComponent mHeaderImage;
|
||||
ImageComponent mBackground;
|
||||
TextListComponent<FileData*> mList;
|
||||
|
||||
inline FileData* getCurrentFolder() { return getCursor()->getParent(); }
|
||||
|
||||
std::stack<FileData*> mCursorStack;
|
||||
};
|
64
src/views/DetailedGameListView.cpp
Normal file
64
src/views/DetailedGameListView.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "DetailedGameListView.h"
|
||||
|
||||
DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
|
||||
BasicGameListView(window, root),
|
||||
mDescContainer(window), mDescription(window),
|
||||
mImage(window)
|
||||
{
|
||||
const float padding = 0.02f;
|
||||
|
||||
mList.setPosition(mSize.x() * (0.50f + padding), mList.getPosition().y());
|
||||
mList.setSize(mSize.x() * (0.50f - 2*padding), mList.getSize().y());
|
||||
mList.setCentered(false);
|
||||
mList.setCursorChangedCallback([&](TextListComponent<FileData*>::CursorState state) { updateInfoPanel(); });
|
||||
|
||||
mImage.setOrigin(0.5f, 0.0f);
|
||||
mImage.setPosition(mSize.x() * 0.25f, mList.getPosition().y());
|
||||
mImage.setResize(mSize.x() * (0.50f - 2*padding), 0, false);
|
||||
addChild(&mImage);
|
||||
|
||||
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.2f);
|
||||
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), 0);
|
||||
mDescContainer.setAutoScroll((int)(1600 + mDescContainer.getSize().x()), 0.025f);
|
||||
addChild(&mDescContainer);
|
||||
|
||||
mDescription.setSize(mDescContainer.getSize().x(), 0);
|
||||
mDescContainer.addChild(&mDescription);
|
||||
|
||||
updateInfoPanel();
|
||||
}
|
||||
|
||||
void DetailedGameListView::setTheme(const std::shared_ptr<ThemeData>& theme)
|
||||
{
|
||||
BasicGameListView::setTheme(theme);
|
||||
|
||||
mDescription.setFont(theme->getFont("descriptionFont"));
|
||||
mDescription.setColor(theme->getColor("descriptionColor"));
|
||||
}
|
||||
|
||||
void DetailedGameListView::updateInfoPanel()
|
||||
{
|
||||
FileData* file = (mList.getList().size() == 0 || mList.isScrolling()) ? NULL : mList.getSelected();
|
||||
|
||||
if(file == NULL)
|
||||
{
|
||||
mImage.setImage("");
|
||||
mDescription.setText("");
|
||||
}else{
|
||||
mImage.setImage(file->metadata.get("image"));
|
||||
|
||||
mDescContainer.setPosition(mDescContainer.getPosition().x(), mImage.getPosition().y() + mImage.getSize().y() * 1.02f);
|
||||
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y());
|
||||
mDescContainer.resetAutoScrollTimer();
|
||||
|
||||
mDescription.setText(file->metadata.get("desc"));
|
||||
}
|
||||
}
|
||||
|
||||
void DetailedGameListView::onFileChanged(FileData* file, FileChangeType type)
|
||||
{
|
||||
BasicGameListView::onFileChanged(file, type);
|
||||
|
||||
if(type == FILE_METADATA_CHANGED && file == getCursor())
|
||||
updateInfoPanel();
|
||||
}
|
25
src/views/DetailedGameListView.h
Normal file
25
src/views/DetailedGameListView.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "BasicGameListView.h"
|
||||
#include "../components/ImageComponent.h"
|
||||
#include "../components/TextComponent.h"
|
||||
#include "../components/ScrollableContainer.h"
|
||||
|
||||
class DetailedGameListView : public BasicGameListView
|
||||
{
|
||||
public:
|
||||
DetailedGameListView(Window* window, FileData* root);
|
||||
|
||||
virtual void setTheme(const std::shared_ptr<ThemeData>& theme) override;
|
||||
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change);
|
||||
|
||||
private:
|
||||
void updateInfoPanel();
|
||||
|
||||
ImageComponent mImage;
|
||||
|
||||
ScrollableContainer mDescContainer;
|
||||
TextComponent mDescription;
|
||||
};
|
||||
|
28
src/views/GameListView.cpp
Normal file
28
src/views/GameListView.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include "GameListView.h"
|
||||
#include "../Window.h"
|
||||
#include "../components/GuiMetaDataEd.h"
|
||||
|
||||
bool GameListView::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(config->getDeviceId() == DEVICE_KEYBOARD && input.id == SDLK_F3 && input.value != 0)
|
||||
{
|
||||
// open metadata editor
|
||||
FileData* file = getCursor();
|
||||
ScraperSearchParams p;
|
||||
p.game = file;
|
||||
p.system = file->getSystem();
|
||||
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, file->getPath().filename().string(),
|
||||
std::bind(&GameListView::onFileChanged, this, file, FILE_METADATA_CHANGED), [file, this] {
|
||||
boost::filesystem::remove(file->getPath()); //actually delete the file on the filesystem
|
||||
file->getParent()->removeChild(file); //unlink it so list repopulations triggered from onFileChanged won't see it
|
||||
onFileChanged(file, FILE_REMOVED); //tell the view
|
||||
delete file; //free it
|
||||
}));
|
||||
return true;
|
||||
}else if(config->isMappedTo("start", input) && input.value != 0)
|
||||
{
|
||||
// open menu
|
||||
}
|
||||
|
||||
return GuiComponent::input(config, input);
|
||||
}
|
36
src/views/GameListView.h
Normal file
36
src/views/GameListView.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "../FileData.h"
|
||||
#include "../Renderer.h"
|
||||
|
||||
class Window;
|
||||
class GuiComponent;
|
||||
class FileData;
|
||||
class ThemeData;
|
||||
|
||||
//GameListView needs to know:
|
||||
// What theme data to use
|
||||
// The root FileData for the tree it should explore
|
||||
|
||||
class GameListView : public GuiComponent
|
||||
{
|
||||
public:
|
||||
GameListView(Window* window, FileData* root) : GuiComponent(window), mRoot(root)
|
||||
{ setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); }
|
||||
|
||||
virtual ~GameListView() {}
|
||||
|
||||
// Called when a new file is added, a file is removed, or a file's metadata changes.
|
||||
virtual void onFileChanged(FileData* file, FileChangeType change) = 0;
|
||||
|
||||
// Called to set or update theme.
|
||||
virtual void setTheme(const std::shared_ptr<ThemeData>& theme) = 0;
|
||||
|
||||
virtual bool input(InputConfig* config, Input input) override;
|
||||
|
||||
virtual FileData* getCursor() = 0;
|
||||
virtual void setCursor(FileData*) = 0;
|
||||
|
||||
protected:
|
||||
FileData* mRoot;
|
||||
};
|
168
src/views/ViewController.cpp
Normal file
168
src/views/ViewController.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#include "ViewController.h"
|
||||
#include "../Log.h"
|
||||
#include "../SystemData.h"
|
||||
|
||||
#include "BasicGameListView.h"
|
||||
#include "DetailedGameListView.h"
|
||||
|
||||
ViewController::ViewController(Window* window)
|
||||
: GuiComponent(window), mCurrentView(nullptr), mCameraPos(Eigen::Affine3f::Identity())
|
||||
{
|
||||
mState.viewing = START_SCREEN;
|
||||
}
|
||||
|
||||
void ViewController::goToSystemSelect()
|
||||
{
|
||||
mState.viewing = SYSTEM_SELECT;
|
||||
goToSystem(SystemData::sSystemVector.at(0));
|
||||
}
|
||||
|
||||
SystemData* getSystemCyclic(SystemData* from, bool reverse)
|
||||
{
|
||||
std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
|
||||
|
||||
if(reverse)
|
||||
{
|
||||
auto it = std::find(sysVec.rbegin(), sysVec.rend(), from);
|
||||
assert(it != sysVec.rend());
|
||||
it++;
|
||||
if(it == sysVec.rend())
|
||||
it = sysVec.rbegin();
|
||||
return *it;
|
||||
}else{
|
||||
auto it = std::find(sysVec.begin(), sysVec.end(), from);
|
||||
assert(it != sysVec.end());
|
||||
it++;
|
||||
if(it == sysVec.end())
|
||||
it = sysVec.begin();
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
void ViewController::goToNextSystem()
|
||||
{
|
||||
assert(mState.viewing == SYSTEM);
|
||||
|
||||
SystemData* system = mState.data.system;
|
||||
if(system == NULL)
|
||||
return;
|
||||
|
||||
goToSystem(getSystemCyclic(system, false));
|
||||
}
|
||||
|
||||
void ViewController::goToPrevSystem()
|
||||
{
|
||||
assert(mState.viewing == SYSTEM);
|
||||
|
||||
SystemData* system = mState.data.system;
|
||||
if(system == NULL)
|
||||
return;
|
||||
|
||||
goToSystem(getSystemCyclic(system, true));
|
||||
}
|
||||
|
||||
void ViewController::goToSystem(SystemData* system)
|
||||
{
|
||||
mState.viewing = SYSTEM;
|
||||
mState.data.system = system;
|
||||
|
||||
mCurrentView = getSystemView(system);
|
||||
}
|
||||
|
||||
void ViewController::onFileChanged(FileData* file, FileChangeType change)
|
||||
{
|
||||
for(auto it = mSystemViews.begin(); it != mSystemViews.end(); it++)
|
||||
{
|
||||
it->second->onFileChanged(file, change);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewController::launch(FileData* game)
|
||||
{
|
||||
if(game->getType() != GAME)
|
||||
{
|
||||
LOG(LogError) << "tried to launch something that isn't a game";
|
||||
return;
|
||||
}
|
||||
|
||||
// Effect TODO
|
||||
game->getSystem()->launchGame(mWindow, game);
|
||||
}
|
||||
|
||||
std::shared_ptr<GameListView> ViewController::getSystemView(SystemData* system)
|
||||
{
|
||||
//if we already made one, return that one
|
||||
auto exists = mSystemViews.find(system);
|
||||
if(exists != mSystemViews.end())
|
||||
return exists->second;
|
||||
|
||||
//if we didn't, make it, remember it, and return it
|
||||
std::shared_ptr<GameListView> view;
|
||||
|
||||
if(system != NULL)
|
||||
{
|
||||
view = std::shared_ptr<GameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
|
||||
view->setTheme(system->getTheme());
|
||||
}else{
|
||||
LOG(LogError) << "null system"; // should eventually return an "all games" gamelist view
|
||||
}
|
||||
|
||||
std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
|
||||
int id = std::find(sysVec.begin(), sysVec.end(), system) - sysVec.begin();
|
||||
view->setPosition(id * (float)Renderer::getScreenWidth(), 0);
|
||||
|
||||
mSystemViews[system] = view;
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool ViewController::input(InputConfig* config, Input input)
|
||||
{
|
||||
if(mCurrentView)
|
||||
return mCurrentView->input(config, input);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
float clamp(float min, float max, float val)
|
||||
{
|
||||
if(val < min)
|
||||
val = min;
|
||||
else if(val > max)
|
||||
val = max;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
//http://en.wikipedia.org/wiki/Smoothstep
|
||||
float smoothStep(float edge0, float edge1, float x)
|
||||
{
|
||||
// Scale, and clamp x to 0..1 range
|
||||
x = clamp(0, 1, (x - edge0)/(edge1 - edge0));
|
||||
|
||||
// Evaluate polynomial
|
||||
return x*x*x*(x*(x*6 - 15) + 10);
|
||||
}
|
||||
|
||||
void ViewController::update(int deltaTime)
|
||||
{
|
||||
if(mCurrentView)
|
||||
{
|
||||
mCurrentView->update(deltaTime);
|
||||
|
||||
// move camera towards current view (should use smoothstep)
|
||||
Eigen::Vector3f diff = (mCurrentView->getPosition() + mCameraPos.translation()) * 0.0075f * (float)deltaTime;
|
||||
mCameraPos.translate(-diff);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewController::render(const Eigen::Affine3f& parentTrans)
|
||||
{
|
||||
Eigen::Affine3f trans = parentTrans * mCameraPos;
|
||||
|
||||
//should really do some clipping here
|
||||
for(auto it = mSystemViews.begin(); it != mSystemViews.end(); it++)
|
||||
it->second->render(trans);
|
||||
}
|
61
src/views/ViewController.h
Normal file
61
src/views/ViewController.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "GameListView.h"
|
||||
|
||||
class SystemData;
|
||||
|
||||
class ViewController : public GuiComponent
|
||||
{
|
||||
public:
|
||||
ViewController(Window* window);
|
||||
|
||||
// Navigation.
|
||||
void goToNextSystem();
|
||||
void goToPrevSystem();
|
||||
void goToSystem(SystemData* system);
|
||||
void goToSystemSelect();
|
||||
void showQuickSystemSelect();
|
||||
|
||||
void onFileChanged(FileData* file, FileChangeType change);
|
||||
|
||||
// Plays a nice launch effect and launches the game at the end of it.
|
||||
// Once the game terminates, plays a return effect.
|
||||
void launch(FileData* game);
|
||||
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
enum ViewMode
|
||||
{
|
||||
START_SCREEN,
|
||||
SYSTEM_SELECT,
|
||||
SYSTEM
|
||||
};
|
||||
|
||||
struct State
|
||||
{
|
||||
ViewMode viewing;
|
||||
|
||||
inline SystemData* getSystem() const { assert(viewing == SYSTEM); return data.system; }
|
||||
|
||||
private:
|
||||
friend ViewController;
|
||||
union
|
||||
{
|
||||
SystemData* system;
|
||||
} data;
|
||||
};
|
||||
|
||||
inline const State& getState() const { return mState; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<GameListView> getSystemView(SystemData* system);
|
||||
|
||||
std::shared_ptr<GuiComponent> mCurrentView;
|
||||
std::map< SystemData*, std::shared_ptr<GameListView> > mSystemViews;
|
||||
|
||||
Eigen::Affine3f mCameraPos;
|
||||
|
||||
State mState;
|
||||
};
|
Loading…
Reference in a new issue