Commit 557445f9 authored by Yankovskiy Georgiy's avatar Yankovskiy Georgiy

Native games tab (without native entry images)

parent e117be13
...@@ -17,6 +17,7 @@ from ingame.models.GameEntry import GameEntry ...@@ -17,6 +17,7 @@ from ingame.models.GameEntry import GameEntry
from ingame.models.GameAgent import GameAgent from ingame.models.GameAgent import GameAgent
from PySide6.QtCore import Property, Signal, Slot, QObject, Qt from PySide6.QtCore import Property, Signal, Slot, QObject, Qt
class App(QtCore.QObject): class App(QtCore.QObject):
app_name = "ingame" app_name = "ingame"
app_author = "foss" app_author = "foss"
...@@ -41,7 +42,8 @@ class App(QtCore.QObject): ...@@ -41,7 +42,8 @@ class App(QtCore.QObject):
self.config_path = user_config_dir(App.app_name, App.app_author) 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.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_config_location: str = '/.config/PortProton.conf'
self.portproton_location: str = '' self.portproton_location: str = ''
self.running_game_process: Union[subprocess.Popen, None] = None self.running_game_process: Union[subprocess.Popen, None] = None
...@@ -65,11 +67,51 @@ class App(QtCore.QObject): ...@@ -65,11 +67,51 @@ class App(QtCore.QObject):
with open(self.home + self.portproton_config_location, 'r') as file: with open(self.home + self.portproton_config_location, 'r') as file:
self.portproton_location = file.read().strip() self.portproton_location = file.read().strip()
print(f'Current PortProton location: {self.portproton_location}') 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() def __native_games_setup(self):
files = glob.glob(f"{self.portproton_location}/*.desktop") # 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 = DesktopFile.from_file(val)
desktop_file_data = desktop_file.data desktop_file_data = desktop_file.data
desktop_entry = desktop_file_data['Desktop Entry'] desktop_entry = desktop_file_data['Desktop Entry']
...@@ -96,31 +138,28 @@ class App(QtCore.QObject): ...@@ -96,31 +138,28 @@ class App(QtCore.QObject):
# Remove extra env in the beginning # Remove extra env in the beginning
entry_exec = f"env START_FROM_STEAM=1 {entry_exec[4:len(entry_exec)]}" 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.portproton_games_model.add_game(GameEntry(name=entry_name, icon=entry_icon, exec=entry_exec))
except:
self.gamepad.run() continue
self.retrieve_games_details()
except FileNotFoundError:
print('File not found')
except Exception as e:
print('An error occurred', e)
pass
# TODO: fix: progress=1.0 not emitted if details already cached/downloaded # TODO: fix: progress=1.0 not emitted if details already cached/downloaded
def retrieve_games_details(self): 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) 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 game_entry: GameEntry
i: int = 0 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_description = t.agent.retrieve_game_description(game_entry.name)
game_entry.icon = game_description['image_location_path'] or game_entry.icon game_entry.icon = game_description['image_location_path'] or game_entry.icon
t.game_list_details_retrieving_progress.emit(float(i) / all_count) t.game_list_details_retrieving_progress.emit(float(i) / all_count)
i += 1 i += 1
t.game_list_details_retrieving_progress.emit(1.0) 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() thread.start()
''' CALLBACKS ''' ''' CALLBACKS '''
...@@ -165,4 +204,8 @@ class App(QtCore.QObject): ...@@ -165,4 +204,8 @@ class App(QtCore.QObject):
@Property(QObject, constant=True) @Property(QObject, constant=True)
def games(self): 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
...@@ -3,6 +3,7 @@ import QtQuick.Controls ...@@ -3,6 +3,7 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs import QtQuick.Dialogs
import "../delegates" import "../delegates"
import '../tabs'
import "../constants/tabs.js" as TabConstants import "../constants/tabs.js" as TabConstants
import "../constants/style.js" as Style import "../constants/style.js" as Style
import "../components" as TopMenuBut import "../components" as TopMenuBut
...@@ -11,6 +12,7 @@ import "../constants/scene.js" as S ...@@ -11,6 +12,7 @@ import "../constants/scene.js" as S
// TODO: code refactor // TODO: code refactor
Rectangle { Rectangle {
property string currentTab: TabConstants.gamesTab property string currentTab: TabConstants.gamesTab
property var currentTabInstance: portProtonGamesTab
property var activeButtonTab: buttonGames property var activeButtonTab: buttonGames
id: tabs id: tabs
...@@ -131,15 +133,19 @@ Rectangle { ...@@ -131,15 +133,19 @@ Rectangle {
tabs.activeButtonTab.isActive = true; tabs.activeButtonTab.isActive = true;
} }
// TODO: поменять всё на Repeater
// слишком много дубликатов кода
TopMenuBut.TextButton { TopMenuBut.TextButton {
id: buttonSystemManagement; id: buttonSystemManagement;
text: TabConstants.systemManagementTab; text: TabConstants.systemManagementTab;
width: 400; width: 400;
onClicked: function(){ onClicked: function(){
tabButtons.x = tabButtons.tempX tabButtons.x = tabButtons.tempX;
tabButtons.changeButtonActiveTab(this) tabButtons.changeButtonActiveTab(this);
tabButtons.toggle = true tabButtons.toggle = true;
tabs.currentTab = TabConstants.systemManagementTab; tabs.currentTab = TabConstants.systemManagementTab;
tabs.currentTabInstance = systemManagementGrid;
} }
onReleased: tabButtons.toggle = false onReleased: tabButtons.toggle = false
} }
...@@ -147,10 +153,12 @@ Rectangle { ...@@ -147,10 +153,12 @@ Rectangle {
id: buttonGames id: buttonGames
text: TabConstants.gamesTab text: TabConstants.gamesTab
onClicked: function(){ onClicked: function(){
tabButtons.x = tabButtons.tempX tabButtons.x = tabButtons.tempX;
tabButtons.changeButtonActiveTab(this) tabButtons.changeButtonActiveTab(this);
tabButtons.toggle = true tabButtons.toggle = true;
tabs.currentTab = TabConstants.gamesTab; tabs.currentTab = TabConstants.gamesTab;
tabs.currentTabInstance = portProtonGamesTab;
//if(core_app === undefined) return; //if(core_app === undefined) return;
//console.log("core_app found"); //console.log("core_app found");
...@@ -165,15 +173,17 @@ Rectangle { ...@@ -165,15 +173,17 @@ Rectangle {
} }
TopMenuBut.TextButton { TopMenuBut.TextButton {
id: testbut2 id: nativeGamesButton
text: "Test" text: TabConstants.nativeGamesTab
//font.pixelSize: 60 //font.pixelSize: 60
//height:Math.ceil(tabs.height/100 * 10) //height:Math.ceil(tabs.height/100 * 10)
onClicked: function(){ onClicked: function(){
tabButtons.x = tabButtons.tempX tabButtons.x = tabButtons.tempX;
tabButtons.changeButtonActiveTab(this) tabButtons.changeButtonActiveTab(this);
tabButtons.toggle = true tabButtons.toggle = true;
tabs.currentTab = TabConstants.nativeGamesTab;
tabs.currentTabInstance = nativeGamesTab;
} }
onReleased: tabButtons.toggle = false onReleased: tabButtons.toggle = false
...@@ -240,112 +250,25 @@ Rectangle { ...@@ -240,112 +250,25 @@ Rectangle {
} }
} }
// Сетка игр // PortProton Games
ScrollView { GamesTab {
id: portProtonGamesTab
visible: tabs.currentTab == TabConstants.gamesTab visible: tabs.currentTab == TabConstants.gamesTab
id: gamesScroller model: core_app.games
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
}
}
}
} }
// Native games
GamesTab {
id: nativeGamesTab
visible: tabs.currentTab == TabConstants.nativeGamesTab
model: core_app.native_games
}
// LOGIC // LOGIC
property int focusedItems: 0;
property int focusedTabs: 0; property int focusedTabs: 0;
function getTabs(){ function getTabs(){
...@@ -353,7 +276,7 @@ Rectangle { ...@@ -353,7 +276,7 @@ Rectangle {
buttonSystemManagement, buttonSystemManagement,
buttonGames, buttonGames,
// testbut1, // testbut1,
testbut2 nativeGamesButton
]; ];
} }
...@@ -375,58 +298,38 @@ Rectangle { ...@@ -375,58 +298,38 @@ Rectangle {
// tabButtons.changeButtonActiveTab(item); // 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 */ /* SIGNALS */
function onGamepadClickedLB(args){ function onGamepadClickedLB(args){
tabs.applyTabsFocus(-1) tabs.applyTabsFocus(-1);
} }
function onGamepadClickedRB(args){ function onGamepadClickedRB(args){
tabs.applyTabsFocus(1) tabs.applyTabsFocus(1);
} }
function onGamepadAxisUp(done){ function onGamepadAxisUp(done){
tabs.applyItemsFocus(-gamesGrid.columns); currentTabInstance.applyItemsFocusByLine(-1);
} }
function onGamepadAxisDown(done){ function onGamepadAxisDown(done){
tabs.applyItemsFocus(gamesGrid.columns); currentTabInstance.applyItemsFocusByLine(1);
} }
function onGamepadAxisLeft(done){ function onGamepadAxisLeft(done){
tabs.applyItemsFocus(-1) currentTabInstance.applyItemsFocus(-1);
} }
function onGamepadAxisRight(args){ function onGamepadAxisRight(args){
tabs.applyItemsFocus(1) currentTabInstance.applyItemsFocus(1);
} }
function onGamepadClickedApply(args){ function onGamepadClickedApply(args){
if(window.scene !== S.homeScene) return; if(window.scene !== S.homeScene) return;
let c = gamesGrid.children; currentTabInstance.pressFocusedItem();
c[tabs.focusedItems].press();
} }
function onGameListDetailsRetrievingProgress(args) { function onGameListDetailsRetrievingProgress(args) {
let progress = args[0]; let progress = args[0];
console.log(progress);
if(progress === 1.0){ if(progress === 1.0){
gamesGridRepeater.model = []; portProtonGamesTab.refreshItems(core_app.games);
gamesGridRepeater.model = core_app.games; nativeGamesTab.refreshItems(core_app.native_games);
} }
} }
......
var systemManagementTab = "System"; var systemManagementTab = "System";
var gamesTab = "Games"; var gamesTab = "Games";
var nativeGamesTab = "Native";
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;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment