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