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