**This document covers the creation of legacy themes only, which is a theme structure that is backward compatible with RetroPie EmulationStation. As ES-DE v2.0 and later has a new theme engine with much improved theming capabilities, this document is mostly intended for historical reference. For creating or porting themes to ES-DE the documentation in [THEMES.md](THEMES.md) should be used instead.**
In general ES-DE is backward compatible with RetroPie theme sets with the notable exception of the _Grid_ view style which is not supported. Themes with grid view styles included will still load correctly, but you can't actually use this view style. As well, any RetroPie theme using the `<resolution>` tag introduced in 2020 will not get loaded. This tag was a very bad idea as it changes sizing of elements from relative values to absolute pixel values.
ES-DE allows the grouping of themes for multiple game systems into a **theme set**. Each theme is a collection of **views** that define some **elements**, each with their own **properties**.
Every game system has its own subdirectory within the theme set directory structure, and these are defined in the systems configuration file `es_systems.xml` either via the optional `<theme>` tag, or otherwise via the mandatory `<name>` tag. When ES-DE populates a system on startup it will look for a file named `theme.xml` in each such directory.
By placing a theme.xml file directly in the root of the theme set directory, that file will be processed as a default if there is no game-specific theme.xml file available.
In the example below, we have a theme set named `mythemeset-DE` which includes the `snes` and `nes` systems. Assuming you have some games installed for these systems, the files `mythemeset-DE/nes/theme.xml` and `mythemeset-DE/snes/theme.xml` will be processed on startup. If there are no games available for a system, its theme.xml file will be skipped.
The directory structure of our example theme set could look something like the following:
```
...
themes/
mythemeset-DE/
core/
font.ttf
bold_font.ttf
frame.png
nes/
theme.xml
background.jpg
logo.svg
logo_video.svg
snes/
theme.xml
background.jpg
logo.svg
logo_video.svg
fonts.xml
theme.xml
```
The theme set approach makes it easy for users to install different themes and choose between them from the _UI Settings_ menu.
There are two places that ES-DE can load theme sets from:
An *element* is a particular visual element, such as an image or a piece of text. You can modify an element that already exists for a particular view, as was done for the "description" example:
"Extra" elements will be drawn in the order they are defined (so make sure to define backgrounds first). In what order they get drawn relative to the pre-existing elements depends on the view. Make sure "extra" element names do not clash with existing element names. An easy way to protect against this is to start all your extra element names with a prefix such as "e_".
*Properties* control how a particular *element* looks - for example its position, size, image path etc. The type of the property determines what kinds of values you can use. You can read about the types below in the "Reference" section. Properties are defined like this:
If you are writing a theme it's recommended to launch ES-DE with the `--debug` flag from a terminal window. You can also pass the `--resolution` flag to avoid having the application window fill the entire screen. By doing so, you can read error messages directly in the terminal window without having to open the log file. You can also reload the current gamelist view and system view with `Ctrl-R` if the `--debug` flag has been set.
Note that properties can get merged. In the above example the `<color>` tag from `fonts.xml` got overwritten by the equivalent tag in `snes/theme.xml`. This happens because that tag was effectively declared after the first `<color>` tag. Be aware that the included file also needs the `<formatVersion>` tag.
Sometimes you want to apply the same properties to the same elements across multiple views. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). So for example, to apply the same logo to the basic and detailed views you could write the following:
You can theme multiple elements *of the same type* simultaneously. The `name` attribute actually works as a list (delimited by any characters of `\t\r\n ,` - that is, whitespace and commas). This is useful if you want to, say, apply the same color to all the metadata labels:
Just remember, _this only works if the elements have the same type._
### Navigation sounds
Navigation sounds are configured globally per theme set, so it needs to be defined as a feature and with the view set to the special "all" category.
It's recommended to put these elements in a separate file and include it from the main theme file (e.g. `<include>./navigationsounds.xml</include>`).
There are seven different navigation sounds that can be configured. The names as well as the element structure should be self-explanatory based
on the example below.
Starting ES-DE with the --debug flag will provide feedback on whether any navigation sound elements were read from the theme set. If no navigation sounds are provided by the theme, ES-DE will use the bundled navigation sounds as a fallback. This is done per sound file, so the theme could provide for example one or two custom sounds while using the bundled ES-DE sounds for the rest.
Example debug output:
```
Jul 12 11:28:58 Debug: NavigationSounds::loadThemeNavigationSounds(): Theme set includes navigation sound support, loading custom sounds
Jul 12 11:28:58 Debug: Sound::getFromTheme(): Looking for tag <soundname="systembrowse">
Jul 12 11:28:58 Debug: Sound::getFromTheme(): Tag found, ready to load theme sound file
Jul 12 11:28:58 Debug: Sound::getFromTheme(): Looking for tag <soundname="quicksysselect">
Jul 12 11:28:58 Debug: Sound::getFromTheme(): Tag not found, using fallback sound file
```
Example `navigationsounds.xml`, to be included from the main theme file:
```xml
<theme>
<formatVersion>7</formatVersion>
<featuresupported="navigationsounds">
<viewname="all">
<soundname="systembrowse">
<path>./core/sounds/systembrowse.wav</path>
</sound>
<soundname="quicksysselect">
<path>./core/sounds/quicksysselect.wav</path>
</sound>
<soundname="select">
<path>./core/sounds/select.wav</path>
</sound>
<soundname="back">
<path>./core/sounds/back.wav</path>
</sound>
<soundname="scroll">
<path>./core/sounds/scroll.wav</path>
</sound>
<soundname="favorite">
<path>./core/sounds/favorite.wav</path>
</sound>
<soundname="launch">
<path>./core/sounds/launch.wav</path>
</sound>
</view>
</feature>
</theme>
```
### Element rendering order using zIndex
You can change the order in which elements are rendered by setting their `zIndex` values. All elements have a default value so you only need to define it for the ones you wish to explicitly change. Elements will be rendered in order from smallest to largest values.
Below are the default zIndex values per element type:
- A header image. If a non-empty `path` is specified, `text name="logoText"` will be hidden and this image will be, by default, displayed roughly in its place.
- A header image. If a non-empty `path` is specified, `text name="logoText"` will be hidden and this image will be, by default, displayed roughly in its place.
- Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default.
- Metadata
- Labels
*`text name="md_lbl_rating"` - ALL
*`text name="md_lbl_releasedate"` - ALL
*`text name="md_lbl_developer"` - ALL
*`text name="md_lbl_publisher"` - ALL
*`text name="md_lbl_genre"` - ALL
*`text name="md_lbl_players"` - ALL
*`text name="md_lbl_lastplayed"` - ALL
*`text name="md_lbl_playcount"` - ALL
* Values
- _All values will follow to the right of their labels if a position isn't specified._
*`image name="md_image"` - POSITION | SIZE | Z_INDEX
- Path is the "image" metadata for the currently selected game.
*`rating name="md_rating"` - ALL
- The "rating" metadata.
*`datetime name="md_releasedate"` - ALL
- The "releasedate" metadata.
*`text name="md_developer"` - ALL
- The "developer" metadata.
*`text name="md_publisher"` - ALL
- The "publisher" metadata.
*`text name="md_genre"` - ALL
- The "genre" metadata.
*`text name="md_players"` - ALL
- The "players" metadata (number of players the game supports).
*`badges name="md_badges"` - ALL
- The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games.
- Text is the "desc" metadata. If no `pos`/`size` is specified, will move and resize to fit under the lowest label and reach to the bottom of the screen.
- A header image. If a non-empty `path` is specified, `text name="logoText"` will be hidden and this image will be, by default, displayed roughly in its place.
- Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. Left aligned by default.
- Metadata
- Labels
*`text name="md_lbl_rating"` - ALL
*`text name="md_lbl_releasedate"` - ALL
*`text name="md_lbl_developer"` - ALL
*`text name="md_lbl_publisher"` - ALL
*`text name="md_lbl_genre"` - ALL
*`text name="md_lbl_players"` - ALL
*`text name="md_lbl_lastplayed"` - ALL
*`text name="md_lbl_playcount"` - ALL
* Values
- _All values will follow to the right of their labels if a position isn't specified._
*`image name="md_image"` - POSITION | SIZE | Z_INDEX
- Path is the "image" metadata for the currently selected game.
*`image name="md_marquee"` - POSITION | SIZE | Z_INDEX
- Path is the "marquee" metadata for the currently selected game.
*`video name="md_video"` - POSITION | SIZE | Z_INDEX
- Path is the "video" metadata for the currently selected game.
*`rating name="md_rating"` - ALL
- The "rating" metadata.
*`datetime name="md_releasedate"` - ALL
- The "releasedate" metadata.
*`text name="md_developer"` - ALL
- The "developer" metadata.
*`text name="md_publisher"` - ALL
- The "publisher" metadata.
*`text name="md_genre"` - ALL
- The "genre" metadata.
*`text name="md_players"` - ALL
- The "players" metadata (number of players the game supports).
*`badges name="md_badges"` - ALL
- The "badges" metadata. Displayed as a group of badges that indicate metadata such as favorites and completed games.
- Text is the "desc" metadata. If no `pos`/`size` is specified, will move and resize to fit under the lowest label and reach to the bottom of the screen.
* NORMALIZED_PAIR - two decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5`. Most commonly used for position (x and y coordinates) and size (width and height).
* NORMALIZED_RECT - four decimals, in the range [0..1], delimited by a space. For example, `0.25 0.5 0.10 0.30`. Most commonly used for padding to store top, left, bottom and right coordinates.
* PATH - a path. If the first character is a `~`, it will be expanded into the environment variable for the home path (`$HOME` for Linux or `%HOMEPATH%` for Windows) unless overridden using the --home command line option. If the first character is a `.`, it will be expanded to the theme file's directory, allowing you to specify resources relative to the theme file, like so: `./../general_art/myfont.ttf`.
Common to almost all elements is a `pos` and `size` property of the NORMALIZED_PAIR type. They are normalized in terms of their "parent" object's size; 99% of the time, this is just the size of the screen. In this case, `<pos>0 0</pos>` would correspond to the top left corner, and `<pos>1 1</pos>` the bottom right corner (a positive Y value points further down). `pos` almost always refers to the top left corner of your element. You *can* use numbers outside of the [0..1] range if you want to place an element partially or completely off-screen.
- If only one axis is specified (and the other is zero), then the other will be automatically calculated in accordance with the image's aspect ratio. Setting both axes to 0 is an error and the size will be clamped to `0.001 0.001` in this case.
- Minimum value per axis is `0.001` and maximum value per axis is `2`. If specifying a value outside the allowed range then no attempt will be made to preserve the aspect ratio.
*`maxSize` - type: NORMALIZED_PAIR
- The image will be resized as large as possible so that it fits within this size while maintaining its aspect ratio. Use this instead of `size` when you don't know what kind of image you're using so it doesn't get grossly oversized on one axis (e.g. with a game's image metadata).
- Minimum value per axis is `0.001` and maximum value per axis is `2`
- Where on the image `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the image exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
- Multiply each pixel's color by this color. For example, an all-white image with `<color>FF0000</color>` would become completely red. You can also control the transparency of an image with `<color>FFFFFFAA</color>` - keeping all the pixels their normal color and only affecting the alpha channel.
- If only one axis is specified (and the other is zero), then the other will be automatically calculated in accordance with the static image's aspect ratio and the video's aspect ratio. Setting both axes to 0 is an error and the size will be clamped to `0.01 0.01` in this case.
- Minimum value per axis is `0.01` and maximum value per axis is `2`. If specifying a value outside the allowed range then no attempt will be made to preserve the aspect ratio.
*`maxSize` - type: NORMALIZED_PAIR
- The static image and video will be resized as large as possible so that they fit within this size while maintaining their aspect ratios. Use this instead of `size` when you don't know what kind of video you're using so it doesn't get grossly oversized on one axis (e.g. with a game's video metadata).
- Minimum value per axis is `0.01` and maximum value per axis is `2`
- Where on the image `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the image exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
-`w h` - works like a "text box". If `h` is non-zero and `h`<= `fontSize` (implying it should be a single line of text), text that goes beyond `w` will be truncated with an elipses (...).
- Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
- Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
- Allows moving of the "selector bar" up or down from its computed position. Useful for fine tuning the position of the "selector bar" relative to the text.
- Horizontal offset for text from the alignment point. If `alignment` is "left", offsets the text to the right. If `alignment` is "right", offsets text to the left. No effect if `alignment` is "center". Given as a percentage of the element's parent's width (same unit as `size`'s X value).
*`forceUppercase` - type: BOOLEAN. Draw text in uppercase.
*`lineSpacing` - type: FLOAT. Controls the space between lines (as a multiple of font height). Default is 1.5.
ES-DE 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).
- Only one value is actually used. The other value should be zero (e.g. specify width OR height, but not both. This is done to maintain the aspect ratio.)
- Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
- Multiply each pixel's color by this color. For example, an all-white image with `<color>FF0000</color>` would become completely red. You can also control the transparency of an image with `<color>FFFFFFAA</color>` - keeping all the pixels their normal color and only affecting the alpha channel.
-`w h` - works like a "text box". If `h` is non-zero and `h`<= `fontSize` (implying it should be a single line of text), text that goes beyond `w` will be truncated with an elipses (...).
- Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
*`textColorDimmed` - type: COLOR. Default is the same value as textColor. Must be placed under the 'system' view.
*`iconColor` - type: COLOR. Default is 777777FF.
*`iconColorDimmed` - type: COLOR. Default is the same value as iconColor. Must be placed under the 'system' view.
*`fontPath` - type: PATH.
*`fontSize` - type: FLOAT.
*`customButtonIcon` - type: PATH.
- A button icon override. Specify the button type in the attribute `button`. The available buttons are:
`dpad_updown`,
`dpad_leftright`,
`dpad_all`,
`thumbstick_click`,
`button_l`,
`button_r`,
`button_lr`,
`button_lt`,
`button_rt`,
`button_a_SNES`,
`button_b_SNES`,
`button_x_SNES`,
`button_y_SNES`,
`button_back_SNES`,
`button_start_SNES`,
`button_a_PS`,
`button_b_PS`,
`button_x_PS`,
`button_y_PS`,
`button_back_PS4`,
`button_start_PS4`,
`button_back_PS5`,
`button_start_PS5`,
`button_a_XBOX`,
`button_b_XBOX`,
`button_x_XBOX`,
`button_y_XBOX`,
`button_back_XBOX`,
`button_start_XBOX`,
`button_back_XBOX360`,
`button_start_XBOX360`.
#### badges
It's strongly recommended to use the same image dimensions for all badges as varying aspect ratios will lead to alignment issues. For the controller images it's recommended to keep to the square canvas size used by the default bundled graphics as otherwise sizing and placement will be inconsistent (unless all controller graphic files are customized of course).
*`pos` - type: NORMALIZED_PAIR.
*`size` - type: NORMALIZED_PAIR.
- Possible combinations:
-`w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. Minimum value per axis is `0.03`, maximum value is `1.0`. Default is `0.15 0.20`.
- Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied. Default is `0.5 0.5`.
- angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`.
- Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. Default is `row`.
*`lines` - type: FLOAT.
- The number of lines available. Default is `2`.
*`itemsPerLine` - type: FLOAT.
- Number of badges that fit on a line. When more badges are available a new line will be started. Default is `4`.
*`itemMargin` - type: NORMALIZED_PAIR.
- The margins between badges. Possible combinations:
-`x y` - horizontal and vertical margins. Minimum value per axis is `0`, maximum value is `0.2`. Default is `0.01 0.01`. If one of the axis is set to `-1` the margin of the other axis (in pixels) will be used, which makes it possible to get identical spacing between all items regardless of screen aspect ratio.
*`slots` - type: STRING.
- The badge types that should be displayed. Should be specified as a list of strings delimited by any characters of `\t\r\n ,` - that is, whitespace and commas. The order will be followed when placing badges on the screen. Available badges are:
-`favorite`: Will be shown when the game is marked as favorite.
-`completed`: Will be shown when the game is marked as completed.
-`kidgame`: Will be shown when the game is marked as a kids game.
-`broken`: Will be shown when the game is marked as broken.
-`controller`: Will be shown and overlaid by the corresponding controller icon if a controller type has been selected for the game using the metadata editor.
-`altemulator`: Will be shown when an alternative emulator is setup for the game.
*`controllerPos` - type: NORMALIZED_PAIR.
- The position of the controller icon relative to the parent `controller` badge. Minimum value per axis is `-1.0`, maximum value is `2.0`. Default is `0.5 0.5` which centers the controller icon on the badge.
*`controllerSize` - type: FLOAT.
- The size of the controller icon relative to the parent `controller` badge. Minimum value is `0.1`, maximum value is `2.0`. Setting the value to `1.0` sizes the controller icon to the same width as the parent badge. The image aspect ratio is always maintained.
*`customBadgeIcon` - type: PATH.
- A badge icon override. Specify the badge type in the attribute `badge`. The available badges are the ones listed above.
*`customControllerIcon` - type: PATH.
- A controller icon override. Specify the controller type in the attribute `controller`. These are the available types:
- Where on the carousel `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the carousel exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themeable, "ORIGIN" is implied.
The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu.