2 Commits
Author SHA1 Message Date
vladimir 6254de74e4 feat(ui): wip session_view.py 2026-08-11 04:08:55 +03:00
vladimir 1da622cd7c feat(ui): wip session_view.py 2026-08-10 03:29:23 +03:00
+332
View File
@@ -0,0 +1,332 @@
import sys
from dataclasses import dataclass
from enum import Enum, auto
from typing import Optional
import wx
from mind_reader.core import SessionState
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 CardState(Enum):
SETTER_NOT_SET = auto()
SETTER_HIDDEN = auto()
SETTER_REVEALED = auto()
GUESSER_DISABLED = auto()
GUESSER_WAITING = auto()
GUESSER_CORRECT = auto()
GUESSER_WRONG = auto()
class ArrowDirection(Enum):
LEFT = auto()
RIGHT = auto()
@dataclass
class SessionStats:
total: int
setted: int
correct: int
wrong: int
taken_time: float
@property
def accuracy(self) -> float:
accuracy: float
if self.total == 0:
accuracy = 0.0
accuracy = (self.correct / self.total) * 100
return round(accuracy, 1)
@dataclass(frozen=True)
class SetterCardColour:
NOT_SET = wx.Colour(82, 97, 107)
HIDDEN = wx.Colour(201, 214, 223)
REVEALED = wx.Colour(240, 245, 249)
@dataclass(frozen=True)
class GuesserCardColour:
DISABLED = wx.Colour(82, 97, 107)
WAITING = wx.Colour(201, 214, 223)
CORRECT = wx.Colour(76, 175, 80)
WRONG = wx.Colour(229, 115, 115)
@dataclass(frozen=True)
class ArrowSymbol:
LEFT: str = ""
RIGHT: str = ""
class Card(wx.Panel):
def __init__(self, parent, state: CardState):
super().__init__(parent, style=wx.BORDER_SIMPLE)
self._direction: Optional[ArrowDirection] = None
self._state = state
self._setup()
self._update()
def _setup(self):
self.SetMinSize(wx.Size(45, 75))
self.label = wx.StaticText(self, style=wx.ALIGN_CENTER)
font = self.label.GetFont()
font.SetPointSize(20)
self.label.SetFont(font)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label, 0, wx.EXPAND)
self.SetSizer(sizer)
def _update(self):
match self._state:
case CardState.SETTER_NOT_SET:
colour = SetterCardColour.NOT_SET
show_arrow = False
case CardState.SETTER_HIDDEN:
colour = SetterCardColour.HIDDEN
show_arrow = False
case CardState.SETTER_REVEALED:
colour = SetterCardColour.REVEALED
show_arrow = True
case CardState.GUESSER_DISABLED:
colour = GuesserCardColour.DISABLED
show_arrow = False
case CardState.GUESSER_WAITING:
colour = GuesserCardColour.WAITING
show_arrow = False
case CardState.GUESSER_CORRECT:
colour = GuesserCardColour.CORRECT
show_arrow = True
case CardState.GUESSER_WRONG:
colour = GuesserCardColour.WRONG
show_arrow = True
if show_arrow and self._direction:
match self._direction:
case ArrowDirection.LEFT:
symbol = ArrowSymbol.LEFT
case ArrowDirection.RIGHT:
symbol = ArrowSymbol.RIGHT
self.label.SetLabel(symbol)
else:
self.label.SetLabel("")
self.SetBackgroundColour(colour)
self.Layout()
self.Refresh()
def set(
self,
state: CardState,
direction: Optional[ArrowDirection] = None,
) -> None:
self._state = state
self._direction = direction
self._update()
class SessionPanel(wx.Panel):
def __init__(
self,
parent,
setter_name: str,
guesser_name: str,
sequence_length: int = 20,
):
super().__init__(parent)
self._setter_name = setter_name
self._guesser_name = guesser_name
self._sequence_length = sequence_length
self._setter_sequence: list[Card] = []
self._guesser_sequence: list[Card] = []
self._statusbar = self._get_statusbar()
self._setup()
self._fit_parent_frame()
def _setup(self) -> None:
main_sizer = wx.BoxSizer(wx.VERTICAL)
setter_title = wx.StaticText(
self, label=f"Загадывает: {self._setter_name}"
)
setter_sizer = wx.BoxSizer(wx.HORIZONTAL)
for _ in range(self._sequence_length):
card = Card(self, CardState.SETTER_NOT_SET)
setter_sizer.Add(card, 0, wx.ALL, 4)
self._setter_sequence.append(card)
guesser_title = wx.StaticText(
self, label=f"Отгадывает: {self._guesser_name}"
)
guesser_sizer = wx.BoxSizer(wx.HORIZONTAL)
for _ in range(self._sequence_length):
card = Card(self, CardState.GUESSER_DISABLED)
guesser_sizer.Add(card, 0, wx.ALL, 4)
self._guesser_sequence.append(card)
controls_sizer = self._create_controls_sizer()
secondary_sizer = wx.BoxSizer(wx.VERTICAL)
secondary_sizer.Add(setter_title, 0, wx.LEFT | wx.TOP, 9)
secondary_sizer.Add(setter_sizer, 0, wx.ALL, 5)
secondary_sizer.Add(guesser_title, 0, wx.LEFT | wx.TOP, 9)
secondary_sizer.Add(guesser_sizer, 0, wx.ALL, 5)
secondary_sizer.Add(
controls_sizer, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 25
)
main_sizer.Add(secondary_sizer, 0, wx.LEFT | wx.ALL, 0)
self.SetSizer(main_sizer)
def _create_controls_sizer(self) -> wx.Sizer:
left = wx.Button(self, label=ArrowSymbol.LEFT, size=wx.Size(145, 55))
right = wx.Button(self, label=ArrowSymbol.RIGHT, size=wx.Size(145, 55))
font = left.GetFont()
font.SetPointSize(22)
left.SetFont(font)
right.SetFont(font)
controls_sizer = wx.BoxSizer(wx.HORIZONTAL)
controls_sizer.Add(left, 0, wx.RIGHT, 10)
controls_sizer.Add(right, 0, wx.LEFT, 10)
return controls_sizer
def _get_statusbar(self) -> Optional[wx.StatusBar]:
top_frame = self.GetTopLevelParent()
if isinstance(top_frame, wx.Frame):
statusbar = top_frame.GetStatusBar()
if not statusbar:
statusbar = top_frame.CreateStatusBar(1)
statusbar.SetFieldsCount(4)
return statusbar
return None
def _autofit_statusbar(self, texts: list[str]) -> None:
if not self._statusbar:
return
dc = wx.ClientDC(self._statusbar)
width_1 = dc.GetTextExtent(texts[0]).width
width_2 = dc.GetTextExtent(texts[1]).width
width_3 = dc.GetTextExtent(texts[2]).width
self._statusbar.SetStatusWidths([-1, width_1, width_2, width_3])
def _fit_parent_frame(self) -> None:
top_frame = self.GetTopLevelParent()
if isinstance(top_frame, wx.Frame):
self.Layout()
best_size = self.GetBestSize()
min_size = top_frame.ClientToWindowSize(best_size)
top_frame.SetMinSize(min_size)
top_frame.Fit()
def update_setter_card(
self,
index: int,
state: CardState,
direction: Optional[ArrowDirection] = None,
) -> None:
self._setter_sequence[index].set(state, direction)
def update_guesser_card(
self,
index: int,
state: CardState,
direction: Optional[ArrowDirection] = None,
) -> None:
self._guesser_sequence[index].set(state, direction)
def update_statusbar(
self,
statusbar_text: Optional[str],
stats: SessionStats,
):
if not self._statusbar:
return
if statusbar_text is not None:
self._statusbar.SetStatusText(statusbar_text, 0)
progress = f" Прогресс {stats.setted}/{stats.total} "
self._statusbar.SetStatusText(progress, 1)
accuracy = (
f" Точность {stats.accuracy}% ({stats.correct}/{stats.wrong}) "
)
self._statusbar.SetStatusText(accuracy, 2)
minutes = int(stats.taken_time) // 60
seconds = int(stats.taken_time) % 60
taken_time = f" Время {minutes:02d}:{seconds:02d} "
self._statusbar.SetStatusText(taken_time, 3)
self._autofit_statusbar([progress, accuracy, taken_time])
class TestFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="TestFrame")
main_panel = SessionPanel(self, "Бывалый", "Балбес")
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(main_panel, 1, wx.EXPAND)
self.SetSizer(main_sizer)
main_panel.update_setter_card(
0, CardState.SETTER_REVEALED, ArrowDirection.LEFT
)
main_panel.update_guesser_card(
0, CardState.GUESSER_WRONG, ArrowDirection.RIGHT
)
s = SessionStats(20, 1, 0, 1, 5.1)
main_panel.update_statusbar(None, s)
if __name__ == "__main__":
enable_high_dpi_support()
app = wx.App()
frame = TestFrame()
frame.Show()
app.MainLoop()