RetroPie forum home
    • Recent
    • Tags
    • Popular
    • Home
    • Docs
    • Register
    • Login

    Pegasus theme development general

    Scheduled Pinned Locked Moved Ideas and Development
    pegasusqmltheme makingtheme help
    156 Posts 16 Posters 50.6k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • M
      msheehan79
      last edited by

      @fluffypillow Nice! This is very helpful indeed. Hope to get some time in the next day or two to take another crack at it.

      I'm still figuring out exactly where in the code I can leverage JavaScript, so knowing I can use it in the model declaration for example is really useful info for me. Up until now I had pretty much just used it inside of functions called on keyPress events.

      1 Reply Last reply Reply Quote 0
      • S
        SinisterSpatula @AndersHP
        last edited by SinisterSpatula

        Hi @AndersHP you can definitely give it a try. I got a little frustrated with the project because I'm not versed enough on QML and finding it extremely difficult to understand lol. I haven't done anything for about a week, and I'm just now starting to regain motivation to try any more with it. I came back to see some really neat stuff about making a fake collection, and that is definitely one of the pieces of the puzzle that was confusing the hell out of me. My only issue is, whenever I start adding the sort proxy filter, things with the gameOS theme start behaving erratically. Once the model changes, the gridview disappears for a moment, but then if you start pressing navigating buttons, it reappears and everything is fine. I just couldn't figure out how I'm supposed to do it so that this doesn't cause it to bug out like that. My other problem is the performance of the grid scrolling. And I think this is down to all the fancy stuff in the theme, slowing it down. I feel like I want to just start over on a very basic code, with none of the fancy elements. I want the grid to flow and scroll as smooth as possible, and I'm thinking the best way is a grid view that only wants to show cartridge art (and maybe screenshot as fallback) but no scaling the currently selected tile, no changing font color to gray, or changing opacity, no animations at all, just the scrolling, and maybe a border around the current selection. Feel like I might start over on a clean code, just keeping the platform menu, and game details screens, and cutting back all the bling on the gridview. Then, trying to implement the fake collections of "Last Played" and "Favorites". If I could get to that point, I would be so happy with it. :D But man, it's a challenge, because I really don't know anything about javascript and QML and everything I try to do is really guesswork and trial and error. Someone who actually has skills at this could do it an 30 minutes, what is taking me weeks LOL.

        PlayingKarrdeP 1 Reply Last reply Reply Quote 1
        • PlayingKarrdeP
          PlayingKarrde @SinisterSpatula
          last edited by

          @SinisterSpatula Have you already taken out the video player in the GridItem? That is most likely the heaviest piece in terms of performance. I haven't tried the theme on a pi zero (or even a pi3 for that matter) but removing all the code for that should be the first step. After that I would look at removing the changing of the background art on each item update call.

          Other than that I don't think there's anything super crazy going on that should cause a lot of performance issues.

          S 1 Reply Last reply Reply Quote 0
          • S
            SinisterSpatula @PlayingKarrde
            last edited by

            @PlayingKarrde Yeah, the video stuff on grid view was taken out very early on, and it still chugs at times. I think it might actually be the the grid tile elements have too much going on as well. I think I need to strip out even the loading spinners, probably, anything that is asking it to use CPU cycles pretty much. I think the logic needs to be purely, grab and display art, show text on top (or not) and do it as fast as you can. The zero is really sensitive to multitasking since it only has it's single core.

            1 Reply Last reply Reply Quote 0
            • S
              SinisterSpatula
              last edited by

              Much better, I think I'm happy with the performance of the grid scrolling now. Had to remove a lot to get it this fast. I still wish it could scroll smoothly, like if I could set the maximum velocity to something slower to give it a chance to load elements before they come into view. If I set the maximum flick velocity it will work for mouse interaction, but it completely ignores key navigation which the gamepad counts as. If I could slow that down just a tad, I think it would scroll even smoother, but seems impossible.

              1 Reply Last reply Reply Quote 0
              • S
                SinisterSpatula
                last edited by

                Well crap. I thought I would be smart and tie my background art changes to onMovementEnded for the gridview. This worked out great, if the grid is flicked with the mouse. But does nothing if it's moved by key navigation (d-pad). So the only way to do this is with a timer? I have to reset the timer every time the grid is being scrolled? That seems so inefficient, and with the pi zero I'm trying to be as efficient as possible. :(

                1 Reply Last reply Reply Quote 0
                • S
                  SinisterSpatula
                  last edited by

                  Holy moly!!! I'm glad I tried the timer, and I messed it up initially because it lead to a major discovery. It stopped a function from getting called (every time the index was changing), and the performance went through the roof! So now I moved that function to the Timer trigger, and it's a dream come true. The grid is now scrolling with the performance I was dreaming of:

                  cyperghostC 1 Reply Last reply Reply Quote 2
                  • cyperghostC
                    cyperghost @SinisterSpatula
                    last edited by

                    @SinisterSpatula looks great - what do you have done?

                    S 1 Reply Last reply Reply Quote 0
                    • S
                      SinisterSpatula @cyperghost
                      last edited by

                      @cyperghost done a lot with it so far, mostly resizing various elements. Reduced grid columns, removed a lot of the animations and behaviors to speed it up, then built a settings menu (which still needs UI improving, but since its eventually going to be handled by the frontend I hesitate to put more time on that), changed the way background art is being handled, same for grid art, figuring out how to get the art assets to all be found and work, lot of trial error, and added pico8 svg and openbor svg logos. Next on my list is Favorites, Last Played, few more cosmetic changes, test megadrive/pce16 -> genesis/tg16 swapping, and if I really want to go wild, see about adding alphabet listview that pops out from the side and jumps to (closest) matching index in gridview, and see if we have on screen keyboard support and add it if so. (Not sure if either of these will happen).

                      1 Reply Last reply Reply Quote 0
                      • S
                        SinisterSpatula
                        last edited by SinisterSpatula

                        Trying my hand at the fake collections, and it seems to be working beautifully! :D below was the code of how I added it, and I'm able to see the two new collections, and switch to all collections and the grid displays all the proper games, the only thing I'm worried about now, is, if the user toggles the favorites bool for the game, will it still work, and if not, how I will fix it, but I have ideas, maybe. maybe not lol. Edit: so yeah, as I expected, the gameData.favorite is a problem, but also another issue with doing it this way is I get a bunch of errors when trying to access game.assets (screenshot, boxFront, etc) saying /GameDetails.qml:287: TypeError: Cannot read property 'boxFront' of undefined I guess because it's not a true "game" from api.collections but rather just a standard array. Hmmmm. So I guess I need to redo this, following the way that @fluffypillow showed, using it as a model data specifically, and I'll just need to use the proxy filter index translating methods when filling or swapping the currentGame object. (if the current collection being displayed comes from one of the proxy sort filters). I guess I'm starting to understand the difference between an Object, a Model, and an Array.

                        FocusScope {
                          
                          SortFilterProxyModel {
                            id: favoriteGames
                            sourceModel: api.allGames
                            filters: ValueFilter {
                              roleName: "favorite"
                              value: true
                            }
                          }
                          property var favoritesCollection: {
                            return {
                              name: "Favorite Games",
                              shortName: "favorites",
                              games: favoriteGames,
                            }
                          }
                          SortFilterProxyModel {
                            id: lastPlayedGames
                            sourceModel: api.allGames
                            sorters: RoleSorter {
                              roleName: "lastPlayed"
                            }
                          }
                          property var lastPlayedCollection: {
                            return {
                              name: "Last Played",
                              shortName: "lastplayed",
                              games: lastPlayedGames,
                            }
                          }
                          
                          //form a collection which contains our favorites, last played, and all real collections.
                          property var dynamicCollections: [favoritesCollection, lastPlayedCollection, ...api.collections.toVarArray()]
                        ...
                          //////////////////////////
                          // Collection switching //
                        
                          function modulo(a,n) {
                            return (a % n + n) % n;
                          }
                        
                          property int collectionIndex: 0
                          property var currentCollection: dynamicCollections[collectionIndex]
                        
                          function nextCollection () {
                            jumpToCollection(collectionIndex + 1);
                          }
                        
                          function prevCollection() {
                            jumpToCollection(collectionIndex - 1);
                          }
                        
                          function jumpToCollection(idx) {
                            api.memory.set('gameCollIndex' + collectionIndex, currentGameIndex); // save game index of current collection
                            collectionIndex = modulo(idx, (api.collections.count + 2)); // new collection index
                            currentGameIndex = api.memory.get('gameCollIndex' + collectionIndex) || 0; // restore game index for newly selected collection
                          }
                        
                          // End collection switching //
                          //////////////////////////////
                        
                          ////////////////////
                          // Game switching //
                        
                          property int currentGameIndex: 0
                          readonly property var currentGame: currentCollection.games.get(currentGameIndex)
                        
                          function changeGameIndex (idx) {
                            currentGameIndex = idx
                            if (collectionIndex && idx) {
                            api.memory.set('gameIndex' + collectionIndex, idx);
                            }
                          }
                        
                          // End game switching //
                          ////////////////////////
                        
                        
                        1 Reply Last reply Reply Quote 0
                        • S
                          SinisterSpatula
                          last edited by

                          I'm stuck and could really use some help on:

                          SortFilterProxyModel {
                              id: lastPlayedGames
                              sourceModel: api.allGames
                              sorters: RoleSorter {
                                roleName: "lastPlayed"
                                enabled: true
                              }
                            }
                          

                          This is giving me a collection of games that does not seem right. It's just a list of api.allGames and it's not being sorted, they are all just alphabetical. I can't understand why that is.

                          1 Reply Last reply Reply Quote 0
                          • M
                            msheehan79
                            last edited by

                            @SinisterSpatula not sure if it will help or not, but I've been playing around a bit with the sort/filter code to get a better handle on it. I've been using the "Flixnet" theme from @fluffypillow's repository as my base as theme as I had used that tutorial to get started.

                            The only differences I see in my code is I flipped the sort order since by default the most recently played games were being added to the end of the list. I also did not include the "enabled" flag but doubt that would make a difference.

                                // Recently Played custom collection
                                SortFilterProxyModel {
                                    id: recentGames
                                    sourceModel: api.allGames
                                    sorters: RoleSorter {
                                        roleName: "lastPlayed"
                                        sortOrder: Qt.DescendingOrder
                                    }
                                }
                            

                            Another tweak I made was to limit the list to the last 20 games games since the api.allGames collection can be quite large.

                            I am not sure if there is a way to layer filters inside of one proxy so that I could filter the last 20 results based on the sorted list, so what I ended up doing was chaining a second proxy that filters the sorted list above. It works fine, but it does make the launch command a bit more tricky since you have to traverse 2 proxies to get back to the original source model.

                            A larger snippet of the code is below, but I have the WIP theme on github too. I haven't broken the theme out into it's own repo as I'm still just experimenting a bit but feel free to have a look and see if it helps you with any points you might be stuck on, as the favorites and recently played collections do work on this theme with these edits.

                            https://github.com/msheehan79/emu-launch-scripts/tree/master/pegasus/config/themes/pegasus-theme-flixnet-master

                                // Recently Played custom collection
                                SortFilterProxyModel {
                                    id: recentGames
                                    sourceModel: api.allGames
                                    sorters: RoleSorter {
                                        roleName: "lastPlayed"
                                        sortOrder: Qt.DescendingOrder
                                    }
                                }
                            
                                // Apply a second proxy to only show the most recent 20 games - not sure if this can be consolidated into 1 proxy?
                                SortFilterProxyModel {
                                    id: filteredRecentGames
                                    sourceModel: recentGames
                                    filters: IndexFilter {
                                        maximumIndex: maxRecentGames
                                    }
                                }
                            
                                property var recentCollection: {
                                    return {
                                        name: "Recently Played",
                                        games: filteredRecentGames
                                    }
                                }
                            
                                property var allCollections: [favCollection, recentCollection, ...api.collections.toVarArray()]
                            
                            
                            S 1 Reply Last reply Reply Quote 2
                            • S
                              SinisterSpatula @msheehan79
                              last edited by SinisterSpatula

                              @msheehan79 thank you so much! This is exactly what I needed and extremely helpful. I wondered about that too, how to limit the number of items, fantastic! I just added an alphabet listview that pops out when the filter button is pressed and I plan to use the logic from holding the Alt key and pressing letters, should work perfectly. Man this is shaping up to exceed beyond what I imagined. Once I get that working plus lastplayed, I think it will be perfect.

                              Yes I think we might be able to combine multiple sorters and filters in the same proxy, I've seen that done before and should be as simple as putting both right in there. I think the order might be important, like you would want to sort decending and then chop. Versus chopping then sorting.

                              edit: I tried combining the sort & chop and it did not work, it seems we do have to double proxy.

                              AndersHPA 1 Reply Last reply Reply Quote 1
                              • AndersHPA
                                AndersHP @SinisterSpatula
                                last edited by AndersHP

                                @SinisterSpatula
                                Great to see the progress! Scrolling looks awesome - on a Pi Zero, that's amazing! Can't wait to test on my CM3+ module.

                                Do share a link when you think it's ready for test!

                                My "Bubble Bobble" Themed Bartop Arcade
                                My Gameboy

                                1 Reply Last reply Reply Quote 1
                                • fluffypillowF
                                  fluffypillow
                                  last edited by

                                  Wow nice progress :) It is possible to use multiple filters, but not for chopping unfortunately: only games that pass all filters (no matter the order) will be present in the output. This is because filters operate on the source model: IndexFilter works on the source index, so combining that with favorites means "games that have less than <some max> index in the source model AND games that are favorites", which might indeed have unexpected results. A workaround could be using another proxy, yes, or if it's getting hard to use, perhaps manually creating and filling a JavaScript array of games could also work. (I think the proxy model does not have a toVarArray function sadly, but if it does, that could make things easier.)

                                  1 Reply Last reply Reply Quote 0
                                  • S
                                    SinisterSpatula
                                    last edited by

                                    This code is pretty wild, but it's working :D I'm so freaking happy right now. The last item I need to finish is the Alpha-jumping menu. Then just fixing any other bugs I can find and figure out.

                                      ////////////////////
                                      // Game switching //
                                    
                                      property int currentGameIndex: 0
                                      readonly property var currentGame: (collectionIndex >= 2) ? currentCollection.games.get(currentGameIndex) : api.allGames.get(findCurrentGameFromProxy(currentGameIndex, collectionIndex))
                                    
                                      function findCurrentGameFromProxy (idx, collidx) {
                                        if (collidx == 0) {
                                          return favoriteGames.mapToSource(idx);
                                        }
                                        if (collidx == 1) {
                                          return lastPlayedFilter.mapToSource((lastPlayedGames.mapToSource(idx)));
                                        }
                                        return;
                                      }
                                    
                                      function changeGameIndex (idx) {
                                        currentGameIndex = idx
                                        if (collectionIndex && idx) {
                                        api.memory.set('gameIndex' + collectionIndex, idx);
                                        }
                                      }
                                    
                                      // End game switching //
                                      ////////////////////////
                                    
                                    fluffypillowF 1 Reply Last reply Reply Quote 1
                                    • fluffypillowF
                                      fluffypillow @SinisterSpatula
                                      last edited by

                                      @SinisterSpatula tip: for currentGame it's not necessary to track back to the original source model; it doesn't matter whether you get() a game from a proxy model or the Api, the games themselves are all the same thing in both places. Ie. you can just favoriteGames.get(someIndex) and it should work fine.

                                      S 1 Reply Last reply Reply Quote 1
                                      • S
                                        SinisterSpatula @fluffypillow
                                        last edited by

                                        @fluffypillow I tried using the lastPlayedGames.get(index) and favoriteGames.get(index) but something strange is happening when I do that. It's like the item it returns is not a "real game item" or something. When other code tries to access stuff it should be able to (currentGame.assets) it says it's null or a type error or something. If I fetch the game all the way back at the source it works fine.

                                        1 Reply Last reply Reply Quote 0
                                        • S
                                          SinisterSpatula
                                          last edited by SinisterSpatula

                                          Man, I'm stuck on this again :(

                                          I'm trying to make a function call to the grid from my AlphaMenu.qml, and I'm not understanding the proper way to reference it:

                                          Keys.onPressed: {
                                                if (api.keys.isAccept(event)) {
                                                    event.accepted = true;
                                                    gamegrid.grid.jumpToMyLetter(lettersList[alphaList.currentIndex]);
                                                    closeMenu();
                                                    return;
                                                }
                                             }
                                          

                                          GameGrid.qml:

                                          FocusScope {
                                            id: root
                                            GridView {
                                              id: grid
                                                 function jumpToMyLetter (inputletter) {
                                                       event.accepted = true;
                                                       var jumpletter = inputletter.toLowerCase();
                                                       var match = false;
                                                       for (var idx = 0; idx < model.count; idx++) { // search title starting-with pattern
                                                         var lowTitle = model.get(idx).title.toLowerCase();
                                                         if (lowTitle.indexOf(jumpletter) == 0) {
                                                           currentIndex = idx;
                                                           match = true;
                                                           break;
                                                         }
                                                       }
                                                       if (!match) { // no match - try to search title containing pattern
                                                         for (var idx = 0; idx < model.count; idx++) {
                                                           var lowTitle = model.get(idx).title.toLowerCase();
                                                           if (lowTitle.indexOf(jumpletter) != -1) {
                                                           currentIndex = idx;
                                                           break;
                                                           }
                                                         }
                                                       }
                                                     }
                                          

                                          It looks so pretty! I just need it to work. I'm so thankful that the game title seeking code existed, I would never have figured that out on my own. Maybe this needs to be a signal and signal handler instead? I'm trying hard to understand and do the right thing.

                                          alt text

                                          edit: Just got it working! I'm sure this is a noobish way of doing it, but it worked:
                                          On gamegrid.qml, under the root id:

                                          function jumpTheGrid (letter) {
                                              grid.jumpToMyLetter(letter);
                                            }
                                          

                                          still on gamegrid.qml, under the Gridview grid id:

                                          function jumpToMyLetter (letter) {
                                          //actual letter jumping code
                                          }
                                          

                                          On alphamenu.qml:

                                          Keys.onPressed: {
                                                if (event.isAutoRepeat)
                                                    return;
                                                if (api.keys.isAccept(event)) {
                                                    event.accepted = true;
                                                    gamegrid.jumpTheGrid(lettersList[alphaList.currentIndex]);
                                                    closeMenu();
                                                    return;
                                                }
                                          }
                                          

                                          So now, the last bit that needs fixing is a bunch of Type errors and reference errors when my theme tries to access the assets of the current game when sometimes the currentGame.assets is null. I just need to probably add a check for null at the beginning of these code blocks. Tomorrow I'll work on making a video to show it off.

                                          1 Reply Last reply Reply Quote 1
                                          • S
                                            SinisterSpatula
                                            last edited by

                                            Video Walkthrough and annoucement post here: https://retropie.org.uk/forum/topic/23682/theme-gpios-based-on-gameos-pegasus-front-end-theme-modified-for-retroflag-gpi-case

                                            1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post

                                            Contributions to the project are always appreciated, so if you would like to support us with a donation you can do so here.

                                            Hosting provided by Mythic-Beasts. See the Hosting Information page for more information.