Themes mostly stable, documentation updated

This commit is contained in:
Aloshi 2013-11-12 17:28:15 -06:00
parent 8bfde96966
commit a7359a2d08
37 changed files with 1228 additions and 1935 deletions

View file

@ -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
View 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
View file

@ -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

View file

@ -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

View file

@ -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;
};

View file

@ -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!";

View file

@ -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()));
}

View file

@ -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
View 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
View 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;
};

View file

@ -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,8 +91,11 @@ 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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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()

View file

@ -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;

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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));
}

View file

@ -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())
{

View 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);
}

View 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;
};

View 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();
}

View 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;
};

View 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
View 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;
};

View 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);
}

View 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;
};