Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
ingame
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vladislav
ingame
Commits
557445f9
Commit
557445f9
authored
Jun 28, 2024
by
Yankovskiy Georgiy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Native games tab (without native entry images)
parent
e117be13
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
254 additions
and
157 deletions
+254
-157
App.py
ingame/models/App.py
+61
-18
Tabs.qml
qml/components/Tabs.qml
+42
-139
tabs.js
qml/constants/tabs.js
+1
-0
GamesTab.qml
qml/tabs/GamesTab.qml
+150
-0
No files found.
ingame/models/App.py
View file @
557445f9
...
...
@@ -17,6 +17,7 @@ from ingame.models.GameEntry import GameEntry
from
ingame.models.GameAgent
import
GameAgent
from
PySide6.QtCore
import
Property
,
Signal
,
Slot
,
QObject
,
Qt
class
App
(
QtCore
.
QObject
):
app_name
=
"ingame"
app_author
=
"foss"
...
...
@@ -41,7 +42,8 @@ class App(QtCore.QObject):
self
.
config_path
=
user_config_dir
(
App
.
app_name
,
App
.
app_author
)
self
.
cache_path
=
user_cache_dir
(
App
.
app_name
,
App
.
app_author
)
self
.
games_model
:
GamesModel
=
GamesModel
()
self
.
portproton_games_model
:
GamesModel
=
GamesModel
()
self
.
native_games_model
:
GamesModel
=
GamesModel
()
self
.
portproton_config_location
:
str
=
'/.config/PortProton.conf'
self
.
portproton_location
:
str
=
''
self
.
running_game_process
:
Union
[
subprocess
.
Popen
,
None
]
=
None
...
...
@@ -65,11 +67,51 @@ class App(QtCore.QObject):
with
open
(
self
.
home
+
self
.
portproton_config_location
,
'r'
)
as
file
:
self
.
portproton_location
=
file
.
read
()
.
strip
()
print
(
f
'Current PortProton location: {self.portproton_location}'
)
self
.
__portproton_games_setup
(
glob
.
glob
(
f
"{self.portproton_location}/*.desktop"
))
except
FileNotFoundError
:
print
(
'File not found'
)
except
Exception
as
e
:
print
(
'An error occurred'
,
e
)
self
.
__native_games_setup
()
self
.
gamepad
.
run
()
self
.
retrieve_games_details
()
self
.
games_model
.
clear
()
files
=
glob
.
glob
(
f
"{self.portproton_location}/*.desktop"
)
def
__native_games_setup
(
self
):
# Reference: https://askubuntu.com/a/490398
# TODO: replace with DesktopFile instance
self
.
native_games_model
.
clear
()
for
val
in
files
:
for
val
in
glob
.
glob
(
"/usr/share/applications/*.desktop"
):
try
:
desktop_file
=
DesktopFile
.
from_file
(
val
)
desktop_file_data
=
desktop_file
.
data
desktop_entry
=
desktop_file_data
[
'Desktop Entry'
]
if
'Categories'
not
in
desktop_entry
:
continue
# Game;ArcadeGame;
entry_categories
=
desktop_entry
[
'Categories'
]
or
''
if
"Game"
not
in
entry_categories
:
continue
entry_name
=
desktop_entry
[
'Name'
]
or
'generic'
entry_exec
=
'Exec'
in
desktop_entry
and
desktop_entry
[
'Exec'
]
or
''
entry_icon
=
''
# desktop_entry['Icon']
assert
(
isinstance
(
entry_name
,
str
)
and
isinstance
(
entry_exec
,
str
)
and
isinstance
(
entry_icon
,
str
))
self
.
native_games_model
.
add_game
(
GameEntry
(
name
=
entry_name
,
icon
=
entry_icon
,
exec
=
entry_exec
))
except
:
continue
def
__portproton_games_setup
(
self
,
files
):
self
.
portproton_games_model
.
clear
()
for
val
in
files
:
try
:
desktop_file
=
DesktopFile
.
from_file
(
val
)
desktop_file_data
=
desktop_file
.
data
desktop_entry
=
desktop_file_data
[
'Desktop Entry'
]
...
...
@@ -96,31 +138,28 @@ class App(QtCore.QObject):
# Remove extra env in the beginning
entry_exec
=
f
"env START_FROM_STEAM=1 {entry_exec[4:len(entry_exec)]}"
self
.
games_model
.
add_game
(
GameEntry
(
name
=
entry_name
,
icon
=
entry_icon
,
exec
=
entry_exec
))
self
.
gamepad
.
run
()
self
.
retrieve_games_details
()
except
FileNotFoundError
:
print
(
'File not found'
)
except
Exception
as
e
:
print
(
'An error occurred'
,
e
)
pass
self
.
portproton_games_model
.
add_game
(
GameEntry
(
name
=
entry_name
,
icon
=
entry_icon
,
exec
=
entry_exec
))
except
:
continue
# TODO: fix: progress=1.0 not emitted if details already cached/downloaded
def
retrieve_games_details
(
self
):
def
retrieve_games_details_thread
(
t
):
def
retrieve_games_details_thread
(
t
,
model
):
t
.
game_list_details_retrieving_progress
.
emit
(
0.0
)
all_count
:
int
=
len
(
self
.
games_
model
.
games_list
)
all_count
:
int
=
len
(
model
.
games_list
)
game_entry
:
GameEntry
i
:
int
=
0
for
game_entry
in
self
.
games_
model
.
games_list
:
for
game_entry
in
model
.
games_list
:
game_description
=
t
.
agent
.
retrieve_game_description
(
game_entry
.
name
)
game_entry
.
icon
=
game_description
[
'image_location_path'
]
or
game_entry
.
icon
t
.
game_list_details_retrieving_progress
.
emit
(
float
(
i
)
/
all_count
)
i
+=
1
t
.
game_list_details_retrieving_progress
.
emit
(
1.0
)
thread
=
threading
.
Thread
(
target
=
retrieve_games_details_thread
,
args
=
(
self
,))
thread
=
threading
.
Thread
(
target
=
retrieve_games_details_thread
,
args
=
(
self
,
self
.
portproton_games_model
))
thread
.
start
()
thread
=
threading
.
Thread
(
target
=
retrieve_games_details_thread
,
args
=
(
self
,
self
.
native_games_model
))
thread
.
start
()
''' CALLBACKS '''
...
...
@@ -165,4 +204,8 @@ class App(QtCore.QObject):
@Property
(
QObject
,
constant
=
True
)
def
games
(
self
):
return
self
.
games_model
return
self
.
portproton_games_model
@Property
(
QObject
,
constant
=
True
)
def
native_games
(
self
):
return
self
.
native_games_model
qml/components/Tabs.qml
View file @
557445f9
...
...
@@ -3,6 +3,7 @@ import QtQuick.Controls
import
QtQuick
.
Layouts
import
QtQuick
.
Dialogs
import
"../delegates"
import
'../tabs'
import
"../constants/tabs.js"
as
TabConstants
import
"../constants/style.js"
as
Style
import
"../components"
as
TopMenuBut
...
...
@@ -11,6 +12,7 @@ import "../constants/scene.js" as S
// TODO: code refactor
Rectangle
{
property
string
currentTab
:
TabConstants
.
gamesTab
property
var
currentTabInstance
:
portProtonGamesTab
property
var
activeButtonTab
:
buttonGames
id
:
tabs
...
...
@@ -131,15 +133,19 @@ Rectangle {
tabs
.
activeButtonTab
.
isActive
=
true
;
}
// TODO: поменять всё на Repeater
// слишком много дубликатов кода
TopMenuBut.TextButton
{
id
:
buttonSystemManagement
;
text
:
TabConstants
.
systemManagementTab
;
width
:
400
;
onClicked
:
function
(){
tabButtons
.
x
=
tabButtons
.
tempX
tabButtons
.
changeButtonActiveTab
(
this
)
tabButtons
.
toggle
=
true
tabButtons
.
x
=
tabButtons
.
tempX
;
tabButtons
.
changeButtonActiveTab
(
this
)
;
tabButtons
.
toggle
=
true
;
tabs
.
currentTab
=
TabConstants
.
systemManagementTab
;
tabs
.
currentTabInstance
=
systemManagementGrid
;
}
onReleased
:
tabButtons
.
toggle
=
false
}
...
...
@@ -147,10 +153,12 @@ Rectangle {
id
:
buttonGames
text
:
TabConstants
.
gamesTab
onClicked
:
function
(){
tabButtons
.
x
=
tabButtons
.
tempX
tabButtons
.
changeButtonActiveTab
(
this
)
tabButtons
.
toggle
=
true
tabButtons
.
x
=
tabButtons
.
tempX
;
tabButtons
.
changeButtonActiveTab
(
this
)
;
tabButtons
.
toggle
=
true
;
tabs
.
currentTab
=
TabConstants
.
gamesTab
;
tabs
.
currentTabInstance
=
portProtonGamesTab
;
//if(core_app === undefined) return;
//console.log("core_app found");
...
...
@@ -165,15 +173,17 @@ Rectangle {
}
TopMenuBut.TextButton
{
id
:
testbut2
text
:
"Test"
id
:
nativeGamesButton
text
:
TabConstants
.
nativeGamesTab
//font.pixelSize: 60
//height:Math.ceil(tabs.height/100 * 10)
onClicked
:
function
(){
tabButtons
.
x
=
tabButtons
.
tempX
tabButtons
.
changeButtonActiveTab
(
this
)
tabButtons
.
toggle
=
true
tabButtons
.
x
=
tabButtons
.
tempX
;
tabButtons
.
changeButtonActiveTab
(
this
);
tabButtons
.
toggle
=
true
;
tabs
.
currentTab
=
TabConstants
.
nativeGamesTab
;
tabs
.
currentTabInstance
=
nativeGamesTab
;
}
onReleased
:
tabButtons
.
toggle
=
false
...
...
@@ -240,112 +250,25 @@ Rectangle {
}
}
// Сетка игр
ScrollView
{
// PortProton Games
GamesTab
{
id
:
portProtonGamesTab
visible
:
tabs
.
currentTab
==
TabConstants
.
gamesTab
id
:
gamesScroller
anchors.fill
:
parent
anchors.topMargin
:
topNavigation
.
height
ScrollBar.vertical
:
ScrollBar
{
id
:
scrolV
;
height
:
parent
.
height
opacity
:
0
position
:
0
property
double
fromAnim
:
0.0
property
double
toAnim
:
0.0
}
function
scrollToY
(
y
,
HItem
)
{
scrolV
.
fromAnim
=
scrolV
.
position
scrolV
.
position
=
(
1.0
-
scrolV
.
size
)
*
y
/
gamesScroller
.
height
scrolV
.
toAnim
=
(
1.0
-
scrolV
.
size
)
*
y
/
gamesScroller
.
height
if
(
scrolV
.
toAnim
!=
scrolV
.
fromAnim
)
scrollAnimation
.
start
()
}
// Анимация авто скролла
PropertyAnimation
{
to
:
scrolV
.
toAnim
;
from
:
scrolV
.
fromAnim
;
target
:
scrolV
;
id
:
scrollAnimation
;
property
:
"position"
;
duration
:
200
;
}
GridLayout
{
id
:
gamesGrid
anchors.left
:
parent
.
left
anchors.right
:
parent
.
right
anchors.top
:
parent
.
top
anchors.bottom
:
parent
.
bottom
columns
:
5
rows
:
Math
.
max
(
Math
.
ceil
(
children
.
length
/
columns
),
1
)
anchors.rightMargin
:
rowSpacing
*
2
anchors.leftMargin
:
rowSpacing
*
2
anchors.bottomMargin
:
90
anchors.topMargin
:
Math
.
floor
(
gamesScroller
.
width
/
100
*
3
)
rowSpacing
:
Math
.
floor
(
gamesScroller
.
width
/
100
*
3
)
columnSpacing
:
rowSpacing
// Повторитель
Repeater
{
id
:
gamesGridRepeater
model
:
core_app
.
games
// Карточка игры
Game
{
id
:
game
gameTitle
:
model
.
name
gameExec
:
model
.
exec
gameIcon
:
model
.
icon
Layout.bottomMargin
:
(
index
-
index
%
gamesGrid
.
columns
)
/
gamesGrid
.
columns
===
gamesGrid
.
rows
-
1
?
gamesGrid
.
rowSpacing
*
2
:
0
onFocusChanged
:
if
(
focus
)
{
gamesScroller
.
scrollToY
(
y
);
}
Layout.preferredWidth
:
(
gamesScroller
.
width
-
(
gamesGrid
.
columns
-
1
)
*
gamesGrid
.
rowSpacing
-
gamesGrid
.
anchors
.
rightMargin
-
gamesGrid
.
anchors
.
leftMargin
)
/
gamesGrid
.
columns
Layout.preferredHeight
:
Layout
.
preferredWidth
/
2
*
3
// Component.onCompleted: {a3.start()}
// SequentialAnimation {
// id:a3
// NumberAnimation {
// property:Layout.topMargin;
// easing.type: Easing.InOutQuad;
// duration: 300;
// from: 100//Layout.preferredHeight;
// to: 0;
// }
// NumberAnimation {
// property:Layout.topMargin;
// easing.type: Easing.InOutQuad;
// duration: 300;
// from: 0//Layout.preferredHeight;
// to: 100;
// }
// loops: Animation.Infinite
// }
// Layout.topMargin: Layout.preferredHeight
}
}
}
model
:
core_app
.
games
}
// Native games
GamesTab
{
id
:
nativeGamesTab
visible
:
tabs
.
currentTab
==
TabConstants
.
nativeGamesTab
model
:
core_app
.
native_games
}
// LOGIC
property
int
focusedItems
:
0
;
property
int
focusedTabs
:
0
;
function
getTabs
(){
...
...
@@ -353,7 +276,7 @@ Rectangle {
buttonSystemManagement
,
buttonGames
,
// testbut1,
testbut2
nativeGamesButton
];
}
...
...
@@ -375,58 +298,38 @@ Rectangle {
// tabButtons.changeButtonActiveTab(item);
}
function
applyItemsFocus
(
inc
){
if
(
window
.
scene
!==
S
.
homeScene
)
return
;
let
c
=
gamesGrid
.
children
;
let
l
=
c
.
length
-
1
;
// exclude QQuickRepeater
// console.log(tabs.focusedItems);
if
(
tabs
.
focusedItems
+
inc
>=
l
)
{
tabs
.
focusedItems
=
(
c
.
focusedItems
+
inc
===
l
-
1
)
?
0
:
l
-
1
;
}
else
if
(
tabs
.
focusedItems
+
inc
<
0
)
{
tabs
.
focusedItems
=
(
c
.
focusedItems
+
inc
===
0
)
?
l
-
1
:
0
;
//;
}
else
{
tabs
.
focusedItems
+=
inc
;
}
c
[
tabs
.
focusedItems
].
forceActiveFocus
();
// gamesScroller.contentY = c[tabs.focusedItems].y; // not working
// c[tabs.focusedItems].clicked();
}
/* SIGNALS */
function
onGamepadClickedLB
(
args
){
tabs
.
applyTabsFocus
(
-
1
)
tabs
.
applyTabsFocus
(
-
1
)
;
}
function
onGamepadClickedRB
(
args
){
tabs
.
applyTabsFocus
(
1
)
tabs
.
applyTabsFocus
(
1
)
;
}
function
onGamepadAxisUp
(
done
){
tabs
.
applyItemsFocus
(
-
gamesGrid
.
columns
);
currentTabInstance
.
applyItemsFocusByLine
(
-
1
);
}
function
onGamepadAxisDown
(
done
){
tabs
.
applyItemsFocus
(
gamesGrid
.
columns
);
currentTabInstance
.
applyItemsFocusByLine
(
1
);
}
function
onGamepadAxisLeft
(
done
){
tabs
.
applyItemsFocus
(
-
1
)
currentTabInstance
.
applyItemsFocus
(
-
1
);
}
function
onGamepadAxisRight
(
args
){
tabs
.
applyItemsFocus
(
1
)
currentTabInstance
.
applyItemsFocus
(
1
);
}
function
onGamepadClickedApply
(
args
){
if
(
window
.
scene
!==
S
.
homeScene
)
return
;
let
c
=
gamesGrid
.
children
;
c
[
tabs
.
focusedItems
].
press
();
currentTabInstance
.
pressFocusedItem
();
}
function
onGameListDetailsRetrievingProgress
(
args
)
{
let
progress
=
args
[
0
];
console
.
log
(
progress
);
if
(
progress
===
1.0
){
gamesGridRepeater
.
model
=
[]
;
gamesGridRepeater
.
model
=
core_app
.
games
;
portProtonGamesTab
.
refreshItems
(
core_app
.
games
)
;
nativeGamesTab
.
refreshItems
(
core_app
.
native_games
)
;
}
}
...
...
qml/constants/tabs.js
View file @
557445f9
var
systemManagementTab
=
"System"
;
var
gamesTab
=
"Games"
;
var
nativeGamesTab
=
"Native"
;
qml/tabs/GamesTab.qml
0 → 100644
View file @
557445f9
import
QtQuick
import
QtQuick
.
Controls
import
QtQuick
.
Layouts
import
QtQuick
.
Dialogs
import
"../delegates"
import
"../constants/tabs.js"
as
TabConstants
import
"../constants/style.js"
as
Style
import
"../components"
as
TopMenuBut
import
"../constants/scene.js"
as
S
ScrollView
{
property
var
model
:
buttonGames
id
:
gamesScroller
anchors.fill
:
parent
anchors.topMargin
:
topNavigation
.
height
ScrollBar.vertical
:
ScrollBar
{
id
:
scrolV
;
height
:
parent
.
height
opacity
:
0
position
:
0
property
double
fromAnim
:
0.0
property
double
toAnim
:
0.0
}
function
scrollToY
(
y
,
HItem
)
{
scrolV
.
fromAnim
=
scrolV
.
position
;
scrolV
.
position
=
(
1.0
-
scrolV
.
size
)
*
y
/
gamesScroller
.
height
;
scrolV
.
toAnim
=
(
1.0
-
scrolV
.
size
)
*
y
/
gamesScroller
.
height
;
if
(
scrolV
.
toAnim
!=
scrolV
.
fromAnim
)
{
scrollAnimation
.
start
();
}
}
// Анимация авто скролла
PropertyAnimation
{
to
:
scrolV
.
toAnim
;
from
:
scrolV
.
fromAnim
;
target
:
scrolV
;
id
:
scrollAnimation
;
property
:
"position"
;
duration
:
200
;
}
GridLayout
{
id
:
gamesGrid
anchors.left
:
parent
.
left
anchors.right
:
parent
.
right
anchors.top
:
parent
.
top
anchors.bottom
:
parent
.
bottom
columns
:
5
rows
:
Math
.
max
(
Math
.
ceil
(
children
.
length
/
columns
),
1
)
anchors.rightMargin
:
rowSpacing
*
2
anchors.leftMargin
:
rowSpacing
*
2
anchors.bottomMargin
:
90
anchors.topMargin
:
Math
.
floor
(
gamesScroller
.
width
/
100
*
3
)
rowSpacing
:
Math
.
floor
(
gamesScroller
.
width
/
100
*
3
)
columnSpacing
:
rowSpacing
// Повторитель
Repeater
{
id
:
gamesGridRepeater
model
:
gamesScroller
.
model
// Карточка игры
Game
{
id
:
game
gameTitle
:
model
.
name
gameExec
:
model
.
exec
gameIcon
:
model
.
icon
Layout.bottomMargin
:
(
index
-
index
%
gamesGrid
.
columns
)
/
gamesGrid
.
columns
===
gamesGrid
.
rows
-
1
?
gamesGrid
.
rowSpacing
*
2
:
0
onFocusChanged
:
if
(
focus
)
{
gamesScroller
.
scrollToY
(
y
);
}
Layout.preferredWidth
:
(
gamesScroller
.
width
-
(
gamesGrid
.
columns
-
1
)
*
gamesGrid
.
rowSpacing
-
gamesGrid
.
anchors
.
rightMargin
-
gamesGrid
.
anchors
.
leftMargin
)
/
gamesGrid
.
columns
Layout.preferredHeight
:
Layout
.
preferredWidth
/
2
*
3
// Component.onCompleted: {a3.start()}
// SequentialAnimation {
// id:a3
// NumberAnimation {
// property:Layout.topMargin;
// easing.type: Easing.InOutQuad;
// duration: 300;
// from: 100//Layout.preferredHeight;
// to: 0;
// }
// NumberAnimation {
// property:Layout.topMargin;
// easing.type: Easing.InOutQuad;
// duration: 300;
// from: 0//Layout.preferredHeight;
// to: 100;
// }
// loops: Animation.Infinite
// }
// Layout.topMargin: Layout.preferredHeight
}
}
}
// LOGIC
property
int
focusedItems
:
0
;
function
applyItemsFocus
(
inc
){
if
(
window
.
scene
!==
S
.
homeScene
)
return
;
let
c
=
gamesGrid
.
children
;
let
l
=
c
.
length
-
1
;
// exclude QQuickRepeater
if
(
gamesScroller
.
focusedItems
+
inc
>=
l
)
{
gamesScroller
.
focusedItems
=
(
gamesScroller
.
focusedItems
+
inc
===
l
-
1
)
?
0
:
l
-
1
;
}
else
if
(
gamesScroller
.
focusedItems
+
inc
<
0
)
{
gamesScroller
.
focusedItems
=
(
gamesScroller
.
focusedItems
+
inc
===
0
)
?
l
-
1
:
0
;
//;
}
else
{
gamesScroller
.
focusedItems
+=
inc
;
}
if
(
c
[
gamesScroller
.
focusedItems
]
!==
undefined
)
{
c
[
gamesScroller
.
focusedItems
].
forceActiveFocus
();
}
}
function
applyItemsFocusByLine
(
inc
){
gamesScroller
.
applyItemsFocus
(
inc
*
gamesGrid
.
columns
);
}
function
pressFocusedItem
(){
let
c
=
gamesGrid
.
children
;
if
(
c
[
gamesScroller
.
focusedItems
]
!==
undefined
)
{
c
[
gamesScroller
.
focusedItems
].
press
();
}
}
function
refreshItems
(
data
){
gamesGridRepeater
.
model
=
[];
gamesGridRepeater
.
model
=
data
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment