├── .nojekyll
├── Bottombar
├── BottomBarIcons.qml
└── IconPair.qml
├── EXTRAINFO.md
├── Extra
├── ClockBar.qml
├── Colcon.qml
└── IconFont.qml
├── GameItems
├── GICusart.qml
├── GINormal.qml
└── VideoPlayer.qml
├── LICENSE
├── Localization.qml
├── MODIFICATIONS.md
├── README.md
├── Theme
├── All.qml
├── Collections.qml
├── Home.qml
├── PegaKey
│ ├── KeyButton.qml
│ ├── Keyboard.qml
│ └── KeyboardObject.qml
└── Settings.qml
├── assets
├── SAFELY_REMOVABLE
│ ├── OLD_DOCUMENTATION.md
│ ├── library-icons-font
│ │ ├── README.txt
│ │ ├── config.json
│ │ ├── css
│ │ │ ├── animation.css
│ │ │ ├── library-icons-font-codes.css
│ │ │ ├── library-icons-font-embedded.css
│ │ │ ├── library-icons-font-ie7-codes.css
│ │ │ ├── library-icons-font-ie7.css
│ │ │ └── library-icons-font.css
│ │ ├── demo.html
│ │ └── font
│ │ │ ├── library-icons-font.eot
│ │ │ ├── library-icons-font.svg
│ │ │ ├── library-icons-font.ttf
│ │ │ ├── library-icons-font.woff
│ │ │ └── library-icons-font.woff2
│ ├── old_screenshots
│ │ ├── old_screenshot_1.png
│ │ ├── old_screenshot_2.png
│ │ ├── old_screenshot_3.png
│ │ ├── old_screenshot_4.png
│ │ ├── old_screenshot_5.png
│ │ ├── old_screenshot_6.png
│ │ ├── old_screenshot_7.png
│ │ ├── old_screenshot_8.png
│ │ └── old_screenshot_9.png
│ ├── screenshot_1.png
│ ├── screenshot_2.png
│ ├── screenshot_3.png
│ ├── screenshot_4.png
│ ├── screenshot_5.png
│ ├── screenshot_6.png
│ ├── screenshot_7.png
│ ├── screenshot_8.png
│ ├── screenshot_9.png
│ └── website
│ │ ├── commonelements.js
│ │ ├── gallery.html
│ │ ├── install.html
│ │ ├── main.html
│ │ └── styles
│ │ ├── Outfit-Variable.ttf
│ │ ├── fonts.css
│ │ └── main.css
├── audio
│ ├── accept.wav
│ ├── back.wav
│ ├── favorite.wav
│ ├── game.wav
│ ├── nav.wav
│ ├── switchB.wav
│ ├── switchF.wav
│ ├── tab.wav
│ └── type.wav
├── backgrounds
│ ├── dark-1.jpg
│ ├── dark-2.jpg
│ ├── light-1.jpg
│ └── light-2.jpg
├── font
│ ├── Gilroy-ExtraBold.otf
│ ├── Gilroy-Light.otf
│ ├── NotoSansJP-ExtraBold.ttf
│ ├── NotoSansJP-Light.ttf
│ └── library-icons-font.ttf
├── logo
│ └── banner
│ │ ├── 3do.jpg
│ │ ├── 3ds.jpg
│ │ ├── all.jpg
│ │ ├── amiga.jpg
│ │ ├── amstradcpc.jpg
│ │ ├── android.jpg
│ │ ├── apple2.jpg
│ │ ├── arcade.jpg
│ │ ├── atari2600.jpg
│ │ ├── atari5200.jpg
│ │ ├── atari7800.jpg
│ │ ├── atarijaguar.jpg
│ │ ├── atarilynx.jpg
│ │ ├── atarist.jpg
│ │ ├── atomiswave.jpg
│ │ ├── c64.jpg
│ │ ├── cdi.jpg
│ │ ├── colecovision.jpg
│ │ ├── cps1.jpg
│ │ ├── cps2.jpg
│ │ ├── cps3.jpg
│ │ ├── daphne.jpg
│ │ ├── dreamcast.jpg
│ │ ├── empty.jpg
│ │ ├── fba.jpg
│ │ ├── fbneo.jpg
│ │ ├── fds.jpg
│ │ ├── flash.jpg
│ │ ├── gamegear.jpg
│ │ ├── gb.jpg
│ │ ├── gba.jpg
│ │ ├── gbc.jpg
│ │ ├── gc.jpg
│ │ ├── generic.png
│ │ ├── genesis.jpg
│ │ ├── gog.jpg
│ │ ├── gog_alt.jpg
│ │ ├── intellivision.jpg
│ │ ├── lutris.jpg
│ │ ├── mame.jpg
│ │ ├── mastersystem.jpg
│ │ ├── megadrive.jpg
│ │ ├── msx.jpg
│ │ ├── msx2.jpg
│ │ ├── n64.jpg
│ │ ├── nds.jpg
│ │ ├── neogeo.jpg
│ │ ├── neogeocd.jpg
│ │ ├── nes.jpg
│ │ ├── ngp.jpg
│ │ ├── ngpc.jpg
│ │ ├── pc.jpg
│ │ ├── pcengine.jpg
│ │ ├── pcfx.jpg
│ │ ├── pico8.jpg
│ │ ├── ports.jpg
│ │ ├── ps2.jpg
│ │ ├── ps3.jpg
│ │ ├── psp.jpg
│ │ ├── psx.jpg
│ │ ├── saturn.jpg
│ │ ├── scumm.jpg
│ │ ├── sega32x.jpg
│ │ ├── segacd.jpg
│ │ ├── snes.jpg
│ │ ├── source-note.txt
│ │ ├── steam.jpg
│ │ ├── steam_alt.jpg
│ │ ├── str.jpg
│ │ ├── switch.jpg
│ │ ├── tic80.jpg
│ │ ├── turbografx16.jpg
│ │ ├── vectrex.jpg
│ │ ├── virtualboy.jpg
│ │ ├── wii.jpg
│ │ ├── wiiu.jpg
│ │ ├── wiiware.jpg
│ │ ├── windows.jpg
│ │ ├── winuwp.jpg
│ │ └── zxspectrum.jpg
└── theme
│ ├── logo.png
│ └── logo.svg
├── index.html
├── theme.cfg
└── theme.qml
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fr75s/library/dd44cb3c00ca14b1bc8eaadb96f385a09b372984/.nojekyll
--------------------------------------------------------------------------------
/Bottombar/BottomBarIcons.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | Item {
8 | /* Button icons for the bottom bar
9 | * Shown when button indicators are enabled
10 | *
11 | */
12 |
13 | width: sw
14 | height: bottomBarSizeIndexToSize[settings["barSize"]]
15 |
16 | property bool limitHeight: (height > sh * 0.0525)
17 |
18 | anchors.bottom: parent.bottom
19 |
20 | /* We define 2 items, one for the right and one for the left
21 | * It would be difficult to separate these.
22 | *
23 | */
24 |
25 | // Right Buttons
26 | Item {
27 | width: parent.width / 2
28 | height: parent.height
29 |
30 | anchors.bottom: parent.bottom
31 | anchors.right: parent.right
32 |
33 | // Right Row
34 | Row {
35 | id: rightRow
36 |
37 | width: parent.width
38 | height: limitHeight ? bottomBarSizeIndexToSize["small"] : parent.height
39 |
40 | spacing: parent.width * .01
41 |
42 | layoutDirection: Qt.RightToLeft
43 |
44 | anchors.verticalCenter: parent.verticalCenter
45 | anchors.right: parent.right
46 | anchors.rightMargin: parent.width * .01
47 |
48 | // A Button Actions
49 | IconPair {
50 | height: parent.height * .8
51 |
52 | src: icons.btnScheme[settings.btnsScheme].A
53 |
54 | // Label for Home, Search, Collections & Settings respectively.
55 | // Same pattern for each IconPair.
56 | label: [loc.bottomBar_aSelect, (isFeed ? loc.bottomBar_aPlay : loc.bottomBar_aSelect), loc.bottomBar_aSelect, loc.bottomBar_aSetting]
57 |
58 | anchors.verticalCenter: parent.verticalCenter
59 | }
60 |
61 | // B Button Actions
62 | IconPair {
63 | height: parent.height * .8
64 |
65 | src: icons.btnScheme[settings.btnsScheme].B
66 |
67 | label: ["", (isFeed ? "" : loc.bottomBar_bKeyboard), loc.bottomBar_bExit, ""]
68 |
69 | anchors.verticalCenter: parent.verticalCenter
70 | }
71 |
72 | // Y Button Actions
73 | IconPair {
74 | height: parent.height * .8
75 |
76 | src: icons.btnScheme[settings.btnsScheme].Y
77 |
78 | label: [loc.bottomBar_yFavorite, (isFeed ? loc.bottomBar_yNext : loc.bottomBar_yKeyboard), loc.bottomBar_yFavorite, loc.bottomBar_yInfo]
79 |
80 | anchors.verticalCenter: parent.verticalCenter
81 | }
82 | }
83 | }
84 |
85 | // Left Buttons
86 | Item {
87 | width: parent.width / 2
88 | height: parent.height
89 |
90 | anchors.bottom: parent.bottom
91 | anchors.left: parent.left
92 |
93 | // Left Row
94 | Row {
95 | id: leftRow
96 |
97 | width: parent.width
98 | height: limitHeight ? bottomBarSizeIndexToSize["small"] : parent.height
99 |
100 | spacing: parent.width * .01
101 |
102 | layoutDirection: Qt.LeftToRight
103 |
104 | anchors.verticalCenter: parent.verticalCenter
105 | anchors.left: parent.left
106 | anchors.leftMargin: parent.width * .01
107 |
108 | // L1, left blank to go with R1
109 | IconPair {
110 | height: parent.height * .8
111 |
112 | src: icons.btnScheme[settings.btnsScheme].L1
113 |
114 |
115 | label: [" ", " ", " ", " "]
116 |
117 | anchors.verticalCenter: parent.verticalCenter
118 | }
119 |
120 | // R1, indicating change page
121 | IconPair {
122 | height: parent.height * .8
123 |
124 | src: icons.btnScheme[settings.btnsScheme].R1
125 |
126 | label: [loc.bottomBar_changePage, loc.bottomBar_changePage, loc.bottomBar_changePage, loc.bottomBar_changePage]
127 |
128 | anchors.verticalCenter: parent.verticalCenter
129 | }
130 |
131 | IconPair {
132 | height: parent.height * .8
133 |
134 | src: icons.btnScheme[settings.btnsScheme].L2
135 |
136 | label: ["", loc.all_feed, "", ""]
137 |
138 | anchors.verticalCenter: parent.verticalCenter
139 | }
140 |
141 | // X Button Actions
142 | IconPair {
143 | height: parent.height * .8
144 |
145 | src: icons.btnScheme[settings.btnsScheme].X
146 |
147 | label: ["", (isFeed ? "" : loc.bottomBar_xKeyboard), "", loc.bottomBar_xNext]
148 |
149 | anchors.verticalCenter: parent.verticalCenter
150 | }
151 |
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/Bottombar/IconPair.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | Row {
8 | /* IconPair
9 | * Defines an icon and text for button icons
10 | *
11 | */
12 |
13 | // Image Source
14 | property string src: ""
15 |
16 | // Labels for each page, with each element corresponding to each page
17 | property var label: ["0", "1", "2", "3"]
18 |
19 | spacing: (textContent.width + height) * 0.05
20 |
21 | width: textContent.width + height + spacing
22 |
23 | visible: (label[menu] != "")
24 |
25 | Item { // Home
26 | width: height
27 | height: parent.height * .8
28 | anchors.verticalCenter: parent.verticalCenter
29 | // Icon
30 | Text {
31 | text: src
32 | anchors.centerIn: parent
33 | font {
34 | family: icons.name;
35 | pixelSize: parent.height * .6
36 | }
37 | color: colors["bottomIcons"]
38 | }
39 | }
40 |
41 | // Text
42 | Text {
43 | id: textContent
44 | height: parent.height
45 | width: contentWidth
46 |
47 | text: label[menu]
48 | color: colors["bottomIcons"]
49 |
50 | font.pixelSize: parent.height * .6
51 | font.family: gilroyLight.name
52 | verticalAlignment: Text.AlignVCenter
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/EXTRAINFO.md:
--------------------------------------------------------------------------------
1 | # Extra Information for Library
2 |
3 | This document contains some extra information regarding some features of Library. It will provide some tips regarding the in-built features of Library, with no code modification necessary. Code modification stuff can be found in `MODIFICATIONS.md`.
4 |
5 | ## Search Modes (1.5.0+)
6 |
7 | There are currently **4** search modes in Library: Regular, Limited, Fuzzy, and Raw.
8 |
9 | - Regular: Finds any games which contain the given search anywhere in their title. Not case sensitive.
10 | - Limited: Finds any games which contain the given search at the beginning of their title. Not case sensitive.
11 | - Fuzzy: Finds games using a command prompt shorthand style of searching. Characters are matched in order and do not need to be adjacent to eachother (for example, unlike in Regular/Limited search, "ut" would match "Multiverse"). Capital letters let you enforce word boundaries by forcing the next letter to be at the beginning of a word (e.g. "BB" would match "Bugle Boggle" but not "Bugle Trouble", while "bb" would match both). While the syntax of fuzzy search is case-sensitive, the matching itself isn't (so, for example, "BB" would match both "Bugle Boggle" and "bugle boggle").
12 | - Raw: Finds games by treating the given search as a raw RegEx pattern. You can use any RegEx symbols to match game titles.
13 |
14 |
15 | ## Replacing the Backgrounds (1.4.0+)
16 |
17 | You can easily add/change the backgrounds shown in Library.
18 |
19 | To do this, go to `assets/backgrounds` in the theme's root directory (`[LIBRARY FOLDER]/assets/backgrounds`). Then, add your desired backgrounds, but be sure to add one for light mode and one for dark mode. Rename the backgrounds to `light-[number].jpg` and `dark-[number].jpg`, where `[number]` is the next number from the last backgrounds (which would be 3 if no backgrounds have been added, as Library comes with 2 sets of backgrounds.). Now, you can change to your background by finding the "Change Background" option.
20 |
21 | You can also set the color of the plain background by setting the color for `plainBG`. To learn more about changing colors, see [Custom Color Schemes](#custom-color-schemes).
22 |
--------------------------------------------------------------------------------
/Extra/ClockBar.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | Item {
8 | // A simple clock bar at the top of the screen
9 |
10 | width: parent.width
11 | height: parent.height * 0.035
12 |
13 | Rectangle {
14 | width: parent.width
15 | height: parent.height
16 |
17 | opacity: 0.5
18 | color: colors["clockBarBG"]
19 | }
20 |
21 | Text {
22 | /* I have known of this method of a time text since first dabbling with Pegasus
23 | * It's from somewhere, but I have no idea where.
24 | */
25 | id: currentTime
26 |
27 | // Set Time
28 | function set() {
29 | currentTime.text = settings["24hClock"] ? Qt.formatTime(new Date(), "hh:mm") : Qt.formatTime(new Date(), "hh:mm AP");
30 | }
31 |
32 | // Runs the timer to update the time every second
33 | Timer {
34 | id: currentTimeTextTimer
35 | interval: 1000 // Run the timer every second
36 | repeat: true
37 | running: true
38 | triggeredOnStart: true // Start immediately
39 | onTriggered: currentTime.set()
40 | }
41 |
42 | width: parent.width * 0.95
43 | anchors.horizontalCenter: parent.horizontalCenter
44 | height: parent.height
45 |
46 | horizontalAlignment: Text.AlignLeft
47 | verticalAlignment: Text.AlignVCenter
48 |
49 | color: colors["text"]
50 | font.family: gilroyExtraBold.name
51 | font.pixelSize: height / 2
52 | font.bold: true
53 | }
54 |
55 | Text {
56 | id: batteryIcon
57 |
58 | width: parent.width * 0.002
59 | height: parent.height
60 |
61 | anchors.left: currentTime.left
62 | anchors.leftMargin: currentTime.contentWidth + parent.width * 0.018
63 |
64 | text: isNaN(api.device.batteryPercent) ? "" : getBatteryIcon(api.device.batteryPercent)
65 |
66 | font {
67 | family: icons.name;
68 | pixelSize: height / 2
69 | }
70 |
71 | color: colors["text"]
72 | }
73 |
74 | Text {
75 | /* Lists the battery, not much more.
76 | */
77 | id: battery
78 |
79 | width: contentWidth
80 | height: parent.height
81 |
82 | anchors.left: currentTime.left
83 | anchors.leftMargin: currentTime.contentWidth + parent.width * 0.02
84 |
85 | horizontalAlignment: Text.AlignLeft
86 | verticalAlignment: Text.AlignVCenter
87 |
88 | text: isNaN(api.device.batteryPercent) ? "" : formPercent(api.device.batteryPercent) + "%"
89 | color: colors["text"]
90 | font.family: gilroyExtraBold.name
91 | font.pixelSize: height / 2
92 | }
93 |
94 |
95 | // Makes battery percentage readable (rather than a random decimal number)
96 | function getBatteryIcon(percent) {
97 |
98 | // Battery Percent must be less than or equal to KEY to be VALUE
99 | if (api.device.batteryCharging && api.device.batteryPercent <= 1) {
100 | return "\ue837";
101 | } else {
102 | var batteryConvert = {
103 | 5: "\ue82c",
104 | 10: "\ue82d",
105 | 20: "\ue82e",
106 | 30: "\ue82f",
107 | 40: "\ue830",
108 | 50: "\ue831",
109 | 60: "\ue832",
110 | 70: "\ue833",
111 | 80: "\ue834",
112 | 90: "\ue835",
113 | 100: "\ue836",
114 | }
115 |
116 | var clean_percent = Math.round(percent * 100);
117 |
118 | var index = 0;
119 | var range_r = Object.keys(batteryConvert)[index];
120 | while (clean_percent > range_r && clean_percent <= 100) {
121 | index += 1;
122 | range_r = Object.keys(batteryConvert)[index];
123 | }
124 |
125 | return batteryConvert[range_r];
126 | }
127 | }
128 |
129 | function formPercent(percent) {
130 | return Math.round(percent * 100);
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/Extra/Colcon.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | FocusScope {
8 |
9 | /* A tool to convert Launchbox items to actual counterparts
10 | * Directly from neoretrō: https://github.com/valsou/neoretro
11 | * Used in GICusart.
12 | */
13 |
14 | function clearShortname(shortname) {
15 | return dataLaunchbox[shortname] ? dataLaunchbox[shortname] : shortname
16 | }
17 |
18 | property variant dataLaunchbox: {
19 | "amstrad cpc" : "amstradcpc",
20 | "apple ii" : "apple2",
21 | "atari 2600" : "atari2600",
22 | "atari 5200" : "atari5200",
23 | "atari st" : "atarist",
24 | "atari 7800" : "atari7800",
25 | "atari lynx" : "atarilynx",
26 | "atari jaguar" : "atarijaguar",
27 | "capcom cps1" : "cps1",
28 | "capcom cps2" : "cps2",
29 | "capcom cps3" : "cps3",
30 | "commodore 64" : "c64",
31 | "commodore amiga" : "amiga",
32 | "mattel intellivision" : "intellivision",
33 | "microsoft msx" : "msx",
34 | "microsoft msx2" : "msx2",
35 | "nec turbografx-16" : "turbografx16",
36 | "pc engine supergrafx" : "supergrafx",
37 | "nec pc-fx" : "pcfx",
38 | "nintendo entertainment system" : "nes",
39 | "nintendo famicom disk system" : "fds",
40 | "nintendo virtual boy" : "virtualboy",
41 | "nintendo game boy" : "gb",
42 | "super nintendo entertainment system" : "snes",
43 | "nintendo 64" : "n64",
44 | "nintendo game boy color" : "gbc",
45 | "nintendo game boy advance" : "gba",
46 | "nintendo gamecube" : "gc",
47 | "nintendo ds" : "nds",
48 | "nintendo wii" : "wii",
49 | "nintendo 3ds" : "3ds",
50 | "nintendo wii u" : "wiiu",
51 | "nintendo switch" : "switch",
52 | "3do interactive multiplayer" : "3do",
53 | "sammy atomiswave" : "atomiswave",
54 | "scummvm" : "scumm",
55 | "sega master system" : "mastersystem",
56 | "sega genesis" : "genesis",
57 | "sega mega drive" : "megadrive",
58 | "sega game gear" : "gamegear",
59 | "sega cd" : "segacd",
60 | "sega 32x" : "sega32x",
61 | "sega saturn" : "saturn",
62 | "sega dreamcast" : "dreamcast",
63 | "sinclair zx spectrum" : "zxspectrum",
64 | "gce vectrex" : "vectrex",
65 | "snk neo geo aes" : "neogeo",
66 | "snk neo geo mvs": "neogeo",
67 | "snk neo geo cd" : "neogeocd",
68 | "snk neo geo pocket" : "ngp",
69 | "snk neo geo pocket color" : "ngpc",
70 | "sony playstation" : "psx",
71 | "sony playstation 2" : "ps2",
72 | "sony playstation 3" : "ps3",
73 | "sony psp" : "psp",
74 | "final burn alpha" : "fba",
75 | "final burn neo" : "fbneo"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Extra/IconFont.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 |
3 | // Custom Font for icons
4 | FontLoader {
5 | id: icons;
6 |
7 | property string allgames: '\ue800';
8 | property string nav_collections: '\ue801';
9 | property string nav_feed: '\ue802';
10 | property string nav_home: '\ue803';
11 | property string nav_search: '\ue804';
12 | property string nav_settings: '\ue805';
13 | property string touch_up: '\ue806';
14 | property string input_universal_a: '\ue807';
15 | property string input_universal_x: '\ue808';
16 | property string input_universal_lb: '\ue809';
17 | property string input_universal_lt: '\ue80a';
18 | property string input_universal_b: '\ue80b';
19 | property string input_universal_rb: '\ue80c';
20 | property string input_universal_rt: '\ue80d';
21 | property string input_universal_y: '\ue80e';
22 | property string input_xbox_a: '\ue80f';
23 | property string input_xbox_x: '\ue810';
24 | property string input_xbox_lb: '\ue811';
25 | property string input_xbox_lt: '\ue812';
26 | property string input_xbox_b: '\ue813';
27 | property string input_xbox_rb: '\ue814';
28 | property string input_xbox_rt: '\ue815';
29 | property string input_xbox_y: '\ue816';
30 | property string input_ps_a: '\ue817';
31 | property string input_ps_x: '\ue818';
32 | property string input_ps_lb: '\ue819';
33 | property string input_ps_lt: '\ue81a';
34 | property string input_ps_b: '\ue81b';
35 | property string input_ps_rb: '\ue81c';
36 | property string input_universal_start: '\ue81d';
37 | property string input_universal_select: '\ue81e';
38 | property string input_xbox_start: '\ue81f';
39 | property string input_xbox_select: '\ue820';
40 | property string input_ps_rt: '\ue821';
41 | property string input_ps_y: '\ue822';
42 | property string input_ps_start: '\ue823';
43 | property string input_ps_select: '\ue824';
44 | property string star: '\ue825';
45 | property string star_empty: '\ue826';
46 | property string meta_date: '\ue827';
47 | property string meta_dev: '\ue828';
48 | property string meta_genre: '\ue829';
49 | property string meta_publisher: '\ue82a';
50 | property string meta_players: '\ue82b';
51 | property string battery_0: '\ue82c';
52 | property string battery_10: '\ue82d';
53 | property string battery_20: '\ue82e';
54 | property string battery_30: '\ue82f';
55 | property string battery_40: '\ue830';
56 | property string battery_50: '\ue831';
57 | property string battery_60: '\ue832';
58 | property string battery_70: '\ue833';
59 | property string battery_80: '\ue834';
60 | property string battery_90: '\ue835';
61 | property string battery_100: '\ue836';
62 | property string battery_charging: '\ue837';
63 | property string clock: '\ue838';
64 | property string input_nintendo_zl: '\ue83b';
65 | property string input_nintendo_zr: '\ue83c';
66 | property string input_sd_l2: '\ue83d';
67 | property string input_sd_r2: '\ue83e';
68 | property string input_sd_l1: '\ue83f';
69 | property string input_sd_r1: '\ue840';
70 | property string input_nintendo_l: '\ue841';
71 | property string input_nintendo_r: '\ue842';
72 | property string input_sd_select: '\ue843';
73 | property string input_sd_start: '\ue844';
74 | property string input_nintendo_sl: '\ue845';
75 | property string input_nintendo_sr: '\ue846';
76 | property string star_half: '\uf123';
77 | property string toggle_off: '\uf204';
78 | property string toggle_on: '\uf204';
79 |
80 | // Button schemes
81 | property var btnScheme: {
82 | "universal": {
83 | A: icons.input_universal_a,
84 | X: icons.input_universal_x,
85 | L1: icons.input_universal_lb,
86 | L2: icons.input_universal_lt,
87 | B: icons.input_universal_b,
88 | R1: icons.input_universal_rb,
89 | R2:icons.input_universal_rt,
90 | Y: icons.input_universal_y,
91 | Start: icons.input_universal_start,
92 | Select: icons.input_universal_select
93 | },
94 | "universal_jp": {
95 | A: icons.input_universal_b,
96 | X: icons.input_universal_y,
97 | L1: icons.input_universal_lb,
98 | L2: icons.input_universal_lt,
99 | B: icons.input_universal_a,
100 | R1: icons.input_universal_rb,
101 | R2:icons.input_universal_rt,
102 | Y: icons.input_universal_x,
103 | Start: icons.input_universal_start,
104 | Select: icons.input_universal_select
105 | },
106 | "xbox": {
107 | A: icons.input_xbox_a,
108 | X: icons.input_xbox_x,
109 | L1: icons.input_xbox_lb,
110 | L2: icons.input_xbox_lt,
111 | B: icons.input_xbox_b,
112 | R1: icons.input_xbox_rb,
113 | R2:icons.input_xbox_rt,
114 | Y: icons.input_xbox_y,
115 | Start: icons.input_xbox_start,
116 | Select: icons.input_xbox_select
117 | },
118 | "ps": {
119 | A: icons.input_ps_a,
120 | X: icons.input_ps_x,
121 | L1: icons.input_ps_lb,
122 | L2: icons.input_ps_lt,
123 | B: icons.input_ps_b,
124 | R1: icons.input_ps_rb,
125 | R2:icons.input_ps_rt,
126 | Y: icons.input_ps_y,
127 | Start: icons.input_ps_start,
128 | Select: icons.input_ps_select
129 | },
130 | "ps_jp": {
131 | A: icons.input_ps_b,
132 | X: icons.input_ps_y,
133 | L1: icons.input_ps_lb,
134 | L2: icons.input_ps_lt,
135 | B: icons.input_ps_a,
136 | R1: icons.input_ps_rb,
137 | R2:icons.input_ps_rt,
138 | Y: icons.input_ps_x,
139 | Start: icons.input_ps_start,
140 | Select: icons.input_ps_select
141 | },
142 | "nintendo": {
143 | A: icons.input_xbox_a,
144 | X: icons.input_xbox_x,
145 | L1: icons.input_nintendo_l,
146 | L2: icons.input_nintendo_zl,
147 | B: icons.input_xbox_b,
148 | R1: icons.input_nintendo_r,
149 | R2:icons.input_nintendo_zr,
150 | Y: icons.input_xbox_y,
151 | Start: icons.input_universal_start,
152 | Select: icons.input_universal_select
153 | },
154 | "steam_deck": {
155 | A: icons.input_xbox_a,
156 | X: icons.input_xbox_x,
157 | L1: icons.input_sd_l1,
158 | L2: icons.input_sd_l2,
159 | B: icons.input_xbox_b,
160 | R1: icons.input_sd_r1,
161 | R2:icons.input_sd_r2,
162 | Y: icons.input_xbox_y,
163 | Start: icons.input_sd_start,
164 | Select: icons.input_sd_select
165 | }
166 | }
167 |
168 | // This list needs to be separate to allow for customization of order, rather than going
169 | // by each key alphabetically in the convert object
170 | property var btnSchemeOrder: {0: "universal", 1: "universal_jp", 2: "xbox", 3: "ps", 4: "ps_jp", 5: "nintendo", 6: "steam_deck"}
171 | // Converts the indexes in btnScheme to the names that are displayed
172 | // This makes it so that what's displayed is possible to customize and localize
173 | property var btnNameConvert: {
174 | "universal": "universal",
175 | "universal_jp": "universal JP",
176 | "xbox": "xbox",
177 | "ps": "playstation",
178 | "ps_jp": "playstation JP",
179 | "nintendo": "nintendo",
180 | "steam_deck": "steam deck"
181 | }
182 |
183 | source: "../assets/font/library-icons-font.ttf";
184 | }
185 |
--------------------------------------------------------------------------------
/GameItems/GICusart.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | Item {
8 | property var currentItem: modelData
9 | property var art: "./assets/logo/banner/empty.jpg"
10 |
11 | id: gameItem
12 |
13 | width: parent.width * 1.00
14 | height: parent.height
15 |
16 | // Name
17 | Text{
18 | id: collectName
19 | text: currentItem.name
20 |
21 | width: parent.width * 0.75
22 | z: gameItemImage.z + 2
23 |
24 | anchors.horizontalCenter: parent.horizontalCenter
25 | anchors.top: parent.top
26 | anchors.topMargin: parent.height * 0.1
27 |
28 | wrapMode: Text.WordWrap
29 |
30 | font.family: gilroyExtraBold.name
31 | font.bold: true
32 |
33 | color: "white"
34 | font.pixelSize: vpx(10) * (3 / settings["collectionRows"])
35 | visible: !(settings["videoPlayback"] && doubleFocus && currentItem.assets.videoList.length)
36 | }
37 |
38 | /*
39 | Rectangle {
40 | color: "black"
41 |
42 | width: collectName.width + vpx(20)
43 | height: collectName.height + vpx(10)
44 | anchors.centerIn: collectName
45 |
46 | z: gameItemImage.z + 1
47 | }
48 | */
49 |
50 | // Top bar
51 | Rectangle {
52 | id: customTopBar
53 | width: parent.width
54 | height: parent.height * .3
55 |
56 | anchors.right: parent.right
57 | anchors.top: parent.top
58 |
59 | color: settings["blurredCollections"] ? "#60000000" : "#90000000"
60 |
61 | z: gameItemImage.z + 1
62 | //radius: roundedGames ? height / roundedGamesRadiusFactor / .3 : 0
63 | visible: false
64 | }
65 |
66 | // Collection Image
67 | Image {
68 | id: gameItemImage
69 | width: parent.width
70 | height: parent.height
71 |
72 | anchors.centerIn: parent
73 | fillMode: Image.PreserveAspectCrop
74 |
75 | asynchronous: true
76 |
77 | source: ".././assets/logo/banner/" + cc.clearShortname(currentItem.shortName) + ".jpg"
78 | visible: false
79 |
80 | onStatusChanged: {
81 | if (status == Image.Error) {
82 | gameItemImage.source = ".././assets/logo/banner/generic.png";
83 | }
84 | }
85 |
86 | VideoPlayer {
87 | game: currentItem
88 |
89 | width: parent.width
90 | height: parent.height
91 | anchors.centerIn: parent
92 |
93 | playing: settings["videoPlayback"] && doubleFocus
94 | noSound: settings["nosfx"]
95 | }
96 | }
97 |
98 |
99 | Rectangle {
100 | id: gameItemMask
101 | anchors.fill: parent
102 | visible: false
103 |
104 | radius: settings["roundedGames"] ? height / roundedGamesRadiusFactor : 0
105 | }
106 |
107 | Rectangle {
108 | id: customTopMask
109 | width: parent.width
110 | height: customTopBar.height
111 | visible: false
112 |
113 | radius: settings["roundedGames"] ? height / roundedGamesRadiusFactor / .3 : 0
114 |
115 | Rectangle {
116 | width: parent.width
117 | height: parent.height / 2
118 |
119 | anchors.bottom: parent.bottom
120 | }
121 | }
122 |
123 | OpacityMask {
124 | id: gameItemRounded
125 | anchors.fill: gameItemImage
126 | source: gameItemImage
127 | maskSource: gameItemMask
128 | visible: !settings["blurredCollections"]
129 | }
130 |
131 | // Blur
132 | FastBlur {
133 | id: gameItemBlur
134 | visible: false
135 | anchors.fill: gameItemRounded
136 | source: gameItemRounded
137 | radius: 48
138 | }
139 |
140 | OpacityMask {
141 | id: gameItemBlurRounded
142 | anchors.fill: gameItemBlur
143 | source: gameItemBlur
144 | maskSource: gameItemMask
145 |
146 | visible: settings["blurredCollections"]
147 | }
148 |
149 |
150 |
151 | OpacityMask {
152 | anchors.fill: customTopBar
153 | source: customTopBar
154 | maskSource: customTopMask
155 | visible: !(settings["videoPlayback"] && doubleFocus && currentItem.assets.videoList.length)
156 | }
157 |
158 | // Drop Shadow
159 | DropShadow {
160 | width: parent.width
161 | height: parent.height
162 |
163 | source: settings["blurredCollections"] ? gameItemBlurRounded : gameItemRounded
164 |
165 | anchors.centerIn: parent
166 |
167 | opacity: giShadowOp
168 |
169 | radius: giShadowRad
170 | samples: giShadowRad * 2 + 1
171 | z: gameItemImage.z - 1
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/GameItems/GINormal.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | Item {
8 | id: gameItem
9 |
10 | // The standard gameItem, showing for all instances where games are shown.
11 | property var currentGame: modelData
12 | property bool wideHead: false
13 | property var backColor: settings["light"] ? "#EEEEEE" : "#181818"
14 |
15 | width: parent.width
16 | height: parent.height
17 |
18 | // Imageless backdrop
19 | Text {
20 | id: gameItemText
21 |
22 | anchors.fill: parent
23 |
24 | horizontalAlignment: Text.AlignHCenter
25 | verticalAlignment: Text.AlignVCenter
26 |
27 | text: currentGame.title
28 | wrapMode: Text.Wrap
29 |
30 | // Max gamesRows can be is 5, i've found 5 / rows to work well for scaling
31 | font.pixelSize: vpx(8) * (5 / settings["gamesRows"])
32 | font.family: gilroyLight.name
33 | font.bold: true
34 |
35 | color: colors["text"]
36 |
37 | Rectangle {
38 | id: gameItemTextBack
39 | anchors.fill: parent
40 | z: parent.z - 1
41 |
42 | color: colors["plainSetting"]
43 | radius: settings["roundedGames"] ? height / roundedGamesRadiusFactor : 0
44 |
45 | LinearGradient {
46 | id: gameItemTextBackGradient
47 | source: parent
48 | anchors.fill: parent
49 | start: Qt.point(0, 0)
50 | end: Qt.point(0, parent.height)
51 | gradient: Gradient {
52 | GradientStop { position: 0.0; color: "transparent" }
53 | GradientStop { position: 1.0; color: colors["giGradient"] }
54 | }
55 | }
56 | }
57 | }
58 |
59 | // The image itself
60 | Image {
61 | id: gameItemImage
62 | width: parent.width
63 | height: parent.height
64 |
65 | z: gameItemText.z + 1
66 |
67 | anchors.centerIn: parent
68 | fillMode: Image.PreserveAspectCrop
69 | visible: false
70 |
71 | asynchronous: true
72 |
73 | source: (settings["wide"] || (wideHead && index == 0)) ? (currentGame.assets.steam || currentGame.assets.screenshot || currentGame.assets.background || currentGame.assets.banner || currentGame.assets.boxFront || currentGame.assets.marquee || currentGame.assets.poster) : (boxArt(currentGame))
74 |
75 | // For Steam games that lack portrait mode art
76 | onStatusChanged: {
77 | if (status == Image.Error) {
78 | // The funny is gone
79 | console.log("No Steam Game art Found.")
80 | gameItemImage.source = currentGame.assets.boxFront;
81 | }
82 | }
83 |
84 | // The background for wide header
85 | Rectangle {
86 | id: wideHeadBG
87 | width: parent.width
88 | height: parent.height * .2
89 |
90 | anchors.right: parent.right
91 | anchors.bottom: parent.bottom
92 |
93 | visible: (settings["wide"] || (wideHead && index == 0)) && !settings["disableWideHeader"]
94 |
95 | color: "#60000000"
96 | }
97 |
98 | VideoPlayer {
99 | id: videoPlayer
100 | game: currentGame
101 |
102 | width: parent.width
103 | height: parent.height
104 | anchors.centerIn: parent
105 |
106 | playing: settings["videoPlayback"] && doubleFocus && !isFeed
107 | noSound: settings["nosfx"]
108 | }
109 | }
110 |
111 | OpacityMask {
112 | id: gameItemImageRounded
113 | anchors.fill: gameItemImage
114 | source: gameItemImage
115 | maskSource: gameItemMask
116 | }
117 |
118 |
119 |
120 | ShaderEffectSource{
121 | id: shaderSource
122 | sourceItem: gameItemImageRounded
123 | width: gameItemImage.width
124 | height: gameItemImage.height * .2
125 |
126 | anchors{
127 | right: gameItemImage.right
128 | bottom: gameItemImage.bottom
129 | }
130 |
131 | sourceRect: Qt.rect(x,y, width, height)
132 | }
133 |
134 | GaussianBlur {
135 | id: gameItemImageBlur
136 | anchors.fill: shaderSource
137 | source: shaderSource
138 | radius: 32
139 | samples: 30
140 |
141 | visible: false //wide || (wideHead && index == 0)
142 | }
143 |
144 | OpacityMask {
145 | anchors.fill: gameItemImageBlur
146 | source: gameItemImageBlur
147 | maskSource: gameItemBlurMask
148 | visible: (settings["wide"] || (wideHead && index == 0)) && !settings["disableWideHeader"]
149 | }
150 |
151 | Rectangle {
152 | id: wideViewTextAnchor
153 | visible: false
154 | width: parent.width
155 | height: parent.height * .2
156 |
157 | anchors.bottom: parent.bottom
158 | }
159 |
160 | // Wide header text
161 | Text {
162 | id: wideViewText
163 |
164 | visible: (settings["wide"] || (wideHead && index == 0)) && !settings["disableWideHeader"]
165 |
166 | anchors.verticalCenter: wideViewTextAnchor.verticalCenter
167 | anchors.left: wideViewTextAnchor.left
168 | anchors.leftMargin: parent.width * 0.05
169 |
170 | width: settings["showWideTimes"] ? parent.width * .75 : parent.width * .9
171 | height: parent.height * .2
172 |
173 | horizontalAlignment: Text.AlignLeft
174 | verticalAlignment: Text.AlignVCenter
175 |
176 | text: currentGame.title
177 | elide: Text.ElideRight
178 |
179 | // The wide header is always present in the home screen at the same size;
180 | // to accomodate smaller wide games, scale based on height.
181 | font.pixelSize: height / 3.5
182 | font.family: gilroyLight.name
183 | font.bold: true
184 |
185 | color: "white"
186 | z: wideHeadBG.z + 1
187 | }
188 |
189 | Text {
190 | id: gameTimeText
191 |
192 | visible: (settings["wide"] || (wideHead && index == 0)) && !settings["disableWideHeader"] && settings["showWideTimes"]
193 |
194 | anchors.verticalCenter: wideViewTextAnchor.verticalCenter
195 | anchors.right: wideViewTextAnchor.right
196 | anchors.rightMargin: parent.width * 0.05
197 |
198 | width: parent.width * .15
199 | height: parent.height * .2
200 |
201 | horizontalAlignment: Text.AlignRight
202 | verticalAlignment: Text.AlignVCenter
203 |
204 | text: '' + icons.clock + ' ' + formattedPlaytime(playTime) + ''
205 |
206 | font.pixelSize: height / 3.5
207 | font.family: gilroyLight.name
208 |
209 | color: "white"
210 | z: wideHeadBG.z + 1
211 |
212 | function formattedPlaytime(playtime) {
213 | if (playtime === 0) {
214 | return loc.playtime_never
215 | } else if (playtime < 60) {
216 | return playtime + loc.playtime_seconds
217 | } else if (playtime < 3600) {
218 | return Math.floor(playtime / 60) + loc.playtime_minutes
219 | } else {
220 | return Math.floor(playtime / 3600) + loc.playtime_hours
221 | }
222 | }
223 | }
224 |
225 |
226 |
227 | // Favorite Image
228 | Item {
229 | id: favImgItem
230 | width: height
231 | height: parent.height * .15
232 | anchors.top: parent.top
233 | anchors.topMargin: parent.height * .025
234 | anchors.right: parent.right
235 | anchors.rightMargin: parent.height * .025
236 | z: parent.z + 5
237 | visible: currentGame.favorite
238 | Behavior on y {
239 | SmoothedAnimation { velocity: animVel }
240 | }
241 | Text {
242 | text: icons.star
243 | anchors.centerIn: parent
244 | font {
245 | family: icons.name;
246 | pixelSize: parent.height * .6
247 | }
248 | color: colors["bottomIcons"]
249 | }
250 | }
251 |
252 | // Drop shadow for the item
253 | DropShadow {
254 | width: parent.width
255 | height: parent.height
256 |
257 | source: gameItemTextBack
258 |
259 | anchors.centerIn: parent
260 |
261 | //visible: !plainBG
262 | opacity: giShadowOp
263 |
264 | radius: giShadowRad
265 | samples: giShadowRad * 2 + 1
266 | z: gameItemTextBack.z - 1
267 | }
268 |
269 | // Drop shadow for the favorite star
270 | DropShadow {
271 | source: favImgItem
272 | anchors.fill: favImgItem
273 |
274 | visible: favImgItem.visible
275 | spread: 0.35
276 |
277 | radius: 16
278 | samples: 17
279 | z: favImgItem.z - 1
280 | }
281 |
282 |
283 |
284 | Rectangle {
285 | id: gameItemMask
286 | anchors.fill: parent
287 | visible: false
288 |
289 | radius: settings["roundedGames"] ? height / roundedGamesRadiusFactor : 0
290 | }
291 |
292 | Rectangle {
293 | id: gameItemBlurMask
294 | anchors.fill: shaderSource
295 | visible: false
296 |
297 | radius: settings["roundedGames"] ? height / roundedGamesRadiusFactor / .2 : 0
298 |
299 | Rectangle {
300 | width: parent.width
301 | height: parent.height / 2
302 |
303 | anchors.top: parent.top
304 | }
305 | }
306 |
307 |
308 | // The following code used to get portrait art for steam games is from shinretro by TigraTT-Driver.
309 |
310 | // Box art
311 | function steamAppID (gameData) {
312 | var str = gameData.assets.boxFront.split("header");
313 | return str[0];
314 | }
315 |
316 | function steamBoxFront(gameData) {
317 | return steamAppID(gameData) + "/library_600x900_2x.jpg"
318 | }
319 |
320 |
321 | function boxArt(data, failed=false) {
322 | if (data != null) {
323 | if (data.assets.boxFront.includes("header.jpg")) {
324 | if (failed) {
325 | return data.assets.boxFront;
326 | } else {
327 | return steamBoxFront(data);
328 | }
329 | }
330 | else {
331 | if (data.assets.poster != "")
332 | return data.assets.poster;
333 | else if (data.assets.boxFront != "")
334 | return data.assets.boxFront;
335 | else if (data.assets.banner != "")
336 | return data.assets.banner;
337 | else if (data.assets.tile != "")
338 | return data.assets.tile;
339 | else if (data.assets.cartridge != "")
340 | return data.assets.cartridge;
341 | else if (data.assets.logo != "")
342 | return data.assets.logo;
343 | }
344 | }
345 | return "";
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/GameItems/VideoPlayer.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.15
2 | import QtGraphicalEffects 1.12
3 | import QtMultimedia 5.15
4 |
5 | Item {
6 | id: root
7 | property var game
8 | property bool playing
9 | property bool noSound
10 |
11 | onGameChanged: {
12 | videoPreviewLoader.sourceComponent = undefined;
13 | videoDelay.restart();
14 | }
15 |
16 | onPlayingChanged: {
17 | videoPreviewLoader.sourceComponent = undefined;
18 | videoDelay.restart();
19 | }
20 |
21 | // Timer to show the video
22 | Timer {
23 | id: videoDelay
24 |
25 | interval: 600
26 | onTriggered: {
27 | if (playing && game && game.assets.videos.length) {
28 | videoPreviewLoader.sourceComponent = videoPreviewWrapper;
29 | }
30 | }
31 | }
32 |
33 | Component {
34 | id: videoPreviewWrapper
35 |
36 | Video {
37 | id: videocomponent
38 |
39 | anchors.fill: parent
40 | source: game.assets.videoList.length ? game.assets.videoList[0] : ""
41 | fillMode: VideoOutput.PreserveAspectCrop
42 | muted: noSound
43 | loops: MediaPlayer.Infinite
44 | autoPlay: true
45 | visible: game.assets.videoList.length
46 | }
47 | }
48 |
49 | Item {
50 | id: videocontainer
51 |
52 | anchors.fill: parent
53 |
54 | Loader {
55 | id: videoPreviewLoader
56 | asynchronous: true
57 | anchors { fill: parent }
58 | }
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/Localization.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 |
3 | /*
4 |
5 | TRANSLATION CREDITS:
6 |
7 | Fr75s: English (EN)
8 |
9 | TigraTT-Driver: German (DE)
10 |
11 | */
12 |
13 | Item {
14 | visible: false
15 |
16 | // Localization Chart
17 |
18 | /* Simply insert another language (e.g. "es") as a code and copy all the keys from the "en" object into your object like so:
19 | * "es": {
20 | * home_recent: "...",
21 | * home_favorite: "...",
22 | * ...
23 | * }
24 | */
25 |
26 | /* Translations are welcome: Simply create a pull request on github or message me on discord (Francisco75s#0331) or matrix (@fr75s:matrix.org) to let me know if you have a good translation, and I will add it to the theme.
27 | *
28 | * TRANSLATION NOTE: If you've previously contributed, please add any translations to any fields labeled [], as these indicate new text that need to be translated.
29 | *
30 | * See more in the Translation section of MODIFICATIONS.md
31 | */
32 |
33 | property var localization: {
34 | "en": {
35 | home_recent: "Recent Games",
36 | home_favorite: "Favorite Games",
37 | all_search: "Search",
38 | all_feed: "Feed",
39 | all_feed_instructions: "Press L2 at any time to stop.",
40 | collections_title: "Collections",
41 | settings_title: "Settings",
42 | settings_light_mode: "Light Mode",
43 | settings_plain_bg: "Plain Background",
44 | settings_disable_buttons: "Disable Button Prompts",
45 | settings_button_scheme: "Button Prompt Style",
46 | settings_rounded_corners: "Rounded Games",
47 | settings_center_titles: "Center Titles",
48 | settings_enable_clockbar: "Enable Clock",
49 | settings_wide_games: "Wide Games View",
50 | settings_wide_games_info: "Increases the width of each game, changing to art that fits closer to the wider ratio.",
51 | settings_diff_aspect: "Change Aspect Ratios",
52 | settings_diff_aspect_info: "Changes each game's aspect ratios from 2:3 and 92:43 to 3:4 and 16:9",
53 | settings_show_wide_times: "Show Playtime (Wide View)",
54 | settings_force_recent_narrow: "Override Wide 1st Recent Game",
55 | settings_force_recent_narrow_info: "Makes the first game in the \"Recent Games\" section portrait, similar to other games when Wide Games View is disabled.",
56 | settings_games_grid_rows: "Number of Rows per Screen for Games",
57 | settings_collection_grid_rows: "Number of Rows per Screen for Collections",
58 | settings_enable_touchnav: "Enable Touch Navigation Icons",
59 | settings_enable_touchnav_info: "Adds arrows on the top right of certain game grids to allow you to go back if you're using a touch device or mouse.",
60 | settings_more_recents: "More Recent Games Shown",
61 | settings_more_recents_info: "Increases the number of games on the Recent Games list from 8 to 16.",
62 | settings_force_home: "Force to Home on Return",
63 | settings_force_home_info: "Makes you go to the home page upon closing a game, rather than returning to where you were.",
64 | settings_limit_search: "Limit Search to Starting Characters",
65 | settings_limit_search_info: "Makes it so that searches search for games that start with the text searched, not games that contain the text searched (both in their titles)",
66 | settings_search_mode: "Search Mode",
67 | settings_search_mode_info: "Changes how games are found using the given search prompt in the search tab. Refer to EXTRAINFO.md in the theme folder for more info.",
68 | settings_search_mode_regular: "regular",
69 | settings_search_mode_limited: "limited",
70 | settings_search_mode_fuzzy: "fuzzy",
71 | settings_search_mode_raw: "raw",
72 | settings_enlarge_bar: "Change Bar Size",
73 | settings_bar_size_tiny: "tiny",
74 | settings_bar_size_small: "small",
75 | settings_bar_size_medium: "medium",
76 | settings_bar_size_large: "large",
77 | settings_classic_colors: "Use Classic Colorscheme",
78 | settings_classic_colors_info: "Reverts the colors of the UI back to the colors used in Library prior to version 1.2.0.",
79 | settings_disable_wide_header: "Disable Wide Game Titles",
80 | settings_quiet_sounds: "Quiet Sound Effects",
81 | settings_mute_sounds: "Mute Audio",
82 | settings_video_playback: "Video Playback",
83 | settings_blur_collects: "Blur Collection Images",
84 | settings_change_localization: "Change Language",
85 | settings_change_bg: "Change Background",
86 | settings_24h_clock: "24-hour clock",
87 |
88 | settings_header_appearance: "Appearance",
89 | settings_header_behavior: "Behavior",
90 | settings_header_av: "Audio & Video",
91 | settings_header_interface: "Interface",
92 | settings_header_localization: "Localization",
93 |
94 | playtime_never: "Never",
95 | playtime_seconds: "s",
96 | playtime_minutes: "m",
97 | playtime_hours: "h",
98 |
99 | bottomBar_changePage: "Change Page",
100 | bottomBar_aSelect: "Select",
101 | bottomBar_aPlay: "Play",
102 | bottomBar_aSetting: "Change Setting",
103 | bottomBar_bKeyboard: "Hide Keyboard",
104 | bottomBar_bExit: "Exit Collection",
105 | bottomBar_xKeyboard: "Keyboard / Backspace",
106 | bottomBar_xNext: "Next Section",
107 | bottomBar_yFavorite: "Favorite",
108 | bottomBar_yNext: "Next",
109 | bottomBar_yKeyboard: "Favorite / Space",
110 | bottomBar_yInfo: "Show Extra Info"
111 |
112 | },
113 | "de": {
114 | home_recent: "Zuletzt gespielt",
115 | home_favorite: "Lieblings Spiele",
116 | all_search: "Suche",
117 | all_feed: "Feed",
118 | all_feed_instructions: "Drücke L2 um Spiele-Feed-Modus zu verlassen.",
119 | collections_title: "Sammlungen",
120 | settings_title: "Einstellungen",
121 | settings_light_mode: "Heller-Modus",
122 | settings_plain_bg: "Einfarbiger Hintergrund",
123 | settings_disable_buttons: "Eingabehilfen deaktivieren",
124 | settings_button_scheme: "Design der Eingabehilfe",
125 | settings_rounded_corners: "Abgerundete Spiele",
126 | settings_center_titles: "Überschriften zentrieren",
127 | settings_enable_clockbar: "Uhr anzeigen",
128 | settings_wide_games: "Breite Spiele-Ansicht",
129 | settings_wide_games_info: "Erhöht die Breite jedes Spiels und wechselt zu Grafiken, die besser in das breitere Verhältnis passen.",
130 | settings_diff_aspect: "[]",
131 | settings_diff_aspect_info: "[]",
132 | settings_show_wide_times: "[]",
133 | settings_force_recent_narrow: "[]",
134 | settings_force_recent_narrow_info: "[]",
135 | settings_games_grid_rows: "Anzahl der Reihen für Spielelisten",
136 | settings_collection_grid_rows: "Anzahl der Reihen für die Sammlungen-Übersicht",
137 | settings_enable_touchnav: "Touch-Navigationssymbole aktivieren",
138 | settings_enable_touchnav_info: "Fügt Pfeile oben rechts in bestimmten Spielrastern hinzu, damit man zurückgehen kann, wenn man ein Touch-Gerät oder eine Maus benutzt.",
139 | settings_more_recents: "Zeige mehr zuletzt gespielte Spiele",
140 | settings_more_recents_info: "Erhöht die Anzahl der Spiele in der Liste der zuletzt gespielten Spiele von 8 auf 16.",
141 | settings_force_home: "[]",
142 | settings_force_home_info: "[]",
143 | settings_limit_search: "Suche auf Textanfang beschränken",
144 | settings_limit_search_info: "Sorgt dafür, dass nach Spielen gesucht wird, die mit dem gesuchten Text beginnen, und nicht nach Spielen, die den gesuchten Text enthalten (beide in ihren Titeln)",
145 | settings_search_mode: "[]",
146 | settings_search_mode_info: "[]",
147 | settings_search_mode_regular: "[]",
148 | settings_search_mode_limited: "[]",
149 | settings_search_mode_fuzzy: "[]",
150 | settings_search_mode_raw: "[]",
151 | settings_enlarge_bar: "Leistengröße",
152 | settings_bar_size_tiny: "winzig",
153 | settings_bar_size_small: "klein",
154 | settings_bar_size_medium: "mittel",
155 | settings_bar_size_large: "groß",
156 | settings_classic_colors: "Verwende klassisches Farbschema",
157 | settings_classic_colors_info: "Benutzeroberfläche verwendet Farbschemas, die in Library vor Version 1.2.0 verwendet wurden",
158 | settings_disable_wide_header: "Deaktiviere Spieletitel-Banner bei der breiten Spiele-Ansicht",
159 | settings_quiet_sounds: "Leisere Soundeffekte",
160 | settings_mute_sounds: "Soundeffekte stummschalten",
161 | settings_video_playback: "Videowiedergabe",
162 | settings_blur_collects: "Unscharfe Sammlungsbilder",
163 | settings_change_localization: "Anzeigesprache",
164 | settings_change_bg: "Hintergrund ändern",
165 | settings_24h_clock: "24-Stunden Uhr",
166 |
167 | settings_header_appearance: "Erscheinungsbild",
168 | settings_header_behavior: "Verhalten",
169 | settings_header_av: "Audio & Video",
170 | settings_header_interface: "Oberfläche",
171 | settings_header_localization: "Lokalisierung",
172 |
173 | playtime_never: "[]",
174 | playtime_seconds: "s",
175 | playtime_minutes: "m",
176 | playtime_hours: "h",
177 |
178 | bottomBar_changePage: "Seite wechseln",
179 | bottomBar_aSelect: "Auswählen",
180 | bottomBar_aPlay: "Spielen",
181 | bottomBar_aSetting: "Einstellung ändern",
182 | bottomBar_bKeyboard: "verstecke Tastatur",
183 | bottomBar_bExit: "Sammlung verlassen",
184 | bottomBar_xKeyboard: "Tastatur / Rücktaste",
185 | bottomBar_xNext: "Nächster Abschnitt",
186 | bottomBar_yFavorite: "Favorit",
187 | bottomBar_yNext: "Nächstes",
188 | bottomBar_yKeyboard: "Favorit / Leertaste",
189 | bottomBar_yInfo: "Mehr Infos"
190 | },
191 | "ja": {
192 | home_recent: "最近プレイしたゲーム",
193 | home_favorite: "お気に入りのゲーム",
194 | all_search: "検索",
195 | all_feed: "フィード",
196 | all_feed_instructions: "停止するには L2 を押してください。",
197 | collections_title: "コレクション",
198 | settings_title: "設定",
199 | settings_light_mode: "ライトモード",
200 | settings_plain_bg: "シンプル背景",
201 | settings_disable_buttons: "ボタンガイドを無効にする",
202 | settings_button_scheme: "ボタンガイドのスタイル",
203 | settings_rounded_corners: "サムネイルを角丸にする",
204 | settings_center_titles: "タイトルを中央寄せ",
205 | settings_enable_clockbar: "時計を有効にする",
206 | settings_wide_games: "ワイドゲームビュー",
207 | settings_wide_games_info: "各ゲームの幅を増やし、ワイド比率に合うようにアートワークの種類を変更します。",
208 | settings_diff_aspect: "[]",
209 | settings_diff_aspect_info: "[]",
210 | settings_show_wide_times: "プレイ時間を表示 (ワイドビュー)",
211 | settings_force_recent_narrow: "最近プレイしたゲームの 1 件目を縦長にする",
212 | settings_force_recent_narrow_info: "ワイドゲームビューが無効の場合、「最近プレイしたゲーム」に表示される 1 件目のゲームを他のゲームと同様に縦長にします。",
213 | settings_games_grid_rows: "画面に表示する行数 (ゲーム)",
214 | settings_collection_grid_rows: "画面に表示する行数 (コレクション)",
215 | settings_enable_touchnav: "タッチナビゲーションアイコンを有効にする",
216 | settings_enable_touchnav_info: "特定の画面の右上に矢印を追加し、タッチデバイスやマウスを使用している場合に戻ることができるようにします。",
217 | settings_more_recents: "最近プレイしたゲームの表示数を増やす",
218 | settings_more_recents_info: "最近プレイしたゲームの一覧に表示されるゲームの件数を 8 から 16 に増やします。",
219 | settings_force_home: "[]",
220 | settings_force_home_info: "[]",
221 | settings_limit_search: "前方一致検索",
222 | settings_limit_search_info: "検索されたテキストを含むゲームではなく、検索されたテキストで始まるゲームを検索するようにします。",
223 | settings_search_mode: "[]",
224 | settings_search_mode_info: "[]",
225 | settings_search_mode_regular: "[]",
226 | settings_search_mode_limited: "[]",
227 | settings_search_mode_fuzzy: "[]",
228 | settings_search_mode_raw: "[]",
229 | settings_enlarge_bar: "バーの大きさを変更",
230 | settings_bar_size_tiny: "極小",
231 | settings_bar_size_small: "小",
232 | settings_bar_size_medium: "中",
233 | settings_bar_size_large: "大",
234 | settings_classic_colors: "古いカラースキームを使用する",
235 | settings_classic_colors_info: "UI 配色をバージョン 1.2.0 以前の Library で使用されていたものに戻します。",
236 | settings_disable_wide_header: "ワイドビューでタイトルを表示しない",
237 | settings_quiet_sounds: "効果音の音量を下げる",
238 | settings_mute_sounds: "オーディオを消音",
239 | settings_video_playback: "ビデオを再生する",
240 | settings_blur_collects: "コレクション画像をぼかす",
241 | settings_change_localization: "言語を変更",
242 | settings_change_bg: "背景を変更",
243 | settings_24h_clock: "24時間時計",
244 |
245 | settings_header_appearance: "外観",
246 | settings_header_behavior: "動作",
247 | settings_header_av: "オーディオ & ビデオ",
248 | settings_header_interface: "インターフェース",
249 | settings_header_localization: "言語",
250 |
251 | playtime_never: "未プレイ",
252 | playtime_seconds: "秒",
253 | playtime_minutes: "分",
254 | playtime_hours: "時間",
255 |
256 | bottomBar_changePage: "ページを変更",
257 | bottomBar_aSelect: "選択",
258 | bottomBar_aPlay: "プレイ",
259 | bottomBar_aSetting: "設定を変更",
260 | bottomBar_bKeyboard: "キーボードを隠す",
261 | bottomBar_bExit: "コレクションから戻る",
262 | bottomBar_xKeyboard: "キーボード / バックスペース",
263 | bottomBar_xNext: "次のセクション",
264 | bottomBar_yFavorite: "お気に入り",
265 | bottomBar_yNext: "次へ",
266 | bottomBar_yKeyboard: "お気に入り / スペース",
267 | bottomBar_yInfo: "追加情報を表示"
268 |
269 | }
270 | }
271 |
272 | function getLocalization(lang) {
273 | return localization[lang]
274 | }
275 |
276 | function getLangs() {
277 | return Object.keys(localization)
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/MODIFICATIONS.md:
--------------------------------------------------------------------------------
1 | # Library Modifications
2 |
3 | ## Table of Contents
4 |
5 | 1. [Necessary Information](#some-information)
6 | 2. [Quick Modifications](#quick-modifications)
7 | - [Custom Color Schemes (1.2.0+)](#custom-color-schemes)
8 | - [Extra Collections](#extra-collections)
9 | 3. [Adding Translations](#translations)
10 |
11 | ## Some Information
12 |
13 | You may want to refer to the QML documentation for common types. QML has many different types, whose properties are thoroughly documented. Common QML types [can be found here](https://doc.qt.io/qt-5/qtquick-qmlmodule.html)
14 |
15 | ## Quick Modifications
16 |
17 | Here are some quick modifications you can do on Library. These don't teach you much, but they add functionality that isn't included by default or in the settings.
18 |
19 | ### Center Titles
20 |
21 | To center game titles, it is incredibly easy. The functionality is already there, though it is disabled by default.
22 |
23 | Go into theme.qml, and find the variable `centerTitles` (it should be under all the settings). Then, you would simply change it to true like so:
24 |
25 | ```
26 | property bool centerTitles: true
27 | ```
28 |
29 | This is all that is needed to make the Collections page top menu become wide in wide mode. One could get wide images if they wish, but to implement these, it would take a bit more effort in GICusart.qml, which I will not cover here.
30 |
31 |
32 | ### Custom Color Schemes
33 |
34 | Creating custom color schemes for this theme can be done by going into theme.qml and modifying the colorschemes variable.
35 |
36 | To do this, first open up theme.qml. You should then navigate to a big object with lots of strings, looking like so:
37 |
38 | ```
39 | property var colorschemes: {
40 | "polar": {
41 | "light": {
42 | "plainBG": "#F2F6FF",
43 | ...
44 | ```
45 |
46 | This is your color scheme variable, and is where you will define color schemes. I recommend adding something like so:
47 |
48 | ```
49 | },
50 | "mycolorscheme": {
51 | "light": {
52 | ...
53 | },
54 | "dark": {
55 | ...
56 | }
57 | }
58 | ```
59 |
60 | After making this, add all the colors in. You can find the names of each color by looking at either the polar or classic color schemes, which are there by default and contain color names like "accent" and "text." Use the same style as the other color schemes, replacing the hex codes or color names with whatever colors you want.
61 |
62 | Once you finish your colorscheme, simply replace the line `property var colors: ...` with the following:
63 |
64 | ```
65 | property var colors: light ? colorschemes["mycolorscheme"]["light"] : colorschemes["mycolorscheme"]["dark"]
66 | ```
67 |
68 | Now you have a custom color scheme for use with Library.
69 |
70 |
71 | ### Extra Collections
72 |
73 | You may see a few extra collections when browsing through the collection images. Here is how to do a few of them.
74 |
75 | **Windows Store Games**
76 | Surprisingly enough, Windows store games can be used and run well in Pegasus, better than steam games even. Using the following method, you will be able to run windows store games on Pegasus.
77 |
78 | 1. Install UWPHook
79 | UWPHook is a tool which enables you to put windows store games on steam, however, it also enables you to launch windows store games using relatively simple commands on the command line. [The link to the project can be found here.](https://briano.dev/UWPHook/)
80 |
81 | 2. Find out your game's code
82 | Finding out the code of your game is the hardest part. This will require you to go into your AppData folder, which is typically hidden in your user folder. Go to %APPDATA%/Local/Packages, and you will find folders containing the contents of windows store applications, but only the names are needed here. Copy the names of the applications you wish to use: you can typically discern your app from others by looking for the developer's name. Copy the names of the folders containing the apps you would like to launch, and save them for later.
83 |
84 | 3. Creating blank files
85 | To go around Pegasus's requirement for 1 file per game, we will create several files. In the same place you wish to put your metadata.pegasus.txt, create one blank file for each package name you got in step 2. Rename these to `[PACKAGE_NAME].txt`
86 |
87 | 4. Creating metadata.pegasus.txt
88 | Once this is done, metadata.pegasus.txt is relatively simple. With a default UWPHook install location:
89 | ```
90 | collection: Windows Store
91 | shortname: winuwp
92 | launch: "C:/Program Files (x86)/Briano/UWPHook/UWPHook.exe" "{file.basename}!App"
93 | ```
94 |
95 | Then, you would define games with:
96 | ```
97 | game: [GAME_NAME]
98 | file: ./[PACKAGE_NAME].txt
99 | ...
100 | ```
101 |
102 | This is all that is needed to run windows store games on pegasus.
103 |
104 |
105 |
106 | **Pico-8**
107 | Pico-8 through pegasus frontend is easy and simple to do if you have the desktop application. Once you do so, creating a metadata.pegasus.txt file is easy:
108 |
109 | ```
110 | collection: Pico-8
111 | shortname: pico8
112 | ...
113 | launch: [PATH OF PICO-8 EXECUTABLE] -run {file.path}
114 | ...
115 | ```
116 |
117 | This is good if Pico-8 is installed portably on your system; if not, simply use pico-8 rather than the full path.
118 |
119 | **Tic-80**
120 | Tic-80 comes as a libretro core, making it easy to use with retroarch. While retroarch may differ per system, using a metadata.pegasus.txt which links to the tic-80 core would allow you to run tic-80 games in retroarch.
121 |
122 |
123 | ## Adding Translations
124 |
125 | Translating this theme is done through the Localization.qml file.
126 |
127 | First, open Localization.qml and navigate to the localization property, which should look like so:
128 |
129 | ```
130 | property var localization: {
131 | "en": {
132 | home_recent: "Recent Games",
133 | home_favorite: "Favorite Games",
134 | ...
135 | }
136 | ...
137 | }
138 | ```
139 |
140 | Pay attention to the "en" object within localization. This is what you should copy to translate the theme. Create a new object with the same keys as `"en"`, then replace the English strings with those of your desired language. Afterwards, rename the `"en"` copy to the 2 letter code for your language, e.g. `"es"` for Spanish. Your localization object should now look like so (using "es"/Spanish as an example):
141 |
142 | ```
143 | property var localization: {
144 | "en": {
145 | home_recent: "Recent Games",
146 | home_favorite: "Favorite Games",
147 | ...
148 | },
149 | "es": {
150 | home_recent: "Juegos Recientes",
151 | home_favorite: "Juegos Favoritos",
152 | ...
153 | }
154 | ...
155 | }
156 | ```
157 |
158 | If your object is written properly, you are done, as Library handles the rest when it comes to using your strings and changing languages. To test your localization, change your language to the one you added by going to the Settings page and clicking the "Change Language" option, located at the bottom of the page.
159 |
160 | Afterwards, you probably want to add this to the theme officially to let others access your translation. Simply add a pull request to the github page (you can search a tutorial for that online) or message me on Discord (Francisco75s#0331; if you prefer matrix @fr75s:matrix.org), and I will happily add your translation.
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## A Clean, Modern, Bigscreen Interface for Pegasus Frontend
5 |
6 | If you are looking to use this theme, [Check out the website for this theme here.](https://fr75s.github.io/library/assets/SAFELY_REMOVABLE/website/main.html)
7 |
8 | Library is a clean, modern interface for [Pegasus Frontend](http://pegasus-frontend.org) that aims to provide a theme which doesn't emphasize text. Inspired partly by the Steam Deck's UI, this interface focuses more on providing the box art in organized categories, with little textual metadata.
9 |
10 | ***
11 |
12 | ## Installation
13 |
14 | [Full installation instructions are available here.](https://fr75s.github.io/library/assets/SAFELY_REMOVABLE/website/install.html)
15 |
16 | To install this theme, first download this repository (either through `git clone https://github.com/Fr75s/library.git` or through downloading this zip), then move (and extract if you downloaded the zip) to [your themes folder.](https://pegasus-frontend.org/docs/user-guide/installing-themes/) Finally, remove the SAFELY_REMOVABLE folder located within this theme's folder under assets.
17 |
18 | ***
19 |
20 | ## Special Thanks
21 |
22 | I used other themes as references while building Library. These themes are listed below.
23 |
24 | - https://github.com/valsou/neoretro: The framework I built this theme off of, used for the separate page file system and Colcon.qml.
25 | - https://github.com/TigraTT-Driver/shinretro: A theme which provided the functionality for portrait-style steam game box art and more. I didn't find out about this theme until Library was mostly complete.
26 | - https://github.com/PlayingKarrde/clearOS: A theme providing part of the search functionality of the theme.
27 |
28 | I would also like to thank the following people:
29 |
30 | [TigraTT-Driver](https://github.com/TigraTT-Driver/), who has contributed several new features to Library and who has provided the German translation.
31 |
32 | [waitingmoon](https://github.com/waitingmoon), who has provided the Japanese translation.
33 |
34 | ***
35 |
36 | ## Issues?
37 |
38 | If you find any issues or want to let me know of anything, feel free to open up an issue in this project. You may also want to check out the [Modifications Guide](https://github.com/Fr75s/library/blob/main/MODIFICATIONS.md) and see if I have placed a fix there. If not, then, once again, feel free to open up an issue, and I will fix it.
39 |
40 | If you know a language other than English, I encourage you to translate this theme. All relevant info is in the [Translation Section](https://github.com/Fr75s/library/blob/main/MODIFICATIONS.md) of the modifications guide and in Localization.qml.
41 |
42 | ***
43 |
44 | ## Screenshots
45 |
46 | The following shows some screenshots of the theme. All screenshots can be found on [The Pegasus Theme Gallery](https://pegasus-frontend.org/tools/themes/), or in [The SAFELY_REMOVABLE Directory.](https://github.com/Fr75s/library/tree/main/assets/SAFELY_REMOVABLE)
47 |
48 | 
49 | 
50 | 
51 | 
52 |
--------------------------------------------------------------------------------
/Theme/All.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.15
2 | import QtMultimedia 5.15
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | import SortFilterProxyModel 0.2
8 |
9 | import "../GameItems"
10 | import "PegaKey"
11 |
12 | /*
13 | *
14 | * Note
15 | * This may be called all, but this has been revitalized to a search
16 | *
17 | */
18 |
19 | FocusScope {
20 | id: search
21 |
22 | property bool feed: false
23 | property bool searching: false
24 |
25 | // Converts the text in searchInput to a regex pattern
26 | function searchInputConvert(inputText) {
27 | if (settings["searchMode"] === "raw") {
28 | return inputText;
29 | } else {
30 | const specialSymbols = ["^", "$", "\\", ".", "*", "+", "?", "(", ")", "[", "]", "{", "}", "|", "/"];
31 | const capitals = /[A-Z]/g;
32 |
33 | if (settings["searchMode"] === "fuzzy") {
34 | let outputStr = "";
35 | for (let i = 0; i < inputText.length; i++) {
36 | if (inputText[i].match(capitals)) {
37 | // Make next letter match require to start at next word
38 | outputStr += "\\b" + inputText[i];
39 | } else if (specialSymbols.includes(inputText[i])) {
40 | // Escape special character
41 | outputStr += "\\" + inputText[i];
42 | } else {
43 | outputStr += inputText[i];
44 | }
45 | // Loose Matching
46 | outputStr += ".*";
47 | }
48 | console.log(JSON.stringify(outputStr));
49 | return outputStr;
50 | } else {
51 | // Escape special symbols
52 | let regexCleanText = inputText.split("");
53 | for (let i = regexCleanText.length - 1; i >= 0; i--) {
54 | if (specialSymbols.includes(regexCleanText[i])) {
55 | regexCleanText.splice(i, 0, "\\");
56 | }
57 | }
58 | let outputStr = "";
59 | for (let i = 0; i < regexCleanText.length; i++) {
60 | outputStr += regexCleanText[i];
61 | }
62 |
63 | // Add ^ to beginning of pattern if limited search
64 | if (settings["searchMode"] === "lim") {
65 | outputStr = "^" + outputStr;
66 | }
67 | return outputStr;
68 | }
69 | }
70 | }
71 |
72 | // Define Current Game
73 | SortFilterProxyModel {
74 | id: searchSort
75 | sourceModel: api.allGames
76 |
77 | // The searching functionality for the model
78 | filters: RegExpFilter {
79 | roleName: "title";
80 | // Limited search matches ^[term] rather than [term]
81 | pattern: searchInputConvert(searchInput.text);
82 | enabled: searchInput.text != "";
83 | caseSensitivity: Qt.CaseInsensitive;
84 | }
85 | }
86 |
87 | /*
88 | * Note: The bug pertaining to not being able to run or favorite games when searching was fixed
89 | * by changing searchSort.get(allView.currentIndex) to api.allGames.get(searchSort.mapToSource(allView.currentIndex)).
90 | * This fix is from clearOS.
91 | *
92 | */
93 |
94 | // The current game
95 | property var currentGameIndex: {
96 | if (searchSort.count == 0 || searchSort.count == api.allGames.count)
97 | return allView.currentIndex;
98 | else
99 | return searchSort.mapToSource(allView.currentIndex);
100 | }
101 | property var current: {
102 | return api.allGames.get(currentGameIndex);
103 | }
104 | property var feedCurrent
105 |
106 | //
107 | // Layouts
108 | //
109 |
110 | // Title
111 | Text {
112 | id: allTitle
113 | width: parent.width * .9
114 |
115 | y: parent.height * 0.075
116 |
117 | text: feed ? loc.all_feed : loc.all_search
118 | color: colors["text"]
119 |
120 | // Alignment
121 | horizontalAlignment: Text.AlignLeft
122 |
123 | font.family: gilroyLight.name
124 | font.pixelSize: parent.height * .05
125 |
126 | anchors.left: parent.left
127 | anchors.leftMargin: parent.width * 0.05
128 | }
129 |
130 |
131 |
132 | // Search Background, to indicate it is a search field
133 | // (outside of searchInput to prevent bug where searchInput text is under the bg)
134 | Rectangle {
135 | id: searchInputBG
136 |
137 | width: searchInput.width + (searchInput.height * .5)
138 | height: searchInput.height * 1.2
139 | anchors.centerIn: searchInput
140 |
141 | radius: height * .25
142 | visible: !feed
143 |
144 | gradient: Gradient {
145 | orientation: Gradient.Horizontal
146 | GradientStop { position: 0.0; color: colors["plainSetting"] }
147 | GradientStop { position: 1.0; color: colors["plainBG"] }
148 | }
149 |
150 | // Highlight when searching
151 | Rectangle {
152 | anchors.fill: parent
153 | radius: parent.radius
154 |
155 | visible: (menu == 1) && !(allView.focus)
156 |
157 | border.color: colors["accent"]
158 | border.width: 2
159 | color: "transparent"
160 | }
161 | }
162 |
163 | DropShadow {
164 | anchors.fill: searchInputBG
165 | source: searchInputBG
166 |
167 | visible: !settings["plainBG"] && !feed
168 | opacity: giShadowOp
169 |
170 | radius: giShadowRad
171 | samples: giShadowRad * 2 + 1
172 | z: searchInputBG.z - 1
173 | }
174 |
175 | // Search text input, used for searching
176 | // Its text property is what is used for searching
177 | TextInput {
178 | id: searchInput
179 | width: parent.width * .6
180 | height: parent.height * .06
181 |
182 | visible: !feed
183 | focus: false
184 |
185 | y: parent.height * 0.075
186 |
187 | color: colors["text"]
188 |
189 | // Alignment
190 | horizontalAlignment: Text.AlignLeft
191 | verticalAlignment: Text.AlignVCenter
192 |
193 | font.family: gilroyLight.name
194 | font.pixelSize: parent.height * .05
195 |
196 | selectByMouse: true;
197 |
198 | anchors.right: parent.right
199 | anchors.rightMargin: parent.width * 0.05
200 |
201 | // Indicator for the word, used to show the end of the text
202 | Rectangle {
203 | width: 1
204 | height: parent.height
205 |
206 | anchors.left: parent.left
207 | anchors.leftMargin: parent.contentWidth
208 |
209 | visible: (menu == 1) && !(allView.focus)
210 | }
211 |
212 | // Click control, invokes keyboard
213 | MouseArea {
214 | anchors.fill: parent
215 |
216 | onClicked: {
217 | keys.invokeNoFocus();
218 | parent.focus = true;
219 | searching = true;
220 | }
221 | }
222 |
223 | Keys.onEscapePressed: {
224 | keys.hide();
225 | }
226 |
227 | Keys.onPressed: {
228 | if (event.isAutoRepeat) {
229 | return
230 | }
231 |
232 | if (api.keys.isAccept(event)) {
233 | event.accepted = true;
234 | keys.hide();
235 | }
236 | }
237 | }
238 |
239 | /* The special keyboard implementation, instantiated here
240 | * This is the main way an actual implementation of this is shown.
241 | * The keyboard's functionality can be found by looking at the files in the PegaKey folder.
242 | *
243 | */
244 | Keyboard {
245 | id: keys
246 |
247 | // Width and height are defined in the keyboard
248 | anchors.fill: parent
249 | z: 20
250 |
251 | kcolors: colors["keyboard"]
252 |
253 | sfxSource: ".././assets/audio/type.wav"
254 | quietSfx: settings["quiet"];
255 | muteSfx: settings["nosfx"];
256 |
257 | // Adds key to input, unless it's a backspace or clear
258 | onSendKey: {
259 | if (text == "bksp") {
260 | if (searchInput.text.length > 0) {
261 | searchInput.text = searchInput.text.substring(0, searchInput.text.length - 1)
262 | }
263 | } else if (text == "CLEAR") {
264 | searchInput.text = ""
265 | } else {
266 | searchInput.text = searchInput.text + text;
267 | searchInput.forceLayout()
268 | }
269 | }
270 |
271 | // Invoke provides more functionality in the keyboard itself.
272 | onInvoked: {
273 | searching = true;
274 | }
275 |
276 | // More functionality is in the keyboard itself.
277 | // It also focuses the game view.
278 | onDone: {
279 | searching = false;
280 | allView.focus = (menu == 1) && !searching;
281 | }
282 | }
283 |
284 | // All View
285 | // See all games or searched games
286 | GridView {
287 | id: allView
288 | width: settings["wide"] ? height * (2.14) : height * (2)
289 | height: settings["wide"] ? parent.height * 0.7 : parent.height * 0.75 //* (Math.ceil(api.allGames.count / 6))
290 |
291 | // Rectangle {
292 | // anchors.fill: parent
293 | // opacity: 0.1
294 | // }
295 |
296 | anchors.top: parent.top
297 | anchors.topMargin: parent.height * .175
298 | anchors.horizontalCenter: parent.horizontalCenter
299 |
300 | visible: !feed
301 |
302 | // Grid
303 | cellWidth: getCellWidth(cellHeight, true)//settings["wide"] ? (cellHeight * (92/43)) : (cellHeight * (2/3))
304 | cellHeight: height / settings["gamesRows"]
305 |
306 | // Sets the model to everything if the search is empty or contains all games
307 | model: (searchSort.count == 0 || searchSort.count == api.allGames.count) ? api.allGames : searchSort
308 |
309 | delegate: Item {
310 | readonly property bool isCurrentItem: GridView.isCurrentItem
311 | // Once again, double focus indicates this game is selected
312 | readonly property bool doubleFocus: allView.focus && isCurrentItem
313 |
314 | width: GridView.view.cellWidth
315 | height: GridView.view.cellHeight
316 |
317 | Item {
318 | anchors {
319 | fill: parent
320 | margins: (doubleFocus) ? 0 : vpx(10)
321 | }
322 |
323 | Behavior on anchors.margins {
324 | SmoothedAnimation { velocity: marginAnimVel }
325 | }
326 |
327 | GINormal { }
328 |
329 | /*
330 | Loader {
331 | anchors.fill: parent
332 | asynchronous: true
333 | sourceComponent: GINormal { }
334 | active: all.focus
335 | visible: status === Loader.Ready
336 | }
337 | */
338 |
339 | // Click functionality
340 | MouseArea {
341 | anchors.fill: parent
342 | onClicked: {
343 | if (isCurrentItem) {
344 | launchGame(modelData)
345 | }
346 | else {
347 | allView.currentIndex = index
348 | if (!settings["nosfx"])
349 | sNav.play();
350 | if (searching)
351 | searching = false
352 | }
353 | }
354 | }
355 | }
356 |
357 |
358 | }
359 |
360 | clip: true
361 | focus: (menu == 1) && !searching
362 |
363 | // Invokes search if at top, otherwise move up
364 | Keys.onUpPressed: {
365 | if (!settings["nosfx"]) sNav.play();
366 | if (settings["wide"]) {
367 | if (allView.currentIndex < settings["gamesRows"])
368 | keys.invoke()
369 | } else {
370 | if (allView.currentIndex < (settings["gamesRows"] * 3))
371 | keys.invoke()
372 | }
373 | if (!searching)
374 | moveCurrentIndexUp()
375 | }
376 | // Move down with special behavior
377 | Keys.onDownPressed: {
378 | if (!settings["nosfx"]) sNav.play();
379 |
380 | // Go to last element if no element below on non-final row
381 | if (settings["wide"]) {
382 | if (allView.currentIndex + (settings["gamesRows"]) >= allView.count && allView.currentIndex % (settings["gamesRows"]) > (allView.count - 1) % (settings["gamesRows"]))
383 | allView.currentIndex = allView.count - 1;
384 | else {
385 | moveCurrentIndexDown();
386 | }
387 | } else {
388 | if (allView.currentIndex + (settings["gamesRows"] * 3) >= allView.count && allView.currentIndex % (settings["gamesRows"] * 3) > (allView.count - 1) % (settings["gamesRows"] * 3))
389 | allView.currentIndex = allView.count - 1;
390 | else {
391 | moveCurrentIndexDown();
392 | }
393 | }
394 | }
395 | // Move left/right
396 | Keys.onLeftPressed: { if (!settings["nosfx"]) sNav.play(); moveCurrentIndexLeft() }
397 | Keys.onRightPressed: { if (!settings["nosfx"]) sNav.play(); moveCurrentIndexRight() }
398 |
399 | Keys.onPressed: {
400 | if (event.isAutoRepeat) {
401 | return
402 | }
403 |
404 | // Launch Game
405 | if (api.keys.isAccept(event)) {
406 | event.accepted = true;
407 | if (!feed) {
408 | api.memory.set("tempSavedAllGame", currentGameIndex);
409 | launchGame(current);
410 | } else {
411 | launchGame(feedCurrent);
412 | }
413 | }
414 |
415 | // Favorite Game / Skip Game Video
416 | if (api.keys.isFilters(event)) {
417 | event.accepted = true;
418 | if (!feed) {
419 | if (!settings["nosfx"])
420 | sFav.play();
421 | current.favorite = !current.favorite;
422 | } else {
423 | feedNext();
424 | }
425 | }
426 |
427 | // Quick Search with X
428 | if (api.keys.isDetails(event)) {
429 | event.accepted = true;
430 | if (!feed) {
431 | keys.invoke()
432 | }
433 | }
434 | }
435 |
436 | Component.onCompleted: {
437 | positionViewAtIndex(currentIndex, GridView.Center);
438 | }
439 | }
440 |
441 |
442 | // Feed Stuff
443 | SortFilterProxyModel {
444 | id: gamesWithVideos;
445 |
446 | sourceModel: api.allGames;
447 | filters: [
448 | ExpressionFilter { expression: { assets.video !== ''; } }
449 | ]
450 | }
451 |
452 | Video {
453 | id: feedPlayer;
454 |
455 | width: parent.width * 0.75
456 | height: parent.height * 0.75
457 | autoPlay: true
458 |
459 | anchors.horizontalCenter: parent.horizontalCenter
460 | anchors.bottom: parent.bottom
461 | anchors.bottomMargin: parent.height * 0.1
462 |
463 | visible: feed
464 |
465 | fillMode: VideoOutput.PreserveAspectFit
466 |
467 | onStatusChanged: {
468 | if (status === MediaPlayer.InvalidMedia) {
469 | console.log("VIDEO INVALID");
470 | feedNext();
471 | }
472 | if (status === MediaPlayer.EndOfMedia) {
473 | console.log("VIDEO EOF");
474 | feedNext();
475 | }
476 | }
477 |
478 | MouseArea {
479 | anchors.fill: parent
480 |
481 | onClicked: {
482 | launchGame(feedCurrent)
483 | }
484 | }
485 | }
486 |
487 | Rectangle {
488 | id: feedPlayerBG
489 | anchors.centerIn: feedPlayer;
490 |
491 | width: feedPlayer.width
492 | height: feedPlayer.height
493 |
494 | visible: feedPlayer.visible
495 |
496 | z: feedPlayer.z - 1
497 |
498 | color: colors["plainBG"]
499 | }
500 |
501 | Text {
502 | id: feedInfoLabel
503 | width: parent.width * .9
504 |
505 | y: parent.height * 0.91
506 |
507 | text: loc.all_feed_instructions
508 | color: colors["text"]
509 |
510 | visible: feed
511 |
512 | // Alignment
513 | horizontalAlignment: Text.AlignHCenter
514 |
515 | font.family: gilroyLight.name
516 | font.pixelSize: parent.height * .025
517 |
518 | anchors.left: parent.left
519 | anchors.leftMargin: parent.width * 0.05
520 | }
521 |
522 | DropShadow {
523 | anchors.fill: feedPlayerBG;
524 | source: feedPlayerBG;
525 |
526 | visible: feedPlayerBG.visible
527 |
528 | opacity: giShadowOp
529 |
530 | radius: giShadowRad * 2
531 | samples: radius * 2 + 1
532 | z: feedPlayerBG.z - 1
533 | }
534 |
535 | Timer {
536 | id: feedRepeatTimer
537 |
538 | interval: 500
539 | repeat: false
540 | running: false
541 | }
542 |
543 | Keys.onPressed: {
544 | if (event.isAutoRepeat) {
545 | return
546 | }
547 |
548 | // Feed Mode
549 | if (api.keys.isPageUp(event)) {
550 | event.accepted = true;
551 |
552 | if (feedRepeatTimer.running == false) {
553 | feed = !feed;
554 | theme.isFeed = !theme.isFeed
555 |
556 | if (feed) {
557 | feedNext();
558 | } else {
559 | feedPlayer.stop();
560 | }
561 |
562 | feedRepeatTimer.start()
563 | }
564 |
565 | }
566 | }
567 |
568 | function feedNext() {
569 | const gindex = Math.floor(Math.random() * gamesWithVideos.count);
570 | feedCurrent = api.allGames.get(gamesWithVideos.mapToSource(gindex));
571 |
572 | console.log(feedCurrent.title);
573 | feedPlayer.source = feedCurrent.assets.video;
574 |
575 | console.log(feedPlayer.source);
576 | feedPlayer.play();
577 | }
578 |
579 | Component.onCompleted: {
580 | if (api.memory.has("tempSavedAllGame")) {
581 | if (!settings["forceHome"]) {
582 | allView.currentIndex = Math.min(api.memory.get("tempSavedAllGame"), searchSort.count === 0 ? api.allGames.count : searchSort.count);
583 | }
584 | api.memory.unset("tempSavedAllGame");
585 | }
586 | feedPlayer.stop();
587 | }
588 |
589 | Connections {
590 | target: theme
591 |
592 | function onVideoControl(pause) {
593 | if (pause) {
594 | feedPlayer.pause()
595 | } else if (feed) {
596 | feedPlayer.play()
597 | }
598 | }
599 | }
600 |
601 | }
602 |
--------------------------------------------------------------------------------
/Theme/Collections.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | import SortFilterProxyModel 0.2
8 |
9 | import "../GameItems"
10 |
11 | FocusScope {
12 | id: collects
13 |
14 | /* gameView shows the games in a collection once clicked.
15 | * It sets the visible property of each GridView here.
16 | * false: Collections View
17 | * true: Game View
18 | */
19 | property bool gameView: false
20 |
21 | /* Sets whether or not you're in a collection
22 | * 0: Collections
23 | * 1: Games
24 | */
25 | property var collection: 0
26 |
27 | // Adds a delay to mouse clicks on the collection screen so that they don't immediately click a game
28 | property bool interact: true
29 |
30 | // Collections Title
31 | Text {
32 | id: tlText
33 | width: parent.width * .9
34 |
35 | y: parent.height * 0.075
36 |
37 | text: loc.collections_title
38 | color: colors["text"]
39 |
40 | font.family: gilroyLight.name
41 | font.pixelSize: parent.height * .05
42 |
43 | // Alignment
44 | horizontalAlignment: settings["centerTitles"] ? Text.AlignHCenter : Text.AlignLeft
45 |
46 | anchors.left: parent.left
47 | anchors.leftMargin: parent.width * 0.05
48 |
49 | opacity: gameView ? 0 : 1
50 | }
51 |
52 | // Games Title
53 | Text {
54 | id: llText
55 | width: parent.width * .9
56 |
57 | y: parent.height * 0.075
58 |
59 | text: collection.name
60 | color: colors["text"]
61 |
62 | font.family: gilroyLight.name
63 | font.pixelSize: parent.height * .05
64 |
65 | // Alignment
66 | horizontalAlignment: settings["centerTitles"] ? Text.AlignHCenter : Text.AlignLeft
67 |
68 | anchors.left: parent.left
69 | anchors.leftMargin: parent.width * 0.05
70 |
71 | opacity: gameView ? 1 : 0
72 | }
73 |
74 | // Clickable Mouse Navigation
75 | Item {
76 | height: parent.height * 0.075
77 | width: height
78 | y: parent.height * 0.075
79 | anchors.right: parent.right
80 | anchors.rightMargin: parent.width * 0.05
81 | visible: gameView && settings["mouseNav"]
82 | Behavior on y {
83 | SmoothedAnimation { velocity: animVel }
84 | }
85 | Text {
86 | text: icons.touch_up
87 | anchors.centerIn: parent
88 | font {
89 | family: icons.name;
90 | pixelSize: parent.height * .6
91 | }
92 | color: colors["text"]
93 | }
94 |
95 | MouseArea {
96 | anchors.fill: parent
97 | onClicked: {
98 | if (!settings["nosfx"])
99 | sBack.play();
100 | gameView = false;
101 | }
102 | }
103 | }
104 |
105 | /* collectionsView: Shows collections
106 | * Works just like a gameView, but has some key differences
107 | *
108 | */
109 | GridView {
110 | id: collectionsView
111 | width: height * (2)
112 | height: parent.height * 0.75 //* (Math.ceil(api.allGames.count / 6))
113 |
114 | anchors.top: parent.top
115 | anchors.topMargin: parent.height * .15
116 | anchors.horizontalCenter: parent.horizontalCenter
117 |
118 | cellWidth: (cellHeight * (2/3))
119 | cellHeight: height / settings["collectionRows"]
120 |
121 | // Collections model from Pegasus
122 | model: api.collections
123 |
124 | delegate: Item {
125 | readonly property bool isCurrentItem: GridView.isCurrentItem
126 | // doubleFocus again focuses the highlighted item
127 | readonly property bool doubleFocus: collectionsView.focus && isCurrentItem
128 |
129 | width: GridView.view.cellWidth
130 | height: GridView.view.cellHeight
131 |
132 | Item {
133 | anchors {
134 | fill: parent
135 | margins: (doubleFocus) ? 0 : vpx(10)
136 | }
137 |
138 | Behavior on anchors.margins {
139 | SmoothedAnimation { velocity: marginAnimVel }
140 | }
141 |
142 | /* Instead of GINormal, this view uses GICusart
143 | * This is to permit collection art to show rather than game art.
144 | */
145 | GICusart{ }
146 |
147 | /*
148 | Loader {
149 | anchors.fill: parent
150 | asynchronous: true
151 | sourceComponent: GICusart{}
152 | active: collects.focus
153 | visible: status === Loader.Ready
154 | }
155 | */
156 |
157 | // Click functionality
158 | MouseArea {
159 | anchors.fill: parent
160 |
161 | onClicked: {
162 | if (isCurrentItem) {
163 | if (!settings["nosfx"]) sAccept.play();
164 | collection = api.collections.get(collectionsView.currentIndex);
165 |
166 | // We set interact to false so that the click doesn't interact
167 | interact = false;
168 | gameView = true;
169 |
170 | // Timeout is .5s, enough to let go of the previous click
171 | setTimeout({interact = true}, 500)
172 | } else {
173 | if (!settings["nosfx"]) sNav.play();
174 | collectionsView.currentIndex = index;
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | clip: true
182 | focus: (menu == 2) && !(gameView)
183 | visible: !gameView
184 |
185 | // Go up
186 | Keys.onUpPressed: {
187 | if (!settings["nosfx"]) sNav.play();
188 | moveCurrentIndexUp();
189 | }
190 |
191 | // Go down with special property
192 | Keys.onDownPressed: {
193 | if (!settings["nosfx"]) sNav.play();
194 |
195 | // Go to last element if no element below on non-final row
196 | if (collectionsView.currentIndex + (settings["collectionRows"] * 3) >= collectionsView.count && collectionsView.currentIndex % (settings["collectionRows"] * 3) > (collectionsView.count - 1) % (settings["collectionRows"] * 3))
197 | collectionsView.currentIndex = collectionsView.count - 1;
198 | else {
199 | moveCurrentIndexDown();
200 | }
201 | }
202 | // Go left/right
203 | Keys.onLeftPressed: { if (!settings["nosfx"]) sNav.play(); moveCurrentIndexLeft() }
204 | Keys.onRightPressed: { if (!settings["nosfx"]) sNav.play(); moveCurrentIndexRight() }
205 |
206 | Keys.onPressed: {
207 | // Stop auto repeat: Don't open a game if A is held down.
208 | if (event.isAutoRepeat) {
209 | return
210 | }
211 |
212 | // Open the current collection
213 | if (api.keys.isAccept(event)) {
214 | if (!settings["nosfx"])
215 | sAccept.play();
216 | // Set the current Collection
217 | collection = api.collections.get(collectionsView.currentIndex)
218 | gameView = true
219 | }
220 | }
221 |
222 | Component.onCompleted: {
223 | positionViewAtIndex(currentIndex, GridView.Center);
224 | }
225 | }
226 |
227 | // Actual Games in a collection
228 | GridView {
229 | id: collectionGamesView
230 | width: settings["wide"] ? height * (2.14) : height * (2)
231 | height: settings["wide"] ? parent.height * 0.7 : parent.height * 0.75 //* (Math.ceil(api.allGames.count / 6))
232 |
233 | anchors.top: parent.top
234 | anchors.topMargin: parent.height * .15
235 | anchors.horizontalCenter: parent.horizontalCenter
236 |
237 | cellWidth: getCellWidth(cellHeight, true)//settings["wide"] ? (cellHeight * (92/43)) : (cellHeight * (2/3))
238 | cellHeight: height / settings["gamesRows"]
239 |
240 | // Games in the current collection is the model
241 | model: collection.games
242 |
243 | delegate: Item {
244 | readonly property bool isCurrentItem: GridView.isCurrentItem
245 | // Again, doubleFocus, doing the same thing
246 | readonly property bool doubleFocus: collectionGamesView.focus && isCurrentItem
247 |
248 | width: GridView.view.cellWidth
249 | height: GridView.view.cellHeight
250 |
251 | Item {
252 | anchors {
253 | fill: parent
254 | margins: (doubleFocus) ? 0 : vpx(10)
255 | }
256 |
257 | Behavior on anchors.margins {
258 | SmoothedAnimation { velocity: marginAnimVel }
259 | }
260 |
261 | GINormal { }
262 |
263 | /*
264 | Loader {
265 | anchors.fill: parent
266 | asynchronous: true
267 | sourceComponent: GINormal{}
268 | active: collects.focus
269 | visible: status === Loader.Ready
270 | }
271 | */
272 |
273 | // Click functionality
274 | MouseArea {
275 | anchors.fill: parent
276 | onClicked: {
277 | if (isCurrentItem) {
278 | api.memory.set("tempSavedCollection", collectionsView.currentIndex);
279 | api.memory.set("tempSavedCollectionGame", index);
280 | launchGame(modelData);
281 | }
282 | else {
283 | collectionGamesView.currentIndex = index;
284 | if (!settings["nosfx"])
285 | sNav.play();
286 | }
287 | }
288 | }
289 | }
290 | }
291 |
292 | clip: true
293 | focus: (menu == 2) && (gameView)
294 | visible: gameView
295 |
296 | // Move up, down, left & right like collectionsView
297 | Keys.onUpPressed: {
298 | if (!settings["nosfx"]) sNav.play();
299 | moveCurrentIndexUp();
300 | }
301 | Keys.onDownPressed: {
302 | if (!settings["nosfx"]) sNav.play();
303 |
304 | // Go to last element if no element below on non-final row
305 | if (settings["wide"]) {
306 | if (collectionGamesView.currentIndex + (settings["gamesRows"]) >= collectionGamesView.count && collectionGamesView.currentIndex % (settings["gamesRows"]) > (collectionGamesView.count - 1) % (settings["gamesRows"]))
307 | collectionGamesView.currentIndex = collectionGamesView.count - 1;
308 | else {
309 | moveCurrentIndexDown();
310 | }
311 | } else {
312 | if (collectionGamesView.currentIndex + (settings["gamesRows"] * 3) >= collectionGamesView.count && collectionGamesView.currentIndex % (settings["gamesRows"] * 3) > (collectionGamesView.count - 1) % (settings["gamesRows"] * 3))
313 | collectionGamesView.currentIndex = collectionGamesView.count - 1;
314 | else {
315 | moveCurrentIndexDown();
316 | }
317 | }
318 | }
319 | Keys.onLeftPressed: { if (!settings["nosfx"]) sNav.play(); moveCurrentIndexLeft() }
320 | Keys.onRightPressed: { if (!settings["nosfx"]) sNav.play(); moveCurrentIndexRight() }
321 |
322 | Keys.onPressed: {
323 | if (event.isAutoRepeat) {
324 | return
325 | }
326 |
327 | // Launch game, only on interact
328 | if (api.keys.isAccept(event)) {
329 | if (interact) {
330 | event.accepted = true;
331 | api.memory.set("tempSavedCollection", collectionsView.currentIndex);
332 | api.memory.set("tempSavedCollectionGame", collectionGamesView.currentIndex);
333 | launchGame(collection.games.get(collectionGamesView.currentIndex))
334 | }
335 | }
336 |
337 | // Go back to the collections
338 | if (api.keys.isCancel(event)) {
339 | if (!settings["nosfx"])
340 | sBack.play();
341 | event.accepted = true;
342 | gameView = false
343 | }
344 |
345 | // Favorite a game
346 | if (api.keys.isFilters(event)) {
347 | if (!settings["nosfx"])
348 | sFav.play();
349 | event.accepted = true;
350 | collection.games.get(collectionGamesView.currentIndex).favorite = !collection.games.get(collectionGamesView.currentIndex).favorite;
351 | }
352 | }
353 |
354 | Component.onCompleted: {
355 | positionViewAtIndex(currentIndex, GridView.Center);
356 | }
357 | }
358 |
359 | Component.onCompleted: {
360 | if (api.memory.has("tempSavedCollection")) {
361 | if (!settings["forceHome"]) {
362 | if (api.memory.get("tempSavedCollection") >= 0 &&
363 | api.memory.get("tempSavedCollection") < api.collections.count) {
364 | collectionsView.currentIndex = api.memory.get("tempSavedCollection");
365 |
366 | collection = api.collections.get(collectionsView.currentIndex);
367 | gameView = true;
368 |
369 | collectionGamesView.currentIndex = api.memory.get("tempSavedCollectionGame");
370 | }
371 | }
372 |
373 | api.memory.unset("tempSavedCollection")
374 | api.memory.unset("tempSavedCollectionGame")
375 | }
376 | }
377 |
378 | }
379 |
--------------------------------------------------------------------------------
/Theme/Home.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtMultimedia 5.9
3 | import QtQuick.Layouts 1.15
4 | import QtGraphicalEffects 1.15
5 | import QtQuick.Window 2.15
6 |
7 | import SortFilterProxyModel 0.2
8 |
9 | import "../GameItems"
10 |
11 | FocusScope {
12 | id: home
13 |
14 | /* Focused changes the view for the home screen.
15 | * It scrolls the page to the favorites when they are focused.
16 | * 0: recent
17 | * 1: favorites
18 | */
19 | property int focused: 0
20 |
21 | // Add more recent games with moreRecents
22 | property int maxRecents: settings["moreRecent"] ? 20 : 10
23 |
24 | // Sort Recents
25 |
26 | // Sorts by recent games, includes all games
27 | SortFilterProxyModel {
28 | id: sort_last_played_base
29 | sourceModel: api.allGames
30 | sorters: RoleSorter { roleName: "lastPlayed"; sortOrder: Qt.DescendingOrder; }
31 | }
32 |
33 | // Sorts the recent games model up to maxRecents
34 | SortFilterProxyModel {
35 | id: recent
36 | sourceModel: sort_last_played_base
37 | filters: IndexFilter { minimumIndex: 0; maximumIndex: maxRecents; }
38 | }
39 |
40 | // Sort Favorites
41 |
42 | // Filters by all favorite games, sorting by ascending order.
43 | SortFilterProxyModel {
44 | id: favorites
45 | sourceModel: api.allGames
46 | sorters: RoleSorter { roleName: "sortBy"; sortOrder: Qt.AscendingOrder; }
47 | filters: ValueFilter { roleName: "favorite"; value: true; }
48 | }
49 |
50 | // Define Current Game
51 | /*
52 | property var currentGame: {
53 | if (focused == 0) // If on recent, set to the current recent view game.
54 | api.allGames.get(sort_last_played_base.mapToSource(recentView.currentIndex))
55 | if (focused == 1) // If on favorites, set to the current favorited game
56 | api.allGames.get(favorites.mapToSource(favoriteView.currentIndex))
57 | else
58 | return null
59 | }
60 | */
61 |
62 | // This is the y position of the whole UI. It moves the recent view out of the way when the favorites are focused.
63 | property var uiY: {
64 | if (focused == 1)
65 | parent.height * -0.4
66 | else
67 | parent.height * 0.15
68 | }
69 |
70 | // Animation velocity for the vertical movement
71 | property int animVel: 1200
72 |
73 | //
74 | // Layouts
75 | //
76 |
77 | // Recent Games Text
78 | Text {
79 | id: recentTitle
80 | width: parent.width * .9
81 |
82 | y: uiY - parent.height * 0.075
83 |
84 | text: loc.home_recent
85 | color: colors["text"]
86 |
87 | font.family: gilroyLight.name
88 | font.pixelSize: parent.height * .05
89 |
90 | // Alignment
91 | horizontalAlignment: settings["centerTitles"] ? Text.AlignHCenter : Text.AlignLeft
92 |
93 | anchors.left: parent.left
94 | anchors.leftMargin: parent.width * 0.05
95 |
96 | Behavior on y {
97 | SmoothedAnimation { velocity: animVel }
98 | }
99 | }
100 |
101 | // Favorite Games Text
102 | Text {
103 | id: favTitle
104 | width: parent.width * .9
105 |
106 | y: uiY + parent.height * 0.475
107 |
108 | text: loc.home_favorite
109 | color: colors["text"]
110 |
111 | font.family: gilroyLight.name
112 | font.pixelSize: parent.height * .05
113 |
114 | // Alignment
115 | horizontalAlignment: settings["centerTitles"] ? Text.AlignHCenter : Text.AlignLeft
116 |
117 | anchors.left: parent.left
118 | anchors.leftMargin: parent.width * 0.05
119 |
120 | Behavior on y {
121 | SmoothedAnimation { velocity: animVel }
122 | }
123 | }
124 |
125 | // Clickable icon to scroll back up
126 | Item {
127 | width: height
128 | height: parent.height * 0.1
129 | y: uiY + parent.height * 0.45
130 | anchors.right: parent.right
131 | anchors.rightMargin: parent.width * 0.05
132 | visible: settings["mouseNav"]
133 |
134 | Behavior on y {
135 | SmoothedAnimation { velocity: animVel }
136 | }
137 |
138 | Text {
139 | text: icons.touch_up
140 | anchors.centerIn: parent
141 | font {
142 | family: icons.name;
143 | pixelSize: parent.height * .6
144 | }
145 | color: colors["text"]
146 | }
147 |
148 | MouseArea {
149 | anchors.fill: parent
150 | onClicked: {
151 | if (focused == 1) {
152 | if (!settings["nosfx"])
153 | sNav.play();
154 | focused = 0;
155 | }
156 | }
157 | }
158 | }
159 |
160 | // Recent Games List
161 | // Shows all the games you've recently played.
162 | ListView {
163 | id: recentView
164 | width: parent.width * 0.9
165 | height: parent.height * 0.4
166 |
167 | y: uiY
168 |
169 | orientation: ListView.Horizontal
170 | model: recent
171 |
172 | displayMarginEnd: 20
173 |
174 | delegate: Item {
175 | readonly property bool isCurrentItem: ListView.isCurrentItem
176 | // doubleFocus property only selects game when the view and item is this one
177 | readonly property bool doubleFocus: recentView.focus && isCurrentItem
178 |
179 | // Set this game to be wide if wide mode is enabled,
180 | // OR if it is the first game and the override is disabled.
181 | width: settings["wide"] || (index == 0 && !settings["forceRecentNarrow"]) ? ListView.view.height * (settings["diffAspect"] ? 16/9 : 92/43) : ListView.view.height * (settings["diffAspect"] ? 3/4 : 2/3)
182 | height: ListView.view.height
183 |
184 | Item {
185 | id: gameItemRoot
186 |
187 | anchors {
188 | fill: parent
189 | margins: (doubleFocus) ? 0 : vpx(10)
190 | }
191 |
192 | Behavior on anchors.margins {
193 | SmoothedAnimation { velocity: marginAnimVel }
194 | }
195 |
196 | // wideHead is set based on forceRecentNarrow, changing art and visual
197 | // properties of the game
198 | GINormal { wideHead: !settings["forceRecentNarrow"] }
199 |
200 | /*
201 | Loader {
202 | anchors.fill: parent
203 | asynchronous: true
204 | sourceComponent: GINormal { wideHead: true }
205 | active: (home.focus)
206 | visible: status === Loader.Ready
207 | }
208 | */
209 |
210 | // Click Functionality
211 | MouseArea {
212 | anchors.fill: parent
213 | onClicked: {
214 | if (doubleFocus)
215 | launchGame(modelData);
216 | else
217 | if (focused == 1)
218 | focused = 0;
219 | if (!settings["nosfx"])
220 | sNav.play();
221 | recentView.currentIndex = index;
222 | }
223 | }
224 | }
225 | }
226 |
227 | // Instant scrolling
228 | highlightMoveDuration: 0
229 | highlightResizeDuration: 0
230 |
231 | anchors.right: parent.right
232 | anchors.rightMargin: parent.width * 0.05
233 |
234 | clip: false
235 | focus: (focused == 0) && (menu == 0)
236 |
237 | Behavior on y {
238 | SmoothedAnimation { velocity: animVel }
239 | }
240 |
241 | // Scroll down when down is pressed
242 | Keys.onDownPressed: {
243 | if (!settings["nosfx"]) sNav.play();
244 | focused = 1;
245 | }
246 |
247 | // Move left/right
248 | Keys.onLeftPressed: {
249 | if (!settings["nosfx"]) sNav.play();
250 | decrementCurrentIndex();
251 | }
252 | Keys.onRightPressed: {
253 | if (!settings["nosfx"]) sNav.play();
254 | incrementCurrentIndex();
255 | }
256 |
257 | // Game Actions
258 | Keys.onPressed: {
259 | if (event.isAutoRepeat) {
260 | return
261 | }
262 |
263 | // Launch the current Game
264 | if (api.keys.isAccept(event)) {
265 | event.accepted = true;
266 | launchGame(api.allGames.get(sort_last_played_base.mapToSource(recentView.currentIndex)))
267 | }
268 |
269 | // Favorite/Unfavorite the current game
270 | if (api.keys.isFilters(event)) {
271 | event.accepted = true;
272 | if (!settings["nosfx"])
273 | sFav.play();
274 |
275 | api.allGames.get(sort_last_played_base.mapToSource(recentView.currentIndex)).favorite = !api.allGames.get(sort_last_played_base.mapToSource(recentView.currentIndex)).favorite;
276 | }
277 | }
278 | }
279 |
280 | // Favorite Games List
281 | // Shows all the games you've favorited.
282 | GridView {
283 | id: favoriteView
284 | width: settings["wide"] ? height * (2.14) : height * (2)
285 | height: settings["wide"] ? parent.height * 0.7 : parent.height * 0.75 //* (Math.ceil(favorites.count / 6))
286 |
287 | y: uiY + parent.height * 0.575
288 | anchors.horizontalCenter: parent.horizontalCenter
289 |
290 | cellWidth: getCellWidth(cellHeight, true) //settings["wide"] ? (cellHeight * (92/43)) : (cellHeight * (2/3))
291 | cellHeight: height / settings["gamesRows"] /// (Math.ceil(favorites.count / 6))
292 |
293 | //currentIndex: 0
294 | model: favorites
295 |
296 | delegate: Item {
297 | readonly property bool isCurrentItem: GridView.isCurrentItem
298 | // doubleFocus property only selects game when the view and item is this one
299 | readonly property bool doubleFocus: favoriteView.focus && isCurrentItem
300 |
301 | width: GridView.view.cellWidth
302 | height: GridView.view.cellHeight
303 |
304 | Item {
305 | anchors {
306 | fill: parent
307 | margins: (doubleFocus) ? 0 : vpx(10)
308 | }
309 |
310 | Behavior on anchors.margins {
311 | SmoothedAnimation { velocity: marginAnimVel }
312 | }
313 |
314 | GINormal { }
315 |
316 | // Click behavior
317 | MouseArea {
318 | anchors.fill: parent
319 | onClicked: {
320 | if (doubleFocus)
321 | launchGame(modelData);
322 | else
323 | if (focused == 0)
324 | focused = 1;
325 | if (!settings["nosfx"])
326 | sNav.play();
327 | favoriteView.currentIndex = index;
328 | }
329 | }
330 | }
331 | }
332 |
333 | clip: true
334 | focus: (focused == 1) && (menu == 0)
335 |
336 | Behavior on y {
337 | SmoothedAnimation { velocity: animVel }
338 | }
339 |
340 | // Scroll back up if at the top, otherwise just move up
341 | Keys.onUpPressed: {
342 | if (!settings["nosfx"])
343 | sNav.play();
344 |
345 | /*
346 |
347 | TOP ROW:
348 | normal:
349 | n * 3 rows
350 | wide:
351 | n rows
352 |
353 | */
354 |
355 | if (settings["wide"]) {
356 | if (favoriteView.currentIndex < settings["gamesRows"])
357 | focused = 0
358 | } else {
359 | if (favoriteView.currentIndex < (settings["gamesRows"] * 3))
360 | focused = 0
361 | }
362 | if (focused == 1)
363 | moveCurrentIndexUp();
364 | }
365 |
366 | // Scroll down
367 | Keys.onDownPressed: {
368 | if (!settings["nosfx"]) sNav.play();
369 |
370 | // Go to last element if no element below on non-final row
371 | if (settings["wide"]) {
372 | if (favoriteView.currentIndex + (settings["gamesRows"]) >= favoriteView.count && favoriteView.currentIndex % (settings["gamesRows"]) > (favoriteView.count - 1) % (settings["gamesRows"]))
373 | favoriteView.currentIndex = favoriteView.count - 1;
374 | else {
375 | moveCurrentIndexDown();
376 | }
377 | } else {
378 | if (favoriteView.currentIndex + (settings["gamesRows"] * 3) >= favoriteView.count && favoriteView.currentIndex % (settings["gamesRows"] * 3) > (favoriteView.count - 1) % (settings["gamesRows"] * 3))
379 | favoriteView.currentIndex = favoriteView.count - 1;
380 | else {
381 | moveCurrentIndexDown();
382 | }
383 | }
384 | }
385 |
386 | // Move left/right
387 | Keys.onLeftPressed: {
388 | if (!settings["nosfx"]) sNav.play();
389 | moveCurrentIndexLeft();
390 | }
391 | Keys.onRightPressed: {
392 | if (!settings["nosfx"]) sNav.play();
393 | moveCurrentIndexRight();
394 | }
395 |
396 | // Game Actions
397 | Keys.onPressed: {
398 | if (event.isAutoRepeat) {
399 | return
400 | }
401 |
402 | // Launch selected game
403 | if (api.keys.isAccept(event)) {
404 | event.accepted = true;
405 | launchGame(api.allGames.get(favorites.mapToSource(favoriteView.currentIndex)))
406 | }
407 | }
408 | }
409 | }
410 |
--------------------------------------------------------------------------------
/Theme/PegaKey/KeyButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import QtMultimedia 5.9
3 |
4 | Rectangle {
5 | /* KeyButton
6 | * Defines the default style for keyboard buttons as a RoundButton
7 | *
8 | */
9 |
10 | signal clicked
11 |
12 | id: btn
13 | radius: 16
14 |
15 | property var clickedColor: "#000000"
16 | property var regColor: "#FFFFFF"
17 | property var textColor: "#888888"
18 |
19 | color: clickArea.containsPress ? clickedColor : regColor
20 |
21 | property string label: ""
22 | property bool large: false
23 |
24 | Text {
25 | anchors.centerIn: parent
26 |
27 | text: label
28 | color: textColor
29 |
30 | font.family: gilroyLight.name
31 | font.pixelSize: large ? btn.height / 2 : btn.height / 5
32 | }
33 |
34 | MouseArea {
35 | id: clickArea
36 | anchors.fill: parent
37 |
38 | onPressed: {
39 | btn.clicked()
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Theme/PegaKey/Keyboard.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import QtMultimedia 5.9
3 |
4 | Item {
5 |
6 | /* Keyboard
7 | * Keyboard.qml instantiates a KeyboardObject. It is not the keyboard itself.
8 | * It obviously shows and hides the keyboard, but it also converts several keys.
9 | *
10 | */
11 |
12 | id: kcreator
13 |
14 | focus: false
15 |
16 | // Signals
17 | signal invoked()
18 | signal sendKey(string text)
19 | signal done()
20 |
21 | // Default Keyboard Colors
22 | property var kcolors: {
23 | "bg": "#FFFFFF", // KEYBOARD: Background
24 | "key": "#DDDDDD", // KEYBOARD: Key Background
25 | "keyPush": "#BBBBBB", // KEYBOARD: Key Background when clicked
26 | "keyHighlight": "#121212", // KEYBOARD: Selected Key Highlight
27 | "text": "#121212" // KEYBOARD: Text
28 | }
29 |
30 | // Quick Character Setting
31 | property string back: "\u2190"
32 | property string enter: "\u2192"
33 | property string shiftc: "\u2191"
34 |
35 | property url sfxSource: ""
36 | property bool quietSfx: false
37 | property bool muteSfx: false
38 |
39 | // Typing Sound Effect
40 | SoundEffect {
41 | id: kSfx
42 | source: sfxSource
43 | volume: quietSfx ? 0.5 : 1.0
44 | }
45 |
46 | // Invokes the keyboard (shows it)
47 | function invoke() {
48 | keyboard.visible = true;
49 | keyboard.focus = true;
50 | // This allows the keyboard to immediately accept presses vs waiting to move the cursor
51 | keyboard.focusEnableKB();
52 | }
53 |
54 | function invokeNoFocus() {
55 | keyboard.visible = true;
56 | }
57 |
58 | function hide() {
59 | kcreator.done();
60 | keyboard.focus = false;
61 | keyboard.visible = false;
62 | }
63 |
64 | // The actual Keyboard Object Keyboard.qml shows
65 | KeyboardObject {
66 | id: keyboard
67 | visible: false
68 | colors: kcolors
69 |
70 | backChar: back
71 | enterChar: enter
72 | shiftChar: shiftc
73 |
74 | width: theme.width
75 | height: theme.height * 0.6
76 |
77 | anchors.bottom: parent.bottom
78 |
79 | // Send Key is a signal of KeyboardObject.qml as well.
80 | onSendKey: {
81 | if (!muteSfx) {
82 | kSfx.play();
83 | }
84 |
85 | if (text == back) { // Backspace Unicode Conversion
86 | kcreator.sendKey("bksp")
87 | } else if (text == enter) { // Enter Unicode Conversion
88 | keyboard.done()
89 | } else if (text == shiftc) { // Shift Unicode Conversion
90 | keyboard.shift = !keyboard.shift
91 | } else if (text == "&123" || text == "ABCD") { // Alt Conversion
92 | keyboard.alts = !keyboard.alts
93 | } else {
94 | kcreator.sendKey(text)
95 | if (keyboard.shift) {
96 | keyboard.shift = false
97 | }
98 | }
99 | }
100 |
101 | // Hides keyboard once done
102 | onDone: {
103 | hide();
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/Theme/PegaKey/KeyboardObject.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import QtMultimedia 5.9
3 |
4 | Item {
5 | id: kRoot
6 |
7 | /* KeyboardObject
8 | * The actual keyboard layout itself
9 | *
10 | */
11 |
12 | // Signals
13 | signal sendKey(string text)
14 | signal done()
15 |
16 |
17 |
18 | // Character Properties
19 | property string backChar: "bksp"
20 | property string enterChar: "enter"
21 | property string shiftChar: "shft"
22 |
23 |
24 | // Some properties that modify the characters
25 | property bool shift: false
26 | property bool alts: false
27 |
28 | // Quick Rowspacing and columnspacing for easy access (gutter)
29 | property real rowspacing: width * 0.005
30 | property real colspacing: rowspacing //height * 0.01
31 |
32 | // Highlight color
33 | property var colors: {}
34 |
35 | // Current Row & Column
36 | property int curRow: 0
37 | property int curCol: 0
38 |
39 | // Block clicking below keyboard
40 | MouseArea {anchors.fill: parent}
41 |
42 | // Background: Parent rectangle has roundness
43 | Rectangle {
44 | anchors.fill: parent
45 | color: colors["bg"]
46 |
47 | radius: height / 16
48 |
49 | Rectangle {
50 | anchors.fill: parent
51 | color: parent.color
52 |
53 | anchors.topMargin: height / 2
54 | }
55 | }
56 |
57 | // Move up/down keyboard
58 | Keys.onUpPressed: {
59 | if (curRow <= 0) {
60 | curRow = 3
61 | } else {
62 | curRow -= 1
63 | }
64 | }
65 | Keys.onDownPressed: {
66 | if (curRow >= 3) {
67 | curRow = 0
68 | } else {
69 | curRow += 1
70 | }
71 | }
72 |
73 | // Move left on the keyboard. Note that wider keys are hardcoded here.
74 | Keys.onLeftPressed: {
75 | if (curRow == 0) {
76 | if (curCol <= 0) {
77 | curCol = 10
78 | } else {
79 | curCol -= 1
80 | }
81 | } else if (curRow == 1) {
82 | if (curCol <= 0) {
83 | curCol = 10
84 | } else {
85 | curCol -= 1
86 | }
87 | if (curCol >= 9) {
88 | curCol -= 1
89 | }
90 | } else if (curRow == 2) {
91 | if (curCol <= 1) {
92 | curCol = 10
93 | } else {
94 | curCol -= 1
95 | }
96 | if (curCol >= 9) {
97 | curCol -= 1
98 | }
99 | } else if (curRow == 3) {
100 | if (curCol <= 1) {
101 | curCol = 10
102 | } else if (curCol >= 3 && curCol <= 7) {
103 | curCol = 2
104 | } else {
105 | curCol -= 1
106 | }
107 | if (curCol >= 9) {
108 | curCol -= 1
109 | }
110 | }
111 | }
112 |
113 | // Move right on the keyboard. Note that wider keys are hardcoded here as well.
114 | Keys.onRightPressed: {
115 | if (curRow == 0) {
116 | if (curCol >= 10) {
117 | curCol = 0
118 | } else {
119 | curCol += 1
120 | }
121 | } else if (curRow == 1) {
122 | if (curCol >= 9) {
123 | curCol = 0
124 | } else {
125 | curCol += 1
126 | }
127 | } else if (curRow == 2) {
128 | if (curCol >= 9) {
129 | curCol = 0
130 | } else {
131 | curCol += 1
132 | }
133 | if (curCol <= 1) {
134 | curCol += 1
135 | }
136 | } else if (curRow == 3) {
137 | if (curCol >= 3 && curCol <= 7) {
138 | curCol = 8
139 | } else if (curCol >= 9) {
140 | curCol = 0
141 | } else {
142 | curCol += 1
143 | }
144 | if (curCol <= 1) {
145 | curCol += 1
146 | }
147 | }
148 | }
149 |
150 | // Other keys
151 | Keys.onPressed: {
152 | // Space Shortcut
153 | if (api.keys.isFilters(event)) {
154 | kRoot.sendKey(" ")
155 | }
156 | // Backspace Shortcut
157 | if (api.keys.isDetails(event)) {
158 | kRoot.sendKey(backChar) // Backspace
159 | }
160 | // Exit Shortcut
161 | if (api.keys.isCancel(event)) {
162 | event.accepted = true;
163 | kRoot.done()
164 | }
165 | }
166 |
167 | Item {
168 | id: keymap
169 |
170 | // The actual keys on the keyboard
171 |
172 | anchors.fill: parent
173 | anchors.leftMargin: parent.width * 0.05
174 | anchors.rightMargin: parent.width * 0.05
175 |
176 | anchors.topMargin: parent.height * 0.025
177 | anchors.bottomMargin: parent.height * 0.1
178 |
179 | Column {
180 | // Keyboard Column, with rows
181 | spacing: colspacing
182 |
183 | Row { // QWERTYUIOP
184 | spacing: rowspacing
185 |
186 | Repeater {
187 | /* Repeaters repeat the keys
188 | * Models have the following properties
189 | * key: Lowercase letter (default)
190 | * alt: Alternate character
191 | * w: Key Width
192 | * il: Lower index (used for selection)
193 | * ih: Higher index (used for selection)
194 | *
195 | */
196 | model: [
197 | {key: "q", alt: "1", w: 1, il: 0, ih: 0},
198 | {key: "w", alt: "2", w: 1, il: 1, ih: 1},
199 | {key: "e", alt: "3", w: 1, il: 2, ih: 2},
200 | {key: "r", alt: "4", w: 1, il: 3, ih: 3},
201 | {key: "t", alt: "5", w: 1, il: 4, ih: 4},
202 | {key: "y", alt: "6", w: 1, il: 5, ih: 5},
203 | {key: "u", alt: "7", w: 1, il: 6, ih: 6},
204 | {key: "i", alt: "8", w: 1, il: 7, ih: 7},
205 | {key: "o", alt: "9", w: 1, il: 8, ih: 8},
206 | {key: "p", alt: "0", w: 1, il: 9, ih: 9},
207 | {key: backChar, alt: backChar, w: 1, il: 10, ih: 10}
208 | ]
209 |
210 | delegate: KeyButton {
211 | label: alts ? (modelData.alt) : (shift ? modelData.key.toUpperCase() : modelData.key)
212 |
213 | width: modelData.w * keymap.width / 11 - rowspacing
214 | height: keymap.height / 4 - colspacing
215 |
216 | focus: curRow == 0 && (curCol >= modelData.il && curCol <= modelData.ih)
217 |
218 | clickedColor:colors["keyPush"]
219 | regColor: colors["key"]
220 | textColor: colors["text"]
221 |
222 | // Key Highlight
223 | Rectangle {
224 | anchors.fill: parent
225 | radius: parent.radius
226 | border.width: 2
227 | border.color: colors["keyHighlight"]
228 | color: "transparent"
229 | visible: curRow == 0 && (curCol >= modelData.il && curCol <= modelData.ih)
230 | }
231 |
232 | // Sending the key (click)
233 | onClicked: {
234 | kRoot.sendKey(label);
235 | curRow = 0;
236 | curCol = modelData.il;
237 | }
238 |
239 | // Sending the key (button)
240 | Keys.onPressed: {
241 | if (api.keys.isAccept(event)) {
242 | kRoot.sendKey(label)
243 | }
244 | }
245 | }
246 |
247 | /* The same thing with the keys here is done with the rest of the Rows.
248 | *
249 | */
250 | }
251 | }
252 |
253 | Row { // ASDFGHJKL + Enter
254 | spacing: rowspacing
255 |
256 | Repeater {
257 | // Key model
258 | model: [
259 | {key: "a", alt: "!", w: 1, il: 0, ih: 0},
260 | {key: "s", alt: "@", w: 1, il: 1, ih: 1},
261 | {key: "d", alt: "#", w: 1, il: 2, ih: 2},
262 | {key: "f", alt: "$", w: 1, il: 3, ih: 3},
263 | {key: "g", alt: "%", w: 1, il: 4, ih: 4},
264 | {key: "h", alt: "&", w: 1, il: 5, ih: 5},
265 | {key: "j", alt: "*", w: 1, il: 6, ih: 6},
266 | {key: "k", alt: "(", w: 1, il: 7, ih: 7},
267 | {key: "l", alt: ")", w: 1, il: 8, ih: 8},
268 | {key: enterChar, alt: enterChar, w: 2, il: 9, ih: 10}
269 | ]
270 |
271 | // Button
272 | delegate: KeyButton {
273 | label: alts ? (modelData.alt) : (shift ? modelData.key.toUpperCase() : modelData.key)
274 |
275 | width: modelData.w * keymap.width / 11 - rowspacing
276 | height: keymap.height / 4 - colspacing
277 |
278 | focus: visible && curRow == 1 && (curCol >= modelData.il && curCol <= modelData.ih)
279 |
280 | clickedColor:colors["keyPush"]
281 | regColor: colors["key"]
282 | textColor: colors["text"]
283 |
284 | // Key send (click)
285 | onClicked: {
286 | kRoot.sendKey(label);
287 | curRow = 1;
288 | curCol = modelData.il;
289 | }
290 | // Key send (button)
291 | Keys.onPressed: {
292 | if (api.keys.isAccept(event)) {
293 | kRoot.sendKey(label)
294 | }
295 | }
296 |
297 | // Key Highlight
298 | Rectangle {
299 | anchors.fill: parent
300 | radius: parent.radius
301 | border.width: 2
302 | border.color: colors["keyHighlight"]
303 | color: "transparent"
304 | visible: curRow == 1 && (curCol >= modelData.il && curCol <= modelData.ih)
305 | }
306 | }
307 | }
308 | }
309 |
310 | Row { // sh + ZXCVBNM + sh
311 | spacing: rowspacing
312 |
313 | Repeater {
314 | model: [
315 | {key: shiftChar, alt: shiftChar, w: 2, il: 0, ih: 1},
316 | {key: "z", alt: "-", w: 1, il: 2, ih: 2},
317 | {key: "x", alt: "'", w: 1, il: 3, ih: 3},
318 | {key: "c", alt: '"', w: 1, il: 4, ih: 4},
319 | {key: "v", alt: "_", w: 1, il: 5, ih: 5},
320 | {key: "b", alt: ",", w: 1, il: 6, ih: 6},
321 | {key: "n", alt: "?", w: 1, il: 7, ih: 7},
322 | {key: "m", alt: "+", w: 1, il: 8, ih: 8},
323 | {key: shiftChar, alt: shiftChar, w: 2, il: 9, ih: 10}
324 | ]
325 |
326 | delegate: KeyButton {
327 | label: alts ? (modelData.alt) : (shift ? modelData.key.toUpperCase() : modelData.key)
328 |
329 | width: modelData.w * keymap.width / 11 - rowspacing
330 | height: keymap.height / 4 - colspacing
331 |
332 | focus: visible && curRow == 2 && (curCol >= modelData.il && curCol <= modelData.ih)
333 |
334 | clickedColor:colors["keyPush"]
335 | regColor: colors["key"]
336 | textColor: colors["text"]
337 |
338 | // Key send (click)
339 | onClicked: {
340 | kRoot.sendKey(label);
341 | curRow = 2;
342 | curCol = modelData.il;
343 | }
344 | // Key send (button)
345 | Keys.onPressed: {
346 | if (api.keys.isAccept(event)) {
347 | kRoot.sendKey(label)
348 | }
349 | }
350 |
351 | // Key Highlight
352 | Rectangle {
353 | anchors.fill: parent
354 | radius: parent.radius
355 | border.width: 2
356 | border.color: colors["keyHighlight"]
357 | color: "transparent"
358 | visible: curRow == 2 && (curCol >= modelData.il && curCol <= modelData.ih)
359 | }
360 | }
361 | }
362 | }
363 |
364 | Row { // sh + ZXCVBNM + sh
365 | spacing: rowspacing
366 |
367 | Repeater {
368 | model: [
369 | {key: "&123", alt: "ABCD", w: 2, il: 0, ih: 1},
370 | {key: ".", alt: ".", w: 1, il: 2, ih: 2},
371 | {key: " ", alt: " ", w: 5, il: 3, ih: 7},
372 | {key: "-", alt: "-", w: 1, il: 8, ih: 8},
373 | {key: "CLEAR", alt: "CLEAR", w: 2, il: 9, ih: 10}
374 | ]
375 |
376 | delegate: KeyButton {
377 | label: alts ? (modelData.alt) : (shift ? modelData.key.toUpperCase() : modelData.key)
378 |
379 | width: modelData.w * keymap.width / 11 - rowspacing
380 | height: keymap.height / 4 - colspacing
381 |
382 | focus: visible && curRow == 3 && (curCol >= modelData.il && curCol <= modelData.ih)
383 |
384 | clickedColor:colors["keyPush"]
385 | regColor: colors["key"]
386 | textColor: colors["text"]
387 |
388 | // Key send (click)
389 | onClicked: {
390 | kRoot.sendKey(label);
391 | curRow = 3;
392 | curCol = modelData.il;
393 | }
394 | // Key send (button)
395 | Keys.onPressed: {
396 | if (api.keys.isAccept(event)) {
397 | kRoot.sendKey(label)
398 | }
399 | }
400 |
401 | // Key Highlight
402 | Rectangle {
403 | anchors.fill: parent
404 | radius: parent.radius
405 | border.width: 2
406 | border.color: colors["keyHighlight"]
407 | color: "transparent"
408 | visible: curRow == 3 && (curCol >= modelData.il && curCol <= modelData.ih)
409 | }
410 | }
411 | }
412 | }
413 |
414 | }
415 | }
416 |
417 |
418 |
419 | function focusEnableKB() {
420 | if (curRow >= 3) {
421 | curRow = 0
422 | } else {
423 | curRow += 1
424 | }
425 | if (curRow <= 0) {
426 | curRow = 3
427 | } else {
428 | curRow -= 1
429 | }
430 | }
431 |
432 | }
433 |
--------------------------------------------------------------------------------
/assets/SAFELY_REMOVABLE/OLD_DOCUMENTATION.md:
--------------------------------------------------------------------------------
1 | # Warning
2 |
3 | This is old documentation that was the original intent for this theme's documentation. I have decided instead to make this into just comments.
4 |
5 |
6 |
7 | ## In Depth Editing Information
8 |
9 | This section will detail this theme fully, in order to allow you to have a greater understanding of this theme as well as QML. This will serve well for those who wish to perform heavy modifications of this theme or fork this theme. It is highly recommended that you take a look at the following sources at times while you look at this information:
10 |
11 | - [QtQuick QML Types](https://doc.qt.io/qt-5/qtquick-qmlmodule.html) (note that a quick search for the type followed by 'QML' will yield information for that type; not all types are here)
12 | - [Pegasus Frontend Theme API](https://pegasus-frontend.org/docs/themes/api/)
13 |
14 | ### theme.qml
15 |
16 | `theme.qml` can be divided into several parts. We first import all necessary QML modules, going from `QtQuick 2.8` to `SortFilterProxyModel 0.2`. Afterwards, we import other parts of the theme. To read more about these parts, see [Theme Folder Pages](#theme-folder-pages) and [Game Items](#game-items).
17 |
18 | The next few sections are described below.
19 |
20 |
21 | #### Variables
22 |
23 | The first portion under the `FocusScope` is where we define all variables. A list of each is below.
24 |
25 | - `focus`: Set the FocusScope to be focused, so that it receives input.
26 | - `sw`, `sh`: Define quick screen width and height references, used instead of parent.width and parent.height in top-level items
27 | - `menu`: Sets the current menu page. This is used to create the home, search, collections and settings pages. See [Pages](#pages).
28 | - `focused`, `gameView`: Defines page behaviors in the home and collections pages respectively, defined here to not be affected in their respective pages.
29 | - FontLoaders: These `FontLoaders` simply define fonts for use throughout the theme.
30 |
31 | - `iconSize`, `marginAnimVel`, `giShadowRad`, `giShadowOp`: These values set several miscellaneous properties for several different parts where these properties would otherwise need to be defined in several places.
32 | - `iconSize` defines the size of the icons in the bottom bar.
33 | - `marginAnimVel` defines the speed in which the expansion/contraction animation for highlighting games.
34 | - `giShadowRad` defines the size of game box shadows.
35 | - `giShadowOp` defines the opacity of the same game box shadows as above.
36 |
37 | - Settings: The last variables defined are settings which correspond to those in the settings page.
38 | - `light`: Light Mode
39 | - `plainBG`: Plain (Flat) Background
40 | - `noBtns`: Removes button indicators
41 | - `sbsl`: Shows clock bar (originally restricted search functionality)
42 | - `nosfx`: Mutes all sound effects
43 | - `wide`: Wide game view
44 | - `quiet`: Quiets sound effects (halves volume)
45 | - `moreRecent`: Increases the number of games under Recent Games
46 | - `mouseNav`: Enables mouse navigation arrows
47 | - `enlargeBar`: Enlarges the bottom bar
48 | - `limSearch`: Limits search results to those which start with the search rather than contain the search
49 |
50 |
51 | #### bottomBar
52 |
53 | After defining the variables, we go on to define the bottom bar. This is used to indicate which page we are currently on. This `bottomBar` Item contains a row of images, a background rectangle and a MouseArea.
54 |
55 | For each image in this row, we get its image from `[THEME_FOLDER]/assets/theme/[image].svg`, as well as allowing for clicks on the icons to switch to the respective page. You can easily modify the icon sizes with the `iconSize` setting from [Variables](#variables), as well as changing the source of the icons.
56 |
57 | `bottomBar` also contains a background rectangle, which simply overlays over the rest of the content. Its color is changeable here.
58 |
59 | The MouseArea here is to block clicks that occur on the bar, preventing one from accidentally clicking an item under the bar when not intended.
60 |
61 | Finally, outside of the `bottomBar` itself are 2 other items. One is the indicator rectangle for the bar, which shows the icon currently selected. It takes a small shortcut with its width, using the home icon to set its width. Its x position is defined by an equation, which simply does the following:
62 | 1. Moves the indicator to the screen's center
63 | 2. Move the indicator left to the home icon, by moving it left 2 icons and 1.5 spaces between the icons (the .5 comes from the fact that there are an even number of icons, so the .5 places it on the icon just left of the center)
64 | 3. Moves it right one space and one icon multiplied by the current menu value (this is why menu is an integer: it allows the indicator to properly move right)
65 | The y position of the indicator rectangle is simply placed respective to the screen height. Finally, a behavior is defined, which animates the indicator's movement.
66 |
67 | The other item outside of the `bottomBar` is the BottomBarIcons item, the first of several items defined outside of the theme.qml file. BottomBarIcons.qml is located under `[THEME_FOLDER]/Bottombar`, and defines the controller icons for the bottom bar. These are simply QML rectangles which are placed on 2 rows, each varying in several properties.
68 |
69 | One potential idea for a modification to `bottomBar` is to change its position, which could be done by anchoring it to another side, changing the height and width, and converting the Row `bbImages` to a Column.
70 |
71 |
72 | #### Pages
73 |
74 | After defining the bottomBar, we move on to pages, which are the objects from Colcon to ClockBar. These contain the main functionality of the theme, but in theme.qml, their definitions here only serve to put them on the theme itself.
75 |
76 | In-depth information on each page is under [Theme Folder Pages](#theme-folder-pages).
77 |
78 | `Colcon` defines a simple converter for Launchbox shortnames, as Launchbox has several shortnames which do not match up with those of the logos in `[THEME_FOLDER]/assets/logos/banner/`. This was **stolen** :(.
79 |
80 | Next we have the `Home`, `All`, `Collections`, and `Settings` pages. These are the main pages that show up. Each one fills the screen, and has a focus which corresponds to the `menu` variable, also setting the visibility. To change which page is visible, we modify `menu`; see [Other Functionalities](#other-functionalities) for more information.
81 |
82 | Finally, we have the `ClockBar`, which is made visible through the `sbsl` variable and placed above the other parts of the theme.
83 |
84 |
85 | #### Other Visuals
86 |
87 | The last visual portions of theme.qml define the background of the theme. First is the background image (`backgroundImage`), which is the actual image of the background, placed under all other parts of the theme. To add some visual effects, we add a ShaderEffectSource and GaussianBlur applied over the image to add a blur effect. If either background image isn't present or `plainBG` is true, we show the plain background, which is simply a rectangle that fills the screen, placed over `backgroundImage`.
88 |
89 | #### Other Functionalities
90 |
91 | The end of theme.qml defines some non-visual functions. It starts by defining several sound effects, located in `[THEME_FOLDER]/assets/audio/`, which are used in numerous places in the theme.
92 |
93 | After the sound effects, we define the menu changing behavior. With the input of the nextPage and previousPage buttons (LB/L1 and RB/R1), we change the current page of the theme. These functions also allow the navigation to wrap around.
94 |
95 | Finally, we define a `launchGame()` function, which launches games. This is where we reach the end of theme.qml.
96 |
97 |
98 | ### Theme Folder Pages
99 |
100 | The following section goes through the files in the `[THEME_FOLDER]/Theme` directory. There are 6 files in this folder, which will be covered in the next sections.
101 |
102 | #### Home.qml
103 |
104 | Home.qml defines the home page of the theme, which shows the recent games and favorite games sections. Home.qml starts by defining a few variables and other items.
105 |
106 | - `maxRecents` defines the maximum number of games in the recent games list.
107 | - `sort_last_played_base`, `recent` and `favorites` define models for the recent and favorite games lists. `sort_last_played_base` defines a model with the last played games, and `recent` restricts that model to `maxRecents` items. `favorites` defines a model listing all favorited games, which are defined by pegasus.
108 | - `currentGame` defines the currently highlighted game. The block of code here sets the game to be the currently selected game in the currently selected list (recent or favorites), allowing for launching the game to work properly.
109 | - `uiY`, similar to `currentGame`, is defined by code. The code here changes this value whether or not the recent games or the favorite games are selected, changing the vertical position of the UI to scroll the page to the favorite games.
110 | - `animVel` defines the animation speed of the animation that plays on scroll.
111 |
112 |
113 | After we define these variables, we create 3 simple objects, 2 of which just define text to indicate each section, and the other creates a clickable arrow which scrolls the page back up. While the Text objects are relatively simple, with the only thing of note being the y position, the up arrow is different, as it has a MouseArea which scrolls the view back up, allowing for mouse-based navigation. This is disabled if `mouseNav` is false.
114 |
115 | We then have a ListView, which shows our recent games. While there is lots of stuff here, the only thing of note is the `doubleFocus` property of the delegate Item, which highlights the item if the ListView and itself is focused. As well, the GINormal Item has the `wideHead` property set to true, making its first item wider.
116 |
117 |
118 |
119 |
120 | ### Game Items
121 |
122 | ### Other Parts
123 |
124 |
--------------------------------------------------------------------------------
/assets/SAFELY_REMOVABLE/library-icons-font/README.txt:
--------------------------------------------------------------------------------
1 | This webfont is generated by https://fontello.com open source project.
2 |
3 |
4 | ================================================================================
5 | Please, note, that you should obey original font licenses, used to make this
6 | webfont pack. Details available in LICENSE.txt file.
7 |
8 | - Usually, it's enough to publish content of LICENSE.txt file somewhere on your
9 | site in "About" section.
10 |
11 | - If your project is open-source, usually, it will be ok to make LICENSE.txt
12 | file publicly available in your repository.
13 |
14 | - Fonts, used in Fontello, don't require a clickable link on your site.
15 | But any kind of additional authors crediting is welcome.
16 | ================================================================================
17 |
18 |
19 | Comments on archive content
20 | ---------------------------
21 |
22 | - /font/* - fonts in different formats
23 |
24 | - /css/* - different kinds of css, for all situations. Should be ok with
25 | twitter bootstrap. Also, you can skip style and assign icon classes
26 | directly to text elements, if you don't mind about IE7.
27 |
28 | - demo.html - demo file, to show your webfont content
29 |
30 | - LICENSE.txt - license info about source fonts, used to build your one.
31 |
32 | - config.json - keeps your settings. You can import it back into fontello
33 | anytime, to continue your work
34 |
35 |
36 | Why so many CSS files ?
37 | -----------------------
38 |
39 | Because we like to fit all your needs :)
40 |
41 | - basic file, .css - is usually enough, it contains @font-face
42 | and character code definitions
43 |
44 | - *-ie7.css - if you need IE7 support, but still don't wish to put char codes
45 | directly into html
46 |
47 | - *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
48 | rules, but still wish to benefit from css generation. That can be very
49 | convenient for automated asset build systems. When you need to update font -
50 | no need to manually edit files, just override old version with archive
51 | content. See fontello source code for examples.
52 |
53 | - *-embedded.css - basic css file, but with embedded WOFF font, to avoid
54 | CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
55 | We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
56 | server headers. But if you ok with dirty hack - this file is for you. Note,
57 | that data url moved to separate @font-face to avoid problems with