Skip to content

Commit

Permalink
Add scoreboard rendering
Browse files Browse the repository at this point in the history
Signed-off-by: John Strunk <[email protected]>
  • Loading branch information
JohnStrunk committed Nov 26, 2022
1 parent fef1d2e commit 2f3201a
Show file tree
Hide file tree
Showing 10 changed files with 684 additions and 188 deletions.
5 changes: 5 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[MAIN]
ignore-patterns=.*_test.py

[DESIGN]
max-parents=99
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ rstcheck = "*"
sphinx-rtd-theme = "*"
sphinx-tabs = "*"
pytest-cov = "*"
types-pillow = "*"

[requires]
python_version = "3.11"
38 changes: 23 additions & 15 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

157 changes: 67 additions & 90 deletions main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,70 +18,19 @@

import os
import sys
from typing import Callable, Optional
from tkinter import *
import tkinter.ttk as ttk
from tkinter import FALSE, Menu, TclError, Tk, Widget, ttk
import ttkwidgets #type: ignore
import ttkwidgets.font #type: ignore
import PIL.Image as PILImage
from PIL import ImageTk

from model import Model
import widgets
import layouts

callback = Optional[Callable[[], None]]

class ViewModel:
def __init__(self):
########################################
## Dropdown menu items
self.on_menu_exit: callback = None
self.do_menu_exit = lambda: self.on_menu_exit() if self.on_menu_exit is not None else None
self.on_menu_docs: callback = None
self.do_menu_docs = lambda: self.on_menu_docs() if self.on_menu_docs is not None else None
self.on_menu_about: callback = None
self.do_menu_about = lambda: self.on_menu_about() if self.on_menu_about is not None else None
########################################
## Buttons
self.on_bg_import: callback = None
self.do_bg_import = lambda: self.on_bg_import() if self.on_bg_import is not None else None
self.on_bg_clear: callback = None
self.do_bg_clear = lambda: self.on_bg_clear() if self.on_bg_clear is not None else None
self.on_dolphin_export: callback = None
self.do_dolphin_export = lambda: self.on_dolphin_export() if self.on_dolphin_export is not None else None
########################################
## Entry fields
self.main_text = StringVar()
self.time_text = StringVar()
self.text_spacing = DoubleVar()
self.heading1 = StringVar()
self.heading2 = StringVar()
# colors
self.color_heading = StringVar()
self.color_event = StringVar()
self.color_even = StringVar()
self.color_odd = StringVar()
self.color_first = StringVar()
self.color_second = StringVar()
self.color_third = StringVar()
self.color_bg = StringVar()
# features
self.num_lanes = IntVar()
self.inhibit = BooleanVar()
# Preview
self.appearance_preview = widgets.ImageVar(PILImage.Image())
# Directories
self.startlist_dir = StringVar()
self.startlist_contents = widgets.StartListVar([])
self.results_dir = StringVar()
self.results_contents = widgets.RaceResultVar([])
# Run tab
self.cc_status = widgets.ChromecastStatusVar([])
self.scoreboard_preview = widgets.ImageVar(PILImage.Image())


class View(ttk.Frame):
def __init__(self, root: Tk, vm: ViewModel):
'''Main window view definition'''
def __init__(self, root: Tk, vm: Model):
super().__init__(root)
self._root = root
self._vm = vm
Expand All @@ -101,13 +50,13 @@ def __init__(self, root: Tk, vm: ViewModel):
self.pack(side="top", fill="both", expand=True)
self._build_menu()
# App close button is same as Exit menu option
root.protocol("WM_DELETE_WINDOW", vm.do_menu_exit)
root.protocol("WM_DELETE_WINDOW", vm.menu_exit.run)

n = ttk.Notebook(self)
n.pack(side="top", fill="both", expand=True)
n.add(_appearanceTab(n, self._vm), text="Appearance", sticky="news")
n.add(_dirsTab(n, self._vm), text="Directories", sticky="news")
n.add(_runTab(n, self._vm), text="Run", sticky="news")
book = ttk.Notebook(self)
book.pack(side="top", fill="both", expand=True)
book.add(_appearanceTab(book, self._vm), text="Appearance", sticky="news")
book.add(_dirsTab(book, self._vm), text="Directories", sticky="news")
book.add(_runTab(book, self._vm), text="Run", sticky="news")

def _build_menu(self) -> None:
'''Creates the dropdown menus'''
Expand All @@ -117,16 +66,16 @@ def _build_menu(self) -> None:
# File menu
file_menu = Menu(menubar)
menubar.add_cascade(menu=file_menu, label='File', underline=0)
file_menu.add_command(label='Exit', underline=1, command=self._vm.do_menu_exit)
file_menu.add_command(label='Exit', underline=1, command=self._vm.menu_exit.run)
# Help menu
help_menu = Menu(menubar)
menubar.add_cascade(menu=help_menu, label='Help', underline=0)
help_menu.add_command(label='Documentation', underline=0, command=self._vm.do_menu_docs)
help_menu.add_command(label='About', underline=0, command=self._vm.do_menu_about)
help_menu.add_command(label='Documentation', underline=0, command=self._vm.menu_docs.run)
help_menu.add_command(label='About', underline=0, command=self._vm.menu_about.run)


class _appearanceTab(ttk.Frame):
def __init__(self, parent: Widget, vm: ViewModel) -> None:
def __init__(self, parent: Widget, vm: Model) -> None:
super().__init__(parent)
# super().__init__(parent, layouts.Orientation.VERTICAL)
self._vm = vm
Expand All @@ -141,11 +90,24 @@ def _fonts(self, parent: Widget) -> Widget:
frame = ttk.LabelFrame(parent, text="Fonts")
frame.columnconfigure(1, weight=1) # col 1 gets any extra space
ttk.Label(frame, text="Main font:", anchor="e").grid(column=0, row=0, sticky="news")
ttkwidgets.font.FontFamilyDropdown(frame, lambda f: self._vm.main_text.set(f)).grid(column=1, row=0, sticky="news")
main_dd = ttkwidgets.font.FontFamilyDropdown(frame, self._vm.main_text.set)
main_dd.grid(column=1, row=0, sticky="news")
# Update dropdown if textvar is changed
self._vm.main_text.trace_add("write",
lambda _a, _b, _c: main_dd.set(self._vm.main_text.get()))
# Set initial value
main_dd.set(self._vm.main_text.get())
ttk.Label(frame, text="Time font:", anchor="e").grid(column=0, row=1, sticky="news")
ttkwidgets.font.FontFamilyDropdown(frame, lambda f: self._vm.time_text.set(f)).grid(column=1, row=1, sticky="news")
time_dd = ttkwidgets.font.FontFamilyDropdown(frame, self._vm.time_text.set)
time_dd.grid(column=1, row=1, sticky="news")
# Update dropdown if textvar is changed
self._vm.time_text.trace_add("write",
lambda _a, _b, _c: time_dd.set(self._vm.time_text.get()))
# Set initial value
time_dd.set(self._vm.time_text.get())
ttk.Label(frame, text="Text spacing:", anchor="e").grid(column=0, row=2, sticky="news")
ttk.Spinbox(frame, from_=0, to=1, increment=0.01, width=4, textvariable=self._vm.text_spacing).grid(column=1, row=2, sticky="nws")
ttk.Spinbox(frame, from_=0.8, to=2.0, increment=0.05, width=4, format="%0.2f",
textvariable=self._vm.text_spacing).grid(column=1, row=2, sticky="nws")
return frame

def _colors(self, parent: Widget) -> Widget:
Expand All @@ -154,20 +116,27 @@ def _colors(self, parent: Widget) -> Widget:
frame.columnconfigure(3, weight=1)
# 1st col
ttk.Label(frame, text="Heading:", anchor="e").grid(column=0, row=0, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_heading).grid(column=1, row=0, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_heading).grid(column=1,
row=0, sticky="nws")
ttk.Label(frame, text="Event:", anchor="e").grid(column=0, row=1, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_event).grid(column=1, row=1, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_event).grid(column=1,
row=1, sticky="nws")
ttk.Label(frame, text="Odd rows:", anchor="e").grid(column=0, row=2, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_odd).grid(column=1, row=2, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_odd).grid(column=1,
row=2, sticky="nws")
ttk.Label(frame, text="Even rows:", anchor="e").grid(column=0, row=3, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_even).grid(column=1, row=3, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_even).grid(column=1,
row=3, sticky="nws")
# 2nd col
ttk.Label(frame, text="1st place:", anchor="e").grid(column=2, row=0, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_first).grid(column=3, row=0, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_first).grid(column=3,
row=0, sticky="nws")
ttk.Label(frame, text="2nd place:", anchor="e").grid(column=2, row=1, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_second).grid(column=3, row=1, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_second).grid(column=3,
row=1, sticky="nws")
ttk.Label(frame, text="3rd place:", anchor="e").grid(column=2, row=2, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_third).grid(column=3, row=2, sticky="nws")
widgets.ColorButton2(frame, color_var=self._vm.color_third).grid(column=3,
row=2, sticky="nws")
ttk.Label(frame, text="Background:", anchor="e").grid(column=2, row=3, sticky="news")
widgets.ColorButton2(frame, color_var=self._vm.color_bg).grid(column=3, row=3, sticky="nws")
# Bottom
Expand All @@ -176,8 +145,10 @@ def _colors(self, parent: Widget) -> Widget:
bottom.columnconfigure(1, weight=1)
bottom.columnconfigure(2, weight=1)
ttk.Label(bottom, text="Background image:", anchor="e").grid(column=0, row=0, sticky="news")
ttk.Button(bottom, text="Import...", command=self._vm.do_bg_import).grid(column=1, row=0, sticky="news")
ttk.Button(bottom, text="Clear", command=self._vm.do_bg_clear).grid(column=2, row=0, sticky="news")
ttk.Button(bottom, text="Import...", command=self._vm.bg_import.run).grid(column=1,
row=0, sticky="news")
ttk.Button(bottom, text="Clear", command=self._vm.bg_clear.run).grid(column=2,
row=0, sticky="news")
return frame

def _features(self, parent: Widget) -> Widget:
Expand All @@ -186,23 +157,24 @@ def _features(self, parent: Widget) -> Widget:
hc_frame = ttk.Frame(frame)
hc_frame.pack(side="top", fill="both")
ttk.Label(hc_frame, text="Heading color:", anchor="e").grid(column=0, row=0, sticky="news")
widgets.ColorButton2(hc_frame, color_var=self._vm.color_heading).grid(column=1, row=0, sticky="nws")
widgets.ColorButton2(hc_frame, color_var=self._vm.color_heading).grid(column=1,
row=0, sticky="nws")

txt_frame = ttk.Frame(frame)
txt_frame.pack(side="top", fill="both")
txt_frame.columnconfigure(1, weight=1)
ttk.Label(txt_frame, text="Heading 1:", anchor="e").grid(column=0, row=1, sticky="news")
ttk.Entry(txt_frame, textvariable=self._vm.heading1).grid(column=1, row=1, sticky="news")
ttk.Label(txt_frame, text="Heading 2:", anchor="e").grid(column=0, row=2, sticky="news")
ttk.Entry(txt_frame, textvariable=self._vm.heading2).grid(column=1, row=2, sticky="news")
ttk.Entry(txt_frame, textvariable=self._vm.heading).grid(column=1, row=1, sticky="news")

opt_frame = ttk.Frame(frame)
opt_frame.pack(side="top", fill="both")
opt_frame.columnconfigure(1, weight=1)
opt_frame.columnconfigure(3, weight=1)
ttk.Label(opt_frame, text="Lanes:", anchor="e").grid(column=0, row=0, sticky="news")
ttk.Spinbox(opt_frame, from_=6, to=10, increment=1, width=3, textvariable=self._vm.num_lanes).grid(column=1, row=0, sticky="nws")
ttk.Label(opt_frame, text="Suppress >0.3s:", anchor="e").grid(column=2, row=0, sticky="news")
ttk.Spinbox(opt_frame, from_=6, to=10, increment=1, width=3,
textvariable=self._vm.num_lanes).grid(column=1, row=0, sticky="nws")
ttk.Label(opt_frame, text="Suppress >0.3s:", anchor="e").grid(column=2,
row=0, sticky="news")
ttk.Checkbutton(opt_frame, variable=self._vm.inhibit).grid(column=3, row=0, sticky="nws")
return frame

Expand All @@ -213,7 +185,7 @@ def _preview(self, parent: Widget) -> Widget:
return frame

class _dirsTab(ttk.Frame):
def __init__(self, parent: Widget, vm: ViewModel) -> None:
def __init__(self, parent: Widget, vm: Model) -> None:
super().__init__(parent)
self._vm = vm
self.columnconfigure(0, weight=1)
Expand All @@ -224,21 +196,26 @@ def __init__(self, parent: Widget, vm: ViewModel) -> None:

def _start_list(self, parent: Widget) -> Widget:
frame = ttk.LabelFrame(parent, text='Start lists')
widgets.DirSelection(frame, self._vm.startlist_dir).grid(column=0, row=0, sticky="news", padx=1, pady=1)
widgets.StartListTreeView(frame, self._vm.startlist_contents).grid(column=0, row=1, sticky="news", padx=1, pady=1)
ttk.Button(frame, padding=(8, 0), text="Export events to Dolphin...", command=self._vm.do_dolphin_export).grid(column=0, row=2, padx=1, pady=1)
widgets.DirSelection(frame, self._vm.startlist_dir).grid(column=0, row=0,
sticky="news", padx=1, pady=1)
widgets.StartListTreeView(frame, self._vm.startlist_contents).grid(column=0,
row=1, sticky="news", padx=1, pady=1)
ttk.Button(frame, padding=(8, 0), text="Export events to Dolphin...",
command=self._vm.dolphin_export.run).grid(column=0, row=2, padx=1, pady=1)
frame.rowconfigure(1, weight=1)
return frame

def _race_results(self, parent: Widget) -> Widget:
frame = ttk.LabelFrame(parent, text='Race results')
widgets.DirSelection(frame, self._vm.results_dir).grid(column=0, row=0, sticky="news", padx=1, pady=1)
widgets.RaceResultTreeView(frame, self._vm.results_contents).grid(column=0, row=1, sticky="news", padx=1, pady=1)
widgets.DirSelection(frame, self._vm.results_dir).grid(column=0, row=0,
sticky="news", padx=1, pady=1)
widgets.RaceResultTreeView(frame, self._vm.results_contents).grid(column=0,
row=1, sticky="news", padx=1, pady=1)
frame.rowconfigure(1, weight=1)
return frame

class _runTab(ttk.Frame):
def __init__(self, parent: Widget, vm: ViewModel) -> None:
def __init__(self, parent: Widget, vm: Model) -> None:
super().__init__(parent)
self._vm = vm
self.columnconfigure(0, weight=1)
Expand Down
Loading

0 comments on commit 2f3201a

Please sign in to comment.