Commit 41ec7952 authored by Antoine Lima's avatar Antoine Lima
Browse files

Restructuration: panel->module, separation of module & ui, subpackage organization

parent dfa09c81
__pycache__/ */__pycache__/
*/*.log
*/*_ui.py
*/*_rc.py
# source-rasp # source-rasp
Source code for the foosball embedded Raspberry PI. It runs the UI and manages games. Source code for the foosball embedded Raspberry PI. It runs the UI and manages games.
The UI is done with PyQt5.
The UI is done with PyQt. ## Launch
* Make sure to have Python>3.5 installed (`sudo apt-get install python3` otherwise)
* Make sure to have PyQt5 installed (`sudo pip install pyqt5` otherwise)
* Compile the .ui and .rc files: `./compile_resources.sh`
* Execute main.py: `python3 main.py`
## Edit the UI ## Edit the UI
The UI is mostly created using Qt-Designer:
* Download qt4-designer (`sudo apt-get install qt4-designer`) * Download qt4-designer (`sudo apt-get install qt4-designer`)
* Edit .ui files with qt4-designer * Edit .ui files with qt4-designer
* Convert the .ui files to python sources (`pyuic5 file.ui -o file_ui.py`) * Compile the .ui in a python file: `./compile_resources.sh`
* Done! * Done!
## UI Organisation ## Sofware Organisation
The software is composed of only one window in which "panels" change. There are currently 3 panels: ### UI
* The main menu, default panel shown on startup The UI is only made of one window with static widgets and changing "modules".
* The game itself, containing the scores, timers, ... There are currently 3 modules:
* The options menu, to configure some options * Main menu, showed on startup and giving access to other panels
* Game, containing the scores, timers, etc of the current game
Those panels are stacked (in a QStackedWidget) and only one of them can be shown at a time. * Options, to allow the user tp configure some options
We can then "swap" from one to the other, making sure to unload the previous and to load the ) * Authentification for two players
For better control over what is being run, panels can be unloaded when swapped and re-loaded when shown again. * ...
This can be done through two overloadable functions: load and unload.
Each panel (inheriting from the Panel class) can use those to perform actions when shown or hidden Those inherit from the `Module` class and are composed of 5 things:
\ No newline at end of file * Access to the main window, through the `parent_win` member
* A widget (built from a .ui file, see sections above)
* 3 management functions:
* Load: run every time the module is showed, meant to start timers & co
* Unload: run every time the module is hidden, meant to stop timers & co
* Other: can be called at any time by other modules, meant to exchange informations
The widget contained by each module are stacked in a QStackedWidget and only one of them can be seen at a time.
To get from one to the other, the current module is unloaded current, then the new one is shown and loaded.
Each module can overload the load/unload/other method in order to have a specific behavior.
### DB
## Todo
* [ ] ...
<RCC>
<qresource prefix="img">
<file>bg0.jpg</file>
</qresource>
</RCC>
This diff is collapsed.
WARNING:root:Unimplemented method
DEBUG:root:start
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score1
DEBUG:root:cancel
WARNING:root:Unimplemented method
DEBUG:root:exit
WARNING:root:Unimplemented method
DEBUG:root:start
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score2
DEBUG:root:score1
DEBUG:root:score1
DEBUG:root:score1
DEBUG:root:score1
DEBUG:root:cancel
WARNING:root:Unimplemented method
DEBUG:root:exit
#! /bin/sh
rm ui/*_ui.py
rm ui/*_rc.py
rm -r __pycache__
rm -r ui/__pycache__
rm -r modules/__pycache__
#! /bin/sh
pyuic5 --import-from=ui ui/main.ui -o ui/main_ui.py
pyuic5 --import-from=ui ui/menu.ui -o ui/menu_ui.py
pyuic5 --import-from=ui ui/game.ui -o ui/game_ui.py
pyuic5 --import-from=ui ui/options.ui -o ui/options_ui.py
pyuic5 --import-from=ui ui/auth2p.ui -o ui/auth2p_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
pyrcc5 -root /ui ui/assets.qrc -o ui/assets_rc.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'game.ui'
#
# Created by: PyQt5 UI code generator 5.11.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(1080, 727)
Form.setStyleSheet("#btnScore1, #btnScore2 {\n"
"background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 white, stop: 1 rgb(168, 170, 184));\n"
"color: black;\n"
" border-style: solid;\n"
" border-width:1px;\n"
" border-radius:14ex;\n"
" border-color: green;\n"
" max-width:200px;\n"
" max-height:200px;\n"
" min-width:200px;\n"
" min-height:200px;\n"
"}\n"
"")
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
self.verticalLayout.addItem(spacerItem)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.btnScore1 = QtWidgets.QPushButton(Form)
self.btnScore1.setObjectName("btnScore1")
self.horizontalLayout.addWidget(self.btnScore1)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem2)
self.btnScore2 = QtWidgets.QPushButton(Form)
self.btnScore2.setObjectName("btnScore2")
self.horizontalLayout.addWidget(self.btnScore2)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem3)
self.verticalLayout.addLayout(self.horizontalLayout)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
self.verticalLayout.addItem(spacerItem4)
self.lcdChrono = QtWidgets.QLCDNumber(Form)
self.lcdChrono.setMinimumSize(QtCore.QSize(0, 75))
self.lcdChrono.setFrameShape(QtWidgets.QFrame.NoFrame)
self.lcdChrono.setSmallDecimalPoint(False)
self.lcdChrono.setDigitCount(8)
self.lcdChrono.setObjectName("lcdChrono")
self.verticalLayout.addWidget(self.lcdChrono)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem5)
self.btnCancel = QtWidgets.QPushButton(Form)
self.btnCancel.setObjectName("btnCancel")
self.horizontalLayout_2.addWidget(self.btnCancel)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.btnScore1.setText(_translate("Form", "0"))
self.btnScore2.setText(_translate("Form", "0"))
self.btnCancel.setText(_translate("Form", "Cancel"))
...@@ -12,20 +12,21 @@ import logging ...@@ -12,20 +12,21 @@ import logging
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from PyQt5.QtCore import QTime from PyQt5.QtCore import QTime
from main_ui import Ui_MainWindow from ui.main_ui import Ui_MainWindow
from panels import MenuPanel, GamePanel, OptionsPanel from modules import *
class MainWin(QtWidgets.QMainWindow): class MainWin(QtWidgets.QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) QtWidgets.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow() self.ui = Ui_MainWindow()
self.ui.setupUi(self) self.ui.setupUi(self)
self.ui.panels.addWidget(MenuPanel(self)) self.modules = [MenuModule, GameModule, OptionsModule]
self.ui.panels.addWidget(GamePanel(self))
self.ui.panels.addWidget(OptionsPanel(self)) for mod in self.modules:
self.ui.panels.addWidget(mod(self))
self.ui.panels.setCurrentIndex(0) self.ui.panels.setCurrentIndex(0)
self.displaySystemTime() self.displaySystemTime()
self.startTimer(1000) self.startTimer(1000)
...@@ -41,4 +42,4 @@ if __name__=='__main__': ...@@ -41,4 +42,4 @@ if __name__=='__main__':
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
myapp = MainWin() myapp = MainWin()
myapp.show() myapp.show()
app.exec_() app.exec_()
\ No newline at end of file
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'main.ui'
#
# Created by: PyQt5 UI code generator 5.11.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(640, 360)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QtCore.QSize(640, 360))
MainWindow.setMaximumSize(QtCore.QSize(1920, 1080))
MainWindow.setSizeIncrement(QtCore.QSize(1, 1))
MainWindow.setBaseSize(QtCore.QSize(640, 360))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setStyleSheet("#centralWidget {border-image: url(:/img/bg0.jpg) 0 0 0 0 stretch stretch; }")
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.lcdTime = QtWidgets.QLCDNumber(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lcdTime.sizePolicy().hasHeightForWidth())
self.lcdTime.setSizePolicy(sizePolicy)
self.lcdTime.setFrameShape(QtWidgets.QFrame.NoFrame)
self.lcdTime.setDigitCount(8)
self.lcdTime.setSegmentStyle(QtWidgets.QLCDNumber.Flat)
self.lcdTime.setObjectName("lcdTime")
self.horizontalLayout.addWidget(self.lcdTime)
self.verticalLayout.addLayout(self.horizontalLayout)
self.panels = QtWidgets.QStackedWidget(self.centralwidget)
self.panels.setAutoFillBackground(False)
self.panels.setObjectName("panels")
self.verticalLayout.addWidget(self.panels)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
self.panels.setCurrentIndex(-1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Babyfoot"))
import assets_rc
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>813</width>
<height>463</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>36</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>37</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>36</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn0Start">
<property name="text">
<string>Start</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>37</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn1Options">
<property name="text">
<string>Options</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn2Exit">
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>36</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>37</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>36</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'menu.ui'
#
# Created by: PyQt5 UI code generator 5.11.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(813, 463)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
spacerItem = QtWidgets.QSpacerItem(20, 36, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
spacerItem1 = QtWidgets.QSpacerItem(20, 37, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem1)
spacerItem2 = QtWidgets.QSpacerItem(20, 36, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem2)
self.btn0Start = QtWidgets.QPushButton(Form)
self.btn0Start.setAutoDefault(True)
self.btn0Start.setDefault(True)
self.btn0Start.setObjectName("btn0Start")
self.verticalLayout.addWidget(self.btn0Start)
spacerItem3 = QtWidgets.QSpacerItem(20, 37, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem3)
self.btn1Options = QtWidgets.QPushButton(Form)
self.btn1Options.setObjectName("btn1Options")
self.verticalLayout.addWidget(self.btn1Options)
self.btn2Exit = QtWidgets.QPushButton(Form)
self.btn2Exit.setObjectName("btn2Exit")
self.verticalLayout.addWidget(self.btn2Exit)
spacerItem4 = QtWidgets.QSpacerItem(20, 36, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem4)
spacerItem5 = QtWidgets.QSpacerItem(20, 37, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem5)
spacerItem6 = QtWidgets.QSpacerItem(20, 36, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem6)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.btn0Start.setText(_translate("Form", "Start"))
self.btn1Options.setText(_translate("Form", "Options"))
self.btn2Exit.setText(_translate("Form", "Exit"))
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import logging
from PyQt5 import QtWidgets
from PyQt5.QtGui import QRegion
from PyQt5.QtCore import QTime, QTimer
from PyQt5.QtWidgets import QTableWidgetItem, QComboBox
from modules import *
class Module(QtWidgets.QWidget):
def __init__(self, parent=None, widget=None):
# UI Setup
QtWidgets.QWidget.__init__(self, parent)
self.parent_win = parent
self.ui = widget
self.ui.setupUi(self)
def switchModule(self, new_type):
panel_idx = self.parent_win.modules.index(new_type)
if panel_idx<0:
logging.error('Error: unknown panel {}'.format(new_type))
else:
self.parent_win.ui.panels.currentWidget().unload()
self.parent_win.ui.panels.setCurrentIndex(panel_idx)
self.parent_win.ui.panels.currentWidget().load()
def load(self):
logging.warning('Unimplemented method "load" for {}'.format(self.__class__))
def unload(self):
logging.warning('Unimplemented method "unload" for {}'.format(self.__class__))
def other(self, **kwargs):
logging.warning('Unimplemented method "other" for {}'.format(self.__class__))
from modules.auth import AuthModule
from modules.game import GameModule
from modules.menu import MenuModule
from modules.options import OptionsModule
from modules.leaderboard import LeaderboardModule
...@@ -8,69 +8,15 @@ Created on Wed Apr 18 18:34:40 2018 ...@@ -8,69 +8,15 @@ Created on Wed Apr 18 18:34:40 2018
import logging import logging
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTime, QTimer from PyQt5.QtCore import QTime, QTimer
from PyQt5.QtGui import QRegion
from PyQt5.QtWidgets import QTableWidgetItem, QComboBox
from menu_ui import Ui_Form as Menu_Form
from game_ui import Ui_Form as Game_Form
from options_ui import Ui_Form as Options_Form
class Panel(QtWidgets.QWidget):
def __init__(self, parent=None, form=None):
Panel.__dict_panels = {MenuPanel: 0, GamePanel: 1, OptionsPanel: 2}
# UI Setup
QtWidgets.QWidget.__init__(self, parent)
self.ui = form
self.ui.setupUi(self)
self.parentWin = parent
def switchPanel(self, newType):
panelIndex = Panel.__dict_panels.get(newType)
if panelIndex!=None:
self.parentWin.ui.panels.currentWidget().unload()
self.parentWin.ui.panels.setCurrentIndex(panelIndex)
self.parentWin.ui.panels.currentWidget().load()
else:
logging.error('Error: unknown panel {} in {}'.format(newType, self.__dict_panels))
def load(self): from module import Module
logging.warning('Unimplemented method') import modules
from ui.game_ui import Ui_Form as GameWidget
def unload(self):
logging.warning('Unimplemented method')
class MenuPanel(Panel):
def __init__(self, parent=None):
super().__init__(parent, Menu_Form())
# Button connections
self.ui.btn0Start.clicked.connect(self.ui_handleClick_btnStart)
self.ui.btn1Options.clicked.connect(self.ui_handleClick_btnOptions)
self.ui.btn2Exit.clicked.connect(self.ui_handleClick_btnExit)
def load(self):
logging.debug('Loading MenuPanel')
def unload(self):
logging.debug('Unloading MenuPanel')
def ui_handleClick_btnStart(self): class GameModule(Module):
logging.debug('start')
self.switchPanel(GamePanel)
def ui_handleClick_btnOptions(self):
self.switchPanel(OptionsPanel)