Unverified Commit 8c3903d8 authored by Mikhail Tergoev's avatar Mikhail Tergoev Committed by GitHub

Merge pull request #3 from yageorgiy/main

Basic game launching
parents ed0cf291 b07b7b0b
...@@ -2,13 +2,10 @@ import QtQuick ...@@ -2,13 +2,10 @@ import QtQuick
import QtQuick.Controls as C import QtQuick.Controls as C
C.Button { C.Button {
// control.down // control.down
// control.activeFocus // control.activeFocus
id: control id: control
width: 150
height: 50
text: qsTr("Button") text: qsTr("Button")
contentItem: Text { contentItem: Text {
......
import QtQuick
import QtQuick.Controls as C
import "."
Button {
property string imageUrl: "../images/generic.svg"
id: button
contentItem: Image {
source: button.imageUrl
fillMode: Image.PreserveAspectFit // ensure it fits
}
}
import QtQuick import QtQuick
import QtQuick.Controls as C
import QtQuick.Layouts
import "../delegates"
import "../constants/tabs.js" as TabConstants import "../constants/tabs.js" as TabConstants
Rectangle { Rectangle {
// PROPERTIES // PROPERTIES
property string currentTab: TabConstants.systemManagementTab property string currentTab: TabConstants.systemManagementTab
...@@ -13,6 +17,9 @@ Rectangle { ...@@ -13,6 +17,9 @@ Rectangle {
height: 480 height: 480
color: "#ffffff" color: "#ffffff"
// onWidthChanged: { console.log("Window Width changed: " + width) }
// onHeightChanged: { console.log("Window Height changed: " + height)}
// COMPONENTS // COMPONENTS
Grid { Grid {
...@@ -38,26 +45,51 @@ Rectangle { ...@@ -38,26 +45,51 @@ Rectangle {
Rectangle { color: "magenta"; width: 10; height: 10 } Rectangle { color: "magenta"; width: 10; height: 10 }
} }
Grid {
id: gamesGrid
visible: tabs.currentTab == TabConstants.gamesTab
columns: 3
spacing: 2
anchors.left: parent.left C.ScrollView {
anchors.right: parent.right visible: tabs.currentTab == TabConstants.gamesTab
anchors.top: parent.top id: gamesScroller
anchors.bottom: parent.bottom anchors.fill: parent
anchors.topMargin: 60 anchors.topMargin: 60
anchors.rightMargin: 0 clip : true
anchors.leftMargin: 0
anchors.bottomMargin: 0
Text { GridLayout {
id: text2 id: gamesGrid
text: qsTr("Text") readonly property int elementWidth: 256 + gamesGrid.rowSpacing*2
font.pixelSize: 12 anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
// columns: Math.max(Math.floor(parent.width / elementWidth), 1)
// rows: Math.max(Math.ceil(children.length / columns), 1)
columns: Math.max(Math.floor(gamesScroller.width / elementWidth), 1)
rows: Math.max(Math.ceil(children.length / columns), 1)
anchors.rightMargin: 8
anchors.leftMargin: 8
anchors.bottomMargin: 8
anchors.topMargin: 8
rowSpacing: 8
columnSpacing: rowSpacing
Repeater {
// Layout.fillHeight: true
// Layout.fillWidth: true
model: core_app.games
Game {
gameTitle: model.name
gameExec: model.exec
gameIcon: model.icon
width: 256
height: 256
// icon: core_app.games.icon
// exec: core_app.games.exec
}
}
} }
} }
...@@ -88,6 +120,8 @@ Rectangle { ...@@ -88,6 +120,8 @@ Rectangle {
Button { Button {
id: buttonSystemManagement id: buttonSystemManagement
text: "System management" text: "System management"
width: 150
height: 50
onClicked: function(){ onClicked: function(){
tabs.currentTab = TabConstants.systemManagementTab; tabs.currentTab = TabConstants.systemManagementTab;
// tabs.changeTab(); // tabs.changeTab();
...@@ -98,10 +132,14 @@ Rectangle { ...@@ -98,10 +132,14 @@ Rectangle {
Button { Button {
id: buttonGames id: buttonGames
text: "Games" text: "Games"
width: 150
height: 50
onClicked: function(){ onClicked: function(){
tabs.currentTab = TabConstants.gamesTab; tabs.currentTab = TabConstants.gamesTab;
if(app === undefined) return; //if(core_app === undefined) return;
app.get_games(); //console.log("core_app found");
//app.get_games();
// tabs.changeTab(); // tabs.changeTab();
// console.log(tabs.currentTab); // console.log(tabs.currentTab);
} }
...@@ -111,3 +149,11 @@ Rectangle { ...@@ -111,3 +149,11 @@ Rectangle {
} }
/*##^##
Designer {
D{i:0}D{i:1;invisible:true}
}
##^##*/
var homeScene = "home";
var gameInfoScene = "gameInfo";
var runningScene = "running";
\ No newline at end of file
import QtQuick
import "../constants/scene.js" as SceneConstants
import "../components/" as C
C.Button {
property string gameTitle: "Generic title"
property string gameIcon: ""
property string gameExec: ""
id: game
width: 256
height: 256
implicitWidth: 256
implicitHeight: 256
text: ""
// color: "#efefef"
//radius: 5
// border.width: 1
onClicked: function(){
// console.log(game.title);
gameInfoScene.title = game.gameTitle;
gameInfoScene.icon = game.gameIcon;
gameInfoScene.exec = game.gameExec;
window.scene = SceneConstants.gameInfoScene;
}
Image {
id: image
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
source: game.gameIcon
anchors.rightMargin: 8
anchors.bottomMargin: 47
anchors.leftMargin: 8
anchors.topMargin: 8
fillMode: Image.PreserveAspectFit
}
Text {
id: title
y: 439
height: 33
text: game.gameTitle
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
font.pixelSize: 22
horizontalAlignment: Text.AlignHCenter
anchors.rightMargin: 8
anchors.leftMargin: 8
anchors.bottomMargin: 8
}
}
<?xml version="1.0" encoding="UTF-8"?>
<svg width="128" height="128" version="1.1" viewBox="0 0 33.867 33.867" xmlns="http://www.w3.org/2000/svg">
<path d="m32.211 22.315h-18.796v6.0794l-11.461-11.461 11.461-11.461v6.0794h18.796z" fill="#f9f9f9" stroke="#1a1a1a" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.438" style="paint-order:normal"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="128" height="128" version="1.1" viewBox="0 0 33.867 33.867" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-1.1156 2.8582)" stroke="#1a1a1a" stroke-linecap="round" stroke-linejoin="round">
<path d="m13.952 5.5733c0.1189-0.93676 0.77503-1.6426 1.43-2.2733 0.38912-0.37471 0.62422-0.62811 1.1-0.88 1.5766-0.83466 2.771-0.42136 4.0333 0.69667 0.50459 0.44693 0.70636 0.62165 1.0633 1.1733 0.96765 1.4955 0.55549 2.3404-0.14667 3.85-0.32616 0.70125-0.69834 1.3791-1.1367 2.0167-0.47007 0.68375-1.0553 1.271-1.54 1.9433-0.20617 0.28598-0.39111 0.58667-0.58667 0.88-0.42478 0.63717-0.89741 1.2767-1.21 1.98-0.36563 0.82266-0.54629 1.6465-0.69667 2.53-0.18045 1.0601-0.32624 2.1115-0.25667 3.19 0.04662 0.72258 0.36404 1.3488 0.58667 2.0167" fill="none" stroke-width="1.438" style="paint-order:normal"/>
<path d="m17.942 25.729a0.63785 0.63785 0 0 1-0.63785 0.63785 0.63785 0.63785 0 0 1-0.63785-0.63785 0.63785 0.63785 0 0 1 0.63785-0.63785 0.63785 0.63785 0 0 1 0.63785 0.63785z" stroke-width="1.0423"/>
</g>
</svg>
...@@ -3,8 +3,23 @@ import QtQuick ...@@ -3,8 +3,23 @@ import QtQuick
// Import all components from folder // Import all components from folder
import "scenes" import "scenes"
import "constants/scene.js" as SceneConstants
Window { Window {
property string scene: SceneConstants.homeScene
Connections {
target: core_app
function onGameStarted(done) {
console.log("gameStarted!!");
window.scene = SceneConstants.runningScene;
}
function onGameEnded(done) {
console.log("gameEnded!!");
window.scene = SceneConstants.gameInfoScene;
}
}
id: window id: window
width: 640 width: 640
height: 480 height: 480
...@@ -12,10 +27,25 @@ Window { ...@@ -12,10 +27,25 @@ Window {
title: qsTr("Launcher") title: qsTr("Launcher")
HomeScene { HomeScene {
id: grid visible: scene == SceneConstants.homeScene
id: homeScene
anchors.fill: parent anchors.fill: parent
} }
GameInfoScene {
visible: scene == SceneConstants.gameInfoScene
id: gameInfoScene
anchors.fill: parent
}
RunningScene {
visible: scene == SceneConstants.runningScene
id: runningScene
anchors.fill: parent
}
/* InputPanelHomeScene { /* InputPanelHomeScene {
id: inputPanel id: inputPanel
z: 99 z: 99
......
import QtQuick
import QtQuick.Controls
import "../components"
import "../constants/scene.js" as S
Rectangle {
property string title: "Generic title"
property string icon: ""
property string exec: ""
id: container
x: 0
y: 0
width: 640
height: 480
Rectangle {
id: rectangle
height: 64
color: "#2b2b2b"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
}
ButtonImage {
id: back
x: 0
y: 0
width: 64
height: 64
imageUrl: "../images/back.svg"
onClicked: function(){
window.scene = S.homeScene;
}
}
Image {
id: gameImage
width: 128
height: 128
anchors.left: parent.left
anchors.top: parent.top
source: container.icon
anchors.leftMargin: 8
anchors.topMargin: 70
fillMode: Image.PreserveAspectFit
}
Text {
id: title
height: 49
text: container.title
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
font.pixelSize: 32
anchors.leftMargin: 142
anchors.topMargin: 70
anchors.rightMargin: 8
}
Button {
anchors.left: parent.left
anchors.top: parent.top
anchors.leftMargin: 142
anchors.topMargin: 125
text: "Run game"
onClicked: function(){
if(core_app === undefined) return;
core_app.start_game(container.exec);
}
}
}
...@@ -12,7 +12,7 @@ Rectangle { ...@@ -12,7 +12,7 @@ Rectangle {
Rectangle { Rectangle {
id: rectangle id: rectangle
height: 50 height: 64
color: "#2b2b2b" color: "#2b2b2b"
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
...@@ -28,7 +28,7 @@ Rectangle { ...@@ -28,7 +28,7 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.topMargin: 48 anchors.topMargin: 64
anchors.bottomMargin: 0 anchors.bottomMargin: 0
anchors.leftMargin: 0 anchors.leftMargin: 0
anchors.rightMargin: 0 anchors.rightMargin: 0
......
import QtQuick
import QtQuick.Controls
import "../components"
Rectangle {
id: container
x: 0
y: 0
width: 640
height: 480
Rectangle {
id: rectangle
color: "#2b2b2b"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
Text {
id: text1
color: "#ffffff"
text: qsTr("Game is now running...")
anchors.fill: parent
font.pixelSize: 24
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
}
}
}
# This Python file uses the following encoding: utf-8
import sys import sys
from pathlib import Path from pathlib import Path
from PySide6.QtGui import QGuiApplication from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQml import QQmlApplicationEngine
from src.models.App import App from models.App import App
from models.GamesModel import GamesModel
# TODO: add VirtualKeyboard # TODO: add VirtualKeyboard
...@@ -13,10 +13,13 @@ if __name__ == "__main__": ...@@ -13,10 +13,13 @@ if __name__ == "__main__":
app = QGuiApplication(sys.argv) app = QGuiApplication(sys.argv)
qml_file = Path(__file__).resolve().parent / "../qml/qml.qml" qml_file = Path(__file__).resolve().parent / "../qml/qml.qml"
engine = QQmlApplicationEngine() engine = QQmlApplicationEngine()
app_model = App()
context = engine.rootContext()
context.setContextProperty("core_app", app_model)
engine.load(qml_file) engine.load(qml_file)
if not engine.rootObjects(): if not engine.rootObjects():
sys.exit(-1) sys.exit(-1)
appModel = App()
context = engine.rootContext()
context.setContextProperty("app", appModel)
sys.exit(app.exec()) sys.exit(app.exec())
import threading
from time import sleep
from PySide6 import QtCore from PySide6 import QtCore
from os.path import expanduser from os.path import expanduser
import glob import glob
from desktop_parser import DesktopFile from desktop_parser import DesktopFile
import os.path
from pathlib import Path
from PySide6.QtCore import Property, Signal, Slot, QObject, Qt
from models.GamesModel import Game, GamesModel
import subprocess
class GameShortcut: class GameShortcut:
...@@ -12,31 +20,89 @@ class GameShortcut: ...@@ -12,31 +20,89 @@ class GameShortcut:
class App(QtCore.QObject): class App(QtCore.QObject):
game_started = Signal(bool, name="gameStarted")
game_ended = Signal(bool, name="gameEnded")
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.games_model = GamesModel()
self.home = expanduser('~') self.home = expanduser('~')
self.config_location = '/.config/PortProton.conf' self.config_location = '/.config/PortProton.conf'
self.portproton_location = '' self.portproton_location = ''
self.running_game_process = None
self.setup() self.setup()
def setup(self): def setup(self):
try: try:
with open(self.home + self.config_location, 'r') as file: with open(self.home + self.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.games_model.clear()
files = glob.glob(f"{self.portproton_location}/*.desktop") files = glob.glob(f"{self.portproton_location}/*.desktop")
# for val in files:
# print(val) for val in files:
# desktop_file = DesktopFile.from_file("path/to/file.desktop") desktop_file = DesktopFile.from_file(val)
data = desktop_file.data
entry = data['Desktop Entry']
name = entry['Name'] or 'generic'
exec = 'Exec' in entry and entry['Exec'] or ''
icon = entry['Icon']
assert (isinstance(name, str)
and isinstance(exec, str)
and isinstance(icon, str))
exec_split = exec.split(' ')
# Ignore extra non-related desktop entries
if (len(exec_split) <= 1 or
('data/scripts/start.sh' not in exec_split[1] or '%F' in exec_split[-1])):
continue
# TODO parse product name
icon = (os.path.isfile(icon) and icon
or os.path.realpath(f"{Path(__file__).resolve().parent}../../../qml/images/game_icon.png"))
self.games_model.add_game(Game(name=name, icon=icon, exec=exec))
except FileNotFoundError: except FileNotFoundError:
print('File not found') print('File not found')
except Exception: except Exception as e:
print('An error occurred') print('An error occurred', e)
pass pass
### SLOTS ### ### SLOTS ###
@QtCore.Slot() @Slot(str)
def get_games(self): def start_game(self, exec):
self.game_started.emit(True)
def run_in_thread(t, _exec):
t.running_game_process = subprocess.Popen(
_exec,
shell=True,
bufsize=0,
stdout=subprocess.PIPE,
universal_newlines=True
)
t.running_game_process.wait()
t.game_ended.emit(True)
# output = self.running_game_process.stdout.read()
# self.running_game_process.stdout.close()
return
thread = threading.Thread(target=run_in_thread, args=(self, exec))
thread.start()
pass pass
### PROPERTIES ###
@Property(QObject, constant=True)
def games(self):
return self.games_model
import typing
from dataclasses import dataclass, fields
from PySide6.QtCore import QAbstractListModel, QModelIndex, Qt, QByteArray
@dataclass
class Game:
name: str = ''
exec: str = ''
icon: str = ''
class GamesModel(QAbstractListModel):
def __init__(self):
super().__init__()
self._list = []
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any:
if 0 <= index.row() < self.rowCount():
student = self._list[index.row()]
name = self.roleNames().get(role)
if name:
return getattr(student, name.decode())
def roleNames(self) -> dict[int, QByteArray]:
d = {}
for i, field in enumerate(fields(Game)):
d[Qt.DisplayRole + i] = field.name.encode()
return d
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
return len(self._list)
def add_game(self, game: Game) -> None:
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._list.append(game)
self.endInsertRows()
def clear(self) -> None:
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._list = []
self.endInsertRows()
pass
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