Add a basic help tab

This commit is contained in:
DevilXD committed 2022-07-23 12:32:07 +02:00
1 parent 4437904689
commit b4ae7a1ae9
1 file changed
+106 -21
+106 -21
View File
@@ -2,11 +2,12 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
import webbrowser
import tkinter as tk import tkinter as tk
from math import log10, ceil from math import log10, ceil
from functools import partial from functools import partial
from tkinter.font import Font
from collections import abc, namedtuple from collections import abc, namedtuple
from tkinter.font import Font, nametofont
from tkinter import Tk, ttk, StringVar, DoubleVar, IntVar from tkinter import Tk, ttk, StringVar, DoubleVar, IntVar
from typing import Any, TypedDict, NoReturn, TYPE_CHECKING from typing import Any, TypedDict, NoReturn, TYPE_CHECKING
@@ -28,8 +29,6 @@ if TYPE_CHECKING:
digits = ceil(log10(WS_TOPICS_LIMIT)) digits = ceil(log10(WS_TOPICS_LIMIT))
WS_FONT = ("Courier New", 10)
LARGE_FONT = (..., 12)
class _TKOutputHandler(logging.Handler): class _TKOutputHandler(logging.Handler):
@@ -235,18 +234,29 @@ class PaddedListbox(tk.Listbox):
class MouseOverLabel(ttk.Label): class MouseOverLabel(ttk.Label):
def __init__(self, *args, alt_text: str = '', **kwargs) -> None: def __init__(self, *args, alt_text: str = '', **kwargs) -> None:
options = {} self._org_text: str = kwargs.get("text", '')
if args and args[0] is not None:
options.update(args[0])
if kwargs:
options.update(kwargs)
self._org_text: str = options.get("text", '')
self._alt_text: str = alt_text self._alt_text: str = alt_text
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.bind("<Enter>", lambda e: self.config(text=self._alt_text)) self.bind("<Enter>", lambda e: self.config(text=self._alt_text))
self.bind("<Leave>", lambda e: self.config(text=self._org_text)) self.bind("<Leave>", lambda e: self.config(text=self._org_text))
class LinkLabel(ttk.Label):
def __init__(self, *args, link: str, **kwargs) -> None:
if "foreground" not in kwargs:
kwargs["foreground"] = "blue"
if "cursor" not in kwargs:
kwargs["cursor"] = "hand2"
if "style" not in kwargs:
kwargs["style"] = "Link.TLabel"
self._link: str = link
super().__init__(*args, **kwargs)
self.bind("<ButtonRelease-1>", self.webopen(self._link))
def webopen(self, url: str):
return lambda e: webbrowser.open_new_tab(url)
class _WSEntry(TypedDict): class _WSEntry(TypedDict):
status: str status: str
topics: int topics: int
@@ -258,24 +268,25 @@ class WebsocketStatus:
self._topics_var = StringVar(master) self._topics_var = StringVar(master)
frame = ttk.LabelFrame(master, text="Websocket Status", padding=(4, 0, 4, 4)) frame = ttk.LabelFrame(master, text="Websocket Status", padding=(4, 0, 4, 4))
frame.grid(column=0, row=0, sticky="nsew", padx=2) frame.grid(column=0, row=0, sticky="nsew", padx=2)
monospace_font = Font(frame, family="Courier New", size=10)
ttk.Label( ttk.Label(
frame, frame,
text='\n'.join(f"Websocket #{i}:" for i in range(1, MAX_WEBSOCKETS + 1)), text='\n'.join(f"Websocket #{i}:" for i in range(1, MAX_WEBSOCKETS + 1)),
font=WS_FONT, font=monospace_font,
).grid(column=0, row=0) ).grid(column=0, row=0)
ttk.Label( ttk.Label(
frame, frame,
textvariable=self._status_var, textvariable=self._status_var,
width=16, width=16,
justify="left", justify="left",
font=WS_FONT, font=monospace_font,
).grid(column=1, row=0) ).grid(column=1, row=0)
ttk.Label( ttk.Label(
frame, frame,
textvariable=self._topics_var, textvariable=self._topics_var,
width=(digits * 2 + 1), width=(digits * 2 + 1),
justify="right", justify="right",
font=WS_FONT, font=monospace_font,
).grid(column=2, row=0) ).grid(column=2, row=0)
self._items: dict[int, _WSEntry | None] = {i: None for i in range(MAX_WEBSOCKETS)} self._items: dict[int, _WSEntry | None] = {i: None for i in range(MAX_WEBSOCKETS)}
self._update() self._update()
@@ -1291,6 +1302,65 @@ class SettingsPanel:
self._exclude_list.delete(idx) self._exclude_list.delete(idx)
class HelpTab:
WIDTH = 800
def __init__(self, manager: GUIManager, master: ttk.Widget):
self._twitch = manager._twitch
master.rowconfigure(0, weight=1)
master.columnconfigure(0, weight=1)
# use a frame to center the content within the tab
center_frame = ttk.Frame(master)
center_frame.grid(column=0, row=0)
irow = 0
links = ttk.LabelFrame(center_frame, padding=(4, 0, 4, 4), text="Useful links")
links.grid(column=0, row=(irow := irow + 1), sticky="nsew", padx=2)
LinkLabel(
links, link="https://www.twitch.tv/drops/inventory", text="See Twitch Inventory"
).grid(column=0, row=0, sticky="nsew")
LinkLabel(
links, link="https://www.twitch.tv/drops/campaigns", text="Manage account links"
).grid(column=0, row=1, sticky="nsew")
howitworks = ttk.LabelFrame(center_frame, padding=(4, 0, 4, 4), text="How It Works")
howitworks.grid(column=0, row=(irow := irow + 1), sticky="nsew", padx=2)
ttk.Label(
howitworks,
text=(
"Every ~60 seconds, the application sends a \"minute watched\" event "
"to the channel that's currently being watched - this is enough "
"to advance the drops. Note that this completely bypasses the need to download "
"any actual stream video and sound. "
"To keep the status (ONLINE or OFFLINE) of the channels up-to-date, "
"there's a websocket connection estabilished that receives events about streams "
"going up or down, or updates regarding the current amount of viewers."
),
wraplength=self.WIDTH,
).grid(sticky="nsew")
getstarted = ttk.LabelFrame(center_frame, padding=(4, 0, 4, 4), text="Getting Started")
getstarted.grid(column=0, row=(irow := irow + 1), sticky="nsew", padx=2)
ttk.Label(
getstarted,
text=(
"1. Login into the application.\n"
"2. Ensure your Twitch account is linked to all campaigns "
"you're interested in mining.\n"
"3. If you're interested in just mining everything, "
"uncheck \"Priority only\" and press on \"Reload\".\n"
"4. If you want to mine specific games first, use the \"Priority\" list "
"to setup an ordered list of games of your choice. Games from the top of the list "
"will be attempted to be mined first, before the ones lower down the list.\n"
"5. Keep the \"Priority only\" option checked, to avoid mining games "
"that are not on the priority list.\n"
"6. Use the \"Exclude\" list to tell the application "
"which games should never be mined."
"7. Changing the contents of either of the lists, or changing the state "
"of the \"Priority only\" option, requires you to press on \"Reload\" "
"for the changes to take an effect."
),
wraplength=self.WIDTH,
).grid(sticky="nsew")
class GUIManager: class GUIManager:
def __init__(self, twitch: Twitch): def __init__(self, twitch: Twitch):
self._twitch: Twitch = twitch self._twitch: Twitch = twitch
@@ -1309,6 +1379,7 @@ class GUIManager:
# style adjustements # style adjustements
self._style = style = ttk.Style(root) self._style = style = ttk.Style(root)
default_font = nametofont("TkDefaultFont")
# theme # theme
theme = '' theme = ''
# theme = style.theme_names()[6] # theme = style.theme_names()[6]
@@ -1336,8 +1407,14 @@ class GUIManager:
sublayout[1] = sublayout[1][1]["children"][0] sublayout[1] = sublayout[1][1]["children"][0]
del original[0][1]["children"][1] del original[0][1]["children"][1]
style.layout("TCheckbutton", original) style.layout("TCheckbutton", original)
# adds a style with a larger font for buttons # adds a button style with a larger font
style.configure("Large.TButton", font=LARGE_FONT) large_font = default_font.copy()
large_font.config(size=12)
style.configure("Large.TButton", font=large_font)
# adds a label style that mimics links
link_font = default_font.copy()
link_font.config(underline=True)
style.configure("Link.TLabel", font=link_font)
# end of style changes # end of style changes
root_frame = ttk.Frame(root, padding=8) root_frame = ttk.Frame(root, padding=8)
@@ -1364,6 +1441,10 @@ class GUIManager:
settings_frame = ttk.Frame(root_frame, padding=8) settings_frame = ttk.Frame(root_frame, padding=8)
self.settings = SettingsPanel(self, settings_frame) self.settings = SettingsPanel(self, settings_frame)
self.tabs.add_tab(settings_frame, name="Settings") self.tabs.add_tab(settings_frame, name="Settings")
# Help tab
help_frame = ttk.Frame(root_frame, padding=8)
self.help = HelpTab(self, help_frame)
self.tabs.add_tab(help_frame, name="Help")
# clamp minimum window size (update geometry first) # clamp minimum window size (update geometry first)
root.update_idletasks() root.update_idletasks()
root.minsize(width=root.winfo_reqwidth(), height=root.winfo_reqheight()) root.minsize(width=root.winfo_reqwidth(), height=root.winfo_reqheight())
@@ -1527,7 +1608,7 @@ if __name__ == "__main__":
def create_drop( def create_drop(
campaign_name: str, campaign_name: str,
rewards: str, rewards: list[str],
claimed_drops: int, claimed_drops: int,
total_drops: int, total_drops: int,
current_minutes: int, current_minutes: int,
@@ -1538,6 +1619,11 @@ if __name__ == "__main__":
cm = current_minutes cm = current_minutes
tm = total_minutes tm = total_minutes
ref_stamp = datetime.now(timezone.utc).replace(minute=0, second=0) ref_stamp = datetime.now(timezone.utc).replace(minute=0, second=0)
image_url = (
"https://static-cdn.jtvnw.net/twitch-drops-assets-prod/"
"BENEFIT-81ab5665-b2f4-4179-96e6-74da5a82da28.jpeg"
)
benefits = [SimpleNamespace(name=name, image_url=image_url) for name in rewards]
mock = SimpleNamespace( mock = SimpleNamespace(
id="0", id="0",
campaign=SimpleNamespace( campaign=SimpleNamespace(
@@ -1556,14 +1642,12 @@ if __name__ == "__main__":
progress=(cd * tm + cm) / (td * tm), progress=(cd * tm + cm) / (td * tm),
remaining_minutes=(td - cd) * tm - cm, remaining_minutes=(td - cd) * tm - cm,
), ),
image_url=( image_url=image_url,
"https://static-cdn.jtvnw.net/twitch-drops-assets-prod/"
"BENEFIT-81ab5665-b2f4-4179-96e6-74da5a82da28.jpeg"
),
can_claim=False, can_claim=False,
is_claimed=False, is_claimed=False,
preconditions=True, preconditions=True,
rewards_text=lambda: rewards, benefits=benefits,
rewards_text=lambda: ', '.join(b.name for b in benefits),
progress=cm/tm, progress=cm/tm,
current_minutes=cm, current_minutes=cm,
required_minutes=tm, required_minutes=tm,
@@ -1645,10 +1729,11 @@ if __name__ == "__main__":
await asyncio.sleep(1) await asyncio.sleep(1)
gui.tray.notify("Bounty Coins (3/7)", "Mined Drop") gui.tray.notify("Bounty Coins (3/7)", "Mined Drop")
# Inventory overview # Inventory overview
drop = create_drop("Wardrobe Cleaning", "Fancy Pants", 2, 7, 239, 240) drop = create_drop("Wardrobe Cleaning", ["Fancy Pants"], 2, 7, 239, 240)
await gui.inv.add_campaign(drop.campaign) await gui.inv.add_campaign(drop.campaign)
# Drop progress # Drop progress
gui.display_drop(drop) gui.display_drop(drop)
await asyncio.sleep(63) await asyncio.sleep(63)
drop.current_minutes = 240 drop.current_minutes = 240
drop.remaining_minutes = 0 drop.remaining_minutes = 0