Compare commits

...

122 commits

Author SHA1 Message Date
Jools Wills
88fe9771f0
Merge pull request #889 from cmitu/mame-resources4
resources: update MAME files
2025-03-12 08:05:05 +00:00
cmitu
17c96ed83d resources: update MAME files
Updated resources to 11.03.2025, using dat files from:

* FinalBurn Neo
* Mame2003 Plus
* MAME 0.275
2025-03-12 05:52:10 +00:00
pjft
51b6f4162d
Merge pull request #888 from cmitu/scraper-updates2
scraper: added FM Towns (Marty)
2024-12-12 14:00:49 +00:00
cmitu
803bad626e scraper: added FM Towns (Marty) 2024-12-11 05:16:57 +00:00
pjft
6b4281ac9f
Merge pull request #887 from GoldenPalazzo/ds4-rgp01-triggers
Detecting DS4 and Anbernic P01 analog triggers
2024-12-08 15:21:58 +00:00
Francesco Palazzo
894543cea6 Detecting DS4 and Anbernic P01 analog triggers 2024-12-08 15:16:52 +01:00
pjft
d46f04a25b
Merge pull request #883 from o-p-a/display_option
Add new option --monitor
2024-09-09 12:26:10 +01:00
opa
a7a9dec637 Add option --monitor N 2024-08-09 23:23:52 +09:00
pjft
81c62c97ee
Merge pull request #880 from o-p-a/help-fullscreen-borderless
help text for option --fullscreen-borderless
2024-07-15 11:00:12 +01:00
opa
75c01f73ab help text for option --fullscreen-borderless 2024-07-12 23:44:54 +09:00
pjft
e8fb5a0869
Merge pull request #875 from Gemba/fix_save_on_new_custom_collections
Fix persistence of new collections, either from theme or custom name
2024-07-01 10:14:37 +01:00
Gemba
3c41f15374 Fix persistence of new collections, either from theme or custom name 2024-07-01 08:18:21 +02:00
pjft
a9ee7e48c4
Merge pull request #878 from Gemba/fix_collection_metadataedit_navigation
Fixes segfault after metadata edit in collections and navigating back…
2024-06-24 08:40:52 +01:00
Gemba
6de6151b09 Fixes segfault after metadata edit in collections and navigating back to carousel 2024-06-23 17:20:34 +02:00
pjft
4a064a2130
Merge pull request #874 from Gemba/fix_lrlb_paging_cursor_misplaced_lastplayed
Fix cursor overrun in last played collection when LR/LB paging is used
2024-04-22 22:31:58 +01:00
Gemba
485b995196 Fix for cursor overrun in last played collection when LR/LB paging is active
and fix cursor placement when changing theme with a smaller/larger gamelist viewport.
2024-04-18 21:57:34 +02:00
pjft
95ba158235
Merge pull request #867 from pjft/fix-vlc-mute
Update VLC Mute logic
2024-02-26 12:41:36 +00:00
pjft
0aa10ae9a1 Update VLC Mute logic
This updates it to support mixer passthrough, and accommodate cases where the mute function wasn't supported.
2024-02-26 12:37:51 +00:00
pjft
fc66fd78ac
Merge pull request #866 from cmitu/mame-resources3
resources: update MAME files and resource generator
2024-02-23 08:17:33 +00:00
cmitu
022e621156 resources: update MAME files and resource generator
Updated the generation script to account for Python 3.12 deprecation of 'datetime.now()'.
Re-generated the MAME resource files from the following DATs:

 * MAME 262 (Jan 2024)
 * FBNeo (22 Feb 2024)
 * Libretro Mame2003-Plus (22 Feb 2024)
2024-02-22 18:02:17 +00:00
pjft
4094f8af03
Merge pull request #863 from Gemba/fix_861_nested_gamelaunch_files
Fix for nested game launchers
2024-02-21 21:09:01 +00:00
pjft
3ed0ac6e83
Merge pull request #865 from cmitu/curl-warning
Handle cURL library deprecation for redirect options
2024-02-21 07:48:55 +00:00
cmitu
d544c73d81 Handle cURL library deprecation for redirect options
Starting with 7.85, `CURLOPT_REDIR_PROTOCOLS` is deprecated [1] and `CURL_REDIR_PROTOCOLS_STR` [2] should be used instead.
The changes modify the option used depending on the (compile time) libcurl version.

[1] https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS.html
[2] https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html
2024-02-21 04:24:12 +00:00
pjft
7b45bf7144
Merge pull request #864 from pjft/fix-collection-warning
Address warning when exiting screensaver
2024-02-20 23:02:39 +00:00
pjft
bebf1e5f8e Address warning when exiting screensaver 2024-02-20 22:44:56 +00:00
Gemba
60a116708e Fix for nested game launchers
Prevent game launchers in a directory used as game launcher, as FileData::addChild() is only valid on filesystem folders with no or no valid game extension for that platform.

Example:
```
scummvm/Violet.svm/  # directory acts as game launcher in ES, bc. of the extension.
├── Violet.svm       # file is another valid game launcher.
└── Violet.zblorb
```

May be end up in the gamelist as:
```
...
<game>
  <path>./Violet.svm</game> <!-- <game>-element bc. ES recognize only those as launchable -->
...
</game>
<game>
  <path>./Violet.svm/Violet.svm</game> <!-- <game> can not be added to parent node as it is also a <game> and not a <folder> -->
...
</game>
```
2024-02-19 13:32:33 +01:00
pjft
bb6d8e9e47
Merge pull request #862 from pjft/fix-invalid-folder
Fix initialization of metadata
2024-02-19 08:33:19 +00:00
pjft
049d378c5c Fix initialization of metadata 2024-02-18 19:59:45 +00:00
Jonathan Washington
3e23bcac75
Update README.md (#834)
Mention that the `pugixml` system library can be used if found, but it's optional
2024-02-17 16:56:22 +02:00
pjft
b912c62580
Merge pull request #860 from Gemba/fix_md_rating_edit_save_prompt
Fix for false "Save Changes" prompt on string compare of game rating.
2024-02-14 11:40:12 +00:00
Gemba
07026edba7 Fix for false "Save Changes" prompt on string compare of game rating. 2024-02-11 16:24:13 +01:00
pjft
a5cc5cea9c
Merge pull request #859 from Gemba/refact_settings_intmap_cpp11
Refactorings to #857 in C++11 standard
2024-02-09 21:07:06 +00:00
Gemba
a4768e7022 Refactorings to RetroPie#857 in C++11 standard and modified es_settings.cfg format for random collections 2024-02-09 21:59:04 +01:00
pjft
aebbf19b3e
Merge pull request #841 from Gemba/fb_gamelist_folder_persistence
Changes to maintain and persist <folder/> information in a gamelist:
2024-02-07 18:00:57 +00:00
Gemba
a113b22ca7 Changes to maintain and persist <folder/> information in a gamelist:
1. folder elements are loaded when present in a gamelist and if they match the filesystem representation
2. missing optional folder elements are rendered empty in theme (and not "unknown")
3. As before non-existing folder elements are generated by ES, with mandatory elements <path/> and <name/>, but with this PR, whenever metadata is edited for this folder object (FileData class) it is persisted
4. Editiing metadata on existing <folder/> gets persisted too
5. UI metadata edit: layout fix that renders entered metadata not centered when returning from editing
6. UI metadata edit: format tooltip for date format in release date field
2024-02-07 17:50:09 +01:00
pjft
6c7dc88ffe
Merge pull request #853 from Gemba/fb_custom_coll_during_scrsaver
Minor suggestions for "Collection use during Screensaver"
2024-02-06 21:59:51 +00:00
Gemba
16cb012a91 Minor issues with "Collection use during Screensaver":
1. Menuitem text shortened to fit on 4:3 or 5:4 displays.
~~~2. <DEFAULT> renamed to "Favorites".~~~ undone
~~~3. Simplified applySettings() as I could not manage to defaultScreenSaverCollection->getSelected() to be not set.~~~ undone
4. setupScreenSaverEditingCollection() renamed to handleScreenSaverEditingCollection().
2024-02-06 22:39:53 +01:00
pjft
6e47f19510
Merge pull request #857 from pjft/random-updates
Updates to Random Collections
2024-02-02 13:54:56 +00:00
pjft
4245966a0b Updates to Random Collections
- Updated labels
- Add "Exclusion" collection setting
2024-02-02 13:53:25 +00:00
pjft
c8bcfa4420
Merge pull request #856 from pjft/random-collection
Add support for a Random collection
2024-02-01 11:01:09 +00:00
pjft
97eea573eb Add Support for Random Game Collections 2024-02-01 10:58:40 +00:00
pjft
771e457a1f
Merge pull request #855 from WiltonMicroSystems/WIN32
Small corrections to WIN32 build documentation in README.md
2024-01-28 16:07:02 +00:00
pjft
fd85ed6a3f
Merge pull request #854 from pjft/fix-vlc-mute
Fix VLC not unmuting itself after initialization
2024-01-28 14:16:49 +00:00
pjft
9b0d64fbac Fix VLC not unmuting itself after initialization 2024-01-28 14:15:45 +00:00
Bob Wilton
0ee1aba658 Small corrections to Windows build documentation 2024-01-28 09:08:58 -05:00
pjft
eb4fbab399
Merge pull request #852 from pjft/default-collection
Add option to specify collection to store favorites from screensaver
2024-01-26 20:11:02 +00:00
pjft
aa88f6b206 Add option to specify collection to store favorites from screensaver 2024-01-26 20:04:41 +00:00
pjft
0a29d3b1ac
Merge pull request #851 from Gemba/rf_screensaver_enhanced_controls
Refactorings and two cornercases of 'screensaver enhanced controls'
2024-01-22 19:51:15 +00:00
Gemba
45259de2c7 Squashed commit of:
- Use constant var instead of -1 for setViewportTop()
- integrate logic to list cursor refresh into setCursor()
- Fix cornercase with cursor placement after screensaver 'A' btn press
- Fix cornercase in GuiGamelistOptions: If user stands inside Collections system which shows folders, then the option to launch a system only screensaver should not be available, only inside such folder. Additionally the Retropie Config 'System' should also not show the launch system screensaver menuentry.
2024-01-22 20:08:25 +01:00
pjft
e782d53637
Merge pull request #847 from pjft/system-screensaver
Change order of menu per user request
Fix image screensaver game selection
Fix glitches with subtitles and video gamelist view
2024-01-21 09:58:22 +00:00
pjft
5d2221e207 Small fixes and UX tweaks to screensaver changes
Change order of menu
Fix image screensaver game selection
Fix some glitches with subtitles and video gamelist view
2024-01-21 09:55:12 +00:00
pjft
19fd7d9ae9
Merge pull request #849 from pjft/collections-toggle-perf
Try to improve performance when adding/removing games from collections
2024-01-18 22:58:24 +00:00
pjft
4f5d50ae5d Try to improve performance when adding/removing games from collections 2024-01-18 22:56:24 +00:00
pjft
3cb9468891
Merge pull request #848 from pjft/revert-gamelist-recreate
Revert "Only recreate GameList View if it can change type"
2024-01-18 20:29:06 +00:00
pjft
822eb758cc Revert "Only recreate GameList View if it can change type"
This reverts part of commit beb193ce0a for unintended behaviors.
2024-01-18 20:24:47 +00:00
pjft
6edd9669aa Fix inputs for image screensaver 2024-01-15 10:05:31 +00:00
pjft
f604dcf2fa
Merge pull request #846 from pjft/system-screensaver
Improve random video/image screensaver controls
2024-01-13 17:22:39 +00:00
pjft
809ffe308e Add Favorites and Previous controls to screensaver 2024-01-13 17:07:33 +00:00
pjft
5baa8f04fa
Merge pull request #845 from pjft/improve-collections-performance
Only recreate GameList View if it can change type
2024-01-11 16:50:32 +00:00
pjft
beb193ce0a Only recreate GameList View if it can change type
- Add logic to only recreate view if automatic, or in a view type that can be upgraded with metadata details
2024-01-10 22:07:46 +00:00
pjft
0b4818b827
Merge pull request #844 from pjft/fix-opaque-rating
Fix rendering of opaque rating textures
2024-01-10 11:35:42 +00:00
pjft
1fa7f4cd9e Fix rendering of opaque rating textures
Change order so we render the filled texture top of the filled texture.
2024-01-10 11:32:56 +00:00
pjft
0905c6dba4 Add option to launch screensaver with games from only the current system
- Add option to launch random video/image screensaver with games from only the current system
- Adjust screensaver controls to allow A to select the current game without launching
2024-01-10 08:12:40 +00:00
pjft
e02ca4a18e
Merge pull request #843 from pjft/pugixml-warn
Fix Pugixml deprecated method
2024-01-08 22:07:29 +00:00
pjft
9d3afd020b Fix Pugixml deprecated method 2024-01-08 22:05:49 +00:00
pjft
587f757ca5
Merge pull request #842 from sinavir/fix_logfile_closing
Fix logfile closing when logfile is not opened
2024-01-06 22:00:48 +00:00
sinavir
858bcf4a23 Fix logfile closing 2024-01-06 22:15:26 +01:00
pjft
01de7618d0
Merge pull request #840 from pjft/invalid-inputs
Ignore unmapped windows during screensaver controls
2023-12-16 23:16:38 +00:00
pjft
0702bf4d1b Ignore unmapped windows during screensaver controls
Some controllers send ghost inputs at times (slight analog movements, or others) that would wake up the video screensaver when expecting other controls. This ignores them in that context.
2023-12-16 23:13:43 +00:00
pjft
eec2cbfc2a
Merge pull request #839 from pjft/threaded-index
Fix crash on game launch from screensaver
2023-12-15 16:36:43 +00:00
pjft
fe0dc785a3
Merge pull request #838 from pjft/collections-crash
Fix crash during collection updates
2023-12-15 16:34:29 +00:00
pjft
610105ec65 Fix crash on game launch from screensaver 2023-12-15 16:31:33 +00:00
pjft
5d6278449d Fix crash during collection updates 2023-12-15 16:30:54 +00:00
pjft
b8800f02f4
Merge pull request #835 from Gemba/fix_slideshow_controls_during_sleep
fix 'input in sleep mode starts game'
2023-12-08 22:53:32 +00:00
Gemba
5985498cd6 fix 'input in sleep mode starts game' 2023-12-08 21:24:06 +01:00
pjft
6d10f1c327
Merge pull request #833 from cmitu/gamelist-exclude-2
system: don't lowercase the extensions during parsing
2023-10-21 09:01:03 +01:00
cmitu
c8f176529b system: don't lowercase the extensions during parsing
This reverts the changes in 5349be1dce that lower-cased the extension of the files listed and the system registered extensions.

On case-sensitive filesystems (i.e. Linux), this restores the ability to 'hide' certain files from being shown just be changing the case of the files' extension.
This ability is used by certain RetroPie users to hide unwanted files, without them showin up in the gamelist, by modifying `es_systems.cfg` and removing the uppercase extension of the files.
2023-10-21 03:48:06 +01:00
pjft
0880bf5469
Merge pull request #832 from cmitu/gamelist-exclude
Gamelist bugfix: don't show un-registered extensions
2023-10-08 07:49:32 +01:00
cmitu
fc0f18b50a Gamelist bugfix: don't show un-registered extensions
Updated the parsing of an existing gamelist to exclude entries without an extension present in `es_systems.cfg`.
This can happen when the list of extensions is updated and some extensions are removed, but the `gamelist.xml` entries are not removed. EmulationStation would still consider the excluded extension's entries as valid gamelist entries, without filtering them out.
2023-10-08 07:43:24 +01:00
pjft
2fd3a78e52
Merge pull request #831 from Gemba/fix_slideshow_custom_video_pt2
Refactorings for "slideshow with custom video"
2023-08-26 22:19:22 +01:00
Gemba
8bad0f0d25 Refactorings, mainly to avoid duplicate code and nested if statements 2023-08-26 22:54:38 +02:00
pjft
421874fbc4
Merge pull request #830 from Gemba/fix_slideshow_custom_video_pt1
Fixes 'blank screen effect' when custom video is used in slideshow
2023-08-25 16:39:40 +01:00
Gemba
992820c76b Fixes 'blank screen effect' when custom video is used in slideshow 2023-08-24 21:25:55 +02:00
pjft
87a05a138b
Merge pull request #829 from Gemba/fix_last_textcomponent_too_large_in_menu
Minor: A TextComponent's value is rendered with an overly large height ...
2023-08-22 22:23:42 +01:00
pjft
928391103b
Merge pull request #821 from tfc/pugixml-portability
Fix pugixml include paths and make submodule optional
2023-08-22 22:23:04 +01:00
Jacek Galowicz
58750da873 Fix include paths of pugixml 2023-08-22 23:21:30 +02:00
Gemba
6df518e171 Fix: A TextComponent's value is rendered with an overly large height
in a GUI menu when put into the _last_ row of a menu.
2023-08-22 20:58:06 +02:00
pjft
362b675ec8
Merge pull request #822 from Gemba/fix_glitches_gui_gamelist_options_menu
Fixes these glitches in UI gamelist options menu:
2023-08-21 22:38:02 +01:00
Gemba
b9c0a5c4b2 Rebased to resolve conflicts with main. Fixes these glitches in options menu:
- Sort order not remembered during ES session
- "Jump to ..." only correct on "name, asc." sort order
- Metadata edit with rename name leaves list cursor misplaced
- Scroll of long filename in gamelist restarts even if option dialog is closed without any changes
- Sort order description "filename" changed to "name"
- Some minor code smells
2023-08-21 19:40:22 +02:00
pjft
127e85fe13
Merge pull request #827 from tomaz82/fix_win32
Fix VC2017 not having unistd.h
2023-08-18 15:45:44 +01:00
Tomas Jakobsson
04829a6c73 Fix VC2017 not having unistd.h 2023-08-18 15:56:02 +02:00
pjft
bf03ad4846
Merge pull request #825 from Gemba/fix_options_jumpto_selection
Fixes the null selection for ROMs starting with Unicode or Umlauts
2023-08-15 23:06:00 +01:00
pjft
ce9464767b
Merge pull request #826 from cmitu/vlc-doc
docs: document the libVLC dependency
2023-08-13 13:10:34 +01:00
cmitu
7d922c0b0d docs: document the libVLC dependency 2023-08-13 09:23:48 +01:00
Gemba
54ae1e623a Fixes the null selection for ROMs starting with Unicode or Umlauts
The null selection will then on confirming "Jump to ..." in the Gamelist Options menu crash ES (assertion failed)
2023-08-12 13:39:38 +02:00
pjft
44df564dcf
Merge pull request #824 from iratahack/master
Resolved compiler warnings using clang 14.0.0 on Ubuntu-22.04.3
2023-08-11 16:43:12 +01:00
Craig Hackney
17b5b0b884 Resolved compiler warnings using clang 14.0.0 on Ubuntu-22.04.3 2023-08-11 08:12:57 -07:00
Craig Hackney
f9542838c2 Updates to allow building on Ubuntu-22.04.2 with clang 2023-08-11 08:36:36 +01:00
pjft
f4c3815b44
Merge pull request #823 from WiltonMicroSystems/WIN32
Add and document WIN32 build support
2023-08-08 07:43:27 -06:00
Bob Wilton
b348bbd1ab Added WIN32 build workflow using Github Actions 2023-08-07 22:32:39 -04:00
Bob Wilton
dec977ce00 Changed Ubuntu build workflow to a manual trigger 2023-08-07 22:30:43 -04:00
Bob Wilton
b2d41e84fd Fixed warning with finding RapidJSON package 2023-08-07 22:27:57 -04:00
Bob Wilton
b2203777a1 Fix to eliminate PkgConfig warning with WIN32 builds 2023-08-07 22:25:30 -04:00
Bob Wilton
236bb03fe7 Documented steps to build ES for WIN32 2023-08-07 22:23:11 -04:00
Bob Wilton
2846859508 Minimal changes required to compile ES for WIN32 2023-08-07 22:21:34 -04:00
pjft
30cbdeb7cd
Address screensaver video/audio not stopping on game launch
Recent reports of specific configurations or versions changing some of the event handling order, resulting in the screensaver video/audio not stopping when a game is launched from it.

Can't replicate on my end, but these changes should be harmless for the current setup, and should help with that scenario. To be fair, it should have been there from the get go - it's just pure luck that things worked until now, as the screensaver was being halted before this code would run.
2023-07-21 17:00:20 +01:00
Jools Wills
08d74d3345
Merge pull request #797 from cmitu/sdl-vendored-input
input: export additional info for joysticks inputs
2023-04-11 20:15:24 +01:00
pjft
9afa234f3c
Merge pull request #817 from Gemba/fb_reenable_backward_compatible_wrapping
Reenables backward compatible text wrapping for some themes
2023-03-23 10:03:49 +00:00
Gemba
955fe06086 Squashed commit of the following:
commit 5d07dac51f0ac77cb3771dc2a7c69677b882de00
Author: Gemba <uid0@sdf-eu.org>
Date:   Wed Mar 22 01:04:40 2023 +0100

    re-enable wrapping when fixed >0 y-size value of TextComponent is given
2023-03-22 01:14:58 +01:00
Gemba
cfc514bc8e Merge branch 'master' of https://github.com/Gemba/EmulationStation 2023-03-21 23:58:45 +01:00
pjft
70f737b5f0
Merge pull request #816 from Gemba/fix_optimize_textwrap_calls
Reduces callcount to TextComponent.onTextChanged()
2023-03-04 08:47:40 +00:00
Gemba
e44e0b7c3d reduce callcount to TextComponent.onTextChanged() 2023-03-03 20:39:24 +01:00
Gemba
e7558c8191 Merge branch 'master' of https://github.com/Gemba/EmulationStation 2023-03-03 19:12:24 +01:00
pjft
4389185cfb
Merge pull request #811 from Gemba/fix_misplaced_cursor_on_lb_lr_paging
Fix misplaced cursor on LB/LR paging on specific conditions
2023-03-02 22:07:26 +00:00
pjft
0a054f0ba1
Merge pull request #803 from Gemba/fix_short_desc_wordwrap
Make short descriptions wrap to next line instead of adding ellipsis.
2023-03-01 15:30:47 +00:00
Gemba
1c80f0ca5a Squashed commit of the following:
commit 47fc0c9edb95b08fccde01fd1778e3789f6e617a
Merge: 7f3bc53 5a3b907
Author: Gemba <uid0@sdf-eu.org>
Date:   Wed Mar 1 07:36:05 2023 +0100

    Merge branch 'master' of https://github.com/Gemba/EmulationStation into fix_short_desc_wordwrap

commit 5a3b9074e1
Merge: 34b2545 0057745
Author: pjft <pjft@users.noreply.github.com>
Date:   Mon Feb 27 20:01:05 2023 +0000

    Merge pull request #801 from Gemba/fix_ui_transition_style_slide_gamelistview_rollover

    UI Slide Mode: Changes to allow rollover left/right at gamelist-view edges

commit 00577453d2
Author: Gemba <uid0@sdf-eu.org>
Date:   Mon Feb 27 20:46:58 2023 +0100

    Squashed commit of the following:

    commit 895e176c513ca1556c60c5c29c85bd43da3675e0
    Author: Gemba <uid0@sdf-eu.org>
    Date:   Fri Aug 5 13:37:44 2022 +0200

        Changes to allow rollover at gamelist edges instead of rolling back in 'slide' mode.

        Needs 'Transition Style' set to 'slide'.
        Needs 'Quick Navigation' in Gamelist view enabled.

commit 7f3bc5345577f6d0d6625bec6a40fa2468e0395d
Merge: be99449 b88f5ac
Author: Gemba <uid0@sdf-eu.org>
Date:   Sat Feb 18 16:11:23 2023 +0100

    Merge branch 'fix_short_desc_wordwrap' of https://github.com/Gemba/EmulationStation into fix_short_desc_wordwrap

commit b88f5acbc03955aa251cb87445bce1437a4abf06
Merge: 1eb0147 34b2545
Author: Gemba <Gemba@users.noreply.github.com>
Date:   Sat Feb 18 12:58:46 2023 +0100

    Merge branch 'RetroPie:master' into fix_short_desc_wordwrap

commit 1eb0147e24573e761f5a65ce7f46e2b055cb124c
Merge: 07002d6 0c4b42d
Author: Gemba <Gemba@users.noreply.github.com>
Date:   Sun Sep 25 14:31:36 2022 +0200

    Merge branch 'RetroPie:master' into fix_short_desc_wordwrap

commit 07002d61d5a1722588c2b34bee6783c01a7a9c18
Author: Gemba <uid0@sdf-eu.org>
Date:   Sat Aug 13 18:37:11 2022 +0200

    Make short descriptions wrap to next line instead of adding ellipsis.

    The ellipsis effect instead of wrapping to a new line had two causes:
    1. A short description ends slightly after the x-bounding value of the textcomponent (can happen in any theme).
    2. Themes with a linespacing with less than 1.2 (120% of highest character) get a wrong flag set for detecting if it is a multi line text. Carbon uses 1.5 linespacing by default thus this case never to carbon.

    Fixes:
    1. An additional clause in Font.cpp::wrapText() was added to fix this effect and to force wrapping instead of adding ellipsis.
    2. The evaluation of the variable isMultiline in TextComponent::onTextChanged() takes the actual linespacing for calculation instead of the "magic float" of 1.2.

    cf. https://retropie.org.uk/forum/topic/32893/very-short-descriptions-don-t-wrap

commit be9944921e47ebfa57acae8200d495f356774c2b
Author: Gemba <uid0@sdf-eu.org>
Date:   Sat Aug 13 18:37:11 2022 +0200

    Make short descriptions wrap to next line instead of adding ellipsis.

    This effect was agnostic to the theme in use. The ellipsis effect was most prevalent on short descriptions, but may also occour whenever the last line of a description shortly ends after the maximum width of the bounding box, overrunning the bounding box by upto two non whitespace characters.

    An additional clause in Font.cpp::wrapText() was added to fix this effect and to force wrapping instead of adding ellipsis.

    cf. https://retropie.org.uk/forum/topic/32893/very-short-descriptions-don-t-wrap
2023-03-01 07:46:02 +01:00
Gemba
797d2929aa Merge branch 'master' of https://github.com/Gemba/EmulationStation 2023-03-01 07:41:08 +01:00
Gemba
966b513c9c Squashed commit of the following:
commit 47fc0c9edb95b08fccde01fd1778e3789f6e617a
Merge: 7f3bc53 5a3b907
Author: Gemba <uid0@sdf-eu.org>
Date:   Wed Mar 1 07:36:05 2023 +0100

    Merge branch 'master' of https://github.com/Gemba/EmulationStation into fix_short_desc_wordwrap

commit 5a3b9074e1
Merge: 34b2545 0057745
Author: pjft <pjft@users.noreply.github.com>
Date:   Mon Feb 27 20:01:05 2023 +0000

    Merge pull request #801 from Gemba/fix_ui_transition_style_slide_gamelistview_rollover

    UI Slide Mode: Changes to allow rollover left/right at gamelist-view edges

commit 00577453d2
Author: Gemba <uid0@sdf-eu.org>
Date:   Mon Feb 27 20:46:58 2023 +0100

    Squashed commit of the following:

    commit 895e176c513ca1556c60c5c29c85bd43da3675e0
    Author: Gemba <uid0@sdf-eu.org>
    Date:   Fri Aug 5 13:37:44 2022 +0200

        Changes to allow rollover at gamelist edges instead of rolling back in 'slide' mode.

        Needs 'Transition Style' set to 'slide'.
        Needs 'Quick Navigation' in Gamelist view enabled.

commit 7f3bc5345577f6d0d6625bec6a40fa2468e0395d
Merge: be99449 b88f5ac
Author: Gemba <uid0@sdf-eu.org>
Date:   Sat Feb 18 16:11:23 2023 +0100

    Merge branch 'fix_short_desc_wordwrap' of https://github.com/Gemba/EmulationStation into fix_short_desc_wordwrap

commit b88f5acbc03955aa251cb87445bce1437a4abf06
Merge: 1eb0147 34b2545
Author: Gemba <Gemba@users.noreply.github.com>
Date:   Sat Feb 18 12:58:46 2023 +0100

    Merge branch 'RetroPie:master' into fix_short_desc_wordwrap

commit 1eb0147e24573e761f5a65ce7f46e2b055cb124c
Merge: 07002d6 0c4b42d
Author: Gemba <Gemba@users.noreply.github.com>
Date:   Sun Sep 25 14:31:36 2022 +0200

    Merge branch 'RetroPie:master' into fix_short_desc_wordwrap

commit 07002d61d5a1722588c2b34bee6783c01a7a9c18
Author: Gemba <uid0@sdf-eu.org>
Date:   Sat Aug 13 18:37:11 2022 +0200

    Make short descriptions wrap to next line instead of adding ellipsis.

    The ellipsis effect instead of wrapping to a new line had two causes:
    1. A short description ends slightly after the x-bounding value of the textcomponent (can happen in any theme).
    2. Themes with a linespacing with less than 1.2 (120% of highest character) get a wrong flag set for detecting if it is a multi line text. Carbon uses 1.5 linespacing by default thus this case never to carbon.

    Fixes:
    1. An additional clause in Font.cpp::wrapText() was added to fix this effect and to force wrapping instead of adding ellipsis.
    2. The evaluation of the variable isMultiline in TextComponent::onTextChanged() takes the actual linespacing for calculation instead of the "magic float" of 1.2.

    cf. https://retropie.org.uk/forum/topic/32893/very-short-descriptions-don-t-wrap

commit be9944921e47ebfa57acae8200d495f356774c2b
Author: Gemba <uid0@sdf-eu.org>
Date:   Sat Aug 13 18:37:11 2022 +0200

    Make short descriptions wrap to next line instead of adding ellipsis.

    This effect was agnostic to the theme in use. The ellipsis effect was most prevalent on short descriptions, but may also occour whenever the last line of a description shortly ends after the maximum width of the bounding box, overrunning the bounding box by upto two non whitespace characters.

    An additional clause in Font.cpp::wrapText() was added to fix this effect and to force wrapping instead of adding ellipsis.

    cf. https://retropie.org.uk/forum/topic/32893/very-short-descriptions-don-t-wrap
2023-03-01 07:37:32 +01:00
pjft
5a3b9074e1
Merge pull request #801 from Gemba/fix_ui_transition_style_slide_gamelistview_rollover
UI Slide Mode: Changes to allow rollover left/right at gamelist-view edges
2023-02-27 20:01:05 +00:00
Gemba
00577453d2 Squashed commit of the following:
commit 895e176c513ca1556c60c5c29c85bd43da3675e0
Author: Gemba <uid0@sdf-eu.org>
Date:   Fri Aug 5 13:37:44 2022 +0200

    Changes to allow rollover at gamelist edges instead of rolling back in 'slide' mode.

    Needs 'Transition Style' set to 'slide'.
    Needs 'Quick Navigation' in Gamelist view enabled.
2023-02-27 20:46:58 +01:00
Gemba
33fdbb2326 Fixes several cursor misplacements when full page scroll with LB/LR on.
Including fix for cursor highlighting in visible list area when game is launched from screensaver.

Squashed into one commit.
2023-02-18 14:00:48 +01:00
cmitu
9473f19c70 input: export additional info for joysticks inputs
Added some extra info to help RetroPie's auto-configuration scripts by exporting the joystick Vendor and Product IDs, which should help with RetroArch's joypad profile generation.

 Since 2.0.14, SDL's joystick name (`SDL_CreateJoystickName`) is a normalized version of the name reported by the OS (culled consecutive spaces, trimming trailing spaces, renaming known joystick names like Xbox/PS). This breaks the input auto-configuration scripts in RetroPie, which generate a config with new name, while the emulators/ports expect to find the OS reported name (e.g. RetroArch - see #3398 [1] for an example).

 This issue is affecting especially PC users, which are not using RetroPie's (old) SDL version and who's RetroArch configuration is incomplete.
Using the Vendor/Product ID would help these situations and support the new SDL versions when added to RetroPie.

[1] https://github.com/RetroPie/RetroPie-Setup/issues/3398
2022-12-22 05:04:03 +00:00
89 changed files with 16025 additions and 5929 deletions

View file

@ -1,6 +1,7 @@
name: C/C++ CI
on: [push]
on:
workflow_dispatch:
jobs:
build:

141
.github/workflows/win32.yml vendored Normal file
View file

@ -0,0 +1,141 @@
name: Build ES for Win32
on:
workflow_dispatch:
jobs:
build:
runs-on:
# https://github.com/actions/runner-images/blob/main/images/win/Windows2022-Readme.md
windows-2022
env:
# Build parameters for CMake
BUILD_TYPE: Release
Platform: Win32
defaults:
run:
shell: cmd
steps:
# Create directories for build (used by CMake) and nuget
- name: Set up directories
working-directory: ${{runner.workspace}}
run: mkdir build nuget
# Check-out repository under $GITHUB_WORKSPACE
# https://github.com/actions/checkout
- name: Check-out repository
uses: actions/checkout@v3
with:
submodules: true
# Discover location of MSBuild tool and to PATH environment variables
# https://github.com/microsoft/setup-msbuild
- name: Locate MSBuild
uses: microsoft/setup-msbuild@v1.3.1
# Use NuGet to download the latest libVLC.
- name: Download libVLC
working-directory: ${{runner.workspace}}/nuget
run: nuget install -ExcludeVersion VideoLAN.LibVLC.Windows
# Use vcpkg to download and build the latest cURL
- name: Build cURL static library
run: vcpkg install curl:x86-windows-static-md
# Use vcpkg to download and build the latest FreeImage
- name: Build FreeImage static library
run: vcpkg install freeimage:x86-windows-static-md
# Use vcpkg to download and build the latest FreeType2
- name: Build FreeType2 static library
run: vcpkg install freetype:x86-windows-static-md
# Use vcpkg to download and build the latest SDL2
- name: Build SDL2 static library
run: vcpkg install sdl2:x86-windows-static-md
# Use vcpkg to download and build the latest RapidJSON
- name: Build RapidJSON static library
run: vcpkg install rapidjson:x86-windows-static-md
# Setup environment variables for subsequent steps
# Note: Forward slashes are used for CMake compatibility
- name: Set up environment
run: |
set VCPKG=%VCPKG_INSTALLATION_ROOT%/installed/x86-windows-static-md
set "VCPKG=%VCPKG:\=/%"
set NUGET=${{runner.workspace}}/nuget
set "NUGET=%NUGET:\=/%"
set VLC_HOME=%NUGET%/VideoLAN.LibVLC.Windows/build/x86
echo VCPKG=%VCPKG%>> %GITHUB_ENV%
echo NUGET=%NUGET%>> %GITHUB_ENV%
echo FREETYPE_DIR=%VCPKG%>> %GITHUB_ENV%
echo FREEIMAGE_HOME=%VCPKG%>> %GITHUB_ENV%
echo VLC_HOME=%VLC_HOME%>> %GITHUB_ENV%
echo RAPIDJSON_INCLUDE_DIRS=%VCPKG%/include>> %GITHUB_ENV%
echo CURL_INCLUDE_DIR=%VCPKG%/include>> %GITHUB_ENV%
echo SDL2_INCLUDE_DIR=%VCPKG%/include/SDL2>> %GITHUB_ENV%
echo VLC_INCLUDE_DIR=%VLC_HOME%/include>> %GITHUB_ENV%
echo CURL_LIBRARY=%VCPKG%/lib/*.lib>> %GITHUB_ENV%
echo SDL2_LIBRARY=%VCPKG%/lib/manual-link/SDL2main.lib>> %GITHUB_ENV%
echo VLC_LIBRARIES=%VLC_HOME%/libvlc*.lib>> %GITHUB_ENV%
echo VLC_VERSION=3.0.11>> %GITHUB_ENV%
# Use CMake to create Visual Studio project in build folder
- name: Create Visual Studio project
working-directory: ${{runner.workspace}}
run: cmake ${{github.workspace}}
-B build
-A %Platform%
-DRAPIDJSON_INCLUDE_DIRS=%RAPIDJSON_INCLUDE_DIRS%
-DCURL_INCLUDE_DIR=%CURL_INCLUDE_DIR%
-DSDL2_INCLUDE_DIR=%SDL2_INCLUDE_DIR%
-DVLC_INCLUDE_DIR=%VLC_INCLUDE_DIR%
-DCURL_LIBRARY=%CURL_LIBRARY%
-DSDL2_LIBRARY=%SDL2_LIBRARY%
-DVLC_LIBRARIES=%VLC_LIBRARIES%
-DVLC_VERSION=%VLC_VERSION%
-DCMAKE_EXE_LINKER_FLAGS=/SAFESEH:NO
# Use CMake to build project
- name: Build EmulationStation
working-directory: ${{runner.workspace}}
run: cmake --build build --config %BUILD_TYPE%
# Copy all other dependencies into Release folder
# Note: Forward slashes are replaced with back slashes for this step
- name: Collect dependencies
working-directory: ${{github.workspace}}/Release
run: |
set "VLC_ROOT=%VLC_HOME:/=\%"
mkdir .emulationstation
xcopy ..\resources .\resources /h /i /c /k /e /r /y
copy %VLC_ROOT%\*.dll .
xcopy %VLC_ROOT%\plugins .\plugins /h /i /c /k /e /r /y
# Create systems configuration file
- name: Create systems configuration file
working-directory: ${{github.workspace}}/Release/.emulationstation
run: |
echo ^<!-- This is the EmulationStation Systems configuration file.> es_systems.cfg
echo All systems must be contained within the ^<systemList^> tag.--^>>> es_systems.cfg
echo:>> es_systems.cfg
echo ^<systemList^>>> es_systems.cfg
echo:>> es_systems.cfg
echo ^</systemList^>>> es_systems.cfg
# Uploads artifacts from workflow
# https://github.com/actions/upload-artifact
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: EmulationStation
path: |
${{github.workspace}}\Release\*.exe
${{github.workspace}}\Release\*.dll
${{github.workspace}}\Release\resources\
${{github.workspace}}\Release\plugins\
${{github.workspace}}\Release\.emulationstation\

View file

@ -61,11 +61,11 @@ mark_as_advanced(RAPIDJSON_INCLUDE_DIRS)
# handle the QUIETLY and REQUIRED arguments and set RAPIDJSON_FOUND to TRUE if
# all listed variables are TRUE
include("FindPackageHandleStandardArgs")
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Rapidjson
FIND_PACKAGE_HANDLE_STANDARD_ARGS(RapidJSON
REQUIRED_VARS RAPIDJSON_INCLUDE_DIRS
FOUND_VAR Rapidjson_FOUND
FOUND_VAR RapidJSON_FOUND
)
if(Rapidjson_FOUND)
set(RAPIDJSON_FOUND ${Rapidjson_FOUND})
if(RapidJSON_FOUND)
set(RAPIDJSON_FOUND ${RapidJSON_FOUND})
endif()

View file

@ -23,14 +23,14 @@ endif(VLC_INCLUDE_DIR AND VLC_LIBRARIES)
# in the FIND_PATH() and FIND_LIBRARY() calls
if(NOT WIN32)
find_package(PkgConfig)
pkg_check_modules(VLC libvlc>=1.0.0)
pkg_check_modules(VLC libvlc>=3.0.0)
set(VLC_DEFINITIONS ${VLC_CFLAGS})
set(VLC_LIBRARIES ${VLC_LDFLAGS})
endif(NOT WIN32)
# TODO add argument support to pass version on find_package
include(MacroEnsureVersion)
macro_ensure_version(1.0.0 ${VLC_VERSION} VLC_VERSION_OK)
macro_ensure_version(3.0.0 ${VLC_VERSION} VLC_VERSION_OK)
if(VLC_VERSION_OK)
set(VLC_FOUND TRUE)
message(STATUS "VLC library found")

View file

@ -70,7 +70,9 @@ endmacro(clear_if_changed)
# Try to get some hints from pkg-config, if available
macro(use_pkgconfig PREFIX PKGNAME)
find_package(PkgConfig)
if(NOT WIN32)
find_package(PkgConfig)
endif(NOT WIN32)
if (PKG_CONFIG_FOUND)
pkg_check_modules(${PREFIX} ${PKGNAME})
endif ()
@ -96,7 +98,7 @@ macro(get_debug_names PREFIX)
endforeach(i)
endmacro(get_debug_names)
# Add the parent dir from DIR to VAR
# Add the parent dir from DIR to VAR
macro(add_parent_dir VAR DIR)
get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE)
set(${VAR} ${${VAR}} ${${DIR}_TEMP})

View file

@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 2.8)
# Note: Visual Studio 2022 generator requires CMake 3.21 or greater.
option(GLES "Set to ON if targeting Embedded OpenGL" ${GLES})
option(GL "Set to ON if targeting Desktop OpenGL" ${GL})
@ -137,8 +138,16 @@ if(MSVC)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
add_definitions(-DNOMINMAX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") #multi-processor compilation
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") #multi-processor compilation
# multi-processor compilation
# disable warning c4018 - signed/unsigned mismatch
# disable warning c4244 - conversion, possible loss of data
# disable warning c4996 - use of deprecated function/member/variable/typedef
# Use extended ASCII character set
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4018 /wd4244 /wd4996 /source-charset:437")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4018 /wd4244 /wd4996 /source-charset:437")
set(CMAKE_EXE_LINKER_FLAGS "/SAFESEH:NO")
# Set the start-up project to in VS to 'emulationstation'
set(VS_STARTUP_PROJECT "emulationstation")
@ -238,7 +247,12 @@ endif()
if(MSVC)
LIST(APPEND COMMON_LIBRARIES
Crypt32
Imm32
Setupapi
Version
winmm
Wldap32
)
endif()

122
README.md
View file

@ -7,34 +7,37 @@ EmulationStation is a cross-platform graphical front-end for emulators with cont
Building
========
**Building on Linux**
Building on Linux
-----------------
EmulationStation uses some C++11 code, which means you'll need to use at least g++-4.7 on Linux, or VS2010 on Windows, to compile.
EmulationStation has a few dependencies. For building, you'll need CMake, SDL2, FreeImage, FreeType, cURL and RapidJSON. You also should probably install the `fonts-droid` package which contains fallback fonts for Chinese/Japanese/Korean characters, but ES will still work fine without it (this package is only used at run-time).
EmulationStation has a few dependencies. For building, you'll need CMake, SDL2, FreeImage, FreeType, LibVLC (ver. 3 or later), cURL and RapidJSON. You also should probably install the `fonts-droid` package which contains fallback fonts for Chinese/Japanese/Korean characters, but ES will still work fine without it (this package is only used at run-time).
**On Debian/Ubuntu:**
### On Debian/Ubuntu:
All of this be easily installed with `apt-get`:
```bash
sudo apt-get install libsdl2-dev libfreeimage-dev libfreetype6-dev libcurl4-openssl-dev rapidjson-dev \
libasound2-dev libgles2-mesa-dev build-essential cmake fonts-droid-fallback libvlc-dev \
libvlccore-dev vlc-bin
```
**On Fedora:**
### On Fedora:
All of this be easily installed with `dnf` (with rpmfusion activated) :
```bash
sudo dnf install SDL2-devel freeimage-devel freetype-devel curl-devel \
alsa-lib-devel mesa-libGL-devel cmake \
vlc-devel rapidjson-devel
vlc-devel rapidjson-devel
```
Optionaly, `pugixml` can be installed and used (Debian package: `libpugixml-dev`, Fedora/SuSE package: `pugixml-devel`), but EmulationStation can use its own included copy if not found.
**Note**: this repository uses a git submodule - to checkout the source and all submodules, use
```bash
git clone --recursive https://github.com/RetroPie/EmulationStation.git
```
or
or
```bash
git clone https://github.com/RetroPie/EmulationStation.git
@ -54,7 +57,7 @@ NOTE: to generate a `Debug` build on Unix/Linux, run the Makefile generation ste
cmake -DCMAKE_BUILD_TYPE=Debug .
```
**On the Raspberry Pi**
### On the Raspberry Pi:
* Choosing a GLES implementation.
@ -71,23 +74,110 @@ cmake -DCMAKE_BUILD_TYPE=Debug .
If your system doesn't have a working GLESv2 implementation, the GLESv1 legacy renderer can be compiled in by adding `-DUSE_GLES1=On` to the build options.
**Building on Windows**
Building on Windows
-------------------
[FreeImage](http://downloads.sourceforge.net/freeimage/FreeImage3154Win32.zip)
* Install [Visual Studio 2022](https://visualstudio.microsoft.com/vs/community/). At a minimum, install the "Desktop development with C++" workload with the default list of optional items.
[FreeType2](http://download.savannah.gnu.org/releases/freetype/freetype-2.4.9.tar.bz2) (you'll need to compile)
* Install the latest version of [CMake](https://cmake.org/download/) (e.g., [cmake-3.27.1-windows-x86_64.msi](https://github.com/Kitware/CMake/releases/download/v3.27.1/cmake-3.27.1-windows-x86_64.msi)). CMake is used for generating the Visual Studio project.
[SDL2](http://www.libsdl.org/release/SDL2-devel-2.0.8-VC.zip)
* Use git to clone [vcpkg](https://vcpkg.io/en/), then run the bootstrap script as shown below to build vcpkg . This is a C/C++ dependency manager from Microsoft.
[cURL](http://curl.haxx.se/download.html) (you'll need to compile or get the pre-compiled DLL version)
```batchfile
C:\src>git clone https://github.com/Microsoft/vcpkg.git
C:\src>.\vcpkg\bootstrap-vcpkg.bat
```
[RapisJSON](https://github.com/tencent/rapidjson) (you'll need the `include/rapidsjon` added to the include path)
* Download the latest [nuget.exe](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) to a folder that is in your Windows PATH. NuGet is a .NET package manager.
(Remember to copy necessary .DLLs into the same folder as the executable: probably FreeImage.dll, freetype6.dll, SDL2.dll, libcurl.dll, and zlib1.dll. Exact list depends on if you built your libraries in "static" mode or not.)
* Use NuGet to download the latest [libVLC](https://www.videolan.org/vlc/libvlc.html). This library is used to play video snaps.
[CMake](http://www.cmake.org/cmake/resources/software.html) (this is used for generating the Visual Studio project)
```batchfile
C:\src\EmulationStation>mkdir nuget
C:\src\EmulationStation>cd nuget
C:\src\EmulationStation\nuget>nuget install -ExcludeVersion VideoLAN.LibVLC.Windows
```
(If you don't know how to use CMake, here are some hints: run cmake-gui and point it at your EmulationStation folder. Point the "build" directory somewhere - I use EmulationStation/build. Click configure, choose "Visual Studio [year] Project", fill in red fields as they appear and keep clicking Configure (you may need to check "Advanced"), then click Generate.)
* Use vcpkg to download the latest pre-compiled [cURL](http://curl.haxx.se/download.html). This is a library for transferring data with URLs.
```batchfile
c:\src>.\vcpkg\vcpkg install curl:x86-windows-static-md
```
* Use vcpkg to download the latest [FreeImage](https://freeimage.sourceforge.io/index.html). This library supports popular graphics image formats.
```batchfile
c:\src\>.\vcpkg\vcpkg install freeimage:x86-windows-static-md
```
* Use vcpkg to download the latest pre-compiled [FreeType2](https://freetype.org/). This library is used to render fonts.
```batchfile
c:\src>.\vcpkg\vcpkg install freetype:x86-windows-static-md
```
* Use vcpkg to download the latest [SDL2](http://www.libsdl.org/). Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.
```batchfile
c:\src>.\vcpkg\vcpkg install sdl2:x86-windows-static-md
```
* Use vcpkg to download the latest [RapidJSON](http://rapidjson.org/). This library provides a fast JSON parser/generator for C++ with both SAX/DOM style API.
```batchfile
c:\src>.\vcpkg\vcpkg install rapidjson:x86-windows-static-md
```
* Using the example shown below, configure environment variables to point to the libraries that were installed in the above steps. Please note that the below example intentionally uses forward slashes for compatibility with CMake.
```batchfile
C:\src\EmulationStation>set VCPKG=C:/src/vcpkg/installed/x86-windows-static-md
C:\src\EmulationStation>set NUGET=C:/src/EmulationStation/nuget
C:\src\EmulationStation>set FREETYPE_DIR=%VCPKG%
C:\src\EmulationStation>set FREEIMAGE_HOME=%VCPKG%
C:\src\EmulationStation>set VLC_HOME=%NUGET%/VideoLAN.LibVLC.Windows/build/x86
C:\src\EmulationStation>set RAPIDJSON_INCLUDE_DIRS=%VCPKG%/include
C:\src\EmulationStation>set CURL_INCLUDE_DIR=%VCPKG%/include
C:\src\EmulationStation>set SDL2_INCLUDE_DIR=%VCPKG%/include/SDL2
C:\src\EmulationStation>set VLC_INCLUDE_DIR=%VLC_HOME%/include
C:\src\EmulationStation>set CURL_LIBRARY=%VCPKG%/lib/*.lib
C:\src\EmulationStation>set SDL2_LIBRARY=%VCPKG%/lib/manual-link/SDL2main.lib
C:\src\EmulationStation>set VLC_LIBRARIES=%VLC_HOME%/libvlc*.lib
C:\src\EmulationStation>set VLC_VERSION=3.0.11
```
* Use CMake to generate the Visual Studio project.
```batchfile
C:\src\EmulationStation>mkdir build
C:\src\EmulationStation>cmake . -B build -A Win32 ^
-DRAPIDJSON_INCLUDE_DIRS=%RAPIDJSON_INCLUDE_DIRS% ^
-DCURL_INCLUDE_DIR=%CURL_INCLUDE_DIR% ^
-DSDL2_INCLUDE_DIR=%SDL2_INCLUDE_DIR% ^
-DVLC_INCLUDE_DIR=%VLC_INCLUDE_DIR% ^
-DCURL_LIBRARY=%CURL_LIBRARY% ^
-DSDL2_LIBRARY=%SDL2_LIBRARY% ^
-DVLC_LIBRARIES=%VLC_LIBRARIES% ^
-DVLC_VERSION=%VLC_VERSION% ^
-DCMAKE_EXE_LINKER_FLAGS=/SAFESEH:NO
```
* Use CMake to build the Visual Studio project.
```batchfile
C:\src\EmulationStation>cmake --build build --config Release
```
* Using the example shown below, copy the newly built binaries and other needed files to destination folder.
```batchfile
C:\src\EmulationStation>mkdir -p C:\apps\EmulationStation\.emulationstation
C:\src\EmulationStation>xcopy C:\src\EmulationStation\resources C:\apps\EmulationStation\resources /h /i /c /k /e /r /y
C:\src\EmulationStation>copy C:\src\EmulationStation\Release\*.exe C:\apps\EmulationStation /Y
C:\src\EmulationStation>copy C:\src\EmulationStation\nuget\VideoLAN.LibVLC.Windows\build\x86\*.dll C:\apps\EmulationStation /Y
C:\src\EmulationStation>xcopy C:\src\EmulationStation\nuget\VideoLAN.LibVLC.Windows\build\x86\plugins C:\apps\EmulationStation\plugins /h /i /c /k /e /r /y
```
Configuring

View file

@ -35,6 +35,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiRandomCollectionOptions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h
# Scrapers
@ -93,6 +94,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiRandomCollectionOptions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp
# Scrapers

View file

@ -1,5 +1,6 @@
#include "CollectionSystemManager.h"
#include "components/TextListComponent.h"
#include "guis/GuiInfoPopup.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
@ -12,12 +13,9 @@
#include "Settings.h"
#include "SystemData.h"
#include "ThemeData.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
#include <fstream>
std::string myCollectionsName = "collections";
#define LAST_PLAYED_MAX 50
#include <cstring>
/* Handling the getting, initialization, deinitialization, saving and deletion of
* a CollectionSystemManager Instance */
@ -26,11 +24,12 @@ CollectionSystemManager* CollectionSystemManager::sInstance = NULL;
CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(window)
{
CollectionSystemDecl systemDecls[] = {
//type name long name //default sort // theme folder // isCustom
{ AUTO_ALL_GAMES, "all", "all games", "filename, ascending", "auto-allgames", false },
{ AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false },
{ AUTO_FAVORITES, "favorites", "favorites", "filename, ascending", "auto-favorites", false },
{ CUSTOM_COLLECTION, myCollectionsName, "collections", "filename, ascending", "custom-collections", true }
//type name long name (display) default sort (key, order) theme folder isCustom
{ AUTO_ALL_GAMES, "all", "all games", "name, ascending", "auto-allgames", false },
{ AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false },
{ AUTO_FAVORITES, "favorites", "favorites", "name, ascending", "auto-favorites", false },
{ AUTO_RANDOM, RANDOM_COLL_ID, "random", "name, ascending", "auto-random", false },
{ CUSTOM_COLLECTION, CUSTOM_COLL_ID, "collections", "name, ascending", "custom-collections", true }
};
// create a map
@ -59,6 +58,7 @@ CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(windo
mEditingCollection = "Favorites";
mEditingCollectionSystemData = NULL;
mCustomCollectionsBundle = NULL;
mRandomCollection = NULL;
}
CollectionSystemManager::~CollectionSystemManager()
@ -98,29 +98,37 @@ void CollectionSystemManager::deinit()
}
}
void CollectionSystemManager::saveCustomCollection(SystemData* sys)
bool CollectionSystemManager::saveCustomCollection(SystemData* sys)
{
std::string name = sys->getName();
std::unordered_map<std::string, FileData*> games = sys->getRootFolder()->getChildrenByFilename();
bool found = mCustomCollectionSystemsData.find(name) != mCustomCollectionSystemsData.cend();
if (found) {
CollectionSystemData sysData = mCustomCollectionSystemsData.at(name);
if (sysData.needsSave)
{
std::ofstream configFile;
configFile.open(getCustomCollectionConfigPath(name));
for(std::unordered_map<std::string, FileData*>::const_iterator iter = games.cbegin(); iter != games.cend(); ++iter)
{
std::string path = iter->first;
configFile << path << std::endl;
}
configFile.close();
}
}
else
if (!found)
{
LOG(LogError) << "Couldn't find collection to save! " << name;
return false;
}
CollectionSystemData sysData = mCustomCollectionSystemsData.at(name);
if (sysData.needsSave)
{
std::string absCollectionFn = getCustomCollectionConfigPath(name);
std::ofstream configFile;
configFile.open(absCollectionFn);
if (!configFile.good())
{
auto const errNo = errno;
LOG(LogError) << "Failed to create file, collection not created: " << absCollectionFn << ": " << std::strerror(errNo) << " (" << errNo << ")";
return false;
}
for(std::unordered_map<std::string, FileData*>::const_iterator iter = games.cbegin(); iter != games.cend(); ++iter)
{
std::string path = iter->first;
configFile << path << std::endl;
}
configFile.close();
}
return true;
}
/* Methods to load all Collections into memory, and handle enabling the active ones */
@ -128,8 +136,8 @@ void CollectionSystemManager::saveCustomCollection(SystemData* sys)
void CollectionSystemManager::loadCollectionSystems(bool async)
{
initAutoCollectionSystems();
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[myCollectionsName];
mCustomCollectionsBundle = createNewCollectionEntry(decl.name, decl, false);
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[CUSTOM_COLL_ID];
mCustomCollectionsBundle = createNewCollectionEntry(decl.name, decl, CollectionFlags::NONE);
// we will also load custom systems here
initCustomCollectionSystems();
if(Settings::getInstance()->getString("CollectionSystemsAuto") != "" || Settings::getInstance()->getString("CollectionSystemsCustom") != "")
@ -171,7 +179,7 @@ void CollectionSystemManager::updateSystemsList()
// remove all Collection Systems
removeCollectionsFromDisplayedSystems();
// add custom enabled ones
addEnabledCollectionsToDisplayedSystems(&mCustomCollectionSystemsData);
addEnabledCollectionsToDisplayedSystems(&mCustomCollectionSystemsData, false);
if(Settings::getInstance()->getBool("SortAllSystems"))
{
@ -197,20 +205,20 @@ void CollectionSystemManager::updateSystemsList()
if(mCustomCollectionsBundle->getRootFolder()->getChildren().size() > 0)
{
mCustomCollectionsBundle->getRootFolder()->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[myCollectionsName].defaultSort));
mCustomCollectionsBundle->getRootFolder()->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[CUSTOM_COLL_ID].defaultSort));
SystemData::sSystemVector.push_back(mCustomCollectionsBundle);
}
// add auto enabled ones
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData);
// add auto enabled ones except random
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData, false);
// finally, add random
addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData, true);
// create views for collections, before reload
for(auto sysIt = SystemData::sSystemVector.cbegin(); sysIt != SystemData::sSystemVector.cend(); sysIt++)
{
if ((*sysIt)->isCollection())
{
ViewController::get()->getGameListView((*sysIt));
}
}
// if we were editing a custom collection, and it's no longer enabled, exit edit mode
@ -284,20 +292,26 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS
rootFolder->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[name].defaultSort));
if (name == "recent")
{
trimCollectionCount(rootFolder, LAST_PLAYED_MAX);
trimCollectionCount(rootFolder, LAST_PLAYED_MAX, false);
ViewController::get()->onFileChanged(rootFolder, FILE_METADATA_CHANGED);
// Force re-calculation of cursor position
ViewController::get()->getGameListView(curSys)->setViewportTop(TextListComponent<FileData>::REFRESH_LIST_CURSOR_POS);
}
else
ViewController::get()->onFileChanged(rootFolder, FILE_SORTED);
}
}
void CollectionSystemManager::trimCollectionCount(FileData* rootFolder, int limit)
void CollectionSystemManager::trimCollectionCount(FileData* rootFolder, int limit, bool shuffle)
{
SystemData* curSys = rootFolder->getSystem();
while ((int)rootFolder->getChildrenListToDisplay().size() > limit)
{
CollectionFileData* gameToRemove = (CollectionFileData*)rootFolder->getChildrenListToDisplay().back();
std::vector<FileData*> games = rootFolder->getFilesRecursive(GAME, true);
if (shuffle)
std::shuffle(games.begin(), games.end(), SystemData::sURNG);
CollectionFileData* gameToRemove = (CollectionFileData*)games.back();
ViewController::get()->getGameListView(curSys).get()->remove(gameToRemove, false, false);
}
ViewController::get()->onFileChanged(rootFolder, FILE_REMOVED);
@ -370,6 +384,7 @@ bool CollectionSystemManager::isThemeCustomCollectionCompatible(std::vector<std:
std::string CollectionSystemManager::getValidNewCollectionName(std::string inName, int index)
{
std::string name = inName;
const std::string infix = " (" + std::to_string(index) + ")";
if(index == 0)
{
@ -383,7 +398,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
}
else
{
name += " (" + std::to_string(index) + ")";
name += infix;
}
if(name == "")
@ -393,7 +408,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
if(name != inName)
{
LOG(LogInfo) << "Had to change name, from: " << inName << " to: " << name;
LOG(LogInfo) << "Name collision, had to change name from: " << inName << " to: " << name;
}
// get used systems in es_systems.cfg
@ -413,7 +428,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
if (*sysIt == name)
{
if(index > 0) {
name = name.substr(0, name.size()-4);
name = name.substr(0, name.size() - infix.size());
}
return getValidNewCollectionName(name, index+1);
}
@ -424,7 +439,7 @@ std::string CollectionSystemManager::getValidNewCollectionName(std::string inNam
return name;
}
void CollectionSystemManager::setEditMode(std::string collectionName)
void CollectionSystemManager::setEditMode(std::string collectionName, bool quiet)
{
if (mCustomCollectionSystemsData.find(collectionName) == mCustomCollectionSystemsData.cend())
{
@ -442,22 +457,38 @@ void CollectionSystemManager::setEditMode(std::string collectionName)
// if it's bundled, this needs to be the bundle system
mEditingCollectionSystemData = sysData;
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + Utils::String::toUpper(collectionName) + "' Collection. Add/remove games with Y.", 10000);
mWindow->setInfoPopup(s);
if (!quiet) {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + Utils::String::toUpper(collectionName) + "' Collection. Add/remove games with Y.", 8000);
mWindow->setInfoPopup(s);
}
}
void CollectionSystemManager::exitEditMode()
void CollectionSystemManager::exitEditMode(bool quiet)
{
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + mEditingCollection + "' Collection.", 4000);
mWindow->setInfoPopup(s);
mIsEditingCustom = false;
mEditingCollection = "Favorites";
if (!quiet) {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + Utils::String::toUpper(mEditingCollection) + "' Collection.", 4000);
mWindow->setInfoPopup(s);
}
if (mIsEditingCustom) {
mIsEditingCustom = false;
mEditingCollection = "Favorites";
mEditingCollectionSystemData->system->onMetaDataSavePoint();
saveCustomCollection(mEditingCollectionSystemData->system);
}
}
mEditingCollectionSystemData->system->onMetaDataSavePoint();
int CollectionSystemManager::getPressCountInDuration() {
Uint32 now = SDL_GetTicks();
if (now - mFirstPressMs < DOUBLE_PRESS_DETECTION_DURATION) {
return 2;
} else {
mFirstPressMs = now;
return 1;
}
}
// adds or removes a game from a specific collection
bool CollectionSystemManager::toggleGameInCollection(FileData* file, int presscount)
bool CollectionSystemManager::toggleGameInCollection(FileData* file)
{
if (file->getType() == GAME)
{
@ -483,7 +514,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
SystemData* systemViewToUpdate = getSystemToView(sysData);
if (found) {
if (needDoublePress(presscount)) {
if (needDoublePress(getPressCountInDuration())) {
return true;
}
adding = false;
@ -504,7 +535,11 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
CollectionFileData* newGame = new CollectionFileData(file, sysData);
rootFolder->addChild(newGame);
fileIndex->addToIndex(newGame);
ViewController::get()->getGameListView(systemViewToUpdate)->onFileChanged(newGame, FILE_METADATA_CHANGED);
// this is the biggest performance bottleneck for this process.
// this code has been here for 7 years, since this feature was added.
// I might have been playing it safe back then, but it feels unnecessary, especially given following onFileChanged to sort
// Commenting this out for now.
//ViewController::get()->getGameListView(systemViewToUpdate)->onFileChanged(newGame, FILE_METADATA_CHANGED);
rootFolder->sort(getSortTypeFromString(mEditingCollectionSystemData->decl.defaultSort));
ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), FILE_SORTED);
// add to bundle index as well, if needed
@ -513,6 +548,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
systemViewToUpdate->getIndex()->addToIndex(newGame);
}
}
sysData->setShuffledCacheDirty();
updateCollectionFolderMetadata(sysData);
}
else
@ -526,7 +562,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
}
else
{
if (needDoublePress(presscount)) {
if (needDoublePress(getPressCountInDuration())) {
return true;
}
adding = false;
@ -546,6 +582,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file, int pressco
{
s = new GuiInfoPopup(mWindow, "Removed '" + Utils::String::removeParenthesis(name) + "' from '" + Utils::String::toUpper(sysName) + "'", 4000);
}
mWindow->setInfoPopup(s);
return true;
}
@ -571,8 +608,57 @@ SystemData* CollectionSystemManager::getSystemToView(SystemData* sys)
return systemToView;
}
void CollectionSystemManager::recreateCollection(SystemData* sysData)
{
CollectionSystemData* colSysData;
if (mAutoCollectionSystemsData.find(sysData->getName()) != mAutoCollectionSystemsData.end())
{
// it's an auto collection
colSysData = &mAutoCollectionSystemsData[sysData->getName()];
}
else if (mCustomCollectionSystemsData.find(sysData->getName()) != mCustomCollectionSystemsData.end())
{
// it's a custom collection
colSysData = &mCustomCollectionSystemsData[sysData->getName()];
}
else
{
LOG(LogDebug) << "Couldn't find collection to recreate in either custom or auto collections: " << sysData->getName();
return;
}
CollectionSystemDecl sysDecl = colSysData->decl;
FileData* rootFolder = sysData->getRootFolder();
FileFilterIndex* index = sysData->getIndex();
const std::unordered_map<std::string, FileData*>& children = rootFolder->getChildrenByFilename();
sysData->getIndex()->resetIndex();
std::string name = sysData->getName();
SystemData* systemViewToUpdate = getSystemToView(sysData);
// while there are games there, remove them from the view and system
while(rootFolder->getChildrenByFilename().size() > 0)
ViewController::get()->getGameListView(systemViewToUpdate).get()->remove(rootFolder->getChildrenByFilename().begin()->second, false, false);
colSysData->isPopulated = false;
if (sysDecl.isCustom)
populateCustomCollection(colSysData);
else
populateAutoCollection(colSysData);
rootFolder->sort(getSortTypeFromString(colSysData->decl.defaultSort));
ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), FILE_SORTED);
// Workaround to force video to play
FileData* cursor = ViewController::get()->getGameListView(systemViewToUpdate)->getCursor();
ViewController::get()->getGameListView(systemViewToUpdate)->setCursor(cursor, true);
}
/* Handles loading a collection system, creating an empty one, and populating on demand */
// loads Automatic Collection systems (All, Favorites, Last Played)
// loads Automatic Collection systems (All, Favorites, Last Played, Random)
void CollectionSystemManager::initAutoCollectionSystems()
{
for(std::map<std::string, CollectionSystemDecl>::const_iterator it = mCollectionSystemDeclsIndex.cbegin() ; it != mCollectionSystemDeclsIndex.cend() ; it++ )
@ -580,7 +666,9 @@ void CollectionSystemManager::initAutoCollectionSystems()
CollectionSystemDecl sysDecl = it->second;
if (!sysDecl.isCustom)
{
createNewCollectionEntry(sysDecl.name, sysDecl);
SystemData* newCol = createNewCollectionEntry(sysDecl.name, sysDecl, CollectionFlags::HOLD_IN_MAP);
if (sysDecl.type == AUTO_RANDOM)
mRandomCollection = newCol;
}
}
}
@ -678,17 +766,20 @@ SystemData* CollectionSystemManager::getAllGamesCollection()
return allSysData->system;
}
SystemData* CollectionSystemManager::addNewCustomCollection(std::string name)
SystemData* CollectionSystemManager::addNewCustomCollection(std::string name, bool needsSave)
{
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[myCollectionsName];
CollectionSystemDecl decl = mCollectionSystemDeclsIndex[CUSTOM_COLL_ID];
decl.themeFolder = name;
decl.name = name;
decl.longName = name;
return createNewCollectionEntry(name, decl);
CollectionFlags flags = CollectionFlags::HOLD_IN_MAP;
if (needsSave)
flags = flags | CollectionFlags::NEEDS_SAVE;
return createNewCollectionEntry(name, decl, flags);
}
// creates a new, empty Collection system, based on the name and declaration
SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index)
SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, const CollectionFlags flags)
{
SystemData* newSys = new SystemData(name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true);
@ -697,9 +788,9 @@ SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name,
newCollectionData.decl = sysDecl;
newCollectionData.isEnabled = false;
newCollectionData.isPopulated = false;
newCollectionData.needsSave = false;
newCollectionData.needsSave = (flags & CollectionFlags::NEEDS_SAVE) == CollectionFlags::NEEDS_SAVE ? true : false;
if (index)
if ((flags & CollectionFlags::HOLD_IN_MAP) == CollectionFlags::HOLD_IN_MAP)
{
if (!sysDecl.isCustom)
{
@ -714,6 +805,118 @@ SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name,
return newSys;
}
void CollectionSystemManager::addRandomGames(SystemData* newSys, SystemData* sourceSystem, FileData* rootFolder,
FileFilterIndex* index, std::map<std::string, std::map<std::string, int>> mapsForRandomColl, int defaultValue)
{
int gamesForSourceSystem = defaultValue;
for (auto& m : mapsForRandomColl)
{
// m.first unused
std::map<std::string, int> collMap = m.second;
if (collMap.find(sourceSystem->getFullName()) != collMap.end())
{
int maxForSys = collMap[sourceSystem->getFullName()];
// we won't add more than the max and less than 0
gamesForSourceSystem = Math::max(Math::min(RANDOM_SYSTEM_MAX, maxForSys), 0);
break;
}
}
// load exclusion collection
std::unordered_map<std::string,FileData*> exclusionMap;
std::string exclusionCollection = Settings::getInstance()->getString("RandomCollectionExclusionCollection");
auto sysDataIt = mCustomCollectionSystemsData.find(exclusionCollection);
if (!exclusionCollection.empty() && sysDataIt != mCustomCollectionSystemsData.end()) {
if (!sysDataIt->second.isPopulated)
{
populateCustomCollection(&(sysDataIt->second));
}
exclusionMap = mCustomCollectionSystemsData[exclusionCollection].system->getRootFolder()->getChildrenByFilename();
}
// we do this to avoid trying to add more games than there are in the system
gamesForSourceSystem = Math::min(gamesForSourceSystem, sourceSystem->getRootFolder()->getFilesRecursive(GAME).size());
int startCount = rootFolder->getFilesRecursive(GAME).size();
int endCount = startCount + gamesForSourceSystem;
int retryCount = 10;
for (int iterCount = startCount; iterCount < endCount;)
{
FileData* randomGame = sourceSystem->getRandomGame()->getSourceFileData();
CollectionFileData* newGame = NULL;
if(exclusionMap.find(randomGame->getFullPath()) == exclusionMap.end())
{
// Not in the exclusion collection
newGame = new CollectionFileData(randomGame, newSys);
rootFolder->addChild(newGame);
index->addToIndex(newGame);
}
if (rootFolder->getFilesRecursive(GAME).size() > iterCount)
{
// added game, proceed
iterCount++;
retryCount = 10;
}
else
{
// the game already exists in the collection, let's try again
LOG(LogDebug) << "Clash: " << randomGame->getName() << " already exists or in exclusion list. Deleting and trying again";
delete newGame;
retryCount--;
if (retryCount == 0)
{
// we give up. Either we were very unlucky, or all the games in this system are already there.
LOG(LogDebug) << "Giving up retrying: cannot add this game. Deleting and moving on.";
return;
}
}
}
}
void CollectionSystemManager::populateRandomCollectionFromCollections(std::map<std::string, std::map<std::string, int>> mapsForRandomColl)
{
CollectionSystemData* sysData = &mAutoCollectionSystemsData[RANDOM_COLL_ID];
SystemData* newSys = sysData->system;
CollectionSystemDecl sysDecl = sysData->decl;
FileData* rootFolder = newSys->getRootFolder();
FileFilterIndex* index = newSys->getIndex();
// iterate the auto collections map
for(auto &c : mAutoCollectionSystemsData)
{
CollectionSystemData csd = c.second;
// we can't add games from the random collection to the random collection
if (csd.decl.type != AUTO_RANDOM)
{
// collections might not be populated
if (!csd.isPopulated)
populateAutoCollection(&csd);
if (csd.isPopulated)
addRandomGames(newSys, csd.system, rootFolder, index, mapsForRandomColl, DEFAULT_RANDOM_COLLECTIONS_GAMES);
}
}
// iterate the custom collections map
for(auto &c : mCustomCollectionSystemsData)
{
CollectionSystemData csd = c.second;
// collections might not be populated
if (!csd.isPopulated)
populateCustomCollection(&csd);
if (csd.isPopulated)
addRandomGames(newSys, csd.system, rootFolder, index, mapsForRandomColl, DEFAULT_RANDOM_COLLECTIONS_GAMES);
}
}
// populates an Automatic Collection System
void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysData)
{
@ -721,35 +924,80 @@ void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysDa
CollectionSystemDecl sysDecl = sysData->decl;
FileData* rootFolder = newSys->getRootFolder();
FileFilterIndex* index = newSys->getIndex();
std::map<std::string, std::map<std::string, int>> mapsForRandomColl;
if (sysDecl.type == AUTO_RANDOM)
{
// user may have defined a custom collection with the same name as a system name, thus keeping maps in another map
std::map<std::string, int> randomSystems = Settings::getInstance()->getMap("RandomCollectionSystems");
mapsForRandomColl["RandomCollectionSystems"] = randomSystems;
std::map<std::string, int> randomAutoColl = Settings::getInstance()->getMap("RandomCollectionSystemsAuto");
mapsForRandomColl["RandomCollectionSystemsAuto"] = randomAutoColl;
std::map<std::string, int> randomCustColl = Settings::getInstance()->getMap("RandomCollectionSystemsCustom");
mapsForRandomColl["RandomCollectionSystemsCustom"] = randomCustColl;
}
// Only iterate through game systems, not collections yet
for(auto sysIt = SystemData::sSystemVector.cbegin(); sysIt != SystemData::sSystemVector.cend(); sysIt++)
{
// we won't iterate all collections
if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection()) {
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME);
for(auto gameIt = files.cbegin(); gameIt != files.cend(); gameIt++)
if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection())
{
if (sysDecl.type == AUTO_RANDOM)
{
bool include = includeFileInAutoCollections((*gameIt));
switch(sysDecl.type) {
case AUTO_LAST_PLAYED:
include = include && (*gameIt)->metadata.get("playcount") > "0";
break;
case AUTO_FAVORITES:
// we may still want to add files we don't want in auto collections in "favorites"
include = (*gameIt)->metadata.get("favorite") == "true";
break;
}
addRandomGames(newSys, *sysIt, rootFolder, index, mapsForRandomColl, DEFAULT_RANDOM_SYSTEM_GAMES);
}
else
{
std::vector<FileData*> files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME);
if (include) {
CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys);
rootFolder->addChild(newGame);
index->addToIndex(newGame);
for(auto gameIt = files.cbegin(); gameIt != files.cend(); gameIt++)
{
bool include = includeFileInAutoCollections(*gameIt);
switch(sysDecl.type) {
case AUTO_LAST_PLAYED:
include = include && (*gameIt)->metadata.get("playcount") > "0";
break;
case AUTO_FAVORITES:
// we may still want to add files we don't want in auto collections in "favorites"
include = (*gameIt)->metadata.get("favorite") == "true";
break;
case AUTO_ALL_GAMES:
break;
default:
// No-op to prevent compiler warnings
// Getting here means that the file is not part of a pre-defined collection.
include = false;
break;
}
if (include)
{
CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys);
rootFolder->addChild(newGame);
index->addToIndex(newGame);
}
}
}
}
}
rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort));
if (sysDecl.type == AUTO_LAST_PLAYED)
trimCollectionCount(rootFolder, LAST_PLAYED_MAX);
// here we finish populating the Random collection based on other Collections
if (sysDecl.type == AUTO_RANDOM)
populateRandomCollectionFromCollections(mapsForRandomColl);
// sort before optional trimming, if collection is displayed
if (sysData->isEnabled)
rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort));
if (sysData->isEnabled && (sysDecl.type == AUTO_LAST_PLAYED || sysDecl.type == AUTO_RANDOM))
{
int trimValue = LAST_PLAYED_MAX;
if (sysDecl.type == AUTO_RANDOM)
trimValue = Settings::getInstance()->getInt("RandomCollectionMaxGames");
if (trimValue > 0)
trimCollectionCount(rootFolder, trimValue, sysDecl.type == AUTO_RANDOM);
}
sysData->isPopulated = true;
}
@ -757,7 +1005,6 @@ void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysDa
void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sysData)
{
SystemData* newSys = sysData->system;
sysData->isPopulated = true;
CollectionSystemDecl sysDecl = sysData->decl;
std::string path = getCustomCollectionConfigPath(newSys->getName());
@ -778,11 +1025,11 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys
std::unordered_map<std::string,FileData*> allFilesMap = getAllGamesCollection()->getRootFolder()->getChildrenByFilename();
// iterate list of files in config file
for(std::string gameKey; getline(input, gameKey); )
{
std::unordered_map<std::string,FileData*>::const_iterator it = allFilesMap.find(gameKey);
if (it != allFilesMap.cend()) {
if (it != allFilesMap.cend())
{
CollectionFileData* newGame = new CollectionFileData(it->second, newSys);
rootFolder->addChild(newGame);
index->addToIndex(newGame);
@ -794,6 +1041,7 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys
}
rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort));
updateCollectionFolderMetadata(newSys);
sysData->isPopulated = true;
}
/* Handle System View removal and insertion of Collections */
@ -829,38 +1077,40 @@ void CollectionSystemManager::removeCollectionsFromDisplayedSystems()
}
}
void CollectionSystemManager::addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData)
// The "random" collection relies on all other collections to have been initialized, so we defer its processing
void CollectionSystemManager::addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData, bool processRandom)
{
// add auto enabled ones
for(std::map<std::string, CollectionSystemData>::iterator it = colSystemData->begin() ; it != colSystemData->end() ; it++ )
{
if(it->second.isEnabled)
if ((!processRandom && it->second.decl.type != AUTO_RANDOM) || (processRandom && it->second.decl.type == AUTO_RANDOM))
{
// check if populated, otherwise populate
if (!it->second.isPopulated)
if(it->second.isEnabled)
{
if(it->second.decl.isCustom)
// check if populated, otherwise populate
if (!it->second.isPopulated)
{
populateCustomCollection(&(it->second));
if(it->second.decl.isCustom)
populateCustomCollection(&(it->second));
else
populateAutoCollection(&(it->second));
}
// check if it has its own view
if(!it->second.decl.isCustom || themeFolderExists(it->first) || !Settings::getInstance()->getBool("UseCustomCollectionsSystem"))
{
// exists theme folder, or we chose not to bundle it under the custom-collections system
// so we need to create a view
SystemData::sSystemVector.push_back(it->second.system);
}
else
{
populateAutoCollection(&(it->second));
FileData* newSysRootFolder = it->second.system->getRootFolder();
mCustomCollectionsBundle->getRootFolder()->addChild(newSysRootFolder);
mCustomCollectionsBundle->getIndex()->importIndex(it->second.system->getIndex());
}
}
// check if it has its own view
if(!it->second.decl.isCustom || themeFolderExists(it->first) || !Settings::getInstance()->getBool("UseCustomCollectionsSystem"))
{
// exists theme folder, or we chose not to bundle it under the custom-collections system
// so we need to create a view
SystemData::sSystemVector.push_back(it->second.system);
}
else
{
FileData* newSysRootFolder = it->second.system->getRootFolder();
mCustomCollectionsBundle->getRootFolder()->addChild(newSysRootFolder);
mCustomCollectionsBundle->getIndex()->importIndex(it->second.system->getIndex());
}
}
}
}
@ -1054,9 +1304,10 @@ bool CollectionSystemManager::includeFileInAutoCollections(FileData* file)
bool CollectionSystemManager::needDoublePress(int presscount) {
if (Settings::getInstance()->getBool("DoublePressRemovesFromFavs") && presscount < 2) {
if (Settings::getInstance()->getBool("DoublePressRemovesFromFavs") && presscount < 2)
{
GuiInfoPopup* toast = new GuiInfoPopup(mWindow, "Press again to remove from '" + Utils::String::toUpper(mEditingCollection)
+ "'", ISimpleGameListView::DOUBLE_PRESS_DETECTION_DURATION, 100, 200);
+ "'", DOUBLE_PRESS_DETECTION_DURATION, 100, 200);
mWindow->setInfoPopup(toast);
return true;
}

View file

@ -3,6 +3,7 @@
#define ES_APP_COLLECTION_SYSTEM_MANAGER_H
#include <map>
#include <SDL_timer.h>
#include <string>
#include <vector>
@ -10,15 +11,43 @@ class FileData;
class SystemData;
class Window;
struct SystemEnvironmentData;
class FileFilterIndex;
static const std::string CUSTOM_COLL_ID = "collections";
static const std::string RANDOM_COLL_ID = "random";
constexpr int LAST_PLAYED_MAX = 50;
constexpr int RANDOM_SYSTEM_MAX = 5;
constexpr int DEFAULT_RANDOM_SYSTEM_GAMES = 1;
constexpr int DEFAULT_RANDOM_COLLECTIONS_GAMES = 0;
enum CollectionSystemType
{
AUTO_ALL_GAMES,
AUTO_LAST_PLAYED,
AUTO_FAVORITES,
AUTO_RANDOM,
CUSTOM_COLLECTION
};
// Flags when loading or creating a collection
enum class CollectionFlags : uint8_t
{
NONE, // create only
HOLD_IN_MAP, // create and keep in mAutoCollectionSystemsData or mCustomCollectionSystemsData
NEEDS_SAVE // force save of newly added collection
};
constexpr CollectionFlags operator|(CollectionFlags a,CollectionFlags b)
{
return static_cast<CollectionFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
constexpr CollectionFlags operator&(CollectionFlags a, CollectionFlags b)
{
return static_cast<CollectionFlags>(static_cast<uint8_t>(a) & static_cast<uint8_t>(b));
}
struct CollectionSystemDecl
{
CollectionSystemType type; // type of system
@ -47,7 +76,7 @@ public:
static CollectionSystemManager* get();
static void init(Window* window);
static void deinit();
void saveCustomCollection(SystemData* sys);
bool saveCustomCollection(SystemData* sys);
void loadCollectionSystems(bool async=false);
void loadEnabledListFromSettings();
@ -56,28 +85,32 @@ public:
void refreshCollectionSystems(FileData* file);
void updateCollectionSystem(FileData* file, CollectionSystemData sysData);
void deleteCollectionFiles(FileData* file);
void recreateCollection(SystemData* sysData);
inline std::map<std::string, CollectionSystemData> getAutoCollectionSystems() { return mAutoCollectionSystemsData; };
inline std::map<std::string, CollectionSystemData> getCustomCollectionSystems() { return mCustomCollectionSystemsData; };
inline SystemData* getCustomCollectionsBundle() { return mCustomCollectionsBundle; };
inline SystemData* getRandomCollection() { return mRandomCollection; };
std::vector<std::string> getUnusedSystemsFromTheme();
SystemData* addNewCustomCollection(std::string name);
SystemData* addNewCustomCollection(std::string name, bool needsSave = false);
bool isThemeGenericCollectionCompatible(bool genericCustomCollections);
bool isThemeCustomCollectionCompatible(std::vector<std::string> stringVector);
std::string getValidNewCollectionName(std::string name, int index = 0);
void setEditMode(std::string collectionName);
void exitEditMode();
void setEditMode(std::string collectionName, bool quiet = false);
void exitEditMode(bool quiet = false);
inline bool isEditing() { return mIsEditingCustom; };
inline std::string getEditingCollection() { return mEditingCollection; };
bool toggleGameInCollection(FileData* file, int presscount);
bool toggleGameInCollection(FileData* file);
SystemData* getSystemToView(SystemData* sys);
void updateCollectionFolderMetadata(SystemData* sys);
SystemData* getAllGamesCollection();
void trimCollectionCount(FileData* rootFolder, int limit, bool shuffle);
private:
static CollectionSystemManager* sInstance;
SystemEnvironmentData* mCollectionEnvData;
@ -88,15 +121,19 @@ private:
bool mIsEditingCustom;
std::string mEditingCollection;
CollectionSystemData* mEditingCollectionSystemData;
Uint32 mFirstPressMs = 0;
void initAutoCollectionSystems();
void initCustomCollectionSystems();
SystemData* createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index = true);
SystemData* createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, const CollectionFlags flags);
void populateAutoCollection(CollectionSystemData* sysData);
void populateCustomCollection(CollectionSystemData* sysData);
void addRandomGames(SystemData* newSys, SystemData* sourceSystem, FileData* rootFolder, FileFilterIndex* index,
std::map<std::string, std::map<std::string, int>> mapsForRandomColl, int defaultValue);
void populateRandomCollectionFromCollections(std::map<std::string, std::map<std::string, int>> mapsForRandomColl);
void removeCollectionsFromDisplayedSystems();
void addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData);
void addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData, bool processRandom);
std::vector<std::string> getSystemsFromConfig();
std::vector<std::string> getSystemsFromTheme();
@ -104,15 +141,17 @@ private:
std::vector<std::string> getCollectionThemeFolders(bool custom);
std::vector<std::string> getUserCollectionThemeFolders();
void trimCollectionCount(FileData* rootFolder, int limit);
bool themeFolderExists(std::string folder);
bool includeFileInAutoCollections(FileData* file);
bool needDoublePress(int presscount);
int getPressCountInDuration();
SystemData* mCustomCollectionsBundle;
SystemData* mRandomCollection;
static const int DOUBLE_PRESS_DETECTION_DURATION = 1500; // millis
};
std::string getCustomCollectionConfigPath(std::string collectionName);

View file

@ -250,21 +250,30 @@ void FileData::removeChild(FileData* file)
void FileData::sort(ComparisonFunction& comparator, bool ascending)
{
std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
if (ascending)
{
if((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending);
std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
{
if((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending);
}
}
else
{
std::stable_sort(mChildren.rbegin(), mChildren.rend(), comparator);
for(auto it = mChildren.rbegin(); it != mChildren.rend(); it++)
{
if((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending);
}
}
if(!ascending)
std::reverse(mChildren.begin(), mChildren.end());
}
void FileData::sort(const SortType& type)
{
sort(*type.comparisonFunction, type.ascending);
mSortDesc = type.description;
}
void FileData::launchGame(Window* window)
@ -377,6 +386,6 @@ FileData::SortType getSortTypeFromString(std::string desc) {
return sort;
}
}
// if not found default to name, ascending
// if not found default to "name, ascending"
return FileSorts::SortTypes.at(0);
}

View file

@ -86,8 +86,8 @@ public:
: comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {}
};
void sort(ComparisonFunction& comparator, bool ascending = true);
void sort(const SortType& type);
std::string getSortDescription() { return mSortDesc; }
MetaDataList metadata;
protected:
@ -96,6 +96,7 @@ protected:
std::string mSystemName;
private:
void sort(ComparisonFunction& comparator, bool ascending = true);
FileType mType;
std::string mPath;
SystemEnvironmentData* mEnvData;
@ -103,6 +104,7 @@ private:
std::unordered_map<std::string,FileData*> mChildrenByFilename;
std::vector<FileData*> mChildren;
std::vector<FileData*> mFilteredChildren;
std::string mSortDesc;
};
class CollectionFileData : public FileData

View file

@ -171,6 +171,9 @@ std::string FileFilterIndex::getIndexableKey(FileData* game, FilterIndexType typ
key = Utils::String::toUpper(game->metadata.get("kidgame"));
break;
}
default:
LOG(LogWarning) << "Unknown Filter type:" << type;
break;
}
key = Utils::String::trim(key);
if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) {
@ -534,4 +537,4 @@ void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index, std::s
void FileFilterIndex::clearIndex(std::map<std::string, int> indexMap)
{
indexMap.clear();
}
}

View file

@ -8,8 +8,8 @@ namespace FileSorts
{
const FileData::SortType typesArr[] = {
FileData::SortType(&compareName, true, "filename, ascending"),
FileData::SortType(&compareName, false, "filename, descending"),
FileData::SortType(&compareName, true, "name, ascending"),
FileData::SortType(&compareName, false, "name, descending"),
FileData::SortType(&compareRating, true, "rating, ascending"),
FileData::SortType(&compareRating, false, "rating, descending"),

View file

@ -8,15 +8,16 @@
#include "Log.h"
#include "Settings.h"
#include "SystemData.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
{
// first, verify that path is within the system's root folder
FileData* root = system->getRootFolder();
bool contains = false;
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains, true);
const std::string systemPath = root->getPath();
// first, verify that path is within the system's root folder
std::string relative = Utils::FileSystem::removeCommonPath(path, systemPath, contains, true);
if(!contains)
{
LOG(LogError) << "File path \"" << path << "\" is outside system path \"" << system->getStartPath() << "\"";
@ -24,17 +25,21 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
}
Utils::FileSystem::stringList pathList = Utils::FileSystem::getPathList(relative);
auto path_it = pathList.begin();
FileData* treeNode = root;
bool found = false;
// iterate over all subpaths below the provided path
while(path_it != pathList.end())
{
const std::unordered_map<std::string, FileData*>& children = treeNode->getChildrenByFilename();
std::string key = *path_it;
found = children.find(key) != children.cend();
std::string pathSegment = *path_it;
auto candidate = children.find(pathSegment);
found = candidate != children.cend();
if (found) {
treeNode = children.at(key);
treeNode = candidate->second;
}
// this is the end
@ -51,8 +56,12 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
FileData* file = new FileData(type, path, system->getSystemEnvData(), system);
// skipping arcade assets from gamelist
if(!file->isArcadeAsset())
// skipping arcade assets from gamelist and add only to filesystem
// (fs) folders, i.e. entriess in gamelist with <folder/> and not to
// fs-folders which are marked as <game/> in gamelist. NB:
// treeNode's type (=parent) is determined by the element in the
// gamelist and not by the fs-type.
if(!file->isArcadeAsset() && treeNode->getType() == FOLDER)
{
treeNode->addChild(file);
}
@ -65,12 +74,23 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
// if type is a folder it's gonna be empty, so don't bother
if(type == FOLDER)
{
LOG(LogWarning) << "gameList: folder doesn't already exist, won't create";
std::string absFolder = Utils::FileSystem::getAbsolutePath(pathSegment, systemPath);
LOG(LogWarning) << "gameList: folder " << absFolder << " absent on fs, no FileData object created. Do remove leftover in gamelist.xml to remediate this warning.";
return NULL;
}
// discard constellations like scummvm/game.svm/game.svm as
// scummvm/game.svm/ is a GAME and not a FOLDER
if (treeNode->getType() == GAME)
{
std::string absFolder = Utils::FileSystem::getAbsolutePath(pathSegment, systemPath);
LOG(LogWarning) << "gameList: trying to add game '" << absFolder << "' to a parent <game/> entry is invalid, no FileData object created. Do remove nested <game/> in gamelist.xml to remediate this warning.";
return NULL;
}
// create folder filedata object
std::string absPath = Utils::FileSystem::resolveRelativePath(treeNode->getPath() + "/" + pathSegment, systemPath, false, true);
FileData* folder = new FileData(FOLDER, absPath, system->getSystemEnvData(), system);
LOG(LogDebug) << "folder not found as FileData, adding: " << folder->getPath();
// create missing folder
FileData* folder = new FileData(FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it, system->getSystemEnvData(), system);
treeNode->addChild(folder);
treeNode = folder;
}
@ -85,6 +105,7 @@ void parseGamelist(SystemData* system)
{
bool trustGamelist = Settings::getInstance()->getBool("ParseGamelistOnly");
std::string xmlpath = system->getGamelistPath(false);
const std::vector<std::string> allowedExtensions = system->getExtensions();
if(!Utils::FileSystem::exists(xmlpath))
return;
@ -117,7 +138,8 @@ void parseGamelist(SystemData* system)
FileType type = typeList[i];
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
{
const std::string path = Utils::FileSystem::resolveRelativePath(fileNode.child("path").text().get(), relativeTo, false, true);
std::string path = fileNode.child("path").text().get();
path = Utils::FileSystem::resolveRelativePath(path, relativeTo, false, true);
if(!trustGamelist && !Utils::FileSystem::exists(path))
{
@ -125,6 +147,13 @@ void parseGamelist(SystemData* system)
continue;
}
// Check whether the file's extension is allowed in the system
if (i == 0 /*game*/ && std::find(allowedExtensions.cbegin(), allowedExtensions.cend(), Utils::FileSystem::getExtension(path)) == allowedExtensions.cend())
{
LOG(LogDebug) << "file " << path << " found in gamelist, but has unregistered extension";
continue;
}
FileData* file = findOrCreateFile(system, path, type);
if(!file)
{
@ -134,7 +163,7 @@ void parseGamelist(SystemData* system)
else if(!file->isArcadeAsset())
{
std::string defaultName = file->metadata.get("name");
file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo);
file->metadata = MetaDataList::createFromXML(file->getType() == GAME ? GAME_METADATA : FOLDER_METADATA, fileNode, relativeTo);
//make sure name gets set if one didn't exist
if(file->metadata.get("name").empty())
@ -165,7 +194,8 @@ void addFileDataNode(pugi::xml_node& parent, const FileData* file, const char* t
//there's something useful in there so we'll keep the node, add the path
// try and make the path relative if we can so things still work if we change the rom folder location in the future
newNode.prepend_child("path").text().set(Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false, true).c_str());
std::string relPath = Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false, true);
newNode.prepend_child("path").text().set(relPath.c_str());
}
}
@ -216,9 +246,8 @@ void updateGamelist(SystemData* system)
{
int numUpdated = 0;
//get only files, no folders
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
// Stage 1: iterate through all files in memory, checking for changes
for(std::vector<FileData*>::const_iterator fit = files.cbegin(); fit != files.cend(); ++fit)
{
@ -226,13 +255,13 @@ void updateGamelist(SystemData* system)
// do not touch if it wasn't changed anyway
if (!(*fit)->metadata.wasChanged())
continue;
// adding item to changed list
if ((*fit)->getType() == GAME)
if ((*fit)->getType() == GAME)
{
changedGames.push_back((*fit));
changedGames.push_back((*fit));
}
else
else
{
changedFolders.push_back((*fit));
}
@ -243,13 +272,13 @@ void updateGamelist(SystemData* system)
const char* tagList[2] = { "game", "folder" };
FileType typeList[2] = { GAME, FOLDER };
std::vector<FileData*> changedList[2] = { changedGames, changedFolders };
for(int i = 0; i < 2; i++)
{
const char* tag = tagList[i];
std::vector<FileData*> changes = changedList[i];
// if changed items of this type
// check for changed items of this type
if (changes.size() > 0) {
// check if the item already exists in the XML
// if it does, remove all corresponding items before adding
@ -266,9 +295,10 @@ void updateGamelist(SystemData* system)
continue;
}
std::string xmlpath = pathNode.text().get();
// apply the same transformation as in Gamelist::parseGamelist
std::string xmlpath = Utils::FileSystem::resolveRelativePath(pathNode.text().get(), relativeTo, false, true);
xmlpath = Utils::FileSystem::resolveRelativePath(xmlpath, relativeTo, false, true);
for(std::vector<FileData*>::const_iterator cfit = changes.cbegin(); cfit != changes.cend(); ++cfit)
{
if(xmlpath == (*cfit)->getPath())

View file

@ -1,8 +1,9 @@
#include "MetaData.h"
#include "utils/FileSystemUtil.h"
#include "utils/TimeUtil.h"
#include "Log.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
MetaDataDecl gameDecls[] = {
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
@ -13,7 +14,7 @@ MetaDataDecl gameDecls[] = {
{"video", MD_PATH , "", false, "video", "enter path to video"},
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
{"rating", MD_RATING, "0", false, "rating", "enter rating"},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
@ -27,6 +28,12 @@ MetaDataDecl gameDecls[] = {
};
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0]));
const inline std::string blankDate() {
// blank date (1970-01-02) is used to render "" (see DateTimeComponent.cpp) for
// folder metadata when no date is provided (=default case)
return Utils::Time::timeToString(Utils::Time::BLANK_DATE, "%Y%m%d");
}
MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false, "name", "enter game name"},
{"sortname", MD_STRING, "", false, "sortname", "enter game sort name"},
@ -35,12 +42,12 @@ MetaDataDecl folderDecls[] = {
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
{"video", MD_PATH, "", false, "video", "enter path to video"},
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"},
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
{"players", MD_INT, "1", false, "players", "enter number of players"}
{"rating", MD_RATING, "0", false, "rating", "enter rating"},
{"releasedate", MD_DATE, blankDate(), true, "release date", "enter release date"},
{"developer", MD_STRING, "", false, "developer", "enter game developer"},
{"publisher", MD_STRING, "", false, "publisher", "enter game publisher"},
{"genre", MD_STRING, "", false, "genre", "enter game genre"},
{"players", MD_INT, "", false, "players", "enter number of players"}
};
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));

View file

@ -29,7 +29,7 @@ struct MetaDataDecl
std::string key;
MetaDataType type;
std::string defaultValue;
bool isStatistic; //if true, ignore scraper values for this metadata
bool isStatistic; // if true: ignore in scraping and hide in metadata edits for this key
std::string displayName; // displayed as this in editors
std::string displayPrompt; // phrase displayed in editors when prompted to enter value (currently only for strings)
};

View file

@ -92,6 +92,7 @@ namespace PlatformIds
"ti99",
"dragon32",
"zmachine",
"fmtowns",
"ignore", // do not allow scraping for this system
"invalid"

View file

@ -93,6 +93,7 @@ namespace PlatformIds
TI_99,
DRAGON32,
ZMACHINE,
FMTOWNS,
PLATFORM_IGNORE, // do not allow scraping for this system
PLATFORM_COUNT

View file

@ -105,7 +105,7 @@ void SystemData::populateFolder(FileData* folder)
//this is a little complicated because we allow a list of extensions to be defined (delimited with a space)
//we first get the extension of the file itself:
extension = Utils::String::toLower(Utils::FileSystem::getExtension(filePath));
extension = Utils::FileSystem::getExtension(filePath);
//fyi, folders *can* also match the extension and be added as games - this is mostly just to support higan
//see issue #75: https://github.com/Aloshi/EmulationStation/issues/75
@ -148,6 +148,9 @@ void SystemData::indexAllGameFilters(const FileData* folder)
{
case GAME: { mFilterIndex->addToIndex(*it); } break;
case FOLDER: { indexAllGameFilters(*it); } break;
default:
LOG(LogInfo) << "Unknown type: " << (*it)->getType();
break;
}
}
}
@ -184,7 +187,7 @@ SystemData* SystemData::loadSystem(pugi::xml_node system)
for (auto extension = list.cbegin(); extension != list.cend(); extension++)
{
std::string xt = Utils::String::toLower(*extension);
std::string xt = std::string(*extension);
if (std::find(extensions.begin(), extensions.end(), xt) == extensions.end())
extensions.push_back(xt);
}
@ -210,7 +213,9 @@ SystemData* SystemData::loadSystem(pugi::xml_node system)
// if there appears to be an actual platform ID supplied but it didn't match the list, warn
if (str != NULL && str[0] != '\0' && platformId == PlatformIds::PLATFORM_UNKNOWN)
{
LOG(LogWarning) << " Unknown platform for system \"" << name << "\" (platform \"" << str << "\" from list \"" << platformList << "\")";
}
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId);
}
@ -552,6 +557,11 @@ SystemData* SystemData::getRandomSystem()
return random_system;
}
void SystemData::setShuffledCacheDirty()
{
mGamesShuffled.clear();
}
FileData* SystemData::getRandomGame()
{
if (mGamesShuffled.empty())

View file

@ -9,7 +9,7 @@
#include <string>
#include <vector>
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
class FileData;
class FileFilterIndex;
@ -76,6 +76,7 @@ public:
FileFilterIndex* getIndex() { return mFilterIndex; };
void onMetaDataSavePoint();
void setShuffledCacheDirty();
private:
static SystemData* loadSystem(pugi::xml_node system);

View file

@ -1,4 +1,5 @@
#include "SystemScreenSaver.h"
#include "components/TextListComponent.h"
#ifdef _OMX_
#include "components/VideoPlayerComponent.h"
@ -7,6 +8,7 @@
#include "CollectionSystemManager.h"
#include "utils/FileSystemUtil.h"
#include "views/gamelist/IGameListView.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "FileData.h"
#include "FileFilterIndex.h"
@ -33,8 +35,11 @@ SystemScreenSaver::SystemScreenSaver(Window* window) :
mOpacity(0.0f),
mTimer(0),
mCurrentGame(NULL),
mStopBackgroundAudio(true)
mPreviousGame(NULL),
mStopBackgroundAudio(true),
mSystem(NULL)
{
remove(getTitlePath().c_str());
mWindow->setScreenSaver(this);
std::string path = getTitleFolder();
if(!Utils::FileSystem::exists(path))
@ -62,6 +67,58 @@ bool SystemScreenSaver::isScreenSaverActive()
return (mState != STATE_INACTIVE);
}
bool SystemScreenSaver::inputDuringScreensaver(InputConfig* config, Input input)
{
bool input_consumed = false;
std::string screensaver_type = Settings::getInstance()->getString("ScreenSaverBehavior");
bool is_media_screensaver = screensaver_type == "random video" || screensaver_type == "slideshow";
if (!mWindow->isSleeping() && is_media_screensaver)
{
// catch any valid screensaver or invalid inputs here to prevent screensaver from stopping
input_consumed = input_consumed || (config->getMappedTo(input).size() == 0);
bool is_next_input = config->isMappedLike("right", input) || config->isMappedTo("select", input);
bool is_previous_input = config->isMappedLike("left", input);
bool is_favorite_input = config->isMappedLike("y", input);
bool is_start_input = config->isMappedTo("start", input);
bool is_select_game_input = config->isMappedTo("a", input);
bool use_gamelistmedia = screensaver_type == "random video" || !Settings::getInstance()->getBool("SlideshowScreenSaverCustomMediaSource");
// catch any valid screensaver or invalid inputs here to prevent screensaver from stopping
input_consumed = input_consumed || (config->getMappedTo(input).size() == 0);
if (input.value != 0)
{
if (is_next_input)
{
changeMediaItem();
input_consumed = true;
}
else if (use_gamelistmedia)
{
if (is_previous_input && mPreviousGame)
{
changeMediaItem(false);
input_consumed = true;
}
else if (is_start_input || is_select_game_input)
{
selectGame(is_start_input);
input_consumed = false;
}
else if (is_favorite_input && !UIModeController::getInstance()->isUIModeKid())
{
assert(mCurrentGame != NULL);
CollectionSystemManager::get()->toggleGameInCollection(mCurrentGame);
input_consumed = true;
}
}
}
}
return input_consumed;
}
void SystemScreenSaver::setVideoScreensaver(std::string& path)
{
#ifdef _OMX_
@ -89,9 +146,10 @@ void SystemScreenSaver::setVideoScreensaver(std::string& path)
mVideoScreensaver->setVideo(path);
mVideoScreensaver->setScreensaverMode(true);
mVideoScreensaver->onShow();
handleScreenSaverEditingCollection();
PowerSaver::runningScreenSaver(true);
mTimer = 0;
return;
}
void SystemScreenSaver::setImageScreensaver(std::string& path)
@ -101,8 +159,6 @@ void SystemScreenSaver::setImageScreensaver(std::string& path)
mImageScreensaver = new ImageComponent(mWindow, false, false);
}
mTimer = 0;
mImageScreensaver->setImage(path);
mImageScreensaver->setOrigin(0.5f, 0.5f);
mImageScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f);
@ -115,6 +171,10 @@ void SystemScreenSaver::setImageScreensaver(std::string& path)
{
mImageScreensaver->setMaxSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
}
handleScreenSaverEditingCollection();
PowerSaver::runningScreenSaver(true);
mTimer = 0;
}
bool SystemScreenSaver::isFileVideo(std::string& path)
@ -127,8 +187,34 @@ bool SystemScreenSaver::isFileVideo(std::string& path)
return pathFilter.find(pathExtension) != std::string::npos;
}
void SystemScreenSaver::startScreenSaver()
void SystemScreenSaver::handleScreenSaverEditingCollection()
{
std::string screensaverCollection = Settings::getInstance()->getString("DefaultScreenSaverCollection");
std::string currentEditingCollection = CollectionSystemManager::get()->getEditingCollection();
// check if we need to change the screensaver collection
if (screensaverCollection != "") {
// check if we're starting the screensaver
if (isScreenSaverActive())
{
// we're entering the screensaver, backup the currently actively editing collection
mRegularEditingCollection = CollectionSystemManager::get()->getEditingCollection();
CollectionSystemManager::get()->setEditMode(screensaverCollection, true);
}
else
{
// we're exiting the screensaver, restore the currently actively editing collection
CollectionSystemManager::get()->exitEditMode(true);
if (mRegularEditingCollection != "Favorites" && mRegularEditingCollection != "")
CollectionSystemManager::get()->setEditMode(mRegularEditingCollection, true);
mRegularEditingCollection = "";
}
}
}
void SystemScreenSaver::startScreenSaver(SystemData* system)
{
mSystem = system;
// if set to index files in background, start thread
if (Settings::getInstance()->getBool("BackgroundIndexing"))
{
@ -148,7 +234,7 @@ void SystemScreenSaver::startScreenSaver()
// Load a random video
std::string path = "";
pickRandomVideo(path);
pickRandomVideo(path, mCurrentGame != NULL);
int retry = 200;
while(retry > 0 && ((path.empty() || !Utils::FileSystem::exists(path)) || mCurrentGame == NULL))
@ -160,7 +246,7 @@ void SystemScreenSaver::startScreenSaver()
if (!path.empty() && Utils::FileSystem::exists(path))
{
setVideoScreensaver(path);
if (mCurrentGame != NULL)
if (mCurrentGame != NULL)
{
Scripting::fireEvent("screensaver-game-select", mCurrentGame->getSystem()->getName(), mCurrentGame->getPath(), mCurrentGame->getName(), "randomvideo");
}
@ -186,33 +272,24 @@ void SystemScreenSaver::startScreenSaver()
}
else
{
pickRandomGameListImage(path);
pickRandomGameListImage(path, mCurrentGame != NULL);
}
if (isFileVideo(path))
{
setVideoScreensaver(path);
}
else
{
setImageScreensaver(path);
}
std::string bg_audio_file = Settings::getInstance()->getString("SlideshowScreenSaverBackgroundAudioFile");
if ((!mBackgroundAudio) && (bg_audio_file != ""))
if (!mBackgroundAudio && bg_audio_file != "" && Utils::FileSystem::exists(bg_audio_file))
{
if (Utils::FileSystem::exists(bg_audio_file))
{
// paused PS so that the background audio keeps playing
PowerSaver::pause();
mBackgroundAudio = Sound::get(bg_audio_file);
mBackgroundAudio->play();
}
// paused PS so that the background audio keeps playing
PowerSaver::pause();
mBackgroundAudio = Sound::get(bg_audio_file);
mBackgroundAudio->play();
}
PowerSaver::runningScreenSaver(true);
mTimer = 0;
if (mCurrentGame != NULL)
if (mCurrentGame != NULL)
{
Scripting::fireEvent("screensaver-game-select", mCurrentGame->getSystem()->getName(), mCurrentGame->getFileName(), mCurrentGame->getName(), "slideshow");
}
@ -223,8 +300,9 @@ void SystemScreenSaver::startScreenSaver()
mCurrentGame = NULL;
}
void SystemScreenSaver::stopScreenSaver()
void SystemScreenSaver::stopScreenSaver(bool toResume)
{
remove(getTitlePath().c_str());
if ((mBackgroundAudio) && (mStopBackgroundAudio))
{
mBackgroundAudio->stop();
@ -241,27 +319,36 @@ void SystemScreenSaver::stopScreenSaver()
delete mImageScreensaver;
mImageScreensaver = NULL;
// Exit the indexing thread
if (Settings::getInstance()->getBool("BackgroundIndexing"))
if (!toResume) {
// if we're not changing videos or images, let's delete the random list
// and all the scrensaver session-related variables
mCurrentGame = NULL;
mPreviousGame = NULL;
mAllFiles.clear();
mSystem = NULL;
}
// Exit the indexing thread in case it's running. Check if thread still exists.
if (Settings::getInstance()->getBool("BackgroundIndexing") && mThread)
{
mExit = true;
mThread->join();
delete mThread;
mThread = NULL;
}
// we need this to loop through different videos
mState = STATE_INACTIVE;
handleScreenSaverEditingCollection();
PowerSaver::runningScreenSaver(false);
}
void SystemScreenSaver::renderScreenSaver()
{
std::string screensaver_behavior = Settings::getInstance()->getString("ScreenSaverBehavior");
if (mVideoScreensaver && screensaver_behavior == "random video")
if (mVideoScreensaver && (screensaver_behavior == "random video" || screensaver_behavior == "slideshow"))
{
// Render black background
Renderer::setMatrix(Transform4x4f::Identity());
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
setBackground();
// Only render the video if the state requires it
if ((int)mState >= STATE_FADE_IN_VIDEO)
@ -269,32 +356,31 @@ void SystemScreenSaver::renderScreenSaver()
Transform4x4f transform = Transform4x4f::Identity();
mVideoScreensaver->render(transform);
}
// Check if slideshow then loop background music
if (screensaver_behavior == "slideshow" && mBackgroundAudio && !mBackgroundAudio->isPlaying())
{
mBackgroundAudio->play();
}
}
else if (mImageScreensaver && screensaver_behavior == "slideshow")
{
// Render black background
Renderer::setMatrix(Transform4x4f::Identity());
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
setBackground();
// Only render the image if the state requires it
if ((int)mState >= STATE_FADE_IN_VIDEO)
if ((int)mState >= STATE_FADE_IN_VIDEO && mImageScreensaver->hasImage())
{
if (mImageScreensaver->hasImage())
{
mImageScreensaver->setOpacity(255- (unsigned char) (mOpacity * 255));
mImageScreensaver->setOpacity(255- (unsigned char) (mOpacity * 255));
Transform4x4f transform = Transform4x4f::Identity();
mImageScreensaver->render(transform);
}
Transform4x4f transform = Transform4x4f::Identity();
mImageScreensaver->render(transform);
}
// Check if we need to restart the background audio
if ((mBackgroundAudio) && (Settings::getInstance()->getString("SlideshowScreenSaverBackgroundAudioFile") != ""))
if (mBackgroundAudio && !mBackgroundAudio->isPlaying())
{
if (!mBackgroundAudio->isPlaying())
{
mBackgroundAudio->play();
}
mBackgroundAudio->play();
}
}
else if (mState != STATE_INACTIVE)
@ -314,7 +400,7 @@ void SystemScreenSaver::backgroundIndexing()
std::vector<FileData*> files = all->getRootFolder()->getFilesRecursive(GAME);
const auto startTs = std::chrono::system_clock::now();
for (lastIndex; lastIndex < files.size(); lastIndex++)
for ( ; lastIndex < files.size(); lastIndex++)
{
if(mExit)
break;
@ -327,10 +413,15 @@ void SystemScreenSaver::backgroundIndexing()
LOG(LogDebug) << "Indexed a total of " << lastIndex << " entries in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms. Stopping.";
}
void SystemScreenSaver::getAllGamelistNodesForSystem(SystemData* system) {
std::vector<FileData*> subsysFiles {};
FileData* rootFileData = system->getRootFolder();
subsysFiles = rootFileData->getFilesRecursive(FileType::GAME, true);
mAllFiles.insert(mAllFiles.end(), subsysFiles.begin(), subsysFiles.end());
}
std::vector<FileData*> SystemScreenSaver::getAllGamelistNodes()
void SystemScreenSaver::getAllGamelistNodes()
{
std::vector<FileData*> allFiles {};
std::vector<FileData*> subsysFiles {};
for (std::vector<SystemData*>::const_iterator it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); ++it)
{
@ -338,65 +429,75 @@ std::vector<FileData*> SystemScreenSaver::getAllGamelistNodes()
if (!(*it)->isGameSystem() || (*it)->isCollection())
continue;
FileData* rootFileData = (*it)->getRootFolder();
subsysFiles = rootFileData->getFilesRecursive(FileType::GAME, true);
allFiles.insert(allFiles.end(), subsysFiles.begin(), subsysFiles.end());
getAllGamelistNodesForSystem(*it);
}
return allFiles;
}
void SystemScreenSaver::pickGameListNode(const char *nodeName, std::string& path)
void SystemScreenSaver::pickGameListNode(const char *nodeName)
{
FileData *itf = nullptr;
bool found = false;
int missCtr = 0;
while (!found) {
if (mAllFiles.empty()) {
mAllFiles = getAllGamelistNodes();
while (!found)
{
if (mAllFiles.empty())
{
if (mSystem)
getAllGamelistNodesForSystem(mSystem);
else
getAllGamelistNodes();
if (mAllFiles.empty()) { return; } // no games at all
mAllFilesSize = mAllFiles.size();
std::shuffle(std::begin(mAllFiles), std::end(mAllFiles), SystemData::sURNG);
}
itf = mAllFiles.back();
mAllFiles.pop_back();
if ((strcmp(nodeName, "video") == 0 && itf->getVideoPath() != "") ||
(strcmp(nodeName, "image") == 0 && itf->getImagePath() != ""))
{
found = true;
} else {
}
else
{
missCtr++;
if (missCtr == mAllFilesSize) {
if (missCtr == mAllFilesSize)
// avoid looping forever when no candidate exist
// with image/video path set
return;
}
}
}
path = (strcmp(nodeName, "video") == 0) ? itf->getVideoPath() : itf->getImagePath();
mCurrentGame = itf;
}
if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never")
{
auto systemName = mCurrentGame->getSystem()->getFullName();
writeSubtitle(mCurrentGame->getName().c_str(), systemName.c_str(),
(Settings::getInstance()->getString("ScreenSaverGameInfo") == "always"));
void SystemScreenSaver::prepareScreenSaverMedia(const char *nodeName, std::string& path)
{
if (mCurrentGame) {
path = (strcmp(nodeName, "video") == 0) ? mCurrentGame->getVideoPath() : mCurrentGame->getImagePath();
if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never")
{
auto systemName = mCurrentGame->getSourceFileData()->getSystem()->getFullName();
writeSubtitle(mCurrentGame->getSourceFileData()->getName().c_str(), systemName.c_str(),
(Settings::getInstance()->getString("ScreenSaverGameInfo") == "always"));
}
}
}
void SystemScreenSaver::pickRandomVideo(std::string& path)
void SystemScreenSaver::pickRandomVideo(std::string& path, bool keepSame)
{
mCurrentGame = NULL;
pickGameListNode("video", path);
if (!keepSame)
pickGameListNode("video");
prepareScreenSaverMedia("video", path);
}
void SystemScreenSaver::pickRandomGameListImage(std::string& path)
void SystemScreenSaver::pickRandomGameListImage(std::string& path, bool keepSame)
{
mCurrentGame = NULL;
pickGameListNode("image", path);
if (!keepSame)
pickGameListNode("image");
prepareScreenSaverMedia("image", path);
}
void SystemScreenSaver::pickRandomCustomMedia(std::string& path)
@ -484,7 +585,7 @@ void SystemScreenSaver::update(int deltaTime)
mTimer += deltaTime;
if (mTimer > mSwapTimeout)
{
nextMediaItem();
changeMediaItem();
}
}
@ -495,10 +596,21 @@ void SystemScreenSaver::update(int deltaTime)
mImageScreensaver->update(deltaTime);
}
void SystemScreenSaver::nextMediaItem() {
void SystemScreenSaver::changeMediaItem(bool next) {
if (!next) {
// swap entries
FileData* tmpGame = mCurrentGame;
mCurrentGame = mPreviousGame;
mPreviousGame = tmpGame;
}
else
{
mPreviousGame = mCurrentGame;
mCurrentGame = NULL;
}
mStopBackgroundAudio = false;
stopScreenSaver();
startScreenSaver();
stopScreenSaver(true);
startScreenSaver(mSystem);
mState = STATE_SCREENSAVER_ACTIVE;
}
@ -507,14 +619,33 @@ FileData* SystemScreenSaver::getCurrentGame()
return mCurrentGame;
}
void SystemScreenSaver::launchGame()
void SystemScreenSaver::selectGame(bool launch)
{
if (mCurrentGame != NULL)
{
// launching Game
ViewController::get()->goToGameList(mCurrentGame->getSystem());
IGameListView* view = ViewController::get()->getGameListView(mCurrentGame->getSystem()).get();
view->setCursor(mCurrentGame);
view->launch(mCurrentGame);
//Stop screensaver
mStopBackgroundAudio = true;
FileData* gameToSelect = mCurrentGame;
stopScreenSaver();
ViewController::get()->goToGameList(gameToSelect->getSystem());
IGameListView* view = ViewController::get()->getGameListView(gameToSelect->getSystem()).get();
if (launch)
view->launch(gameToSelect);
else
// Flag true is set to re-calculate the cursor position on the visible gamelist section on screen.
// This flag is only to be set when there is no previous navigation state,
// i.e. when jumping to a game from the screensaver or launching a game.
// The latter case is covered in view->launch() ==> ViewController::launch().
// The former case must be flagged as below.
// see also: TextListComponent.REFRESH_LIST_CURSOR_POS and its usage for the 'true' flag
view->setCursor(gameToSelect, true);
}
}
void SystemScreenSaver::setBackground()
{
// Render black background
Renderer::setMatrix(Transform4x4f::Identity());
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
}

View file

@ -16,29 +16,33 @@ public:
SystemScreenSaver(Window* window);
virtual ~SystemScreenSaver();
virtual void startScreenSaver();
virtual void stopScreenSaver();
virtual void nextMediaItem();
virtual void startScreenSaver(SystemData* system=NULL);
virtual void stopScreenSaver(bool toResume=false);
virtual void renderScreenSaver();
virtual bool allowSleep();
virtual void update(int deltaTime);
virtual bool isScreenSaverActive();
virtual FileData* getCurrentGame();
virtual void launchGame();
virtual void selectGame(bool launch);
virtual bool inputDuringScreensaver(InputConfig* config, Input input);
private:
void pickGameListNode(const char *nodeName, std::string& path);
void pickRandomVideo(std::string& path);
void pickRandomGameListImage(std::string& path);
void changeMediaItem(bool next = true);
void pickGameListNode(const char *nodeName);
void prepareScreenSaverMedia(const char *nodeName, std::string& path);
void pickRandomVideo(std::string& path, bool keepSame = false);
void pickRandomGameListImage(std::string& path, bool keepSame = false);
void pickRandomCustomMedia(std::string& path);
void setVideoScreensaver(std::string& path);
void setImageScreensaver(std::string& path);
bool isFileVideo(std::string& path);
std::vector<std::string> getCustomMediaFiles(const std::string &mediaDir);
std::vector<FileData*> getAllGamelistNodes();
void getAllGamelistNodes();
void getAllGamelistNodesForSystem(SystemData* system);
void backgroundIndexing();
void setBackground();
void handleScreenSaverEditingCollection();
void input(InputConfig* config, Input input);
enum STATE {
@ -52,18 +56,21 @@ private:
VideoComponent* mVideoScreensaver;
ImageComponent* mImageScreensaver;
Window* mWindow;
SystemData* mSystem;
STATE mState;
float mOpacity;
int mTimer;
FileData* mCurrentGame;
int mSwapTimeout;
FileData* mPreviousGame;
int mSwapTimeout;
std::shared_ptr<Sound> mBackgroundAudio;
bool mStopBackgroundAudio;
std::vector<FileData*> mAllFiles;
std::vector<std::string> mCustomMediaFiles;
int mAllFilesSize;
std::thread* mThread;
bool mExit;
std::vector<FileData*> mAllFiles;
std::vector<std::string> mCustomMediaFiles;
int mAllFilesSize;
std::thread* mThread;
bool mExit;
std::string mRegularEditingCollection;
};
#endif // ES_APP_SYSTEM_SCREEN_SAVER_H

View file

@ -7,22 +7,21 @@
class MoveCameraAnimation : public Animation
{
public:
MoveCameraAnimation(Transform4x4f& camera, const Vector3f& target) : mCameraStart(camera), mTarget(target), cameraOut(camera) {}
MoveCameraAnimation(Transform4x4f& camera, const Vector3f& target) : mCameraStart(camera), mTarget(target), mCameraOut(camera) { }
int getDuration() const override { return 400; }
void apply(float t) override
{
// cubic ease out
t -= 1;
cameraOut.translation() = -Vector3f().lerp(-mCameraStart.translation(), mTarget, t*t*t + 1);
mCameraOut.translation() = -Vector3f().lerp(-mCameraStart.translation(), mTarget, t*t*t + 1 /*cubic ease out*/);
}
private:
Transform4x4f mCameraStart;
Vector3f mTarget;
Transform4x4f& cameraOut;
Transform4x4f& mCameraOut;
};
#endif // ES_APP_ANIMATIONS_MOVE_CAMERA_ANIMATION_H

View file

@ -110,12 +110,12 @@ void RatingComponent::render(const Transform4x4f& parentTrans)
Transform4x4f trans = parentTrans * getTransform();
Renderer::setMatrix(trans);
mFilledTexture->bind();
Renderer::drawTriangleStrips(&mVertices[0], 4);
mUnfilledTexture->bind();
Renderer::drawTriangleStrips(&mVertices[4], 4);
mFilledTexture->bind();
Renderer::drawTriangleStrips(&mVertices[0], 4);
renderChildren(trans);
}

View file

@ -23,7 +23,7 @@ public:
void setValue(const std::string& value) override; // Should be a normalized float (in the range [0..1]) - if it's not, it will be clamped.
bool input(InputConfig* config, Input input) override;
void render(const Transform4x4f& parentTrans);
void render(const Transform4x4f& parentTrans) override;
void onSizeChanged() override;

View file

@ -30,13 +30,17 @@ protected:
using IList<TextListData, T>::getTransform;
using IList<TextListData, T>::mSize;
using IList<TextListData, T>::mCursor;
using IList<TextListData, T>::Entry;
using IList<TextListData, T>::mViewportTop;
using IList<TextListData, T>::mEntry;
public:
using IList<TextListData, T>::size;
using IList<TextListData, T>::isScrolling;
using IList<TextListData, T>::stopScrolling;
// flag to re-evaluate list cursor position in visible list section
static constexpr int REFRESH_LIST_CURSOR_POS = -1;
TextListComponent(Window* window);
bool input(InputConfig* config, Input input) override;
@ -81,8 +85,8 @@ public:
inline void setLineSpacing(float lineSpacing) { mLineSpacing = lineSpacing; }
protected:
virtual void onScroll(int /*amt*/) { if(!mScrollSound.empty()) Sound::get(mScrollSound)->play(); }
virtual void onCursorChanged(const CursorState& state);
virtual void onScroll(int /*amt*/) override { if(!mScrollSound.empty()) Sound::get(mScrollSound)->play(); }
virtual void onCursorChanged(const CursorState& state) override;
private:
int mMarqueeOffset;
@ -92,7 +96,7 @@ private:
Alignment mAlignment;
float mHorizontalMargin;
int getFirstVisibleEntry();
int viewportTop();
std::function<void(CursorState state)> mCursorChangedCallback;
std::shared_ptr<Font> mFont;
@ -107,10 +111,8 @@ private:
std::string mScrollSound;
static const unsigned int COLOR_ID_COUNT = 2;
unsigned int mColors[COLOR_ID_COUNT];
unsigned int mScreenCount;
int mStartEntry = 0;
unsigned int mCursorPrev = -1;
bool mOneEntryUpDn = true;
int mViewportHeight;
int mCursorPrev = -1;
ImageComponent mSelectorImage;
};
@ -151,32 +153,34 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
const float entrySize = Math::max(font->getHeight(1.0), (float)font->getSize()) * mLineSpacing;
// number of entries that can fit on the screen simultaniously
mScreenCount = (int)(mSize.y() / entrySize);
// number of listentries that can fit on the screen
mViewportHeight = (int)(mSize.y() / entrySize);
if(mViewportTop == REFRESH_LIST_CURSOR_POS)
{
// returning from screen saver activated game launch or screensaver press 'A'
mViewportTop = mCursor - mViewportHeight/2;
mCursorPrev = -1; // reset to pristine to calc viewportTop() right when jumping to game with 'A' pressed
}
if(mCursor != mCursorPrev)
{
mStartEntry = (size() > mScreenCount) ? getFirstVisibleEntry() : 0;
mViewportTop = (size() > mViewportHeight) ? viewportTop() : 0;
mCursorPrev = mCursor;
}
unsigned int listCutoff = mStartEntry + mScreenCount;
unsigned int listCutoff = mViewportTop + mViewportHeight;
if(listCutoff > size())
listCutoff = size();
float y = (mSize.y() - (mScreenCount * entrySize)) * 0.5f;
float y = (mSize.y() - (mViewportHeight * entrySize)) * 0.5f;
// draw selector bar
if(mStartEntry < listCutoff)
{
if (mSelectorImage.hasImage()) {
mSelectorImage.setPosition(0.f, y + (mCursor - mStartEntry)*entrySize + mSelectorOffsetY, 0.f);
mSelectorImage.render(trans);
} else {
Renderer::setMatrix(trans);
Renderer::drawRect(0.0f, y + (mCursor - mStartEntry)*entrySize + mSelectorOffsetY, mSize.x(),
mSelectorHeight, mSelectorColor, mSelectorColorEnd, mSelectorColorGradientHorizontal);
}
if (mSelectorImage.hasImage()) {
mSelectorImage.setPosition(0.f, y + (mCursor - mViewportTop)*entrySize + mSelectorOffsetY, 0.f);
mSelectorImage.render(trans);
} else {
Renderer::setMatrix(trans);
Renderer::drawRect(0.0f, y + (mCursor - mViewportTop)*entrySize + mSelectorOffsetY, mSize.x(),
mSelectorHeight, mSelectorColor, mSelectorColorEnd, mSelectorColorGradientHorizontal);
}
// clip to inside margins
@ -185,7 +189,7 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
Renderer::pushClipRect(Vector2i((int)(trans.translation().x() + mHorizontalMargin), (int)trans.translation().y()),
Vector2i((int)(dim.x() - mHorizontalMargin*2), (int)dim.y()));
for(int i = mStartEntry; i < listCutoff; i++)
for(int i = mViewportTop; i < listCutoff; i++)
{
typename IList<TextListData, T>::Entry& entry = mEntries.at((unsigned int)i);
@ -254,91 +258,65 @@ void TextListComponent<T>::render(const Transform4x4f& parentTrans)
template <typename T>
int TextListComponent<T>::getFirstVisibleEntry()
int TextListComponent<T>::viewportTop()
{
if (mCursorPrev == -1)
{
// init or returned from emulator
mCursorPrev = mCursor;
int quot = div(mCursor, mScreenCount).quot;
mStartEntry = quot * mScreenCount;
}
int screenRelCursor = mCursorPrev - mStartEntry;
bool cursorCentered = screenRelCursor == mScreenCount/2;
int visibleEntryMax = size() - mScreenCount;
int firstVisibleEntry = 0;
int viewportTopMax = size() - mViewportHeight;
int topNew = mViewportTop;
if(Settings::getInstance()->getBool("UseFullscreenPaging") && !cursorCentered)
if (mCursorPrev == -1)
mCursorPrev = mCursor;
int delta = mCursor - mCursorPrev;
if(Settings::getInstance()->getBool("UseFullscreenPaging"))
{
// keep visible cursor constant but move visible list (default)
firstVisibleEntry = mCursor - screenRelCursor;
if(mOneEntryUpDn)
// delta may be greater/less than +/-mViewportHeight on re-sorting of list
if (delta <= -mViewportHeight || delta >= mViewportHeight
// keep cursor sticky at position unless the user navigates
// to the middle of the viewport
|| delta < 0 && mCursor - mViewportTop < mViewportHeight/2
|| delta > 0 && mCursor - mViewportTop > mViewportHeight/2)
{
int delta = mCursor - mCursorPrev;
// detect rollover (== delta is more than one item)
if(delta < -3)
firstVisibleEntry = 0;
else if(delta > 3)
firstVisibleEntry = visibleEntryMax;
else if(screenRelCursor < mScreenCount/2 && delta > 0 /*down pressed*/
|| screenRelCursor > mScreenCount/2 && delta < 0 /*up pressed*/)
// cases for list begin / list end
// move visible cursor and keep visible list section constant
firstVisibleEntry = firstVisibleEntry - delta;
topNew += delta;
}
} else {
// cursor always in middle of visible list
firstVisibleEntry = mCursor - mScreenCount/2;
// no match above will place the cursor more towards the middle
}
// bounds check
if(firstVisibleEntry < 0)
firstVisibleEntry = 0;
else if(firstVisibleEntry > visibleEntryMax)
firstVisibleEntry = visibleEntryMax;
return firstVisibleEntry;
else
// put cursor in middle of visible list
topNew = mCursor - mViewportHeight/2;
if (mCursor <= mViewportHeight/2)
topNew = 0;
else if (mCursor >= viewportTopMax + mViewportHeight/2)
topNew = viewportTopMax;
return topNew;
}
template <typename T>
bool TextListComponent<T>::input(InputConfig* config, Input input)
{
if(size() > 0)
bool isSingleStep = config->isMappedLike("down", input) || config->isMappedLike("up", input);
bool isPageStep = config->isMappedLike("rightshoulder", input) || config->isMappedLike("leftshoulder", input);
if(size() > 0 && (isSingleStep || isPageStep))
{
if(input.value != 0)
{
if(config->isMappedLike("down", input))
int delta;
mCursorPrev = mCursor;
if(isSingleStep)
delta = config->isMappedLike("down", input) ? 1 : -1;
else
{
listInput(1);
mOneEntryUpDn = true;
return true;
}
if(config->isMappedLike("up", input))
{
listInput(-1);
mOneEntryUpDn = true;
return true;
}
if(config->isMappedLike("rightshoulder", input))
{
int delta = Settings::getInstance()->getBool("UseFullscreenPaging") ? mScreenCount : 10;
listInput(delta);
mOneEntryUpDn = false;
return true;
}
if(config->isMappedLike("leftshoulder", input))
{
int delta = Settings::getInstance()->getBool("UseFullscreenPaging") ? mScreenCount : 10;
listInput(-delta);
mOneEntryUpDn = false;
return true;
delta = Settings::getInstance()->getBool("UseFullscreenPaging") ? mViewportHeight : 10;
if (config->isMappedLike("leftshoulder", input))
delta = -delta;
}
listInput(delta);
return true;
}else{
if(config->isMappedLike("down", input) || config->isMappedLike("up", input) ||
config->isMappedLike("rightshoulder", input) || config->isMappedLike("leftshoulder", input))
{
stopScrolling();
}
stopScrolling();
}
}
@ -414,6 +392,11 @@ void TextListComponent<T>::onCursorChanged(const CursorState& state)
template <typename T>
void TextListComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
if(Settings::getInstance()->getBool("UseFullscreenPaging"))
{
mViewportTop = REFRESH_LIST_CURSOR_POS;
}
GuiComponent::applyTheme(theme, view, element, properties);
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "textlist");

View file

@ -1,7 +1,11 @@
#include <fstream>
#include "guis/GuiCollectionSystemsOptions.h"
#include "components/OptionListComponent.h"
#include "components/SwitchComponent.h"
#include "guis/GuiInfoPopup.h"
#include "guis/GuiRandomCollectionOptions.h"
#include "guis/GuiSettings.h"
#include "guis/GuiTextEditPopup.h"
#include "utils/StringUtil.h"
@ -19,53 +23,63 @@ void GuiCollectionSystemsOptions::initializeMenu()
addChild(&mMenu);
// get collections
addSystemsToMenu();
// add "Create New Custom Collection from Theme"
// manage random collection
addEntry("RANDOM GAME COLLECTION SETTINGS", 0x777777FF, true, [this] { openRandomCollectionSettings(); });
std::vector<std::string> unusedFolders = CollectionSystemManager::get()->getUnusedSystemsFromTheme();
if (unusedFolders.size() > 0)
{
addEntry("CREATE NEW CUSTOM COLLECTION FROM THEME", 0x777777FF, true,
[this, unusedFolders] {
auto s = new GuiSettings(mWindow, "SELECT THEME FOLDER");
std::shared_ptr< OptionListComponent<std::string> > folderThemes = std::make_shared< OptionListComponent<std::string> >(mWindow, "SELECT THEME FOLDER", true);
// add Custom Systems
for(auto it = unusedFolders.cbegin() ; it != unusedFolders.cend() ; it++ )
{
ComponentListRow row;
std::string name = *it;
std::function<void()> createCollectionCall = [name, this, s] {
createCollection(name);
};
row.makeAcceptInputHandler(createCollectionCall);
auto themeFolder = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(themeFolder, true);
s->addRow(row);
}
mWindow->pushGui(s);
});
}
ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
auto createCustomCollection = [this](const std::string& newVal) {
std::string name = newVal;
// we need to store the first Gui and remove it, as it'll be deleted by the actual Gui
Window* window = mWindow;
GuiComponent* topGui = window->peekGui();
window->removeGui(topGui);
createCollection(name);
};
row.makeAcceptInputHandler([this, createCustomCollection] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, "New Collection Name", "", createCustomCollection, false));
});
if(CollectionSystemManager::get()->isEditing())
{
row.addElement(std::make_shared<TextComponent>(mWindow, "FINISH EDITING '" + Utils::String::toUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.makeAcceptInputHandler(std::bind(&GuiCollectionSystemsOptions::exitEditMode, this));
mMenu.addRow(row);
}
else
{
// add "Create New Custom Collection from Theme"
std::vector<std::string> unusedFolders = CollectionSystemManager::get()->getUnusedSystemsFromTheme();
if (unusedFolders.size() > 0)
{
addEntry("CREATE NEW CUSTOM COLLECTION FROM THEME", 0x777777FF, true,
[this, unusedFolders] {
auto s = new GuiSettings(mWindow, "SELECT THEME FOLDER");
std::shared_ptr< OptionListComponent<std::string> > folderThemes = std::make_shared< OptionListComponent<std::string> >(mWindow, "SELECT THEME FOLDER", true);
mMenu.addRow(row);
// add Custom Systems
for(auto it = unusedFolders.cbegin() ; it != unusedFolders.cend() ; it++ )
{
ComponentListRow row;
std::string name = *it;
std::function<void()> createCollectionCall = [name, this, s] {
createCollection(name);
};
row.makeAcceptInputHandler(createCollectionCall);
auto themeFolder = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(themeFolder, true);
s->addRow(row);
}
mWindow->pushGui(s);
});
}
row.addElement(std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
auto createCustomCollection = [this](const std::string& newVal) {
std::string name = newVal;
// we need to store the first Gui and remove it, as it'll be deleted by the actual Gui
Window* window = mWindow;
GuiComponent* topGui = window->peekGui();
window->removeGui(topGui);
createCollection(name);
};
row.makeAcceptInputHandler([this, createCustomCollection] {
mWindow->pushGui(new GuiTextEditPopup(mWindow, "New Collection Name", "", createCustomCollection, false));
});
mMenu.addRow(row);
}
bundleCustomCollections = std::make_shared<SwitchComponent>(mWindow);
bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem"));
@ -84,14 +98,23 @@ void GuiCollectionSystemsOptions::initializeMenu()
doublePressToRemoveFavs->setState(Settings::getInstance()->getBool("DoublePressRemovesFromFavs"));
mMenu.addWithLabel("PRESS (Y) TWICE TO REMOVE FROM FAVS./COLL.", doublePressToRemoveFavs);
if(CollectionSystemManager::get()->isEditing())
// Add option to select default collection for screensaver
defaultScreenSaverCollection = std::make_shared< OptionListComponent<std::string> >(mWindow, "ADD/REMOVE GAMES WHILE SCREENSAVER TO", false);
// Add default option
std::string defaultScreenSaverCollectionName = Settings::getInstance()->getString("DefaultScreenSaverCollection");
defaultScreenSaverCollection->add("<DEFAULT>", "", defaultScreenSaverCollectionName == "");
std::map<std::string, CollectionSystemData> customSystems = CollectionSystemManager::get()->getCustomCollectionSystems();
// add all enabled Custom Systems
for(std::map<std::string, CollectionSystemData>::const_iterator it = customSystems.cbegin() ; it != customSystems.cend() ; it++ )
{
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "FINISH EDITING '" + Utils::String::toUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.makeAcceptInputHandler(std::bind(&GuiCollectionSystemsOptions::exitEditMode, this));
mMenu.addRow(row);
if (it->second.isEnabled)
defaultScreenSaverCollection->add(it->second.decl.longName, it->second.decl.name, defaultScreenSaverCollectionName == it->second.decl.name);
}
mMenu.addWithLabel("ADD/REMOVE GAMES WHILE SCREENSAVER TO", defaultScreenSaverCollection);
mMenu.addButton("BACK", "back", std::bind(&GuiCollectionSystemsOptions::applySettings, this));
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
@ -116,9 +139,17 @@ void GuiCollectionSystemsOptions::addEntry(const char* name, unsigned int color,
mMenu.addRow(row);
}
void GuiCollectionSystemsOptions::createCollection(std::string inName) {
std::string name = CollectionSystemManager::get()->getValidNewCollectionName(inName);
SystemData* newSys = CollectionSystemManager::get()->addNewCustomCollection(name);
void GuiCollectionSystemsOptions::createCollection(std::string inName)
{
CollectionSystemManager* collSysMgr = CollectionSystemManager::get();
std::string name = collSysMgr->getValidNewCollectionName(inName);
SystemData* newSys = collSysMgr->addNewCustomCollection(name, true);
if (!collSysMgr->saveCustomCollection(newSys)) {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Failed creating '" + Utils::String::toUpper(name) + "' Collection. See log for details.", 8000);
mWindow->setInfoPopup(s);
return;
}
customOptionList->add(name, name, true);
std::string outAuto = Utils::String::vectorToDelimitedString(autoOptionList->getSelectedObjects(), ",");
std::string outCustom = Utils::String::vectorToDelimitedString(customOptionList->getSelectedObjects(), ",");
@ -126,10 +157,14 @@ void GuiCollectionSystemsOptions::createCollection(std::string inName) {
ViewController::get()->goToSystemView(newSys);
Window* window = mWindow;
CollectionSystemManager::get()->setEditMode(name);
collSysMgr->setEditMode(name);
while(window->peekGui() && window->peekGui() != ViewController::get())
delete window->peekGui();
return;
}
void GuiCollectionSystemsOptions::openRandomCollectionSettings()
{
mWindow->pushGui(new GuiRandomCollectionOptions(mWindow));
}
void GuiCollectionSystemsOptions::exitEditMode()
@ -181,15 +216,42 @@ void GuiCollectionSystemsOptions::applySettings()
bool prevBundle = Settings::getInstance()->getBool("UseCustomCollectionsSystem");
bool prevShow = Settings::getInstance()->getBool("CollectionShowSystemInfo");
bool outShow = toggleSystemNameInCollections->getState();
bool prevDblPressRmFavs = Settings::getInstance()->getBool("DoublePressRemovesFromFavs");
bool outDblPressRmFavs = doublePressToRemoveFavs->getState();
bool needUpdateSettings = prevAuto != outAuto || prevCustom != outCustom || outSort != prevSort || outBundle != prevBundle
|| prevShow != outShow || prevDblPressRmFavs != outDblPressRmFavs;
if (needUpdateSettings)
// check if custom collection is still enabled for 'collection during screensaver'.
// if not set 'collection during screensaver' to "" which renders as <DEFAULT>
std::string enabledCollectionName = "";
std::vector<std::string> selection = defaultScreenSaverCollection->getSelectedObjects();
if (selection.size() > 0)
{
std::string selectedCollection = selection.at(0);
if (selectedCollection != "")
{
std::vector<std::string> enabledCollections = customOptionList->getSelectedObjects();
for(auto nameIt = enabledCollections.begin(); nameIt != enabledCollections.end(); nameIt++)
{
if(*nameIt == selectedCollection)
{
enabledCollectionName = selectedCollection;
break;
}
}
}
}
Settings::getInstance()->setString("DefaultScreenSaverCollection", enabledCollectionName);
Settings::getInstance()->setBool("DoublePressRemovesFromFavs", doublePressToRemoveFavs->getState());
bool needRefreshCollectionSettings = prevAuto != outAuto || prevCustom != outCustom || outSort != prevSort || outBundle != prevBundle
|| prevShow != outShow;
if (needRefreshCollectionSettings)
{
updateSettings(outAuto, outCustom);
}
else
{
Settings::getInstance()->saveFile();
}
delete this;
}
@ -200,8 +262,9 @@ void GuiCollectionSystemsOptions::updateSettings(std::string newAutoSettings, st
Settings::getInstance()->setBool("SortAllSystems", sortAllSystemsSwitch->getState());
Settings::getInstance()->setBool("UseCustomCollectionsSystem", bundleCustomCollections->getState());
Settings::getInstance()->setBool("CollectionShowSystemInfo", toggleSystemNameInCollections->getState());
Settings::getInstance()->setBool("DoublePressRemovesFromFavs", doublePressToRemoveFavs->getState());
Settings::getInstance()->saveFile();
CollectionSystemManager::get()->loadEnabledListFromSettings();
CollectionSystemManager::get()->updateSystemsList();
ViewController::get()->goToStart();

View file

@ -26,8 +26,10 @@ private:
void updateSettings(std::string newAutoSettings, std::string newCustomSettings);
void createCollection(std::string inName);
void exitEditMode();
void openRandomCollectionSettings();
std::shared_ptr< OptionListComponent<std::string> > autoOptionList;
std::shared_ptr< OptionListComponent<std::string> > customOptionList;
std::shared_ptr< OptionListComponent<std::string> > defaultScreenSaverCollection;
std::shared_ptr<SwitchComponent> sortAllSystemsSwitch;
std::shared_ptr<SwitchComponent> bundleCustomCollections;
std::shared_ptr<SwitchComponent> toggleSystemNameInCollections;

View file

@ -14,7 +14,7 @@ public:
void onSizeChanged() override;
bool input(InputConfig* config, Input input) override;
void update(int deltaTime);
void update(int deltaTime) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
private:

View file

@ -10,73 +10,98 @@
#include "FileSorts.h"
#include "GuiMetaDataEd.h"
#include "SystemData.h"
#include "components/TextListComponent.h"
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window),
mSystem(system), mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false)
mSystem(system), mMenu(window, "OPTIONS"), mFromPlaceholder(false), mFiltersChanged(false),
mJumpToSelected(false), mMetadataChanged(false)
{
addChild(&mMenu);
// check it's not a placeholder folder - if it is, only show "Filter Options"
FileData* file = getGamelist()->getCursor();
fromPlaceholder = file->isPlaceHolder();
mFromPlaceholder = file->isPlaceHolder();
ComponentListRow row;
if (!fromPlaceholder) {
// jump to letter
if (!mFromPlaceholder) {
row.elements.clear();
// define supported character range
// this range includes all numbers, capital letters, and most reasonable symbols
char startChar = '!';
char endChar = '_';
std::string currentSort = mSystem->getRootFolder()->getSortDescription();
std::string reqSort = FileSorts::SortTypes.at(0).description;
char curChar = (char)toupper(getGamelist()->getCursor()->getSortName()[0]);
if(curChar < startChar || curChar > endChar)
curChar = startChar;
// "jump to letter" menuitem only available (and correct jumping) on sort order "name, asc"
if (currentSort == reqSort) {
bool outOfRange = false;
char curChar = (char)toupper(getGamelist()->getCursor()->getSortName()[0]);
// define supported character range
// this range includes all numbers, capital letters, and most reasonable symbols
char startChar = '!';
char endChar = '_';
if (curChar < startChar || curChar > endChar) {
// most likely 8 bit ASCII or Unicode (Prefix: 0xc2 or 0xe2) value
curChar = startChar;
outOfRange = true;
}
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO...", false);
for (char c = startChar; c <= endChar; c++)
{
// check if c is a valid first letter in current list
const std::vector<FileData*>& files = getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
for (auto file : files)
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO ...", false);
for (char c = startChar; c <= endChar; c++)
{
char candidate = (char)toupper(file->getSortName()[0]);
if (c == candidate)
// check if c is a valid first letter in current list
const std::vector<FileData*>& files = getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
for (auto file : files)
{
mJumpToLetterList->add(std::string(1, c), c, c == curChar);
break;
char candidate = (char)toupper(file->getSortName()[0]);
if (c == candidate)
{
mJumpToLetterList->add(std::string(1, c), c, (c == curChar) || outOfRange);
outOfRange = false; // only override selection on very first c == candidate match
break;
}
}
}
row.addElement(std::make_shared<TextComponent>(mWindow, "JUMP TO ...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(mJumpToLetterList, false);
row.input_handler = [&](InputConfig* config, Input input) {
if(config->isMappedTo("a", input) && input.value)
{
jumpToLetter();
return true;
}
else if(mJumpToLetterList->input(config, input))
{
return true;
}
return false;
};
mMenu.addRow(row);
}
row.addElement(std::make_shared<TextComponent>(mWindow, "JUMP TO...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(mJumpToLetterList, false);
row.input_handler = [&](InputConfig* config, Input input) {
if(config->isMappedTo("a", input) && input.value)
{
jumpToLetter();
return true;
}
else if(mJumpToLetterList->input(config, input))
{
return true;
}
return false;
};
mMenu.addRow(row);
// add launch system screensaver
std::string screensaver_behavior = Settings::getInstance()->getString("ScreenSaverBehavior");
bool useGamelistMedia = screensaver_behavior == "random video" || (screensaver_behavior == "slideshow" && !Settings::getInstance()->getBool("SlideshowScreenSaverCustomMediaSource"));
bool rpConfigSelected = "retropie" == mSystem->getName();
bool collectionsSelected = mSystem->getName() == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName();
// sort list by
if (!rpConfigSelected && useGamelistMedia && (!collectionsSelected || collectionsSelected && file->getType() == GAME)) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "LAUNCH SYSTEM SCREENSAVER", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::launchSystemScreenSaver, this));
mMenu.addRow(row);
}
// "sort list by" menuitem
mListSort = std::make_shared<SortList>(mWindow, "SORT GAMES BY", false);
for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++)
{
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
mListSort->add(sort.description, &sort, i == 0); // TODO - actually make the sort type persistent
mListSort->add(sort.description, &sort, sort.description == currentSort);
}
mMenu.addWithLabel("SORT GAMES BY", mListSort);
}
// show filtered menu
if(!Settings::getInstance()->getBool("ForceDisableFilters"))
{
@ -107,10 +132,21 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
mMenu.addRow(row);
}
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder && !(mSystem->isCollection() && file->getType() == FOLDER))
if(UIModeController::getInstance()->isUIModeFull() && system == CollectionSystemManager::get()->getRandomCollection())
{
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(mWindow, "GET NEW RANDOM GAMES", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::recreateCollection, this));
mMenu.addRow(row);
}
if (UIModeController::getInstance()->isUIModeFull() && !mFromPlaceholder && !(mSystem->isCollection() && file->getType() == FOLDER))
{
row.elements.clear();
std::string lblTxt = std::string("EDIT THIS ");
lblTxt += std::string((file->getType() == FOLDER ? "FOLDER" : "GAME"));
lblTxt += std::string("'S METADATA");
row.addElement(std::make_shared<TextComponent>(mWindow, lblTxt, Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row);
@ -123,23 +159,48 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
GuiGamelistOptions::~GuiGamelistOptions()
{
FileData* root = mSystem->getRootFolder();
// apply sort
if (!fromPlaceholder) {
FileData* root = mSystem->getRootFolder();
root->sort(*mListSort->getSelected()); // will also recursively sort children
if (!mFromPlaceholder) {
const FileData::SortType selectedSort = mJumpToSelected ? FileSorts::SortTypes.at(0) /* force "name, asc" */ : *mListSort->getSelected();
if (root->getSortDescription() != selectedSort.description) {
root->sort(selectedSort); // will also recursively sort children
// notify that the root folder was sorted
getGamelist()->onFileChanged(root, FILE_SORTED);
}
}
// notify that the root folder was sorted
getGamelist()->onFileChanged(root, FILE_SORTED);
}
if (mFiltersChanged)
if (mFiltersChanged || mMetadataChanged)
{
// only reload full view if we came from a placeholder
// as we need to re-display the remaining elements for whatever new
// game is selected
// force refresh of cursor list position
ViewController::get()->getGameListView(mSystem)->setViewportTop(TextListComponent<FileData>::REFRESH_LIST_CURSOR_POS);
// re-display the elements for whatever new or renamed game is selected
ViewController::get()->reloadGameListView(mSystem);
if (mFiltersChanged) {
// trigger repaint of cursor and list detail
getGamelist()->onFileChanged(root, FILE_SORTED);
}
}
}
bool GuiGamelistOptions::launchSystemScreenSaver()
{
SystemData* system = mSystem;
std::string systemName = system->getName();
// need to check if we're in a folder inside the collections bundle, to launch from there
if(systemName == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName())
{
FileData* file = getGamelist()->getCursor(); // is GAME otherwise menuentry would have been hidden
// we are inside a specific collection. We want to launch for that one.
system = file->getSystem();
}
mWindow->startScreenSaver(system);
mWindow->renderScreenSaver();
delete this;
return true;
}
void GuiGamelistOptions::openGamelistFilter()
{
mFiltersChanged = true;
@ -147,6 +208,12 @@ void GuiGamelistOptions::openGamelistFilter()
mWindow->pushGui(ggf);
}
void GuiGamelistOptions::recreateCollection()
{
CollectionSystemManager::get()->recreateCollection(mSystem);
delete this;
}
void GuiGamelistOptions::startEditMode()
{
std::string editingSystem = mSystem->getName();
@ -184,8 +251,14 @@ void GuiGamelistOptions::openMetaDataEd()
p.game = file;
p.system = file->getSystem();
std::function<void()> deleteBtnFunc;
std::function<void()> saveBtnFunc;
saveBtnFunc = [this, file] {
ViewController::get()->getGameListView(mSystem)->setCursor(file, true);
mMetadataChanged = true;
ViewController::get()->getGameListView(file->getSystem())->onFileChanged(file, FILE_METADATA_CHANGED);
};
std::function<void()> deleteBtnFunc;
if (file->getType() == FOLDER)
{
deleteBtnFunc = NULL;
@ -198,8 +271,7 @@ void GuiGamelistOptions::openMetaDataEd()
};
}
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, Utils::FileSystem::getFileName(file->getPath()),
std::bind(&IGameListView::onFileChanged, ViewController::get()->getGameListView(file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, Utils::FileSystem::getFileName(file->getPath()), saveBtnFunc, deleteBtnFunc));
}
void GuiGamelistOptions::jumpToLetter()
@ -234,6 +306,9 @@ void GuiGamelistOptions::jumpToLetter()
gamelist->setCursor(files.at(mid));
// flag to force default sort order "name, asc", if user changed the sortorder in the options dialog
mJumpToSelected = true;
delete this;
}

View file

@ -22,8 +22,10 @@ public:
private:
void openGamelistFilter();
bool launchSystemScreenSaver();
void openMetaDataEd();
void startEditMode();
void recreateCollection();
void exitEditMode();
void jumpToLetter();
@ -37,8 +39,10 @@ private:
SystemData* mSystem;
IGameListView* getGamelist();
bool fromPlaceholder;
bool mFromPlaceholder;
bool mFiltersChanged;
bool mJumpToSelected;
bool mMetadataChanged;
};
#endif // ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H

View file

@ -14,7 +14,7 @@ public:
GuiInfoPopup(Window* window, std::string message, int duration, int fadein = 500, int fadeout = 500);
~GuiInfoPopup();
void render(const Transform4x4f& parentTrans) override;
inline void stop() { running = false; };
inline void stop() override { running = false; };
private:
std::string mMessage;
int mDuration;

View file

@ -301,8 +301,7 @@ void GuiMenu::openUISettings()
{
Scripting::fireEvent("theme-changed", theme_set->getSelected(), oldTheme);
CollectionSystemManager::get()->updateSystemsList();
ViewController::get()->goToStart();
ViewController::get()->reloadAll(); // TODO - replace this with some sort of signal-based implementation
ViewController::get()->reloadAll(true); // TODO - replace this with some sort of signal-based implementation
}
});
}

View file

@ -1,7 +1,9 @@
#include "guis/GuiMetaDataEd.h"
#include <stdlib.h>
#include "components/ButtonComponent.h"
#include "components/ComponentList.h"
#include "components/DateTimeComponent.h"
#include "components/DateTimeEditComponent.h"
#include "components/MenuComponent.h"
#include "components/RatingComponent.h"
@ -18,6 +20,7 @@
#include "FileFilterIndex.h"
#include "SystemData.h"
#include "Window.h"
#include "Log.h"
GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector<MetaDataDecl>& mdd, ScraperSearchParams scraperParams,
const std::string& /*header*/, std::function<void()> saveCallback, std::function<void()> deleteFunc) : GuiComponent(window),
@ -28,7 +31,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
mMetaDataDecl(mdd),
mMetaData(md),
mSavedCallback(saveCallback), mDeleteFunc(deleteFunc)
mSavedCallback(saveCallback),
mDeleteFunc(deleteFunc)
{
addChild(&mBackground);
addChild(&mGrid);
@ -36,8 +40,9 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
mHeaderGrid = std::make_shared<ComponentGrid>(mWindow, Vector2i(1, 5));
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER);
mSubtitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath())),
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
std::string tgt = md->getType() == GAME_METADATA ? "GAME" : "FOLDER";
std::string subt = tgt + ": " + Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game->getPath()));
mSubtitle = std::make_shared<TextComponent>(mWindow, subt, Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
mHeaderGrid->setEntry(mTitle, Vector2i(0, 1), false, true);
mHeaderGrid->setEntry(mSubtitle, Vector2i(0, 3), false, true);
@ -49,16 +54,20 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
// populate list
for(auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
{
std::shared_ptr<GuiComponent> ed;
// don't add statistics
if(iter->isStatistic)
continue;
std::shared_ptr<GuiComponent> ed;
// create ed and add it (and any related components) to mMenu
// ed's value will be set below
ComponentListRow row;
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF);
auto lblTxt = Utils::String::toUpper(iter->displayName);
if (iter->type == MD_DATE) {
lblTxt += " (" + DateTimeComponent::getDateformatTip() + ")";
}
auto lbl = std::make_shared<TextComponent>(mWindow, lblTxt, Font::get(FONT_SIZE_SMALL), 0x777777FF);
row.addElement(lbl, true); // label
switch(iter->type)
@ -110,6 +119,8 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
{
// MD_STRING
ed = std::make_shared<TextComponent>(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
const float height = lbl->getSize().y() * 0.71f;
ed->setSize(0, height);
row.addElement(ed, true);
auto spacer = std::make_shared<GuiComponent>(mWindow);
@ -139,7 +150,7 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
std::vector< std::shared_ptr<ButtonComponent> > buttons;
if(!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
if(md->getType() == GAME_METADATA && !scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save", [&] { save(); delete this; }));
@ -184,11 +195,17 @@ void GuiMetaDataEd::save()
// remove game from index
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
for(unsigned int i = 0; i < mEditors.size(); i++)
assert(mMetaDataDecl.size() >= mEditors.size());
// there may be less editfields than metadata entries as
// statistic md fields are not shown to the user.
// md statistic fields are not necessarily at the end of the md list
int edIdx = 0;
for(auto &mdd : mMetaDataDecl)
{
if(mMetaDataDecl.at(i).isStatistic)
continue;
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
if(!mdd.isStatistic) {
mMetaData->set(mdd.key, mEditors.at(edIdx)->getValue());
edIdx++;
}
}
// enter game in index
@ -211,29 +228,21 @@ void GuiMetaDataEd::fetch()
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
{
for(unsigned int i = 0; i < mEditors.size(); i++)
assert(mMetaDataDecl.size() >= mEditors.size());
int edIdx = 0;
for(auto &mdd : mMetaDataDecl)
{
if(mMetaDataDecl.at(i).isStatistic)
if(mdd.isStatistic)
continue;
const std::string& key = mMetaDataDecl.at(i).key;
mEditors.at(i)->setValue(result.mdl.get(key));
mEditors.at(edIdx)->setValue(result.mdl.get(mdd.key));
edIdx++;
}
}
void GuiMetaDataEd::close(bool closeAllWindows)
{
// find out if the user made any changes
bool dirty = false;
for(unsigned int i = 0; i < mEditors.size(); i++)
{
const std::string& key = mMetaDataDecl.at(i).key;
if(mMetaData->get(key) != mEditors.at(i)->getValue())
{
dirty = true;
break;
}
}
bool dirty = hasChanges();
std::function<void()> closeFunc;
if(!closeAllWindows)
@ -247,7 +256,6 @@ void GuiMetaDataEd::close(bool closeAllWindows)
};
}
if(dirty)
{
// changes were made, ask if the user wants to save them
@ -261,6 +269,49 @@ void GuiMetaDataEd::close(bool closeAllWindows)
}
}
bool GuiMetaDataEd::hasChanges()
{
assert(mMetaDataDecl.size() >= mEditors.size());
// find out if the user made any changes
int edIdx = 0;
for(auto &mdd : mMetaDataDecl)
{
if(!mdd.isStatistic)
{
std::string gamelistVal = mMetaData->get(mdd.key);
std::string editorVal = mEditors.at(edIdx++)->getValue();
if (mdd.key == "rating")
{
// needed to catch "0", "0.0" or ".<d>" (and "1.0") from gamelist string rating
// getValue() of RatingComponent returns "0" for floats 0, 0.0; "0.<d>" for .<d>
// and "1" for float 1.0
// convert to float and compare to avoid false "Save Changes" prompt
bool ok;
if (to_float(gamelistVal, ok) != to_float(editorVal, ok))
return true;
}
else
{
// string compare
if (gamelistVal != editorVal)
return true;
}
}
}
return false;
}
float GuiMetaDataEd::to_float(const std::string& str, bool& ok)
{
errno = 0;
char* end = nullptr;
float f = std::strtof(str.c_str(), &end);
ok = !str.empty() && !*end && errno == 0;
if (!ok)
LOG(LogWarning) << "Conversion of input string '" << str << "' to float failed or is incomplete. Return value: " << f;
return f;
}
bool GuiMetaDataEd::input(InputConfig* config, Input input)
{
if(GuiComponent::input(config, input))

View file

@ -8,6 +8,7 @@
#include "GuiComponent.h"
#include "MetaData.h"
class ComponentList;
class TextComponent;
@ -26,6 +27,8 @@ private:
void fetch();
void fetchDone(const ScraperSearchResult& result);
void close(bool closeAllWindows);
bool hasChanges();
float to_float(const std::string& str, bool& ok);
NinePatchComponent mBackground;
ComponentGrid mGrid;

View file

@ -0,0 +1,228 @@
#include "guis/GuiRandomCollectionOptions.h"
#include "GuiRandomCollectionOptions.h"
#include "components/OptionListComponent.h"
#include "components/SwitchComponent.h"
#include "guis/GuiSettings.h"
#include "guis/GuiTextEditPopup.h"
#include "utils/StringUtil.h"
#include "views/ViewController.h"
#include "CollectionSystemManager.h"
#include "SystemData.h"
#include "Window.h"
GuiRandomCollectionOptions::GuiRandomCollectionOptions(Window* window) : GuiComponent(window), mMenu(window, "RANDOM COLLECTION")
{
customCollectionLists.clear();
autoCollectionLists.clear();
systemLists.clear();
mNeedsCollectionRefresh = false;
initializeMenu();
}
void GuiRandomCollectionOptions::initializeMenu()
{
// get collections
addEntry("INCLUDE SYSTEMS", 0x777777FF, true, [this] { selectSystems(); });
addEntry("INCLUDE AUTO COLLECTIONS", 0x777777FF, true, [this] { selectAutoCollections(); });
addEntry("INCLUDE CUSTOM COLLECTIONS", 0x777777FF, true, [this] { selectCustomCollections(); });
// Add option to exclude games from a collection
exclusionCollection = std::make_shared< OptionListComponent<std::string> >(mWindow, "EXCLUDE GAMES FROM", false);
// Add default option
exclusionCollection->add("<NONE>", "", Settings::getInstance()->getString("RandomCollectionExclusionCollection") == "");
std::map<std::string, CollectionSystemData> customSystems = CollectionSystemManager::get()->getCustomCollectionSystems();
// add all enabled Custom Systems
for(std::map<std::string, CollectionSystemData>::const_iterator it = customSystems.cbegin() ; it != customSystems.cend() ; it++ )
{
exclusionCollection->add(it->second.decl.longName, it->second.decl.name, Settings::getInstance()->getString("RandomCollectionExclusionCollection") == it->second.decl.name);
}
mMenu.addWithLabel("EXCLUDE GAMES FROM", exclusionCollection);
// Add option to trim random collection items
trimRandom = std::make_shared<NumberList>(mWindow, "MAX GAMES", false);
// Add default entry
int maxGames = Settings::getInstance()->getInt("RandomCollectionMaxGames");
trimRandom->add("ALL", 0, maxGames == 0);
// add limit values for size of random collection
for(int i = 5; i <= 50; i = i+5)
{
trimRandom->add(std::to_string(i), i, maxGames == i);
}
mMenu.addWithLabel("MAX GAMES", trimRandom);
addChild(&mMenu);
mMenu.addButton("OK", "ok", std::bind(&GuiRandomCollectionOptions::saveSettings, this));
mMenu.addButton("CANCEL", "cancel", [&] { delete this; });
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
}
void GuiRandomCollectionOptions::addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func)
{
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);
// populate the list
ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, name, font, color), true);
if(add_arrow)
{
std::shared_ptr<ImageComponent> bracket = makeArrow(mWindow);
row.addElement(bracket, false);
}
row.makeAcceptInputHandler(func);
mMenu.addRow(row);
}
void GuiRandomCollectionOptions::selectSystems()
{
std::map<std::string, CollectionSystemData> systems;
for(auto &sys : SystemData::sSystemVector)
{
// we won't iterate all collections
if (sys->isGameSystem() && !sys->isCollection())
{
CollectionSystemDecl sysDecl;
sysDecl.name = sys->getName();
sysDecl.longName = sys->getFullName();
CollectionSystemData newCollectionData;
newCollectionData.system = sys;
newCollectionData.decl = sysDecl;
newCollectionData.isEnabled = true;
systems[sysDecl.name] = newCollectionData;
}
}
selectEntries(systems, "RandomCollectionSystems", DEFAULT_RANDOM_SYSTEM_GAMES, &systemLists);
}
void GuiRandomCollectionOptions::selectAutoCollections()
{
selectEntries(CollectionSystemManager::get()->getAutoCollectionSystems(), "RandomCollectionSystemsAuto", DEFAULT_RANDOM_COLLECTIONS_GAMES, &autoCollectionLists);
}
void GuiRandomCollectionOptions::selectCustomCollections()
{
selectEntries(CollectionSystemManager::get()->getCustomCollectionSystems(), "RandomCollectionSystemsCustom", DEFAULT_RANDOM_COLLECTIONS_GAMES, &customCollectionLists);
}
GuiRandomCollectionOptions::~GuiRandomCollectionOptions()
{
}
void GuiRandomCollectionOptions::selectEntries(std::map<std::string, CollectionSystemData> collection, std::string settingsLabel, int defaultValue, std::vector< SystemGames>* results) {
auto s = new GuiSettings(mWindow, "INCLUDE GAMES FROM");
std::map<std::string, int> initValues = Settings::getInstance()->getMap(settingsLabel);
results->clear();
for(auto &c : collection)
{
CollectionSystemData csd = c.second;
if (csd.system != CollectionSystemManager::get()->getRandomCollection())
{
ComponentListRow row;
std::string label = csd.decl.longName;
int selectedValue = defaultValue;
if (initValues.find(label) != initValues.end())
{
int maxForSys = initValues[label];
// we won't add more than the max and less than 0
selectedValue = Math::max(Math::min(RANDOM_SYSTEM_MAX, maxForSys), 0);
mNeedsCollectionRefresh |= selectedValue != maxForSys; // force overwrite of outlier in settings
}
initValues[label] = selectedValue;
std::shared_ptr<NumberList> colItems = std::make_shared<NumberList>(mWindow, label, false);
for (int i = 0; i <= RANDOM_SYSTEM_MAX; i++)
{
colItems->add(std::to_string(i), i, i == selectedValue);
}
row.addElement(std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(label), Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(colItems, false);
s->addRow(row);
SystemGames sys;
sys.name = label;
sys.gamesSelection = colItems;
results->push_back(sys);
}
}
setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
s->setPosition((mSize.x() - s->getSize().x()) / 2, (mSize.y() - s->getSize().y()) / 2);
s->addSaveFunc([this, settingsLabel, initValues, results] { applyGroupSettings(settingsLabel, initValues, results); });
mWindow->pushGui(s);
}
void GuiRandomCollectionOptions::applyGroupSettings(std::string settingsLabel, const std::map<std::string, int> &initialValues, std::vector<SystemGames> *results)
{
std::map<std::string, int> currentValues;
for (auto it = results->begin(); it != results->end(); ++it)
{
currentValues[(*it).name] = (*it).gamesSelection->getSelected();
}
if (currentValues != initialValues)
{
mNeedsCollectionRefresh = true;
Settings::getInstance()->setMap(settingsLabel, currentValues);
}
}
void GuiRandomCollectionOptions::saveSettings()
{
int curTrim = trimRandom->getSelected();
int prevTrim = Settings::getInstance()->getInt("RandomCollectionMaxGames");
Settings::getInstance()->setInt("RandomCollectionMaxGames", curTrim);
std::string curExclusion = exclusionCollection->getSelected();
std::string prevExclusion = Settings::getInstance()->getString("RandomCollectionExclusionCollection");
Settings::getInstance()->setString("RandomCollectionExclusionCollection", curExclusion);
mNeedsCollectionRefresh |= (curTrim != prevTrim || curExclusion != prevExclusion);
if (mNeedsCollectionRefresh)
{
Settings::getInstance()->saveFile();
CollectionSystemManager::get()->recreateCollection(CollectionSystemManager::get()->getRandomCollection());
}
delete this;
}
bool GuiRandomCollectionOptions::input(InputConfig* config, Input input)
{
bool consumed = GuiComponent::input(config, input);
if(consumed)
return true;
if(config->isMappedTo("b", input) && input.value != 0)
saveSettings();
return false;
}
std::vector<HelpPrompt> GuiRandomCollectionOptions::getHelpPrompts()
{
std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
prompts.push_back(HelpPrompt("b", "back"));
return prompts;
}

View file

@ -0,0 +1,54 @@
#pragma once
#ifndef ES_APP_GUIS_GUI_RANDOM_COLLECTION_OPTIONS_H
#define ES_APP_GUIS_GUI_RANDOM_COLLECTION_OPTIONS_H
#include "components/MenuComponent.h"
template<typename T>
class OptionListComponent;
class SwitchComponent;
class SystemData;
class GuiSettings;
struct CollectionSystemData;
typedef OptionListComponent<int> NumberList;
struct SystemGames
{
std::string name;
std::shared_ptr<NumberList> gamesSelection;
};
class GuiRandomCollectionOptions : public GuiComponent
{
public:
GuiRandomCollectionOptions(Window* window);
~GuiRandomCollectionOptions();
bool input(InputConfig* config, Input input) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
private:
void initializeMenu();
void saveSettings();
void applyGroupSettings(std::string settingsLabel, const std::map<std::string, int> &initialValues, std::vector<SystemGames>* results);
void addSystemsToMenu();
void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func);
void selectEntries(std::map<std::string, CollectionSystemData> collection, std::string settingsLabel, int defaultValue, std::vector< SystemGames>* results);
void selectSystems();
void selectAutoCollections();
void selectCustomCollections();
bool mNeedsCollectionRefresh;
std::vector< SystemGames> customCollectionLists;
std::vector< SystemGames> autoCollectionLists;
std::vector< SystemGames> systemLists;
std::shared_ptr< NumberList> trimRandom;
std::shared_ptr< OptionListComponent<std::string> > exclusionCollection;
MenuComponent mMenu;
SystemData* mSystem;
};
#endif // ES_APP_GUIS_GUI_RANDOM_COLLECTION_OPTIONS_H

View file

@ -66,6 +66,8 @@ GuiSlideshowScreensaverOptions::GuiSlideshowScreensaverOptions(Window* window, c
// custom video filter
auto sss_video_filter = std::make_shared<TextComponent>(mWindow, "", Font::get(FONT_SIZE_SMALL), 0x777777FF);
// set y-size >0 on last TextComponent in menu to assure proper fit into available row height
sss_video_filter->setSize(Vector2f(0, Font::get(FONT_SIZE_SMALL)->getLetterHeight()));
addEditableTextComponent(row, "CUSTOM VIDEO FILTER", sss_video_filter, Settings::getInstance()->getString("SlideshowScreenSaverVideoFilter"));
addSaveFunc([sss_video_filter] {
Settings::getInstance()->setString("SlideshowScreenSaverVideoFilter", sss_video_filter->getValue());

View file

@ -52,7 +52,18 @@ bool parseArgs(int argc, char* argv[])
for(int i = 1; i < argc; i++)
{
if(strcmp(argv[i], "--resolution") == 0)
if(strcmp(argv[i], "--monitor") == 0)
{
if (i >= argc - 1)
{
std::cerr << "Invalid monitor supplied.";
return false;
}
int monitor = atoi(argv[i + 1]);
i++; // skip the argument value
Settings::getInstance()->setInt("MonitorID", monitor);
}else if(strcmp(argv[i], "--resolution") == 0)
{
if(i >= argc - 2)
{
@ -180,7 +191,9 @@ bool parseArgs(int argc, char* argv[])
"--screensize WIDTH HEIGHT for a canvas smaller than the full resolution,\n"
" or if rotating into portrait mode\n"
"--screenoffset X Y move the canvas by x,y pixels\n"
"--fullscreen-borderless borderless fullscreen window\n"
"--windowed not fullscreen, should be used with --resolution\n"
"--monitor N monitor index (0-)\n"
"\nGame and settings visibility in ES and behaviour of ES:\n"
"--force-disable-filters force the UI to ignore applied filters on\n"
" gamelist (p)\n"

View file

@ -10,7 +10,7 @@
#include "Settings.h"
#include "SystemData.h"
#include "utils/TimeUtil.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
/* When raspbian will get an up to date version of rapidjson we'll be
able to have it throw in case of error with the following:
@ -109,6 +109,7 @@ const std::map<PlatformId, std::string> gamesdb_new_platformid_map{
{ TRS80_COLOR_COMPUTER, "4941" },
{ TI_99, "4953" },
{ TANDY, "4941" },
{ FMTOWNS, "4932" },
};
void thegamesdb_generate_json_scraper_requests(const ScraperSearchParams& params,

View file

@ -7,7 +7,7 @@
#include "PlatformId.h"
#include "Settings.h"
#include "SystemData.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
#include <cstring>
using namespace PlatformIds;
@ -100,7 +100,8 @@ const std::map<PlatformId, unsigned short> screenscraper_platformid_map{
{ TANDY, 144 },
{ TI_99, 205 },
{ DRAGON32, 91 },
{ ZMACHINE, 21 }
{ ZMACHINE, 21 },
{ FMTOWNS, 253 }
};
@ -184,7 +185,7 @@ void ScreenScraperRequest::process(const std::unique_ptr<HttpReq>& req, std::vec
assert(req->status() == HttpReq::REQ_SUCCESS);
pugi::xml_document doc;
pugi::xml_parse_result parseResult = doc.load(req->getContent().c_str());
pugi::xml_parse_result parseResult = doc.load_string(req->getContent().c_str());
if (!parseResult)
{

View file

@ -54,7 +54,7 @@ void ViewController::goToStart()
if ((*it)->getName() == requestedSystem)
{
goToGameList(*it);
Scripting::fireEvent("system-select", requestedSystem, "requestedsystem");
Scripting::fireEvent("system-select", requestedSystem, "requestedsystem");
FileData* cursor = getGameListView(*it)->getCursor();
if (cursor != NULL)
{
@ -193,11 +193,50 @@ void ViewController::playViewTransition()
}else{
advanceAnimation(0, (int)(mFadeOpacity * FADE_DURATION));
}
} else if (transition_style == "slide"){
}
else if (transition_style == "slide")
{
// slide or simple slide
setAnimation(new MoveCameraAnimation(mCamera, target));
bool inGamelistNav = -mCamera.translation().y() == target.y() // not in/out gamelist nav
&& -mCamera.translation().x() - target.x(); // left/right movement
cancelAnimation(0);
Vector3f tgt = Vector3f(target);
Vector3f positionOrig;
if (inGamelistNav) {
const float screenWidth = (float)Renderer::getScreenWidth();
if (-mCamera.translation().x() - tgt.x() >= 2 * screenWidth)
{
// right rollover
mLockInput = true;
tgt.x() = screenWidth * mGameListViews.size();
}
else if (-mCamera.translation().x() - tgt.x() <= 2 * -screenWidth)
{
// left rollover
mLockInput = true;
tgt.x() = -screenWidth;
}
// deny any further input on rollover as mCurrentView would be
// different on subsequent animations, resulting in restoring
// a unrelated mCurrentView/mCamera with the original position
if (mLockInput)
{
positionOrig = Vector3f(mCurrentView->getPosition());
mCurrentView->setPosition(tgt.x(), tgt.y());
}
}
setAnimation(new MoveCameraAnimation(mCamera, tgt), 0, [this, positionOrig] {
if (mLockInput) {
mCurrentView->setPosition(positionOrig);
mCamera.translation() = -positionOrig;
}
mLockInput = false;
});
updateHelpPrompts(); // update help prompts immediately
} else {
}
else
{
// instant
setAnimation(new LambdaAnimation(
[this, target](float /*t*/)
@ -245,10 +284,12 @@ void ViewController::launch(FileData* game, Vector3f center)
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc]
{
game->launchGame(mWindow);
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this] { mLockInput = false; }, true);
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game] { mLockInput = false; }, true);
this->onFileChanged(game, FILE_METADATA_CHANGED);
if (mCurrentView)
if (mCurrentView) {
this->getGameListView(game->getSystem())->setCursor(game, true);
mCurrentView->onShow();
}
});
} else if (transition_style == "slide"){
// move camera to zoom in on center + fade out, launch game, come back in
@ -256,20 +297,24 @@ void ViewController::launch(FileData* game, Vector3f center)
{
game->launchGame(mWindow);
mCamera = origCamera;
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, [this] { mLockInput = false; }, true);
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, [this, game] { mLockInput = false; }, true);
this->onFileChanged(game, FILE_METADATA_CHANGED);
if (mCurrentView)
if (mCurrentView) {
this->getGameListView(game->getSystem())->setCursor(game, true);
mCurrentView->onShow();
}
});
} else { // instant
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, origCamera, center, game]
{
game->launchGame(mWindow);
mCamera = origCamera;
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this] { mLockInput = false; }, true);
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, game] { mLockInput = false; }, true);
this->onFileChanged(game, FILE_METADATA_CHANGED);
if (mCurrentView)
if (mCurrentView) {
this->getGameListView(game->getSystem())->setCursor(game, true);
mCurrentView->onShow();
}
});
}
}
@ -285,6 +330,24 @@ void ViewController::removeGameListView(SystemData* system)
}
}
ViewController::GameListViewType ViewController::getGameListViewType()
{
//decide type
GameListViewType selectedViewType = AUTOMATIC;
std::string viewPreference = Settings::getInstance()->getString("GamelistViewStyle");
if (viewPreference.compare("basic") == 0)
selectedViewType = BASIC;
if (viewPreference.compare("detailed") == 0)
selectedViewType = DETAILED;
if (viewPreference.compare("grid") == 0)
selectedViewType = GRID;
if (viewPreference.compare("video") == 0)
selectedViewType = VIDEO;
return selectedViewType;
}
std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* system)
{
//if we already made one, return that one
@ -299,17 +362,7 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
bool themeHasVideoView = system->getTheme()->hasView("video");
//decide type
GameListViewType selectedViewType = AUTOMATIC;
std::string viewPreference = Settings::getInstance()->getString("GamelistViewStyle");
if (viewPreference.compare("basic") == 0)
selectedViewType = BASIC;
if (viewPreference.compare("detailed") == 0)
selectedViewType = DETAILED;
if (viewPreference.compare("grid") == 0)
selectedViewType = GRID;
if (viewPreference.compare("video") == 0)
selectedViewType = VIDEO;
GameListViewType selectedViewType = getGameListViewType();
if (selectedViewType == AUTOMATIC)
{
@ -477,6 +530,7 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
bool isCurrent = (mCurrentView == it->second);
SystemData* system = it->first;
FileData* cursor = view->getCursor();
int viewportTop = view->getViewportTop();
mGameListViews.erase(it);
if(reloadTheme)
@ -487,6 +541,7 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
// to counter having come from a placeholder
if (!cursor->isPlaceHolder()) {
newView->setCursor(cursor);
newView->setViewportTop(viewportTop);
}
if(isCurrent)
mCurrentView = newView;
@ -500,17 +555,18 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
}
void ViewController::reloadAll()
void ViewController::reloadAll(bool themeChanged)
{
// clear all gamelistviews
std::map<SystemData*, FileData*> cursorMap;
std::map<SystemData*, int> viewportTopMap;
for(auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++)
{
cursorMap[it->first] = it->second->getCursor();
viewportTopMap[it->first] = it->second->getViewportTop();
}
mGameListViews.clear();
// load themes, create gamelistviews and reset filters
for(auto it = cursorMap.cbegin(); it != cursorMap.cend(); it++)
{
@ -519,6 +575,15 @@ void ViewController::reloadAll()
getGameListView(it->first)->setCursor(it->second);
}
if(!themeChanged || !Settings::getInstance()->getBool("UseFullscreenPaging"))
{
// restore index of first list item on display
for(auto it = viewportTopMap.cbegin(); it != viewportTopMap.cend(); it++)
{
getGameListView(it->first)->setViewportTop(it->second);
}
}
// Rebuild SystemListView
mSystemListView.reset();
getSystemListView();

View file

@ -28,7 +28,7 @@ public:
// the current gamelist view (as it may change to be detailed).
void reloadGameListView(IGameListView* gamelist, bool reloadTheme = false);
inline void reloadGameListView(SystemData* system, bool reloadTheme = false) { reloadGameListView(getGameListView(system).get(), reloadTheme); }
void reloadAll(); // Reload everything with a theme. Used when the "ThemeSet" setting changes.
void reloadAll(bool themeChanged = false); // Reload everything with a theme. When the "ThemeSet" setting changes, themeChanged is true.
// Navigation.
void goToNextGameList();
@ -65,6 +65,8 @@ public:
VIDEO
};
ViewController::GameListViewType getGameListViewType();
struct State
{
ViewMode viewing;

View file

@ -61,12 +61,19 @@ FileData* BasicGameListView::getCursor()
return mList.getSelected();
}
void BasicGameListView::setCursor(FileData* cursor)
void BasicGameListView::setCursor(FileData* cursor, bool refreshListCursorPos)
{
if(!mList.setCursor(cursor) && (!cursor->isPlaceHolder()))
if (refreshListCursorPos)
setViewportTop(mList.REFRESH_LIST_CURSOR_POS);
bool notInList = !mList.setCursor(cursor);
if(!refreshListCursorPos && notInList && !cursor->isPlaceHolder())
{
populateList(cursor->getParent()->getChildrenListToDisplay());
mList.setCursor(cursor);
// this extra call is needed iff a system has games organized in folders
// and the cursor is focusing a game in a folder
if (cursor->getParent()->getType() == FOLDER)
mList.setCursor(cursor);
// update our cursor stack in case our cursor just got set to some folder we weren't in before
if(mCursorStack.empty() || mCursorStack.top() != cursor->getParent())
@ -90,6 +97,17 @@ void BasicGameListView::setCursor(FileData* cursor)
}
}
void BasicGameListView::setViewportTop(int index)
{
mList.setViewportTop(index);
}
int BasicGameListView::getViewportTop()
{
return mList.getViewportTop();
}
void BasicGameListView::addPlaceholder()
{
// empty list - add a placeholder

View file

@ -11,12 +11,14 @@ 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 void onFileChanged(FileData* file, FileChangeType change) override;
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual FileData* getCursor() override;
virtual void setCursor(FileData* file) override;
virtual void setCursor(FileData* file, bool refreshListCursorPos = false) override;
virtual int getViewportTop() override;
virtual void setViewportTop(int index) override;
virtual const char* getName() const override { return "basic"; }

View file

@ -131,7 +131,7 @@ FileData* GridGameListView::getCursor()
return mGrid.getSelected();
}
void GridGameListView::setCursor(FileData* file)
void GridGameListView::setCursor(FileData* file, bool refreshListCursorPos)
{
if(!mGrid.setCursor(file) && (!file->isPlaceHolder()))
{

View file

@ -20,7 +20,9 @@ public:
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual FileData* getCursor() override;
virtual void setCursor(FileData*) override;
virtual void setCursor(FileData*, bool refreshListCursorPos = false) override;
virtual void setViewportTop(int index) override { ; }
virtual int getViewportTop() override { return -1; }
virtual bool input(InputConfig* config, Input input) override;

View file

@ -30,7 +30,12 @@ public:
inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; }
virtual FileData* getCursor() = 0;
virtual void setCursor(FileData*) = 0;
// if flag is true then the cursor position on the visible gamelist section on screen is recalculated
// used only in list based views and only to be set true when there is no previous navigation to a game
// see also: TextListComponent.REFRESH_LIST_CURSOR_POS for the use 'true' flag
virtual void setCursor(FileData*, bool refreshListCursorPos = false) = 0;
virtual int getViewportTop() = 0;
virtual void setViewportTop(int index) = 0;
virtual bool input(InputConfig* config, Input input) override;
virtual void remove(FileData* game, bool deleteFile, bool refreshView=true) = 0;

View file

@ -7,7 +7,6 @@
#include "Settings.h"
#include "Sound.h"
#include "SystemData.h"
#include <SDL_timer.h>
ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGameListView(window, root),
mHeaderText(window), mHeaderImage(window), mBackground(window)
@ -152,8 +151,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
{
if(mRoot->getSystem()->isGameSystem())
{
int presscount = getPressCountInDuration();
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor(), presscount))
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor()))
{
return true;
}
@ -171,15 +169,4 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
Scripting::fireEvent("game-select", "NULL", "NULL", "NULL", "input");
}
return IGameListView::input(config, input);
}
int ISimpleGameListView::getPressCountInDuration() {
Uint32 now = SDL_GetTicks();
if (now - firstPressMs < DOUBLE_PRESS_DETECTION_DURATION) {
return 2;
} else {
firstPressMs = now;
return 1;
}
}
}

View file

@ -17,18 +17,18 @@ public:
// Called when a new file is added, a file is removed, a file's metadata changes, or a file's children are sorted.
// NOTE: FILE_SORTED is only reported for the topmost FileData, where the sort started.
// Since sorts are recursive, that FileData's children probably changed too.
virtual void onFileChanged(FileData* file, FileChangeType change);
virtual void onFileChanged(FileData* file, FileChangeType change) override;
// Called whenever the theme changes.
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme);
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual FileData* getCursor() = 0;
virtual void setCursor(FileData*) = 0;
virtual FileData* getCursor() override = 0;
virtual void setCursor(FileData*, bool refreshListCursorPos = false) override = 0;
virtual int getViewportTop() override = 0;
virtual void setViewportTop(int index) override = 0;
virtual bool input(InputConfig* config, Input input) override;
virtual void launch(FileData* game) = 0;
static const int DOUBLE_PRESS_DETECTION_DURATION = 1500; // millis
virtual void launch(FileData* game) override = 0;
protected:
static const int DESCRIPTION_SCROLL_DELAY = 5 * 1000; // five secs
@ -47,7 +47,6 @@ protected:
private:
int getPressCountInDuration();
Uint32 firstPressMs = 0;
};
#endif // ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H

View file

@ -77,7 +77,13 @@ HttpReq::HttpReq(const std::string& url)
}
//set curl restrict redirect protocols
//starting with 7.85.0, CURLOPT_REDIR_PROTOCOLS is deprecated
// and CURLOPT_REDIR_PROTOCOLS_STR should be used instead
#if CURL_AT_LEAST_VERSION(7,85,0)
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
#else
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
#endif
if(err != CURLE_OK)
{
mStatus = REQ_IO_ERROR;

View file

@ -1,7 +1,8 @@
#include "InputConfig.h"
#include "Log.h"
#include <pugixml/src/pugixml.hpp>
#include "utils/StringUtil.h"
#include <pugixml.hpp>
//some util functions
std::string inputTypeToString(InputType type)
@ -39,19 +40,10 @@ InputType stringToInputType(const std::string& type)
}
std::string toLower(std::string str)
{
for(unsigned int i = 0; i < str.length(); i++)
{
str[i] = (char)tolower(str[i]);
}
return str;
}
//end util functions
InputConfig::InputConfig(int deviceId, const std::string& deviceName, const std::string& deviceGUID) : mDeviceId(deviceId), mDeviceName(deviceName), mDeviceGUID(deviceGUID)
{
mVendorId = 0;
mProductId = 0;
}
void InputConfig::clear()
@ -66,19 +58,19 @@ bool InputConfig::isConfigured()
void InputConfig::mapInput(const std::string& name, Input input)
{
mNameMap[toLower(name)] = input;
mNameMap[Utils::String::toLower(name)] = input;
}
void InputConfig::unmapInput(const std::string& name)
{
auto it = mNameMap.find(toLower(name));
auto it = mNameMap.find(Utils::String::toLower(name));
if(it != mNameMap.cend())
mNameMap.erase(it);
}
bool InputConfig::getInputByName(const std::string& name, Input* result)
{
auto it = mNameMap.find(toLower(name));
auto it = mNameMap.find(Utils::String::toLower(name));
if(it != mNameMap.cend())
{
*result = it->second;
@ -188,7 +180,7 @@ void InputConfig::loadFromXML(pugi::xml_node& node)
if(value == 0)
LOG(LogWarning) << "WARNING: InputConfig value is 0 for " << type << " " << id << "!\n";
mNameMap[toLower(name)] = Input(mDeviceId, typeEnum, id, value, true);
mNameMap[Utils::String::toLower(name)] = Input(mDeviceId, typeEnum, id, value, true);
}
}
@ -210,6 +202,11 @@ void InputConfig::writeToXML(pugi::xml_node& parent)
{
cfg.append_attribute("type") = "joystick";
cfg.append_attribute("deviceName") = mDeviceName.c_str();
if(mVendorId && mProductId)
{
cfg.append_attribute("vendorId") = mVendorId;
cfg.append_attribute("productId") = mProductId;
}
}
cfg.append_attribute("deviceGUID") = mDeviceGUID.c_str();

View file

@ -102,9 +102,14 @@ public:
void mapInput(const std::string& name, Input input);
void unmapInput(const std::string& name); // unmap all Inputs mapped to this name
inline int getDeviceId() const { return mDeviceId; };
inline int getDeviceId() const { return mDeviceId; }
inline const std::string& getDeviceName() { return mDeviceName; }
inline const std::string& getDeviceGUIDString() { return mDeviceGUID; }
inline const unsigned short getVendorId() { return mVendorId; }
inline const unsigned short getProductId() { return mProductId; }
inline void setVendorId(unsigned short vendorID) { mVendorId = vendorID; }
inline void setProductId(unsigned short productID) { mProductId = productID; }
//Returns true if Input is mapped to this name, false otherwise.
bool isMappedTo(const std::string& name, Input input);
@ -127,6 +132,9 @@ private:
const int mDeviceId;
const std::string mDeviceName;
const std::string mDeviceGUID;
unsigned short mVendorId;
unsigned short mProductId;
};
#endif // ES_CORE_INPUT_CONFIG_H

View file

@ -6,7 +6,7 @@
#include "platform.h"
#include "Scripting.h"
#include "Window.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
#include <SDL.h>
#include <iostream>
#include <assert.h>
@ -100,6 +100,11 @@ void InputManager::addJoystickByDeviceIndex(int id)
// create the InputConfig
mInputConfigs[joyId] = new InputConfig(joyId, SDL_JoystickName(joy), guid);
// add Vendor and Product IDs
mInputConfigs[joyId]->setVendorId(SDL_JoystickGetVendor(joy));
mInputConfigs[joyId]->setProductId(SDL_JoystickGetProduct(joy));
if(!loadInputConfig(mInputConfigs[joyId]))
{
LOG(LogInfo) << "Added unconfigured joystick '" << SDL_JoystickName(joy) << "' (GUID: " << guid << ", instance ID: " << joyId << ", device index: " << id << ").";

View file

@ -53,6 +53,7 @@ void Log::flush()
void Log::close()
{
if(file == NULL) return;
fclose(file);
file = NULL;
}

View file

@ -5,8 +5,8 @@
#include <sstream>
#define LOG(level) \
if(level > Log::getReportingLevel()) ; \
else Log().get(level)
if(level <= Log::getReportingLevel()) \
Log().get(level)
enum LogLevel { LogError, LogWarning, LogInfo, LogDebug };

View file

@ -3,7 +3,7 @@
#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "Log.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
#include <string.h>
MameNames* MameNames::sInstance = nullptr;

View file

@ -4,7 +4,7 @@
#include "Log.h"
#include "Scripting.h"
#include "platform.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
#include <algorithm>
#include <vector>
@ -13,27 +13,28 @@ Settings* Settings::sInstance = NULL;
// these values are NOT saved to es_settings.xml
// since they're set through command-line arguments, and not the in-program settings menu
std::vector<const char*> settings_dont_save {
{ "Debug" },
{ "DebugGrid" },
{ "DebugText" },
{ "DebugImage" },
{ "ForceKid" },
{ "ForceKiosk" },
{ "IgnoreGamelist" },
{ "HideConsole" },
{ "ShowExit" },
{ "ConfirmQuit" },
{ "SplashScreen" },
{ "VSync" },
{ "FullscreenBorderless" },
{ "Windowed" },
{ "WindowWidth" },
{ "WindowHeight" },
{ "ScreenWidth" },
{ "ScreenHeight" },
{ "ScreenOffsetX" },
{ "ScreenOffsetY" },
{ "ScreenRotate" }
"Debug",
"DebugGrid",
"DebugText",
"DebugImage",
"ForceKid",
"ForceKiosk",
"IgnoreGamelist",
"HideConsole",
"ShowExit",
"ConfirmQuit",
"SplashScreen",
"VSync",
"FullscreenBorderless",
"Windowed",
"WindowWidth",
"WindowHeight",
"ScreenWidth",
"ScreenHeight",
"ScreenOffsetX",
"ScreenOffsetY",
"ScreenRotate",
"MonitorID"
};
Settings::Settings()
@ -139,8 +140,17 @@ void Settings::setDefaults()
mStringMap["VlcScreenSaverResolution"] = "original";
// Audio out device for Video playback using OMX player.
mStringMap["OMXAudioDev"] = "both";
mIntMap["RandomCollectionMaxGames"] = 0; // 0 == no limit
std::map<std::string, int> m1;
mMapIntMap["RandomCollectionSystemsAuto"] = m1;
std::map<std::string, int> m2;
mMapIntMap["RandomCollectionSystemsCustom"] = m2;
std::map<std::string, int> m3;
mMapIntMap["RandomCollectionSystems"] = m3;
mStringMap["RandomCollectionExclusionCollection"] = "";
mStringMap["CollectionSystemsAuto"] = "";
mStringMap["CollectionSystemsCustom"] = "";
mStringMap["DefaultScreenSaverCollection"] = "";
mBoolMap["CollectionShowSystemInfo"] = true;
mBoolMap["SortAllSystems"] = false;
mBoolMap["UseCustomCollectionsSystem"] = true;
@ -169,6 +179,7 @@ void Settings::setDefaults()
mIntMap["ScreenOffsetX"] = 0;
mIntMap["ScreenOffsetY"] = 0;
mIntMap["ScreenRotate"] = 0;
mIntMap["MonitorID"] = 0;
mBoolMap["UseFullscreenPaging"] = false;
@ -212,6 +223,20 @@ void Settings::saveFile()
node.append_attribute("value").set_value(iter->second.c_str());
}
for(auto &m : mMapIntMap)
{
pugi::xml_node node = doc.append_child("map");
node.append_attribute("name").set_value(m.first.c_str());
std::string datatype = "int";
node.append_attribute("type").set_value(datatype.c_str());
for(auto &intMap : m.second) // intMap is a <string, int> map
{
pugi::xml_node entry = node.append_child(datatype.c_str());
entry.append_attribute("name").set_value(intMap.first.c_str());
entry.append_attribute("value").set_value(intMap.second);
}
}
doc.save_file(path.c_str());
Scripting::fireEvent("config-changed");
@ -242,9 +267,45 @@ void Settings::loadFile()
for(pugi::xml_node node = doc.child("string"); node; node = node.next_sibling("string"))
setString(node.attribute("name").as_string(), node.attribute("value").as_string());
for(pugi::xml_node node = doc.child("map"); node; node = node.next_sibling("map"))
{
std::string mapName = node.attribute("name").as_string();
std::string mapType = node.attribute("type").as_string();
if (mapType == "int") {
// only supporting int value maps currently
std::map<std::string, int> _map;
for(pugi::xml_node entry : node.children(mapType.c_str()))
{
_map[entry.attribute("name").as_string()] = entry.attribute("value").as_int();
}
setMap(mapName, _map);
} else {
LOG(LogWarning) << "Map: '" << mapName << "'. Unsupported data type '"<< mapType <<"'. Value ignored!";
}
}
processBackwardCompatibility();
}
void Settings::setMap(const std::string& key, const std::map<std::string, int>& map)
{
mMapIntMap[key] = map;
}
const std::map<std::string, int> Settings::getMap(const std::string& key)
{
if(mMapIntMap.find(key) == mMapIntMap.cend())
{
LOG(LogError) << "Tried to use undefined setting " << key << "!";
std::map<std::string, int> empty;
return empty;
}
return mMapIntMap[key];
}
template<typename Map>
void Settings::renameSetting(Map& map, std::string&& oldName, std::string&& newName)
{

View file

@ -20,11 +20,13 @@ public:
int getInt(const std::string& name);
float getFloat(const std::string& name);
const std::string& getString(const std::string& name);
const std::map<std::string, int> getMap(const std::string& name);
void setBool(const std::string& name, bool value);
void setInt(const std::string& name, int value);
void setFloat(const std::string& name, float value);
void setString(const std::string& name, const std::string& value);
void setMap(const std::string& name, const std::map<std::string, int>& map);
private:
static Settings* sInstance;
@ -40,6 +42,7 @@ private:
std::map<std::string, int> mIntMap;
std::map<std::string, float> mFloatMap;
std::map<std::string, std::string> mStringMap;
std::map<std::string, std::map<std::string, int>> mMapIntMap;
};
#endif // ES_CORE_SETTINGS_H

View file

@ -7,7 +7,7 @@
#include "Log.h"
#include "platform.h"
#include "Settings.h"
#include <pugixml/src/pugixml.hpp>
#include <pugixml.hpp>
#include <algorithm>
std::vector<std::string> ThemeData::sSupportedViews { { "system" }, { "basic" }, { "detailed" }, { "grid" }, { "video" } };

View file

@ -117,8 +117,9 @@ void Window::textInput(const char* text)
void Window::input(InputConfig* config, Input input)
{
if (mScreenSaver && mScreenSaver->isScreenSaverActive() && Settings::getInstance()->getBool("ScreenSaverControls")
&& inputDuringScreensaver(config, input))
&& mScreenSaver->inputDuringScreensaver(config, input))
{
mTimeSinceLastInput = 0;
return;
}
@ -131,7 +132,7 @@ void Window::input(InputConfig* config, Input input)
}
mTimeSinceLastInput = 0;
if (cancelScreenSaver())
if (input.value != 0 && cancelScreenSaver())
return;
bool dbg_keyboard_key_press = Settings::getInstance()->getBool("Debug") && config->getDeviceId() == DEVICE_KEYBOARD && input.value;
@ -156,37 +157,6 @@ void Window::input(InputConfig* config, Input input)
}
}
bool Window::inputDuringScreensaver(InputConfig* config, Input input)
{
bool input_consumed = false;
std::string screensaver_type = Settings::getInstance()->getString("ScreenSaverBehavior");
if (screensaver_type == "random video" || screensaver_type == "slideshow")
{
bool is_select_input = config->isMappedLike("right", input) || config->isMappedTo("select", input);
bool is_start_input = config->isMappedTo("start", input);
if (is_select_input)
{
if (input.value) {
mScreenSaver->nextMediaItem();
// user input resets sleep time counter
mTimeSinceLastInput = 0;
}
input_consumed = true;
}
else if (is_start_input)
{
bool slideshow_custom_images = Settings::getInstance()->getBool("SlideshowScreenSaverCustomMediaSource");
if (screensaver_type == "random video" || !slideshow_custom_images)
{
mScreenSaver->launchGame();
}
}
}
return input_consumed;
}
void Window::update(int deltaTime)
{
if(mNormalizeNextUpdate)
@ -271,7 +241,7 @@ void Window::render()
// or not because it may perform a fade on transition
renderScreenSaver();
if(!mRenderScreenSaver && mInfoPopup)
if(mInfoPopup)
{
mInfoPopup->render(transform);
}
@ -448,7 +418,7 @@ bool Window::isProcessing()
return count_if(mGuiStack.cbegin(), mGuiStack.cend(), [](GuiComponent* c) { return c->isProcessing(); }) > 0;
}
void Window::startScreenSaver()
void Window::startScreenSaver(SystemData* system)
{
if (mScreenSaver && !mRenderScreenSaver)
{
@ -457,7 +427,7 @@ void Window::startScreenSaver()
for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
(*i)->onScreenSaverActivate();
mScreenSaver->startScreenSaver();
mScreenSaver->startScreenSaver(system);
mRenderScreenSaver = true;
}
}

View file

@ -8,6 +8,7 @@
#include <memory>
class SystemData;
class FileData;
class Font;
class GuiComponent;
@ -23,15 +24,15 @@ class Window
public:
class ScreenSaver {
public:
virtual void startScreenSaver() = 0;
virtual void stopScreenSaver() = 0;
virtual void nextMediaItem() = 0;
virtual void startScreenSaver(SystemData* system=NULL) = 0;
virtual void stopScreenSaver(bool toResume=false) = 0;
virtual void renderScreenSaver() = 0;
virtual bool allowSleep() = 0;
virtual void update(int deltaTime) = 0;
virtual bool isScreenSaverActive() = 0;
virtual FileData* getCurrentGame() = 0;
virtual void launchGame() = 0;
virtual void selectGame(bool launch) = 0;
virtual bool inputDuringScreensaver(InputConfig* config, Input input) = 0;
};
class InfoPopup {
@ -72,14 +73,13 @@ public:
void setInfoPopup(InfoPopup* infoPopup) { delete mInfoPopup; mInfoPopup = infoPopup; }
inline void stopInfoPopup() { if (mInfoPopup) mInfoPopup->stop(); };
void startScreenSaver();
void startScreenSaver(SystemData* system=NULL);
bool cancelScreenSaver();
void renderScreenSaver();
private:
void onSleep();
void onWake();
bool inputDuringScreensaver(InputConfig* config, Input input);
// Returns true if at least one component on the stack is processing
bool isProcessing();

View file

@ -7,6 +7,7 @@ class Animation
public:
virtual int getDuration() const = 0;
virtual void apply(float t) = 0;
virtual ~Animation() = default;
};
#endif // ES_CORE_ANIMATIONS_ANIMATION_H

View file

@ -4,15 +4,17 @@
#include "Log.h"
#include "Settings.h"
#include <ctime>
DateTimeComponent::DateTimeComponent(Window* window) : TextComponent(window), mDisplayRelative(false)
{
setFormat("%m/%d/%Y");
setFormat(getDateformat());
}
DateTimeComponent::DateTimeComponent(Window* window, const std::string& text, const std::shared_ptr<Font>& font, unsigned int color, Alignment align,
Vector3f pos, Vector2f size, unsigned int bgcolor) : TextComponent(window, text, font, color, align, pos, size, bgcolor), mDisplayRelative(false)
{
setFormat("%m/%d/%Y");
setFormat(getDateformat());
}
void DateTimeComponent::setValue(const std::string& val)
@ -47,9 +49,13 @@ void DateTimeComponent::onTextChanged()
std::string DateTimeComponent::getDisplayString() const
{
if(std::difftime(mTime.getTime(), Utils::Time::BLANK_DATE) == 0.0) {
return "";
}
if (mDisplayRelative) {
//relative time
if(mTime.getTime() == 0)
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
return "never";
Utils::Time::DateTime now(Utils::Time::now());
@ -69,7 +75,7 @@ std::string DateTimeComponent::getDisplayString() const
return std::string(buf);
}
if(mTime.getTime() == 0)
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
return "unknown";
return Utils::Time::timeToString(mTime.getTime(), mFormat);

View file

@ -25,6 +25,9 @@ public:
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
static std::string getDateformat() { return "%m/%d/%Y"; }
static std::string getDateformatTip() { return "MM/DD/YYYY"; }
protected:
void onTextChanged() override;

View file

@ -1,5 +1,6 @@
#include "components/DateTimeEditComponent.h"
#include "DateTimeComponent.h"
#include "resources/Font.h"
#include "utils/StringUtil.h"
@ -196,17 +197,17 @@ std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const
switch(mode)
{
case DISP_DATE:
fmt = "%m/%d/%Y";
fmt = DateTimeComponent::getDateformat();
break;
case DISP_DATE_TIME:
if(mTime.getTime() == 0)
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
return "unknown";
fmt = "%m/%d/%Y %H:%M:%S";
fmt = DateTimeComponent::getDateformat() + " %H:%M:%S";
break;
case DISP_RELATIVE_TO_NOW:
{
//relative time
if(mTime.getTime() == 0)
if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME)
return "never";
Utils::Time::DateTime now(Utils::Time::now());

View file

@ -22,7 +22,7 @@ public:
GridTileComponent(Window* window);
void render(const Transform4x4f& parentTrans) override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties);
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
// Made this a static function because the ImageGridComponent need to know the default tile max size
// to calculate the grid dimension before it instantiate the GridTileComponents
@ -41,7 +41,7 @@ public:
Vector3f getBackgroundPosition();
virtual void update(int deltaTime);
virtual void update(int deltaTime) override;
std::shared_ptr<TextureResource> getTexture();

View file

@ -58,7 +58,10 @@ public:
};
protected:
struct Entry mEntry;
int mCursor;
int mViewportTop;
int mScrollTier;
int mScrollVelocity;
@ -81,6 +84,7 @@ public:
mGradient(window), mTierList(tierList), mLoopType(loopType)
{
mCursor = 0;
mViewportTop = 0;
mScrollTier = 0;
mScrollVelocity = 0;
mScrollTierAccumulator = 0;
@ -158,6 +162,16 @@ public:
return false;
}
void setViewportTop(int index)
{
mViewportTop = index;
}
int getViewportTop()
{
return mViewportTop;
}
// entry management
void add(const Entry& e)
{

View file

@ -40,7 +40,7 @@ protected:
using IList<ImageGridData, T>::getTransform;
using IList<ImageGridData, T>::mSize;
using IList<ImageGridData, T>::mCursor;
using IList<ImageGridData, T>::Entry;
using IList<ImageGridData, T>::mEntry;
using IList<ImageGridData, T>::mWindow;
public:
@ -305,7 +305,9 @@ void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
std::string path = elem->get<std::string>("gameImage");
if (!ResourceManager::getInstance()->fileExists(path))
{
LOG(LogWarning) << "Could not replace default game image, check path: " << path;
}
else
{
std::string oldDefaultGameTexture = mDefaultGameTexture;
@ -326,7 +328,9 @@ void ImageGridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
std::string path = elem->get<std::string>("folderImage");
if (!ResourceManager::getInstance()->fileExists(path))
{
LOG(LogWarning) << "Could not replace default folder image, check path: " << path;
}
else
{
std::string oldDefaultFolderTexture = mDefaultFolderTexture;

View file

@ -18,7 +18,7 @@ public:
bool getState() const;
void setState(bool state);
std::string getValue() const;
std::string getValue() const override;
void setValue(const std::string& statestring) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;

View file

@ -120,6 +120,9 @@ void TextComponent::render(const Transform4x4f& parentTrans)
case ALIGN_CENTER:
yOff = (getSize().y() - textSize.y()) / 2.0f;
break;
default:
LOG(LogError) << "Unknown vertical alignment: " << mVerticalAlignment;
break;
}
Vector3f off(0, yOff, 0);
@ -147,68 +150,73 @@ void TextComponent::render(const Transform4x4f& parentTrans)
case ALIGN_RIGHT:
Renderer::drawRect(mSize.x() - mTextCache->metrics.size.x(), 0.0f, mTextCache->metrics.size.x(), mTextCache->metrics.size.y(), 0x00000033, 0x00000033);
break;
default:
LOG(LogError) << "Unknown horizontal alignment: " << mHorizontalAlignment;
break;
}
}
mFont->renderTextCache(mTextCache.get());
}
}
void TextComponent::calculateExtent()
std::string TextComponent::calculateExtent(bool allow_wrapping)
{
std::string text = mUppercase ? Utils::String::toUpper(mText) : mText;
if(mAutoCalcExtent.x())
{
mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing);
}else{
if(mAutoCalcExtent.y())
{
mSize[1] = mFont->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText) : mText, getSize().x(), mLineSpacing).y();
mSize = mFont->sizeText(text, mLineSpacing);
}else if(mAutoCalcExtent.y() || allow_wrapping)
// usually a textcomponent wraps only when x > 0 and y == 0 in size (see TextComponent.h).
// The extra flag allow_wrapping does wrapping if an textcomponent has x > 0 and y > height of
// one line (calculated by fontsize and line spacing).
// Some themes rely on this wrap functionality while having an fixed y (y>0) in <size/>.
{
text = mFont->wrapText(text, getSize().x());
if (mAutoCalcExtent.y()) {
// only resize when y was 0 before
// otherwise leave y value as defined before (i.e. theme value)
mSize.y() = mFont->sizeText(text, mLineSpacing).y();
}
}
return text;
}
void TextComponent::onTextChanged()
{
calculateExtent();
if(!mFont || mText.empty())
{
mTextCache.reset();
return;
}
std::string text = mUppercase ? Utils::String::toUpper(mText) : mText;
std::shared_ptr<Font> f = mFont;
const bool isMultiline = (mSize.y() == 0 || mSize.y() > f->getHeight()*1.2f);
std::string text = calculateExtent(mSize.y() > f->getHeight(mLineSpacing));
const bool oneLiner = mSize.y() > 0 && mSize.y() <= f->getHeight(mLineSpacing);
bool addAbbrev = false;
if(!isMultiline)
if(oneLiner)
{
bool addAbbrev = false;
size_t newline = text.find('\n');
text = text.substr(0, newline); // single line of text - stop at the first newline since it'll mess everything up
addAbbrev = newline != std::string::npos;
}
Vector2f size = f->sizeText(text);
addAbbrev = newline != std::string::npos || size.x() > mSize.x();
Vector2f size = f->sizeText(text);
if(!isMultiline && mSize.x() && text.size() && (size.x() > mSize.x() || addAbbrev))
{
// abbreviate text
const std::string abbrev = "...";
Vector2f abbrevSize = f->sizeText(abbrev);
while(text.size() && size.x() + abbrevSize.x() > mSize.x())
if(addAbbrev)
{
size_t newSize = Utils::String::prevCursor(text, text.size());
text.erase(newSize, text.size() - newSize);
size = f->sizeText(text);
// abbreviate text
const std::string abbrev = "...";
Vector2f abbrevSize = f->sizeText(abbrev);
while(text.size() && size.x() + abbrevSize.x() > mSize.x())
{
size_t newSize = Utils::String::prevCursor(text, text.size());
text.erase(newSize, text.size() - newSize);
size = f->sizeText(text);
}
text.append(abbrev);
}
text.append(abbrev);
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(text, Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
}else{
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(f->wrapText(text, mSize.x()), Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
}
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(text, Vector2f(0, 0), (mColor >> 8 << 8) | mOpacity, mSize.x(), mHorizontalAlignment, mLineSpacing));
}
void TextComponent::onColorChanged()

View file

@ -49,7 +49,7 @@ protected:
std::shared_ptr<Font> mFont;
private:
void calculateExtent();
std::string calculateExtent(bool allow_wrapping);
void onColorChanged();

View file

@ -56,7 +56,7 @@ public:
virtual std::vector<HelpPrompt> getHelpPrompts() override;
virtual void update(int deltaTime);
virtual void update(int deltaTime) override;
// Resize the video to fit this size. If one axis is zero, scale that axis to maintain aspect ratio.
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing.

View file

@ -5,13 +5,16 @@
#include "utils/StringUtil.h"
#include "PowerSaver.h"
#include "Settings.h"
#ifdef WIN32
#include <basetsd.h>
#include <codecvt>
typedef SSIZE_T ssize_t;
#else
#include <unistd.h>
#endif
#include <vlc/vlc.h>
#include <SDL_mutex.h>
#ifdef WIN32
#include <codecvt>
#endif
libvlc_instance_t* VideoVlcComponent::mVLC = NULL;
// VLC prepares to render a video frame.
@ -220,11 +223,7 @@ void VideoVlcComponent::handleLooping()
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
if (state == libvlc_Ended)
{
if (!Settings::getInstance()->getBool("VideoAudio") ||
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
{
libvlc_audio_set_mute(mMediaPlayer, 1);
}
setMuteMode();
//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_set_media(mMediaPlayer, mMedia);
libvlc_media_player_play(mMediaPlayer);
@ -255,7 +254,9 @@ void VideoVlcComponent::startVideo()
{
unsigned track_count;
// Get the media metadata so we can find the aspect ratio
libvlc_media_parse(mMedia);
libvlc_media_parse_with_options(mMedia, libvlc_media_fetch_local, -1);
while (libvlc_media_get_parsed_status(mMedia) == 0)
;
libvlc_media_track_t** tracks;
track_count = libvlc_media_tracks_get(mMedia, &tracks);
for (unsigned track = 0; track < track_count; ++track)
@ -276,7 +277,7 @@ void VideoVlcComponent::startVideo()
{
std::string resolution = Settings::getInstance()->getString("VlcScreenSaverResolution");
if(resolution != "original") {
float scale = 1;
float scale = 1;
if (resolution == "low")
// 25% of screen resolution
scale = 0.25;
@ -299,17 +300,17 @@ void VideoVlcComponent::startVideo()
}
}
}
else
{
remove(getTitlePath().c_str());
}
PowerSaver::pause();
setupContext();
// Setup the media player
mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
if (!Settings::getInstance()->getBool("VideoAudio") ||
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
{
libvlc_audio_set_mute(mMediaPlayer, 1);
}
setMuteMode();
libvlc_media_player_play(mMediaPlayer);
libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display, (void*)&mContext);
@ -339,3 +340,11 @@ void VideoVlcComponent::stopVideo()
PowerSaver::resume();
}
}
void VideoVlcComponent::setMuteMode()
{
Settings *cfg = Settings::getInstance();
if (!cfg->getBool("VideoAudio") || (cfg->getBool("ScreenSaverVideoMute") && mScreensaverMode)) {
libvlc_media_add_option(mMedia, ":no-audio");
}
}

View file

@ -40,24 +40,25 @@ public:
// If both are non-zero, potentially break the aspect ratio. If both are zero, no resizing.
// Can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height);
void setResize(float width, float height) override;
// Resize the video to be as large as possible but fit within a box of this size.
// Can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
void setMaxSize(float width, float height);
void setMaxSize(float width, float height) override;
private:
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change.
void resize();
// Start the video Immediately
virtual void startVideo();
virtual void startVideo() override;
// Stop the video
virtual void stopVideo();
virtual void stopVideo() override;
// Handle looping the video. Must be called periodically
virtual void handleLooping();
virtual void handleLooping() override;
void setMuteMode();
void setupContext();
void freeContext();

View file

@ -342,27 +342,39 @@ bool GuiInputConfig::filterTrigger(Input input, InputConfig* config, int inputId
#if defined(__linux__)
// on Linux, some gamepads return both an analog axis and a digital button for the trigger;
// we want the analog axis only, so this function removes the button press event
bool isPlaystation = (
// match PlayStation joystick with 6 axes only
strstr(config->getDeviceName().c_str(), "PLAYSTATION") != NULL
|| strstr(config->getDeviceName().c_str(), "Sony Interactive") != NULL // Official dualshock 4
|| strstr(config->getDeviceName().c_str(), "PS3 Ga") != NULL
|| strstr(config->getDeviceName().c_str(), "PS(R) Ga") != NULL
// BigBen kid's PS3 gamepad 146b:0902, matched on SDL GUID because its name "Bigben Interactive Bigben Game Pad" may be too generic
|| strcmp(config->getDeviceGUIDString().c_str(), "030000006b1400000209000011010000") == 0
);
bool isAnbernic = (
strcmp(config->getDeviceGUIDString().c_str(), "03004ab1020500000913000010010000") == 0 // Anbernic RG P01 has same issue
);
if((
// match PlayStation joystick with 6 axes only
strstr(config->getDeviceName().c_str(), "PLAYSTATION") != NULL
|| strstr(config->getDeviceName().c_str(), "PS3 Ga") != NULL
|| strstr(config->getDeviceName().c_str(), "PS(R) Ga") != NULL
// BigBen kid's PS3 gamepad 146b:0902, matched on SDL GUID because its name "Bigben Interactive Bigben Game Pad" may be too generic
|| strcmp(config->getDeviceGUIDString().c_str(), "030000006b1400000209000011010000") == 0
) && InputManager::getInstance()->getAxisCountByDevice(config->getDeviceId()) == 6)
if((isPlaystation || isAnbernic)
&& InputManager::getInstance()->getAxisCountByDevice(config->getDeviceId()) == 6)
{
// digital triggers are unwanted
if(input.type == TYPE_BUTTON && (input.id == 6 || input.id == 7))
if((
(isPlaystation && (input.id == 6 || input.id == 7))
|| (isAnbernic && (input.id == 8 || input.id == 9))
) && input.type == TYPE_BUTTON)
{
mHoldingInput = false;
return true;
}
}
// ignore negative pole for axes 2/5 only when triggers are being configured
if(input.type == TYPE_AXIS && (input.id == 2 || input.id == 5))
bool genericTrigger = !isAnbernic && (input.id == 2 || input.id == 5);
bool anbernicTrigger = isAnbernic && (input.id == 4 || input.id == 5);
// ignore negative pole for axes only when triggers are being configured
if(input.type == TYPE_AXIS && (genericTrigger || anbernicTrigger))
{
if(strstr(GUI_INPUT_CONFIG_LIST[inputId].name, "Trigger") != NULL)
{
if(input.value == 1)

View file

@ -15,8 +15,8 @@ public:
GuiTextEditPopup(Window* window, const std::string& title, const std::string& initValue,
const std::function<void(const std::string&)>& okCallback, bool multiLine, const char* acceptBtnText = "OK");
bool input(InputConfig* config, Input input);
void onSizeChanged();
bool input(InputConfig* config, Input input) override;
void onSizeChanged() override;
std::vector<HelpPrompt> getHelpPrompts() override;
private:

View file

@ -85,5 +85,10 @@ void processQuitMode()
touch("/tmp/es-shutdown");
runShutdownCommand();
break;
default:
// No-op to prevent compiler warnings
// If we reach here, it is not a RESTART, REBOOT,
// or SHUTDOWN. Basically a normal exit.
break;
}
}

View file

@ -75,8 +75,14 @@ namespace Renderer
initialCursorState = (SDL_ShowCursor(0) != 0);
int displayIndex = Settings::getInstance()->getInt("MonitorID");
if(displayIndex < 0 || displayIndex >= SDL_GetNumVideoDisplays()){
displayIndex = 0;
}
SDL_DisplayMode dispMode;
SDL_GetDesktopDisplayMode(0, &dispMode);
SDL_GetDesktopDisplayMode(displayIndex, &dispMode);
windowWidth = Settings::getInstance()->getInt("WindowWidth") ? Settings::getInstance()->getInt("WindowWidth") : dispMode.w;
windowHeight = Settings::getInstance()->getInt("WindowHeight") ? Settings::getInstance()->getInt("WindowHeight") : dispMode.h;
screenWidth = Settings::getInstance()->getInt("ScreenWidth") ? Settings::getInstance()->getInt("ScreenWidth") : windowWidth;
@ -89,7 +95,7 @@ namespace Renderer
const unsigned int windowFlags = (Settings::getInstance()->getBool("Windowed") ? 0 : (Settings::getInstance()->getBool("FullscreenBorderless") ? SDL_WINDOW_BORDERLESS : SDL_WINDOW_FULLSCREEN)) | getWindowFlags();
if((sdlWindow = SDL_CreateWindow("EmulationStation", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, windowWidth, windowHeight, windowFlags)) == nullptr)
if((sdlWindow = SDL_CreateWindow("EmulationStation", SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), windowWidth, windowHeight, windowFlags)) == nullptr)
{
LOG(LogError) << "Error creating SDL window!\n\t" << SDL_GetError();
return false;

View file

@ -526,7 +526,11 @@ std::string Font::wrapText(std::string text, float maxWidth)
}
}
if(cursor == text.length()) // arrived at end of text.
if(cursor == text.length() && lineWidth <= maxWidth)
// arrived at end of text while being in bounds of textbox
// second clause is mandatory for short descriptions which coincidentially
// ending with cursor at text end but slightly overrunning the bounding box
// to be wrapped on the next line, thus have to hit the else branch
{
out += text;
text.erase();
@ -600,12 +604,12 @@ float Font::getNewlineStartOffset(const std::string& text, const unsigned int& c
return 0;
case ALIGN_CENTER:
{
unsigned int endChar = (unsigned int)text.find('\n', charStart);
size_t endChar = text.find('\n', charStart);
return (xLen - sizeText(text.substr(charStart, endChar != std::string::npos ? endChar - charStart : endChar)).x()) / 2.0f;
}
case ALIGN_RIGHT:
{
unsigned int endChar = (unsigned int)text.find('\n', charStart);
size_t endChar = text.find('\n', charStart);
return xLen - (sizeText(text.substr(charStart, endChar != std::string::npos ? endChar - charStart : endChar)).x());
}
default:

View file

@ -22,23 +22,23 @@ namespace Utils
else if((c & 0xE0) == 0xC0) // 110xxxxx, two byte character
{
// 110xxxxx 10xxxxxx
result = ((_string[_cursor++] & 0x1F) << 6) |
((_string[_cursor++] & 0x3F) );
result = (_string[_cursor++] & 0x1F) << 6;
result |= (_string[_cursor++] & 0x3F);
}
else if((c & 0xF0) == 0xE0) // 1110xxxx, three byte character
{
// 1110xxxx 10xxxxxx 10xxxxxx
result = ((_string[_cursor++] & 0x0F) << 12) |
((_string[_cursor++] & 0x3F) << 6) |
((_string[_cursor++] & 0x3F) );
result = (_string[_cursor++] & 0x0F) << 12;
result |= (_string[_cursor++] & 0x3F) << 6;
result |= (_string[_cursor++] & 0x3F);
}
else if((c & 0xF8) == 0xF0) // 11110xxx, four byte character
{
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
result = ((_string[_cursor++] & 0x07) << 18) |
((_string[_cursor++] & 0x3F) << 12) |
((_string[_cursor++] & 0x3F) << 6) |
((_string[_cursor++] & 0x3F) );
result = (_string[_cursor++] & 0x07) << 18;
result |= (_string[_cursor++] & 0x3F) << 12;
result |= (_string[_cursor++] & 0x3F) << 6;
result |= (_string[_cursor++] & 0x3F);
}
else
{

View file

@ -9,7 +9,13 @@ namespace Utils
{
namespace Time
{
static inline time_t blankDate() {
// 1970-01-02
tm timeStruct = { 0, 0, 0, 2, 0, 70, 0, 0, -1 };
return mktime(&timeStruct);
}
static int NOT_A_DATE_TIME = 0;
static time_t BLANK_DATE = blankDate();
class DateTime
{

View file

@ -2,4 +2,9 @@
# package managers are included with the project (in the 'external' folder)
add_subdirectory("nanosvg")
add_subdirectory("pugixml")
find_package(pugixml)
if(NOT pugixml_FOUND)
add_subdirectory("pugixml")
endif()

2
external/pugixml vendored

@ -1 +1 @@
Subproject commit d2deb420bc70369faa12785df2b5dd4d390e523d
Subproject commit a0e064336317c9347a91224112af9933598714e9

View file

@ -1,4 +1,4 @@
<!-- Generated on 2022-02-11, from MAME 0.240 (Arcade).dat, fbneo.dat, mame2003-plus.xml -->
<!-- Generated on 2025-03-10, from MAME 0.275 (arcade).dat, fbneo.dat, mame2003-plus.xml -->
<bios>3dobios</bios>
<bios>acpsx</bios>
<bios>airlbios</bios>
@ -9,6 +9,7 @@
<bios>ar_bios</bios>
<bios>aristmk5</bios>
<bios>aristmk6</bios>
<bios>aristmk7</bios>
<bios>atarisy1</bios>
<bios>awbios</bios>
<bios>bubsys</bios>
@ -23,6 +24,7 @@
<bios>coh1001l</bios>
<bios>coh1002e</bios>
<bios>coh1002m</bios>
<bios>coh1002t</bios>
<bios>coh1002v</bios>
<bios>coh3002c</bios>
<bios>coh3002t</bios>
@ -47,6 +49,7 @@
<bios>konamigx</bios>
<bios>konendev</bios>
<bios>kpython</bios>
<bios>kpython2</bios>
<bios>kviper</bios>
<bios>lindbios</bios>
<bios>mac2bios</bios>
@ -67,6 +70,7 @@
<bios>nss</bios>
<bios>pgm</bios>
<bios>playch10</bios>
<bios>pumpitup</bios>
<bios>recel</bios>
<bios>sammymdl</bios>
<bios>segasp</bios>

View file

@ -1,7 +1,8 @@
<!-- Generated on 2022-02-11, from MAME 0.240.dat -->
<!-- Generated on 2025-03-10, from MAME 0.275.dat -->
<device>22vp931</device>
<device>3c505</device>
<device>3xtwin</device>
<device>4dparprn</device>
<device>a1000kbd_de</device>
<device>a1000kbd_dk</device>
<device>a1000kbd_fr</device>
@ -36,8 +37,8 @@
<device>a2cffa02</device>
<device>a2cffa2</device>
<device>a2corvus</device>
<device>a2diskii</device>
<device>a2diskiing</device>
<device>a2excel9</device>
<device>a2focdrv</device>
<device>a2grappler</device>
<device>a2grapplerplus</device>
@ -52,11 +53,14 @@
<device>a2q68</device>
<device>a2q68plus</device>
<device>a2ramfac</device>
<device>a2romfp</device>
<device>a2romint</device>
<device>a2scsi</device>
<device>a2sd</device>
<device>a2sider1</device>
<device>a2sider2</device>
<device>a2ssc</device>
<device>a2superdrive</device>
<device>a2suprterm</device>
<device>a2surance</device>
<device>a2swyft</device>
@ -67,6 +71,7 @@
<device>a2ulttrm</device>
<device>a2uniprint</device>
<device>a2vidtrm</device>
<device>a2vistaa800</device>
<device>a2vtc1</device>
<device>a2vulcan</device>
<device>a2vulgld</device>
@ -109,7 +114,6 @@
<device>abc_db4107</device>
<device>abc_db4112</device>
<device>abc_fd2</device>
<device>abc_memcard</device>
<device>abc_slutprov</device>
<device>abc_ssa</device>
<device>abc_uni800</device>
@ -126,6 +130,7 @@
<device>adam_kb</device>
<device>adam_prn</device>
<device>adam_spi</device>
<device>adbmodem</device>
<device>agat7_flop</device>
<device>agat840k_hle</device>
<device>agat9_flop</device>
@ -137,8 +142,18 @@
<device>aha1542cp</device>
<device>aha1740</device>
<device>aha1742a</device>
<device>alice_mcx128</device>
<device>aha2940au</device>
<device>alto2_cpu</device>
<device>altos2_kbd</device>
<device>amiga_a2091</device>
<device>amiga_a570</device>
<device>amiga_a590</device>
<device>amiga_ar1</device>
<device>amiga_ar2</device>
<device>amiga_ar3</device>
<device>amiga_buddha</device>
<device>amiga_oktagon2008</device>
<device>amiga_ripple</device>
<device>ampex230_kbd</device>
<device>ap2000</device>
<device>aplcd150</device>
@ -153,26 +168,42 @@
<device>arc_bbcio_we</device>
<device>arc_eaglem2</device>
<device>arc_ether1_aka25</device>
<device>arc_ether2_aeh50</device>
<device>arc_ether3_aeh54</device>
<device>arc_ethera</device>
<device>arc_etherd</device>
<device>arc_etherr</device>
<device>arc_faxpack</device>
<device>arc_greyhawk</device>
<device>arc_hdisc_akd52</device>
<device>arc_hdisc_cw</device>
<device>arc_hdisc_morley</device>
<device>arc_hdisc_we</device>
<device>arc_ide_be</device>
<device>arc_ide_rdev</device>
<device>arc_iomidi_aka15</device>
<device>arc_lark</device>
<device>arc_lbp4</device>
<device>arc_midi2</device>
<device>arc_midi_aka16</device>
<device>arc_midimax</device>
<device>arc_midimax2</device>
<device>arc_nexus_a500</device>
<device>arc_rom_aka05</device>
<device>arc_rom_r225</device>
<device>arc_rs423</device>
<device>arc_scan256</device>
<device>arc_scanjunior</device>
<device>arc_scanjunior3</device>
<device>arc_scanlight</device>
<device>arc_scanvideo</device>
<device>arc_scsi_a500</device>
<device>arc_scsi_aka31</device>
<device>arc_scsi_aka32</device>
<device>arc_scsi_cumana</device>
<device>arc_scsi_ling</device>
<device>arc_scsi_morley</device>
<device>arc_scsi_oak</device>
<device>arc_scsi_vti</device>
<device>arc_serial</device>
<device>arc_spectra</device>
@ -188,7 +219,11 @@
<device>atari810</device>
<device>atom_discpack</device>
<device>ax208</device>
<device>ax208p</device>
<device>banshee_x86</device>
<device>basf7100_kbd</device>
<device>bbc_24bbc</device>
<device>bbc_2ndserial</device>
<device>bbc_acorn1770</device>
<device>bbc_acorn8271</device>
<device>bbc_ams3</device>
@ -200,6 +235,7 @@
<device>bbc_bitstik2</device>
<device>bbc_cc500</device>
<device>bbc_chameleon</device>
<device>bbc_cisco</device>
<device>bbc_cumana1</device>
<device>bbc_cumana2</device>
<device>bbc_cumana68k</device>
@ -208,6 +244,7 @@
<device>bbc_ieee488</device>
<device>bbc_integrab</device>
<device>bbc_kenda</device>
<device>bbc_memexb20</device>
<device>bbc_mertec</device>
<device>bbc_morleyaa</device>
<device>bbc_multiform</device>
@ -218,6 +255,7 @@
<device>bbc_opus8272</device>
<device>bbc_opusa</device>
<device>bbc_pdram</device>
<device>bbc_pms64k</device>
<device>bbc_ramdisc</device>
<device>bbc_raven20</device>
<device>bbc_stl1770_1</device>
@ -233,14 +271,18 @@
<device>bbc_tube_32016</device>
<device>bbc_tube_32016l</device>
<device>bbc_tube_6502</device>
<device>bbc_tube_6502e</device>
<device>bbc_tube_6502p</device>
<device>bbc_tube_65c102</device>
<device>bbc_tube_80186</device>
<device>bbc_tube_80286</device>
<device>bbc_tube_a500</device>
<device>bbc_tube_a500d</device>
<device>bbc_tube_arm</device>
<device>bbc_tube_arm7</device>
<device>bbc_tube_casper</device>
<device>bbc_tube_cms6809</device>
<device>bbc_tube_matchbox</device>
<device>bbc_tube_pcplus</device>
<device>bbc_tube_rc6502</device>
<device>bbc_tube_rc65816</device>
@ -258,6 +300,7 @@
<device>bbc_weddb3</device>
<device>betadisk</device>
<device>bingoct</device>
<device>bk_kmd</device>
<device>bluechip</device>
<device>bml3kanji</device>
<device>bml3mp1802</device>
@ -295,6 +338,7 @@
<device>c64_nl10</device>
<device>c64_supercpu</device>
<device>c64_xl80</device>
<device>c64_z80videopak</device>
<device>c8050</device>
<device>c8050fdc</device>
<device>c8250</device>
@ -319,6 +363,9 @@
<device>cdu415</device>
<device>cdu561_25</device>
<device>cdu75s</device>
<device>centennial_sl01m_15_11194</device>
<device>centennial_sl02m_15_11194</device>
<device>centennial_sl04m_15_11194</device>
<device>cffa1</device>
<device>cfp1080s</device>
<device>cga</device>
@ -335,20 +382,20 @@
<device>cit101e_kbd</device>
<device>cit220p_kbd</device>
<device>clgd542x</device>
<device>clgd5446_pci</device>
<device>clgd5465_laguna</device>
<device>cmdhd</device>
<device>cmdrc2</device>
<device>cmi_ankbd</device>
<device>cmi_mkbd</device>
<device>cms_4080term</device>
<device>cmsscsi</device>
<device>coco2_hdb1</device>
<device>coco3_hdb1</device>
<device>coco_dcmodem</device>
<device>coco_fdc</device>
<device>coco_fdc_v11</device>
<device>coco_orch90</device>
<device>coco_psg</device>
<device>coco_rs232</device>
<device>coco_scii</device>
<device>coco_ssc</device>
<device>coco_t4426</device>
<device>coco_wpk</device>
@ -362,6 +409,8 @@
<device>comx_pl80</device>
<device>comx_prn</device>
<device>comx_thm</device>
<device>concept_kbd</device>
<device>cp2024</device>
<device>cp31</device>
<device>cp450_fdc</device>
<device>cpc_brunword4</device>
@ -374,6 +423,7 @@
<device>cpc_smartwatch</device>
<device>cpc_ssa1</device>
<device>cpc_transtape</device>
<device>cpuap</device>
<device>cq90_028</device>
<device>crd254sh</device>
<device>crt9028_000</device>
@ -381,24 +431,32 @@
<device>crvfdc02</device>
<device>csd1</device>
<device>cuda</device>
<device>cuda302</device>
<device>cudalite</device>
<device>cv8lc</device>
<device>cw7501</device>
<device>d2fdc</device>
<device>d9060</device>
<device>d9090</device>
<device>db50xg</device>
<device>db60xg</device>
<device>dc320b</device>
<device>dc320e</device>
<device>dc820</device>
<device>dc820b</device>
<device>dectalk_isa</device>
<device>dg640</device>
<device>digilog_kbd</device>
<device>dim68k_kbd</device>
<device>dio98543</device>
<device>dio98544</device>
<device>dio98550</device>
<device>dio98603a</device>
<device>dio98603b</device>
<device>dio98628</device>
<device>dio98629</device>
<device>diskii13</device>
<device>dm_clgd5430</device>
<device>dms3d2kp</device>
<device>dmv_k220</device>
<device>dmv_k230</device>
<device>dmv_k231</device>
@ -427,6 +485,8 @@
<device>eispc_kb</device>
<device>electron_ap1</device>
<device>electron_ap6</device>
<device>electron_elksd128</device>
<device>electron_elksd64</device>
<device>electron_m2105</device>
<device>electron_mc68k</device>
<device>electron_mode7</device>
@ -435,58 +495,79 @@
<device>electron_pwrjoy</device>
<device>electron_romboxp</device>
<device>electron_sidewndr</device>
<device>electron_voxbox</device>
<device>enh2000</device>
<device>enp10</device>
<device>ep64_exdos</device>
<device>epson_pf10</device>
<device>epson_qx_option_multifont</device>
<device>epson_rx80</device>
<device>epson_tf20</device>
<device>ergoline_kbd</device>
<device>et4000</device>
<device>et4000_kasan16</device>
<device>et4kw32i</device>
<device>europc_kbd</device>
<device>ev346</device>
<device>ex1280</device>
<device>ex800</device>
<device>executive10_102_kbd</device>
<device>exorterm155_device</device>
<device>f4431_kbd</device>
<device>fc_disksys</device>
<device>fccpu20</device>
<device>fccpu21</device>
<device>fccpu21a</device>
<device>fccpu21b</device>
<device>fccpu21s</device>
<device>fccpu21ya</device>
<device>fccpu21yb</device>
<device>fcisio1</device>
<device>fcscsi1</device>
<device>fd148</device>
<device>fd2000</device>
<device>fd4000</device>
<device>fdc344</device>
<device>fdcmag</device>
<device>filetto_cga</device>
<device>freedom220_kbd</device>
<device>fsd1</device>
<device>fsd2</device>
<device>g80_1500</device>
<device>geforce256</device>
<device>geforce256ddr</device>
<device>gfxultra</device>
<device>gfxultrap</device>
<device>gic</device>
<device>gunsense</device>
<device>h89bus</device>
<device>hardbox</device>
<device>hcpu30</device>
<device>hd44780_a00</device>
<device>hd44780</device>
<device>hd44780u</device>
<device>hd61830</device>
<device>hd63484</device>
<device>he191_3425</device>
<device>heath_gp19_tlb</device>
<device>heath_igc_super19_tlb_device</device>
<device>heath_igc_tlb_device</device>
<device>heath_igc_ultra_tlb_device</device>
<device>heath_igc_watz_tlb_device</device>
<device>heath_imaginator_tlb</device>
<device>heath_super19_tlb</device>
<device>heath_superset_tlb</device>
<device>heath_tlb</device>
<device>heath_ultra_tlb</device>
<device>heath_watz_tlb</device>
<device>hk68v10</device>
<device>hp82900</device>
<device>hp82937</device>
<device>hp82939</device>
<device>hp9122c</device>
<device>hp9133</device>
<device>hp98034</device>
<device>hp98035</device>
<device>hp98046</device>
<device>hp9845_prt</device>
<device>hp9895</device>
<device>hpblp</device>
<device>human_interface</device>
<device>i8244</device>
<device>i8245</device>
<device>ibm_mfc</device>
<device>ibm_vga</device>
<device>idpart_video</device>
<device>ie15_device</device>
<device>ie15kbd</device>
<device>imds2ioc</device>
@ -508,16 +589,19 @@
<device>isa_hercules</device>
<device>isa_ibm_mda</device>
<device>isa_ibm_pgc</device>
<device>isa_ibm_speech</device>
<device>isa_pcmidi</device>
<device>isa_prose4001</device>
<device>isbc202</device>
<device>isbc8024</device>
<device>isbc_215g</device>
<device>jaleco_vj_pc</device>
<device>jc1310</device>
<device>jvs13551</device>
<device>k573_dio</device>
<device>k573kara</device>
<device>k573mcr</device>
<device>k573msu</device>
<device>k573npu</device>
<device>k7659_keyboard</device>
<device>kaypro10kbd</device>
<device>kb_3270pc</device>
@ -531,10 +615,15 @@
<device>kbd_lle_en_us</device>
<device>kc_d004</device>
<device>kc_d004_gide</device>
<device>keytronic_l2207</device>
<device>keytronic_pc3270</device>
<device>keytronic_pc3270_at</device>
<device>km035</device>
<device>ks0066_f05</device>
<device>ks0066</device>
<device>labtam_3232</device>
<device>labtam_8086cpu</device>
<device>labtam_vducom</device>
<device>labtam_z80sbc</device>
<device>lba_enhancer</device>
<device>lc7985</device>
<device>ldp1000</device>
@ -557,6 +646,7 @@
<device>m68705p5</device>
<device>m68705r3</device>
<device>m68705u3</device>
<device>m68hc05pge</device>
<device>m68hc705c4a</device>
<device>m68hc705c8a</device>
<device>mach64isa</device>
@ -566,21 +656,26 @@
<device>mackbd_m0110a_j</device>
<device>mackbd_m0110b</device>
<device>mackbd_m0110f</device>
<device>mackbd_m0110j</device>
<device>mackbd_m0110t</device>
<device>mackbd_m0120</device>
<device>mackbd_m0120p</device>
<device>mb90082</device>
<device>mbc55x_kbd</device>
<device>mc10_mcx128</device>
<device>mc1502_rom</device>
<device>mcx128</device>
<device>md_kbd</device>
<device>md_rom_svp</device>
<device>mg1_kbd_device</device>
<device>mga2064w</device>
<device>microtan_kbd_mt009</device>
<device>microtouch</device>
<device>midcsd</device>
<device>midssio</device>
<device>mie</device>
<device>mindset_sound_module</device>
<device>minichif</device>
<device>mks3</device>
<device>mm1kb</device>
<device>mm5740</device>
<device>mockingboardd</device>
@ -595,6 +690,17 @@
<device>mpcba79</device>
<device>mpcbb68</device>
<device>mpcbb92</device>
<device>mpf1_epb</device>
<device>mpf1_epb_ibp</device>
<device>mpf1_iom_ip</device>
<device>mpf1_prt</device>
<device>mpf1_prt_ip</device>
<device>mpf1_sgb</device>
<device>mpf1_ssb</device>
<device>mpf1_tva_ip</device>
<device>mpf1_vid</device>
<device>mps1200</device>
<device>mps1250</device>
<device>mpu401</device>
<device>ms7004</device>
<device>msdsd1</device>
@ -609,15 +715,27 @@
<device>msx_cart_easispeech</device>
<device>msx_cart_sfg01</device>
<device>msx_cart_sfg05</device>
<device>msx_cart_skw01</device>
<device>msx_cart_sunrise_ataide</device>
<device>msx_moonsound</device>
<device>mtx_cfx</device>
<device>mtx_sdxbas</device>
<device>mtx_sdxcpm</device>
<device>mu5lcd</device>
<device>mulcd</device>
<device>mvme120</device>
<device>mvme121</device>
<device>mvme122</device>
<device>mvme123</device>
<device>mvme147</device>
<device>mvme180</device>
<device>mvme181</device>
<device>mvme187</device>
<device>mvme327a</device>
<device>mvme328</device>
<device>mvme350</device>
<device>mzr8105</device>
<device>nabupc_keyboard</device>
<device>namco50</device>
<device>namco51</device>
<device>namco52</device>
@ -627,18 +745,32 @@
<device>namco58</device>
<device>namco59</device>
<device>namco62</device>
<device>namco_amc</device>
<device>namco_asca1</device>
<device>namco_asca3</device>
<device>namco_asca3a</device>
<device>namco_asca5</device>
<device>namco_csz1</device>
<device>namco_cyberlead</device>
<device>namco_cyberlead_led</device>
<device>namco_cyberleada</device>
<device>namco_emio102</device>
<device>namco_fca10</device>
<device>namco_fca11</device>
<device>namco_fcb</device>
<device>namco_tssio</device>
<device>namco_xmiu1</device>
<device>namcoc65</device>
<device>namcoc67</device>
<device>namcoc68</device>
<device>namcoc69</device>
<device>namcoc70</device>
<device>namcoc71</device>
<device>namcoc74</device>
<device>namcoc75</device>
<device>namcoc76</device>
<device>nanoreseau_mo</device>
<device>nanoreseau_to</device>
<device>nb_48gc</device>
<device>nb_824gc</device>
<device>nb_aenet</device>
<device>nb_amc3b</device>
<device>nb_btbug</device>
@ -647,17 +779,26 @@
<device>nb_laserview</device>
<device>nb_m2hr</device>
<device>nb_m2vc</device>
<device>nb_mdc48</device>
<device>nb_mdc824</device>
<device>nb_qdlink</device>
<device>nb_rtpd</device>
<device>nb_sp8s3</device>
<device>nb_spdq</device>
<device>nb_thungx</device>
<device>nb_vikbw</device>
<device>nb_wspt</device>
<device>ncr53c825_pci</device>
<device>neon250</device>
<device>nes_rob</device>
<device>newbrain_eim</device>
<device>newbrain_fdc</device>
<device>nlq401</device>
<device>nmk004</device>
<device>novell_dcb</device>
<device>np600a3</device>
<device>nss_tvinterface</device>
<device>omniwave</device>
<device>omti8621isa</device>
<device>oric_jasmin</device>
<device>oric_microdisc</device>
@ -665,15 +806,18 @@
<device>osa_maestro</device>
<device>osa_maestroa</device>
<device>osa_sparc</device>
<device>oti64111_pci</device>
<device>p1_fdc</device>
<device>p1_hdc</device>
<device>p1_rom</device>
<device>p72</device>
<device>pa7246</device>
<device>partner_gdp</device>
<device>pc1512kb</device>
<device>pc1640_iga</device>
<device>pc80s31</device>
<device>pc80s31k</device>
<device>pc88va2_fd_if</device>
<device>pc9801_118</device>
<device>pc9801_26</device>
<device>pc9801_55l</device>
@ -689,19 +833,35 @@
<device>pd3_lviw</device>
<device>pd3_mclr</device>
<device>pd3_pc16</device>
<device>pdc20262</device>
<device>pds30_emac</device>
<device>pds_hyper</device>
<device>pds_sefp</device>
<device>pdslc_macconlc</device>
<device>pet_softbox</device>
<device>pet_superpet</device>
<device>pg200</device>
<device>plg100_vl</device>
<device>plg150_ap</device>
<device>plus4_sid</device>
<device>polyvti</device>
<device>pr8210</device>
<device>premier_fdc</device>
<device>profighterq</device>
<device>profighterqa</device>
<device>profighterqb</device>
<device>profighterx</device>
<device>promotion3210</device>
<device>ps2_keybc</device>
<device>psion_3fax</device>
<device>psion_3link_ser</device>
<device>psion_parallel</device>
<device>psx_cd</device>
<device>psxgboost</device>
<device>pvga1a</device>
<device>pvga1a_jk</device>
<device>px320a</device>
<device>qg640</device>
<device>ql_cumanafdi</device>
<device>ql_gold</device>
<device>ql_kdi</device>
@ -725,7 +885,24 @@
<device>qsound</device>
<device>qsound_hle</device>
<device>qts1</device>
<device>qx10_keyboard</device>
<device>quadro</device>
<device>qx10_keyboard_ascii</device>
<device>qx10_keyboard_hasci</device>
<device>rageiidvd</device>
<device>rc2014_micro</device>
<device>rc2014_mini_cpm</device>
<device>rc2014_pagable_rom</device>
<device>rc2014_rom_ram_512k</device>
<device>rc2014_switchable_rom</device>
<device>riva128</device>
<device>riva128zx</device>
<device>rivatnt</device>
<device>rivatnt2</device>
<device>rivatnt2_m64</device>
<device>rivatnt2_ultra</device>
<device>rm_mq2</device>
<device>robotron_k7070</device>
<device>robotron_k7071</device>
<device>rolm_pdc</device>
<device>rolm_smioc</device>
<device>rtpc_kbd</device>
@ -738,8 +915,6 @@
<device>s100_sj2db</device>
<device>s1410</device>
<device>s3_764</device>
<device>s3virge</device>
<device>s3virgedx</device>
<device>s97269pb</device>
<device>s_smp</device>
<device>sa1403d</device>
@ -760,22 +935,36 @@
<device>sb300p</device>
<device>sbus_hme</device>
<device>sbus_sunpc</device>
<device>sc119</device>
<device>scorpion_ic</device>
<device>sdtandy_fdc</device>
<device>sed1200da</device>
<device>sed1200db</device>
<device>sed1200fa</device>
<device>sed1200fb</device>
<device>sed1278_0b</device>
<device>sed1278</device>
<device>sed1330</device>
<device>sega837_14438</device>
<device>segaai_soundbox</device>
<device>segabill</device>
<device>segadimm</device>
<device>seganetw</device>
<device>sente6vb</device>
<device>sfd10001</device>
<device>serad</device>
<device>sfd1001</device>
<device>sgi_gm1</device>
<device>sgi_ip4</device>
<device>sgi_kbd</device>
<device>side116</device>
<device>simutrek</device>
<device>sis630_gui</device>
<device>sis6326_agp</device>
<device>sis900_eth</device>
<device>smoc501</device>
<device>sms_diypaddle</device>
<device>smvme2000</device>
<device>sn74s262</device>
<device>sn74s263</device>
<device>sns_dsp1bleg</device>
<device>sns_dsp1leg</device>
<device>sns_dsp1leg_hi</device>
@ -808,8 +997,13 @@
<device>spectrum_lprint3</device>
<device>spectrum_mface1</device>
<device>spectrum_mface128</device>
<device>spectrum_mface128v1</device>
<device>spectrum_mface1v1</device>
<device>spectrum_mface1v2</device>
<device>spectrum_mface1v3</device>
<device>spectrum_mface3</device>
<device>spectrum_mikroplus</device>
<device>spectrum_mpoker</device>
<device>spectrum_mprint</device>
<device>spectrum_opus</device>
<device>spectrum_plus2test</device>
@ -818,25 +1012,52 @@
<device>spectrum_sdi</device>
<device>spectrum_spdos</device>
<device>spectrum_speccydos</device>
<device>spectrum_specmate</device>
<device>spectrum_swiftdisc</device>
<device>spectrum_swiftdisc2</device>
<device>spectrum_usource</device>
<device>spectrum_uspeech</device>
<device>spectrum_vtx5000</device>
<device>spectrum_wafa</device>
<device>st_kbd</device>
<device>stereo_fx</device>
<device>stic</device>
<device>sun1cpu</device>
<device>sv603</device>
<device>sv806</device>
<device>sw1000xg</device>
<device>swtpc8212_device</device>
<device>sx1541</device>
<device>sys68k_cpu1</device>
<device>sys68k_cpu20</device>
<device>sys68k_cpu21</device>
<device>sys68k_cpu21a</device>
<device>sys68k_cpu21b</device>
<device>sys68k_cpu21s</device>
<device>sys68k_cpu21ya</device>
<device>sys68k_cpu21yb</device>
<device>sys68k_cpu30</device>
<device>sys68k_cpu30be</device>
<device>sys68k_cpu30lite</device>
<device>sys68k_cpu30x</device>
<device>sys68k_cpu30xa</device>
<device>sys68k_cpu30za</device>
<device>sys68k_cpu33</device>
<device>sys68k_iscsi1</device>
<device>sys68k_isio1</device>
<device>t5182</device>
<device>tanbus_ra32k</device>
<device>tanbus_ra32krom</device>
<device>tanbus_tandos</device>
<device>tanbus_tanex</device>
<device>tanbus_vid8082</device>
<device>tandberg_tdv2100_disp_logic</device>
<device>tandberg_tdv2100_keyboard</device>
<device>tandy2kb</device>
<device>tdl12</device>
<device>technica</device>
<device>tek410x_kbd</device>
<device>tek_msu_fdc</device>
<device>teletex800</device>
<device>tetriskr_cga</device>
<device>tgui9680</device>
<device>ti99_bwg</device>
@ -851,30 +1072,43 @@
<device>ti99_pcode</device>
<device>ti99_rs232</device>
<device>ti99_speech</device>
<device>ti99_tipi</device>
<device>ti99_whtscsi</device>
<device>ti_hx5102</device>
<device>tiki100_8088</device>
<device>tim011_kbd</device>
<device>tk02</device>
<device>tms32030</device>
<device>tms32031</device>
<device>tms32032</device>
<device>tms32033</device>
<device>to8_kbd</device>
<device>to9_kbd</device>
<device>to9p_kbd</device>
<device>tp880v</device>
<device>tp881v</device>
<device>trs80m2kb</device>
<device>turbogx</device>
<device>turbogxp</device>
<device>tv950kb</device>
<device>tv955kb</device>
<device>tvc_hbf</device>
<device>tvga9000</device>
<device>ultra12f</device>
<device>ultra12f32</device>
<device>ultra14f</device>
<device>ultra24f</device>
<device>umc6650</device>
<device>upd7220</device>
<device>upd7220a</device>
<device>uts_400kbd</device>
<device>uts_extw</device>
<device>v102_kbd</device>
<device>v1050kb</device>
<device>v50_kbd</device>
<device>v550_kbd</device>
<device>vanta</device>
<device>vector4_kbd</device>
<device>vic1515</device>
<device>vic1520</device>
<device>vic20_fe3</device>
@ -883,8 +1117,12 @@
<device>victor9k_kb</device>
<device>virge_pci</device>
<device>virgedx_pci</device>
<device>vision864</device>
<device>vision964</device>
<device>vision968</device>
<device>voicebox</device>
<device>votrax</device>
<device>votrsc01</device>
<device>votrsc01a</device>
<device>vp700</device>
<device>vtech_fdc</device>
<device>vtech_rs232</device>
@ -898,7 +1136,17 @@
<device>wangpckb</device>
<device>wd1002a_wx1</device>
<device>wd1007a</device>
<device>wd90c00_jk</device>
<device>wd90c11_lr</device>
<device>wd90c30_lr</device>
<device>wd90c31_lr</device>
<device>wd90c31a_lr</device>
<device>wd90c31a_zs</device>
<device>wd90c33_zz</device>
<device>wd90c90_jk</device>
<device>wd9710_pci</device>
<device>wdxt_gen</device>
<device>wg130</device>
<device>wyse700</device>
<device>x68k_cz6bs1</device>
<device>x820kb</device>
@ -910,10 +1158,6 @@
<device>ymf281</device>
<device>z8671</device>
<device>z8682</device>
<device>zip100_ide</device>
<device>zorba_kbd</device>
<device>zorro_a2091</device>
<device>zorro_a590</device>
<device>zorro_ar1</device>
<device>zorro_ar2</device>
<device>zorro_ar3</device>
<device>zorro_buddha</device>
<device>zxbus_neogs</device>

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ Format notes:
"""
from xml.sax.saxutils import escape
from datetime import datetime
from datetime import datetime,timezone
import xml.etree.ElementTree as et
import sys
import os
@ -77,7 +77,7 @@ for dat in sys.argv[1:]:
games[name] = desc
print(f"Found {len(games)} games, {len(sorted(set(bioses)))} BIOSes and {len(sorted(set(devices)))} devices")
ident_info = f"<!-- Generated on {datetime.utcnow().strftime('%F')}, from {', '.join(files)} -->"
ident_info = f"<!-- Generated on {datetime.now(timezone.utc).strftime('%F')}, from {', '.join(files)} -->"
if len(games) > 0:
with open('mamenames.xml', 'w') as f: