init: начало работы над приложением с использованием wxPython
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[codz]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
.pybuild
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
ENTRY_POINT = os.path.join(PROJECT_ROOT, "src", "mind_reader", "app.py")
|
||||||
|
|
||||||
|
|
||||||
|
def build():
|
||||||
|
cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"PyInstaller",
|
||||||
|
"--noconfirm",
|
||||||
|
"--clean",
|
||||||
|
"--onedir",
|
||||||
|
"--windowed",
|
||||||
|
"--name",
|
||||||
|
"MindReader",
|
||||||
|
"--paths",
|
||||||
|
os.path.join(PROJECT_ROOT, "src"),
|
||||||
|
ENTRY_POINT,
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, cwd=PROJECT_ROOT)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
dist_path = os.path.join(PROJECT_ROOT, "dist", "MindReader")
|
||||||
|
print("\nBuild completed!")
|
||||||
|
print(f"{dist_path}")
|
||||||
|
else:
|
||||||
|
print("\nError!")
|
||||||
|
|
||||||
|
|
||||||
|
def clean():
|
||||||
|
print("Cleaning...")
|
||||||
|
|
||||||
|
dirs_to_remove = [
|
||||||
|
os.path.join(PROJECT_ROOT, "build"),
|
||||||
|
os.path.join(PROJECT_ROOT, "dist"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for d in dirs_to_remove:
|
||||||
|
if os.path.exists(d):
|
||||||
|
shutil.rmtree(d)
|
||||||
|
print(f"{d} - deleted")
|
||||||
|
|
||||||
|
for item in os.listdir(PROJECT_ROOT):
|
||||||
|
if item.endswith(".spec"):
|
||||||
|
spec_path = os.path.join(PROJECT_ROOT, item)
|
||||||
|
os.remove(spec_path)
|
||||||
|
print(f"{spec_path} - deleted")
|
||||||
|
|
||||||
|
print("Cleaning completed!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--clean-only", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.clean_only:
|
||||||
|
clean()
|
||||||
|
else:
|
||||||
|
build()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "mind-reader"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Сетевая игра на угадывание последовательностей"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"wxPython>=4.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
mind-reader = "mind_reader.app:main"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from mind_reader.app import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import wx
|
||||||
|
|
||||||
|
from mind_reader.ui.main_frame import MainFrame
|
||||||
|
|
||||||
|
|
||||||
|
def enable_high_dpi_support():
|
||||||
|
if sys.platform == "win32":
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
ctypes.windll.shcore.SetProcessDpiAwareness(2)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
ctypes.windll.user32.SetProcessDPIAware()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MindReaderApp(wx.App):
|
||||||
|
def OnInit(self) -> bool:
|
||||||
|
self.frame = MainFrame()
|
||||||
|
self.frame.Show(True)
|
||||||
|
self.SetTopWindow(self.frame)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
enable_high_dpi_support()
|
||||||
|
|
||||||
|
app = MindReaderApp(redirect=False)
|
||||||
|
app.MainLoop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from mind_reader.ui.dialogs.client_dialog import ClientConnectDialog
|
||||||
|
from mind_reader.ui.dialogs.server_dialog import ServerStartDialog
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ClientConnectDialog",
|
||||||
|
"ServerStartDialog",
|
||||||
|
]
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import wx
|
||||||
|
|
||||||
|
|
||||||
|
class ClientConnectDialog(wx.Dialog):
|
||||||
|
def __init__(self, parent: wx.Window):
|
||||||
|
super().__init__(
|
||||||
|
parent=parent,
|
||||||
|
title="Подключение к серверу",
|
||||||
|
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._init_ui()
|
||||||
|
|
||||||
|
self.Fit()
|
||||||
|
calculated_size = self.GetSize()
|
||||||
|
self.SetMinSize(calculated_size)
|
||||||
|
self.CenterOnParent()
|
||||||
|
|
||||||
|
def _init_ui(self):
|
||||||
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
grid_sizer = wx.FlexGridSizer(rows=2, cols=2, vgap=10, hgap=10)
|
||||||
|
grid_sizer.AddGrowableCol(1, 1)
|
||||||
|
|
||||||
|
label_name = wx.StaticText(self, label="Ваше имя:")
|
||||||
|
self.text_name = wx.TextCtrl(self, value="Игрок1")
|
||||||
|
|
||||||
|
grid_sizer.Add(label_name, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||||
|
grid_sizer.Add(self.text_name, 1, wx.EXPAND)
|
||||||
|
|
||||||
|
label_ip = wx.StaticText(self, label="IP адрес сервера:")
|
||||||
|
self.text_ip = wx.TextCtrl(self, value="127.0.0.1")
|
||||||
|
|
||||||
|
grid_sizer.Add(label_ip, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||||
|
grid_sizer.Add(self.text_ip, 1, wx.EXPAND)
|
||||||
|
|
||||||
|
main_sizer.Add(grid_sizer, 0, wx.EXPAND | wx.ALL, 15)
|
||||||
|
|
||||||
|
main_sizer.Add(
|
||||||
|
wx.StaticLine(self), 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10
|
||||||
|
)
|
||||||
|
|
||||||
|
button_sizer = wx.StdDialogButtonSizer()
|
||||||
|
self.button_connect = wx.Button(self, wx.ID_OK, label="Подключиться")
|
||||||
|
self.button_cancel = wx.Button(self, wx.ID_CANCEL, label="Отмена")
|
||||||
|
|
||||||
|
button_sizer.AddButton(self.button_connect)
|
||||||
|
button_sizer.AddButton(self.button_cancel)
|
||||||
|
button_sizer.Realize()
|
||||||
|
|
||||||
|
main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||||
|
|
||||||
|
self.SetSizer(main_sizer)
|
||||||
|
|
||||||
|
self.button_connect.Bind(wx.EVT_BUTTON, self._on_connect)
|
||||||
|
|
||||||
|
def _on_connect(self, event):
|
||||||
|
name = self.text_name.GetValue().strip()
|
||||||
|
ip = self.text_ip.GetValue().strip()
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
wx.MessageBox(
|
||||||
|
"Пожалуйста, введите ваше имя",
|
||||||
|
"Ошибка",
|
||||||
|
wx.OK | wx.ICON_WARNING,
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not ip:
|
||||||
|
wx.MessageBox(
|
||||||
|
"Пожалуйста, введите IP адрес сервера",
|
||||||
|
"Ошибка",
|
||||||
|
wx.OK | wx.ICON_WARNING,
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
def get_data(self) -> tuple[str, str]:
|
||||||
|
return (
|
||||||
|
self.text_name.GetValue().strip(),
|
||||||
|
self.text_ip.GetValue().strip(),
|
||||||
|
)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
import wx
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_ip_addresses() -> list[str]:
|
||||||
|
ip_list = [
|
||||||
|
"127.0.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
for ip in socket.gethostbyname_ex(hostname)[2]:
|
||||||
|
if ip not in ip_list:
|
||||||
|
ip_list.append(ip)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ip_list
|
||||||
|
|
||||||
|
|
||||||
|
class ServerStartDialog(wx.Dialog):
|
||||||
|
def __init__(self, parent: wx.Window):
|
||||||
|
super().__init__(
|
||||||
|
parent=parent,
|
||||||
|
title="Запуск сервера",
|
||||||
|
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._init_ui()
|
||||||
|
|
||||||
|
self.Fit()
|
||||||
|
calculated_size = self.GetSize()
|
||||||
|
self.SetMinSize(calculated_size)
|
||||||
|
self.CenterOnParent()
|
||||||
|
|
||||||
|
def _init_ui(self):
|
||||||
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
grid_sizer = wx.FlexGridSizer(rows=2, cols=2, vgap=10, hgap=10)
|
||||||
|
grid_sizer.AddGrowableCol(1, 1)
|
||||||
|
|
||||||
|
label_name = wx.StaticText(self, label="Ваше имя:")
|
||||||
|
self.text_name = wx.TextCtrl(self, value="Игрок2")
|
||||||
|
grid_sizer.Add(label_name, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||||
|
grid_sizer.Add(self.text_name, 1, wx.EXPAND)
|
||||||
|
|
||||||
|
label_ip = wx.StaticText(self, label="IP адрес для запуска:")
|
||||||
|
available_ips = get_available_ip_addresses()
|
||||||
|
self.combo_ip = wx.ComboBox(
|
||||||
|
self,
|
||||||
|
choices=available_ips,
|
||||||
|
style=wx.CB_READONLY,
|
||||||
|
)
|
||||||
|
if available_ips:
|
||||||
|
self.combo_ip.SetSelection(0)
|
||||||
|
|
||||||
|
grid_sizer.Add(label_ip, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||||
|
grid_sizer.Add(self.combo_ip, 1, wx.EXPAND)
|
||||||
|
|
||||||
|
main_sizer.Add(grid_sizer, 0, wx.EXPAND | wx.ALL, 15)
|
||||||
|
|
||||||
|
main_sizer.Add(
|
||||||
|
wx.StaticLine(self), 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10
|
||||||
|
)
|
||||||
|
|
||||||
|
button_sizer = wx.StdDialogButtonSizer()
|
||||||
|
self.button_start = wx.Button(
|
||||||
|
self, wx.ID_OK, label="Ожидать подключение"
|
||||||
|
)
|
||||||
|
self.button_cancel = wx.Button(self, wx.ID_CANCEL, label="Отмена")
|
||||||
|
|
||||||
|
button_sizer.AddButton(self.button_start)
|
||||||
|
button_sizer.AddButton(self.button_cancel)
|
||||||
|
button_sizer.Realize()
|
||||||
|
|
||||||
|
main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||||
|
|
||||||
|
self.SetSizer(main_sizer)
|
||||||
|
|
||||||
|
self.button_start.Bind(wx.EVT_BUTTON, self._on_start)
|
||||||
|
|
||||||
|
def _on_start(self, event):
|
||||||
|
name = self.text_name.GetValue().strip()
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
wx.MessageBox(
|
||||||
|
"Пожалуйста, введите ваше имя",
|
||||||
|
"Ошибка",
|
||||||
|
wx.OK | wx.ICON_WARNING,
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
def get_data(self) -> tuple[str, str]:
|
||||||
|
return (
|
||||||
|
self.text_name.GetValue().strip(),
|
||||||
|
self.combo_ip.GetValue(),
|
||||||
|
)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import wx
|
||||||
|
|
||||||
|
from mind_reader.ui.dialogs import ClientConnectDialog, ServerStartDialog
|
||||||
|
from mind_reader.ui.menu_bar import AppMenuBar
|
||||||
|
|
||||||
|
|
||||||
|
class MainFrame(wx.Frame):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
parent=None,
|
||||||
|
id=wx.ID_ANY,
|
||||||
|
title="Mind Reader",
|
||||||
|
size=wx.Size(700, 500),
|
||||||
|
style=wx.DEFAULT_FRAME_STYLE,
|
||||||
|
)
|
||||||
|
self.Center()
|
||||||
|
|
||||||
|
self.menu_bar = AppMenuBar(self)
|
||||||
|
self.SetMenuBar(self.menu_bar)
|
||||||
|
|
||||||
|
self.CreateStatusBar(1)
|
||||||
|
self.SetStatusText("Подключение отсутствует")
|
||||||
|
|
||||||
|
self._bind_menu_events()
|
||||||
|
|
||||||
|
def _bind_menu_events(self):
|
||||||
|
self.Bind(
|
||||||
|
wx.EVT_MENU, self.on_client_connect, self.menu_bar.item_client
|
||||||
|
)
|
||||||
|
self.Bind(wx.EVT_MENU, self.on_server_start, self.menu_bar.item_server)
|
||||||
|
self.Bind(
|
||||||
|
wx.EVT_MENU, self.on_disconnect, self.menu_bar.item_disconnect
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_client_connect(self, event):
|
||||||
|
d = ClientConnectDialog(self)
|
||||||
|
if d.ShowModal() == wx.ID_OK:
|
||||||
|
name, ip = d.get_data()
|
||||||
|
self.menu_bar.set_connected_state(True)
|
||||||
|
self.SetStatusText(f"Подключено к {ip} | Ваше имя {name}")
|
||||||
|
d.Destroy()
|
||||||
|
|
||||||
|
def on_server_start(self, event):
|
||||||
|
d = ServerStartDialog(self)
|
||||||
|
if d.ShowModal() == wx.ID_OK:
|
||||||
|
name, ip = d.get_data()
|
||||||
|
self.menu_bar.set_connected_state(True)
|
||||||
|
self.SetStatusText(f"Сервер запущен на {ip} | Ваше имя {name}")
|
||||||
|
d.Destroy()
|
||||||
|
|
||||||
|
def on_disconnect(self, event):
|
||||||
|
self.menu_bar.set_connected_state(False)
|
||||||
|
self.SetStatusText("Подключение отсутствует")
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import wx
|
||||||
|
|
||||||
|
|
||||||
|
class AppMenuBar(wx.MenuBar):
|
||||||
|
def __init__(self, parent: wx.Frame):
|
||||||
|
super().__init__()
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self._create_file_menu()
|
||||||
|
self._create_connection_menu()
|
||||||
|
self._create_help_menu()
|
||||||
|
|
||||||
|
def _create_file_menu(self):
|
||||||
|
self.file_menu = wx.Menu()
|
||||||
|
|
||||||
|
self.item_open = self.file_menu.Append(
|
||||||
|
wx.ID_OPEN,
|
||||||
|
"&Открыть...\tCtrl+O",
|
||||||
|
"Открыть файл с результатами сессий",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.item_save = self.file_menu.Append(
|
||||||
|
wx.ID_SAVE,
|
||||||
|
"&Сохранить\tCtrl+S",
|
||||||
|
"Сохранить текущие результаты сессий",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.item_save_as = self.file_menu.Append(
|
||||||
|
wx.ID_SAVEAS,
|
||||||
|
"Сохранить &как...\tCtrl+Shift+S",
|
||||||
|
"Сохранить результаты сессий в новый файл",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.file_menu.AppendSeparator()
|
||||||
|
|
||||||
|
self.item_exit = self.file_menu.Append(
|
||||||
|
wx.ID_EXIT,
|
||||||
|
"&Выход\tAlt+F4",
|
||||||
|
"Завершить работу программы",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.Append(self.file_menu, "&Файл")
|
||||||
|
|
||||||
|
def _create_connection_menu(self):
|
||||||
|
self.conn_menu = wx.Menu()
|
||||||
|
|
||||||
|
self.item_client = self.conn_menu.Append(
|
||||||
|
wx.ID_ANY,
|
||||||
|
"&Клиент...",
|
||||||
|
"Подключиться к серверу",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.item_server = self.conn_menu.Append(
|
||||||
|
wx.ID_ANY,
|
||||||
|
"&Сервер...",
|
||||||
|
"Запустить сервер",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.conn_menu.AppendSeparator()
|
||||||
|
|
||||||
|
self.item_disconnect = self.conn_menu.Append(
|
||||||
|
wx.ID_ANY,
|
||||||
|
"&Разорвать подключение",
|
||||||
|
"Завершить сетевое соединение",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.item_disconnect.Enable(False)
|
||||||
|
|
||||||
|
self.Append(self.conn_menu, "&Подключение")
|
||||||
|
|
||||||
|
def _create_help_menu(self):
|
||||||
|
self.help_menu = wx.Menu()
|
||||||
|
|
||||||
|
self.item_about = self.help_menu.Append(
|
||||||
|
wx.ID_ABOUT,
|
||||||
|
"&О программе...\tF1",
|
||||||
|
"Информация о программе",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.Append(self.help_menu, "&Справка")
|
||||||
|
|
||||||
|
def set_connected_state(self, is_connected: bool):
|
||||||
|
self.item_client.Enable(not is_connected)
|
||||||
|
self.item_server.Enable(not is_connected)
|
||||||
|
self.item_disconnect.Enable(is_connected)
|
||||||
Reference in New Issue
Block a user