From f1d4fb2d1c50249fdfe561a4b428d79b186272c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B2=E8=8F=AF?= <42814579+yunwah@users.noreply.github.com> Date: Tue, 4 May 2021 14:52:18 -0400 Subject: [PATCH] Move window styling and timers to separate script files and/ or class This commit aims to clean up the significant mess that was the timer code and amassing of functions. In order to better clean up the main GUI code these functions have been moved to their own respective script files. More specifically any future commits that contain window styling aspects will be added to the styles.py script and any timer related code will be moved into timers.py. The timer code has also been abstracted a little more, however, we are still restricted by the API, one solution would be to modify warpy and better standardize the returns from the three open world json objects. Currently there is explicit handler functions within the class that can set this information, however, it may be more reasonable to return smarter json objects that have the current state attached. --- src/overlay.py | 134 +++++++++++++++---------------------------------- src/styles.py | 31 ++++++++++++ src/timers.py | 54 ++++++++++++++++++++ 3 files changed, 126 insertions(+), 93 deletions(-) create mode 100644 src/styles.py create mode 100644 src/timers.py diff --git a/src/overlay.py b/src/overlay.py index 8dd2114..35058af 100644 --- a/src/overlay.py +++ b/src/overlay.py @@ -4,13 +4,8 @@ from warpy import warpy import win32gui import win32api import win32con -import math -from datetime import datetime -from ctypes import windll - -GWL_EXSTYLE = -20 -WS_EX_APPWINDOW = 0x00040000 -WS_EX_TOOLWINDOW = 0x00000080 +from styles import enable_clickthrough +from timers import Timer X_POS_WIN_OFFSET = 8 Y_POS_WIN_OFFSET = 31 @@ -23,32 +18,7 @@ root.wm_attributes("-disabled", True) # Disable interactions with the window root.wm_attributes("-transparent", '#2e3440') # Used to make window transparent root.overrideredirect(True) # Remove the title bar - -def enable_clickthrough(_root): - """ - Sets the app window to be click through. - :param _root: Tkinter parent - :return: - """ - _hwnd = windll.user32.GetParent(_root.winfo_id()) - style = windll.user32.GetWindowLongPtrW(_hwnd, GWL_EXSTYLE) - style |= win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED - win32gui.SetWindowLong(_hwnd, win32con.GWL_EXSTYLE, style) - - -def disable_clickthrough(_root): - """ - TODO: Properly implement this so it can undo click-through transparency - :param _root: Tkinter parent - :return: - """ - _hwnd = windll.user32.GetParent(_root.winfo_id()) - style = windll.user32.GetWindowLongPtrW(_hwnd, GWL_EXSTYLE) - style |= win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED - win32gui.SetWindowLong(_hwnd, win32con.GWL_EXSTYLE, style) - - -root.after(10, lambda: enable_clickthrough(root)) # Make overlay click through. +root.after(1, lambda: enable_clickthrough(root)) # Make overlay click through. # Define the root's geometry based on the geometry of specified application. # List of Warframe class names: # - WarframePublicEvolutionGfxD3D12 @@ -95,76 +65,54 @@ warframe = warpy.Worldstate("pc", loop=loop) timers.grid(row=0, column=0) tk.Label(timers, text="Cetus", fg="#FFFFFF").grid(row=0, column=0) -tk.Label(timers, text="Deimos", fg="#FFFFFF").grid(row=0, column=2) -tk.Label(timers, text="Vallis", fg="#FFFFFF").grid(row=0, column=4) +tk.Label(timers, text="Deimos", fg="#FFFFFF").grid(row=0, column=3) +tk.Label(timers, text="Vallis", fg="#FFFFFF").grid(row=0, column=6) -cetus_timer_svar = tk.StringVar(timers) -vallis_timer_svar = tk.StringVar(timers) -cambion_timer_svar = tk.StringVar(timers) +# String state indicators +cetus_timer = Timer(tk.StringVar(timers), tk.StringVar(timers), root, loop) +vallis_timer = Timer(tk.StringVar(timers), tk.StringVar(timers), root, loop) +cambion_timer = Timer(tk.StringVar(timers), tk.StringVar(timers), root, loop) -cetus_timer_label = tk.Label(timers, textvariable=cetus_timer_svar) -vallis_timer_label = tk.Label(timers, textvariable=vallis_timer_svar) -cambion_timer_label = tk.Label(timers, textvariable=cambion_timer_svar) +cetus_state_label = tk.Label(timers, textvariable=cetus_timer.state) +vallis_state_label = tk.Label(timers, textvariable=vallis_timer.state) +cambion_state_label = tk.Label(timers, textvariable=cambion_timer.state) -cetus_timer_label.grid(row=0, column=1) -cambion_timer_label.grid(row=0, column=3) -vallis_timer_label.grid(row=0, column=5) +cetus_state_label.grid(row=0, column=1) +cambion_state_label.grid(row=0, column=4) +vallis_state_label.grid(row=0, column=7) + +cetus_timer_label = tk.Label(timers, textvariable=cetus_timer.timer) +vallis_timer_label = tk.Label(timers, textvariable=vallis_timer.timer) +cambion_timer_label = tk.Label(timers, textvariable=cambion_timer.timer) + +cetus_timer_label.grid(row=0, column=2) +cambion_timer_label.grid(row=0, column=5) +vallis_timer_label.grid(row=0, column=8) # Asynchronous functions +async def cetus_state_widget(): + json = await warframe.cetus_status() + next_attempt, remaining_time = cetus_timer.set_state(json, cetus_timer.cetus_handler) + cetus_timer.update_timer(remaining_time) + root.after(next_attempt, lambda: loop.run_until_complete(cetus_state_widget())) -# TODO: See if there is a way to decorate these methods and reduce some duplicate code -# Timers -# Following methods are using an additional 120000 ms (2m) to attempt to avoid polling the API before expiration -# update which would result in a negative timedelta -async def get_cetus_cycle(): - cetus_status = await warframe.cetus_status() - expiration = datetime.strptime(cetus_status["expiry"], "%Y-%m-%dT%H:%M:%S.%fZ") - if cetus_status["isDay"]: - cetus_timer_svar.set("Day") - else: - cetus_timer_svar.set("Night") - remaining_time = expiration - datetime.utcnow() - if remaining_time.total_seconds() < 0: - retry = 120000 - else: - retry = remaining_time.total_seconds() * 1000.0 + 120000 - print("Next Cetus cycle retry in: " + str(remaining_time)) - root.after(math.ceil(retry), lambda: loop.run_until_complete(get_cetus_cycle())) +async def vallis_state_widget(): + json = await warframe.vallis_status() + next_attempt, remaining_time = vallis_timer.set_state(json, vallis_timer.vallis_handler) + vallis_timer.update_timer(remaining_time) + root.after(next_attempt, lambda: loop.run_until_complete(vallis_state_widget())) -async def get_vallis_cycle(): - vallis_status = await warframe.vallis_status() - expiration = datetime.strptime(vallis_status["expiry"], "%Y-%m-%dT%H:%M:%S.%fZ") - if vallis_status["isWarm"]: - vallis_timer_svar.set("Warm") - else: - vallis_timer_svar.set("Cold") - remaining_time = expiration - datetime.utcnow() - if remaining_time.total_seconds() < 0: - retry = 120000 - else: - retry = remaining_time.total_seconds() * 1000.0 + 120000 - print("Next Vallis cycle retry in: " + str(remaining_time)) - root.after(math.ceil(retry), lambda: loop.run_until_complete(get_vallis_cycle())) +async def cambion_state_widget(): + json = await warframe.cambion_status() + next_attempt, remaining_time = cambion_timer.set_state(json, cambion_timer.cambion_handler) + cambion_timer.update_timer(remaining_time) + root.after(next_attempt, lambda: loop.run_until_complete(cambion_state_widget())) - -async def get_cambion_cycle(): - cambion_status = await warframe.cambion_status() - expiration = datetime.strptime(cambion_status["expiry"], "%Y-%m-%dT%H:%M:%S.%fZ") - cambion_timer_svar.set(cambion_status["active"].capitalize()) - remaining_time = expiration - datetime.utcnow() - if remaining_time.total_seconds() < 0: - retry = 120000 - else: - retry = remaining_time.total_seconds() * 1000.0 + 120000 - print("Next Cambion cycle retry in: " + str(remaining_time)) - root.after(math.ceil(retry), lambda: loop.run_until_complete(get_cambion_cycle())) - - -loop.run_until_complete(get_cetus_cycle()) -loop.run_until_complete(get_vallis_cycle()) -loop.run_until_complete(get_cambion_cycle()) +loop.run_until_complete(cetus_state_widget()) +loop.run_until_complete(vallis_state_widget()) +loop.run_until_complete(cambion_state_widget()) root.mainloop() diff --git a/src/styles.py b/src/styles.py new file mode 100644 index 0000000..338c33e --- /dev/null +++ b/src/styles.py @@ -0,0 +1,31 @@ +import win32gui +import win32con +from ctypes import windll + +GWL_EXSTYLE = -20 +WS_EX_APPWINDOW = 0x00040000 +WS_EX_TOOLWINDOW = 0x00000080 + + +def enable_clickthrough(_root): + """ + Sets the app window to be click through. + :param _root: Tkinter parent + :return: + """ + _hwnd = windll.user32.GetParent(_root.winfo_id()) + style = windll.user32.GetWindowLongPtrW(_hwnd, GWL_EXSTYLE) + style |= win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED + win32gui.SetWindowLong(_hwnd, win32con.GWL_EXSTYLE, style) + + +def disable_clickthrough(_root): + """ + TODO: Properly implement this so it can undo click-through transparency + :param _root: Tkinter parent + :return: + """ + _hwnd = windll.user32.GetParent(_root.winfo_id()) + style = windll.user32.GetWindowLongPtrW(_hwnd, GWL_EXSTYLE) + style |= win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED + win32gui.SetWindowLong(_hwnd, win32con.GWL_EXSTYLE, style) \ No newline at end of file diff --git a/src/timers.py b/src/timers.py new file mode 100644 index 0000000..4fdf287 --- /dev/null +++ b/src/timers.py @@ -0,0 +1,54 @@ +import math +import styles +from datetime import datetime, timedelta + + +def _create_timer_values(json): + expiration = datetime.strptime(json["expiry"], "%Y-%m-%dT%H:%M:%S.%fZ") + remaining_time = expiration - datetime.utcnow() + remaining_seconds = remaining_time.total_seconds() + return expiration, remaining_time, remaining_seconds + + +class Timer: + def __init__(self, timer, state, root, loop): + self.timer = timer + self.state = state + self._root = root + self._loop = loop + + def set_state(self, json, state_handler): + expiration, remaining_time, remaining_seconds = _create_timer_values(json) + if remaining_seconds < 0: + self.timer.set(str('00:00:00')) + retry = 120000 + else: + self.timer.set(str(remaining_time)) + retry = remaining_seconds * 1000.0 + 120000 + state_handler(json) + return round(retry), remaining_time + + # Do these belong here? + def cetus_handler(self, json): + self.state.set(json['state'].capitalize()) + + def vallis_handler(self, json): + if json["isWarm"]: + self.state.set("Warm") + else: + self.state.set("Cold") + + def cambion_handler(self, json): + self.state.set(json['active'].capitalize()) + # End ? + + def update_timer(self, remaining_time): + remaining_time -= timedelta(seconds=1) + seconds = remaining_time.total_seconds() + if seconds <= 0: + return + else: + self.timer.set(str(remaining_time).split(".")[0]) + self._root.after(1000, lambda: self.update_timer(remaining_time)) + self._root.after(1000, lambda: styles.enable_clickthrough(self._root)) # Required to maintain transparency +