From 28c918308f1f649edd973185a17a816c76985b9a Mon Sep 17 00:00:00 2001 From: kilhome <66371974+Crykon@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:16:17 +0200 Subject: [PATCH 01/25] Preview implementation Only src/common/structs.py and src/modules/gui.py were changed --- common/__pycache__/meta.cpython-312.pyc | Bin 0 -> 1050 bytes common/__pycache__/meta.cpython-314.pyc | Bin 0 -> 1124 bytes common/__pycache__/parser.cpython-314.pyc | Bin 0 -> 31432 bytes common/__pycache__/structs.cpython-314.pyc | Bin 0 -> 79852 bytes common/structs.py | 55 ++++++++++++++++++ modules/__pycache__/api.cpython-314.pyc | Bin 0 -> 103292 bytes modules/__pycache__/callbacks.cpython-314.pyc | Bin 0 -> 40066 bytes modules/__pycache__/colors.cpython-314.pyc | Bin 0 -> 2645 bytes modules/__pycache__/db.cpython-314.pyc | Bin 0 -> 57442 bytes modules/__pycache__/globals.cpython-314.pyc | Bin 0 -> 12403 bytes modules/__pycache__/gui.cpython-314.pyc | Bin 0 -> 403476 bytes modules/__pycache__/icons.cpython-314.pyc | Bin 0 -> 4373 bytes modules/__pycache__/msgbox.cpython-314.pyc | Bin 0 -> 6122 bytes .../notification_proc.cpython-314.pyc | Bin 0 -> 9046 bytes modules/__pycache__/patches.cpython-314.pyc | Bin 0 -> 4406 bytes .../__pycache__/rpc_thread.cpython-314.pyc | Bin 0 -> 10405 bytes modules/__pycache__/rpdl.cpython-314.pyc | Bin 0 -> 26354 bytes modules/__pycache__/utils.cpython-314.pyc | Bin 0 -> 25006 bytes modules/__pycache__/webview.cpython-314.pyc | Bin 0 -> 48038 bytes modules/gui.py | 50 +++++++++++++++- 20 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 common/__pycache__/meta.cpython-312.pyc create mode 100644 common/__pycache__/meta.cpython-314.pyc create mode 100644 common/__pycache__/parser.cpython-314.pyc create mode 100644 common/__pycache__/structs.cpython-314.pyc create mode 100644 modules/__pycache__/api.cpython-314.pyc create mode 100644 modules/__pycache__/callbacks.cpython-314.pyc create mode 100644 modules/__pycache__/colors.cpython-314.pyc create mode 100644 modules/__pycache__/db.cpython-314.pyc create mode 100644 modules/__pycache__/globals.cpython-314.pyc create mode 100644 modules/__pycache__/gui.cpython-314.pyc create mode 100644 modules/__pycache__/icons.cpython-314.pyc create mode 100644 modules/__pycache__/msgbox.cpython-314.pyc create mode 100644 modules/__pycache__/notification_proc.cpython-314.pyc create mode 100644 modules/__pycache__/patches.cpython-314.pyc create mode 100644 modules/__pycache__/rpc_thread.cpython-314.pyc create mode 100644 modules/__pycache__/rpdl.cpython-314.pyc create mode 100644 modules/__pycache__/utils.cpython-314.pyc create mode 100644 modules/__pycache__/webview.cpython-314.pyc diff --git a/common/__pycache__/meta.cpython-312.pyc b/common/__pycache__/meta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10f7f90a7af8ba0346146fcb96433ebd46b5a7f4 GIT binary patch literal 1050 zcmZ`%O-vI(6rSDfcH5CjCOA#F@gHh19-_gw=E+6yiok$tDP=mnbSCtY}+o=|8? zESAbDC>A5mLL4L2AZ{HgshB+q;UP+xY2wrksF}vmi7+MtL`Bt96ymVEz5|E$=6}QE zqjE&UQ}Sdsf%?ZZm{rsAM0j{CgOXDS_eZiS)TVV=$4NP<<@1^<=jm+0LW#KU_M{{H7g^u1?VE3%&#dr!?w+#qTmR6U zJQsN(KbIG-E(aHbU%h>sfz7s$fvv!HW-t73KRj6tPwso7RZq0+u6bL(c)RAOE3PYL zz9xvztf$sO>vG4djurgITD9IMH*)K_4Q*Z9)_1KPYj<{MwlW;4UXRo~{wL!H9O!+_ I-~iq4A5l99ivR!s literal 0 HcmV?d00001 diff --git a/common/__pycache__/meta.cpython-314.pyc b/common/__pycache__/meta.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..664d97dbdcef2bd37cec9be98ae1ed7d2e78f1ab GIT binary patch literal 1124 zcmaJ6@0_S`0JH~QbmvL|b$s>EN)66JSD{;%|g`Of9<}feu*eiKuFZEdLlLd-qC7;BN(i--{ z4RV81SLvwgFwv{E+{NAIMMD=OHQQ01x+edN7jQ7q&ddCIF&vZ|?lVqTwQ0L9{HH;H zn`k#TE?09n%5-%SHro3i%w!bmuuD)I*N27{= z_M@?%xBiuGzc_|rPJr(O1WthPO=!0GiqJ^6#-Jrun%h>}He1(*-(||7v7On^Pqt7w zo;*-8!I~fDYSt$&9~-<^!EJL{I<5G?dtpHp1Hrgw_LtEd(<~u5u08<{LO*> KXAEkmBmM=;2=pQV literal 0 HcmV?d00001 diff --git a/common/__pycache__/parser.cpython-314.pyc b/common/__pycache__/parser.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57ade37a92f5f2b9fb3c2cbed1dba40a6406c31d GIT binary patch literal 31432 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 literal 0 HcmV?d00001 diff --git a/common/__pycache__/structs.cpython-314.pyc b/common/__pycache__/structs.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7898166c4dc030d3007a25a99701ff5d2006b0b0 GIT binary patch literal 79852 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.*`` and is replaced during refreshes. Keeping the + preview cache under a separate prefix prevents a cover reset from + deleting the gallery. + """ + if self.previews_loading or self.previews_loaded or not self.previews_urls: + return + self.previews_loading = True + try: + from external import imagehelper + from modules import api, globals, utils + self.preview_images.clear() + for index, url in enumerate(self.previews_urls): + if not isinstance(url, str) or not url.startswith(("http://", "https://")): + continue + digest = hashlib.sha1(url.encode("utf-8")).hexdigest()[:16] + glob = f"preview-{self.id}-{digest}.*" + paths = list(globals.images_path.glob(glob)) + if not paths: + try: + data = await api.fetch( + "GET", url, + timeout=globals.settings.request_timeout * 4, + raise_for_status=True, + ) + if data: + path = globals.images_path / f"preview-{self.id}-{digest}.{utils.image_ext(data)}" + path.write_bytes(data) + except Exception: + continue + self.preview_images.append(imagehelper.ImageHelper(globals.images_path, glob=glob)) + self.previews_loaded = True + finally: + self.previews_loading = False + def delete_images(self): from modules import globals + self.unload_previews() for img in globals.images_path.glob(f"{self.id}.*"): try: img.unlink() except Exception: pass + for img in globals.images_path.glob(f"preview-{self.id}-*"): + try: + img.unlink() + except Exception: + pass + + def unload_previews(self): + """Release decoded preview data and GPU textures, keeping disk cache.""" + for image in self.preview_images: + image.unload() + self.preview_images.clear() + self.previews_loaded = False def refresh_image(self): self.image.glob = f"{self.id}.*" diff --git a/modules/__pycache__/api.cpython-314.pyc b/modules/__pycache__/api.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c5566afe0072d82e825149d5b2873d9658df4e4 GIT binary patch literal 103292 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 L8Xm5cr3e2H`qu6_ literal 0 HcmV?d00001 diff --git a/modules/__pycache__/colors.cpython-314.pyc b/modules/__pycache__/colors.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbc8993a176951ff833084f76f1f8b0a1ffbab0a GIT binary patch literal 2645 zcmc&$O-vg{6rR~#8z(kkFogU#B|soJwP^WKn<5kk2x%*|s9C_MlJUwIV_F&8owZd$ zTFF(ALZw#P15{205(g?ZDy>kINE~wLrKk2_2`r&42l}H%s25VI=GOGRu?Z|qfS&rK z{bu&fdoyp|yl-cpFDg7rK>msky?b9I}q78 zg2-Y8iP&56+Jx{7O9?DZ221f7mS(UlFjz`5tsySU2CmT^q=5LKU`!bC)>(iQ@e;pq zVSe%@(K$j}<#8Xw74_ zz%nV~+uj32Efe=iZo|QEm+8y-Z2KR(tve%eYVZ;c=_MGv#6xe`U@^h zL4HqTehsrqhn!Ve;opj1{8rJEcP6H|`>dt^L3l0^-$t#JN!oq_A zFl_ zIc-U^67H&!Hx6g zrP**;oE9R(6zCFWrQ0RwrbIFx39&Lzx+p{6965LndGT{7!GT(U90QzK`p%i!d2zmH zp=Lo{?EQK?bLN%o$yPqFvsv)qf@i}wdy#WOHq?~g$co2qw9dev zUAS^Zr!@?}OF#!FH0&5c1;TNJ69E6B#Wo+18$c0O1xCm=^(H`ytXqnv99nO>YPvtI zP7f@X)MPC+srJ?Xq+SDZwA{F+HBNlEYAc=UNtdMqvlVmAv!|EK&t+}rkREoRM`6+A zdy_$}N^@vewazrv)Qt61Mmn`qK>5!R`Cr*042FR#3_gZLHw{N(fy91XK{K_UC-V`J zXg__(ai)AGIUic+yW6#N`Q96u%1hZ3?YWVi-GUPrEOt(pb?Bs^oS@_lSAvl;UfPoX zKJ2EBY`6~m29iA8!_61Zfh_L+?E4KAIoLZccz3NXOFJH%B(OIi2^6zzhJr2#e|`&v zUJBp;Oe-FL>3dK33wNjU_n&|Bga<~y`}@xIJDzYZtaXn|dT_n$n^%7tpZBmjQ0AZ% zdM=4!vj_0@kH_A3iL3?&byGMnIvUWWNOClwVvga;$XytA_&ErLSu7bHQuyBhI}gLi zD|H9}s9`Hkb*)=VraIF?x^dQ&PA-?#X05efn(sOmej!u_IgGk+pVw>-`~^ zk$W?J-mKi8dE39-tz^5DtW`-}UadR7(6i`R41J}n)U{=-)fvf~>F@&bN^qIP3&zR+og9t(K~nPv3U8w9|Csyn*RXt$->G2 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/db.cpython-314.pyc b/modules/__pycache__/db.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cac6b890cf285179609721af1c12d8b377347a8f GIT binary patch literal 57442 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 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/globals.cpython-314.pyc b/modules/__pycache__/globals.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c89d46782cc2399809f90836f076fe00515e8ba GIT binary patch literal 12403 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 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/gui.cpython-314.pyc b/modules/__pycache__/gui.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca3a7d1c6e72c99093d427a742de5a7a9c086817 GIT binary patch literal 403476 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�&luNtJI4U2`I<=TgKF{KP5ggUfwNVI+ID5yz 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 e?HI`AO)3kQ0mkz|kA4ss9D@;TQJ+ literal 0 HcmV?d00001 diff --git a/modules/__pycache__/icons.cpython-314.pyc b/modules/__pycache__/icons.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1acc8b6ab8927f5c5ea23db250044ec6bc9e95c GIT binary patch literal 4373 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 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/notification_proc.cpython-314.pyc b/modules/__pycache__/notification_proc.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dd8ab0f7744ebb399449b55d2ec6356acf70e28 GIT binary patch literal 9046 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 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/patches.cpython-314.pyc b/modules/__pycache__/patches.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac8194f0d1c1d2e9eaf5d7bc61ed2b17b6c5083e GIT binary patch literal 4406 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

c_!dQzUbs4k#K!iCwz73g`yxA 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 Date: Mon, 24 Aug 2026 22:34:34 +0200 Subject: [PATCH 03/25] Fix crash when closing popup --- common/structs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/structs.py b/common/structs.py index f53fcfcb..2f8e0ec0 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1085,8 +1085,9 @@ def delete_images(self): def unload_previews(self): """Release decoded preview data and GPU textures, keeping disk cache.""" + from external import imagehelper for image in self.preview_images: - image.unload() + imagehelper.unload_queue.append(image) self.preview_images.clear() self.previews_loaded = False From 76d9ada50e6be33beec8e231d580220dc466aeff Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:04:54 +0200 Subject: [PATCH 04/25] Add files via upload --- modules/db.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/db.py b/modules/db.py index 442a92ab..5ae56099 100644 --- a/modules/db.py +++ b/modules/db.py @@ -556,6 +556,7 @@ def py_to_sql(value: enum.Enum | Timestamp | bool | list | tuple | typing.Any): async def update_game_id(game: Game, new_id): + old_id = game.id await connection.execute(f""" UPDATE games SET @@ -575,11 +576,24 @@ async def update_game_id(game: Game, new_id): for event in game.timeline_events: event.game_id = new_id - for img in globals.images_path.glob(f"{game.id}.*"): + for img in globals.images_path.glob(f"{old_id}.*"): try: shutil.move(img, img.with_name(f"{new_id}{''.join(img.suffixes)}")) except Exception: pass + old_preview_dir = globals.images_path / "previews" / str(old_id) + new_preview_dir = globals.images_path / "previews" / str(new_id) + if old_preview_dir.is_dir(): + new_preview_dir.mkdir(parents=True, exist_ok=True) + for img in old_preview_dir.iterdir(): + try: + shutil.move(img, new_preview_dir / img.name) + except Exception: + pass + try: + old_preview_dir.rmdir() + except OSError: + pass game.id = new_id game.refresh_image() From 8810b7e3f698cb35cf8b10a73096de4c62cb8074 Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:05:28 +0200 Subject: [PATCH 05/25] Add files via upload --- gui.py | 5750 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5750 insertions(+) create mode 100644 gui.py diff --git a/gui.py b/gui.py new file mode 100644 index 00000000..4fb1422f --- /dev/null +++ b/gui.py @@ -0,0 +1,5750 @@ +import asyncio +import builtins +import concurrent.futures +import configparser +import dataclasses +import datetime as dt +import functools +import itertools +import pathlib +import pickle +import platform +import sys +import threading +import time +import tomllib + +from PIL import Image +from PyQt6 import ( + QtCore, + QtGui, + QtWidgets, +) +import aiohttp +import glfw +import imgui +import OpenGL +import OpenGL.GL as gl + +from common.structs import ( + Browser, + Category, + Datestamp, + DefaultStyle, + DisplayMode, + ExeState, + Filter, + FilterMode, + Game, + Label, + LaunchState, + MsgBox, + Os, + ProxyType, + SortSpec, + Status, + Tab, + Tag, + TagHighlight, + TexCompress, + TimelineEventType, + Timestamp, + Type, +) +from common import parser +from external import ( + async_thread, + error, + filepicker, + imagehelper, + imgui_glfw, + ratingwidget, +) +from modules import ( + api, + callbacks, + colors, + db, + globals, + icons, + msgbox, + rpc_thread, + rpdl, + utils, + wine, +) + +tool_page = api.f95_threads_page + "44173/" +github_page = "https://github.com/WillyJL/F95Checker" +developer_page = "https://linktr.ee/WillyJL" + +imgui.io = None +imgui.style = None + + +class Columns: + + @dataclasses.dataclass(slots=True) + class Column: + cols: object + name: str + flags: int = 0 + ghost: bool = False + default: bool = False + hideable: bool = True + sortable: bool = False + resizable: bool = True + enabled: bool = None + no_header: str = False + short_header: str = False + header: str = None + index: int = None + + def __post_init__(self): + # Header + if self.ghost or self.no_header: + self.header = "##" + self.name[2:] + elif self.short_header: + self.header = self.name[:1] + else: + self.header = self.name[2:] + # Flags + if self.ghost: + self.flags |= ( + imgui.TABLE_COLUMN_NO_SORT | + imgui.TABLE_COLUMN_NO_RESIZE | + imgui.TABLE_COLUMN_NO_REORDER | + imgui.TABLE_COLUMN_NO_HEADER_WIDTH + ) + if not self.default: + self.flags |= imgui.TABLE_COLUMN_DEFAULT_HIDE + if not self.hideable: + self.flags |= imgui.TABLE_COLUMN_NO_HIDE + if not self.sortable: + self.flags |= imgui.TABLE_COLUMN_NO_SORT + if not self.resizable: + self.flags |= imgui.TABLE_COLUMN_NO_RESIZE + # Add to outer class + self.cols.items.append(self) + self.cols.count = len(self.cols.items) + self.index = self.cols.count - 1 + + def __init__(self): + self.items = [] + self.count = 0 + # Ghosts (more info in MainGUI.draw_games_list()) + self.manual_sort = self.Column( + self, f"{icons.cursor_move} Manual Sort", + ghost=True, + ) + self.version = self.Column( + self, f"{icons.star_shooting} Version", + ghost=True, + default=True, + ) + self.finished_version = self.Column( + self, f"{icons.flag_checkered} Finished Version", + ghost=True, + ) + self.installed_version = self.Column( + self, f"{icons.download} Installed Version", + ghost=True, + ) + self.status = self.Column( + self, f"{icons.checkbox_marked_circle} Status (after name)", + ghost=True, + default=True, + ) + self.separator = self.Column( + self, "-----------------------------------", + ghost=True, + default=True, + hideable=False, + ) + # Regulars + self.play_button = self.Column( + self, f"{icons.play} Play Button", + default=True, + resizable=False, + no_header=True, + ) + self.type = self.Column( + self, f"{icons.book_information_variant} Type", + default=True, + sortable=True, + resizable=False, + ) + self.name = self.Column( + self, f"{icons.gamepad_variant} Name", + imgui.TABLE_COLUMN_WIDTH_STRETCH | imgui.TABLE_COLUMN_DEFAULT_SORT, + default=True, + sortable=True, + hideable=False, + ) + self.developer = self.Column( + self, f"{icons.account} Developer", + sortable=True, + ) + self.last_updated = self.Column( + self, f"{icons.update} Last Updated", + default=True, + sortable=True, + resizable=False, + ) + self.last_launched = self.Column( + self, f"{icons.play} Last Launched", + sortable=True, + resizable=False, + ) + self.added_on = self.Column( + self, f"{icons.plus} Added On", + sortable=True, + resizable=False, + ) + self.finished = self.Column( + self, f"{icons.flag_checkered} Finished", + default=True, + sortable=True, + resizable=False, + short_header=True, + ) + self.installed = self.Column( + self, f"{icons.download} Installed", + default=True, + sortable=True, + resizable=False, + short_header=True, + ) + self.rating = self.Column( + self, f"{icons.star} Rating", + sortable=True, + resizable=False, + ) + self.notes = self.Column( + self, f"{icons.draw_pen} Notes", + sortable=True, + ) + self.open_thread = self.Column( + self, f"{icons.open_in_new} Open Thread", + default=True, + resizable=False, + no_header=True, + ) + self.copy_link = self.Column( + self, f"{icons.content_copy} Copy Link", + resizable=False, + no_header=True, + ) + self.open_folder = self.Column( + self, f"{icons.folder_open_outline} Open Folder", + resizable=False, + no_header=True, + ) + self.status_standalone = self.Column( + self, f"{icons.checkbox_marked_circle} Status (own column)", + sortable=True, + resizable=False, + no_header=True, + ) + self.score = self.Column( + self, f"{icons.message_star} Forum Score", + sortable=True, + resizable=False, + short_header=True, + ) + self.playtime = self.Column( + self, f"{icons.timer_play_outline} Playtime", + sortable=True, + resizable=False, + short_header=True, + ) + +cols = Columns() + + +@functools.cache +def _scaled(mult: float, size: int | float): + return size * mult + + +class MainGUI(): + def __init__(self): + # Constants + self.sidebar_size = 234 + self.window_flags: int = ( + imgui.WINDOW_NO_MOVE | + imgui.WINDOW_NO_RESIZE | + imgui.WINDOW_NO_COLLAPSE | + imgui.WINDOW_NO_TITLE_BAR | + imgui.WINDOW_NO_SCROLLBAR | + imgui.WINDOW_NO_SCROLL_WITH_MOUSE + ) + self.tabbar_flags: int = ( + imgui.TAB_BAR_FITTING_POLICY_SCROLL + ) + self.game_list_table_flags: int = ( + imgui.TABLE_SCROLL_Y | + imgui.TABLE_HIDEABLE | + imgui.TABLE_SORTABLE | + imgui.TABLE_RESIZABLE | + imgui.TABLE_SORT_MULTI | + imgui.TABLE_REORDERABLE | + imgui.TABLE_ROW_BACKGROUND | + imgui.TABLE_SIZING_FIXED_FIT | + imgui.TABLE_NO_HOST_EXTEND_Y | + imgui.TABLE_NO_BORDERS_IN_BODY_UTIL_RESIZE + ) + self.game_grid_table_flags: int = ( + imgui.TABLE_SCROLL_Y | + imgui.TABLE_PAD_OUTER_X | + imgui.TABLE_NO_HOST_EXTEND_Y | + imgui.TABLE_SIZING_FIXED_SAME | + imgui.TABLE_NO_SAVED_SETTINGS + ) + self.game_kanban_table_flags: int = ( + imgui.TABLE_SCROLL_X | + imgui.TABLE_SCROLL_Y | + imgui.TABLE_PAD_OUTER_X | + imgui.TABLE_REORDERABLE | + imgui.TABLE_NO_HOST_EXTEND_Y | + imgui.TABLE_SIZING_FIXED_SAME | + imgui.TABLE_BORDERS_INNER_VERTICAL + ) + self.game_hitbox_drag_drop_flags: int = ( + imgui.DRAG_DROP_ACCEPT_PEEK_ONLY | + imgui.DRAG_DROP_SOURCE_ALLOW_NULL_ID | + imgui.DRAG_DROP_SOURCE_NO_PREVIEW_TOOLTIP + ) + self.watermark_text = f"F95Checker {globals.version_name}{'' if not globals.release else ' by WillyJL'}" + + # Variables + self.focused = True + self.minimized = False + self.filtering = False + self.add_box_text = "" + self.new_styles = False + self.prev_size = (0, 0) + self.screen_pos = (0, 0) + self.repeat_chars = False + self.scroll_percent = 0.0 + self.prev_manual_sort = 0 + self.add_box_valid = False + self.bg_mode_paused = False + self.recalculate_ids = True + self.current_tab: Tab = None + self.selected_games_count = 0 + self.dragging_tab: Tab = None + self.game_hitbox_click = False + self.hovered_game: Game = None + self.filters: list[Filter] = [] + self.poll_chars: list[int] = [] + self.refresh_ratio_smooth = 0.0 + self.bg_mode_timer: float = None + self.input_chars: list[int] = [] + self.switched_display_mode = False + self.type_label_width: float = None + self.call_soon: list[callable] = [] + self.last_selected_game: Game = None + self.prev_filters: list[Filter] = [] + self.ghost_columns_enabled_count = 0 + self.bg_mode_notifs_timer: float = None + self.sorts: dict[str, list[SortSpec]] = {} + self.show_games_ids: dict[Tab, list[int]] = {} + + # Setup Qt objects + self.qt_app = QtWidgets.QApplication(sys.argv) + self.tray = TrayIcon(self) + + # Setup ImGui + imgui.create_context() + imgui.io = imgui.get_io() + imgui.io.ini_file_name = str(globals.data_path / "imgui.ini") + imgui.io.config_drag_click_to_input_text = True + imgui.io.config_cursor_blink = False + size = tuple() + pos = tuple() + try: + # Get window size + with open(imgui.io.ini_file_name, "r") as f: + ini = f.read() + imgui.load_ini_settings_from_memory(ini) + start = ini.find("[Window][F95Checker]") + assert start != -1 + end = ini.find("\n\n", start) + assert end != -1 + config = configparser.RawConfigParser() + config.read_string(ini[start:end]) + try: + size = tuple(int(x) for x in config.get("Window][F95Checker", "Size").split(",")) + except Exception: + pass + try: + pos = tuple(int(x) for x in config.get("Window][F95Checker", "ScreenPos").split(",")) + except Exception: + pass + except Exception: + pass + if not all(type(x) is int for x in size) or not len(size) == 2: + size = (1280, 720) + + # Setup GLFW + if not glfw.init(): + print("Could not initialize OpenGL context") + sys.exit(1) + glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) + glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) + glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) + glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, gl.GL_TRUE) # OS X supports only forward-compatible core profiles from 3.2 + glfw.window_hint(glfw.VISIBLE, False) + + # Create a windowed mode window and its OpenGL context + self.window: glfw._GLFWwindow = glfw.create_window(*size, "F95Checker", None, None) + if not self.window: + print("Could not initialize Window") + glfw.terminate() + sys.exit(1) + glfw.make_context_current(self.window) + self.impl = imgui_glfw.GlfwRenderer(self.window) + + # Window position and icon + if all(type(x) is int for x in pos) and len(pos) == 2 and utils.validate_geometry(*pos, *size): + glfw.set_window_pos(self.window, *pos) + self.screen_pos = glfw.get_window_pos(self.window) + if globals.settings.start_in_background: + self.hide() + glfw.set_window_icon(self.window, 1, Image.open(globals.self_path / "resources/icons/icon.png")) + + # Window callbacks + glfw.set_char_callback(self.window, self.char_callback) + glfw.set_window_close_callback(self.window, self.close_callback) + glfw.set_window_iconify_callback(self.window, self.minimize_callback) + glfw.set_window_focus_callback(self.window, self.focus_callback) + glfw.set_window_pos_callback(self.window, self.pos_callback) + glfw.set_drop_callback(self.window, self.drop_callback) + glfw.swap_interval(globals.settings.vsync_ratio) + + self.logo_texture = imagehelper.ImageHelper(globals.self_path / "resources/icons/logo.png") + self.refresh_fonts() + self.load_filters() + self.load_styles_from_toml() + + # Show errors in threads + def syncexcepthook(args: threading.ExceptHookArgs): + if args.exc_type is not msgbox.Exc: + err = error.text(args.exc_value) + tb = error.traceback(args.exc_value) + utils.push_popup( + msgbox.msgbox, "Oops!", + "Something went wrong in a parallel task:\n" + f"{err}", + MsgBox.error, + more=tb + ) + threading.excepthook = syncexcepthook + def asyncexcepthook(future: asyncio.Future): + try: + exc = future.exception() + except concurrent.futures.CancelledError: + return + if not exc or type(exc) is msgbox.Exc: + return + err = error.text(exc) + tb = error.traceback(exc) + if isinstance(exc, (aiohttp.ClientError, asyncio.TimeoutError)): + utils.push_popup( + msgbox.msgbox, "Connection error", + "A connection request has failed:\n" + f"{err}\n" + "\n" + "Possible causes include:\n" + " - You're not connected to the internet, or your connection is unstable\n" + " - Your timeout value is too low, try increasing it in settings\n" + " - The server is experiencing difficulties, try waiting a bit and retrying\n" + " - The web address is blocked in your country, network, antivirus, DNS or firewall, try a VPN\n" + " - You are refreshing with too many connections, try lowering them in settings\n" + " - Your retries value is too low, try increasing it in settings (last resort!)", + MsgBox.warn, + more=tb + ) + return + utils.push_popup( + msgbox.msgbox, "Oops!", + "Something went wrong in an asynchronous task:\n" + f"{err}", + MsgBox.error, + more=tb + ) + async_thread.done_callback = asyncexcepthook + + # Load style configuration + imgui.style = imgui.get_style() + imgui.style.item_spacing = (imgui.style.item_spacing.y, imgui.style.item_spacing.y) + imgui.style.colors[imgui.COLOR_MODAL_WINDOW_DIM_BACKGROUND] = (0, 0, 0, 0.5) + imgui.style.scrollbar_size = 10 + imgui.style.frame_border_size = 1.6 + imgui.style.colors[imgui.COLOR_TABLE_BORDER_STRONG] = (0, 0, 0, 0) + self.refresh_styles() + # No redundant vprintf + imgui.text = imgui.text_unformatted + # Custom checkbox style + imgui._checkbox = imgui.checkbox + def checkbox(label: str, state: bool): + if state: + imgui.push_style_color(imgui.COLOR_FRAME_BACKGROUND_HOVERED, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) + imgui.push_style_color(imgui.COLOR_FRAME_BACKGROUND, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) + imgui.push_style_color(imgui.COLOR_CHECK_MARK, *imgui.style.colors[imgui.COLOR_WINDOW_BACKGROUND]) + ret = imgui._checkbox(label, state) + if state: + imgui.pop_style_color(3) + return ret + imgui.checkbox = checkbox + # Custom combo style + imgui._combo = imgui.combo + def combo(*args, **kwargs): + imgui.push_style_color(imgui.COLOR_BUTTON, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) + imgui.push_style_color(imgui.COLOR_HEADER, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED][:3], 0.5) + ret = imgui._combo(*args, **kwargs) + imgui.pop_style_color(2) + return ret + imgui.combo = combo + imgui._begin_combo = imgui.begin_combo + def begin_combo(*args, **kwargs): + imgui.push_style_color(imgui.COLOR_BUTTON, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) + ret = imgui._begin_combo(*args, **kwargs) + imgui.pop_style_color() + if ret: + imgui.push_style_color(imgui.COLOR_HEADER, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED][:3], 0.5) + return ret + imgui.begin_combo = begin_combo + imgui._end_combo = imgui.end_combo + def end_combo(*args, **kwargs): + imgui.pop_style_color() + return imgui._end_combo(*args, **kwargs) + imgui.end_combo = end_combo + # Fix clicking into multiline textboxes + imgui._input_text_multiline = imgui.input_text_multiline + def input_text_multiline(*args, **kwargs): + pos = imgui.io.mouse_pos + imgui.io.mouse_pos = (pos.x - 8, pos.y) + ret = imgui._input_text_multiline(*args, **kwargs) + imgui.io.mouse_pos = (pos.x, pos.y) + return ret + imgui.input_text_multiline = input_text_multiline + # Fix some ID hell + imgui._button = imgui.button + def button(*args, **kwargs): + imgui._button(*args, **kwargs) + return imgui.is_item_clicked() + imgui.button = button + imgui._small_button = imgui.small_button + def small_button(*args, **kwargs): + imgui._small_button(*args, **kwargs) + return imgui.is_item_clicked() + imgui.small_button = small_button + imgui._invisible_button = imgui.invisible_button + def invisible_button(*args, **kwargs): + imgui._invisible_button(*args, **kwargs) + return imgui.is_item_clicked() + imgui.invisible_button = invisible_button + imgui._selectable = imgui.selectable + def selectable(*args, **kwargs): + _, selected = imgui._selectable(*args, **kwargs) + return imgui.is_item_clicked(), selected + imgui.selectable = selectable + # Utils + def push_y(offset: float): + imgui.begin_group() + imgui.set_cursor_pos_y(imgui.get_cursor_pos_y() + offset) + imgui.push_y = push_y + def pop_y(): + imgui.end_group() + imgui.pop_y = pop_y + def push_no_interaction(): + imgui.internal.push_item_flag(imgui.internal.ITEM_DISABLED, True) + imgui.push_no_interaction = push_no_interaction + def pop_no_interaction(): + imgui.internal.pop_item_flag() + imgui.pop_no_interaction = pop_no_interaction + def push_alpha(amount: float): + imgui.push_style_var(imgui.STYLE_ALPHA, imgui.style.alpha * amount) + imgui.push_alpha = push_alpha + def pop_alpha(): + imgui.pop_style_var() + imgui.pop_alpha = pop_alpha + def push_disabled(): + imgui.push_no_interaction() + imgui.push_alpha(0.5) + imgui.push_disabled = push_disabled + def pop_disabled(): + imgui.pop_alpha() + imgui.pop_no_interaction() + imgui.pop_disabled = pop_disabled + def is_topmost(): + return not imgui.is_popup_open("", imgui.POPUP_ANY_POPUP_ID) + imgui.is_topmost = is_topmost + + def refresh_styles(self): + _ = \ + imgui.style.colors[imgui.COLOR_CHECK_MARK] = \ + imgui.style.colors[imgui.COLOR_TAB_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_SLIDER_GRAB] = \ + imgui.style.colors[imgui.COLOR_TAB_HOVERED] = \ + imgui.style.colors[imgui.COLOR_BUTTON_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_HEADER_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_NAV_HIGHLIGHT] = \ + imgui.style.colors[imgui.COLOR_PLOT_HISTOGRAM] = \ + imgui.style.colors[imgui.COLOR_HEADER_HOVERED] = \ + imgui.style.colors[imgui.COLOR_BUTTON_HOVERED] = \ + imgui.style.colors[imgui.COLOR_SEPARATOR_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_SEPARATOR_HOVERED] = \ + imgui.style.colors[imgui.COLOR_RESIZE_GRIP_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_RESIZE_GRIP_HOVERED] = \ + imgui.style.colors[imgui.COLOR_TAB_UNFOCUSED_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_SCROLLBAR_GRAB_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_FRAME_BACKGROUND_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_TITLE_BACKGROUND_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_TEXT_SELECTED_BACKGROUND] = \ + globals.settings.style_accent + _ = \ + imgui.style.colors[imgui.COLOR_TAB] = \ + imgui.style.colors[imgui.COLOR_RESIZE_GRIP] = \ + imgui.style.colors[imgui.COLOR_TAB_UNFOCUSED] = \ + imgui.style.colors[imgui.COLOR_FRAME_BACKGROUND_HOVERED] = \ + (*globals.settings.style_accent[:3], 0.25) + _ = \ + imgui.style.colors[imgui.COLOR_TABLE_HEADER_BACKGROUND] = \ + imgui.style.colors[imgui.COLOR_TABLE_ROW_BACKGROUND_ALT] = \ + globals.settings.style_alt_bg + _ = \ + imgui.style.colors[imgui.COLOR_BUTTON] = \ + imgui.style.colors[imgui.COLOR_HEADER] = \ + imgui.style.colors[imgui.COLOR_FRAME_BACKGROUND] = \ + imgui.style.colors[imgui.COLOR_CHILD_BACKGROUND] = \ + imgui.style.colors[imgui.COLOR_POPUP_BACKGROUND] = \ + imgui.style.colors[imgui.COLOR_TITLE_BACKGROUND] = \ + imgui.style.colors[imgui.COLOR_WINDOW_BACKGROUND] = \ + imgui.style.colors[imgui.COLOR_SLIDER_GRAB_ACTIVE] = \ + imgui.style.colors[imgui.COLOR_SCROLLBAR_BACKGROUND] = \ + globals.settings.style_bg + _ = \ + imgui.style.colors[imgui.COLOR_BORDER] = \ + imgui.style.colors[imgui.COLOR_SEPARATOR] = \ + globals.settings.style_border + _ = \ + imgui.style.tab_rounding = \ + imgui.style.grab_rounding = \ + imgui.style.frame_rounding = \ + imgui.style.child_rounding = \ + imgui.style.popup_rounding = \ + imgui.style.window_rounding = \ + imgui.style.scrollbar_rounding = \ + self.scaled(globals.settings.style_corner_radius) + _ = \ + imgui.style.colors[imgui.COLOR_TEXT] = \ + globals.settings.style_text + _ = \ + imgui.style.colors[imgui.COLOR_TEXT_DISABLED] = \ + globals.settings.style_text_dim + if globals.os is not Os.MacOS: + self.qt_app.setStyleSheet(f""" + QMenu {{ + padding: 5px; + background-color: {colors.rgba_0_1_to_hex(globals.settings.style_bg)[:-2]}; + }} + QMenu::item {{ + margin: 1px; + padding: 2px 7px 2px 7px; + border-radius: {globals.settings.style_corner_radius}; + color: {colors.rgba_0_1_to_hex(globals.settings.style_text)[:-2]}; + }} + QMenu::item:disabled {{ + color: {colors.rgba_0_1_to_hex(globals.settings.style_text_dim)[:-2]}; + }} + QMenu::item:selected:enabled {{ + background-color: {colors.rgba_0_1_to_hex(globals.settings.style_accent)[:-2]}; + }} + QMenu::icon {{ + padding-left: 7px; + }} + """) + + def refresh_fonts(self): + imgui.io.fonts.clear() + max_tex_size = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) + imgui.io.fonts.texture_desired_width = max_tex_size + win_w, win_h = glfw.get_window_size(self.window) + fb_w, fb_h = glfw.get_framebuffer_size(self.window) + font_scaling_factor = max(fb_w / win_w, fb_h / win_h) + imgui.io.font_global_scale = 1 / font_scaling_factor + karlar_path = str(next(globals.self_path.glob("resources/fonts/Karla-Regular.*.ttf"))) + karlab_path = str(next(globals.self_path.glob("resources/fonts/Karla-Bold.*.ttf"))) + meslo_path = str(next(globals.self_path.glob("resources/fonts/MesloLGS-Regular.*.ttf"))) + noto_path = str(next(globals.self_path.glob("resources/fonts/NotoSans-Regular.*.ttf"))) + mdi_path = str(icons.font_path) + mixin_path = str(next(globals.self_path.glob("resources/fonts/custom-mixin.ttf"))) + merge = dict(merge_mode=True) + oversample = dict(oversample_h=2, oversample_v=2) + karla_config = imgui.core.FontConfig( glyph_offset_y=-0.5, **oversample) + meslo_config = imgui.core.FontConfig( **oversample) + noto_config = imgui.core.FontConfig(**merge, glyph_offset_y=-0.5, **oversample) + mdi_config = imgui.core.FontConfig(**merge, glyph_offset_y=+1.0) + mixin_config = imgui.core.FontConfig(**merge) + karla_range = imgui.core.GlyphRanges([0x1, 0x25ca, 0]) + meslo_range = imgui.core.GlyphRanges([0x1, 0x2e2e, 0]) + noto_range = imgui.core.GlyphRanges([0x1, 0xfffd, 0]) + mdi_range = imgui.core.GlyphRanges([icons.min_char, icons.max_char, 0]) + mixin_range = imgui.core.GlyphRanges([0x3000, 0x3000, 0]) + msgbox_range_values = [] + for icon in [icons.information, icons.alert_rhombus, icons.alert_octagon]: + msgbox_range_values += [ord(icon), ord(icon)] + msgbox_range_values.append(0) + msgbox_range = imgui.core.GlyphRanges(msgbox_range_values) + size_14 = round(self.scaled(14 * font_scaling_factor)) + size_15 = round(self.scaled(15 * font_scaling_factor)) + size_17 = round(self.scaled(17 * font_scaling_factor)) + size_18 = round(self.scaled(18 * font_scaling_factor)) + size_22 = round(self.scaled(22 * font_scaling_factor)) + size_28 = round(self.scaled(28 * font_scaling_factor)) + size_32 = round(self.scaled(32 * font_scaling_factor)) + size_69 = round(self.scaled(69 * font_scaling_factor)) + fonts = type("FontStore", (), {})() + imgui.fonts = fonts + add_font = imgui.io.fonts.add_font_from_file_ttf + # Default font + more glyphs + icons + fonts.default = add_font(karlar_path, size_18, font_config=karla_config, glyph_ranges=karla_range) + add_font( noto_path, size_18, font_config=noto_config, glyph_ranges=noto_range) + add_font( mdi_path, size_18, font_config=mdi_config, glyph_ranges=mdi_range) + add_font( mixin_path, size_18, font_config=mixin_config, glyph_ranges=mixin_range) + # Bold font + more glyphs + icons + fonts.bold = add_font(karlab_path, size_22, font_config=karla_config, glyph_ranges=karla_range) + add_font( noto_path, size_22, font_config=noto_config, glyph_ranges=noto_range) + add_font( mdi_path, size_18, font_config=mdi_config, glyph_ranges=mdi_range) + # Big font + more glyphs + icons + fonts.big = add_font(karlab_path, size_32, font_config=karla_config, glyph_ranges=karla_range) + add_font( noto_path, size_32, font_config=noto_config, glyph_ranges=noto_range) + add_font( mdi_path, size_28, font_config=mdi_config, glyph_ranges=mdi_range) + # Small font + more glyphs + icons + fonts.small = add_font(karlar_path, size_14, font_config=karla_config, glyph_ranges=karla_range) + add_font( noto_path, size_14, font_config=noto_config, glyph_ranges=noto_range) + add_font( mdi_path, size_14, font_config=mdi_config, glyph_ranges=mdi_range) + # Monospace font for some dates + fonts.mono = add_font(meslo_path, size_17, font_config=meslo_config, glyph_ranges=meslo_range) + # Small monospace font for more info dropdowns + fonts.mono_sm = add_font(meslo_path, size_15, font_config=meslo_config, glyph_ranges=meslo_range) + # MsgBox type icons/thumbnails + fonts.msgbox = add_font(mdi_path, size_69, glyph_ranges=msgbox_range) + try: + self.impl.refresh_font_texture() + except Exception: + if globals.settings.interface_scaling == 1.0: + raise + globals.settings.interface_scaling = 1.0 + async_thread.run(db.update_settings("interface_scaling")) + return self.refresh_fonts() + self.type_label_width = None + + def save_filters(self): + with open(globals.data_path / "filters.pkl", "wb") as file: + pickle.dump(self.filters, file) + + def load_filters(self): + try: + with open(globals.data_path / "filters.pkl", "rb") as file: + self.filters = pickle.load(file) + for flt in self.filters: + match flt.mode: + case FilterMode.Label: + # Field added later, replace broken object with real one + if not hasattr(flt.match, "position"): + try: + flt.match = Label.get(flt.match.id) + except Exception: + self.filters.remove(flt) + except Exception: + self.filters = [] + + def load_styles_from_toml(self): + if not (path := pathlib.Path(globals.data_path / 'styles.toml')).is_file(): + return + try: + with open(path, 'rb') as file: + styles = tomllib.load(file) + globals.settings.style_corner_radius = styles.get("corner_radius", DefaultStyle.corner_radius) + globals.settings.style_accent = colors.hex_to_rgba_0_1(styles.get("accent", DefaultStyle.accent)) + globals.settings.style_bg = colors.hex_to_rgba_0_1(styles.get("bg", DefaultStyle.bg)) + globals.settings.style_alt_bg = colors.hex_to_rgba_0_1(styles.get("alt_bg", DefaultStyle.alt_bg)) + globals.settings.style_border = colors.hex_to_rgba_0_1(styles.get("border", DefaultStyle.border)) + globals.settings.style_text = colors.hex_to_rgba_0_1(styles.get("text", DefaultStyle.text)) + globals.settings.style_text_dim = colors.hex_to_rgba_0_1(styles.get("text_dim", DefaultStyle.text_dim)) + self.new_styles = True + self.refresh_styles() + except Exception: + pass + + def close(self, *_, **__): + glfw.set_window_should_close(self.window, True) + + def char_callback(self, window: glfw._GLFWwindow, char: int): + self.impl.char_callback(window, char) + self.poll_chars.append(char) + + def close_callback(self, window: glfw._GLFWwindow): + if globals.settings.background_on_close: + self.hide() + glfw.set_window_should_close(self.window, False) + + def minimize_callback(self, window: glfw._GLFWwindow, minimized: int): + self.minimized = minimized + + def focus_callback(self, window: glfw._GLFWwindow, focused: int): + self.focused = focused + + def pos_callback(self, window: glfw._GLFWwindow, x: int, y: int): + if not glfw.get_window_attrib(self.window, glfw.ICONIFIED): + self.screen_pos = (x, y) + + def drop_callback(self, window: glfw._GLFWwindow, items: list[str]): + paths = [pathlib.Path(item) for item in items] + if globals.popup_stack and isinstance(picker := getattr(globals.popup_stack[-1].func, "__self__", None), filepicker.FilePicker): + path = paths[0] + if (picker.dir_picker and path.is_dir()) or (not picker.dir_picker and path.is_file()): + picker.selected = str(path) + if picker.callback: + picker.callback(picker.selected) + picker.active = False + else: + for path in paths: + if path.suffix and path.suffix.lower() == ".html": + async_thread.run(api.import_browser_bookmarks(path)) + elif path.suffix and path.suffix.lower() == ".url": + async_thread.run(api.import_url_shortcut(path)) + + @property + def hidden(self): + return not glfw.get_window_attrib(self.window, glfw.VISIBLE) + + def _hide(self, *_, **__): + self.screen_pos = glfw.get_window_pos(self.window) + glfw.hide_window(self.window) + self.tray.update_status() + + def hide(self, *_, **__): + self.call_soon.append(self._hide) + + def _show(self, *_, **__): + self.bg_mode_timer = None + self.bg_mode_notifs_timer = None + # if not self.hidden: + # glfw.hide_window(self.window) + glfw.show_window(self.window) + if utils.validate_geometry(*self.screen_pos, *self.prev_size): + glfw.set_window_pos(self.window, *self.screen_pos) + glfw.focus_window(self.window) + self.tray.update_status() + + def show(self, *_, **__): + self.call_soon.append(self._show) + + def scaled(self, size: int | float): + return _scaled(globals.settings.interface_scaling, size) + + def main_loop(self): + # Loop variables + prev_scaling = globals.settings.interface_scaling + self.scroll_energy = 0.0 + prev_any_hovered = None + prev_win_hovered = None + prev_mouse_pos = None + prev_minimized = None + prev_focused = None + any_hovered = False + win_hovered = None + prev_cursor = None + prev_hidden = None + draw_next = 5.0 + size = (0, 0) + cursor = -1 + first_frame = True + try: + # While window is open + while not glfw.window_should_close(self.window): + # Tick events and inputs + prev_mouse_pos = imgui.io.mouse_pos + prev_minimized = self.minimized + prev_win_hovered = win_hovered + prev_any_hovered = any_hovered + prev_focused = self.focused + prev_hidden = self.hidden + self.prev_size = size + prev_cursor = cursor + self.qt_app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents) + glfw.make_context_current(self.window) + if self.repeat_chars: + for char in self.input_chars: + imgui.io.add_input_character(char) + self.repeat_chars = False + self.input_chars.clear() + glfw.poll_events() + self.input_chars, self.poll_chars = self.poll_chars, self.input_chars + self.impl.process_inputs() + if self.call_soon: + while self.call_soon and (call_soon := self.call_soon.pop(0)): + call_soon() + # Window state handling + size = imgui.io.display_size + mouse_pos = imgui.io.mouse_pos + cursor = imgui.get_mouse_cursor() + any_hovered = imgui.is_any_item_hovered() + win_hovered = glfw.get_window_attrib(self.window, glfw.HOVERED) + if first_frame or (not self.hidden and not self.minimized): # Visible + + # Scroll modifiers (must be before new_frame()) + imgui.io.mouse_wheel *= globals.settings.scroll_amount + if globals.settings.scroll_smooth: + + if self.scroll_energy * imgui.io.mouse_wheel < 0: # fast check if signs are opposite + # we want to immediately reverse rather than slowly decelerating. + self.scroll_energy = 0.0 + + self.scroll_energy += imgui.io.mouse_wheel + if abs(self.scroll_energy) > 0.1: + scroll_now = self.scroll_energy * imgui.io.delta_time * globals.settings.scroll_smooth_speed + self.scroll_energy -= scroll_now + else: + scroll_now = 0.0 + self.scroll_energy = 0.0 + imgui.io.mouse_wheel = scroll_now + + # Redraw only when needed + launch_state_changed = callbacks.launch_state_changed + callbacks.launch_state_changed = False + popup_stack_changed = globals.popup_stack_changed + globals.popup_stack_changed = False + draw = ( + (api.downloads and any(dl.state in (dl.State.Verifying, dl.State.Extracting) for dl in api.downloads.values())) + or (imagehelper.redraw and globals.settings.play_gifs and (self.focused or globals.settings.play_gifs_unfocused)) + or imgui.io.mouse_wheel or self.input_chars or any(imgui.io.mouse_down) or any(imgui.io.keys_down) + or (prev_mouse_pos != mouse_pos and (prev_win_hovered or win_hovered)) + or imagehelper.apply_queue or imagehelper.unload_queue + or prev_scaling != globals.settings.interface_scaling + or prev_minimized != self.minimized + or api.session.connector._acquired + or prev_focused != self.focused + or prev_hidden != self.hidden + or size != self.prev_size + or self.recalculate_ids + or launch_state_changed + or popup_stack_changed + or self.new_styles + or api.updating + ) + if draw: + draw_next = max(draw_next, imgui.io.delta_time + 1.0) # Draw for at least next half second + if draw_next > 0.0: # Visible and drawing + if not first_frame: + draw_next -= imgui.io.delta_time + draw_start = time.perf_counter() + + # Reactive mouse cursors + if cursor != prev_cursor or any_hovered != prev_any_hovered: + shape = glfw.ARROW_CURSOR + if cursor == imgui.MOUSE_CURSOR_TEXT_INPUT: + shape = glfw.IBEAM_CURSOR + elif any_hovered: + shape = glfw.HAND_CURSOR + glfw.set_cursor(self.window, glfw.create_standard_cursor(shape)) + + # Start drawing + prev_scaling = globals.settings.interface_scaling + imgui.new_frame() + self.new_styles = False + imagehelper.redraw = False + + # Imgui window is top left of display window, and has same size + imgui.set_next_window_position(0, 0, imgui.ONCE) + if size != self.prev_size and not self.minimized: + imgui.set_next_window_size(*size, imgui.ALWAYS) + + # Create main window + imgui.push_style_var(imgui.STYLE_WINDOW_BORDERSIZE, 0) + imgui.begin("F95Checker", closable=False, flags=self.window_flags) + imgui.pop_style_var() + sidebar_size = self.scaled(self.sidebar_size) + + # Main pane + imgui.begin_child("###main_frame", width=-sidebar_size) + self.hovered_game = None + # Tabbar + self.draw_tabbar() + # Games container + match globals.settings.display_mode.value: + case DisplayMode.list.value: + self.draw_games_list() + case DisplayMode.grid.value: + self.draw_games_grid() + case DisplayMode.kanban.value: + self.draw_games_kanban() + # Bottombar + self.draw_bottombar() + imgui.end_child() + + # Prepare bottom status / watermark text (done before sidebar to get text offset from bottom of window) + if (count := api.images_counter.count) > 0: + text = f"Downloading {count} image{'s' if count > 1 else ''}..." + elif (count := api.full_checks_counter.count) > 0: + text = f"Fetching {count} full thread{'s' if count > 1 else ''}..." + elif (count := api.fast_checks_counter) > 0: + text = f"Validating {count} cached item{'s' if count > 1 else ''}..." + elif globals.last_update_check is None: + text = "Checking for updates..." + elif (count := imagehelper.compress_counter) > 0: + text = "Compressing images..." if count == 1 else f"Compressing {count} frames..." + elif api.f95_ratelimit._waiters or api.f95_ratelimit_sleeping.count: + text = f"Waiting for F95zone ratelimit..." + else: + text = self.watermark_text + _3 = self.scaled(3) + _6 = self.scaled(6) + text_size = imgui.calc_text_size(text) + text_x = size.x - text_size.x - _6 + text_y = size.y - text_size.y - _6 + + # Sidebar + imgui.same_line(spacing=imgui.style.item_spacing.x) + imgui.begin_child("###sidebar_frame", width=sidebar_size - imgui.style.item_spacing.x + 1, height=-text_size.y) + self.draw_sidebar() + imgui.end_child() + + # Status / watermark text + imgui.set_cursor_screen_pos((text_x - _3, text_y)) + if imgui.invisible_button("", width=text_size.x + _6, height=text_size.y + _3): + utils.push_popup(self.draw_about_popup) + elif imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + callbacks.open_webpage(api.f95_host) + imgui.set_cursor_screen_pos((text_x, text_y)) + imgui.text(text) + + # Popups + open_popup_count = 0 + for popup in globals.popup_stack: + opened, closed = popup() + if closed: + globals.popup_stack.remove(popup) + globals.popup_stack_changed = True + open_popup_count += opened + # Popups are closed all at the end to allow stacking + for _ in range(open_popup_count): + imgui.end_popup() + + # Close main window (technically popups are inside the window, and inside one another - this gives proper stacking order) + imgui.end() + + # Render interface + imgui.render() + self.impl.render(imgui.get_draw_data()) + # Rescale fonts + if prev_scaling != globals.settings.interface_scaling: + self.refresh_fonts() + self.refresh_styles() + async_thread.run(db.update_settings("interface_scaling")) + imagehelper.post_draw(time.perf_counter() - draw_start) + # Wait idle time + glfw.swap_buffers(self.window) + if first_frame: + if not globals.settings.start_in_background: + glfw.show_window(self.window) + if globals.settings.start_refresh: + utils.start_refresh_task(api.refresh()) + first_frame = False + else: # Visible but not drawing + glfw.wait_events_timeout(1 / 15) + else: # Not visible + # Unload images if necessary + imagehelper.post_draw(0) + # Tray bg mode and not paused + if self.hidden and not self.bg_mode_paused and not utils.is_refreshing(): + if not self.bg_mode_timer: + # Schedule next refresh + self.bg_mode_timer = time.time() + globals.settings.bg_refresh_interval * 60 + self.tray.update_status() + elif self.bg_mode_timer and time.time() > self.bg_mode_timer: + # Run scheduled refresh + self.bg_mode_timer = None + utils.start_refresh_task(api.refresh(notifs=False), reset_bg_timers=False) + elif globals.settings.check_notifs: + if not self.bg_mode_notifs_timer: + # Schedule next notif check + self.bg_mode_notifs_timer = time.time() + globals.settings.bg_notifs_interval * 60 + self.tray.update_status() + elif self.bg_mode_notifs_timer and time.time() > self.bg_mode_notifs_timer: + # Run scheduled notif check + self.bg_mode_notifs_timer = None + utils.start_refresh_task(api.check_notifs(standalone=True), reset_bg_timers=False, notify_new_games=False) + # Wait idle time + if self.tray.menu_open: + time.sleep(1 / 60) + else: + time.sleep(1 / 8) # 8 FPS tray refresh icon + if utils.is_refreshing(): + globals.gui.tray.animate_refresh_icon() + finally: + # Main loop over, cleanup and close + self.save_filters() + async_thread.wait(db.update_settings("zoom_area", "zoom_times")) + imgui.save_ini_settings_to_disk(imgui.io.ini_file_name) + ini = imgui.save_ini_settings_to_memory() + try: + start = ini.find("[Window][F95Checker]") + assert start != -1 + end = ini.find("\n\n", start) + assert end != -1 + if "ScreenPos=" not in ini[start:end]: + insert = ini.find("\n", start) + new_ini = ini[:insert] + f"\nScreenPos={self.screen_pos[0]},{self.screen_pos[1]}" + ini[insert:] + else: + new_ini = ini + except Exception: + new_ini = ini + with open(imgui.io.ini_file_name, "w") as f: + f.write(new_ini) + self.impl.shutdown() + glfw.terminate() + + def draw_hover_text(self, hover_text: str, text="(?)", force=False): + if text: + imgui.text_disabled(text) + if force or imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) + imgui.text(hover_text) + imgui.pop_text_wrap_pos() + imgui.end_tooltip() + + def begin_framed_text(self, color: tuple[float], interaction=True): + if interaction: + imgui.push_style_color(imgui.COLOR_BUTTON_ACTIVE, *color) + imgui.push_style_color(imgui.COLOR_BUTTON_HOVERED, *color) + else: + imgui.push_no_interaction() + imgui.push_style_color(imgui.COLOR_BUTTON, *color) + imgui.push_style_color(imgui.COLOR_TEXT, *colors.foreground_color(color)) + imgui.push_style_var(imgui.STYLE_FRAME_BORDERSIZE, 0) + + def end_framed_text(self, interaction=True): + imgui.pop_style_var() + if interaction: + imgui.pop_style_color(4) + else: + imgui.pop_style_color(2) + imgui.pop_no_interaction() + + def get_type_label_width(self): + if self.type_label_width is None: + self.type_label_width = 0 + for name in Type._member_names_: + self.type_label_width = max(self.type_label_width, imgui.calc_text_size(name).x) + self.type_label_width += 8 + return self.type_label_width + + def draw_type_widget(self, type: Type, wide=True, align=False): + quick_filter = globals.settings.quick_filters + self.begin_framed_text(type.color, interaction=quick_filter) + if wide: + x_padding = 4 + backup_y_padding = imgui.style.frame_padding.y + imgui.push_style_var(imgui.STYLE_FRAME_PADDING, (x_padding, 0)) + if align: + imgui.push_y(backup_y_padding) + clicked = imgui.button(type.name, width=self.get_type_label_width()) + if align: + imgui.pop_y() + imgui.pop_style_var() + else: + clicked = imgui.small_button(type.name) + if clicked and quick_filter: + flt = Filter(FilterMode.Type) + flt.match = type + self.filters.append(flt) + self.end_framed_text(interaction=quick_filter) + + def draw_tag_widget(self, tag: Tag, quick_filter=True, change_highlight=True): + quick_filter = quick_filter and globals.settings.quick_filters + interaction = quick_filter or change_highlight + color = (0.3, 0.3, 0.3, 1.0) + if globals.settings.highlight_tags: + if highlight := globals.settings.tags_highlights.get(tag): + color = highlight.color + self.begin_framed_text(color, interaction=interaction) + if imgui.small_button(tag.text) and quick_filter: + flt = Filter(FilterMode.Tag) + flt.match = tag + self.filters.append(flt) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_RIGHT) and change_highlight: + if tag not in globals.settings.tags_highlights: + globals.settings.tags_highlights[tag] = TagHighlight[TagHighlight._member_names_[0]] + else: + highlight = globals.settings.tags_highlights[tag] + if highlight is TagHighlight[TagHighlight._member_names_[-1]]: + del globals.settings.tags_highlights[tag] + else: + globals.settings.tags_highlights[tag] = TagHighlight(highlight + 1) + async_thread.run(db.update_settings("tags_highlights")) + self.end_framed_text(interaction=interaction) + + def draw_label_widget(self, label: Label, short=False): + quick_filter = globals.settings.quick_filters + self.begin_framed_text(label.color, interaction=quick_filter) + if imgui.small_button(label.short_name if short else label.name) and quick_filter: + flt = Filter(FilterMode.Label) + flt.match = label + self.filters.append(flt) + if short and imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.push_font(imgui.fonts.default) + self.draw_label_widget(label, short=False) + imgui.pop_font() + imgui.end_tooltip() + self.end_framed_text(interaction=quick_filter) + + def draw_tab_widget(self, tab: Tab): + color = (tab and tab.color) or globals.settings.style_accent + self.begin_framed_text(color, interaction=False) + if (tab and tab.icon) and (tab and (tab.name or 'New Tab')): + imgui.small_button(f"{tab.icon} {tab.name or 'New Tab'}") + else: + imgui.small_button(f"{Tab.first_tab_label()}") + self.end_framed_text(interaction=False) + + def draw_status_widget(self, status: Status): + quick_filter = globals.settings.quick_filters + if quick_filter: + imgui.begin_group() + pos = imgui.get_cursor_pos() + imgui.text_colored(getattr(icons, status.icon), *status.color) + if quick_filter: + imgui.set_cursor_pos(pos) + if imgui.invisible_button("", *imgui.get_item_rect_size()): + flt = Filter(FilterMode.Status) + flt.match = status + self.filters.append(flt) + imgui.end_group() + + def draw_game_update_icon(self, game: Game): + quick_filter = globals.settings.quick_filters + with imgui.begin_group(): + pos = imgui.get_cursor_pos() + imgui.text_colored(icons.star_circle, 0.85, 0.85, 0.00) + imgui.set_cursor_pos(pos) + imgui.invisible_button("", *imgui.get_item_rect_size()) + if imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) + imgui.text("This game has been updated!") + imgui.text_disabled("Installed:") + imgui.same_line() + imgui.text(game.installed or 'N/A') + imgui.text_disabled("Latest:") + imgui.same_line() + imgui.text(game.version) + imgui.text( + "To remove this update marker:\n" + f"{icons.menu_right} Middle click\n" + f"{icons.menu_right} Alt + Left click\n" + f"{icons.menu_right} Mark game as installed" + ) + imgui.pop_text_wrap_pos() + imgui.end_tooltip() + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + # Middle click - remove update marker + game.updated = False + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_LEFT): + if imgui.is_key_down(glfw.KEY_LEFT_ALT): + # Alt + left click - remove update marker + game.updated = False + elif quick_filter: + # Left click - trigger quick filter + flt = Filter(FilterMode.Updated) + self.filters.append(flt) + + def draw_game_unknown_tags_icon(self, game: Game): + with imgui.begin_group(): + pos = imgui.get_cursor_pos() + imgui.text_colored(icons.progress_tag, 1.00, 0.65, 0.00) + imgui.set_cursor_pos(pos) + imgui.invisible_button("", *imgui.get_item_rect_size()) + if imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) + imgui.text( + "This game has new tags that F95Checker failed to recognize:\n" + + "\n".join(f" - {tag}" for tag in game.unknown_tags) + ) + imgui.text( + "To copy them:\n" + f"{icons.menu_right} Shift + Left click\n" + f"{icons.menu_right} Use Copy button in Tags section\n" + ) + imgui.text( + "To remove this marker:\n" + f"{icons.menu_right} Middle click\n" + f"{icons.menu_right} Alt + Left click\n" + ) + imgui.pop_text_wrap_pos() + imgui.end_tooltip() + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + # Middle click - remove unknown tags marker + game.unknown_tags_flag = False + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_LEFT): + if imgui.is_key_down(glfw.KEY_LEFT_ALT): + # Alt + left click - remove unknown tags marker + game.unknown_tags_flag = False + elif imgui.is_key_down(glfw.KEY_LEFT_SHIFT): + # Shift + left click - copy tags to clipboard + callbacks.clipboard_copy(", ".join(game.unknown_tags)) + + def draw_game_archive_icon(self, game: Game): + quick_filter = globals.settings.quick_filters + if quick_filter: + imgui.begin_group() + pos = imgui.get_cursor_pos() + imgui.text_disabled(icons.archive) + if quick_filter: + imgui.set_cursor_pos(pos) + if imgui.invisible_button("", *imgui.get_item_rect_size()): + flt = Filter(FilterMode.Archived) + self.filters.append(flt) + imgui.end_group() + self.draw_hover_text( + "This game is archived!\n" + "In this state you won't receive update notifications for\n" + "this game and it will stay at the bottom of the list.\n" + "Middle click to remove it from the archive, alternatively\n" + "use the right click menu to do the same.", + text=None, + ) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + game.archived = False + + def draw_game_more_info_button(self, game: Game, label="", selectable=False, carousel_ids: list = None): + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if clicked: + if not game: + carousel_ids = [game.id for game in globals.games.values() if game.selected] + game = globals.games[carousel_ids[0]] + utils.push_popup(self.draw_game_info_popup, game, carousel_ids.copy() if carousel_ids else None) + + def draw_game_play_button(self, game: Game, label="", selectable=False, executable: str = None): + if game and not game.executables: + imgui.push_style_color(imgui.COLOR_TEXT, *imgui.style.colors[imgui.COLOR_TEXT_DISABLED][:3], 0.75) + elif game: + if executable: + try: + valid = game.executables_valids[game.executables.index(executable)] + except Exception: + valid = False + else: + valid = game.executables_valid + if not valid: + imgui.push_style_color(imgui.COLOR_TEXT, 0.87, 0.20, 0.20) + launch_state = game.launch_state if game else LaunchState.Idle + if launch_state is LaunchState.Starting: + label = label.replace(" Play", " Starting") + imgui.push_style_color(imgui.COLOR_TEXT, 0.95, 0.75, 0.20) + elif launch_state is LaunchState.Playing: + label = label.replace(" Play", " Playing") + imgui.push_style_color(imgui.COLOR_TEXT, 0.30, 0.85, 0.35) + else: + launch_state = LaunchState.Idle # Make sure we don't imgui.pop_style_color() after + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if launch_state is not LaunchState.Idle: + imgui.pop_style_color() + if game and (not game.executables or not valid): + imgui.pop_style_color() + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + if game: + callbacks.open_game_folder(game, executable=executable) + else: + for game in globals.games.values(): + if game.selected: + callbacks.open_game_folder(game) + elif clicked: + if game: + callbacks.launch_game(game, executable=executable) + else: + for game in globals.games.values(): + if game.selected: + callbacks.launch_game(game) + + def draw_game_name_text(self, game: Game): + if game.archived: + imgui.text_disabled(game.name) + elif game.finished == game.version: + imgui.text(game.name) + else: + imgui.text_colored(game.name, *globals.settings.style_accent) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + callbacks.clipboard_copy(game.name) + + def draw_game_finished_checkbox(self, game: Game, label=""): + if game: + installed_finished = game.finished == (game.installed or game.version) + if installed_finished: + checkbox = imgui.checkbox + else: + checkbox = imgui._checkbox + changed, _ = checkbox(f"{label}###{game.id}_finished", bool(game.finished)) + if changed: + if installed_finished: + game.finished = "" # Finished -> Not finished + else: + game.finished = (game.installed or game.version) # Not finished -> Finished, Outdated finished -> Finished + game.add_timeline_event(TimelineEventType.GameFinished, game.version) + if game.finished and not installed_finished and imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) + imgui.text_disabled("Finished:") + imgui.same_line() + imgui.text(game.finished) + imgui.text_disabled("Installed:") + imgui.same_line() + imgui.text(game.installed or 'N/A') + imgui.text("Click to mark installed as finished.") + imgui.pop_text_wrap_pos() + imgui.end_tooltip() + else: + if imgui.small_button(icons.check): + for game in globals.games.values(): + if game.selected: + game.finished = (game.installed or game.version) + game.add_timeline_event(TimelineEventType.GameFinished, game.version) + imgui.same_line() + if imgui.small_button(icons.close): + for game in globals.games.values(): + if game.selected: + game.finished = "" + imgui.same_line() + imgui.text(label + " ") + + def draw_game_installed_checkbox(self, game: Game, label=""): + if game: + latest_installed = game.installed == game.version + if latest_installed: + checkbox = imgui.checkbox + else: + checkbox = imgui._checkbox + changed, _ = checkbox(f"{label}###{game.id}_installed", bool(game.installed)) + if changed: + if latest_installed: + game.installed = "" # Latest installed -> Not installed + else: + game.add_timeline_event(TimelineEventType.GameInstalled, game.version) + game.installed = game.version # Not installed -> Latest installed, Outdated installed -> Latest installed + game.updated = False + if game.installed and not latest_installed and imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) + imgui.text_disabled("Installed:") + imgui.same_line() + imgui.text(game.installed or 'N/A') + imgui.text_disabled("Latest:") + imgui.same_line() + imgui.text(game.version) + imgui.text("Click to mark latest as installed.") + imgui.pop_text_wrap_pos() + imgui.end_tooltip() + else: + if imgui.small_button(icons.check): + for game in globals.games.values(): + if game.selected: + game.add_timeline_event(TimelineEventType.GameInstalled, game.version) + game.installed = game.version + game.updated = False + imgui.same_line() + if imgui.small_button(icons.close): + for game in globals.games.values(): + if game.selected: + game.installed = "" + imgui.same_line() + imgui.text(label + " ") + + def draw_game_rating_widget(self, game: Game): + if not game: + imgui.text("Set:") + imgui.same_line() + if imgui.small_button(icons.close): + for game in globals.games.values(): + if game.selected: + game.rating = 0 + imgui.same_line() + changed, value = ratingwidget.ratingwidget("", game.rating if game else 0) + if changed: + if game: + game.rating = value + else: + for game in globals.games.values(): + if game.selected: + game.rating = value + + def draw_game_open_thread_button(self, game: Game, label="", selectable=False): + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if game and imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + if globals.settings.copy_urls_as_bbcode: + _url = lambda game: f"[URL='{game.url}']{game.name}[/URL]" + else: + _url = lambda game: game.url + if game: + text = _url(game) + else: + text = "\n".join(_url(game) for game in globals.games.values() if game.selected if game.url) + callbacks.clipboard_copy(text) + elif clicked: + if game: + callbacks.open_webpage(game.url) + else: + for game in globals.games.values(): + if game.selected: + callbacks.open_webpage(game.url) + + def draw_game_copy_link_button(self, game: Game, label="", selectable=False): + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if clicked: + if globals.settings.copy_urls_as_bbcode: + _url = lambda game: f"[URL='{game.url}']{game.name}[/URL]" + else: + _url = lambda game: game.url + if game: + text = _url(game) + else: + text = "\n".join(_url(game) for game in globals.games.values() if game.selected if game.url) + callbacks.clipboard_copy(text) + + def draw_game_archive_button(self, game: Game, label_off="", label_on="", selectable=False): + if game: + if selectable: + clicked = imgui.selectable(label_on if game.archived else label_off, False)[0] + else: + clicked = imgui.button(label_on if game.archived else label_off) + if clicked: + game.archived = not game.archived + if game.archived: + game.updated = False + else: + if imgui.small_button(icons.check): + for game in globals.games.values(): + if game.selected: + game.archived = True + game.updated = False + imgui.same_line() + if imgui.small_button(icons.close): + for game in globals.games.values(): + if game.selected: + game.archived = False + imgui.same_line() + imgui.text(label_off + " ") + + def draw_game_remove_button(self, game: Game, label="", selectable=False): + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if clicked: + if game: + callbacks.remove_game(game) + else: + callbacks.remove_game(*filter(lambda game: game.selected, globals.games.values())) + + def draw_game_add_exe_button(self, game: Game, label="", selectable=False): + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if clicked: + if game: + callbacks.add_game_exe(game) + else: + for game in globals.games.values(): + if game.selected: + callbacks.add_game_exe(game) + + def draw_game_clear_exes_button(self, game: Game, label="", selectable=False): + if game and not game.executables: + imgui.push_disabled() + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if game and not game.executables: + imgui.pop_disabled() + if clicked: + imgui.close_current_popup() + if game: + game.clear_executables() + else: + for game in globals.games.values(): + if game.selected: + game.clear_executables() + + def draw_game_open_folder_button(self, game: Game, label="", selectable=False, executable: str = None): + if game and not game.executables: + imgui.push_alpha(0.5) + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if game and not game.executables: + imgui.pop_alpha() + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + def _exe(exe): + if utils.is_uri(exe): + return exe + exe = pathlib.Path(exe) + if globals.settings.default_exe_dir.get(globals.os) and not exe.is_absolute(): + exe = pathlib.Path(globals.settings.default_exe_dir.get(globals.os)) / exe + return str(exe) + if game: + if executable: + text = _exe(executable) + else: + text = "\n".join(_exe(exe) for exe in game.executables) + else: + text = "\n".join("\n".join(_exe(exe) for exe in game.executables) for game in globals.games.values() if game.selected) + callbacks.clipboard_copy(text) + elif clicked: + if game: + callbacks.open_game_folder(game, executable=executable) + else: + for game in globals.games.values(): + if game.selected: + callbacks.open_game_folder(game) + + def draw_game_id_button(self, game: Game, label="", selectable=False): + if game.custom: + imgui.push_disabled() + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if clicked: + callbacks.clipboard_copy(str(game.id)) + self.draw_hover_text( + f"Thread ID: {game.id}\n" + "Click to copy!", + text=None, + ) + if game.custom: + imgui.pop_disabled() + + def draw_game_recheck_button(self, game: Game, label="", selectable=False): + if game and game.custom: + imgui.push_disabled() + if selectable: + clicked = imgui.selectable(label, False)[0] + else: + clicked = imgui.button(label) + if clicked: + games = [game] if game else list(filter(lambda g: g.selected, globals.games.values())) + for g in games: + g.add_timeline_event(TimelineEventType.RecheckUserReq) + utils.start_refresh_task(api.refresh(*games, full=True, notifs=False)) + if game and game.custom: + imgui.pop_disabled() + + def draw_game_tab_widget(self, game: Game): + self.draw_tab_widget(game.tab) + if imgui.begin_popup_context_item(f"###{game.id}_context_tab"): + imgui.text("Move To:") + imgui.separator() + self.draw_game_tab_select_widget(game) + imgui.end_popup() + + def draw_game_labels_select_widget(self, game: Game): + if Label.instances: + if game: + for label in Label.instances: + changed, value = imgui.checkbox(f"###{game.id}_label_{label.id}", label in game.labels) + if changed: + if value: + game.add_label(label) + else: + game.remove_label(label) + imgui.same_line() + self.draw_label_widget(label) + else: + for label in Label.instances: + if imgui.small_button(icons.check): + for game in globals.games.values(): + if game.selected: + game.add_label(label) + imgui.same_line() + if imgui.small_button(icons.close): + for game in globals.games.values(): + if game.selected: + game.remove_label(label) + imgui.same_line() + self.draw_label_widget(label) + else: + imgui.text_disabled("Make some labels first!") + + def draw_game_tab_select_widget(self, game: Game): + current_tab = game.tab if game else self.current_tab + new_tab = current_tab + if current_tab is None: + imgui.push_disabled() + if imgui.selectable(f"{Tab.first_tab_label()}###move_tab_-1", False)[0]: + new_tab = None + if current_tab is None: + imgui.pop_disabled() + for tab in Tab.instances: + if current_tab is tab: + imgui.push_disabled() + color = (tab and tab.color) or globals.settings.style_accent + imgui.push_style_color(imgui.COLOR_HEADER_HOVERED, *color) + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###move_tab_{tab.id}", False)[0]: + new_tab = tab + imgui.set_cursor_pos(pos) + if imgui.is_item_hovered(): + imgui.pop_style_color() + imgui.push_style_color(imgui.COLOR_TEXT, *colors.foreground_color(color)) + imgui.text(f"{tab.icon} {tab.name or 'New Tab'}") + imgui.pop_style_color() + if current_tab is tab: + imgui.pop_disabled() + if imgui.selectable(f"{icons.tab_plus} New Tab###move_tab_-2", False)[0]: + new_tab = async_thread.wait(db.create_tab()) + globals.settings.display_tab = new_tab + async_thread.run(db.update_settings("display_tab")) + self.recalculate_ids = True + imgui.close_current_popup() + if new_tab is not current_tab: + imgui.close_current_popup() + if game: + game.tab = new_tab + else: + for game in globals.games.values(): + if game.selected: + game.tab = new_tab + + def draw_game_tags_select_widget(self, game: Game): + for tag in Tag: + changed, value = imgui.checkbox(f"###{game.id}_tag_{tag.value}", tag in game.tags) + if changed: + if value: + game.tags = tuple(sorted(list(game.tags) + [tag], key=lambda tag: tag.name)) + else: + game.tags = tuple(sorted(filter(lambda x: x is not tag, game.tags), key=lambda tag: tag.name)) + imgui.same_line() + self.draw_tag_widget(tag, quick_filter=False, change_highlight=False) + + def draw_game_context_menu(self, game: Game = None): + if not game: + imgui.text(f"Selected games: {self.selected_games_count}") + self.draw_game_more_info_button(game, f"{icons.information_outline} More Info", selectable=True, carousel_ids=self.show_games_ids[self.current_tab]) + self.draw_game_recheck_button(game, f"{icons.reload_alert} Full Recheck", selectable=True) + imgui.separator() + self.draw_game_play_button(game, f"{icons.play} Play", selectable=True) + self.draw_game_open_thread_button(game, f"{icons.open_in_new} Open Thread", selectable=True) + self.draw_game_copy_link_button(game, f"{icons.content_copy} Copy Link", selectable=True) + imgui.separator() + self.draw_game_add_exe_button(game, f"{icons.folder_edit_outline} Add Exe", selectable=True) + self.draw_game_clear_exes_button(game, f"{icons.folder_remove_outline} Clear Exes", selectable=True) + self.draw_game_open_folder_button(game, f"{icons.folder_open_outline} Open Folder", selectable=True) + imgui.separator() + self.draw_game_finished_checkbox(game, f"{icons.flag_checkered} Finished") + self.draw_game_installed_checkbox(game, f"{icons.download} Installed") + imgui.separator() + self.draw_game_rating_widget(game) + if imgui.begin_menu(f"{icons.label_multiple_outline} Labels"): + self.draw_game_labels_select_widget(game) + imgui.end_menu() + if imgui.begin_menu(f"{icons.tab} Move to Tab"): + self.draw_game_tab_select_widget(game) + imgui.end_menu() + imgui.separator() + self.draw_game_archive_button(game, label_off=f"{icons.archive_outline} Archive", label_on=f"{icons.archive_off_outline} Unarchive", selectable=True) + self.draw_game_remove_button(game, f"{icons.trash_can_outline} Remove", selectable=True) + + def draw_game_notes_widget(self, game: Game, multiline=True, width: int | float = None): + if multiline: + changed, value = imgui.input_text_multiline( + f"###{game.id}_notes", + value=game.notes, + width=width or imgui.get_content_region_available_width(), + height=self.scaled(450) + ) + if changed: + game.notes = value + if imgui.begin_popup_context_item(f"###{game.id}_notes_context"): + utils.text_context(game, "notes") + imgui.end_popup() + else: + imgui.set_next_item_width(width or imgui.get_content_region_available_width()) + # Only show first line + if (offset := game.notes.find("\n")) != -1: + first_line = game.notes[:offset] + else: + first_line = game.notes + changed, first_line = imgui.input_text( + f"###{game.id}_notes_inline", + value=first_line + ) + def setter_extra(value: str): + # Merge with remaining lines + if (offset := game.notes.find("\n")) != -1: + value += game.notes[offset:] + game.notes = value + if changed: + setter_extra(first_line) + if imgui.begin_popup_context_item(f"###{game.id}_notes_inline_context"): + utils.text_context(type("_", (), dict(_=first_line))(), "_", setter_extra) + imgui.end_popup() + + def draw_game_reviews_widget(self, game: Game): + icon_coordinates: list[tuple[x1, y1, x2, y2]] = [] + text_coordinates: list[tuple[x1, y1, x2, y2]] = [] + line_coordinates: list[tuple[x1, y1, x2, y2]] = [] + + imgui.dummy(0, self.scaled(6)) + + for review in game.reviews: + if review.score > 3: + icon = icons.thumb_up + elif review.score == 3: + icon = icons.approximately_equal + else: + icon = icons.thumb_down + date = dt.datetime.fromtimestamp(review.timestamp) + # Draw icon + imgui.dummy(0, 0) + imgui.same_line() + cur = imgui.get_cursor_screen_pos() + imgui.text_colored(icon, *globals.settings.style_accent) + icon_size = imgui.get_item_rect_size() + icon_coordinates.append((cur.x, cur.y, cur.x + icon_size.x, cur.y + icon_size.y)) + # Draw header + imgui.same_line(spacing=self.scaled(15)) + pos_x, header_pos_y = imgui.get_cursor_screen_pos() + imgui.push_font(imgui.fonts.bold) + imgui.text_colored(review.user, *globals.settings.style_accent) + imgui.pop_font() + imgui.same_line() + imgui.push_y(self.scaled(3.5)) + imgui.text_disabled(date.strftime(globals.settings.datestamp_format)) + self.draw_hover_text(date.strftime(globals.settings.timestamp_format), text=None) + imgui.same_line() + imgui.push_no_interaction() + ratingwidget.ratingwidget("", review.score) + imgui.pop_no_interaction() + imgui.pop_y() + imgui.spacing() + # Draw message + message_pos_y = imgui.get_cursor_screen_pos().y + imgui.set_cursor_screen_pos((pos_x, message_pos_y)) + imgui.text(review.message) + # Draw footer + if review.likes: + footer_pos_y = imgui.get_cursor_screen_pos().y + imgui.set_cursor_screen_pos((pos_x, footer_pos_y)) + imgui.text_disabled(f"Liked by {review.likes} user{'' if review.likes == 1 else 's'}") + final_pos_x = imgui.get_cursor_screen_pos().x + imgui.get_content_region_available_width() - self.scaled(2) + final_pos_y = imgui.get_cursor_screen_pos().y - self.scaled(2) + text_coordinates.append((pos_x, header_pos_y, final_pos_x, final_pos_y)) + line_coordinates.append((pos_x, message_pos_y, final_pos_x, message_pos_y)) + imgui.dummy(0, self.scaled(16)) + + if len(game.reviews) < game.reviews_total: + imgui.dummy(0, 0) + imgui.same_line() + cur = imgui.get_cursor_screen_pos() + imgui.begin_group() + imgui.text_colored(icons.open_in_new, *globals.settings.style_accent) + imgui.same_line(spacing=self.scaled(10)) + imgui.text(f"Read {game.reviews_total - len(game.reviews)} more reviews in forum thread ") + imgui.end_group() + button_size = imgui.get_item_rect_size() + if imgui.is_item_clicked(): + callbacks.open_webpage(game.url + "/page-2/br-reviews") + icon_coordinates.append((cur.x, cur.y, cur.x + button_size.x, cur.y + button_size.y)) + imgui.set_cursor_screen_pos(cur) + imgui.invisible_button("", *button_size) + + thickness = imgui.style.frame_border_size + prev_rect = None + padding = self.scaled(3) + dl = imgui.get_window_draw_list() + color = imgui.get_color_u32_rgba(*globals.settings.style_border) + + # Draw timeline primitives + rounding = self.scaled(globals.settings.style_corner_radius) + for x1, y1, x2, y2 in icon_coordinates: + dl.add_rect(x1 - padding, y1 - padding, x2 + padding, y2 + padding, color, rounding=rounding, thickness=thickness) + if prev_rect: + dl.add_line((prev_rect[0] + prev_rect[2]) / 2, prev_rect[3] + padding, (prev_rect[0] + prev_rect[2]) / 2, y1 - padding, color, thickness=thickness) + prev_rect = (x1, y1, x2, y2) + for x1, y1, x2, y2 in text_coordinates: + dl.add_rect(x1 - padding - self.scaled(2), y1 - padding, x2 + padding + self.scaled(2), y2 + padding, color, rounding=rounding, thickness=thickness) + for x1, y1, x2, y2 in line_coordinates: + dl.add_line(x1 - padding - self.scaled(2), y1 - padding, x2 + padding + self.scaled(2), y2 - padding, color, thickness=thickness) + + def draw_game_tags_widget(self, game: Game): + pad = 2 * imgui.style.frame_padding.x + imgui.style.item_spacing.x + for tag in game.tags: + if imgui.get_content_region_available_width() < imgui.calc_text_size(tag.name).x + pad: + imgui.dummy(0, 0) + self.draw_tag_widget(tag) + imgui.same_line() + imgui.dummy(0, 0) + + def draw_game_labels_widget(self, game: Game, wrap=True, small=False, short=False, align=False): + pad = 2 * imgui.style.frame_padding.x + imgui.style.item_spacing.x + if small: + short = True + imgui.push_font(imgui.fonts.small) + if align: + imgui.push_y(self.scaled(2.5)) + popped_y = False + for label in game.labels: + if wrap and imgui.get_content_region_available_width() < imgui.calc_text_size(label.short_name if short else label.name).x + pad: + if small and align and not popped_y: + imgui.pop_y() + popped_y = True + imgui.dummy(0, 0) + self.draw_label_widget(label, short=short) + imgui.same_line() + if small and align and not popped_y: + imgui.pop_y() + elif wrap: + imgui.dummy(0, 0) + if small: + imgui.pop_font() + + def draw_timeline_filter_widget(self, game: Game): + label = f"###{game.id}_timeline_filter_popup" + if imgui.button(f"{icons.eye_off_outline} Hide events"): + imgui.open_popup(label) + if imgui.begin_popup(label): + for event_type in TimelineEventType: + changed, value = imgui.checkbox(f"###{game.id}_event_{event_type.value}", event_type in globals.settings.hidden_timeline_events) + if changed: + if value: + globals.settings.hidden_timeline_events.append(event_type) + else: + globals.settings.hidden_timeline_events.remove(event_type) + async_thread.run(db.update_settings("hidden_timeline_events")) + imgui.same_line() + imgui.text(f"{getattr(icons, event_type.icon)} {event_type.display}") + imgui.end_popup() + + def draw_game_timeline_widget(self, game: Game): + icon_coordinates: list[tuple[x1, y1, x2, y2]] = [] + text_coordinates: list[tuple[x1, y1, x2, y2]] = [] + + self.draw_timeline_filter_widget(game) + imgui.dummy(0, 0 if globals.settings.compact_timeline else self.scaled(6)) + + def draw_event(timestamp, type, args, spacing=True): + icon = getattr(icons, type.icon) + date = dt.datetime.fromtimestamp(timestamp) + message = type.template.format(*args, *["?" for _ in range(type.args_min - len(args))]) + # Short timeline variant + if globals.settings.compact_timeline: + imgui.push_font(imgui.fonts.mono) + imgui.text_disabled(date.strftime(globals.settings.timestamp_format)) + imgui.pop_font() + imgui.same_line() + imgui.text_colored(icon, *globals.settings.style_accent) + imgui.same_line() + imgui.text(message) + return + # Draw icon + imgui.dummy(0, 0) + imgui.same_line() + cur = imgui.get_cursor_screen_pos() + imgui.text_colored(icon, *globals.settings.style_accent) + icon_size = imgui.get_item_rect_size() + icon_coordinates.append((cur.x, cur.y, cur.x + icon_size.x, cur.y + icon_size.y)) + # Draw timestamp + imgui.same_line(spacing=self.scaled(15)) + timestamp_pos = imgui.get_cursor_screen_pos() + imgui.text_disabled(date.strftime(globals.settings.datestamp_format)) + timestamp_size = imgui.get_item_rect_size() + self.draw_hover_text(date.strftime(globals.settings.timestamp_format), text=None) + # Draw message + message_pos = (timestamp_pos.x, timestamp_pos.y + timestamp_size.y - self.scaled(2)) + imgui.set_cursor_screen_pos(message_pos) + imgui.text(message) + message_size = imgui.get_item_rect_size() + final_x = timestamp_pos.x + timestamp_size.x if timestamp_size.x > message_size.x else message_pos[0] + message_size.x + text_coordinates.append((timestamp_pos.x, timestamp_pos.y, final_x, message_pos[1] + message_size.y)) + if spacing: + imgui.dummy(0, self.scaled(10)) + + for event in game.timeline_events: + if event.type not in globals.settings.hidden_timeline_events: + draw_event(event.timestamp.value, event.type, event.arguments) + + if TimelineEventType.GameAdded not in globals.settings.hidden_timeline_events: + draw_event(game.added_on.value, TimelineEventType.GameAdded, [], spacing=False) + + thickness = imgui.style.frame_border_size + prev_rect = None + padding = self.scaled(3) + dl = imgui.get_window_draw_list() + color = imgui.get_color_u32_rgba(*globals.settings.style_border) + + # Draw timeline primitives + rounding = self.scaled(globals.settings.style_corner_radius) + for x1, y1, x2, y2 in icon_coordinates: + dl.add_rect(x1 - padding, y1 - padding, x2 + padding, y2 + padding, color, rounding=rounding, thickness=thickness) + if prev_rect: + dl.add_line((prev_rect[0] + prev_rect[2]) / 2, prev_rect[3] + padding, (x1 + x2) / 2, y1 - padding, color, thickness=thickness) + prev_rect = (x1, y1, x2, y2) + for x1, y1, x2, y2 in text_coordinates: + dl.add_rect(x1 - padding - self.scaled(2), y1 - padding, x2 + padding + self.scaled(2), y2 + padding, color, rounding=rounding, thickness=thickness) + + def draw_game_downloads_header(self, game: Game): + pad = 3 * imgui.style.item_spacing.x + def _cluster_text(name, text): + imgui.text_disabled(name[0]) + if imgui.is_item_hovered(): + imgui.set_tooltip(name[2:]) + imgui.same_line() + imgui.text(text) + imgui.same_line(spacing=pad) + _cluster_text(cols.name.name, game.name) + _cluster_text(cols.version.name, game.version) + _cluster_text(cols.last_updated.name, game.last_updated.display or "Unknown") + _cluster_text(cols.developer.name, game.developer) + imgui.spacing() + imgui.spacing() + + def draw_updates_popup(self, popup_uuid: str = ""): + def popup_content(): + indent = self.scaled(222) + width = indent - 3 * imgui.style.item_spacing.x + full_width = 3 * indent + img_pos_x = imgui.get_cursor_pos_x() + category = None + category_open = False + imgui.push_text_wrap_pos(full_width) + imgui.indent(indent) + for game_i, id in enumerate(globals.updated_games_sorted_ids): + if id not in globals.games: + globals.updated_games_sorted_ids.remove(id) + continue + old_game = globals.updated_games[id] + game = globals.games[id] + if category is not game.type.category: + category = game.type.category + imgui.push_font(imgui.fonts.big) + imgui.set_cursor_pos_x(img_pos_x - self.scaled(8)) + category_open = imgui.tree_node( + category.name, + flags=( + imgui.TREE_NODE_NO_TREE_PUSH_ON_OPEN | + imgui.TREE_NODE_SPAN_FULL_WIDTH | + imgui.TREE_NODE_DEFAULT_OPEN + ) + ) + imgui.pop_font() + if not category_open: + continue + img_pos_y = imgui.get_cursor_pos_y() + imgui.begin_group() + + imgui.push_font(imgui.fonts.big) + imgui.text(old_game.name) + imgui.pop_font() + + imgui.spacing() + imgui.text_disabled("Update date: ") + imgui.same_line() + imgui.text(game.last_updated.display) + + if Tab.instances: + imgui.spacing() + imgui.text_disabled("Tab: ") + imgui.same_line() + self.draw_game_tab_widget(game) + + for attr in ("name", "version"): + old_val = getattr(old_game, attr) or "Unknown" + new_val = getattr(game, attr) or "Unknown" + if new_val != old_val: + imgui.spacing() + imgui.text_disabled(f"{attr.title()}: ") + imgui.same_line() + # Hack: get fast line wrapping in ImGui but draw arrow in other color: + # Save position, draw full text with arrow in dimmed color, restore position, + # and draw full text in normal color like an overlay, with an Em Space instead + # of the arrow. This seems to be the only character that is whitespace but + # counts as "solid" for wrapping. This way, if arrow itself wraps to nextline, + # when drawing without arrow the Em Space will make ImGui leave space for it. + val_start = imgui.get_cursor_pos() + imgui.text_disabled(f"{old_val} -> {new_val}") + imgui.set_cursor_pos(val_start) + em_space = " " + imgui.text(f"{old_val} {em_space} {new_val}") + + if game.status is not old_game.status: + imgui.spacing() + imgui.text_disabled("Status: ") + imgui.same_line() + imgui.text(old_game.status.name) + imgui.same_line() + self.draw_status_widget(old_game.status) + imgui.same_line() + arrow_width = imgui.calc_text_size(" -> ").x + imgui.style.item_spacing.x + if full_width - imgui.get_cursor_pos_x() < arrow_width: + imgui.dummy(0, 0) + imgui.text_disabled(" -> ") + imgui.same_line() + imgui.text(game.status.name) + imgui.same_line() + self.draw_status_widget(game.status) + + imgui.spacing() + self.draw_game_open_thread_button(game, f"{icons.open_in_new} Thread") + imgui.same_line() + self.draw_game_copy_link_button(game, f"{icons.content_copy} Link") + imgui.same_line() + self.draw_game_more_info_button(game, f"{icons.information_outline} Info", carousel_ids=globals.updated_games_sorted_ids) + + imgui.end_group() + height = imgui.get_item_rect_size().y + imgui.style.item_spacing.y + crop = game.image.crop_to_ratio(width / height, fit=globals.settings.fit_images) + imgui.set_cursor_pos((img_pos_x, img_pos_y)) + game.image.render(width, height, *crop, rounding=self.scaled(globals.settings.style_corner_radius)) + + if game_i != len(globals.updated_games_sorted_ids) - 1: + imgui.text("\n") + imgui.unindent(indent) + imgui.pop_text_wrap_pos() + opened, closed = utils.popup( + f"{len(globals.updated_games_sorted_ids)} update{'' if len(globals.updated_games_sorted_ids) == 1 else 's'}", + popup_content, + buttons=True, + closable=True, + outside=False, + popup_uuid=popup_uuid + ) + if closed: + globals.updated_games_sorted_ids.clear() + globals.updated_games.clear() + return opened, closed + + def draw_game_image_error(self, game: Game, width: float, height: float): + if game.image.error == "Image file missing": + text = "Image missing!" + if game.custom: + hover_text = "Right click in More Info popup to add an image." + else: + hover_text = ( + "This image link blocks us! You can blame Imgur." if game.image_url == "blocked" else + "This thread does not seem to have an image!" if game.image_url == "missing" else + "This image link cannot be reached anymore!" if game.image_url == "dead" else + "Run a refresh/recheck to try downloading it again!" + ) + else: + text = "Image error!" + hover_text = game.image.error or "Unknown error" + + text_size = imgui.calc_text_size(text) + if text_size.x >= width: + hover_text = f"{text}\n{hover_text}" + text = "( ! )" + text_size = imgui.calc_text_size(text) + + pos = imgui.get_cursor_pos() + imgui.set_cursor_pos((pos.x + (width - text_size.x) / 2, pos.y + (height - text_size.y) / 2)) + self.draw_hover_text(text=text, hover_text=hover_text) + imgui.set_cursor_pos(pos) + imgui.dummy(width, height) + + def draw_game_info_popup(self, game: Game, carousel_ids: list = None, popup_uuid: str = ""): + def popup_content(): + # Image + imgui.indent(imgui.style.scrollbar_size) + image = game.image + avail = imgui.get_content_region_available() + if imgui.get_scroll_max_y() <= 0.0: + # Avoid shifing content with/without scroll bar + avail = avail._replace(x=avail.x - imgui.style.scrollbar_size) + close_image = False + zoom_popup = False + rounding = self.scaled(globals.settings.style_corner_radius) + out_height = (min(avail.y, self.scaled(690)) * self.scaled(0.4)) or 1 + out_width = avail.x or 1 + if image.error: + self.draw_game_image_error(game, out_width, out_height) + else: + aspect_ratio = image.height / image.width + if aspect_ratio > (out_height / out_width): + height = out_height + width = height * (1 / aspect_ratio) + margin_x = (out_width - width) / 2 + margin_y = 0 + else: + width = out_width + height = width * aspect_ratio + margin_x = 0 + margin_y = (out_height - height) / 2 + prev_pos = imgui.get_cursor_pos() + imgui.set_cursor_pos((prev_pos.x + margin_x, prev_pos.y + margin_y)) + image_pos = imgui.get_cursor_screen_pos() + + imgui.begin_child("###image_zoomer", width=width, height=height + 1.0, flags=imgui.WINDOW_NO_SCROLLBAR) + imgui.dummy(width + 2.0, height) + imgui.set_scroll_x(1.0) + imgui.set_cursor_screen_pos(image_pos) + image.render(width, height, rounding=rounding) + if imgui.is_item_hovered(): + # Image popup + if imgui.is_mouse_down(): + size = imgui.io.display_size + if aspect_ratio > size.y / size.x: + height = size.y - self.scaled(10) + width = height / aspect_ratio + else: + width = size.x - self.scaled(10) + height = width * aspect_ratio + x = (size.x - width) / 2 + y = (size.y - height) / 2 + flags = imgui.DRAW_ROUND_CORNERS_ALL + pos2 = (x + width, y + height) + fg_draw_list = imgui.get_foreground_draw_list() + fg_draw_list.add_image_rounded(image.texture_id, (x, y), pos2, rounding=rounding, flags=flags) + # Zoom + elif globals.settings.zoom_enabled: + if int(imgui.get_scroll_x() - 1.0): + if globals.settings.scroll_smooth: + diff = imgui.io.delta_time * self.scroll_energy * 30 + else: + diff = imgui.io.mouse_wheel * 3 + if imgui.is_key_down(glfw.KEY_LEFT_ALT): + globals.settings.zoom_area = min(max(globals.settings.zoom_area + diff, 1), 500) + else: + globals.settings.zoom_times = min(max(globals.settings.zoom_times * (diff / 50.0 + 1.0), 1), 20) + zoom_popup = True + out_size = min(*imgui.io.display_size) * globals.settings.zoom_area / 100 + in_size = out_size / globals.settings.zoom_times + mouse_pos = imgui.io.mouse_pos + off_x = utils.map_range(in_size, 0.0, width, 0.0, 1.0) / 2.0 + off_y = utils.map_range(in_size, 0.0, height, 0.0, 1.0) / 2.0 + x = utils.map_range(mouse_pos.x, image_pos.x, image_pos.x + width, 0.0, 1.0) + y = utils.map_range(mouse_pos.y, image_pos.y, image_pos.y + height, 0.0, 1.0) + imgui.set_next_window_position(*mouse_pos, pivot_x=0.5, pivot_y=0.5) + imgui.begin_tooltip() + image.render(out_size, out_size, (x - off_x, y - off_y), (x + off_x, y + off_y), rounding=rounding) + imgui.end_tooltip() + close_image = True + if imgui.begin_popup_context_item("###image_context"): + if imgui.selectable(f"{icons.folder_open_outline} Set custom image", False)[0]: + def select_callback(selected): + if selected: + game.image_url = "custom" + game.set_image_sync(pathlib.Path(selected).read_bytes()) + utils.push_popup(filepicker.FilePicker( + title=f"Select or drop image for {game.name}", + callback=select_callback + ).tick) + if imgui.selectable(f"{icons.trash_can_outline} Reset image", False)[0]: + game.delete_images() + game.refresh_image() + imgui.end_popup() + if close_image: + imgui.end_child() + imgui.set_cursor_pos(prev_pos) + imgui.dummy(out_width, out_height) + imgui.unindent(imgui.style.scrollbar_size) + + # The indexer stores these URLs separately from the cover image. + # Load them lazily so opening an info popup does not slow startup + # or download images for games the user never inspects. + if game.previews_urls: + if not game.previews_loaded and not game.previews_loading: + game.preview_load_future = async_thread.run(game.load_previews_async()) + if game.previews_loading: + download_count = api.images_counter.count + loading_text = ( + f" · Downloading {download_count} image" + f"{'s' if download_count != 1 else ''}" + if download_count else " · Loading..." + ) + else: + loading_text = "" + imgui.text(f"Previews ({len(game.preview_images)}/{len(game.previews_urls)}){loading_text}") + if game.preview_images: + # Keep each preview at a useful thumbnail size and put + # the row in a child with an explicit horizontal bar. + # Without a child, ImGui clips same-line items at the + # popup boundary and the parent only scrolls vertically. + preview_width = max(self.scaled(240), (out_width - imgui.style.item_spacing.x) / 2) + preview_height = self.scaled(260) + horizontal_flags = ( + getattr(imgui, "WINDOW_HORIZONTAL_SCROLLING_BAR", 0) | + getattr(imgui, "WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR", 0) + ) + imgui.begin_child( + "###game_previews_gallery", + width=out_width, + height=preview_height + imgui.get_frame_height() + imgui.style.item_spacing.y, + flags=horizontal_flags, + ) + first = True + for preview in game.preview_images: + aspect = preview.height / preview.width if preview.width else 1.0 + height = min(preview_height, preview_width * aspect) + if not first: + imgui.same_line() + preview_pos = imgui.get_cursor_pos() + preview.render(preview_width, height, rounding=rounding) + if preview.error: + # Keep failed previews in the gallery so users + # can see that an item failed rather than assuming + # it was silently omitted. + preview_hovered = imgui.is_item_hovered() + imgui.set_cursor_pos(preview_pos) + imgui.text_wrapped("Preview unavailable") + imgui.set_cursor_pos((preview_pos.x, preview_pos.y + height)) + if preview_hovered: + imgui.set_tooltip(preview.error) + first = False + imgui.end_child() + imgui.push_text_wrap_pos() + + imgui.push_font(imgui.fonts.big) + self.draw_game_name_text(game) + imgui.pop_font() + + self.draw_game_play_button(game, f"{icons.play} Play") + imgui.same_line() + self.draw_game_open_thread_button(game, f"{icons.open_in_new} Thread") + imgui.same_line() + self.draw_game_copy_link_button(game, f"{icons.content_copy} Link") + imgui.same_line() + self.draw_game_id_button(game, f"{icons.pound} ID") + imgui.same_line() + self.draw_game_finished_checkbox(game, f"{icons.flag_checkered} Finished") + _10 = self.scaled(10) + imgui.same_line(spacing=_10) + self.draw_game_installed_checkbox(game, f"{icons.download} Installed") + imgui.same_line(spacing=_10) + self.draw_game_recheck_button(game, f"{icons.reload_alert} Recheck") + imgui.same_line() + self.draw_game_archive_button(game, label_off=f"{icons.archive_outline} Archive", label_on=f"{icons.archive_off_outline} Unarchive") + imgui.same_line() + self.draw_game_remove_button(game, f"{icons.trash_can_outline} Remove") + + # Minimum width to avoid content shifting around and clipping (like Archive/Unarchive) + imgui.dummy(self.scaled(790), 0) + + if imgui.begin_table(f"###details", column=2, outer_size_width=avail.x): + imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) + imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) + + imgui.table_next_row() + + imgui.table_next_column() + imgui.text_disabled("Version:") + imgui.same_line() + if game.updated: + self.draw_game_update_icon(game) + imgui.same_line() + if game.unknown_tags_flag: + self.draw_game_unknown_tags_icon(game) + imgui.same_line() + imgui.text(game.version) + + imgui.table_next_column() + imgui.text_disabled("Added On:") + imgui.same_line() + imgui.text(game.added_on.display) + + imgui.table_next_row() + + imgui.table_next_column() + imgui.text_disabled("Developer:") + imgui.same_line() + imgui.text(game.developer) + + imgui.table_next_column() + imgui.text_disabled("Last Updated:") + imgui.same_line() + imgui.text(game.last_updated.display or "Unknown") + + imgui.table_next_row() + + imgui.table_next_column() + imgui.text_disabled("Status:") + imgui.same_line() + imgui.text(game.status.name) + imgui.same_line() + self.draw_status_widget(game.status) + + imgui.table_next_column() + imgui.text_disabled("Last Launched:") + imgui.same_line() + imgui.text(game.last_launched.display or "Never") + if imgui.is_item_clicked(): + game.last_launched = time.time() + game.add_timeline_event(TimelineEventType.GameLaunched, "date set manually") + self.draw_hover_text("Click to set as launched right now!", text=None) + + imgui.table_next_row() + + imgui.table_next_column() + imgui.text_disabled("Forum Score:") + imgui.same_line() + imgui.text(f"{game.score:.1f}/5") + imgui.same_line() + imgui.text_disabled(f"({game.votes})") + + imgui.table_next_column() + imgui.text_disabled("Playtime:") + imgui.same_line() + imgui.text(game.playtime_display or "None") + + imgui.table_next_row() + + imgui.table_next_column() + imgui.text_disabled("Type:") + imgui.same_line() + self.draw_type_widget(game.type) + + imgui.table_next_column() + imgui.text_disabled("Personal Rating:") + imgui.same_line() + self.draw_game_rating_widget(game) + + imgui.table_next_row() + + imgui.table_next_column() + imgui.align_text_to_frame_padding() + imgui.text_disabled("Executables:") + imgui.same_line() + self.draw_game_add_exe_button(game, f"{icons.folder_edit_outline} Add") + imgui.same_line() + self.draw_game_open_folder_button(game, f"{icons.folder_open_outline} Open Folder") + imgui.same_line() + self.draw_game_clear_exes_button(game, f"{icons.folder_remove_outline} Clear") + + imgui.table_next_column() + imgui.text_disabled("Tab:") + imgui.same_line() + self.draw_game_tab_widget(game) + + imgui.table_next_row() + + # Draw labels on right first, so executables on left can then overflow below to right + imgui.table_set_column_index(1) + imgui.begin_group() + imgui.align_text_to_frame_padding() + imgui.text_disabled("Labels:") + imgui.same_line() + if game.labels: + self.draw_game_labels_widget(game) + else: + imgui.button("Right click to add") + imgui.end_group() + labels_end_y = imgui.get_cursor_pos_y() + if imgui.begin_popup_context_item(f"###{game.id}_context_labels"): + self.draw_game_labels_select_widget(game) + imgui.end_popup() + + imgui.table_set_column_index(0) + imgui.align_text_to_frame_padding() + imgui.text_disabled("Exe Wrapper:") + imgui.same_line() + imgui.set_next_item_width(-1) + changed, value = imgui.input_text_with_hint( + f"###{game.id}_launch_wrapper", + f"Inherit default for {game.type.name}", + game.launch_wrapper.get(globals.os, "") + ) + if changed: + game.launch_wrapper[globals.os] = value + async_thread.run(db.update_game(game, "launch_wrapper")) + if imgui.begin_popup_context_item(f"###{game.id}_launch_wrapper_context"): + utils.text_context(game, "launch_wrapper", no_icons=True) + imgui.end_popup() + self.draw_hover_text( + "A custom command to wrap the executable path into. Useful for specifying custom ways to open it, like Wine/Proton for Windows games on Linux/macOS or a media player for animation/comic collections.\n" + "Can also be used to specify custom arguments, to the exe or to a wrapper.\n" + "\n" + "Use %command% to substitute in the executable path, for example:\n" + "- VLC.exe -LZ %command%\n" + "- %command% -dx12\n" + "- env WINEPREFIX=/path/to/prefix wine %command%\n" + "\n" + "This is a game-specific override. You can setup default wrappers in Settings > Manage > Exe Wrappers, which will also auto-detect Wine/Proton runners on Linux/macOS.", + text=None, + ) + ended_table = False + for executable in game.executables: + if not ended_table and (pos_y := imgui.get_cursor_pos_y()) >= labels_end_y: + imgui.end_table() + ended_table = True + imgui.set_cursor_pos_y(pos_y) + self.draw_game_play_button(game, icons.play, executable=executable) + imgui.same_line() + self.draw_game_open_folder_button(game, icons.folder_open_outline, executable=executable) + imgui.same_line() + if imgui.button(icons.folder_remove_outline): + game.remove_executable(executable) + imgui.same_line() + # Hack: make ImGui wrap arbitrarily by using an idegraphic space (U+3000) with 0-width font + ig_space = " " + imgui.text(executable.replace("/", f"/{ig_space}").replace("\\", f"\\{ig_space}")) + + if not ended_table: + pos_y = max(imgui.get_cursor_pos_y(), labels_end_y) + imgui.end_table() + imgui.set_cursor_pos_y(pos_y) + + imgui.spacing() + imgui.spacing() + + if imgui.begin_tab_bar("Details"): + + if imgui.begin_tab_item(( + icons.clipboard_text_outline if game.changelog else + icons.clipboard_text_off_outline + ) + " Changelog###changelog")[0]: + imgui.spacing() + if game.changelog: + imgui.text(game.changelog) + else: + imgui.text_disabled("Either this game doesn't have a changelog, or the thread is not formatted properly!") + imgui.end_tab_item() + + if imgui.begin_tab_item(( + icons.information_outline if game.description else + icons.information_off_outline + ) + " Description###description")[0]: + imgui.spacing() + if game.description: + imgui.text(game.description) + else: + imgui.text_disabled("Either this game doesn't have a description, or the thread is not formatted properly!") + imgui.end_tab_item() + + if not game.custom and imgui.begin_tab_item(icons.tray_arrow_down + " Downloads###downloads")[0]: + imgui.spacing() + imgui.text("RPDL Torrents:") + imgui.same_line() + if imgui.small_button(f"{icons.magnify}Search"): + rpdl.open_search_popup(game) + imgui.spacing() + imgui.text("F95zone Donor DDL:") + imgui.same_line() + if imgui.small_button(f"{icons.cloud_check_variant_outline}Check"): + api.open_ddl_popup(game) + imgui.spacing() + imgui.spacing() + imgui.spacing() + imgui.text("Regular Downloads:") + imgui.same_line() + self.draw_hover_text( + "Left clicking opens the webpage in your chosen browser.\n" + "Middle clicking copies the link to your clipboard.\n\n" + "There's 3 types of links that work slightly different:\n" + f"{icons.link}Direct: Plain download link, retrieved with automated integrated browser\n" + f"{icons.domino_mask}Masked: CAPTCHA protected link, unmasked with automated integrated browser\n" + f"{icons.open_in_app}Forum: Link internal to F95zone, no special processing\n" + ) + imgui.spacing() + if game.downloads: + can_add_spacing = False + _20 = self.scaled(20) + for name, mirrors in game.downloads: + if mirrors: + can_add_spacing = True + imgui.text(name + ":") + for mirror, link in mirrors: + imgui.same_line() + if imgui.get_content_region_available_width() < imgui.calc_text_size(icons.link + mirror).x + _20: + imgui.dummy(0, 0) + if not link: + # Use thread url when link is missing + link = game.url + if link.startswith("//"): + # XPath expression + if imgui.small_button(icons.link + mirror): + callbacks.redirect_xpath_link(game.url, link) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + callbacks.redirect_xpath_link(game.url, link, copy=True) + elif link.startswith(f"{api.f95_host}/masked/"): + # Masked link + if imgui.small_button(icons.domino_mask + mirror): + callbacks.redirect_masked_link(link) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + callbacks.redirect_masked_link(link, copy=True) + elif link.startswith(api.f95_host): + # F95zone link + if imgui.small_button(icons.open_in_app + mirror): + callbacks.open_webpage(link) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + callbacks.clipboard_copy(link) + else: + # Should never happen, but here for backwards compatibility + if imgui.small_button(icons.link + mirror): + callbacks.open_webpage(link) + if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): + callbacks.clipboard_copy(link) + else: + if can_add_spacing: + imgui.text("") + imgui.text(name) + imgui.spacing() + imgui.spacing() + can_add_spacing = False + else: + imgui.text_disabled("Either this game doesn't have regular downloads, or the thread is not formatted properly!") + imgui.end_tab_item() + + if imgui.begin_tab_item(( + icons.draw_pen if game.notes else + icons.pencil_plus_outline + ) + " Notes###notes")[0]: + imgui.spacing() + self.draw_game_notes_widget(game) + imgui.end_tab_item() + + if not game.custom and imgui.begin_tab_item(( + icons.star_outline if game.reviews else + icons.star_off_outline + ) + " Reviews###reviews")[0]: + imgui.spacing() + if game.reviews: + self.draw_game_reviews_widget(game) + else: + imgui.text_disabled("This game doesn't have any reviews yet!") + imgui.end_tab_item() + + if imgui.begin_tab_item(( + icons.tag_multiple_outline if len(game.tags) > 1 else + icons.tag_outline if len(game.tags) == 1 else + icons.tag_off_outline + ) + " Tags###tags")[0]: + imgui.spacing() + if game.tags: + self.draw_game_tags_widget(game) + else: + imgui.text_disabled("This game has no tags!") + if utags := game.unknown_tags: + imgui.spacing() + if imgui.tree_node(f"Unknown tags ({len(utags)})"): + if imgui.button(f"{icons.tag_multiple_outline} Copy"): + callbacks.clipboard_copy(", ".join(utags)) + for tag in utags: + imgui.text(f"- {tag}") + imgui.tree_pop() + imgui.end_tab_item() + + if imgui.begin_tab_item(icons.timeline_clock_outline + " Timeline###timeline")[0]: + imgui.spacing() + self.draw_game_timeline_widget(game) + imgui.end_tab_item() + + if imgui.begin_tab_item(( + icons.puzzle_check_outline if game.custom else + icons.puzzle_remove_outline + ) + " Custom###custom")[0]: + if game.custom: + imgui.text( + "This is a custom game. You can edit all its details below (except downloads). " + "If you wish to convert it back to a F95zone game, then make sure to fill the " + "game url with a valid F95zone thread url before pressing the button below." + ) + if imgui.button(f"{icons.puzzle_remove} Convert to F95zone game"): + callbacks.convert_custom_to_f95zone(game) + imgui.text("") + imgui.push_item_width(-imgui.FLOAT_MIN) + pos_x = imgui.get_cursor_pos_x() + imgui.calc_text_size("Description:").x + + imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("URL:").x) + imgui.align_text_to_frame_padding() + imgui.text("URL:") + imgui.same_line() + changed, value = imgui.input_text("###url", game.url) + if changed: + game.url = value + if imgui.begin_popup_context_item("###url_context"): + utils.text_context(game, "url", no_icons=True) + imgui.end_popup() + + imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Name:").x) + imgui.align_text_to_frame_padding() + imgui.text("Name:") + imgui.same_line() + imgui.set_next_item_width(imgui.get_content_region_available_width() - self.scaled(120)) + changed, value = imgui.input_text("###name", game.name) + if changed: + game.name = value + if imgui.begin_popup_context_item("###name_context"): + utils.text_context(game, "name", no_icons=True) + imgui.end_popup() + imgui.same_line(spacing=imgui.style.item_spacing.x * 2) + imgui.text("Score:") + imgui.same_line() + changed, value = imgui.drag_float("###score", game.score, change_speed=0.01, min_value=0, max_value=5, format="%.1f/5") + if changed: + game.score = value + + imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Version:").x) + imgui.align_text_to_frame_padding() + imgui.text("Version:") + imgui.same_line() + imgui.set_next_item_width(imgui.get_content_region_available_width() / 3.5) + changed, value = imgui.input_text("###version", game.version) + if changed: + game.version = value or "N/A" + if imgui.begin_popup_context_item("###version_context"): + utils.text_context(game, "version", no_icons=True) + imgui.end_popup() + imgui.same_line(spacing=imgui.style.item_spacing.x * 2) + imgui.text("Developer:") + imgui.same_line() + imgui.set_next_item_width(imgui.get_content_region_available_width() / 2.1) + changed, value = imgui.input_text("###developer", game.developer) + if changed: + game.developer = value + if imgui.begin_popup_context_item("###developer_context"): + utils.text_context(game, "developer", no_icons=True) + imgui.end_popup() + imgui.same_line(spacing=imgui.style.item_spacing.x * 2) + imgui.text("Status:") + imgui.same_line() + if imgui.begin_combo("###status", game.status.name): + for status in Status: + selected = status is game.status + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###status_{status.value}", selected)[0]: + game.status = status + if selected: + imgui.set_item_default_focus() + imgui.set_cursor_pos(pos) + self.draw_status_widget(status) + imgui.same_line() + imgui.text(status.name) + imgui.end_combo() + + imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Tags:").x) + imgui.align_text_to_frame_padding() + imgui.text("Tags:") + imgui.same_line() + imgui.button("Right click") + if imgui.begin_popup_context_item("###tags_context"): + self.draw_game_tags_select_widget(game) + imgui.end_popup() + imgui.same_line(spacing=imgui.style.item_spacing.x * 2) + imgui.text("Updated:") + imgui.same_line() + changed, value = imgui.checkbox("###updated", game.updated) + if changed: + game.updated = value + imgui.same_line(spacing=imgui.style.item_spacing.x * 2) + imgui.text("Last Updated:") + date = dt.datetime.fromtimestamp(game.last_updated.value) + day, month, year = date.day, date.month, date.year + frame_height = imgui.get_frame_height() + imgui.same_line() + imgui.set_next_item_width(1.5 * frame_height) + day = min(max(imgui.drag_int("###last_updated_day", day, change_speed=0.05, min_value=1, max_value=31)[1], 1), 31) + imgui.same_line() + imgui.set_next_item_width(1.5 * frame_height) + month = min(max(imgui.drag_int("###last_updated_month", month, change_speed=0.03, min_value=1, max_value=12)[1], 1), 12) + imgui.same_line() + imgui.set_next_item_width(2 * frame_height) + year = min(max(imgui.drag_int("###last_updated_year", year, change_speed=0.05, min_value=1970, max_value=9999)[1], 1970), 9999) + if day != date.day or month != date.month or year != date.year: + for _ in range(5): + try: + date = date.replace(day=day, month=month, year=year) + game.last_updated = parser.datestamp(date.timestamp()) + except (ValueError, OSError): + day -= 1 + continue + break + imgui.same_line(spacing=imgui.style.item_spacing.x * 2) + imgui.text("Type:") + imgui.same_line() + if imgui.begin_combo("###type", game.type.name): + category = None + for type in Type: + if category is not type.category: + category = type.category + imgui.text(category.name) + selected = type is game.type + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###type_{type.value}", selected)[0]: + game.type = type + if selected: + imgui.set_item_default_focus() + imgui.set_cursor_pos(pos) + self.draw_type_widget(type) + imgui.end_combo() + + imgui.align_text_to_frame_padding() + imgui.text("Description:") + imgui.same_line() + changed, value = imgui.input_text_multiline("###description", value=game.description) + if changed: + game.description = value + if imgui.begin_popup_context_item("###description_context"): + utils.text_context(game, "description") + imgui.end_popup() + + imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Changelog:").x) + imgui.align_text_to_frame_padding() + imgui.text("Changelog:") + imgui.same_line() + changed, value = imgui.input_text_multiline("###changelog", value=game.changelog) + if changed: + game.changelog = value + if imgui.begin_popup_context_item("###changelog_context"): + utils.text_context(game, "changelog") + imgui.end_popup() + + imgui.pop_item_width() + else: + imgui.text( + "Here you have the option to convert this game to a custom game. Custom games are not checked for updates and become untied from F95zone. " + "This is useful for games that have been removed for breaking forum rules, or for adding games from other platforms to your library. Custom " + "games allow you to edit all their details (except downloads) that would normally be fetched from F95zone." + ) + if imgui.button(f"{icons.puzzle_check} Convert to custom game"): + callbacks.convert_f95zone_to_custom(game) + imgui.end_tab_item() + + imgui.end_tab_bar() + imgui.pop_text_wrap_pos() + change_id = None + if carousel_ids and len(carousel_ids) > 1 and game.id in carousel_ids and imgui.is_topmost() and not imgui.is_any_item_active(): + # Has and is in carousel ids, is not the only one in them, is topmost popup and no item is active + pos = imgui.get_window_position() + size = imgui.get_window_size() + if size and pos: + imgui.push_font(imgui.fonts.big) + text_size = imgui.calc_text_size(icons.arrow_left_drop_circle) + offset = self.scaled(10) + mouse_pos = imgui.get_mouse_pos() + mouse_clicked = imgui.is_mouse_clicked() + y = pos.y + (size.y + text_size.y) / 2 + x1 = pos.x - offset - text_size.x + x2 = pos.x + size.x + offset + if not zoom_popup: + draw_list = imgui.get_foreground_draw_list() + col = imgui.get_color_u32_rgba(*globals.settings.style_text_dim) + draw_list.add_text(x1, y, col, icons.arrow_left_drop_circle) + draw_list.add_text(x2, y, col, icons.arrow_right_drop_circle) + y_ok = y <= mouse_pos.y <= y + text_size.y + clicked_left = mouse_clicked and x1 <= mouse_pos.x <= x1 + text_size.x and y_ok + clicked_right = mouse_clicked and x2 <= mouse_pos.x <= x2 + text_size.x and y_ok + imgui.pop_font() + idx = carousel_ids.index(game.id) + if imgui.is_key_pressed(glfw.KEY_LEFT, repeat=True) or clicked_left: + idx -= 1 + if idx == -1: + idx = len(carousel_ids) - 1 + change_id = carousel_ids[idx] + if imgui.is_key_pressed(glfw.KEY_RIGHT, repeat=True) or clicked_right: + idx += 1 + if idx == len(carousel_ids): + idx = 0 + change_id = carousel_ids[idx] + if change_id is not None: + # Swiping to another game closes this popup's gallery. Keep + # the downloaded files, but release decoded/GPU image data. + game.cancel_preview_loading() + game.unload_previews() + utils.push_popup(self.draw_game_info_popup, globals.games[change_id], carousel_ids).uuid = popup_uuid + return True + elif utils.close_weak_popup(): + return True + if game.id not in globals.games: + game.cancel_preview_loading() + return 0, True + result = utils.popup(game.name, popup_content, closable=True, outside=False, resize=False, popup_uuid=popup_uuid) + if result[1]: + # A closed info popup is no longer a visible owner of its + # textures. The URL cache stays on disk for cheap reopening. + game.cancel_preview_loading() + game.unload_previews() + return result + + def draw_about_popup(self, popup_uuid: str = ""): + def popup_content(): + _60 = self.scaled(60) + _230 = self.scaled(230) + imgui.begin_group() + imgui.dummy(_60, _230) + imgui.same_line() + self.logo_texture.render(_230, _230, rounding=self.scaled(globals.settings.style_corner_radius)) + imgui.same_line() + imgui.begin_group() + imgui.push_font(imgui.fonts.big) + imgui.text("F95Checker") + imgui.pop_font() + imgui.text(f"Version {globals.version_name}") + imgui.text("Made with <3 by WillyJL") + imgui.text("") + imgui.text(f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") + imgui.text(f"OpenGL {gl.glGetInteger(gl.GL_MAJOR_VERSION)}.{gl.glGetInteger(gl.GL_MINOR_VERSION)}, Py {OpenGL.__version__}") + imgui.text(f"GLFW {'.'.join(str(num) for num in glfw.get_version())}, Py {glfw.__version__}") + imgui.text(f"ImGui {imgui.get_version()}, Py {imgui.__version__}") + imgui.text(f"Qt {QtCore.QT_VERSION_STR}, Py {QtCore.PYQT_VERSION_STR}") + if globals.os is Os.Linux: + imgui.text(f"{platform.system()} {platform.release()}") + elif globals.os is Os.Windows: + imgui.text(f"{platform.system()} {platform.release()} {platform.version()}") + elif globals.os is Os.MacOS: + imgui.text(f"{platform.system()} {platform.release()}") + imgui.end_group() + imgui.same_line() + imgui.dummy(_60, _230) + imgui.end_group() + imgui.spacing() + width = imgui.get_content_region_available_width() + btn_width = (width - 2 * imgui.style.item_spacing.x) / 3 + if imgui.button(f"{icons.open_in_new} F95zone Thread", width=btn_width): + callbacks.open_webpage(tool_page) + imgui.same_line() + if imgui.button(f"{icons.github} GitHub Repo", width=btn_width): + callbacks.open_webpage(github_page) + imgui.same_line() + if imgui.button(f"{icons.link_variant} Donate + Links", width=btn_width): + callbacks.open_webpage(developer_page) + imgui.spacing() + imgui.spacing() + imgui.push_text_wrap_pos(width) + imgui.text("This software is licensed under the 3rd revision of the GNU General Public License (GPLv3) and is provided to you for free. " + "Furthermore, due to its license, it is also free as in freedom: you are free to use, study, modify and share this software " + "in whatever way you wish as long as you keep the same license.") + imgui.spacing() + imgui.spacing() + imgui.text("However, F95Checker is actively developed by one person only, WillyJL, and not with the aim of profit but out of personal " + "interest and benefit for the whole F95zone community. Donations are although greatly appreciated and aid the development " + "of this software. You can find donation links above.") + imgui.spacing() + imgui.spacing() + imgui.text("If you find bugs or have some feedback, don't be afraid to let me know either on GitHub (using issues or pull requests) " + "or on F95zone (in the thread comments or in direct messages).") + imgui.spacing() + imgui.spacing() + imgui.text("Please note that this software is not ( yet ;) ) officially affiliated with the F95zone platform.") + imgui.spacing() + imgui.spacing() + imgui.text("") + imgui.push_font(imgui.fonts.big) + size = imgui.calc_text_size("Cool people") + imgui.set_cursor_pos_x((width - size.x + imgui.style.scrollbar_size) / 2) + imgui.text("Cool people") + imgui.pop_font() + imgui.spacing() + imgui.spacing() + imgui.text("Supporters:") + for name in [ + "FaceCrap", + "WhiteVanDaycare", + "ascsd", + "GioBol", + "Jarulf", + "salkrim", + "rozzic", + "Belfaier", + "warez_gamez", + "DeadMoan", + "And 4 anons" + ]: + if imgui.get_content_region_available_width() < imgui.calc_text_size(name).x + self.scaled(20): + imgui.dummy(0, 0) + imgui.bullet_text(name) + imgui.same_line(spacing=16) + imgui.spacing() + imgui.spacing() + imgui.text("Contributors:") + imgui.bullet() + imgui.text("r37r05p3C7: Tab idea and customization, timeline, many extension features") + imgui.bullet() + imgui.text("littleraisins: Fixes, features and misc ideas from the (defunct) 'X' fork") + imgui.bullet() + imgui.text("FaceCrap: Multiple small fixes, improvements and finetuning") + imgui.bullet() + imgui.text("blackop: Proxy support, temporary ratelimit fix, linux login fix") + imgui.bullet() + imgui.text("cicklolwut: Security fixes, Linux wine/proton config, playtime stats") + imgui.bullet() + imgui.text("Sam: Support from F95zone side to make much this possible") + imgui.bullet() + imgui.text("GR3ee3N: Optimized build workflows and other PRs") + imgui.bullet() + imgui.text("batblue: MacOS suppport and feedback guy") + imgui.bullet() + imgui.text("unroot: Linux support and feedback guy") + imgui.bullet() + imgui.text("ploper26: Suggested HEAD checks (no longer used)") + imgui.bullet() + imgui.text("ascsd: Helped with brainstorming on some issues and gave some tips") + imgui.spacing() + imgui.spacing() + imgui.text("Community:") + for name in [ + "abada25", + "AtotehZ", + "bitogno", + "BrockLanders", + "d_pedestrian", + "Danv", + "DarK x Duke", + "Dukez", + "GrammerCop", + "harem.king", + "MillenniumEarl", + "simple_human", + "SmurfyBlue", + "WhiteVanDaycare", + "yohudood", + "And others that I might be forgetting" + ]: + if imgui.get_content_region_available_width() < imgui.calc_text_size(name).x + self.scaled(20): + imgui.dummy(0, 0) + imgui.bullet_text(name) + imgui.same_line(spacing=16) + imgui.pop_text_wrap_pos() + return utils.popup("About F95Checker", popup_content, closable=True, outside=True, popup_uuid=popup_uuid) + + def draw_tag_highlights_popup(self, popup_uuid: str = ""): + def popup_content(): + imgui.text_disabled("Right click tags to cycle highlighting options") + search = "" + imgui.set_next_item_width(-imgui.FLOAT_MIN) + _, search = imgui.input_text_with_hint(f"###tag_highlights_input", "Search tags...", search) + imgui.begin_child(f"###tag_highlights_frame", width=min(imgui.io.display_size.x * 0.5, self.scaled(600)), height=imgui.io.display_size.y * 0.5) + pad = 2 * imgui.style.frame_padding.x + imgui.style.item_spacing.x + for tag in Tag: + if not search or search in tag.text or search in tag.name: + if imgui.get_content_region_available_width() < imgui.calc_text_size(tag.name).x + pad: + imgui.dummy(0, 0) + self.draw_tag_widget(tag, quick_filter=False) + imgui.same_line() + imgui.end_child() + return utils.popup("Tag highlight preferences", popup_content, closable=True, outside=True, popup_uuid=popup_uuid) + + def draw_tabbar(self): + display_tab = globals.settings.display_tab + select_tab = self.current_tab is not display_tab + if not self.sorts: + # Fixes incorrect tab for first frame, causing un-shown images to load + # Sorting would happen later otherwise and tabs would be hidden due to that + self.current_tab = display_tab + self.tick_list_columns() + save_new_tab = False + elif self.dragging_tab: + # Keep current tab while resetting tabbar due to dragging + save_new_tab = False + select_tab = True + else: + save_new_tab = True + new_tab = None + if Tab.instances and not (globals.settings.filter_all_tabs and self.filtering): + if imgui.begin_tab_bar( + f"###tabbar_{','.join(str(tab.id) for tab in Tab.instances)}", + flags=self.tabbar_flags + ): + hide = globals.settings.hide_empty_tabs + count = len(self.show_games_ids.get(None, ())) + if (count or not hide) and imgui.begin_tab_item( + f"{Tab.first_tab_label()} ({count})###tab_-1", + flags=imgui.TAB_ITEM_NONE + )[0]: + new_tab = None + imgui.end_tab_item() + swap = None + for tab_i, tab in enumerate(Tab.instances): + count = len(self.show_games_ids.get(tab, ())) + if hide and not count: + continue + if tab.color: + imgui.push_style_color(imgui.COLOR_TAB, *tab.color[:3], 0.5) + imgui.push_style_color(imgui.COLOR_TAB_ACTIVE, *tab.color) + imgui.push_style_color(imgui.COLOR_TAB_HOVERED, *tab.color) + imgui.push_style_color(imgui.COLOR_TEXT, *colors.foreground_color(tab.color)) + if imgui.begin_tab_item( + f"{tab.icon} {tab.name or 'New Tab'} ({count})###tab_{tab.id}", + flags=imgui.TAB_ITEM_SET_SELECTED if select_tab and tab is display_tab else 0 + )[0]: + new_tab = tab + imgui.end_tab_item() + if tab.color: + imgui.pop_style_color(4) + if self.dragging_tab is tab and imgui.is_mouse_released(): + self.dragging_tab = None + elif imgui.is_item_active() or self.dragging_tab is tab: + mouse_pos = imgui.get_mouse_pos() + if tab_i > 0 and imgui.get_item_rect_min().x > 0 and mouse_pos.x < imgui.get_item_rect_min().x: + if imgui.get_mouse_drag_delta().x < 0: + self.dragging_tab = tab + swap = (tab_i, tab_i - 1) + imgui.reset_mouse_drag_delta() + elif tab_i < len(Tab.instances) - 1 and imgui.get_item_rect_max().x > 0 and mouse_pos.x > imgui.get_item_rect_max().x: + if imgui.get_mouse_drag_delta().x > 0: + self.dragging_tab = tab + swap = (tab_i, tab_i + 1) + imgui.reset_mouse_drag_delta() + context_id = f"###tab_{tab.id}_context" + set_focus = not imgui.is_popup_open(context_id) + if imgui.begin_popup_context_item(context_id): + imgui.set_next_item_width(imgui.get_content_region_available_width()) + if set_focus: + imgui.set_keyboard_focus_here() + changed, value = imgui.input_text_with_hint(f"###tab_name_{tab.id}", "Tab name", tab.name) + setter_extra = functools.partial(lambda t, _=None: async_thread.run(db.update_tab(t, "name")), tab) + if changed: + tab.name = value + setter_extra() + if imgui.begin_popup_context_item(f"###tab_name_{tab.id}_context"): + utils.text_context(tab, "name", setter_extra) + imgui.end_popup() + set_focus = False + if imgui.button(tab.icon): + imgui.open_popup(f"###tab_icon_{tab.id}") + set_focus = True + if imgui.begin_popup(f"###tab_icon_{tab.id}"): + search = "" + imgui.set_next_item_width(-imgui.FLOAT_MIN) + if set_focus: + imgui.set_keyboard_focus_here() + _, search = imgui.input_text_with_hint(f"###tab_icons_{tab.id}_search", "Search icons...", search) + imgui.begin_child(f"###tab_icons_{tab.id}_frame", width=self.scaled(250), height=imgui.io.display_size.y * 0.35) + for name, icon in icons.names.items(): + if not search or search in name or search in icon: + if imgui.selectable(f"{icon} {name}")[0]: + tab.icon = icon + async_thread.run(db.update_tab(tab, "icon")) + imgui.close_current_popup() + imgui.end_child() + imgui.end_popup() + imgui.same_line() + if imgui.button("Reset icon", width=imgui.get_content_region_available_width()): + tab.icon = Tab.base_icon() + async_thread.run(db.update_tab(tab, "icon")) + color = tab.color[:3] if tab.color else (0.0, 0.0, 0.0) + changed, value = imgui.color_edit3(f"###tab_color_{tab.id}", *color, flags=imgui.COLOR_EDIT_NO_INPUTS) + if changed: + tab.color = (*value, 1.0) + async_thread.run(db.update_tab(tab, "color")) + imgui.same_line() + if imgui.button("Reset color", width=imgui.get_content_region_available_width()): + tab.color = None + async_thread.run(db.update_tab(tab, "color")) + if imgui.button(f"{icons.trash_can_outline} Close (keeps games)"): + close_callback = functools.partial(lambda t: async_thread.run(db.delete_tab(t)), tab) + if globals.settings.confirm_on_remove: + buttons = { + f"{icons.check} Yes": close_callback, + f"{icons.cancel} No": None + } + utils.push_popup( + msgbox.msgbox, f"Close tab {tab.icon} {tab.name or 'New Tab'}", + "Are you sure you want to close this tab?\n" + f"The games will go back to {Tab.first_tab_label()} tab.", + MsgBox.warn, + buttons + ) + else: + close_callback() + imgui.end_popup() + imgui.end_tab_bar() + if swap: + Tab.instances[swap[0]], Tab.instances[swap[1]] = Tab.instances[swap[1]], Tab.instances[swap[0]] + Tab.update_positions() + async def _update_tab_positions(): + for tab in Tab.instances: + await db.update_tab(tab, "position") + async_thread.run(_update_tab_positions()) + if new_tab is not self.current_tab and save_new_tab: + for game in globals.games.values(): + game.selected = False + self.current_tab = new_tab + self.recalculate_ids = True + globals.settings.display_tab = new_tab + async_thread.run(db.update_settings("display_tab")) + + def calculate_ids(self, table_id: str, sorts: imgui.core._ImGuiTableSortSpecs): + manual_sort = cols.manual_sort.enabled + if manual_sort != self.prev_manual_sort: + self.prev_manual_sort = manual_sort + self.recalculate_ids = True + if self.prev_filters != self.filters: + self.prev_filters = self.filters.copy() + self.recalculate_ids = True + if sorts.specs_dirty: + new_sorts = [] + for sort_spec in sorts.specs: + new_sorts.insert(0, SortSpec(index=sort_spec.column_index, reverse=bool(sort_spec.sort_direction - 1))) + self.sorts[table_id] = new_sorts + sorts.specs_dirty = False + self.recalculate_ids = True + if self.recalculate_ids: + self.recalculate_ids = False + # Pick base ID list + if manual_sort: + changed = False + for id in globals.settings.manual_sort_list.copy(): + if id not in globals.games: + globals.settings.manual_sort_list.remove(id) + changed = True + for id in globals.games: + if id not in globals.settings.manual_sort_list: + globals.settings.manual_sort_list.insert(0, id) + changed = True + if changed: + async_thread.run(db.update_settings("manual_sort_list")) + base_ids = globals.settings.manual_sort_list + else: + base_ids = globals.games.keys() + filtering = False + # Filter globally by filters + for flt in self.filters: + match flt.mode.value: + case FilterMode.Archived.value: + key = lambda game, f: (game.archived is True) + case FilterMode.Custom.value: + key = lambda game, f: (game.custom is True) + case FilterMode.Exe_State.value: + key = lambda game, f: ( + (not game.executables) if f.match is ExeState.Unset else + (bool(game.executables) and (game.executables_valid != (f.match is ExeState.Invalid))) + ) + case FilterMode.Finished.value: + key = lambda game, f: ( + (game.finished != "") if f.match else + (game.finished == (game.installed or game.version)) + ) + case FilterMode.Installed.value: + key = lambda game, f: ( + (game.installed != "") if f.match else + (game.installed == game.version) + ) + case FilterMode.Label.value: + key = lambda game, f: (f.match in game.labels) + case FilterMode.Rating.value: + key = lambda game, f: (game.rating == f.match) + case FilterMode.Score.value: + key = lambda game, f: (game.score >= f.match) + case FilterMode.Status.value: + key = lambda game, f: (game.status is f.match) + case FilterMode.Tag.value: + key = lambda game, f: (f.match in game.tags) + case FilterMode.Type.value: + key = lambda game, f: (game.type is f.match) + case FilterMode.Updated.value: + key = lambda game, f: (game.updated is True) + case _: + key = None + if key is not None: + base_ids = filter(functools.partial(lambda f, k, id: f.invert != k(globals.games[id], f), flt, key), base_ids) + filtering = True + # Filter globally by search + if self.add_box_text: + if self.add_box_valid: + id_matches = [match.id for match in utils.extract_thread_matches(self.add_box_text)] + base_ids = filter(lambda id: id in id_matches, base_ids) + filtering = True + else: + search = self.add_box_text.lower() + def key(id): + game = globals.games[id] + return search in game.version.lower() or search in game.developer.lower() or search in game.name.lower() or search in game.notes.lower() + base_ids = filter(key, base_ids) + filtering = True + self.filtering = filtering + # Finally consume the iterators (was lazy up until now) + base_ids = list(base_ids) + # Sort globally by sortspecs + if not manual_sort: + for sort_spec in self.sorts[table_id]: + match sort_spec.index: + case cols.type.index: + key = lambda id: globals.games[id].type.name + case cols.developer.index: + key = lambda id: globals.games[id].developer.lower() + case cols.playtime.index: + key = lambda id: - globals.games[id].playtime + case cols.last_updated.index: + key = lambda id: - globals.games[id].last_updated.value + case cols.last_launched.index: + key = lambda id: - globals.games[id].last_launched.value + case cols.added_on.index: + key = lambda id: - globals.games[id].added_on.value + case cols.finished.index: + key = lambda id: 2 if not globals.games[id].finished else 1 if globals.games[id].finished == (globals.games[id].installed or globals.games[id].version) else 0 + case cols.installed.index: + key = lambda id: 2 if not globals.games[id].installed else 1 if globals.games[id].installed == globals.games[id].version else 0 + case cols.rating.index: + key = lambda id: - globals.games[id].rating + case cols.notes.index: + key = lambda id: globals.games[id].notes.lower() or "z" + case cols.status_standalone.index: + key = lambda id: globals.games[id].status.value + case cols.score.index: + if globals.settings.weighted_score: + key = lambda id: - utils.bayesian_average(globals.games[id].score, globals.games[id].votes) + else: + key = lambda id: - globals.games[id].score + case _: # Name and all others + key = lambda id: globals.games[id].name.lower() + base_ids.sort(key=key, reverse=sort_spec.reverse) + base_ids.sort(key=lambda id: globals.games[id].archived) + base_ids.sort(key=lambda id: globals.games[id].type is not Type.Unchecked) + # Loop all tabs and filter by them + self.show_games_ids = { + tab: ( + base_ids if filtering and globals.settings.filter_all_tabs else + list(filter(lambda id: tab is globals.games[id].tab, base_ids)) + ) + for tab in (None, *Tab.instances) + } + tab_games_ids = self.show_games_ids[self.current_tab] + # Deselect things that arent't visible anymore + for game in globals.games.values(): + if game.selected and game.id not in tab_games_ids: + game.selected = False + else: + tab_games_ids = self.show_games_ids[self.current_tab] + + def handle_game_hitbox_events(self, game: Game, drag_drop: bool = False): + manual_sort = cols.manual_sort.enabled + if imgui.is_item_hovered(imgui.HOVERED_ALLOW_WHEN_BLOCKED_BY_ACTIVE_ITEM): + # Hover = image on refresh button + self.hovered_game = game + if imgui.is_item_clicked(): + self.game_hitbox_click = True + if self.game_hitbox_click and not imgui.is_mouse_down(): + self.game_hitbox_click = False + if imgui.is_key_down(glfw.KEY_LEFT_SHIFT): + # Shift + Left click = multi select + if self.selected_games_count and self.last_selected_game.selected: + tab_games_ids = self.show_games_ids[self.current_tab] + start = tab_games_ids.index(self.last_selected_game.id) + end = tab_games_ids.index(game.id) + if start > end: + start, end = end, start + for select in tab_games_ids[start:end + 1]: + globals.games[select].selected = True + else: + game.selected = True + elif imgui.is_key_down(glfw.KEY_LEFT_CONTROL): + # Ctrl + Left click = single select + game.selected = not game.selected + else: + if any(game.selected for game in globals.games.values()): + for game in globals.games.values(): + game.selected = False + else: + # Left click = open game info popup + utils.push_popup(self.draw_game_info_popup, game, self.show_games_ids[self.current_tab].copy()) + # Left click drag = swap if in manual sort mode + if imgui.begin_drag_drop_source(flags=self.game_hitbox_drag_drop_flags): + self.game_hitbox_click = False + payload = (globals.settings.manual_sort_list.index(game.id) if manual_sort else 0) + 1 + payload = payload.to_bytes(payload.bit_length(), sys.byteorder) + imgui.set_drag_drop_payload("manual_swap", payload) + imgui.end_drag_drop_source() + if drag_drop and manual_sort: + if imgui.begin_drag_drop_target(): + if payload := imgui.accept_drag_drop_payload("manual_swap", flags=self.game_hitbox_drag_drop_flags): + payload = int.from_bytes(payload, sys.byteorder) + payload = payload - 1 + lst = globals.settings.manual_sort_list + switch = lst.index(game.id) + lst[switch], lst[payload] = lst[payload], lst[switch] + async_thread.run(db.update_settings("manual_sort_list")) + self.recalculate_ids = True + imgui.end_drag_drop_target() + context_id = f"###{game.id}_context" + if (imgui.is_topmost() or imgui.is_popup_open(context_id)) and imgui.begin_popup_context_item(context_id): + # Right click = context menu + if game.selected: + self.draw_game_context_menu() + else: + self.draw_game_context_menu(game) + imgui.end_popup() + + def sync_scroll(self): + if (scroll_max_y := imgui.get_scroll_max_y()) > 1.0: + if self.switched_display_mode: + imgui.set_scroll_y(self.scroll_percent * scroll_max_y) + self.switched_display_mode = False + else: + self.scroll_percent = imgui.get_scroll_y() / scroll_max_y + + def games_table_id(self): + tab_id = self.current_tab.id if self.current_tab else -1 + return f"###game_list{tab_id if globals.settings.independent_tab_views else ''}" + + def draw_games_list(self): + table_id = self.games_table_id() + # Hack: custom toggles in table header right click menu by adding tiny empty "ghost" columns and hiding them + # by starting the table render before the content region. + ghost_column_size = (imgui.style.frame_padding.x + imgui.style.cell_padding.x * 2) + offset = ghost_column_size * self.ghost_columns_enabled_count + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() - offset) + if imgui.begin_table( + table_id, + column=cols.count, + flags=self.game_list_table_flags, + outer_size_height=-imgui.get_frame_height_with_spacing() # Bottombar + ): + # Setup columns + self.ghost_columns_enabled_count = 0 + can_sort = 0 + for column in cols.items: + imgui.table_setup_column(column.name, column.flags | (can_sort * column.sortable)) + # Enabled columns + column.enabled = bool(imgui.table_get_column_flags(column.index) & imgui.TABLE_COLUMN_IS_ENABLED) + # Ghosts count + if column.ghost and column.enabled: + self.ghost_columns_enabled_count += 1 + # Set sorting condition + if column is cols.manual_sort: + can_sort = imgui.TABLE_COLUMN_NO_SORT * cols.manual_sort.enabled + imgui.table_setup_scroll_freeze(0, 1) # Sticky column headers + self.calculate_ids(table_id, imgui.table_get_sort_specs()) + + # Column headers + imgui.table_next_row(imgui.TABLE_ROW_HEADERS) + for column in cols.items: + imgui.table_set_column_index(column.index) + imgui.table_header(column.header) + + # Loop rows + self.sync_scroll() + frame_height = imgui.get_frame_height() + notes_width = None + for id in self.show_games_ids[self.current_tab]: + game = globals.games[id] + imgui.table_next_row() + imgui.table_set_column_index(cols.separator.index) + # Skip if outside view + if not imgui.is_rect_visible(imgui.io.display_size.x, frame_height): + imgui.dummy(0, frame_height) + continue + # Base row height with a buttom to align the following text calls to center vertically + imgui.button("", width=imgui.FLOAT_MIN) + # Loop columns + for column in cols.items: + if not column.enabled or column.ghost: + continue + imgui.table_set_column_index(column.index) + match column.index: + case cols.play_button.index: + self.draw_game_play_button(game, cols.play_button.name[0]) + case cols.type.index: + self.draw_type_widget(game.type, align=True) + case cols.name.index: + if globals.settings.show_remove_btn: + self.draw_game_remove_button(game, icons.trash_can_outline) + imgui.same_line() + if game.archived: + self.draw_game_archive_icon(game) + imgui.same_line() + if game.updated: + self.draw_game_update_icon(game) + imgui.same_line() + if game.unknown_tags_flag: + self.draw_game_unknown_tags_icon(game) + imgui.same_line() + self.draw_game_name_text(game) + if game.notes: + imgui.same_line() + imgui.text_colored(icons.draw_pen, 0.85, 0.20, 0.85) + self.draw_hover_text(game.notes, text=None) + if game.labels: + imgui.same_line() + self.draw_game_labels_widget(game, wrap=False, small=True, align=True) + if cols.status.enabled and game.status is not Status.Normal: + imgui.same_line() + self.draw_status_widget(game.status) + if cols.version.enabled or cols.finished_version.enabled or cols.installed_version.enabled: + imgui.same_line() + versions = [] + if cols.version.enabled: + if cols.finished_version.enabled and game.finished and game.finished != (game.installed or game.version): + versions.append(f"{cols.finished_version.name[0]} {game.finished}") + if cols.installed_version.enabled and game.installed and game.installed != game.version: + versions.append(f"{cols.installed_version.name[0]} {game.installed}") + versions.append(f"{cols.version.name[0]} {game.version}") + elif game.finished == game.installed != "": + if cols.finished_version.enabled: + versions.append(f"{cols.finished_version.name[0]} {game.finished}") + elif cols.installed_version.enabled: + versions.append(f"{cols.installed_version.name[0]} {game.installed}") + else: + if cols.finished_version.enabled and game.finished: + versions.append(f"{cols.finished_version.name[0]} {game.finished}") + if cols.installed_version.enabled and game.installed: + versions.append(f"{cols.installed_version.name[0]} {game.installed}") + imgui.text_disabled(" | ".join(versions)) + case cols.developer.index: + imgui.text(game.developer or "Unknown") + case cols.playtime.index: + imgui.push_font(imgui.fonts.mono) + imgui.text(game.playtime_display or "None") + imgui.pop_font() + case cols.last_updated.index: + imgui.push_font(imgui.fonts.mono) + imgui.text(game.last_updated.display or "Unknown") + imgui.pop_font() + case cols.last_launched.index: + imgui.push_font(imgui.fonts.mono) + imgui.text(game.last_launched.display or "Never") + imgui.pop_font() + case cols.added_on.index: + imgui.push_font(imgui.fonts.mono) + imgui.text(game.added_on.display) + imgui.pop_font() + case cols.finished.index: + self.draw_game_finished_checkbox(game) + case cols.installed.index: + self.draw_game_installed_checkbox(game) + case cols.rating.index: + self.draw_game_rating_widget(game) + case cols.notes.index: + if notes_width is None: + notes_width = imgui.get_content_region_available_width() - 2 * imgui.style.item_spacing.x + self.draw_game_notes_widget(game, multiline=False, width=notes_width) + case cols.open_thread.index: + self.draw_game_open_thread_button(game, cols.open_thread.name[0]) + case cols.copy_link.index: + self.draw_game_copy_link_button(game, cols.copy_link.name[0]) + case cols.open_folder.index: + self.draw_game_open_folder_button(game, cols.open_folder.name[0]) + case cols.status_standalone.index: + self.draw_status_widget(game.status) + case cols.score.index: + with imgui.begin_group(): + imgui.text(f"{game.score:.1f}") + imgui.same_line() + imgui.text_disabled(f"({game.votes})") + self.draw_hover_text(f"Weighted: {utils.bayesian_average(game.score, game.votes):.2f}", text=None) + # Row hitbox + imgui.same_line() + imgui.set_cursor_pos_y(imgui.get_cursor_pos_y() - imgui.style.frame_padding.y) + if game.selected: + imgui.push_alpha(0.5) + imgui.get_window_draw_list().add_rect_filled( + 0, pos_y := imgui.get_cursor_screen_pos().y + 1, + imgui.io.display_size.x, pos_y + frame_height + 2 * imgui.style.cell_padding.y, + imgui.get_color_u32_rgba(*globals.settings.style_accent) + ) + imgui.pop_alpha() + imgui.push_alpha(0.25) + imgui.selectable(f"###{game.id}_hitbox", False, flags=imgui.SELECTABLE_SPAN_ALL_COLUMNS, height=frame_height) + imgui.pop_alpha() + self.handle_game_hitbox_events(game, drag_drop=True) + + imgui.end_table() + + def tick_list_columns(self): + # Hack: get sort and column specs for list mode in grid and kanban mode + table_id = self.games_table_id() + # Hack: custom toggles in table header right click menu by adding tiny empty "ghost" columns and hiding them + # by starting the table render before the content region. + ghost_column_size = (imgui.style.frame_padding.x + imgui.style.cell_padding.x * 2) + offset = ghost_column_size * self.ghost_columns_enabled_count + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() - offset) + pos_y = imgui.get_cursor_pos_y() + header_h = imgui.get_text_line_height_with_spacing() if globals.settings.table_header_outside_list else 1 + if imgui.begin_table( + table_id, + column=cols.count, + flags=self.game_list_table_flags, + outer_size_height=header_h + ): + # Setup columns + self.ghost_columns_enabled_count = 0 + can_sort = 0 + for column in cols.items: + imgui.table_setup_column(column.name, column.flags | (can_sort * column.sortable) | imgui.TABLE_COLUMN_NO_REORDER) + # Enabled columns + column.enabled = bool(imgui.table_get_column_flags(column.index) & imgui.TABLE_COLUMN_IS_ENABLED) + # Ghosts count + if column.ghost and column.enabled: + self.ghost_columns_enabled_count += 1 + # Set sorting condition + if column is cols.manual_sort: + can_sort = imgui.TABLE_COLUMN_NO_SORT * cols.manual_sort.enabled + self.calculate_ids(table_id, imgui.table_get_sort_specs()) + + if globals.settings.table_header_outside_list: + # Column headers + imgui.table_next_row(imgui.TABLE_ROW_HEADERS) + for column in cols.items: + imgui.table_set_column_index(column.index) + imgui.table_header(column.header) + + imgui.end_table() + if not globals.settings.table_header_outside_list: + imgui.set_cursor_pos_y(pos_y + header_h) + + def get_game_cell_config(self): + side_indent = imgui.style.item_spacing.x * 2 + checkboxes = cols.finished.enabled + cols.installed.enabled + buttons = cols.play_button.enabled + cols.open_folder.enabled + cols.open_thread.enabled + cols.copy_link.enabled + action_items = checkboxes + buttons + cluster_pad = imgui.style.item_spacing.x * 3 + bg_col = imgui.get_color_u32_rgba(*imgui.style.colors[imgui.COLOR_TABLE_ROW_BACKGROUND_ALT]) + frame_height = imgui.get_frame_height() + + actions_width = ( + imgui.style.item_spacing.x * action_items + # Spacing * 6 action items + imgui.style.frame_padding.x * 2 * buttons + # Button padding * 2 sides * 4 buttons + imgui.style.item_inner_spacing.x * checkboxes + # Checkbox to label spacing * 2 checkboxes + frame_height * checkboxes + # (Checkbox height = width) * 2 checkboxes + imgui.calc_text_size( # Text + cols.play_button.name[0] * cols.play_button.enabled + + cols.open_folder.name[0] * cols.open_folder.enabled + + cols.open_thread.name[0] * cols.open_thread.enabled + + cols.copy_link.name[0] * cols.copy_link.enabled + + cols.finished.name[0] * cols.finished.enabled + + cols.installed.name[0] * cols.installed.enabled + ).x + ) + + base_width = max( + ( # Type / Version line + 2 * self.get_type_label_width() + ), + ( # Clustered data + imgui.style.item_spacing.x + # Between text + imgui.calc_text_size(f"{cols.last_updated.name[0]}00/00/0000").x # Text + ) + ) + + min_cell_width = ( + side_indent * 2 + # Side indent * 2 sides + max(actions_width, base_width) + ) + + min_expand_width = ( + side_indent * 2 + # Side indent * 2 sides + max( + ( # Expanded actions text + actions_width + + imgui.calc_text_size( + " Play" * cols.play_button.enabled + + " Folder" * cols.open_folder.enabled + + " Thread" * cols.open_thread.enabled + + " Link" * cols.copy_link.enabled + ).x + ), + base_width + ) + ) + + config = (side_indent, action_items, cluster_pad, bg_col, frame_height) + return min_cell_width, min_expand_width, config + + def draw_game_cell(self, game: Game, drag_drop: bool, draw_list, cell_width: float, expand: bool, img_height: float, config: tuple): + (side_indent, action_items, cluster_pad, bg_col, frame_height) = config + draw_list.channels_split(2) + draw_list.channels_set_current(1) + pos = imgui.get_cursor_pos() + rounding = self.scaled(globals.settings.style_corner_radius) + imgui.begin_group() + # Image + if game.image.error: + showed_img = imgui.is_rect_visible(cell_width, img_height) + self.draw_game_image_error(game, cell_width, img_height) + else: + crop = game.image.crop_to_ratio(globals.settings.cell_image_ratio, fit=globals.settings.fit_images) + showed_img = game.image.render(cell_width, img_height, *crop, rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_TOP) + # Alignments + imgui.indent(side_indent) + wrap_pos = pos.x + cell_width - side_indent + imgui.push_text_wrap_pos(wrap_pos) + imgui.spacing() + imgui.spacing() + # Image overlays + if showed_img: + old_pos = imgui.get_cursor_pos() + # Remove button + if globals.settings.show_remove_btn: + imgui.set_cursor_pos((pos.x + imgui.style.item_spacing.x, pos.y + imgui.style.item_spacing.y)) + self.draw_game_remove_button(game, icons.trash_can_outline) + # Type + if cols.type.enabled: + imgui.set_cursor_pos((pos.x + imgui.style.item_spacing.x, pos.y + img_height + imgui.style.item_spacing.y - frame_height / 2)) + self.draw_type_widget(game.type, wide=False) + # Version + if cols.version.enabled: + cut = len(game.version) + while (w := imgui.calc_text_size(game.version[:cut]).x) > cell_width / 2: + cut -= 1 + imgui.set_cursor_pos((pos.x + cell_width - side_indent - imgui.style.item_spacing.x - w, pos.y + img_height + imgui.style.item_spacing.y - frame_height / 2)) + self.begin_framed_text((0.3, 0.3, 0.3, 1.0), interaction=True) + imgui.small_button(game.version[:cut]) + if imgui.is_item_hovered() and cut != len(game.version): + imgui.set_tooltip(game.version) + self.end_framed_text(interaction=True) + imgui.set_cursor_pos(old_pos) + # Name line + imgui.push_font(imgui.fonts.bold) + self.draw_game_name_text(game) + imgui.pop_font() + # Buttons line + if action_items: + if imgui.is_rect_visible(cell_width, frame_height): + # Play Button + did_newline = False + if cols.play_button.enabled: + if did_newline: + imgui.same_line() + self.draw_game_play_button(game, f"{cols.play_button.name[0]}{' Play' if expand else ''}") + did_newline = True + # Open Folder + if cols.open_folder.enabled: + if did_newline: + imgui.same_line() + self.draw_game_open_folder_button(game, f"{cols.open_folder.name[0]}{' Folder' if expand else ''}") + did_newline = True + # Open Thread + if cols.open_thread.enabled: + if did_newline: + imgui.same_line() + self.draw_game_open_thread_button(game, f"{cols.open_thread.name[0]}{' Thread' if expand else ''}") + did_newline = True + # Copy Link + if cols.copy_link.enabled: + if did_newline: + imgui.same_line() + self.draw_game_copy_link_button(game, f"{cols.copy_link.name[0]}{' Link' if expand else ''}") + did_newline = True + # Finished + if cols.finished.enabled: + if did_newline: + imgui.same_line() + self.draw_game_finished_checkbox(game, cols.finished.name[0]) + did_newline = True + # Installed + if cols.installed.enabled: + if did_newline: + imgui.same_line() + self.draw_game_installed_checkbox(game, cols.installed.name[0]) + did_newline = True + else: + # Skip if outside view + imgui.dummy(0, frame_height) + # Cluster data + cluster = False + if game.archived: + self.draw_game_archive_icon(game) + imgui.same_line() + cluster = True + if game.updated: + self.draw_game_update_icon(game) + imgui.same_line() + cluster = True + if game.unknown_tags_flag: + self.draw_game_unknown_tags_icon(game) + imgui.same_line() + cluster = True + if game.notes: + imgui.text_colored(icons.draw_pen, 0.85, 0.20, 0.85) + self.draw_hover_text(game.notes, text=None) + imgui.same_line() + cluster = True + if (cols.status.enabled or cols.status_standalone.enabled) and game.status is not Status.Normal: + self.draw_status_widget(game.status) + imgui.same_line() + cluster = True + if game.labels: + self.draw_game_labels_widget(game, short=True, align=False) + imgui.same_line() + cluster = True + def _cluster_text(name, text): + nonlocal cluster + if imgui.get_cursor_pos_x() + imgui.style.item_spacing.x + imgui.calc_text_size(name[0] + text).x > wrap_pos: + imgui.dummy(0, 0) + imgui.text_disabled(name[0]) + if imgui.is_item_hovered(): + imgui.set_tooltip(name[2:]) + imgui.same_line() + imgui.text(text) + imgui.same_line(spacing=cluster_pad) + cluster = True + if cols.score.enabled: + _cluster_text(cols.score.name, f"{game.score:.1f} ({game.votes})") + self.draw_hover_text(f"Weighted: {utils.bayesian_average(game.score, game.votes):.2f}", text=None) + if cols.playtime.enabled and game.playtime_display: + _cluster_text(cols.playtime.name, game.playtime_display) + if cols.last_updated.enabled: + _cluster_text(cols.last_updated.name, game.last_updated.display or "Unknown") + if cols.last_launched.enabled: + _cluster_text(cols.last_launched.name, game.last_launched.display or "Never") + if cols.added_on.enabled: + _cluster_text(cols.added_on.name, game.added_on.display) + if cols.rating.enabled: + if imgui.get_cursor_pos_x() + imgui.calc_text_size(icons.star * 5).x > wrap_pos: + imgui.dummy(0, 0) + self.draw_game_rating_widget(game) + imgui.same_line(spacing=cluster_pad) + cluster = True + if cols.developer.enabled: + _cluster_text(cols.developer.name, game.developer) + if cols.finished_version.enabled or cols.installed_version.enabled: + if cols.version.enabled: + if cols.finished_version.enabled and game.finished and game.finished != (game.installed or game.version): + _cluster_text(cols.finished_version.name, game.finished) + if cols.installed_version.enabled and game.installed and game.installed != game.version: + _cluster_text(cols.installed_version.name, game.installed) + elif game.finished == game.installed != "": + if cols.finished_version.enabled: + _cluster_text(cols.finished_version.name, game.finished) + elif cols.installed_version.enabled: + _cluster_text(cols.installed_version.name, game.installed) + else: + if cols.finished_version.enabled and game.finished: + _cluster_text(cols.finished_version.name, game.finished) + if cols.installed_version.enabled and game.installed: + _cluster_text(cols.installed_version.name, game.installed) + if cluster: + imgui.dummy(0, 0) + # Notes line + if cols.notes.enabled: + if imgui.is_rect_visible(cell_width, frame_height): + self.draw_game_notes_widget(game, multiline=False, width=cell_width - 2 * side_indent) + else: + # Skip if outside view + imgui.dummy(0, frame_height) + # Cell hitbox + imgui.pop_text_wrap_pos() + imgui.spacing() + imgui.spacing() + imgui.end_group() + draw_list.channels_set_current(0) + imgui.set_cursor_pos(pos) + cell_height = imgui.get_item_rect_size().y + if imgui.is_rect_visible(cell_width, cell_height): + # Skip if outside view + imgui.invisible_button(f"###{game.id}_hitbox", cell_width, cell_height) + self.handle_game_hitbox_events(game, drag_drop=drag_drop) + rect_min = imgui.get_item_rect_min() + rect_max = imgui.get_item_rect_max() + if game.selected: + imgui.push_alpha(0.5) + draw_list.add_rect_filled( + *rect_min, *rect_max, imgui.get_color_u32_rgba(*globals.settings.style_accent), + rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_ALL + ) + imgui.pop_alpha() + else: + draw_list.add_rect_filled(*rect_min, *rect_max, bg_col, rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_ALL) + else: + imgui.dummy(cell_width, cell_height) + draw_list.channels_merge() + + def draw_games_grid(self): + # Configure table + self.tick_list_columns() + min_cell_width, min_expand_width, cell_config = self.get_game_cell_config() + padding = self.scaled(8) + avail = imgui.get_content_region_available_width() + column_count = globals.settings.grid_columns + while (cell_width := (avail - padding * 2 * column_count) / column_count) < min_cell_width and column_count > 1: + column_count -= 1 + expand = cell_width > min_expand_width + img_height = cell_width / globals.settings.cell_image_ratio + imgui.push_style_var(imgui.STYLE_CELL_PADDING, (padding, padding)) + if imgui.begin_table( + "###game_grid", + column=column_count, + flags=self.game_grid_table_flags, + outer_size_height=-imgui.get_frame_height_with_spacing() # Bottombar + ): + # Setup + for _ in range(column_count): + imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) + + # Loop cells + self.sync_scroll() + draw_list = imgui.get_window_draw_list() + for id in self.show_games_ids[self.current_tab]: + game = globals.games[id] + imgui.table_next_column() + self.draw_game_cell(game, True, draw_list, cell_width, expand, img_height, cell_config) + + imgui.end_table() + imgui.pop_style_var() + + def draw_games_kanban(self): + # Configure table + self.tick_list_columns() + cell_width, _, cell_config = self.get_game_cell_config() + padding = self.scaled(4) + imgui.push_style_var(imgui.STYLE_CELL_PADDING, (padding, padding)) + img_height = cell_width / globals.settings.cell_image_ratio + cells_per_column = 1 + column_count = len(Label.instances) + 1 + avail = imgui.get_content_region_available_width() + table_width = lambda: (padding * 2 + (cell_width + imgui.style.item_spacing.x) * cells_per_column + imgui.style.scrollbar_size) * column_count + while table_width() < avail: + cells_per_column += 1 + cells_per_column = max(cells_per_column - 1, 1) + if imgui.begin_table( + "###game_kanban", + column=column_count, + flags=self.game_kanban_table_flags, + inner_width=table_width(), + outer_size_width=-imgui.style.scrollbar_size, + outer_size_height=-imgui.get_frame_height_with_spacing() # Bottombar + ): + # Setup columns + not_labelled = len(Label.instances) + for label in Label.instances: + imgui.table_setup_column(label.name, imgui.TABLE_COLUMN_WIDTH_STRETCH) + imgui.table_setup_column("Not Labelled", imgui.TABLE_COLUMN_WIDTH_STRETCH) + tab_games_ids = self.show_games_ids[self.current_tab] + + # Column headers + imgui.table_setup_scroll_freeze(0, 1) # Sticky column headers + imgui.table_next_row(imgui.TABLE_ROW_HEADERS) + for label_i, label in enumerate(Label.instances): + imgui.table_set_column_index(label_i) + count = 0 + for id in tab_games_ids: + if label in globals.games[id].labels: + count += 1 + imgui.table_header(f"{label.name} ({count})") + imgui.table_set_column_index(not_labelled) + count = 0 + for id in tab_games_ids: + if not globals.games[id].labels: + count += 1 + imgui.table_header(f"Not Labelled ({count})") + + # Loop cells + for label_i, label in (*enumerate(Label.instances), (not_labelled, None)): + imgui.table_next_column() + imgui.begin_child(f"###game_kanban_{label_i}", height=-padding) + draw_list = imgui.get_window_draw_list() + wrap = cells_per_column + imgui.begin_group() + for id in tab_games_ids: + game = globals.games[id] + if label_i == not_labelled: + if game.labels: + continue + elif label not in game.labels: + continue + self.draw_game_cell(game, False, draw_list, cell_width, False, img_height, cell_config) + wrap -= 1 + if wrap: + imgui.same_line() + else: + imgui.end_group() + wrap = cells_per_column + imgui.begin_group() + imgui.end_group() + imgui.end_child() + + imgui.end_table() + imgui.pop_style_var() + + def draw_bottombar(self): + new_display_mode = None + + for display_mode in DisplayMode: + if globals.settings.display_mode is display_mode: + imgui.push_style_color(imgui.COLOR_BUTTON, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) + if imgui.button(getattr(icons, display_mode.icon)): + new_display_mode = display_mode + self.switched_display_mode = True + if globals.settings.display_mode is display_mode: + imgui.pop_style_color() + imgui.same_line() + + if new_display_mode is not None: + globals.settings.display_mode = new_display_mode + async_thread.run(db.update_settings("display_mode")) + + if self.add_box_valid: + imgui.set_next_item_width(-(imgui.calc_text_size("Add!").x + 2 * imgui.style.frame_padding.x) - imgui.style.item_spacing.x) + else: + imgui.set_next_item_width(-imgui.FLOAT_MIN) + any_active_old = imgui.is_any_item_active() + any_active = False + def setter_extra(_=None): + self.add_box_valid = len(utils.extract_thread_matches(self.add_box_text)) > 0 + self.recalculate_ids = True + if not globals.popup_stack and not any_active_old and (self.input_chars or any(imgui.io.keys_down)): + if imgui.is_key_pressed(glfw.KEY_BACKSPACE): + self.add_box_text = self.add_box_text[:-1] + setter_extra() + if imgui.is_key_down(glfw.KEY_LEFT_CONTROL) and imgui.is_key_pressed(glfw.KEY_A): + tab_games_ids = self.show_games_ids[self.current_tab] + selected = not any(globals.games[id].selected for id in tab_games_ids) + for id in tab_games_ids: + globals.games[id].selected = selected + if self.input_chars: + self.repeat_chars = True + imgui.set_keyboard_focus_here() + any_active = True + activated, value = imgui.input_text_with_hint( + "###bottombar", + "Type to filter the list, press enter to add a game (link/search)", + self.add_box_text, + flags=imgui.INPUT_TEXT_ENTER_RETURNS_TRUE + ) + changed = value != self.add_box_text + activated = bool(activated and value) + any_active = any_active or imgui.is_any_item_active() + if any_active_old != any_active and imgui.is_key_pressed(glfw.KEY_ESCAPE): + # Changed active state, and escape is pressed, so clear textbox + value = "" + changed = True + for game in globals.games.values(): + game.selected = False + if changed: + self.add_box_text = value + setter_extra() + if imgui.begin_popup_context_item("###bottombar_context"): + # Right click = more options context menu + utils.text_context(self, "add_box_text", setter_extra, no_icons=True) + imgui.separator() + if imgui.selectable(f"{icons.information_outline} More info", False)[0]: + utils.push_popup( + msgbox.msgbox, "About the bottom bar", + "This is the filter/add bar. By typing inside it you can search your game list.\n" + "Pressing enter will search F95zone for a matching thread and ask if you wish to\n" + "add it to your list.\n" + "\n" + "When you instead paste a link to a F95zone thread, the 'Add!' button will show\n" + "up, allowing you to add that thread to your list. When a link is detected you\n" + "can also press enter on your keyboard to trigger the 'Add!' button.", + MsgBox.info + ) + imgui.end_popup() + if self.add_box_valid: + imgui.same_line() + if imgui.button("Add!") or activated: + async_thread.run(callbacks.add_games(*utils.extract_thread_matches(self.add_box_text))) + self.add_box_text = "" + self.add_box_valid = False + self.recalculate_ids = True + elif activated: + api.open_search_popup(self.add_box_text) + self.add_box_text = "" + self.add_box_valid = False + self.recalculate_ids = True + + def draw_sidebar(self): + set = globals.settings + right_width = self.scaled(90) + frame_height = imgui.get_frame_height() + checkbox_offset = right_width - frame_height + + def draw_settings_section(name: str, collapsible=True): + if collapsible: + header = imgui.collapsing_header(name)[0] + else: + header = True + opened = header and imgui.begin_table(f"###settings_{name}", column=2, flags=imgui.TABLE_NO_CLIP) + if opened: + imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) + imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_FIXED) + imgui.table_next_row() + imgui.table_set_column_index(1) # Right + imgui.dummy(right_width, 1) + imgui.push_item_width(right_width) + return opened + + def draw_settings_label(label: str, tooltip: str = None): + imgui.table_next_row() + imgui.table_next_column() + imgui.text(label) + if tooltip: + imgui.same_line() + self.draw_hover_text(tooltip) + imgui.table_next_column() + + def draw_settings_checkbox(setting: str): + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) + changed, value = imgui.checkbox(f"###{setting}", getattr(set, setting)) + if changed: + setattr(set, setting, value) + async_thread.run(db.update_settings(setting)) + return changed + + def draw_settings_color(setting: str): + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) + changed, value = imgui.color_edit3(f"###{setting}", *getattr(set, setting)[:3], flags=imgui.COLOR_EDIT_NO_INPUTS) + if changed: + setattr(set, setting, (*value, 1.0)) + self.refresh_styles() + async_thread.run(db.update_settings(setting)) + return changed + + width = imgui.get_content_region_available_width() + height = self.scaled(100) + if utils.is_refreshing(): + # Refresh progress bar + ratio = globals.refresh_progress / globals.refresh_total + self.refresh_ratio_smooth += (ratio - self.refresh_ratio_smooth) * imgui.io.delta_time * 8 + imgui.progress_bar(self.refresh_ratio_smooth, (width, height)) + draw_list = imgui.get_window_draw_list() + screen_pos = imgui.get_cursor_screen_pos() + col = imgui.get_color_u32_rgba(1, 1, 1, 1) + if imgui.is_item_clicked(): + # Click = cancel + globals.refresh_task.cancel() + if imgui.is_item_hovered(): + text = "Click to cancel!" + text_size = imgui.calc_text_size(text) + text_x = screen_pos.x + (width - text_size.x) / 2 + text_y = screen_pos.y - text_size.y - 3 * imgui.style.item_spacing.y + draw_list.add_text(text_x, text_y, col, text) + text = f"{ratio:.0%}" + text_size = imgui.calc_text_size(text) + text_x = screen_pos.x + (width - text_size.x) / 2 + text_y = screen_pos.y - (height + text_size.y) / 2 - imgui.style.item_spacing.y + draw_list.add_text(text_x, text_y, col, text) + elif self.hovered_game: + # Hover = show image + game = self.hovered_game + if game.image.error: + if game.image.error == "Image file missing": + text = "Image missing!" + else: + text = "Invalid image!" + imgui.button(text, width=width, height=height) + else: + crop = game.image.crop_to_ratio(width / height, fit=globals.settings.fit_images) + game.image.render(width, height, *crop, rounding=self.scaled(globals.settings.style_corner_radius)) + else: + # Normal button + if imgui.button("Refresh!", width=width, height=height): + utils.start_refresh_task(api.refresh()) + elif imgui.is_item_hovered(): + draw_list = imgui.get_window_draw_list() + screen_pos = imgui.get_cursor_screen_pos() + col = imgui.get_color_u32_rgba(1, 1, 1, 1) + text = str(f"Last refresh: {set.last_successful_refresh.display or 'Never'}") + text_size = imgui.calc_text_size(text) + text_x = screen_pos.x + (width - text_size.x) / 2 + text_y = screen_pos.y - text_size.y - 3 * imgui.style.item_spacing.y + draw_list.add_text(text_x, text_y, col, text) + if imgui.begin_popup_context_item("###refresh_context"): + # Right click = more options context menu + if imgui.selectable(f"{icons.bell_badge_outline} Check notifs", False)[0]: + utils.start_refresh_task(api.check_notifs(standalone=True)) + if imgui.selectable(f"{icons.reload_alert} Full Refresh", False)[0]: + utils.start_refresh_task(api.refresh(full=True)) + if not globals.settings.refresh_completed_games or not globals.settings.refresh_archived_games: + imgui.separator() + if not globals.settings.refresh_archived_games: + if imgui.selectable(f"{icons.reload_alert} Full Refresh (incl. archived)", False)[0]: + utils.start_refresh_task(api.refresh(full=True, force_archived=True)) + if not globals.settings.refresh_completed_games: + if imgui.selectable(f"{icons.reload_alert} Full Refresh (incl. completed)", False)[0]: + utils.start_refresh_task(api.refresh(full=True, force_completed=True)) + if not globals.settings.refresh_completed_games and not globals.settings.refresh_archived_games: + if imgui.selectable(f"{icons.reload_alert} Full Refresh (incl. everything)", False)[0]: + utils.start_refresh_task(api.refresh(full=True, force_archived=True, force_completed=True)) + imgui.separator() + if imgui.selectable(f"{icons.monitor_arrow_down_variant} Check for app updates", False)[0]: + utils.start_update_check() + imgui.separator() + if imgui.selectable(f"{icons.information_outline} More info", False)[0]: + utils.push_popup( + msgbox.msgbox, "About refreshing", + "Refreshing is the process by which F95Checker goes through your games and checks\n" + "if they have received updates, aswell as syncing other game info.\n" + "To keep it fast and smooth while avoiding excessive stress on F95zone servers, this\n" + "is done using a dedicated F95Checker Cache API.\n" + "\n" + "This Cache API gets data from F95zone, parses the relevant details, then saves them for\n" + "up to 7 days. To make sure you don't fall behind, it also monitors the F95zone Latest\n" + "Updates to invalidate cache when games are updated / some details change. However,\n" + "not all details are tracked by Latest Updates, so games are still periodically checked.\n" + "There's a bit more complexity to it, but that's the gist of it. Check the OP / README for\n" + "a more detailed explanation and all the caveats and behaviors.\n" + "\n" + "All this data can become quite large if you have lots of games (~2MiB for 100 games) so\n" + "fetching everything at each refresh would be quite expensive. Instead F95Checker will\n" + "ask the Cache API when each game last changed any of its details (which will also update\n" + "the cache if needed) 10 games at a time, then fetch the full game details only for those\n" + "that have changed since the last refresh.\n" + "You can force full rechecks to fetch all cached game data again, either for single games\n" + "or for the whole list, with the right click menu on the game and on the refresh button,\n" + "but this usually should not be necessary.", + MsgBox.info + ) + imgui.end_popup() + + imgui.begin_child("Settings") + + if draw_settings_section("Filter", collapsible=False): + draw_settings_label(f"Total games count: {len(globals.games)}") + imgui.text("") + imgui.spacing() + + if self.selected_games_count: + draw_settings_label(f"Selected games count: {self.selected_games_count}") + imgui.text("") + imgui.spacing() + + if self.filtering: + if Tab.instances: + if globals.settings.filter_all_tabs: + draw_settings_label(f"Total filtered count: {len(self.show_games_ids.get(None, ()))}") + else: + draw_settings_label(f"Total filtered count: {sum(len(ids) for ids in self.show_games_ids.values())}") + else: + draw_settings_label(f"Filtered games count: {len(self.show_games_ids.get(None, ()))}") + imgui.text("") + imgui.spacing() + + draw_settings_label("Add filter:") + changed, value = imgui.combo("###add_filter", 0, FilterMode._member_names_) + if changed and value > 0: + flt = Filter(FilterMode[FilterMode._member_names_[value]]) + match flt.mode.value: + case FilterMode.Exe_State.value: + flt.match = ExeState[ExeState._member_names_[0]] + case FilterMode.Finished.value: + flt.match = True + case FilterMode.Installed.value: + flt.match = True + case FilterMode.Label.value: + if Label.instances: + flt.match = Label.instances[0] + case FilterMode.Rating.value: + flt.match = 0 + case FilterMode.Score.value: + flt.match = 0.0 + case FilterMode.Status.value: + flt.match = Status[Status._member_names_[0]] + case FilterMode.Tag.value: + flt.match = Tag[Tag._member_names_[0]] + case FilterMode.Type.value: + flt.match = Type[Type._member_names_[0]] + self.filters.append(flt) + + text_width = imgui.calc_text_size("Invrt ").x + buttons_offset = right_width - (2 * frame_height + text_width + imgui.style.item_spacing.x) + for flt in self.filters: + imgui.text("") + draw_settings_label(f"Filter by {flt.mode.name}:") + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + buttons_offset) + if imgui.button((icons.invert_colors if flt.invert else icons.invert_colors_off) + "Invrt", width=frame_height + text_width): + flt.invert = not flt.invert + self.recalculate_ids = True + imgui.same_line() + if imgui.button(icons.trash_can_outline, width=frame_height): + self.filters.remove(flt) + + match flt.mode.value: + case FilterMode.Exe_State.value: + draw_settings_label("Executable state:") + changed, value = imgui.combo(f"###filter_{flt.id}_value", flt.match._index_, ExeState._member_names_) + if changed: + flt.match = ExeState[ExeState._member_names_[value]] + self.recalculate_ids = True + case FilterMode.Finished.value: + draw_settings_label("Include outdated:") + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) + changed, value = imgui.checkbox(f"###filter_{flt.id}_value", flt.match) + if changed: + flt.match = value + self.recalculate_ids = True + case FilterMode.Installed.value: + draw_settings_label("Include outdated:") + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) + changed, value = imgui.checkbox(f"###filter_{flt.id}_value", flt.match) + if changed: + flt.match = value + self.recalculate_ids = True + case FilterMode.Label.value: + if Label.instances: + if flt.match is None: + flt.match = Label.instances[0] + draw_settings_label("Label value:") + if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.name): + for label in Label.instances: + selected = label is flt.match + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###filter_{flt.id}_value_{label.id}", selected)[0]: + flt.match = label + self.recalculate_ids = True + if selected: + imgui.set_item_default_focus() + imgui.set_cursor_pos(pos) + self.draw_label_widget(label) + imgui.end_combo() + else: + draw_settings_label("Make some labels first!") + imgui.text("") + imgui.spacing() + case FilterMode.Rating.value: + draw_settings_label("Rating value:") + changed, value = ratingwidget.ratingwidget(f"filter_{flt.id}_value", flt.match) + if changed: + flt.match = value + self.recalculate_ids = True + imgui.spacing() + case FilterMode.Score.value: + draw_settings_label("Score value:") + changed, value = imgui.drag_float(f"###filter_{flt.id}_value", flt.match, change_speed=0.01, min_value=0, max_value=5, format="%.1f/5") + if changed: + flt.match = value + self.recalculate_ids = True + case FilterMode.Status.value: + draw_settings_label("Status value:") + if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.name): + for status in Status: + selected = status is flt.match + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###filter_{flt.id}_value_{status.value}", selected)[0]: + flt.match = status + self.recalculate_ids = True + if selected: + imgui.set_item_default_focus() + imgui.set_cursor_pos(pos) + self.draw_status_widget(status) + imgui.same_line() + imgui.text(status.name) + imgui.end_combo() + case FilterMode.Tag.value: + draw_settings_label("Tag value:") + if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.text): + for tag in Tag: + selected = tag is flt.match + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###filter_{flt.id}_value_{tag.value}", selected)[0]: + flt.match = tag + self.recalculate_ids = True + if selected: + imgui.set_item_default_focus() + imgui.set_cursor_pos(pos) + self.draw_tag_widget(tag, quick_filter=False, change_highlight=False) + imgui.end_combo() + case FilterMode.Type.value: + draw_settings_label("Type value:") + if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.name): + category = None + for type in Type: + if category is not type.category: + category = type.category + imgui.text(category.name) + selected = type is flt.match + pos = imgui.get_cursor_pos() + if imgui.selectable(f"###filter_{flt.id}_value_{type.value}", selected)[0]: + flt.match = type + self.recalculate_ids = True + if selected: + imgui.set_item_default_focus() + imgui.set_cursor_pos(pos) + self.draw_type_widget(type) + imgui.end_combo() + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Browser"): + draw_settings_label( + "Browser:", + "All the options you select here ONLY affect how F95Checker opens links for you, it DOES NOT affect how this tool " + "operates internally. F95Checker DOES NOT interact with your browsers in any meaningful way, it uses a separate " + "session just for itself." + ) + changed, value = imgui.combo("###browser", set.browser.index, Browser.avail_list) + if changed: + set.browser = Browser.get(Browser.avail_list[value]) + async_thread.run(db.update_settings("browser")) + + if set.browser.custom: + draw_settings_label("Custom browser:") + if imgui.button("Configure", width=right_width): + def popup_content(): + imgui.text("Executable: ") + imgui.same_line() + pos = imgui.get_cursor_pos_x() + changed, value = imgui.input_text("###browser_custom_executable", set.browser_custom_executable) + setter_extra = lambda _=None: async_thread.run(db.update_settings("browser_custom_executable")) + if changed: + set.browser_custom_executable = value + setter_extra() + if imgui.begin_popup_context_item("###browser_custom_executable_context"): + utils.text_context(set, "browser_custom_executable", setter_extra, no_icons=True) + imgui.end_popup() + imgui.same_line() + clicked = imgui.button(icons.folder_open_outline) + imgui.same_line(spacing=0) + args_width = imgui.get_cursor_pos_x() - pos + imgui.dummy(0, 0) + if clicked: + def callback(selected: str): + if selected: + set.browser_custom_executable = selected + async_thread.run(db.update_settings("browser_custom_executable")) + utils.push_popup(filepicker.FilePicker( + title="Select or drop browser executable", + start_dir=set.browser_custom_executable, + callback=callback + ).tick) + imgui.text("Arguments: ") + imgui.same_line() + imgui.set_cursor_pos_x(pos) + imgui.set_next_item_width(args_width) + changed, value = imgui.input_text("###browser_custom_arguments", set.browser_custom_arguments) + setter_extra = lambda _=None: async_thread.run(db.update_settings("browser_custom_arguments")) + if changed: + set.browser_custom_arguments = value + setter_extra() + if imgui.begin_popup_context_item("###browser_custom_arguments_context"): + utils.text_context(set, "browser_custom_arguments", setter_extra, no_icons=True) + imgui.end_popup() + utils.push_popup( + utils.popup, "Configure custom browser", + popup_content, + buttons=True, + closable=True, + outside=False + ) + else: + draw_settings_label("Use private mode:") + draw_settings_checkbox("browser_private") + + draw_settings_label( + "Download pages:", + "With this enabled links will first be downloaded by F95Checker and then opened as simple HTML files in your " + "browser. This might be useful if you use private mode because the page will load as if you were logged in, " + "allowing you to see links and spoiler content without actually logging in." + ) + draw_settings_checkbox("browser_html") + + draw_settings_label( + "Software Webview:", + "Forces software mode for integrated browser webview, disables GPU acceleration. Enable if you have issues with " + "the integrated browser not rendering correctly." + ) + draw_settings_checkbox("software_webview") + + draw_settings_label("Copy game links as BBcode:") + draw_settings_checkbox("copy_urls_as_bbcode") + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Extension"): + draw_settings_label( + "RPC enabled:", + f"The RPC allows other programs on your pc to interact with F95Checker via the API on {globals.rpc_url}. " + "Essentially this is what makes the web browser extension work. Disable this if you are having issues with the RPC, " + "but do note that doing so will prevent the extension from working at all." + ) + if draw_settings_checkbox("rpc_enabled"): + if set.rpc_enabled: + rpc_thread.start() + else: + rpc_thread.stop() + + draw_settings_label("Install extension:") + cant_install_extension = set.browser.integrated or not set.rpc_enabled + def cant_install_extension_tooltip(): + self.draw_hover_text( + "You have selected the Integrated browser, this already includes the extension!" + if set.browser.integrated else + "RPC must be enabled for the browser extension to work!" + if not set.rpc_enabled else + "Huh?", + text=None, + ) + if cant_install_extension: + imgui.push_disabled() + if imgui.button(icons.google_chrome, width=(right_width - imgui.style.item_spacing.x) / 2): + buttons={ + f"{icons.check} Ok": lambda: async_thread.run(callbacks.default_open(globals.self_path / "browser")), + f"{icons.cancel} Cancel": None + } + utils.push_popup( + msgbox.msgbox, "Chrome extension", + "Unfortunately, the F95Checker extension is banned from the Chrome Webstore.\n" + "Therefore, you must install it manually via developer mode:\n" + " - Open your Chromium-based browser\n" + " - Navigate to 'chrome://extensions/'\n" + " - Enable the 'Developer mode' toggle\n" + " - Refresh the page\n" + " - Click Ok below and drag 'chrome.zip' into your browser window\n", + MsgBox.info, + buttons + ) + if cant_install_extension: + imgui.pop_disabled() + cant_install_extension_tooltip() + imgui.push_disabled() + imgui.same_line() + if imgui.button(icons.firefox, width=(right_width - imgui.style.item_spacing.x) / 2): + if globals.release: + callbacks.open_webpage("https://addons.mozilla.org/firefox/addon/f95checker-browser-addon/") + else: + callbacks.open_webpage("https://addons.mozilla.org/firefox/addon/f95checker-beta-browser-addon/") + if cant_install_extension: + imgui.pop_disabled() + cant_install_extension_tooltip() + + draw_settings_label( + "Icon glow:", + "Icons in some locations will cast colored shadow to improve visibility." + ) + draw_settings_checkbox("ext_icon_glow") + + draw_settings_label( + "Highlight tags:", + "To change tag preferences go to 'Interface' > 'Tags to highlight'." + ) + draw_settings_checkbox("ext_highlight_tags") + + draw_settings_label( + "Add in background:", + "Don't open F95Checker window after adding a game." + ) + draw_settings_checkbox("ext_background_add") + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Images"): + draw_settings_label( + "Cell ratio:", + "The aspect ratio to use for images in grid and kanban view. This is width:height, AKA how many times wider the image " + "is compared to its height. Default is 3:1." + ) + changed, value = imgui.drag_float("###cell_image_ratio", set.cell_image_ratio, change_speed=0.02, min_value=0.5, max_value=5, format="%.1f:1") + set.cell_image_ratio = min(max(value, 0.5), 5) + if changed: + async_thread.run(db.update_settings("cell_image_ratio")) + + draw_settings_label( + "Fit images:", + "Fit images instead of cropping. When cropping the images fill all the space they have available, cutting " + "off the sides a bit. When fitting the images you see the whole image but it has some empty space at the sides." + ) + draw_settings_checkbox("fit_images") + + draw_settings_label( + "Zoom on hover:", + "Allow zooming header images inside info popups.\n" + "Tip: hold shift and scroll while hovering the image to change the zoom amount, or hold shift and alt while " + "scrolling to change the zoom area." + ) + draw_settings_checkbox("zoom_enabled") + + if not set.zoom_enabled: + imgui.push_disabled() + + draw_settings_label( + "Zoom area:", + "The size of the zoom popup compared to the main window size (uses the shorter of the two window dimensions). " + "Default 50%." + ) + changed, value = imgui.drag_int("###zoom_area", set.zoom_area, change_speed=0.2, min_value=1, max_value=500, format="%d%%") + set.zoom_area = min(max(value, 1), 500) + if changed: + async_thread.run(db.update_settings("zoom_area")) + + draw_settings_label( + "Zoom times:", + "How many times to magnify the zoomed area of the image. Default 4x." + ) + changed, value = imgui.drag_float("###zoom_times", set.zoom_times, change_speed=0.02, min_value=1, max_value=20, format="%.1fx") + set.zoom_times = min(max(value, 1), 20) + if changed: + async_thread.run(db.update_settings("zoom_times")) + + if not set.zoom_enabled: + imgui.pop_disabled() + + draw_settings_label("Play GIFs:") + if draw_settings_checkbox("play_gifs"): + for image in imagehelper.ImageHelper.instances: + image.reload() + + if not set.play_gifs: + imgui.push_disabled() + draw_settings_label("Play GIFs unfocused:") + draw_settings_checkbox("play_gifs_unfocused") + if not set.play_gifs: + imgui.pop_disabled() + + draw_settings_label( + "Tex compress:", + "Compress textures using ASTC (6x6/80) or BC7. If supported by GPU, results in dramatically faster image loading " + "with no perceptible loss in visual quality. Depending on GPU model and drivers it might also decrease VRAM " + "usage. Disk usage should be roughly the same (some images compress better than others, it averages out).\n\n" + "ASTC:\n" + "+ when supported takes 9x less VRAM\n" + "+ takes 20% less disk space than original images\n" + "+ compresses slightly faster than BC7\n" + "- very limited GPU support, may not work at all\n" + "- when unsupported may use same VRAM as uncompressed (decompressed on-the-fly)\n" + "- may heavily stutter when loading (due to decompressing on-the-fly)\n" + "BC7:\n" + "+ when supported takes 4x less VRAM\n" + "+ supported by most GPUs\n" + "+ more likely to decrease VRAM usage than ASTC\n" + "- takes 60% more disk space than original images\n" + "- compresses slightly slower than ASTC\n" + "- not supported on MacOS\n" + "Visual quality tends to be equivalent.\n\n" + "Images are compressed when first shown, and it takes some time, especially so for GIFs. After compressing, the " + "result is saved to file, and next loads will be instantaneous.\n" + "If you're looking to compare VRAM usage, make sure to restart the tool (fully quit and reopen) between " + "measurements. This is because the GPU does not release VRAM until something else needs it, it's just marked " + "as unused, which would give the same VRAM usage number between compressed and not.\n" + "If only a compressed image is found it will be used even if this option is disabled (for example, if you enabled " + "Compress replace, the replaced images will continue to use the compressed file even if this setting is off)." + ) + changed, value = imgui.combo("###tex_compress", set.tex_compress._index_, TexCompress._member_names_) + if changed: + set.tex_compress = TexCompress[TexCompress._member_names_[value]] + async_thread.run(db.update_settings("tex_compress")) + for image in imagehelper.ImageHelper.instances: + image.reload() + + if set.tex_compress is TexCompress.Disabled: + imgui.push_disabled() + draw_settings_label( + "Compress replace:", + "Remove original images after texture compression. Enabling this is retro-active: it will delete source images for " + "ones already compressed. Not enabling this option means roughly double disk space usage due to duplicate " + "images files." + ) + if draw_settings_checkbox("tex_compress_replace"): + for image in imagehelper.ImageHelper.instances: + image.reload() + if set.tex_compress is TexCompress.Disabled: + imgui.pop_disabled() + + draw_settings_label( + "Unload off-screen:", + "Unloads images from VRAM when they are not visible. Will be loaded again when next visible. Works best " + "together with Tex compress, so image load times are less noticeable." + ) + draw_settings_checkbox("unload_offscreen_images") + + draw_settings_label( + "Preload nearby images:", + "Starts loading images when they aren't yet visible but are less than a window width/height scroll away. Works " + "best together with Tex compress, so image load times are very short and completely unnoticeable due to preloading." + ) + draw_settings_checkbox("preload_nearby_images") + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Interface"): + draw_settings_label("Scaling:") + changed, value = imgui.drag_float("###interface_scaling", set.interface_scaling, change_speed=imgui.FLOAT_MIN, min_value=0.5, max_value=4, format="%.2fx") + if imgui.is_item_deactivated(): # Only change when editing by text input and accepting the edit + set.interface_scaling = min(max(value, 0.5), 4) + async_thread.run(db.update_settings("interface_scaling")) + + draw_settings_label( + "BG on close:", + "When closing the window F95Checker will instead switch to background mode. Quit the app via the tray icon." + ) + draw_settings_checkbox("background_on_close") + + draw_settings_label( + "Table header ouside list:", + "Shows the table header in all view modes, allowing to change sorting and shown elements ouside of list mode." + ) + draw_settings_checkbox("table_header_outside_list") + + draw_settings_label( + "Grid columns:", + "How many games will show in each row in grid view. It is a maximum value because when there is insufficient " + "space to show all these columns, the number will be internally reduced to render each grid cell properly." + ) + changed, value = imgui.drag_int("###grid_columns", set.grid_columns, change_speed=0.05, min_value=1, max_value=10) + set.grid_columns = min(max(value, 1), 10) + if changed: + async_thread.run(db.update_settings("grid_columns")) + + draw_settings_label("Smooth scrolling:") + draw_settings_checkbox("scroll_smooth") + + if not set.scroll_smooth: + imgui.push_disabled() + + draw_settings_label( + "Smoothness:", + "How fast or slow the smooth scrolling animation is. Default is 8." + ) + changed, value = imgui.drag_float("###scroll_smooth_speed", set.scroll_smooth_speed, change_speed=0.25, min_value=0.1, max_value=50) + set.scroll_smooth_speed = min(max(value, 0.1), 50) + if changed: + async_thread.run(db.update_settings("scroll_smooth_speed")) + + if not set.scroll_smooth: + imgui.pop_disabled() + + draw_settings_label( + "Scroll mult:", + "Multiplier for how much a single scroll event should actually scroll. Default is 1." + ) + changed, value = imgui.drag_float("###scroll_amount", set.scroll_amount, change_speed=0.05, min_value=0.1, max_value=10, format="%.2fx") + set.scroll_amount = min(max(value, 0.1), 10) + if changed: + async_thread.run(db.update_settings("scroll_amount")) + + draw_settings_label( + "Quick filters:", + "When this is enabled you can click on some widgets to quickly add filters for them. This includes: type, tag and " + "label widgets, and status and update icons." + ) + draw_settings_checkbox("quick_filters") + + draw_settings_label("Compact timeline:") + draw_settings_checkbox("compact_timeline") + + draw_settings_label("Highlight tags:") + draw_settings_checkbox("highlight_tags") + + draw_settings_label("Tags to highlight:") + if imgui.button("Select", width=right_width): + utils.push_popup(self.draw_tag_highlights_popup) + + draw_settings_label( + "Time format:", + "The format expression to use for full timestamps. Uses the strftime specification. Default is '%d/%m/%Y %H:%M'." + ) + changed, value = imgui.input_text("###timestamp_format", set.timestamp_format) + def setter_extra(_=None): + async_thread.run(db.update_settings("timestamp_format")) + for timestamp in Timestamp.instances: + timestamp.update() + if changed: + set.timestamp_format = value + setter_extra() + if imgui.begin_popup_context_item("###timestamp_format_context"): + utils.text_context(set, "timestamp_format", setter_extra) + imgui.end_popup() + + now = dt.datetime.now() + try: + timestamp = now.strftime(set.timestamp_format) + except Exception: + timestamp = "Bad format!" + draw_settings_label(f"Time: {timestamp}") + imgui.text("") + imgui.spacing() + + draw_settings_label( + "Date format:", + "The format expression to use for short datestamps. Uses the strftime specification. Default is '%b %d, %Y'." + ) + changed, value = imgui.input_text("###datestamp_format", set.datestamp_format) + def setter_extra(_=None): + async_thread.run(db.update_settings("datestamp_format")) + for datestamp in Datestamp.instances: + datestamp.update() + if changed: + set.datestamp_format = value + setter_extra() + if imgui.begin_popup_context_item("###datestamp_format_context"): + utils.text_context(set, "datestamp_format", setter_extra) + imgui.end_popup() + + try: + datestamp = now.strftime(set.datestamp_format) + except Exception: + datestamp = "Bad format!" + draw_settings_label(f"Date: {datestamp}") + imgui.text("") + imgui.spacing() + + draw_settings_label( + "Vsync ratio:", + "Vsync means that the framerate should be synced to the one your monitor uses. The ratio modifies this behavior. " + "A ratio of 1:0 means uncapped framerate, while all other numbers indicate the ratio between screen and app FPS. " + "For example a ratio of 1:2 means the app refreshes every 2nd monitor frame, resulting in half the framerate." + ) + changed, value = imgui.drag_int("###vsync_ratio", set.vsync_ratio, change_speed=0.05, min_value=0, max_value=10, format="1:%d") + set.vsync_ratio = min(max(value, 0), 10) + if changed: + glfw.swap_interval(set.vsync_ratio) + async_thread.run(db.update_settings("vsync_ratio")) + + draw_settings_label(f"Current framerate: {round(imgui.io.framerate, 3)}") + imgui.text("") + imgui.spacing() + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Labels"): + swap = None + for label_i, label in enumerate(Label.instances): + imgui.table_next_row() + imgui.table_next_column() + if imgui.button(icons.filter_plus_outline, width=frame_height): + flt = Filter(FilterMode.Label) + flt.match = label + self.filters.append(flt) + imgui.same_line() + if label_i == 0: + imgui.push_disabled() + if imgui.button(icons.arrow_up, width=frame_height): + swap = (label_i, label_i - 1) + if label_i == 0: + imgui.pop_disabled() + imgui.same_line() + if label_i == len(Label.instances) - 1: + imgui.push_disabled() + if imgui.button(icons.arrow_down, width=frame_height): + swap = (label_i, label_i + 1) + if label_i == len(Label.instances) - 1: + imgui.pop_disabled() + imgui.same_line() + changed, value = imgui.color_edit3(f"###label_color_{label.id}", *label.color[:3], flags=imgui.COLOR_EDIT_NO_INPUTS) + if changed: + label.color = (*value, 1.0) + async_thread.run(db.update_label(label, "color")) + imgui.same_line() + imgui.set_next_item_width(width - frame_height * 5 - imgui.style.cell_padding.x * 5 - imgui.style.scrollbar_size * (imgui.get_scroll_max_y() > 0.0)) + changed, value = imgui.input_text_with_hint(f"###label_name_{label.id}", "Label name", label.name) + setter_extra = lambda _=None: async_thread.run(db.update_label(label, "name")) + if changed: + label.name = value + setter_extra() + if imgui.begin_popup_context_item(f"###label_name_{label.id}_context"): + utils.text_context(label, "name", setter_extra) + imgui.end_popup() + imgui.same_line() + if imgui.button(icons.trash_can_outline, width=frame_height): + async_thread.run(db.delete_label(label)) + + if swap: + Label.instances[swap[0]], Label.instances[swap[1]] = Label.instances[swap[1]], Label.instances[swap[0]] + Label.update_positions() + for game in globals.games.values(): + game.labels.sort(key=lambda label: label.position) + async_thread.run(db.update_label_positions()) + + draw_settings_label("New label:") + if imgui.button("Add", width=right_width): + if len(Label.instances) == 63: + utils.push_popup( + msgbox.msgbox, "Alert!", + "You have reached the maximum number (63) of labels.", + MsgBox.warn + ) + else: + async_thread.run(db.create_label()) + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Manage"): + imgui.table_next_row() + imgui.table_next_column() + pos = imgui.get_cursor_pos() + imgui.table_next_column() + imgui.set_cursor_pos(pos) + imgui.begin_group() + if imgui.tree_node("Import", flags=imgui.TREE_NODE_SPAN_AVAILABLE_WIDTH): + offset = imgui.get_cursor_pos_x() - pos.x + if imgui.button("Thread links", width=-offset): + thread_links = builtins.type("_", (), dict(_=""))() + def popup_content(): + nonlocal thread_links + imgui.text( + "Any kind of F95zone thread link, preferably 1 per line. Will be parsed and cleaned,\n" + "so don't worry about tidiness and paste like it's anarchy!" + ) + _, thread_links._ = imgui.input_text_multiline( + "###import_links", + value=thread_links._, + width=min(self.scaled(600), imgui.io.display_size.x * 0.6), + height=imgui.io.display_size.y * 0.6 + ) + if imgui.begin_popup_context_item("###import_links_context"): + utils.text_context(thread_links, "_", no_icons=True) + imgui.end_popup() + buttons={ + f"{icons.check} Import": lambda: async_thread.run(callbacks.add_games(*utils.extract_thread_matches(thread_links._))), + f"{icons.cancel} Cancel": None + } + utils.push_popup( + utils.popup, "Import thread links", + popup_content, + buttons, + closable=True, + outside=False + ) + if imgui.button("F95 bookmarks", width=-offset): + utils.start_refresh_task(api.import_f95_bookmarks(), reset_bg_timers=False, notify_new_games=False) + if imgui.button("F95 watched threads", width=-offset): + utils.start_refresh_task(api.import_f95_watched_threads(), reset_bg_timers=False, notify_new_games=False) + if imgui.button("Browser bookmarks", width=-offset): + def callback(selected): + if selected: + async_thread.run(api.import_browser_bookmarks(selected)) + buttons={ + f"{icons.check} Ok": lambda: utils.push_popup(filepicker.FilePicker( + title="Select or drop bookmark file", + callback=callback + ).tick), + f"{icons.cancel} Cancel": None + } + utils.push_popup( + msgbox.msgbox, "Bookmark file", + "F95Checker can import your browser bookmarks using an exported bookmark HTML.\n" + "Exporting such a file may vary between browsers, but generally speaking you need to:\n" + " - Open your browser's bookmark manager\n" + " - Find an import / export section, menu or dropdown\n" + " - Click export as HTML\n" + " - Save the file in some place you can find easily\n" + "\n" + "Once you have done this click Ok and select this file.", + MsgBox.info, + buttons + ) + file_hover = imgui.is_item_hovered() + if imgui.button("URL Shortcut file", width=-offset): + def callback(selected): + if selected: + async_thread.run(api.import_url_shortcut(selected)) + utils.push_popup(filepicker.FilePicker( + title="Select or drop shortcut file", + callback=callback + ).tick), + file_hover = file_hover or imgui.is_item_hovered() + if file_hover: + self.draw_hover_text("You can also drag and drop .html and .url files into the window for this!", text=None, force=True) + imgui.tree_pop() + if imgui.tree_node("Export", flags=imgui.TREE_NODE_SPAN_AVAILABLE_WIDTH): + offset = imgui.get_cursor_pos_x() - pos.x + if imgui.button("Thread links", width=-offset): + thread_links = builtins.type("_", (), dict(_="\n".join(game.url for game in globals.games.values())))() + def popup_content(): + imgui.input_text_multiline( + "###export_links", + value=thread_links._, + width=min(self.scaled(600), imgui.io.display_size.x * 0.6), + height=imgui.io.display_size.y * 0.6, + flags=imgui.INPUT_TEXT_READ_ONLY + ) + if imgui.begin_popup_context_item("###export_links_context"): + utils.text_context(thread_links, "_", editable=False) + imgui.end_popup() + utils.push_popup( + utils.popup, "Export thread links", + popup_content, + buttons=True, + closable=True, + outside=False + ) + if imgui.button("Unknown tags", width=-offset): + unknown_tags = builtins.type("_", (), dict(_="\n".join(builtins.set(itertools.chain.from_iterable(game.unknown_tags for game in globals.games.values())))))() + def popup_content(): + imgui.input_text_multiline( + "###export_unknown_tags", + value=unknown_tags._, + width=min(self.scaled(600), imgui.io.display_size.x * 0.6), + height=imgui.io.display_size.y * 0.6, + flags=imgui.INPUT_TEXT_READ_ONLY + ) + if imgui.begin_popup_context_item("###export_unknown_tags_context"): + utils.text_context(unknown_tags, "_", editable=False) + imgui.end_popup() + utils.push_popup( + utils.popup, "Export unknown tags", + popup_content, + buttons=True, + closable=True, + outside=False + ) + imgui.tree_pop() + if imgui.tree_node("Clear", flags=imgui.TREE_NODE_SPAN_AVAILABLE_WIDTH): + offset = imgui.get_cursor_pos_x() - pos.x + if imgui.button("All cookies", width=-offset): + buttons = { + f"{icons.check} Yes": lambda: async_thread.run(db.update_cookies({})), + f"{icons.cancel} No": None + } + utils.push_popup( + msgbox.msgbox, "Clear cookies", + "Are you sure you want to clear your session cookies?\n" + "This will invalidate your login session, but might help\n" + "if you are having issues.", + MsgBox.warn, + buttons + ) + if imgui.button("RPDL session", width=-offset): + def clear_callback(): + globals.settings.rpdl_username = "" + globals.settings.rpdl_password = "" + globals.settings.rpdl_token = "" + async_thread.run(db.update_settings("rpdl_username", "rpdl_password", "rpdl_token")) + buttons = { + f"{icons.check} Yes": clear_callback, + f"{icons.cancel} No": None + } + utils.push_popup( + msgbox.msgbox, "Clear RPDL session", + "Are you sure you want to clear your RPDL session?\n" + "You will need to sign in to RPDL again to use torrents.", + MsgBox.warn, + buttons + ) + imgui.tree_pop() + imgui.end_group() + imgui.spacing() + + draw_settings_label( + "Ask exe on add:", + "When this is enabled you will be asked to select a game executable right after adding the game to F95Checker." + ) + draw_settings_checkbox("select_executable_after_add") + + draw_settings_label( + "Installed on add:", + "When this is enabled games will be marked as installed by default when first added to F95Checker." + ) + draw_settings_checkbox("mark_installed_after_add") + + draw_settings_label( + "Set exe dir:", + "This setting indicates what folder you keep all your games in (if you're organized). Executables inside this folder are " + "remembered relatively, this means you can select the executables with this setting on, then move your entire folder, update this " + "setting, and all the executables are still valid.\n" + "This setting is OS dependent, it has different values for different operating systems you use F95Checker on.\n\n" + f"Current value: {set.default_exe_dir.get(globals.os) or 'Unset'}" + ) + if imgui.button("Choose", width=right_width): + def select_callback(selected): + set.default_exe_dir[globals.os] = selected or "" + async_thread.run(db.update_settings("default_exe_dir")) + for game in globals.games.values(): + game.validate_executables() + utils.push_popup(filepicker.DirPicker( + title="Select or drop default exe dir", + start_dir=set.default_exe_dir.get(globals.os), + callback=select_callback + ).tick) + + draw_settings_label( + "Exe Wrappers:", + "Customize how executables are launched by default for each game engine / media type.\n" + "Useful for specifying custom ways to open them, like Wine/Proton for Windows games on Linux/macOS or a media player for animation/comic collections.\n" + "Can also be used to specify custom arguments, to the exe or to a wrapper." + ) + if imgui.button("Configure", width=right_width): + if wine.is_supported() and not wine.cache: + wine.refresh() + def popup_content(): + imgui.text( + "Customize how executables are launched by default for each game engine / media type.\n" + "Useful for specifying custom ways to open them, like Wine/Proton for Windows games\n" + "on Linux/macOS or a media player for animation/comic collections.\n" + "Can also be used to specify custom arguments, to the exe or to a wrapper.\n" + "\n" + "Use %command% to substitute in the executable path, for example:\n" + "- VLC.exe -LZ %command%\n" + "- %command% -dx12\n" + "- env WINEPREFIX=/path/to/prefix wine %command%\n" + "\n" + "Each engine/type can have a wrapper configured for it below, and individual games can\n" + "have it overridden from their info panel." + ) + + if wine.is_supported(): + imgui.spacing() + imgui.spacing() + imgui.push_font(imgui.fonts.bold) + imgui.text("Wine/Proton runners") + imgui.pop_font() + + imgui.text( + "Run games through a compatibility runner instead of leaving Windows builds to the system\n" + "wine. By default F95Checker will also specify separate prefixes for each engine." + ) + + imgui.spacing() + + imgui.align_text_to_frame_padding() + imgui.text(f"Runners found: {len(wine.cache)}") + imgui.same_line() + if imgui.button(f"{icons.reload_alert} Rescan"): + wine.refresh() + + if set.wine_extra_runners_dirs.get(globals.os) is None: + set.wine_extra_runners_dirs[globals.os] = [] + imgui.align_text_to_frame_padding() + imgui.text("Extra runner folders:") + imgui.same_line() + self.draw_hover_text( + "Only needed if a Steam install or a set of runners lives somewhere\n" + "unusual (eg Heroic, Lutris...). Steam's own libraries, including ones\n" + "on other drives, arealready found on their own.\n" + "\n" + "Accepts either a Steam installation or a folder holding runners." + ) + imgui.same_line() + if imgui.button(icons.plus): + set.wine_extra_runners_dirs[globals.os].append("") + async_thread.run(db.update_settings("wine_extra_runners_dirs")) + for i in reversed(range(len(set.wine_extra_runners_dirs[globals.os]))): + if imgui.button(icons.trash_can_outline): + del set.wine_extra_runners_dirs[globals.os][i] + async_thread.run(db.update_settings("wine_extra_runners_dirs")) + continue + imgui.same_line() + imgui.set_next_item_width(-imgui.FLOAT_MIN) + changed, value = imgui.input_text_with_hint( + f"###wine_extra_runners_dirs_{i}", + "A Steam install or runner folder found elsewhere", + set.wine_extra_runners_dirs[globals.os][i] + ) + if changed: + set.wine_extra_runners_dirs[globals.os][i] = value + async_thread.run(db.update_settings("wine_extra_runners_dirs")) + + imgui.spacing() + + imgui.align_text_to_frame_padding() + imgui.text("Prefixes folder:") + imgui.same_line() + self.draw_hover_text( + "Where Wine/Proton keep their files, this is also where saves will (usually) be buried.\n" + "These reach hundreds of MB each, so somewhere with room is worth picking.\n" + "\n" + "F95Checker will default to a separate prefix for each engine/type that is configured\n" + "below to use a Wine/Proton runner.\n" + "\n" + "Only applies to commands filled in after changing it." + ) + imgui.same_line() + imgui.set_next_item_width(-imgui.FLOAT_MIN) + changed, value = imgui.input_text_with_hint( + "###wine_prefixes_dir", str(wine.prefix_root()), set.wine_prefixes_dir.get(globals.os, "") + ) + if changed: + set.wine_prefixes_dir[globals.os] = value + async_thread.run(db.update_settings("wine_prefixes_dir")) + + if set.default_launch_wrapper.get(globals.os) is None: + set.default_launch_wrapper[globals.os] = {} + category = None + for type in Type: + if type.category is Category.Misc: + continue + if category is not type.category: + category = type.category + imgui.spacing() + imgui.spacing() + imgui.push_font(imgui.fonts.bold) + imgui.text(type.category.name) + imgui.pop_font() + current = set.default_launch_wrapper[globals.os].get(type, None) + wine_match = wine.match_runner(current) if wine.is_supported() else None + self.draw_type_widget(type) + imgui.same_line() + column_x = imgui.get_cursor_pos_x() + imgui.set_next_item_width(self.scaled(240)) + if imgui.begin_combo(f"###wrapper_{type.value}", wine_match or ("Custom" if current is not None else "None")): + if imgui.selectable("None", current is None)[0]: + set.default_launch_wrapper[globals.os].pop(type, None) + current = None + async_thread.run(db.update_settings("default_launch_wrapper")) + if imgui.selectable("Custom", current is not None and not wine_match)[0]: + set.default_launch_wrapper[globals.os][type] = "%command%" + current = set.default_launch_wrapper[globals.os][type] + async_thread.run(db.update_settings("default_launch_wrapper")) + if wine.is_supported(): + for name, path in wine.cache: + if imgui.selectable(name, name == wine_match)[0]: + set.default_launch_wrapper[globals.os][type] = wine.build_wrapper( + path, wine.prefix_for(type.name) + ) + async_thread.run(db.update_settings("default_launch_wrapper")) + imgui.end_combo() + if current is not None: + imgui.set_cursor_pos_x(column_x) + imgui.align_text_to_frame_padding() + imgui.set_next_item_width(-imgui.FLOAT_MIN) + changed, value = imgui.input_text(f"###wrapper_text_{type.value}", current) + if changed: + set.default_launch_wrapper[globals.os][type] = value + async_thread.run(db.update_settings("default_launch_wrapper")) + utils.push_popup( + utils.popup, "Exe wrappers", + popup_content, + {f"{icons.check} Done": None}, + closable=True, + outside=True + ) + + draw_settings_label( + "Downloads dir:", + "Where downloads will be saved to. Currently, only F95zone Donor DDL downloads are supported in F95Checker, but this " + "setting is also used for saving RPDL torrent files.\n" + "This setting is OS dependent, it has different values for different operating systems you use F95Checker on.\n\n" + f"Current value: {set.downloads_dir.get(globals.os) or pathlib.Path.home() / 'Downloads'}\n\n" + "For other download types, you may want to consider a download manager like JDownloader2, and configure it to monitor the " + "clipboard: then you will be able to copy links and have them automatically download in your external download manager." + ) + if imgui.button("Choose", width=right_width): + def select_callback(selected): + set.downloads_dir[globals.os] = selected or "" + async_thread.run(db.update_settings("downloads_dir")) + utils.push_popup(filepicker.DirPicker( + title="Select or drop downloads dir", + start_dir=set.downloads_dir.get(globals.os), + callback=select_callback + ).tick) + + draw_settings_label("Show remove button:") + draw_settings_checkbox("show_remove_btn") + + draw_settings_label("Confirm when removing:") + draw_settings_checkbox("confirm_on_remove") + + draw_settings_label( + "Weighted score:", + "Use weighted rating algorithm when sorting table by forum score.\n" + "You can see the final value used by hovering over the score number." + ) + if draw_settings_checkbox("weighted_score"): + self.recalculate_ids = True + + draw_settings_label( + "Custom game:", + "Add a custom game that is untied from F95zone. Useful for games removed for breaking forum rules, or for adding games " + "from other platforms. Custom games are not checked for updates and you have to add the core details (name, url, version...) " + "yourself. You can later convert a custom game to an F95zone game from the info popup." + ) + if imgui.button("Add", width=right_width): + game_id = async_thread.wait(db.create_game(custom=True)) + async_thread.wait(db.load_games(game_id)) + game = globals.games[game_id] + utils.push_popup(self.draw_game_info_popup, game, None) + if globals.settings.mark_installed_after_add: + game.installed = game.version + if globals.settings.select_executable_after_add: + callbacks.add_game_exe(game) + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Proxy"): + draw_settings_label( + "Type:", + "All listed proxy types work with the main F95Checker functionality.\n\n" + "The integrated browser (also used for login) instead has some limitations due to Qt:\n" + "- SOCKS4 is not supported at all\n" + "- SOCKS5 with authentication won't work\n" + "- HTTP with authentication is not implemented" + ) + changed, value = imgui.combo("###proxy_type", set.proxy_type._index_, ProxyType._member_names_) + if changed: + set.proxy_type = ProxyType[ProxyType._member_names_[value]] + async_thread.run(db.update_settings("proxy_type")) + api.make_session() + + if set.proxy_type is ProxyType.Disabled: + imgui.push_disabled() + + draw_settings_label( + "Host:", + "Domain or IP address of proxy server.\n" + "For example: 127.0.0.1, myproxy.example.com" + ) + changed, value = imgui.input_text_with_hint("###proxy_host", "Domain/IP", set.proxy_host) + if changed: + set.proxy_host = value + async_thread.run(db.update_settings("proxy_host")) + api.make_session() + + draw_settings_label("Port:") + changed, value = imgui.drag_int("###proxy_port", set.proxy_port, change_speed=0.5, min_value=1, max_value=65535) + set.proxy_port = min(max(value, 1), 65535) + if changed: + set.proxy_port = int(value) + async_thread.run(db.update_settings("proxy_port")) + api.make_session() + + draw_settings_label("Username:", "Leave empty if proxy does not require authentication") + changed, value = imgui.input_text("###proxy_username", set.proxy_username) + if changed: + set.proxy_username = value + async_thread.run(db.update_settings("proxy_username")) + api.make_session() + + draw_settings_label("Password:", "Leave empty if proxy does not require authentication") + changed, value = imgui.input_text( + "###proxy_password", + set.proxy_password, + flags=imgui.INPUT_TEXT_PASSWORD, + ) + if changed: + set.proxy_password = value + async_thread.run(db.update_settings("proxy_password")) + api.make_session() + + if set.proxy_type is ProxyType.Disabled: + imgui.pop_disabled() + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Refresh"): + draw_settings_label("Check alerts and inbox:") + draw_settings_checkbox("check_notifs") + + draw_settings_label( + "Update notifs banners:", + "Whether to include a banner image when sending desktop notifications for updates." + ) + draw_settings_checkbox("notifs_show_update_banner") + + draw_settings_label("Refresh if archived:") + draw_settings_checkbox("refresh_archived_games") + + draw_settings_label("Refresh if completed:") + draw_settings_checkbox("refresh_completed_games") + + draw_settings_label( + "Connections:", + "Games are checked 10 at a time for updates, and of those only those with new data are fetched for all game " + "info from the F95Checker Cache API. This setting determines how many of those can be fetched simultaneously. " + "In most cases 10 should be fine, but lower it if your internet struggles when doing a full refresh." + ) + changed, value = imgui.drag_int("###max_connections", set.max_connections, change_speed=0.5, min_value=1, max_value=10) + set.max_connections = min(max(value, 1), 10) + if changed: + async_thread.run(db.update_settings("max_connections")) + + draw_settings_label( + "Timeout:", + "To check for updates, notifications and other functionality, F95Checker sends web requests (to its dedicated " + "Cache API, to F95zone itself, and to other third-parties like RPDL.net if you so choose). However this can sometimes " + "go wrong. The timeout is the maximum amount of seconds that a request can try to connect for before it fails.\n" + "A timeout of 10-30 seconds is most typical." + ) + changed, value = imgui.drag_int("###request_timeout", set.request_timeout, change_speed=0.6, min_value=1, max_value=120, format="%d sec") + set.request_timeout = min(max(value, 1), 120) + if changed: + async_thread.run(db.update_settings("request_timeout")) + + draw_settings_label( + "Retries:", + "While refreshing, a lot of web requests are made quite quickly, so some of them might fail. This setting " + "determines how many times a failed request will be reattempted before failing completely. However these " + "connection errors are often caused by misconfigured connections and timeout values, so try to tinker with those " + "instead of the retries value. This setting should only be used if you know your connection is very unreliable.\n" + "Usually 2 max retries are fine for stable connections." + ) + changed, value = imgui.drag_int("###max_retries", set.max_retries, change_speed=0.05, min_value=0, max_value=10) + set.max_retries = min(max(value, 0), 10) + if changed: + async_thread.run(db.update_settings("max_retries")) + + draw_settings_label( + "No semaphore timeout:", + "If you are having connection issues specifically with 'WinError 121' and 'The semaphore timeout period has expired' " + "then try to enable this option, it will suppress these errors and retry all connections as if they never happened. " + "However this type of error is usually caused by hardware or driver issues, or some bad Windows updates. It is recommended " + "you first try to repair your system with sfc and DISM (Google them) and update your drivers. Use this option as a last resort." + ) + draw_settings_checkbox("ignore_semaphore_timeouts") + + draw_settings_label(f"Insecure SSL:") + draw_settings_checkbox("insecure_ssl") + + draw_settings_label(f"Async tasks count: {sum((0 if task.done() else 1) for task in asyncio.all_tasks(loop=async_thread.loop))}") + imgui.text("") + imgui.spacing() + + draw_settings_label( + "BG interval:", + "When F95Checker is in background mode it automatically refreshes your games periodically. This " + "controls how often (in minutes) this happens." + ) + changed, value = imgui.drag_int("###bg_refresh_interval", set.bg_refresh_interval, change_speed=4.0, min_value=30, max_value=1440, format="%d min") + set.bg_refresh_interval = min(max(value, 30), 1440) + if changed: + async_thread.run(db.update_settings("bg_refresh_interval")) + + if not set.check_notifs: + imgui.push_disabled() + + draw_settings_label( + "BG notifs intv:", + "When F95Checker is in background mode it automatically checks your notifications periodically. This " + "controls how often (in minutes) this happens." + ) + changed, value = imgui.drag_int("###bg_notifs_interval", set.bg_notifs_interval, change_speed=4.0, min_value=15, max_value=1440, format="%d min") + set.bg_notifs_interval = min(max(value, 15), 1440) + if changed: + async_thread.run(db.update_settings("bg_notifs_interval")) + + if not set.check_notifs: + imgui.pop_disabled() + + draw_settings_label(f"Last refresh: {set.last_successful_refresh.display or 'Never'}") + imgui.text("") + imgui.spacing() + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Startup"): + draw_settings_label("Refresh at start:") + draw_settings_checkbox("start_refresh") + + draw_settings_label( + "Start in BG:", + "F95Checker will start in background mode, hidden in the system tray." + ) + draw_settings_checkbox("start_in_background") + + draw_settings_label("Start with system:") + imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) + changed, value = imgui.checkbox("###start_with_system", globals.start_with_system) + if changed: + callbacks.update_start_with_system(value) + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Style"): + draw_settings_label("Corner radius:") + changed, value = imgui.drag_int("###style_corner_radius", set.style_corner_radius, change_speed=0.04, min_value=0, max_value=6, format="%d px") + set.style_corner_radius = min(max(value, 0), 6) + if changed: + imgui.style.window_rounding = imgui.style.frame_rounding = imgui.style.tab_rounding = \ + imgui.style.child_rounding = imgui.style.grab_rounding = imgui.style.popup_rounding = \ + imgui.style.scrollbar_rounding = globals.settings.style_corner_radius + async_thread.run(db.update_settings("style_corner_radius")) + + draw_settings_label("Accent:") + draw_settings_color("style_accent") + + draw_settings_label("Background:") + draw_settings_color("style_bg") + + draw_settings_label("Alt background:") + draw_settings_color("style_alt_bg") + + draw_settings_label("Border:") + draw_settings_color("style_border") + + draw_settings_label("Text:") + draw_settings_color("style_text") + + draw_settings_label("Text dim:") + draw_settings_color("style_text_dim") + + draw_settings_label("Defaults:") + if imgui.button("Restore", width=right_width): + set.style_corner_radius = DefaultStyle.corner_radius + set.style_accent = colors.hex_to_rgba_0_1(DefaultStyle.accent) + set.style_alt_bg = colors.hex_to_rgba_0_1(DefaultStyle.alt_bg) + set.style_bg = colors.hex_to_rgba_0_1(DefaultStyle.bg) + set.style_border = colors.hex_to_rgba_0_1(DefaultStyle.border) + set.style_text = colors.hex_to_rgba_0_1(DefaultStyle.text) + set.style_text_dim = colors.hex_to_rgba_0_1(DefaultStyle.text_dim) + self.refresh_styles() + async_thread.run(db.update_settings( + "style_corner_radius", + "style_accent", + "style_alt_bg", + "style_bg", + "style_border", + "style_text", + "style_text_dim", + )) + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Tabs"): + draw_settings_label("Filter all tabs:") + if draw_settings_checkbox("filter_all_tabs"): + self.recalculate_ids = True + + draw_settings_label("Hide empty tabs:") + draw_settings_checkbox("hide_empty_tabs") + + draw_settings_label( + f"Default {icons.arrow_left_right} New", + "Change 'Default' tab to 'New'. Only a visual change.\n" + "Can be useful if you only store new games in default tab." + ) + draw_settings_checkbox("default_tab_is_new") + + draw_settings_label( + "Independent views:", + "Each tab will have its own sorting/column preferences." + ) + if draw_settings_checkbox("independent_tab_views"): + self.recalculate_ids = True + + imgui.end_table() + imgui.spacing() + + if draw_settings_section("Background", collapsible=False): + draw_settings_label( + "BG mode:", + "When in background mode, F95Checker hides the main window and only keeps the icon in the system tray. The main feature of " + "background mode is periodic refreshing: your list will be automatically refreshed at regular intervals and you will receive " + "a desktop notification if some updates / notifications have been found." + ) + if imgui.button("Switch", width=right_width): + self.hide() + + imgui.end_table() + + if api.downloads: + imgui.text("\nDonor DDL:\n") + to_remove = [] + for download_id, download in api.downloads.items(): + if not download: + continue + if download.state == download.State.Removed: + to_remove.append(download_id) + continue + imgui.spacing() + imgui.spacing() + imgui.spacing() + imgui.text(download.name) + errored = download.error or download.progress != download.total + + if download.state == download.State.Downloading: + space_after = ( + 1 * ( + 2 * imgui.style.frame_padding.y + + imgui.style.frame_border_size + + imgui.style.item_spacing.x + ) + + imgui.calc_text_size(icons.stop).x + + imgui.style.frame_border_size + ) + elif download.state == download.State.Stopped: + if imgui.button(icons.folder_open_outline): + extracted_dir = download.extracted if not errored else None + async_thread.run(callbacks.default_open(extracted_dir or download.path.parent)) + imgui.same_line() + if not errored: + if imgui.button(icons.open_in_app): + async_thread.run(callbacks.default_open(download.path)) + imgui.same_line() + space_after = ( + 2 * ( + 2 * imgui.style.frame_padding.y + + imgui.style.frame_border_size + + imgui.style.item_spacing.x + ) + + imgui.calc_text_size(icons.cancel + icons.trash_can_outline).x + + imgui.style.frame_border_size + ) + else: + space_after = 0 + + ratio = download.progress / (download.total or 1) + downd = download.progress + speed = downd / ((download.current - download.start) or 1) + width = imgui.get_content_region_available_width() - space_after + height = imgui.get_frame_height() + imgui.progress_bar(ratio, (width, height)) + if download.state == download.State.Downloading: + text = f"{utils.sizeof_fmt(downd):>7} {min(ratio, 99):>3.0%} ({utils.sizeof_fmt(speed):>7}/s)" + elif download.state == download.State.Stopped: + if not errored: + text = "Done!" + else: + text = "Error!" + self.draw_hover_text( + download.error or f"Received less data than expected ({download.progress} != {download.total})", + text=None, + ) + if download.traceback and imgui.is_item_clicked(): + utils.push_popup( + msgbox.msgbox, f"Error downloading {download.name}", + download.error, + MsgBox.error, + more=download.traceback, + ) + else: + text = f"{download.state.name}..." + imgui.same_line() + imgui.push_font(imgui.fonts.mono_sm) + draw_list = imgui.get_window_draw_list() + col = imgui.get_color_u32_rgba(1, 1, 1, 1) + text_size = imgui.calc_text_size(text) + screen_pos = imgui.get_cursor_screen_pos() + text_x = screen_pos.x - (width + text_size.x) / 2 - imgui.style.item_spacing.x + text_y = screen_pos.y + (height - text_size.y) / 2 + draw_list.add_text(text_x, text_y, col, text) + imgui.pop_font() + + if download.state == download.State.Downloading: + if was_canceling := download.cancel: + imgui.push_disabled() + if imgui.button(icons.stop): + download.cancel = True + if was_canceling: + imgui.pop_disabled() + elif download.state == download.State.Stopped: + if imgui.button(icons.cancel): + to_remove.append(download_id) + imgui.same_line() + if imgui.button(icons.trash_can_outline): + async_thread.run(download.delete()) + for download_id in to_remove: + del api.downloads[download_id] + + imgui.spacing() + imgui.spacing() + imgui.spacing() + imgui.end_child() + + +class TrayIcon(QtWidgets.QSystemTrayIcon): + def __init__(self, main_gui: MainGUI): + self.show_gui_events = [ + QtWidgets.QSystemTrayIcon.ActivationReason.DoubleClick, + QtWidgets.QSystemTrayIcon.ActivationReason.MiddleClick, + QtWidgets.QSystemTrayIcon.ActivationReason.Trigger + ] + self.main_gui = main_gui + self.idle_icon = QtGui.QIcon(str(globals.self_path / 'resources/icons/logo.png')) + self.paused_icon = QtGui.QIcon(str(globals.self_path / 'resources/icons/paused.png')) + super().__init__(self.idle_icon) + + self.watermark = QtGui.QAction(f"F95Checker {globals.version_name}") + self.watermark.triggered.connect(lambda *_: self.main_gui.show()) + + self.next_refresh = QtGui.QAction("Next Refresh: N/A") + self.next_refresh.setEnabled(False) + + self.refresh_btn = QtGui.QAction("Refresh Now!") + self.refresh_btn.triggered.connect(lambda *_: globals.refresh_task.cancel() if utils.is_refreshing() else utils.start_refresh_task(api.refresh())) + + def update_pause(*_): + self.main_gui.bg_mode_paused = not self.main_gui.bg_mode_paused + if self.main_gui.bg_mode_paused: + self.main_gui.bg_mode_timer = None + self.main_gui.bg_mode_notifs_timer = None + self.update_status() + self.toggle_pause = QtGui.QAction("Pause Auto Refresh") + self.toggle_pause.triggered.connect(update_pause) + + self.toggle_gui = QtGui.QAction("Toggle GUI") + self.toggle_gui.triggered.connect(lambda *_: self.main_gui.show() if self.main_gui.hidden else self.main_gui.hide()) + + self.quit = QtGui.QAction("Quit") + self.quit.triggered.connect(self.main_gui.close) + + self.menu = QtWidgets.QMenu() + self.menu.addAction(self.watermark) + self.menu.addAction(self.next_refresh) + self.menu.addAction(self.refresh_btn) + self.menu.addAction(self.toggle_pause) + self.menu.addAction(self.toggle_gui) + self.menu.addAction(self.quit) + if globals.os is not Os.MacOS: + self.setContextMenu(self.menu) + self.menu_open = False + self.menu.aboutToShow.connect(self.showing_menu) + self.menu.aboutToHide.connect(self.hiding_menu) + self.menu.aboutToShow.connect(self.update_menu) + + self.activated.connect(self.activated_filter) + + self.show() + + def update_icon(self, *_): + if self.main_gui.bg_mode_paused and self.main_gui.hidden: + self.setIcon(self.paused_icon) + else: + self.setIcon(self.idle_icon) + + def animate_refresh_icon(self): + icn = 1 + (int(time.time() * 8) % 16) + self.setIcon(QtGui.QIcon(str(globals.self_path / f"resources/icons/refreshing{icn}.png"))) + + def showing_menu(self, *_): + self.menu_open = True + + def hiding_menu(self, *_): + self.menu_open = False + + def update_menu(self, *_): + if self.main_gui.hidden: + if self.main_gui.bg_mode_paused: + next_refresh = "Paused" + elif self.main_gui.bg_mode_timer or self.main_gui.bg_mode_notifs_timer: + next_refresh = dt.datetime.fromtimestamp( + min( + self.main_gui.bg_mode_timer or globals.settings.bg_refresh_interval, + self.main_gui.bg_mode_notifs_timer or globals.settings.bg_notifs_interval + ) + ).strftime("%H:%M") + elif utils.is_refreshing(): + next_refresh = "Now" + else: + next_refresh = "N/A" + self.next_refresh.setText(f"Next Refresh: {next_refresh}") + self.next_refresh.setVisible(True) + else: + self.next_refresh.setVisible(False) + + if utils.is_refreshing(): + self.refresh_btn.setText("Cancel Refresh") + else: + self.refresh_btn.setText("Refresh Now!") + + if self.main_gui.hidden: + if self.main_gui.bg_mode_paused: + self.toggle_pause.setText("Unpause Auto Refresh") + else: + self.toggle_pause.setText("Pause Auto Refresh") + self.toggle_pause.setVisible(True) + else: + self.toggle_pause.setVisible(False) + + if self.main_gui.hidden: + self.toggle_gui.setText("Switch to GUI") + else: + self.toggle_gui.setText("Switch to BG") + + def update_status(self, *_): + self.update_menu() + self.update_icon() + + def activated_filter(self, reason: QtWidgets.QSystemTrayIcon.ActivationReason): + if globals.os is Os.MacOS and reason == QtWidgets.QSystemTrayIcon.ActivationReason.Context: + self.menu.popup(QtGui.QCursor.pos()) + elif reason in self.show_gui_events: + self.main_gui.show() From 4e91f0b558a80372417f886d71ff51bb910f410a Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:06:08 +0200 Subject: [PATCH 06/25] Add files via upload --- structs.py | 1322 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1322 insertions(+) create mode 100644 structs.py diff --git a/structs.py b/structs.py new file mode 100644 index 00000000..7bc404b2 --- /dev/null +++ b/structs.py @@ -0,0 +1,1322 @@ +import asyncio +import dataclasses +import datetime as dt +import enum +import functools +import hashlib +import json +import os +import pathlib +import shutil +import sys +import time +import typing +import weakref + +from external import weakerset +from modules import colors + + +class CounterContext: + __slots__ = ("count",) + + def __init__(self): + self.count = 0 + + def __enter__(self): + self.count += 1 + + def __exit__(self, exc_type, exc_val, exc_tb): + self.count -= 1 + + async def __aenter__(self): + self.__enter__() + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self.__exit__(exc_type, exc_val, exc_tb) + + +class Popup(functools.partial): + next_uuid = 0 + + __slots__ = ("open", "uuid",) + + def __init__(self, *_, **__): + from modules import utils + self.open = True + cls = type(self) + uuid = cls.next_uuid + cls.next_uuid += 1 + self.uuid = f"{uuid}_{str(time.time()).split('.')[-1]}_{utils.rand_num_str()}" + + def __call__(self, *args, **kwargs): + if not self.open: + return 0, True + opened, closed = super().__call__(*args, popup_uuid=self.uuid, **kwargs) + if closed: + self.open = False + return opened, closed + + +class DaemonProcess: + __slots__ = ("finalize",) + + def __init__(self, proc): + self.finalize = weakref.finalize(proc, self.kill, proc) + + @staticmethod + def kill(proc): + try: + # Multiprocessing + if getattr(proc, "exitcode", False) is None: + proc.kill() + # Asyncio subprocess + elif getattr(proc, "returncode", False) is None: + proc.kill() + # Standard subprocess + elif getattr(proc, "poll", lambda: False)() is None: + proc.kill() + except Exception: + pass + + def __enter__(self): + return self + + def __exit__(self, *_): + self.finalize() + + +class AbstractPipe: + def __init__(self): + raise NotImplementedError() + + async def get_async(self): + raise NotImplementedError() + + def put(self, data: dict | list | str): + raise NotImplementedError() + + +class DaemonPipe(AbstractPipe): + __slots__ = ("proc", "daemon",) + + class DaemonPipeExit(Exception): + __slots__ = () + + def __init__(self, proc: asyncio.subprocess.Process): + self.proc = proc + self.daemon = DaemonProcess(proc) + + async def is_alive(self): + await asyncio.sleep(0) + return self.proc.returncode is None + + async def get_async(self): + assert self.proc.stdout + while await self.is_alive() and not self.proc.stdout.at_eof(): + line = await self.proc.stdout.readline() + try: + return json.loads(line) + except json.JSONDecodeError: + pass + raise self.DaemonPipeExit() + + def put(self, data: dict | list | str): + assert self.proc.stdin + if self.proc.returncode is None: + self.proc.stdin.write(json.dumps(data).encode() + b"\n") + return + raise self.DaemonPipeExit() + + def __enter__(self): + self.daemon.__enter__() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.daemon.__exit__() + if exc_type is self.DaemonPipeExit: + return True + + def kill(self): + self.daemon.__exit__() + + +class ChildPipe(AbstractPipe): + __slots__ = ("loop", "stdin_event") + + def __init__(self): + try: + self.loop = asyncio.get_running_loop() + except RuntimeError: + self.loop = None + + if self.loop and sys.stdin: + self.stdin_event = asyncio.Event() + try: + self.loop.add_reader(sys.stdin, self.stdin_event.set) + except NotImplementedError: + self.stdin_event = None + + async def get(self): + assert sys.stdin + while True: + line = sys.stdin.readline() + try: + return json.loads(line) + except json.JSONDecodeError: + pass + + async def get_async(self): + assert sys.stdin + assert self.loop + while True: + if self.stdin_event: + await self.stdin_event.wait() + self.stdin_event.clear() + line = await self.loop.run_in_executor(None, sys.stdin.readline) + if not line: + return None + try: + return json.loads(line) + except json.JSONDecodeError: + pass + await asyncio.sleep(0) + + def put(self, data: dict | list | str): + assert sys.stdout + print(json.dumps(data), flush=True) + + +class Timestamp: + instances = weakerset.WeakerSet() + + __slots__ = ("value", "_display", "__weakref__",) + + def __init__(self, unix_time: int | float): + self.update(unix_time) + type(self).instances.add(self) + + def update(self, unix_time: int | float = None): + if unix_time is not None: + self.value = int(unix_time) + self._display = None + + @property + def format(self): + from modules import globals + return globals.settings.timestamp_format + + @property + def display(self): + if self._display is None: + if self.value == 0: + self._display = "" + else: + try: + self._display = dt.datetime.fromtimestamp(self.value).strftime(self.format) + except Exception: + self._display = "Bad format!" + return self._display + + +class Datestamp(Timestamp): + instances = weakerset.WeakerSet() + + __slots__ = () + + def __init__(self, unix_time: int | float): + self.update(unix_time) + type(self).instances.add(self) + + @property + def format(self): + from modules import globals + return globals.settings.datestamp_format + + +class DefaultStyle: + accent = "#d4202e" + alt_bg = "#101010" + bg = "#0a0a0a" + border = "#454545" + corner_radius = 6 + text = "#ffffff" + text_dim = "#808080" + + +@dataclasses.dataclass(slots=True) +class ThreadMatch: + title: str + id: int + + +@dataclasses.dataclass(slots=True) +class SearchResult: + title: str + creator: str + url: str + id: int + + +@dataclasses.dataclass(slots=True) +class TorrentResult: + id: int + title: str + size: int | str + seeders: int | str + leechers:int | str + date: int | str + + def __post_init__(self): + from modules import ( + globals, + utils, + ) + self.size = utils.sizeof_fmt(self.size) + self.seeders = str(self.seeders) + self.leechers = str(self.leechers) + self.date = dt.datetime.fromtimestamp(self.date).strftime(globals.settings.datestamp_format) + + +@dataclasses.dataclass(slots=True) +class DdlFile: + thread_id: int + id: str + title: str + filename: str + size: int + date: str + sha1: str + size_display: str = None + + def __post_init__(self): + if not self.id: + return + from modules import ( + globals, + utils, + ) + self.size_display = utils.sizeof_fmt(int(self.size)) + self.date = dt.datetime.strptime(self.date, r"%Y-%m-%d").strftime(globals.settings.datestamp_format) + + +@dataclasses.dataclass(slots=True) +class FileDownload: + name: str + url: str = "" + cookies: dict = True + checksum: tuple[str, str] = None + path: pathlib.Path = None + path_nest_level: int = 0 + progress: int = 0 + total: int = None + + class State(enum.IntEnum): + Preparing = enum.auto() + Downloading = enum.auto() + Verifying = enum.auto() + Extracting = enum.auto() + Stopped = enum.auto() + Deleting = enum.auto() + Removed = enum.auto() + + cancel: bool = False + state: State = State.Preparing + start: Timestamp = time.time() + current: Timestamp = time.time() + extracted: pathlib.Path = None + error: str = None + traceback: str = None + + async def delete(self): + assert self.state == self.State.Stopped + self.state = self.State.Deleting + try: + loop = asyncio.get_event_loop() + if self.extracted: + await loop.run_in_executor(None, functools.partial(shutil.rmtree, self.extracted, ignore_errors=True)) + await loop.run_in_executor(None, functools.partial(self.path.unlink, missing_ok=True)) + path = self.path + for _ in range(self.path_nest_level): + path = path.parent + if next(path.iterdir(), None) is None: + await loop.run_in_executor(None, functools.partial(path.rmdir)) + except Exception: + pass + self.state = self.State.Removed + + +@dataclasses.dataclass(slots=True) +class SortSpec: + index: int + reverse: bool + + +class IntEnumHack(enum.IntEnum): + def __new__(cls, value, attrs: dict = None): + self = int.__new__(cls, value) + self._value_ = value + # Add additional attributes + if isinstance(attrs, dict): + for key, value in attrs.items(): + setattr(self, key, value) + return self + def __init__(self, *args, **kwargs): + cls = type(self) + # Add index for use with _member_names_ + self._index_ = len(cls._member_names_) # self is added later, so the length is up to the previous item, so not len() - 1 + # Replace spaces with _, - with __ and add _ in front if starting with a number. Allows using Enum._1_special__name in code for "1 special-name" + new_name = "_" * self._name_[0].isdigit() + self._name_.replace(" ", "_").replace("-", "__") + if new_name != self._name_: + setattr(cls, new_name, self) + + +Os = IntEnumHack("Os", [ + ("Windows", 1), + ("Linux", 2), + ("MacOS", 3), +]) + + +DisplayMode = IntEnumHack("DisplayMode", [ + ("list", (1, {"icon": "view_agenda_outline"})), + ("grid", (2, {"icon": "view_grid_outline"})), + ("kanban", (3, {"icon": "view_week_outline"})), +]) + + +Status = IntEnumHack("Status", [ + ("Normal", (1, {"color" : (0.95, 0.95, 0.95), "icon": "lightning_bolt_circle"})), + ("Completed", (2, {"color" : (0.00, 0.87, 0.00), "icon": "checkbox_marked_circle"})), + ("OnHold", (3, {"color" : (0.00, 0.50, 0.95), "icon": "pause_circle"})), + ("Abandoned", (4, {"color" : (0.87, 0.20, 0.20), "icon": "close_circle"})), + ("Unchecked", (5, {"color" : (0.50, 0.50, 0.50), "icon": "alert_circle"})), + ("Custom", (6, {"color" : (0.95, 0.50, 0.00), "icon": "dots_horizontal_circle"})), + ("Unknown", (7, {"color" : (0.50, 0.50, 0.50), "icon": "alert_circle"})), +]) + + +Tag = IntEnumHack("Tag", [ + ("2d-game", (1, {"text": "2d game"})), + ("2dcg", (2, {"text": "2dcg"})), + ("3d-game", (3, {"text": "3d game"})), + ("3dcg", (4, {"text": "3dcg"})), + ("adventure", (5, {"text": "adventure"})), + ("ahegao", (6, {"text": "ahegao"})), + ("ai-cg", (140, {"text": "ai cg"})), + ("anal-sex", (7, {"text": "anal sex"})), + ("animated", (8, {"text": "animated"})), + ("asset-addon", (9, {"text": "asset-addon"})), + ("asset-ai-shoujo", (10, {"text": "asset-ai-shoujo"})), + ("asset-animal", (11, {"text": "asset-animal"})), + ("asset-animation", (12, {"text": "asset-animation"})), + ("asset-audio", (13, {"text": "asset-audio"})), + ("asset-bundle", (14, {"text": "asset-bundle"})), + ("asset-character", (15, {"text": "asset-character"})), + ("asset-clothing", (16, {"text": "asset-clothing"})), + ("asset-daz-gen1", (151, {"text": "asset-daz-gen1"})), + ("asset-daz-gen2", (141, {"text": "asset-daz-gen2"})), + ("asset-daz-gen3", (142, {"text": "asset-daz-gen3"})), + ("asset-daz-gen8", (143, {"text": "asset-daz-gen8"})), + ("asset-daz-gen81", (144, {"text": "asset-daz-gen81"})), + ("asset-daz-gen9", (145, {"text": "asset-daz-gen9"})), + ("asset-daz-m4", (152, {"text": "asset-daz-m4"})), + ("asset-daz-v4", (153, {"text": "asset-daz-v4"})), + ("asset-environment", (17, {"text": "asset-environment"})), + ("asset-expression", (18, {"text": "asset-expression"})), + ("asset-female", (146, {"text": "asset-female"})), + ("asset-hair", (19, {"text": "asset-hair"})), + ("asset-hdri", (20, {"text": "asset-hdri"})), + ("asset-honey-select", (21, {"text": "asset-honey-select"})), + ("asset-honey-select2", (22, {"text": "asset-honey-select2"})), + ("asset-koikatu", (23, {"text": "asset-koikatu"})), + ("asset-light", (24, {"text": "asset-light"})), + ("asset-male", (147, {"text": "asset-male"})), + ("asset-morph", (25, {"text": "asset-morph"})), + ("asset-nonbinary", (148, {"text": "asset-nonbinary"})), + ("asset-playhome", (150, {"text": "asset-playhome"})), + ("asset-plugin", (26, {"text": "asset-plugin"})), + ("asset-pose", (27, {"text": "asset-pose"})), + ("asset-prop", (28, {"text": "asset-prop"})), + ("asset-scene", (149, {"text": "asset-scene"})), + ("asset-script", (29, {"text": "asset-script"})), + ("asset-shader", (30, {"text": "asset-shader"})), + ("asset-texture", (31, {"text": "asset-texture"})), + ("asset-utility", (32, {"text": "asset-utility"})), + ("asset-vehicle", (33, {"text": "asset-vehicle"})), + ("bdsm", (34, {"text": "bdsm"})), + ("bestiality", (35, {"text": "bestiality"})), + ("big-ass", (36, {"text": "big ass"})), + ("big-tits", (37, {"text": "big tits"})), + ("blackmail", (38, {"text": "blackmail"})), + ("bukkake", (39, {"text": "bukkake"})), + ("censored", (40, {"text": "censored"})), + ("character-creation", (41, {"text": "character creation"})), + ("cheating", (42, {"text": "cheating"})), + ("combat", (43, {"text": "combat"})), + ("corruption", (44, {"text": "corruption"})), + ("cosplay", (45, {"text": "cosplay"})), + ("creampie", (46, {"text": "creampie"})), + ("dating-sim", (47, {"text": "dating sim"})), + ("dilf", (48, {"text": "dilf"})), + ("drugs", (49, {"text": "drugs"})), + ("dystopian-setting", (50, {"text": "dystopian setting"})), + ("exhibitionism", (51, {"text": "exhibitionism"})), + ("fantasy", (52, {"text": "fantasy"})), + ("female-protagonist", (54, {"text": "female protagonist"})), + ("femaledomination", (53, {"text": "female domination"})), + ("footjob", (55, {"text": "footjob"})), + ("furry", (56, {"text": "furry"})), + ("futa-trans", (57, {"text": "futa/trans"})), + ("futa-trans-protagonist", (58, {"text": "futa/trans protagonist"})), + ("gay", (59, {"text": "gay"})), + ("graphic-violence", (60, {"text": "graphic violence"})), + ("groping", (61, {"text": "groping"})), + ("group-sex", (62, {"text": "group sex"})), + ("handjob", (63, {"text": "handjob"})), + ("harem", (64, {"text": "harem"})), + ("horror", (65, {"text": "horror"})), + ("humiliation", (66, {"text": "humiliation"})), + ("humor", (67, {"text": "humor"})), + ("incest", (68, {"text": "incest"})), + ("internal-view", (69, {"text": "internal view"})), + ("interracial", (70, {"text": "interracial"})), + ("japanese-game", (71, {"text": "japanese game"})), + ("kinetic-novel", (72, {"text": "kinetic novel"})), + ("lactation", (73, {"text": "lactation"})), + ("lesbian", (74, {"text": "lesbian"})), + ("loli", (75, {"text": "loli"})), + ("male-protagonist", (77, {"text": "male protagonist"})), + ("maledomination", (76, {"text": "male domination"})), + ("management", (78, {"text": "management"})), + ("masturbation", (79, {"text": "masturbation"})), + ("milf", (80, {"text": "milf"})), + ("mind-control", (81, {"text": "mind control"})), + ("mobile-game", (82, {"text": "mobile game"})), + ("monster", (83, {"text": "monster"})), + ("monster-girl", (84, {"text": "monster girl"})), + ("multiple-endings", (85, {"text": "multiple endings"})), + ("multiple-penetration", (86, {"text": "multiple penetration"})), + ("multiple-protagonist", (87, {"text": "multiple protagonist"})), + ("necrophilia", (88, {"text": "necrophilia"})), + ("no-sexual-content", (89, {"text": "no sexual content"})), + ("ntr", (90, {"text": "netorare"})), + ("oral-sex", (91, {"text": "oral sex"})), + ("paranormal", (92, {"text": "paranormal"})), + ("parody", (93, {"text": "parody"})), + ("platformer", (94, {"text": "platformer"})), + ("point-click", (95, {"text": "point & click"})), + ("possession", (96, {"text": "possession"})), + ("pov", (97, {"text": "pov"})), + ("pregnancy", (98, {"text": "pregnancy"})), + ("prostitution", (99, {"text": "prostitution"})), + ("puzzle", (100, {"text": "puzzle"})), + ("rape", (101, {"text": "rape"})), + ("real-porn", (102, {"text": "real porn"})), + ("religion", (103, {"text": "religion"})), + ("romance", (104, {"text": "romance"})), + ("rpg", (105, {"text": "rpg"})), + ("sandbox", (106, {"text": "sandbox"})), + ("scat", (107, {"text": "scat"})), + ("school-setting", (108, {"text": "school setting"})), + ("sci-fi", (109, {"text": "sci-fi"})), + ("sex-toys", (110, {"text": "sex toys"})), + ("sexual-harassment", (111, {"text": "sexual harassment"})), + ("shooter", (112, {"text": "shooter"})), + ("shota", (113, {"text": "shota"})), + ("side-scroller", (114, {"text": "side-scroller"})), + ("simulator", (115, {"text": "simulator"})), + ("sissification", (116, {"text": "sissification"})), + ("slave", (117, {"text": "slave"})), + ("sleep-sex", (118, {"text": "sleep sex"})), + ("spanking", (119, {"text": "spanking"})), + ("strategy", (120, {"text": "strategy"})), + ("stripping", (121, {"text": "stripping"})), + ("superpowers", (122, {"text": "superpowers"})), + ("swinging", (123, {"text": "swinging"})), + ("teasing", (124, {"text": "teasing"})), + ("tentacles", (125, {"text": "tentacles"})), + ("text-based", (126, {"text": "text based"})), + ("titfuck", (127, {"text": "titfuck"})), + ("trainer", (128, {"text": "trainer"})), + ("transformation", (129, {"text": "transformation"})), + ("trap", (130, {"text": "trap"})), + ("turn-based-combat", (131, {"text": "turn based combat"})), + ("twins", (132, {"text": "twins"})), + ("urination", (133, {"text": "urination"})), + ("vaginal-sex", (134, {"text": "vaginal sex"})), + ("virgin", (135, {"text": "virgin"})), + ("virtual-reality", (136, {"text": "virtual reality"})), + ("voiced", (137, {"text": "voiced"})), + ("vore", (138, {"text": "vore"})), + ("voyeurism", (139, {"text": "voyeurism"})), + ("unknown", (154, {"text": "unknown"})), +]) + + +TagHighlight = IntEnumHack("TagHighlight", [ + ("Positive", (1, {"color": (0.0, 0.6, 0.0, 1.0)})), + ("Negative", (2, {"color": (0.6, 0.0, 0.0, 1.0)})), + ("Critical", (3, {"color": (0.0, 0.0, 0.0, 1.0)})), +]) + + +ExeState = IntEnumHack("ExeState", [ + ("Invalid", 1), + ("Selected", 2), + ("Unset", 3), +]) + + +LaunchState = IntEnumHack("LaunchState", [ + ("Idle", 1), + ("Starting", 2), + ("Playing", 3), +]) + + +MsgBox = IntEnumHack("MsgBox", [ + ("info", (1, {"color": (0.10, 0.69, 0.95), "icon": "information"})), + ("warn", (2, {"color": (0.95, 0.69, 0.10), "icon": "alert_rhombus"})), + ("error", (3, {"color": (0.95, 0.22, 0.22), "icon": "alert_octagon"})), +]) + + +FilterMode = IntEnumHack("FilterMode", [ + ("Choose", 1), + ("Archived", 2), + ("Custom", 13), + ("Exe State", 3), + ("Finished", 6), + ("Installed", 4), + ("Label", 5), + ("Rating", 7), + ("Score", 8), + ("Status", 9), + ("Tag", 10), + ("Type", 11), + ("Updated", 12), +]) + + +Category = IntEnumHack("Category", [ + ("Games", 1), + ("Animations", 2), + ("Comics", 3), + ("Misc", 4), +]) + + +ProxyType = IntEnumHack("ProxyType", [ + ("Disabled", 1), + ("SOCKS4", 2), + ("SOCKS5", 3), + ("HTTP", 4), +]) + + +TexCompress = IntEnumHack("TexCompress", [ + ("Disabled", 1), + ("ASTC", 2), + ("BC7", 3), +]) + + +@dataclasses.dataclass(slots=True) +class Filter: + mode: FilterMode + invert: bool = False + match: typing.Any = None + id: int = None + + def __post_init__(self): + self.id = id(self) + + +TimelineEventType = IntEnumHack("TimelineEventType", [ + ("GameAdded", (1, {"display": "Added", "icon": "alert_decagram", "args_min": 0, "template": "Added to the library"})), + ("GameLaunched", (2, {"display": "Launched", "icon": "play", "args_min": 1, "template": "Launched {}"})), + ("GameFinished", (3, {"display": "Finished", "icon": "flag_checkered", "args_min": 1, "template": "Finished {}"})), + ("GameInstalled", (4, {"display": "Installed", "icon": "download", "args_min": 1, "template": "Installed {}"})), + ("ChangedName", (5, {"display": "Changed name", "icon": "spellcheck", "args_min": 2, "template": "Name changed from \"{}\" to \"{}\""})), + ("ChangedStatus", (6, {"display": "Changed status", "icon": "lightning_bolt", "args_min": 2, "template": "Status changed from \"{}\" to \"{}\""})), + ("ChangedVersion", (7, {"display": "Changed version", "icon": "star", "args_min": 2, "template": "Version changed from \"{}\" to \"{}\""})), + ("ChangedDeveloper", (8, {"display": "Changed developer", "icon": "account", "args_min": 2, "template": "Developer changed from \"{}\" to \"{}\""})), + ("ChangedType", (9, {"display": "Changed type", "icon": "shape", "args_min": 2, "template": "Type changed from \"{}\" to \"{}\""})), + ("TagsAdded", (10, {"display": "Tags added", "icon": "tag_plus", "args_min": 1, "template": "Tags were added: {}"})), + ("TagsRemoved", (11, {"display": "Tags removed", "icon": "tag_minus", "args_min": 1, "template": "Tags were removed: {}"})), + ("ScoreIncreased", (12, {"display": "Score increased", "icon": "thumb_up", "args_min": 4, "template": "Forum score increased from {} ({}) to {} ({})"})), + ("ScoreDecreased", (13, {"display": "Score decreased", "icon": "thumb_down", "args_min": 4, "template": "Forum score decreased from {} ({}) to {} ({})"})), + ("RecheckExpired", (14, {"display": "Recheck expired", "icon": "timer_sync", "args_min": 1, "template": "Forcefully performed a full recheck because game has remained idle for {} day(s)"})), # Unused + ("RecheckUserReq", (15, {"display": "Recheck requested", "icon": "reload_alert", "args_min": 0, "template": "Forcefully performed a full recheck requested by user"})), +]) + + +@dataclasses.dataclass(slots=True) +class TimelineEvent: + game_id: int + timestamp: Timestamp + arguments: list[str] + type: TimelineEventType + + +@dataclasses.dataclass(slots=True) +class Review: + user: str + score: int + message: str + likes: int + timestamp: int + + +@dataclasses.dataclass(slots=True) +class Label: + id: int + name: str + color: tuple[float] + position: int + instances: typing.ClassVar = [] + + @property + def short_name(self): + return "".join(word[:1] for word in self.name.split(" ")) + + @classmethod + def add(cls, *args, **kwargs): + if args and isinstance(obj := args[0], cls): + self = obj + else: + self = cls(*args, **kwargs) + if self in cls.instances: + return + cls.instances.append(self) + + @classmethod + def get(cls, id: int): + for self in cls.instances: + if self.id == id: + return self + + @classmethod + def remove(cls, self): + while self in cls.instances: + cls.instances.remove(self) + + @classmethod + def update_positions(cls): + for self_i, self in enumerate(cls.instances): + self.position = self_i + + @classmethod + def sort_instances(cls): + cls.instances.sort(key=lambda self: self.position) + cls.update_positions() + + +@dataclasses.dataclass(slots=True) +class Tab: + id: int + name: str + icon: str + color: tuple[float] | None + position: int + instances: typing.ClassVar = [] + + @classmethod + def add(cls, *args, **kwargs): + if args and isinstance(obj := args[0], cls): + self = obj + else: + self = cls(*args, **kwargs) + if self in cls.instances: + return + cls.instances.append(self) + + @classmethod + def get(cls, id: int): + for self in cls.instances: + if self.id == id: + return self + + @classmethod + def remove(cls, self): + while self in cls.instances: + cls.instances.remove(self) + cls.sort_instances() + + @classmethod + def update_positions(cls): + for self_i, self in enumerate(cls.instances): + self.position = self_i + + @classmethod + def sort_instances(cls): + cls.instances.sort(key=lambda self: self.position) + cls.update_positions() + + @classmethod + def base_icon(cls): + from modules import icons + return icons.heart_box + + @classmethod + def first_tab_label(cls): + from modules import globals, icons + if globals.settings.default_tab_is_new: + return f"{icons.alert_decagram} New" + else: + return f"{icons.heart_box} Default" + + def __hash__(self): + return hash(self.id) + + +@dataclasses.dataclass(slots=True) +class Browser: + name: str + hash: int = None + args: list[str] = None + hashed_name: str = None + integrated: bool = None + custom: bool = None + private_arg: list = None + index : int = None + instances: typing.ClassVar = {} + avail_list: typing.ClassVar = [] + + def __post_init__(self): + if self.hash is None: + self.hash = self.make_hash(self.name) + self.hashed_name = f"{self.name}###{self.hash}" + self.integrated = self.hash == 0 + self.custom = self.hash == -1 + private_args = { + "Opera": "-private", + "Chrom": "-incognito", + "Brave": "-incognito", + "Edge": "-inprivate", + "fox": "-private-window" + } + self.private_arg = [] + for search, arg in private_args.items(): + if search in self.name: + self.private_arg.append(arg) + break + + @classmethod + def make_hash(cls, name: str): + return int(hashlib.md5(name.encode()).hexdigest()[-12:], 16) + + @classmethod + def add(cls, *args, **kwargs): + if args and isinstance(obj := args[0], cls): + self = obj + else: + self = cls(*args, **kwargs) + if self.hashed_name in cls.instances: + return + cls.instances[self.hashed_name] = self + cls.avail_list.append(self.hashed_name) + for browser in cls.instances.values(): + browser.index = cls.avail_list.index(browser.hashed_name) + + @classmethod + def get(cls, hash): + for browser in cls.instances.values(): + if browser.hash == hash or browser.hashed_name == hash: + return browser + return cls.get(0) + +Browser.add("Integrated", 0) +Browser.add("Custom", -1) + + +Type = IntEnumHack("Type", [ + ("ADRIFT", (2, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Games})), + ("Flash", (4, {"color": colors.hex_to_rgba_0_1("#616161"), "category": Category.Games})), + ("Godot", (31, {"color": colors.hex_to_rgba_0_1("#03A9F4"), "category": Category.Games})), + ("HTML", (5, {"color": colors.hex_to_rgba_0_1("#689F38"), "category": Category.Games})), + ("Java", (6, {"color": colors.hex_to_rgba_0_1("#52A6B0"), "category": Category.Games})), + ("Others", (9, {"color": colors.hex_to_rgba_0_1("#8BC34A"), "category": Category.Games})), + ("QSP", (10, {"color": colors.hex_to_rgba_0_1("#D32F2F"), "category": Category.Games})), + ("RAGS", (11, {"color": colors.hex_to_rgba_0_1("#FF9800"), "category": Category.Games})), + ("RenPy", (14, {"color": colors.hex_to_rgba_0_1("#B069E8"), "category": Category.Games})), + ("RPGM", (13, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Games})), + ("Tads", (16, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Games})), + ("Unity", (19, {"color": colors.hex_to_rgba_0_1("#FE5901"), "category": Category.Games})), + ("Unreal Eng", (20, {"color": colors.hex_to_rgba_0_1("#0D47A1"), "category": Category.Games})), + ("WebGL", (21, {"color": colors.hex_to_rgba_0_1("#FE5901"), "category": Category.Games})), + ("Wolf RPG", (22, {"color": colors.hex_to_rgba_0_1("#4CAF50"), "category": Category.Games})), + ("GIF", (25, {"color": colors.hex_to_rgba_0_1("#03A9F4"), "category": Category.Animations})), + ("Video", (29, {"color": colors.hex_to_rgba_0_1("#FF9800"), "category": Category.Animations})), + ("CG", (30, {"color": colors.hex_to_rgba_0_1("#DFCB37"), "category": Category.Comics})), + ("Comics", (24, {"color": colors.hex_to_rgba_0_1("#FF9800"), "category": Category.Comics})), + ("Manga", (26, {"color": colors.hex_to_rgba_0_1("#0FB2FC"), "category": Category.Comics})), + ("Pinup", (27, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Comics})), + ("Cheat Mod", (3, {"color": colors.hex_to_rgba_0_1("#D32F2F"), "category": Category.Misc})), + ("Mod", (8, {"color": colors.hex_to_rgba_0_1("#BA4545"), "category": Category.Misc})), + ("README", (12, {"color": colors.hex_to_rgba_0_1("#DC143C"), "category": Category.Misc})), + ("Request", (15, {"color": colors.hex_to_rgba_0_1("#D32F2F"), "category": Category.Misc})), + ("Tool", (17, {"color": colors.hex_to_rgba_0_1("#EC5555"), "category": Category.Misc})), + ("Tutorial", (18, {"color": colors.hex_to_rgba_0_1("#EC5555"), "category": Category.Misc})), + ("Misc", (1, {"color": colors.hex_to_rgba_0_1("#B8B00C"), "category": Category.Misc})), + ("Unchecked", (23, {"color": colors.hex_to_rgba_0_1("#393939"), "category": Category.Misc})), + ("Unknown", (32, {"color": colors.hex_to_rgba_0_1("#393939"), "category": Category.Misc})), +]) + + +@dataclasses.dataclass(slots=True) +class Settings: + background_on_close : bool + bg_notifs_interval : int + bg_refresh_interval : int + browser : Browser.get + browser_custom_arguments : str + browser_custom_executable : str + browser_html : bool + browser_private : bool + cell_image_ratio : float + check_notifs : bool + compact_timeline : bool + confirm_on_remove : bool + copy_urls_as_bbcode : bool + datestamp_format : str + default_exe_dir : dict[Os, str] + default_launch_wrapper : dict[Os, dict[Type, str]] + default_tab_is_new : bool + display_mode : DisplayMode + display_tab : Tab.get + downloads_dir : dict[Os, str] + ext_background_add : bool + ext_highlight_tags : bool + ext_icon_glow : bool + filter_all_tabs : bool + fit_images : bool + grid_columns : int + hidden_timeline_events : list[TimelineEventType] + hide_empty_tabs : bool + highlight_tags : bool + ignore_semaphore_timeouts : bool + independent_tab_views : bool + insecure_ssl : bool + interface_scaling : float + last_successful_refresh : Timestamp + manual_sort_list : list[int] + mark_installed_after_add : bool + max_connections : int + max_retries : int + notifs_show_update_banner : bool + play_gifs : bool + play_gifs_unfocused : bool + preload_nearby_images : bool + proxy_type : ProxyType + proxy_host : str + proxy_port : int + proxy_username : str + proxy_password : str + quick_filters : bool + refresh_archived_games : bool + refresh_completed_games : bool + request_timeout : int + rpc_enabled : bool + rpdl_password : str + rpdl_token : str + rpdl_username : str + scroll_amount : float + scroll_smooth : bool + scroll_smooth_speed : float + select_executable_after_add : bool + show_remove_btn : bool + software_webview : bool + start_in_background : bool + start_refresh : bool + style_accent : tuple[float] + style_alt_bg : tuple[float] + style_bg : tuple[float] + style_border : tuple[float] + style_corner_radius : int + style_text : tuple[float] + style_text_dim : tuple[float] + table_header_outside_list : bool + tags_highlights : dict[Tag, TagHighlight] + tex_compress : TexCompress + tex_compress_replace : bool + timestamp_format : str + unload_offscreen_images : bool + vsync_ratio : int + weighted_score : bool + wine_extra_runners_dirs : dict[Os, list[str]] + wine_prefixes_dir : dict[Os, str] + zoom_area : int + zoom_enabled : bool + zoom_times : float + + def __post_init__(self): + if "" in self.default_exe_dir: + from modules import globals + self.default_exe_dir[globals.os] = self.default_exe_dir[""] + del self.default_exe_dir[""] + + +@dataclasses.dataclass(slots=True) +class Game: + id : int + custom : bool | None + name : str + version : str + developer : str + type : Type + status : Status + url : str + added_on : Datestamp + last_updated : Datestamp + last_full_check : int + last_check_version : str + last_launched : Datestamp + playtime : float + score : float + votes : int + rating : int + finished : str + installed : str + updated : bool | None + archived : bool + executables : list[str] + launch_wrapper : dict[Os, str] + description : str + changelog : str + tags : tuple[Tag] + unknown_tags : list[str] + unknown_tags_flag : bool + labels : list[Label.get] + tab : Tab.get + notes : str + image_url : str + previews_urls : list[str] + downloads : tuple[tuple[str, list[tuple[str, str]]]] + reviews_total : int + reviews : list[Review] + selected : bool = False + launch_state : LaunchState = LaunchState.Idle + launch_started : float = 0.0 + launch_flushed : float = 0.0 + launch_process : typing.Any = None + image : "imagehelper.ImageHelper" = None + preview_images : list["imagehelper.ImageHelper"] = dataclasses.field(default_factory=list) + previews_loading : bool = False + previews_loaded : bool = False + preview_load_future: typing.Any = dataclasses.field(default=None, init=False, repr=False, compare=False) + executables_valids : list[bool] = None + executables_valid : bool = None + timeline_events : list[TimelineEvent] = dataclasses.field(default_factory=list) + _did_init : bool = False + + def __post_init__(self): + self._did_init = True + self.labels.sort(key=lambda label: label.position) + if self.custom is None: + self.custom = bool(self.status is Status.Custom) + if self.id < 0: + self.custom = True + if self.updated is None: + self.updated = bool(self.installed) and self.installed != self.version + if self.image_url == "-": + self.image_url = "missing" + if self.finished == "True" and self.installed != "True" and self.version != "True": + self.finished = (self.installed or self.version) + elif self.finished == "False" and self.installed != "False" and self.version != "False": + self.finished = "" + from external import imagehelper + from modules import globals + self.image = imagehelper.ImageHelper(globals.images_path, glob=f"{self.id}.*") + self.preview_images = [] + self.validate_executables() + + async def load_previews_async(self): + """Download and cache the indexer's preview URLs on first use. + + Preview URLs are intentionally not part of the cover-image cache: the + latter uses ``.*`` and is replaced during refreshes. Keeping the + preview cache under a separate prefix prevents a cover reset from + deleting the gallery. + """ + if self.previews_loading or self.previews_loaded or not self.previews_urls: + return + self.previews_loading = True + try: + from external import imagehelper + from modules import api, globals, utils + # A retry or resumed load may already have partially populated + # this list. Use the normal cleanup path so existing textures and + # decoded image data are released before rebuilding it. + self.unload_previews() + for index, url in enumerate(self.previews_urls): + if not isinstance(url, str) or not url.startswith(("http://", "https://")): + continue + digest = hashlib.sha1(url.encode("utf-8")).hexdigest() + preview_dir = globals.images_path / "previews" / str(self.id) + glob = f"{digest}.*" + paths = list(preview_dir.glob(glob)) + if not paths: + # Migrate the previous flat, 16-character cache name + # instead of downloading the same preview again. + legacy_paths = list(globals.images_path.glob(f"preview-{self.id}-{digest[:16]}.*")) + if legacy_paths: + preview_dir.mkdir(parents=True, exist_ok=True) + for legacy_path in legacy_paths: + try: + migrated_path = preview_dir / f"{digest}{''.join(legacy_path.suffixes)}" + shutil.move(legacy_path, migrated_path) + except Exception: + pass + paths = list(preview_dir.glob(glob)) + if not paths: + try: + with api.images_counter: + data = await api.fetch( + "GET", url, + timeout=globals.settings.request_timeout * 4, + raise_for_status=True, + ) + if data: + preview_dir.mkdir(parents=True, exist_ok=True) + path = preview_dir / f"{digest}.{utils.image_ext(data)}" + path.write_bytes(data) + except Exception: + continue + self.preview_images.append(imagehelper.ImageHelper(preview_dir, glob=glob)) + self.previews_loaded = True + finally: + self.previews_loading = False + + def cancel_preview_loading(self): + """Cancel an in-flight preview request when its popup is dismissed.""" + future = self.preview_load_future + if future is not None and not future.done(): + future.cancel() + self.preview_load_future = None + self.previews_loading = False + + def delete_images(self): + from modules import globals + self.unload_previews() + for img in globals.images_path.glob(f"{self.id}.*"): + try: + img.unlink() + except Exception: + pass + # Remove caches made by the pre-subfolder preview implementation. + for img in globals.images_path.glob(f"preview-{self.id}-*"): + try: + img.unlink() + except Exception: + pass + preview_dir = globals.images_path / "previews" / str(self.id) + for img in preview_dir.glob("*"): + try: + img.unlink() + except Exception: + pass + try: + preview_dir.rmdir() + except OSError: + pass + + def unload_previews(self): + """Release decoded preview data and GPU textures, keeping disk cache.""" + from external import imagehelper + for image in self.preview_images: + imagehelper.unload_queue.append(image) + self.preview_images.clear() + self.previews_loaded = False + + def refresh_image(self): + self.image.glob = f"{self.id}.*" + self.image.reload() + + async def set_image_async(self, data: bytes): + from modules import globals, utils + import aiofiles + self.delete_images() + if data: + async with aiofiles.open(globals.images_path / f"{self.id}.{utils.image_ext(data)}", "wb") as f: + await f.write(data) + self.refresh_image() + + def set_image_sync(self, data: bytes): + from modules import globals, utils + self.delete_images() + if data: + (globals.images_path / f"{self.id}.{utils.image_ext(data)}").write_bytes(data) + self.refresh_image() + + def validate_executables(self): + from modules import globals, utils + if globals.settings.default_exe_dir.get(globals.os): + changed = False + executables_valids = [] + base = pathlib.Path(globals.settings.default_exe_dir.get(globals.os)) + for i, executable in enumerate(self.executables): + if utils.is_uri(executable): + executables_valids.append(True) + continue + exe = pathlib.Path(executable) + if exe.is_absolute(): + if base in exe.parents: + self.executables[i] = exe.relative_to(base).as_posix() + changed = True + executables_valids.append(exe.is_file() or exe.is_dir()) + else: + abs_exe = base / exe + executables_valids.append(abs_exe.is_file() or abs_exe.is_dir()) + self.executables_valids = executables_valids + if changed: + from external import async_thread + from modules import db + async_thread.run(db.update_game(self, "executables")) + else: + def _exe_valid(executable): + if utils.is_uri(executable): + return True + exe = pathlib.Path(executable) + return exe.is_file() or (globals.os is Os.MacOS and exe.suffix == ".app" and exe.is_dir()) + self.executables_valids = [_exe_valid(e) for e in self.executables] + self.executables_valid = all(self.executables_valids) + if globals.gui: + globals.gui.recalculate_ids = True + + def add_executable(self, executable: str): + from modules import globals, utils + if not utils.is_uri(executable): + exe = pathlib.Path(executable) + if globals.settings.default_exe_dir.get(globals.os): + base = pathlib.Path(globals.settings.default_exe_dir.get(globals.os)) + if base in exe.parents: + exe = exe.relative_to(base) + executable = exe.as_posix() + if executable in self.executables: + return + self.executables.append(executable) + from external import async_thread + from modules import db + async_thread.run(db.update_game(self, "executables")) + self.validate_executables() + if self.installed != self.version: + self.add_timeline_event(TimelineEventType.GameInstalled, self.version) + self.installed = self.version + self.updated = False + + def remove_executable(self, executable: str): + self.executables.remove(executable) + from external import async_thread + from modules import db + async_thread.run(db.update_game(self, "executables")) + self.validate_executables() + + def clear_executables(self): + self.executables.clear() + from external import async_thread + from modules import db + async_thread.run(db.update_game(self, "executables")) + self.validate_executables() + + def add_label(self, label: Label): + if label not in self.labels: + self.labels.append(label) + self.labels.sort(key=lambda label: label.position) + from external import async_thread + from modules import db, globals + async_thread.run(db.update_game(self, "labels")) + if globals.gui: + globals.gui.recalculate_ids = True + + def remove_label(self, label: Label): + while label in self.labels: + self.labels.remove(label) + from external import async_thread + from modules import db, globals + async_thread.run(db.update_game(self, "labels")) + if globals.gui: + globals.gui.recalculate_ids = True + + def add_timeline_event(self, type: TimelineEventType, *args): + from external import async_thread + from modules import db + async_thread.run(db.create_timeline_event(self.id, Timestamp(time.time()), list(args), type)) + + + @property + def playtime_display(self): + playtime = self.playtime + if self.launch_state is not LaunchState.Idle and self.launch_started: + playtime += time.time() - self.launch_started - self.launch_flushed + if playtime >= 3600: + return f"{playtime / 3600:.1f}h" + if playtime >= 60: + return f"{int(playtime / 60)}m" + if playtime: + return "<1m" + return "" + + def __setattr__(self, name: str, value: typing.Any): + if hasattr(self, "_did_init") and self._did_init and name in [ + "custom", + "name", + "version", + "developer", + "type", + "status", + "url", + "added_on", + "last_updated", + "last_full_check", + "last_check_version", + "last_launched", + "playtime", + "score", + "votes", + "rating", + "finished", + "installed", + "updated", + "archived", + "executables", + "description", + "changelog", + "tags", + "unknown_tags", + "unknown_tags_flag", + "labels", + "tab", + "notes", + "image_url", + "previews_urls", + "downloads", + "reviews_total", + "reviews", + ]: + if isinstance(attr := getattr(self, name), Timestamp): + attr.update(value) + else: + super(Game, self).__setattr__(name, value) + from external import async_thread + from modules import db, globals + async_thread.run(db.update_game(self, name)) + if globals.gui: + globals.gui.recalculate_ids = True + return + super(Game, self).__setattr__(name, value) + if name == "selected": + from modules import globals + if globals.gui: + if value: + globals.gui.last_selected_game = self + globals.gui.selected_games_count = len(list(filter(lambda game: game.selected, globals.games.values()))) + + +@dataclasses.dataclass(slots=True) +class OldGame: + id : int + name : str + version : str + status : Status From bd9fc1776f17588c961ddd43defb3d6b60399984 Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:07:59 +0200 Subject: [PATCH 07/25] Delete structs.py --- structs.py | 1322 ---------------------------------------------------- 1 file changed, 1322 deletions(-) delete mode 100644 structs.py diff --git a/structs.py b/structs.py deleted file mode 100644 index 7bc404b2..00000000 --- a/structs.py +++ /dev/null @@ -1,1322 +0,0 @@ -import asyncio -import dataclasses -import datetime as dt -import enum -import functools -import hashlib -import json -import os -import pathlib -import shutil -import sys -import time -import typing -import weakref - -from external import weakerset -from modules import colors - - -class CounterContext: - __slots__ = ("count",) - - def __init__(self): - self.count = 0 - - def __enter__(self): - self.count += 1 - - def __exit__(self, exc_type, exc_val, exc_tb): - self.count -= 1 - - async def __aenter__(self): - self.__enter__() - - async def __aexit__(self, exc_type, exc_val, exc_tb): - self.__exit__(exc_type, exc_val, exc_tb) - - -class Popup(functools.partial): - next_uuid = 0 - - __slots__ = ("open", "uuid",) - - def __init__(self, *_, **__): - from modules import utils - self.open = True - cls = type(self) - uuid = cls.next_uuid - cls.next_uuid += 1 - self.uuid = f"{uuid}_{str(time.time()).split('.')[-1]}_{utils.rand_num_str()}" - - def __call__(self, *args, **kwargs): - if not self.open: - return 0, True - opened, closed = super().__call__(*args, popup_uuid=self.uuid, **kwargs) - if closed: - self.open = False - return opened, closed - - -class DaemonProcess: - __slots__ = ("finalize",) - - def __init__(self, proc): - self.finalize = weakref.finalize(proc, self.kill, proc) - - @staticmethod - def kill(proc): - try: - # Multiprocessing - if getattr(proc, "exitcode", False) is None: - proc.kill() - # Asyncio subprocess - elif getattr(proc, "returncode", False) is None: - proc.kill() - # Standard subprocess - elif getattr(proc, "poll", lambda: False)() is None: - proc.kill() - except Exception: - pass - - def __enter__(self): - return self - - def __exit__(self, *_): - self.finalize() - - -class AbstractPipe: - def __init__(self): - raise NotImplementedError() - - async def get_async(self): - raise NotImplementedError() - - def put(self, data: dict | list | str): - raise NotImplementedError() - - -class DaemonPipe(AbstractPipe): - __slots__ = ("proc", "daemon",) - - class DaemonPipeExit(Exception): - __slots__ = () - - def __init__(self, proc: asyncio.subprocess.Process): - self.proc = proc - self.daemon = DaemonProcess(proc) - - async def is_alive(self): - await asyncio.sleep(0) - return self.proc.returncode is None - - async def get_async(self): - assert self.proc.stdout - while await self.is_alive() and not self.proc.stdout.at_eof(): - line = await self.proc.stdout.readline() - try: - return json.loads(line) - except json.JSONDecodeError: - pass - raise self.DaemonPipeExit() - - def put(self, data: dict | list | str): - assert self.proc.stdin - if self.proc.returncode is None: - self.proc.stdin.write(json.dumps(data).encode() + b"\n") - return - raise self.DaemonPipeExit() - - def __enter__(self): - self.daemon.__enter__() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.daemon.__exit__() - if exc_type is self.DaemonPipeExit: - return True - - def kill(self): - self.daemon.__exit__() - - -class ChildPipe(AbstractPipe): - __slots__ = ("loop", "stdin_event") - - def __init__(self): - try: - self.loop = asyncio.get_running_loop() - except RuntimeError: - self.loop = None - - if self.loop and sys.stdin: - self.stdin_event = asyncio.Event() - try: - self.loop.add_reader(sys.stdin, self.stdin_event.set) - except NotImplementedError: - self.stdin_event = None - - async def get(self): - assert sys.stdin - while True: - line = sys.stdin.readline() - try: - return json.loads(line) - except json.JSONDecodeError: - pass - - async def get_async(self): - assert sys.stdin - assert self.loop - while True: - if self.stdin_event: - await self.stdin_event.wait() - self.stdin_event.clear() - line = await self.loop.run_in_executor(None, sys.stdin.readline) - if not line: - return None - try: - return json.loads(line) - except json.JSONDecodeError: - pass - await asyncio.sleep(0) - - def put(self, data: dict | list | str): - assert sys.stdout - print(json.dumps(data), flush=True) - - -class Timestamp: - instances = weakerset.WeakerSet() - - __slots__ = ("value", "_display", "__weakref__",) - - def __init__(self, unix_time: int | float): - self.update(unix_time) - type(self).instances.add(self) - - def update(self, unix_time: int | float = None): - if unix_time is not None: - self.value = int(unix_time) - self._display = None - - @property - def format(self): - from modules import globals - return globals.settings.timestamp_format - - @property - def display(self): - if self._display is None: - if self.value == 0: - self._display = "" - else: - try: - self._display = dt.datetime.fromtimestamp(self.value).strftime(self.format) - except Exception: - self._display = "Bad format!" - return self._display - - -class Datestamp(Timestamp): - instances = weakerset.WeakerSet() - - __slots__ = () - - def __init__(self, unix_time: int | float): - self.update(unix_time) - type(self).instances.add(self) - - @property - def format(self): - from modules import globals - return globals.settings.datestamp_format - - -class DefaultStyle: - accent = "#d4202e" - alt_bg = "#101010" - bg = "#0a0a0a" - border = "#454545" - corner_radius = 6 - text = "#ffffff" - text_dim = "#808080" - - -@dataclasses.dataclass(slots=True) -class ThreadMatch: - title: str - id: int - - -@dataclasses.dataclass(slots=True) -class SearchResult: - title: str - creator: str - url: str - id: int - - -@dataclasses.dataclass(slots=True) -class TorrentResult: - id: int - title: str - size: int | str - seeders: int | str - leechers:int | str - date: int | str - - def __post_init__(self): - from modules import ( - globals, - utils, - ) - self.size = utils.sizeof_fmt(self.size) - self.seeders = str(self.seeders) - self.leechers = str(self.leechers) - self.date = dt.datetime.fromtimestamp(self.date).strftime(globals.settings.datestamp_format) - - -@dataclasses.dataclass(slots=True) -class DdlFile: - thread_id: int - id: str - title: str - filename: str - size: int - date: str - sha1: str - size_display: str = None - - def __post_init__(self): - if not self.id: - return - from modules import ( - globals, - utils, - ) - self.size_display = utils.sizeof_fmt(int(self.size)) - self.date = dt.datetime.strptime(self.date, r"%Y-%m-%d").strftime(globals.settings.datestamp_format) - - -@dataclasses.dataclass(slots=True) -class FileDownload: - name: str - url: str = "" - cookies: dict = True - checksum: tuple[str, str] = None - path: pathlib.Path = None - path_nest_level: int = 0 - progress: int = 0 - total: int = None - - class State(enum.IntEnum): - Preparing = enum.auto() - Downloading = enum.auto() - Verifying = enum.auto() - Extracting = enum.auto() - Stopped = enum.auto() - Deleting = enum.auto() - Removed = enum.auto() - - cancel: bool = False - state: State = State.Preparing - start: Timestamp = time.time() - current: Timestamp = time.time() - extracted: pathlib.Path = None - error: str = None - traceback: str = None - - async def delete(self): - assert self.state == self.State.Stopped - self.state = self.State.Deleting - try: - loop = asyncio.get_event_loop() - if self.extracted: - await loop.run_in_executor(None, functools.partial(shutil.rmtree, self.extracted, ignore_errors=True)) - await loop.run_in_executor(None, functools.partial(self.path.unlink, missing_ok=True)) - path = self.path - for _ in range(self.path_nest_level): - path = path.parent - if next(path.iterdir(), None) is None: - await loop.run_in_executor(None, functools.partial(path.rmdir)) - except Exception: - pass - self.state = self.State.Removed - - -@dataclasses.dataclass(slots=True) -class SortSpec: - index: int - reverse: bool - - -class IntEnumHack(enum.IntEnum): - def __new__(cls, value, attrs: dict = None): - self = int.__new__(cls, value) - self._value_ = value - # Add additional attributes - if isinstance(attrs, dict): - for key, value in attrs.items(): - setattr(self, key, value) - return self - def __init__(self, *args, **kwargs): - cls = type(self) - # Add index for use with _member_names_ - self._index_ = len(cls._member_names_) # self is added later, so the length is up to the previous item, so not len() - 1 - # Replace spaces with _, - with __ and add _ in front if starting with a number. Allows using Enum._1_special__name in code for "1 special-name" - new_name = "_" * self._name_[0].isdigit() + self._name_.replace(" ", "_").replace("-", "__") - if new_name != self._name_: - setattr(cls, new_name, self) - - -Os = IntEnumHack("Os", [ - ("Windows", 1), - ("Linux", 2), - ("MacOS", 3), -]) - - -DisplayMode = IntEnumHack("DisplayMode", [ - ("list", (1, {"icon": "view_agenda_outline"})), - ("grid", (2, {"icon": "view_grid_outline"})), - ("kanban", (3, {"icon": "view_week_outline"})), -]) - - -Status = IntEnumHack("Status", [ - ("Normal", (1, {"color" : (0.95, 0.95, 0.95), "icon": "lightning_bolt_circle"})), - ("Completed", (2, {"color" : (0.00, 0.87, 0.00), "icon": "checkbox_marked_circle"})), - ("OnHold", (3, {"color" : (0.00, 0.50, 0.95), "icon": "pause_circle"})), - ("Abandoned", (4, {"color" : (0.87, 0.20, 0.20), "icon": "close_circle"})), - ("Unchecked", (5, {"color" : (0.50, 0.50, 0.50), "icon": "alert_circle"})), - ("Custom", (6, {"color" : (0.95, 0.50, 0.00), "icon": "dots_horizontal_circle"})), - ("Unknown", (7, {"color" : (0.50, 0.50, 0.50), "icon": "alert_circle"})), -]) - - -Tag = IntEnumHack("Tag", [ - ("2d-game", (1, {"text": "2d game"})), - ("2dcg", (2, {"text": "2dcg"})), - ("3d-game", (3, {"text": "3d game"})), - ("3dcg", (4, {"text": "3dcg"})), - ("adventure", (5, {"text": "adventure"})), - ("ahegao", (6, {"text": "ahegao"})), - ("ai-cg", (140, {"text": "ai cg"})), - ("anal-sex", (7, {"text": "anal sex"})), - ("animated", (8, {"text": "animated"})), - ("asset-addon", (9, {"text": "asset-addon"})), - ("asset-ai-shoujo", (10, {"text": "asset-ai-shoujo"})), - ("asset-animal", (11, {"text": "asset-animal"})), - ("asset-animation", (12, {"text": "asset-animation"})), - ("asset-audio", (13, {"text": "asset-audio"})), - ("asset-bundle", (14, {"text": "asset-bundle"})), - ("asset-character", (15, {"text": "asset-character"})), - ("asset-clothing", (16, {"text": "asset-clothing"})), - ("asset-daz-gen1", (151, {"text": "asset-daz-gen1"})), - ("asset-daz-gen2", (141, {"text": "asset-daz-gen2"})), - ("asset-daz-gen3", (142, {"text": "asset-daz-gen3"})), - ("asset-daz-gen8", (143, {"text": "asset-daz-gen8"})), - ("asset-daz-gen81", (144, {"text": "asset-daz-gen81"})), - ("asset-daz-gen9", (145, {"text": "asset-daz-gen9"})), - ("asset-daz-m4", (152, {"text": "asset-daz-m4"})), - ("asset-daz-v4", (153, {"text": "asset-daz-v4"})), - ("asset-environment", (17, {"text": "asset-environment"})), - ("asset-expression", (18, {"text": "asset-expression"})), - ("asset-female", (146, {"text": "asset-female"})), - ("asset-hair", (19, {"text": "asset-hair"})), - ("asset-hdri", (20, {"text": "asset-hdri"})), - ("asset-honey-select", (21, {"text": "asset-honey-select"})), - ("asset-honey-select2", (22, {"text": "asset-honey-select2"})), - ("asset-koikatu", (23, {"text": "asset-koikatu"})), - ("asset-light", (24, {"text": "asset-light"})), - ("asset-male", (147, {"text": "asset-male"})), - ("asset-morph", (25, {"text": "asset-morph"})), - ("asset-nonbinary", (148, {"text": "asset-nonbinary"})), - ("asset-playhome", (150, {"text": "asset-playhome"})), - ("asset-plugin", (26, {"text": "asset-plugin"})), - ("asset-pose", (27, {"text": "asset-pose"})), - ("asset-prop", (28, {"text": "asset-prop"})), - ("asset-scene", (149, {"text": "asset-scene"})), - ("asset-script", (29, {"text": "asset-script"})), - ("asset-shader", (30, {"text": "asset-shader"})), - ("asset-texture", (31, {"text": "asset-texture"})), - ("asset-utility", (32, {"text": "asset-utility"})), - ("asset-vehicle", (33, {"text": "asset-vehicle"})), - ("bdsm", (34, {"text": "bdsm"})), - ("bestiality", (35, {"text": "bestiality"})), - ("big-ass", (36, {"text": "big ass"})), - ("big-tits", (37, {"text": "big tits"})), - ("blackmail", (38, {"text": "blackmail"})), - ("bukkake", (39, {"text": "bukkake"})), - ("censored", (40, {"text": "censored"})), - ("character-creation", (41, {"text": "character creation"})), - ("cheating", (42, {"text": "cheating"})), - ("combat", (43, {"text": "combat"})), - ("corruption", (44, {"text": "corruption"})), - ("cosplay", (45, {"text": "cosplay"})), - ("creampie", (46, {"text": "creampie"})), - ("dating-sim", (47, {"text": "dating sim"})), - ("dilf", (48, {"text": "dilf"})), - ("drugs", (49, {"text": "drugs"})), - ("dystopian-setting", (50, {"text": "dystopian setting"})), - ("exhibitionism", (51, {"text": "exhibitionism"})), - ("fantasy", (52, {"text": "fantasy"})), - ("female-protagonist", (54, {"text": "female protagonist"})), - ("femaledomination", (53, {"text": "female domination"})), - ("footjob", (55, {"text": "footjob"})), - ("furry", (56, {"text": "furry"})), - ("futa-trans", (57, {"text": "futa/trans"})), - ("futa-trans-protagonist", (58, {"text": "futa/trans protagonist"})), - ("gay", (59, {"text": "gay"})), - ("graphic-violence", (60, {"text": "graphic violence"})), - ("groping", (61, {"text": "groping"})), - ("group-sex", (62, {"text": "group sex"})), - ("handjob", (63, {"text": "handjob"})), - ("harem", (64, {"text": "harem"})), - ("horror", (65, {"text": "horror"})), - ("humiliation", (66, {"text": "humiliation"})), - ("humor", (67, {"text": "humor"})), - ("incest", (68, {"text": "incest"})), - ("internal-view", (69, {"text": "internal view"})), - ("interracial", (70, {"text": "interracial"})), - ("japanese-game", (71, {"text": "japanese game"})), - ("kinetic-novel", (72, {"text": "kinetic novel"})), - ("lactation", (73, {"text": "lactation"})), - ("lesbian", (74, {"text": "lesbian"})), - ("loli", (75, {"text": "loli"})), - ("male-protagonist", (77, {"text": "male protagonist"})), - ("maledomination", (76, {"text": "male domination"})), - ("management", (78, {"text": "management"})), - ("masturbation", (79, {"text": "masturbation"})), - ("milf", (80, {"text": "milf"})), - ("mind-control", (81, {"text": "mind control"})), - ("mobile-game", (82, {"text": "mobile game"})), - ("monster", (83, {"text": "monster"})), - ("monster-girl", (84, {"text": "monster girl"})), - ("multiple-endings", (85, {"text": "multiple endings"})), - ("multiple-penetration", (86, {"text": "multiple penetration"})), - ("multiple-protagonist", (87, {"text": "multiple protagonist"})), - ("necrophilia", (88, {"text": "necrophilia"})), - ("no-sexual-content", (89, {"text": "no sexual content"})), - ("ntr", (90, {"text": "netorare"})), - ("oral-sex", (91, {"text": "oral sex"})), - ("paranormal", (92, {"text": "paranormal"})), - ("parody", (93, {"text": "parody"})), - ("platformer", (94, {"text": "platformer"})), - ("point-click", (95, {"text": "point & click"})), - ("possession", (96, {"text": "possession"})), - ("pov", (97, {"text": "pov"})), - ("pregnancy", (98, {"text": "pregnancy"})), - ("prostitution", (99, {"text": "prostitution"})), - ("puzzle", (100, {"text": "puzzle"})), - ("rape", (101, {"text": "rape"})), - ("real-porn", (102, {"text": "real porn"})), - ("religion", (103, {"text": "religion"})), - ("romance", (104, {"text": "romance"})), - ("rpg", (105, {"text": "rpg"})), - ("sandbox", (106, {"text": "sandbox"})), - ("scat", (107, {"text": "scat"})), - ("school-setting", (108, {"text": "school setting"})), - ("sci-fi", (109, {"text": "sci-fi"})), - ("sex-toys", (110, {"text": "sex toys"})), - ("sexual-harassment", (111, {"text": "sexual harassment"})), - ("shooter", (112, {"text": "shooter"})), - ("shota", (113, {"text": "shota"})), - ("side-scroller", (114, {"text": "side-scroller"})), - ("simulator", (115, {"text": "simulator"})), - ("sissification", (116, {"text": "sissification"})), - ("slave", (117, {"text": "slave"})), - ("sleep-sex", (118, {"text": "sleep sex"})), - ("spanking", (119, {"text": "spanking"})), - ("strategy", (120, {"text": "strategy"})), - ("stripping", (121, {"text": "stripping"})), - ("superpowers", (122, {"text": "superpowers"})), - ("swinging", (123, {"text": "swinging"})), - ("teasing", (124, {"text": "teasing"})), - ("tentacles", (125, {"text": "tentacles"})), - ("text-based", (126, {"text": "text based"})), - ("titfuck", (127, {"text": "titfuck"})), - ("trainer", (128, {"text": "trainer"})), - ("transformation", (129, {"text": "transformation"})), - ("trap", (130, {"text": "trap"})), - ("turn-based-combat", (131, {"text": "turn based combat"})), - ("twins", (132, {"text": "twins"})), - ("urination", (133, {"text": "urination"})), - ("vaginal-sex", (134, {"text": "vaginal sex"})), - ("virgin", (135, {"text": "virgin"})), - ("virtual-reality", (136, {"text": "virtual reality"})), - ("voiced", (137, {"text": "voiced"})), - ("vore", (138, {"text": "vore"})), - ("voyeurism", (139, {"text": "voyeurism"})), - ("unknown", (154, {"text": "unknown"})), -]) - - -TagHighlight = IntEnumHack("TagHighlight", [ - ("Positive", (1, {"color": (0.0, 0.6, 0.0, 1.0)})), - ("Negative", (2, {"color": (0.6, 0.0, 0.0, 1.0)})), - ("Critical", (3, {"color": (0.0, 0.0, 0.0, 1.0)})), -]) - - -ExeState = IntEnumHack("ExeState", [ - ("Invalid", 1), - ("Selected", 2), - ("Unset", 3), -]) - - -LaunchState = IntEnumHack("LaunchState", [ - ("Idle", 1), - ("Starting", 2), - ("Playing", 3), -]) - - -MsgBox = IntEnumHack("MsgBox", [ - ("info", (1, {"color": (0.10, 0.69, 0.95), "icon": "information"})), - ("warn", (2, {"color": (0.95, 0.69, 0.10), "icon": "alert_rhombus"})), - ("error", (3, {"color": (0.95, 0.22, 0.22), "icon": "alert_octagon"})), -]) - - -FilterMode = IntEnumHack("FilterMode", [ - ("Choose", 1), - ("Archived", 2), - ("Custom", 13), - ("Exe State", 3), - ("Finished", 6), - ("Installed", 4), - ("Label", 5), - ("Rating", 7), - ("Score", 8), - ("Status", 9), - ("Tag", 10), - ("Type", 11), - ("Updated", 12), -]) - - -Category = IntEnumHack("Category", [ - ("Games", 1), - ("Animations", 2), - ("Comics", 3), - ("Misc", 4), -]) - - -ProxyType = IntEnumHack("ProxyType", [ - ("Disabled", 1), - ("SOCKS4", 2), - ("SOCKS5", 3), - ("HTTP", 4), -]) - - -TexCompress = IntEnumHack("TexCompress", [ - ("Disabled", 1), - ("ASTC", 2), - ("BC7", 3), -]) - - -@dataclasses.dataclass(slots=True) -class Filter: - mode: FilterMode - invert: bool = False - match: typing.Any = None - id: int = None - - def __post_init__(self): - self.id = id(self) - - -TimelineEventType = IntEnumHack("TimelineEventType", [ - ("GameAdded", (1, {"display": "Added", "icon": "alert_decagram", "args_min": 0, "template": "Added to the library"})), - ("GameLaunched", (2, {"display": "Launched", "icon": "play", "args_min": 1, "template": "Launched {}"})), - ("GameFinished", (3, {"display": "Finished", "icon": "flag_checkered", "args_min": 1, "template": "Finished {}"})), - ("GameInstalled", (4, {"display": "Installed", "icon": "download", "args_min": 1, "template": "Installed {}"})), - ("ChangedName", (5, {"display": "Changed name", "icon": "spellcheck", "args_min": 2, "template": "Name changed from \"{}\" to \"{}\""})), - ("ChangedStatus", (6, {"display": "Changed status", "icon": "lightning_bolt", "args_min": 2, "template": "Status changed from \"{}\" to \"{}\""})), - ("ChangedVersion", (7, {"display": "Changed version", "icon": "star", "args_min": 2, "template": "Version changed from \"{}\" to \"{}\""})), - ("ChangedDeveloper", (8, {"display": "Changed developer", "icon": "account", "args_min": 2, "template": "Developer changed from \"{}\" to \"{}\""})), - ("ChangedType", (9, {"display": "Changed type", "icon": "shape", "args_min": 2, "template": "Type changed from \"{}\" to \"{}\""})), - ("TagsAdded", (10, {"display": "Tags added", "icon": "tag_plus", "args_min": 1, "template": "Tags were added: {}"})), - ("TagsRemoved", (11, {"display": "Tags removed", "icon": "tag_minus", "args_min": 1, "template": "Tags were removed: {}"})), - ("ScoreIncreased", (12, {"display": "Score increased", "icon": "thumb_up", "args_min": 4, "template": "Forum score increased from {} ({}) to {} ({})"})), - ("ScoreDecreased", (13, {"display": "Score decreased", "icon": "thumb_down", "args_min": 4, "template": "Forum score decreased from {} ({}) to {} ({})"})), - ("RecheckExpired", (14, {"display": "Recheck expired", "icon": "timer_sync", "args_min": 1, "template": "Forcefully performed a full recheck because game has remained idle for {} day(s)"})), # Unused - ("RecheckUserReq", (15, {"display": "Recheck requested", "icon": "reload_alert", "args_min": 0, "template": "Forcefully performed a full recheck requested by user"})), -]) - - -@dataclasses.dataclass(slots=True) -class TimelineEvent: - game_id: int - timestamp: Timestamp - arguments: list[str] - type: TimelineEventType - - -@dataclasses.dataclass(slots=True) -class Review: - user: str - score: int - message: str - likes: int - timestamp: int - - -@dataclasses.dataclass(slots=True) -class Label: - id: int - name: str - color: tuple[float] - position: int - instances: typing.ClassVar = [] - - @property - def short_name(self): - return "".join(word[:1] for word in self.name.split(" ")) - - @classmethod - def add(cls, *args, **kwargs): - if args and isinstance(obj := args[0], cls): - self = obj - else: - self = cls(*args, **kwargs) - if self in cls.instances: - return - cls.instances.append(self) - - @classmethod - def get(cls, id: int): - for self in cls.instances: - if self.id == id: - return self - - @classmethod - def remove(cls, self): - while self in cls.instances: - cls.instances.remove(self) - - @classmethod - def update_positions(cls): - for self_i, self in enumerate(cls.instances): - self.position = self_i - - @classmethod - def sort_instances(cls): - cls.instances.sort(key=lambda self: self.position) - cls.update_positions() - - -@dataclasses.dataclass(slots=True) -class Tab: - id: int - name: str - icon: str - color: tuple[float] | None - position: int - instances: typing.ClassVar = [] - - @classmethod - def add(cls, *args, **kwargs): - if args and isinstance(obj := args[0], cls): - self = obj - else: - self = cls(*args, **kwargs) - if self in cls.instances: - return - cls.instances.append(self) - - @classmethod - def get(cls, id: int): - for self in cls.instances: - if self.id == id: - return self - - @classmethod - def remove(cls, self): - while self in cls.instances: - cls.instances.remove(self) - cls.sort_instances() - - @classmethod - def update_positions(cls): - for self_i, self in enumerate(cls.instances): - self.position = self_i - - @classmethod - def sort_instances(cls): - cls.instances.sort(key=lambda self: self.position) - cls.update_positions() - - @classmethod - def base_icon(cls): - from modules import icons - return icons.heart_box - - @classmethod - def first_tab_label(cls): - from modules import globals, icons - if globals.settings.default_tab_is_new: - return f"{icons.alert_decagram} New" - else: - return f"{icons.heart_box} Default" - - def __hash__(self): - return hash(self.id) - - -@dataclasses.dataclass(slots=True) -class Browser: - name: str - hash: int = None - args: list[str] = None - hashed_name: str = None - integrated: bool = None - custom: bool = None - private_arg: list = None - index : int = None - instances: typing.ClassVar = {} - avail_list: typing.ClassVar = [] - - def __post_init__(self): - if self.hash is None: - self.hash = self.make_hash(self.name) - self.hashed_name = f"{self.name}###{self.hash}" - self.integrated = self.hash == 0 - self.custom = self.hash == -1 - private_args = { - "Opera": "-private", - "Chrom": "-incognito", - "Brave": "-incognito", - "Edge": "-inprivate", - "fox": "-private-window" - } - self.private_arg = [] - for search, arg in private_args.items(): - if search in self.name: - self.private_arg.append(arg) - break - - @classmethod - def make_hash(cls, name: str): - return int(hashlib.md5(name.encode()).hexdigest()[-12:], 16) - - @classmethod - def add(cls, *args, **kwargs): - if args and isinstance(obj := args[0], cls): - self = obj - else: - self = cls(*args, **kwargs) - if self.hashed_name in cls.instances: - return - cls.instances[self.hashed_name] = self - cls.avail_list.append(self.hashed_name) - for browser in cls.instances.values(): - browser.index = cls.avail_list.index(browser.hashed_name) - - @classmethod - def get(cls, hash): - for browser in cls.instances.values(): - if browser.hash == hash or browser.hashed_name == hash: - return browser - return cls.get(0) - -Browser.add("Integrated", 0) -Browser.add("Custom", -1) - - -Type = IntEnumHack("Type", [ - ("ADRIFT", (2, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Games})), - ("Flash", (4, {"color": colors.hex_to_rgba_0_1("#616161"), "category": Category.Games})), - ("Godot", (31, {"color": colors.hex_to_rgba_0_1("#03A9F4"), "category": Category.Games})), - ("HTML", (5, {"color": colors.hex_to_rgba_0_1("#689F38"), "category": Category.Games})), - ("Java", (6, {"color": colors.hex_to_rgba_0_1("#52A6B0"), "category": Category.Games})), - ("Others", (9, {"color": colors.hex_to_rgba_0_1("#8BC34A"), "category": Category.Games})), - ("QSP", (10, {"color": colors.hex_to_rgba_0_1("#D32F2F"), "category": Category.Games})), - ("RAGS", (11, {"color": colors.hex_to_rgba_0_1("#FF9800"), "category": Category.Games})), - ("RenPy", (14, {"color": colors.hex_to_rgba_0_1("#B069E8"), "category": Category.Games})), - ("RPGM", (13, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Games})), - ("Tads", (16, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Games})), - ("Unity", (19, {"color": colors.hex_to_rgba_0_1("#FE5901"), "category": Category.Games})), - ("Unreal Eng", (20, {"color": colors.hex_to_rgba_0_1("#0D47A1"), "category": Category.Games})), - ("WebGL", (21, {"color": colors.hex_to_rgba_0_1("#FE5901"), "category": Category.Games})), - ("Wolf RPG", (22, {"color": colors.hex_to_rgba_0_1("#4CAF50"), "category": Category.Games})), - ("GIF", (25, {"color": colors.hex_to_rgba_0_1("#03A9F4"), "category": Category.Animations})), - ("Video", (29, {"color": colors.hex_to_rgba_0_1("#FF9800"), "category": Category.Animations})), - ("CG", (30, {"color": colors.hex_to_rgba_0_1("#DFCB37"), "category": Category.Comics})), - ("Comics", (24, {"color": colors.hex_to_rgba_0_1("#FF9800"), "category": Category.Comics})), - ("Manga", (26, {"color": colors.hex_to_rgba_0_1("#0FB2FC"), "category": Category.Comics})), - ("Pinup", (27, {"color": colors.hex_to_rgba_0_1("#2196F3"), "category": Category.Comics})), - ("Cheat Mod", (3, {"color": colors.hex_to_rgba_0_1("#D32F2F"), "category": Category.Misc})), - ("Mod", (8, {"color": colors.hex_to_rgba_0_1("#BA4545"), "category": Category.Misc})), - ("README", (12, {"color": colors.hex_to_rgba_0_1("#DC143C"), "category": Category.Misc})), - ("Request", (15, {"color": colors.hex_to_rgba_0_1("#D32F2F"), "category": Category.Misc})), - ("Tool", (17, {"color": colors.hex_to_rgba_0_1("#EC5555"), "category": Category.Misc})), - ("Tutorial", (18, {"color": colors.hex_to_rgba_0_1("#EC5555"), "category": Category.Misc})), - ("Misc", (1, {"color": colors.hex_to_rgba_0_1("#B8B00C"), "category": Category.Misc})), - ("Unchecked", (23, {"color": colors.hex_to_rgba_0_1("#393939"), "category": Category.Misc})), - ("Unknown", (32, {"color": colors.hex_to_rgba_0_1("#393939"), "category": Category.Misc})), -]) - - -@dataclasses.dataclass(slots=True) -class Settings: - background_on_close : bool - bg_notifs_interval : int - bg_refresh_interval : int - browser : Browser.get - browser_custom_arguments : str - browser_custom_executable : str - browser_html : bool - browser_private : bool - cell_image_ratio : float - check_notifs : bool - compact_timeline : bool - confirm_on_remove : bool - copy_urls_as_bbcode : bool - datestamp_format : str - default_exe_dir : dict[Os, str] - default_launch_wrapper : dict[Os, dict[Type, str]] - default_tab_is_new : bool - display_mode : DisplayMode - display_tab : Tab.get - downloads_dir : dict[Os, str] - ext_background_add : bool - ext_highlight_tags : bool - ext_icon_glow : bool - filter_all_tabs : bool - fit_images : bool - grid_columns : int - hidden_timeline_events : list[TimelineEventType] - hide_empty_tabs : bool - highlight_tags : bool - ignore_semaphore_timeouts : bool - independent_tab_views : bool - insecure_ssl : bool - interface_scaling : float - last_successful_refresh : Timestamp - manual_sort_list : list[int] - mark_installed_after_add : bool - max_connections : int - max_retries : int - notifs_show_update_banner : bool - play_gifs : bool - play_gifs_unfocused : bool - preload_nearby_images : bool - proxy_type : ProxyType - proxy_host : str - proxy_port : int - proxy_username : str - proxy_password : str - quick_filters : bool - refresh_archived_games : bool - refresh_completed_games : bool - request_timeout : int - rpc_enabled : bool - rpdl_password : str - rpdl_token : str - rpdl_username : str - scroll_amount : float - scroll_smooth : bool - scroll_smooth_speed : float - select_executable_after_add : bool - show_remove_btn : bool - software_webview : bool - start_in_background : bool - start_refresh : bool - style_accent : tuple[float] - style_alt_bg : tuple[float] - style_bg : tuple[float] - style_border : tuple[float] - style_corner_radius : int - style_text : tuple[float] - style_text_dim : tuple[float] - table_header_outside_list : bool - tags_highlights : dict[Tag, TagHighlight] - tex_compress : TexCompress - tex_compress_replace : bool - timestamp_format : str - unload_offscreen_images : bool - vsync_ratio : int - weighted_score : bool - wine_extra_runners_dirs : dict[Os, list[str]] - wine_prefixes_dir : dict[Os, str] - zoom_area : int - zoom_enabled : bool - zoom_times : float - - def __post_init__(self): - if "" in self.default_exe_dir: - from modules import globals - self.default_exe_dir[globals.os] = self.default_exe_dir[""] - del self.default_exe_dir[""] - - -@dataclasses.dataclass(slots=True) -class Game: - id : int - custom : bool | None - name : str - version : str - developer : str - type : Type - status : Status - url : str - added_on : Datestamp - last_updated : Datestamp - last_full_check : int - last_check_version : str - last_launched : Datestamp - playtime : float - score : float - votes : int - rating : int - finished : str - installed : str - updated : bool | None - archived : bool - executables : list[str] - launch_wrapper : dict[Os, str] - description : str - changelog : str - tags : tuple[Tag] - unknown_tags : list[str] - unknown_tags_flag : bool - labels : list[Label.get] - tab : Tab.get - notes : str - image_url : str - previews_urls : list[str] - downloads : tuple[tuple[str, list[tuple[str, str]]]] - reviews_total : int - reviews : list[Review] - selected : bool = False - launch_state : LaunchState = LaunchState.Idle - launch_started : float = 0.0 - launch_flushed : float = 0.0 - launch_process : typing.Any = None - image : "imagehelper.ImageHelper" = None - preview_images : list["imagehelper.ImageHelper"] = dataclasses.field(default_factory=list) - previews_loading : bool = False - previews_loaded : bool = False - preview_load_future: typing.Any = dataclasses.field(default=None, init=False, repr=False, compare=False) - executables_valids : list[bool] = None - executables_valid : bool = None - timeline_events : list[TimelineEvent] = dataclasses.field(default_factory=list) - _did_init : bool = False - - def __post_init__(self): - self._did_init = True - self.labels.sort(key=lambda label: label.position) - if self.custom is None: - self.custom = bool(self.status is Status.Custom) - if self.id < 0: - self.custom = True - if self.updated is None: - self.updated = bool(self.installed) and self.installed != self.version - if self.image_url == "-": - self.image_url = "missing" - if self.finished == "True" and self.installed != "True" and self.version != "True": - self.finished = (self.installed or self.version) - elif self.finished == "False" and self.installed != "False" and self.version != "False": - self.finished = "" - from external import imagehelper - from modules import globals - self.image = imagehelper.ImageHelper(globals.images_path, glob=f"{self.id}.*") - self.preview_images = [] - self.validate_executables() - - async def load_previews_async(self): - """Download and cache the indexer's preview URLs on first use. - - Preview URLs are intentionally not part of the cover-image cache: the - latter uses ``.*`` and is replaced during refreshes. Keeping the - preview cache under a separate prefix prevents a cover reset from - deleting the gallery. - """ - if self.previews_loading or self.previews_loaded or not self.previews_urls: - return - self.previews_loading = True - try: - from external import imagehelper - from modules import api, globals, utils - # A retry or resumed load may already have partially populated - # this list. Use the normal cleanup path so existing textures and - # decoded image data are released before rebuilding it. - self.unload_previews() - for index, url in enumerate(self.previews_urls): - if not isinstance(url, str) or not url.startswith(("http://", "https://")): - continue - digest = hashlib.sha1(url.encode("utf-8")).hexdigest() - preview_dir = globals.images_path / "previews" / str(self.id) - glob = f"{digest}.*" - paths = list(preview_dir.glob(glob)) - if not paths: - # Migrate the previous flat, 16-character cache name - # instead of downloading the same preview again. - legacy_paths = list(globals.images_path.glob(f"preview-{self.id}-{digest[:16]}.*")) - if legacy_paths: - preview_dir.mkdir(parents=True, exist_ok=True) - for legacy_path in legacy_paths: - try: - migrated_path = preview_dir / f"{digest}{''.join(legacy_path.suffixes)}" - shutil.move(legacy_path, migrated_path) - except Exception: - pass - paths = list(preview_dir.glob(glob)) - if not paths: - try: - with api.images_counter: - data = await api.fetch( - "GET", url, - timeout=globals.settings.request_timeout * 4, - raise_for_status=True, - ) - if data: - preview_dir.mkdir(parents=True, exist_ok=True) - path = preview_dir / f"{digest}.{utils.image_ext(data)}" - path.write_bytes(data) - except Exception: - continue - self.preview_images.append(imagehelper.ImageHelper(preview_dir, glob=glob)) - self.previews_loaded = True - finally: - self.previews_loading = False - - def cancel_preview_loading(self): - """Cancel an in-flight preview request when its popup is dismissed.""" - future = self.preview_load_future - if future is not None and not future.done(): - future.cancel() - self.preview_load_future = None - self.previews_loading = False - - def delete_images(self): - from modules import globals - self.unload_previews() - for img in globals.images_path.glob(f"{self.id}.*"): - try: - img.unlink() - except Exception: - pass - # Remove caches made by the pre-subfolder preview implementation. - for img in globals.images_path.glob(f"preview-{self.id}-*"): - try: - img.unlink() - except Exception: - pass - preview_dir = globals.images_path / "previews" / str(self.id) - for img in preview_dir.glob("*"): - try: - img.unlink() - except Exception: - pass - try: - preview_dir.rmdir() - except OSError: - pass - - def unload_previews(self): - """Release decoded preview data and GPU textures, keeping disk cache.""" - from external import imagehelper - for image in self.preview_images: - imagehelper.unload_queue.append(image) - self.preview_images.clear() - self.previews_loaded = False - - def refresh_image(self): - self.image.glob = f"{self.id}.*" - self.image.reload() - - async def set_image_async(self, data: bytes): - from modules import globals, utils - import aiofiles - self.delete_images() - if data: - async with aiofiles.open(globals.images_path / f"{self.id}.{utils.image_ext(data)}", "wb") as f: - await f.write(data) - self.refresh_image() - - def set_image_sync(self, data: bytes): - from modules import globals, utils - self.delete_images() - if data: - (globals.images_path / f"{self.id}.{utils.image_ext(data)}").write_bytes(data) - self.refresh_image() - - def validate_executables(self): - from modules import globals, utils - if globals.settings.default_exe_dir.get(globals.os): - changed = False - executables_valids = [] - base = pathlib.Path(globals.settings.default_exe_dir.get(globals.os)) - for i, executable in enumerate(self.executables): - if utils.is_uri(executable): - executables_valids.append(True) - continue - exe = pathlib.Path(executable) - if exe.is_absolute(): - if base in exe.parents: - self.executables[i] = exe.relative_to(base).as_posix() - changed = True - executables_valids.append(exe.is_file() or exe.is_dir()) - else: - abs_exe = base / exe - executables_valids.append(abs_exe.is_file() or abs_exe.is_dir()) - self.executables_valids = executables_valids - if changed: - from external import async_thread - from modules import db - async_thread.run(db.update_game(self, "executables")) - else: - def _exe_valid(executable): - if utils.is_uri(executable): - return True - exe = pathlib.Path(executable) - return exe.is_file() or (globals.os is Os.MacOS and exe.suffix == ".app" and exe.is_dir()) - self.executables_valids = [_exe_valid(e) for e in self.executables] - self.executables_valid = all(self.executables_valids) - if globals.gui: - globals.gui.recalculate_ids = True - - def add_executable(self, executable: str): - from modules import globals, utils - if not utils.is_uri(executable): - exe = pathlib.Path(executable) - if globals.settings.default_exe_dir.get(globals.os): - base = pathlib.Path(globals.settings.default_exe_dir.get(globals.os)) - if base in exe.parents: - exe = exe.relative_to(base) - executable = exe.as_posix() - if executable in self.executables: - return - self.executables.append(executable) - from external import async_thread - from modules import db - async_thread.run(db.update_game(self, "executables")) - self.validate_executables() - if self.installed != self.version: - self.add_timeline_event(TimelineEventType.GameInstalled, self.version) - self.installed = self.version - self.updated = False - - def remove_executable(self, executable: str): - self.executables.remove(executable) - from external import async_thread - from modules import db - async_thread.run(db.update_game(self, "executables")) - self.validate_executables() - - def clear_executables(self): - self.executables.clear() - from external import async_thread - from modules import db - async_thread.run(db.update_game(self, "executables")) - self.validate_executables() - - def add_label(self, label: Label): - if label not in self.labels: - self.labels.append(label) - self.labels.sort(key=lambda label: label.position) - from external import async_thread - from modules import db, globals - async_thread.run(db.update_game(self, "labels")) - if globals.gui: - globals.gui.recalculate_ids = True - - def remove_label(self, label: Label): - while label in self.labels: - self.labels.remove(label) - from external import async_thread - from modules import db, globals - async_thread.run(db.update_game(self, "labels")) - if globals.gui: - globals.gui.recalculate_ids = True - - def add_timeline_event(self, type: TimelineEventType, *args): - from external import async_thread - from modules import db - async_thread.run(db.create_timeline_event(self.id, Timestamp(time.time()), list(args), type)) - - - @property - def playtime_display(self): - playtime = self.playtime - if self.launch_state is not LaunchState.Idle and self.launch_started: - playtime += time.time() - self.launch_started - self.launch_flushed - if playtime >= 3600: - return f"{playtime / 3600:.1f}h" - if playtime >= 60: - return f"{int(playtime / 60)}m" - if playtime: - return "<1m" - return "" - - def __setattr__(self, name: str, value: typing.Any): - if hasattr(self, "_did_init") and self._did_init and name in [ - "custom", - "name", - "version", - "developer", - "type", - "status", - "url", - "added_on", - "last_updated", - "last_full_check", - "last_check_version", - "last_launched", - "playtime", - "score", - "votes", - "rating", - "finished", - "installed", - "updated", - "archived", - "executables", - "description", - "changelog", - "tags", - "unknown_tags", - "unknown_tags_flag", - "labels", - "tab", - "notes", - "image_url", - "previews_urls", - "downloads", - "reviews_total", - "reviews", - ]: - if isinstance(attr := getattr(self, name), Timestamp): - attr.update(value) - else: - super(Game, self).__setattr__(name, value) - from external import async_thread - from modules import db, globals - async_thread.run(db.update_game(self, name)) - if globals.gui: - globals.gui.recalculate_ids = True - return - super(Game, self).__setattr__(name, value) - if name == "selected": - from modules import globals - if globals.gui: - if value: - globals.gui.last_selected_game = self - globals.gui.selected_games_count = len(list(filter(lambda game: game.selected, globals.games.values()))) - - -@dataclasses.dataclass(slots=True) -class OldGame: - id : int - name : str - version : str - status : Status From 8b331087bcac897efbcf248a95ead27d5b12d1ae Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:08:09 +0200 Subject: [PATCH 08/25] Delete gui.py --- gui.py | 5750 -------------------------------------------------------- 1 file changed, 5750 deletions(-) delete mode 100644 gui.py diff --git a/gui.py b/gui.py deleted file mode 100644 index 4fb1422f..00000000 --- a/gui.py +++ /dev/null @@ -1,5750 +0,0 @@ -import asyncio -import builtins -import concurrent.futures -import configparser -import dataclasses -import datetime as dt -import functools -import itertools -import pathlib -import pickle -import platform -import sys -import threading -import time -import tomllib - -from PIL import Image -from PyQt6 import ( - QtCore, - QtGui, - QtWidgets, -) -import aiohttp -import glfw -import imgui -import OpenGL -import OpenGL.GL as gl - -from common.structs import ( - Browser, - Category, - Datestamp, - DefaultStyle, - DisplayMode, - ExeState, - Filter, - FilterMode, - Game, - Label, - LaunchState, - MsgBox, - Os, - ProxyType, - SortSpec, - Status, - Tab, - Tag, - TagHighlight, - TexCompress, - TimelineEventType, - Timestamp, - Type, -) -from common import parser -from external import ( - async_thread, - error, - filepicker, - imagehelper, - imgui_glfw, - ratingwidget, -) -from modules import ( - api, - callbacks, - colors, - db, - globals, - icons, - msgbox, - rpc_thread, - rpdl, - utils, - wine, -) - -tool_page = api.f95_threads_page + "44173/" -github_page = "https://github.com/WillyJL/F95Checker" -developer_page = "https://linktr.ee/WillyJL" - -imgui.io = None -imgui.style = None - - -class Columns: - - @dataclasses.dataclass(slots=True) - class Column: - cols: object - name: str - flags: int = 0 - ghost: bool = False - default: bool = False - hideable: bool = True - sortable: bool = False - resizable: bool = True - enabled: bool = None - no_header: str = False - short_header: str = False - header: str = None - index: int = None - - def __post_init__(self): - # Header - if self.ghost or self.no_header: - self.header = "##" + self.name[2:] - elif self.short_header: - self.header = self.name[:1] - else: - self.header = self.name[2:] - # Flags - if self.ghost: - self.flags |= ( - imgui.TABLE_COLUMN_NO_SORT | - imgui.TABLE_COLUMN_NO_RESIZE | - imgui.TABLE_COLUMN_NO_REORDER | - imgui.TABLE_COLUMN_NO_HEADER_WIDTH - ) - if not self.default: - self.flags |= imgui.TABLE_COLUMN_DEFAULT_HIDE - if not self.hideable: - self.flags |= imgui.TABLE_COLUMN_NO_HIDE - if not self.sortable: - self.flags |= imgui.TABLE_COLUMN_NO_SORT - if not self.resizable: - self.flags |= imgui.TABLE_COLUMN_NO_RESIZE - # Add to outer class - self.cols.items.append(self) - self.cols.count = len(self.cols.items) - self.index = self.cols.count - 1 - - def __init__(self): - self.items = [] - self.count = 0 - # Ghosts (more info in MainGUI.draw_games_list()) - self.manual_sort = self.Column( - self, f"{icons.cursor_move} Manual Sort", - ghost=True, - ) - self.version = self.Column( - self, f"{icons.star_shooting} Version", - ghost=True, - default=True, - ) - self.finished_version = self.Column( - self, f"{icons.flag_checkered} Finished Version", - ghost=True, - ) - self.installed_version = self.Column( - self, f"{icons.download} Installed Version", - ghost=True, - ) - self.status = self.Column( - self, f"{icons.checkbox_marked_circle} Status (after name)", - ghost=True, - default=True, - ) - self.separator = self.Column( - self, "-----------------------------------", - ghost=True, - default=True, - hideable=False, - ) - # Regulars - self.play_button = self.Column( - self, f"{icons.play} Play Button", - default=True, - resizable=False, - no_header=True, - ) - self.type = self.Column( - self, f"{icons.book_information_variant} Type", - default=True, - sortable=True, - resizable=False, - ) - self.name = self.Column( - self, f"{icons.gamepad_variant} Name", - imgui.TABLE_COLUMN_WIDTH_STRETCH | imgui.TABLE_COLUMN_DEFAULT_SORT, - default=True, - sortable=True, - hideable=False, - ) - self.developer = self.Column( - self, f"{icons.account} Developer", - sortable=True, - ) - self.last_updated = self.Column( - self, f"{icons.update} Last Updated", - default=True, - sortable=True, - resizable=False, - ) - self.last_launched = self.Column( - self, f"{icons.play} Last Launched", - sortable=True, - resizable=False, - ) - self.added_on = self.Column( - self, f"{icons.plus} Added On", - sortable=True, - resizable=False, - ) - self.finished = self.Column( - self, f"{icons.flag_checkered} Finished", - default=True, - sortable=True, - resizable=False, - short_header=True, - ) - self.installed = self.Column( - self, f"{icons.download} Installed", - default=True, - sortable=True, - resizable=False, - short_header=True, - ) - self.rating = self.Column( - self, f"{icons.star} Rating", - sortable=True, - resizable=False, - ) - self.notes = self.Column( - self, f"{icons.draw_pen} Notes", - sortable=True, - ) - self.open_thread = self.Column( - self, f"{icons.open_in_new} Open Thread", - default=True, - resizable=False, - no_header=True, - ) - self.copy_link = self.Column( - self, f"{icons.content_copy} Copy Link", - resizable=False, - no_header=True, - ) - self.open_folder = self.Column( - self, f"{icons.folder_open_outline} Open Folder", - resizable=False, - no_header=True, - ) - self.status_standalone = self.Column( - self, f"{icons.checkbox_marked_circle} Status (own column)", - sortable=True, - resizable=False, - no_header=True, - ) - self.score = self.Column( - self, f"{icons.message_star} Forum Score", - sortable=True, - resizable=False, - short_header=True, - ) - self.playtime = self.Column( - self, f"{icons.timer_play_outline} Playtime", - sortable=True, - resizable=False, - short_header=True, - ) - -cols = Columns() - - -@functools.cache -def _scaled(mult: float, size: int | float): - return size * mult - - -class MainGUI(): - def __init__(self): - # Constants - self.sidebar_size = 234 - self.window_flags: int = ( - imgui.WINDOW_NO_MOVE | - imgui.WINDOW_NO_RESIZE | - imgui.WINDOW_NO_COLLAPSE | - imgui.WINDOW_NO_TITLE_BAR | - imgui.WINDOW_NO_SCROLLBAR | - imgui.WINDOW_NO_SCROLL_WITH_MOUSE - ) - self.tabbar_flags: int = ( - imgui.TAB_BAR_FITTING_POLICY_SCROLL - ) - self.game_list_table_flags: int = ( - imgui.TABLE_SCROLL_Y | - imgui.TABLE_HIDEABLE | - imgui.TABLE_SORTABLE | - imgui.TABLE_RESIZABLE | - imgui.TABLE_SORT_MULTI | - imgui.TABLE_REORDERABLE | - imgui.TABLE_ROW_BACKGROUND | - imgui.TABLE_SIZING_FIXED_FIT | - imgui.TABLE_NO_HOST_EXTEND_Y | - imgui.TABLE_NO_BORDERS_IN_BODY_UTIL_RESIZE - ) - self.game_grid_table_flags: int = ( - imgui.TABLE_SCROLL_Y | - imgui.TABLE_PAD_OUTER_X | - imgui.TABLE_NO_HOST_EXTEND_Y | - imgui.TABLE_SIZING_FIXED_SAME | - imgui.TABLE_NO_SAVED_SETTINGS - ) - self.game_kanban_table_flags: int = ( - imgui.TABLE_SCROLL_X | - imgui.TABLE_SCROLL_Y | - imgui.TABLE_PAD_OUTER_X | - imgui.TABLE_REORDERABLE | - imgui.TABLE_NO_HOST_EXTEND_Y | - imgui.TABLE_SIZING_FIXED_SAME | - imgui.TABLE_BORDERS_INNER_VERTICAL - ) - self.game_hitbox_drag_drop_flags: int = ( - imgui.DRAG_DROP_ACCEPT_PEEK_ONLY | - imgui.DRAG_DROP_SOURCE_ALLOW_NULL_ID | - imgui.DRAG_DROP_SOURCE_NO_PREVIEW_TOOLTIP - ) - self.watermark_text = f"F95Checker {globals.version_name}{'' if not globals.release else ' by WillyJL'}" - - # Variables - self.focused = True - self.minimized = False - self.filtering = False - self.add_box_text = "" - self.new_styles = False - self.prev_size = (0, 0) - self.screen_pos = (0, 0) - self.repeat_chars = False - self.scroll_percent = 0.0 - self.prev_manual_sort = 0 - self.add_box_valid = False - self.bg_mode_paused = False - self.recalculate_ids = True - self.current_tab: Tab = None - self.selected_games_count = 0 - self.dragging_tab: Tab = None - self.game_hitbox_click = False - self.hovered_game: Game = None - self.filters: list[Filter] = [] - self.poll_chars: list[int] = [] - self.refresh_ratio_smooth = 0.0 - self.bg_mode_timer: float = None - self.input_chars: list[int] = [] - self.switched_display_mode = False - self.type_label_width: float = None - self.call_soon: list[callable] = [] - self.last_selected_game: Game = None - self.prev_filters: list[Filter] = [] - self.ghost_columns_enabled_count = 0 - self.bg_mode_notifs_timer: float = None - self.sorts: dict[str, list[SortSpec]] = {} - self.show_games_ids: dict[Tab, list[int]] = {} - - # Setup Qt objects - self.qt_app = QtWidgets.QApplication(sys.argv) - self.tray = TrayIcon(self) - - # Setup ImGui - imgui.create_context() - imgui.io = imgui.get_io() - imgui.io.ini_file_name = str(globals.data_path / "imgui.ini") - imgui.io.config_drag_click_to_input_text = True - imgui.io.config_cursor_blink = False - size = tuple() - pos = tuple() - try: - # Get window size - with open(imgui.io.ini_file_name, "r") as f: - ini = f.read() - imgui.load_ini_settings_from_memory(ini) - start = ini.find("[Window][F95Checker]") - assert start != -1 - end = ini.find("\n\n", start) - assert end != -1 - config = configparser.RawConfigParser() - config.read_string(ini[start:end]) - try: - size = tuple(int(x) for x in config.get("Window][F95Checker", "Size").split(",")) - except Exception: - pass - try: - pos = tuple(int(x) for x in config.get("Window][F95Checker", "ScreenPos").split(",")) - except Exception: - pass - except Exception: - pass - if not all(type(x) is int for x in size) or not len(size) == 2: - size = (1280, 720) - - # Setup GLFW - if not glfw.init(): - print("Could not initialize OpenGL context") - sys.exit(1) - glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) - glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) - glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) - glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, gl.GL_TRUE) # OS X supports only forward-compatible core profiles from 3.2 - glfw.window_hint(glfw.VISIBLE, False) - - # Create a windowed mode window and its OpenGL context - self.window: glfw._GLFWwindow = glfw.create_window(*size, "F95Checker", None, None) - if not self.window: - print("Could not initialize Window") - glfw.terminate() - sys.exit(1) - glfw.make_context_current(self.window) - self.impl = imgui_glfw.GlfwRenderer(self.window) - - # Window position and icon - if all(type(x) is int for x in pos) and len(pos) == 2 and utils.validate_geometry(*pos, *size): - glfw.set_window_pos(self.window, *pos) - self.screen_pos = glfw.get_window_pos(self.window) - if globals.settings.start_in_background: - self.hide() - glfw.set_window_icon(self.window, 1, Image.open(globals.self_path / "resources/icons/icon.png")) - - # Window callbacks - glfw.set_char_callback(self.window, self.char_callback) - glfw.set_window_close_callback(self.window, self.close_callback) - glfw.set_window_iconify_callback(self.window, self.minimize_callback) - glfw.set_window_focus_callback(self.window, self.focus_callback) - glfw.set_window_pos_callback(self.window, self.pos_callback) - glfw.set_drop_callback(self.window, self.drop_callback) - glfw.swap_interval(globals.settings.vsync_ratio) - - self.logo_texture = imagehelper.ImageHelper(globals.self_path / "resources/icons/logo.png") - self.refresh_fonts() - self.load_filters() - self.load_styles_from_toml() - - # Show errors in threads - def syncexcepthook(args: threading.ExceptHookArgs): - if args.exc_type is not msgbox.Exc: - err = error.text(args.exc_value) - tb = error.traceback(args.exc_value) - utils.push_popup( - msgbox.msgbox, "Oops!", - "Something went wrong in a parallel task:\n" - f"{err}", - MsgBox.error, - more=tb - ) - threading.excepthook = syncexcepthook - def asyncexcepthook(future: asyncio.Future): - try: - exc = future.exception() - except concurrent.futures.CancelledError: - return - if not exc or type(exc) is msgbox.Exc: - return - err = error.text(exc) - tb = error.traceback(exc) - if isinstance(exc, (aiohttp.ClientError, asyncio.TimeoutError)): - utils.push_popup( - msgbox.msgbox, "Connection error", - "A connection request has failed:\n" - f"{err}\n" - "\n" - "Possible causes include:\n" - " - You're not connected to the internet, or your connection is unstable\n" - " - Your timeout value is too low, try increasing it in settings\n" - " - The server is experiencing difficulties, try waiting a bit and retrying\n" - " - The web address is blocked in your country, network, antivirus, DNS or firewall, try a VPN\n" - " - You are refreshing with too many connections, try lowering them in settings\n" - " - Your retries value is too low, try increasing it in settings (last resort!)", - MsgBox.warn, - more=tb - ) - return - utils.push_popup( - msgbox.msgbox, "Oops!", - "Something went wrong in an asynchronous task:\n" - f"{err}", - MsgBox.error, - more=tb - ) - async_thread.done_callback = asyncexcepthook - - # Load style configuration - imgui.style = imgui.get_style() - imgui.style.item_spacing = (imgui.style.item_spacing.y, imgui.style.item_spacing.y) - imgui.style.colors[imgui.COLOR_MODAL_WINDOW_DIM_BACKGROUND] = (0, 0, 0, 0.5) - imgui.style.scrollbar_size = 10 - imgui.style.frame_border_size = 1.6 - imgui.style.colors[imgui.COLOR_TABLE_BORDER_STRONG] = (0, 0, 0, 0) - self.refresh_styles() - # No redundant vprintf - imgui.text = imgui.text_unformatted - # Custom checkbox style - imgui._checkbox = imgui.checkbox - def checkbox(label: str, state: bool): - if state: - imgui.push_style_color(imgui.COLOR_FRAME_BACKGROUND_HOVERED, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) - imgui.push_style_color(imgui.COLOR_FRAME_BACKGROUND, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) - imgui.push_style_color(imgui.COLOR_CHECK_MARK, *imgui.style.colors[imgui.COLOR_WINDOW_BACKGROUND]) - ret = imgui._checkbox(label, state) - if state: - imgui.pop_style_color(3) - return ret - imgui.checkbox = checkbox - # Custom combo style - imgui._combo = imgui.combo - def combo(*args, **kwargs): - imgui.push_style_color(imgui.COLOR_BUTTON, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) - imgui.push_style_color(imgui.COLOR_HEADER, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED][:3], 0.5) - ret = imgui._combo(*args, **kwargs) - imgui.pop_style_color(2) - return ret - imgui.combo = combo - imgui._begin_combo = imgui.begin_combo - def begin_combo(*args, **kwargs): - imgui.push_style_color(imgui.COLOR_BUTTON, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) - ret = imgui._begin_combo(*args, **kwargs) - imgui.pop_style_color() - if ret: - imgui.push_style_color(imgui.COLOR_HEADER, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED][:3], 0.5) - return ret - imgui.begin_combo = begin_combo - imgui._end_combo = imgui.end_combo - def end_combo(*args, **kwargs): - imgui.pop_style_color() - return imgui._end_combo(*args, **kwargs) - imgui.end_combo = end_combo - # Fix clicking into multiline textboxes - imgui._input_text_multiline = imgui.input_text_multiline - def input_text_multiline(*args, **kwargs): - pos = imgui.io.mouse_pos - imgui.io.mouse_pos = (pos.x - 8, pos.y) - ret = imgui._input_text_multiline(*args, **kwargs) - imgui.io.mouse_pos = (pos.x, pos.y) - return ret - imgui.input_text_multiline = input_text_multiline - # Fix some ID hell - imgui._button = imgui.button - def button(*args, **kwargs): - imgui._button(*args, **kwargs) - return imgui.is_item_clicked() - imgui.button = button - imgui._small_button = imgui.small_button - def small_button(*args, **kwargs): - imgui._small_button(*args, **kwargs) - return imgui.is_item_clicked() - imgui.small_button = small_button - imgui._invisible_button = imgui.invisible_button - def invisible_button(*args, **kwargs): - imgui._invisible_button(*args, **kwargs) - return imgui.is_item_clicked() - imgui.invisible_button = invisible_button - imgui._selectable = imgui.selectable - def selectable(*args, **kwargs): - _, selected = imgui._selectable(*args, **kwargs) - return imgui.is_item_clicked(), selected - imgui.selectable = selectable - # Utils - def push_y(offset: float): - imgui.begin_group() - imgui.set_cursor_pos_y(imgui.get_cursor_pos_y() + offset) - imgui.push_y = push_y - def pop_y(): - imgui.end_group() - imgui.pop_y = pop_y - def push_no_interaction(): - imgui.internal.push_item_flag(imgui.internal.ITEM_DISABLED, True) - imgui.push_no_interaction = push_no_interaction - def pop_no_interaction(): - imgui.internal.pop_item_flag() - imgui.pop_no_interaction = pop_no_interaction - def push_alpha(amount: float): - imgui.push_style_var(imgui.STYLE_ALPHA, imgui.style.alpha * amount) - imgui.push_alpha = push_alpha - def pop_alpha(): - imgui.pop_style_var() - imgui.pop_alpha = pop_alpha - def push_disabled(): - imgui.push_no_interaction() - imgui.push_alpha(0.5) - imgui.push_disabled = push_disabled - def pop_disabled(): - imgui.pop_alpha() - imgui.pop_no_interaction() - imgui.pop_disabled = pop_disabled - def is_topmost(): - return not imgui.is_popup_open("", imgui.POPUP_ANY_POPUP_ID) - imgui.is_topmost = is_topmost - - def refresh_styles(self): - _ = \ - imgui.style.colors[imgui.COLOR_CHECK_MARK] = \ - imgui.style.colors[imgui.COLOR_TAB_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_SLIDER_GRAB] = \ - imgui.style.colors[imgui.COLOR_TAB_HOVERED] = \ - imgui.style.colors[imgui.COLOR_BUTTON_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_HEADER_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_NAV_HIGHLIGHT] = \ - imgui.style.colors[imgui.COLOR_PLOT_HISTOGRAM] = \ - imgui.style.colors[imgui.COLOR_HEADER_HOVERED] = \ - imgui.style.colors[imgui.COLOR_BUTTON_HOVERED] = \ - imgui.style.colors[imgui.COLOR_SEPARATOR_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_SEPARATOR_HOVERED] = \ - imgui.style.colors[imgui.COLOR_RESIZE_GRIP_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_RESIZE_GRIP_HOVERED] = \ - imgui.style.colors[imgui.COLOR_TAB_UNFOCUSED_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_SCROLLBAR_GRAB_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_FRAME_BACKGROUND_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_TITLE_BACKGROUND_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_TEXT_SELECTED_BACKGROUND] = \ - globals.settings.style_accent - _ = \ - imgui.style.colors[imgui.COLOR_TAB] = \ - imgui.style.colors[imgui.COLOR_RESIZE_GRIP] = \ - imgui.style.colors[imgui.COLOR_TAB_UNFOCUSED] = \ - imgui.style.colors[imgui.COLOR_FRAME_BACKGROUND_HOVERED] = \ - (*globals.settings.style_accent[:3], 0.25) - _ = \ - imgui.style.colors[imgui.COLOR_TABLE_HEADER_BACKGROUND] = \ - imgui.style.colors[imgui.COLOR_TABLE_ROW_BACKGROUND_ALT] = \ - globals.settings.style_alt_bg - _ = \ - imgui.style.colors[imgui.COLOR_BUTTON] = \ - imgui.style.colors[imgui.COLOR_HEADER] = \ - imgui.style.colors[imgui.COLOR_FRAME_BACKGROUND] = \ - imgui.style.colors[imgui.COLOR_CHILD_BACKGROUND] = \ - imgui.style.colors[imgui.COLOR_POPUP_BACKGROUND] = \ - imgui.style.colors[imgui.COLOR_TITLE_BACKGROUND] = \ - imgui.style.colors[imgui.COLOR_WINDOW_BACKGROUND] = \ - imgui.style.colors[imgui.COLOR_SLIDER_GRAB_ACTIVE] = \ - imgui.style.colors[imgui.COLOR_SCROLLBAR_BACKGROUND] = \ - globals.settings.style_bg - _ = \ - imgui.style.colors[imgui.COLOR_BORDER] = \ - imgui.style.colors[imgui.COLOR_SEPARATOR] = \ - globals.settings.style_border - _ = \ - imgui.style.tab_rounding = \ - imgui.style.grab_rounding = \ - imgui.style.frame_rounding = \ - imgui.style.child_rounding = \ - imgui.style.popup_rounding = \ - imgui.style.window_rounding = \ - imgui.style.scrollbar_rounding = \ - self.scaled(globals.settings.style_corner_radius) - _ = \ - imgui.style.colors[imgui.COLOR_TEXT] = \ - globals.settings.style_text - _ = \ - imgui.style.colors[imgui.COLOR_TEXT_DISABLED] = \ - globals.settings.style_text_dim - if globals.os is not Os.MacOS: - self.qt_app.setStyleSheet(f""" - QMenu {{ - padding: 5px; - background-color: {colors.rgba_0_1_to_hex(globals.settings.style_bg)[:-2]}; - }} - QMenu::item {{ - margin: 1px; - padding: 2px 7px 2px 7px; - border-radius: {globals.settings.style_corner_radius}; - color: {colors.rgba_0_1_to_hex(globals.settings.style_text)[:-2]}; - }} - QMenu::item:disabled {{ - color: {colors.rgba_0_1_to_hex(globals.settings.style_text_dim)[:-2]}; - }} - QMenu::item:selected:enabled {{ - background-color: {colors.rgba_0_1_to_hex(globals.settings.style_accent)[:-2]}; - }} - QMenu::icon {{ - padding-left: 7px; - }} - """) - - def refresh_fonts(self): - imgui.io.fonts.clear() - max_tex_size = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) - imgui.io.fonts.texture_desired_width = max_tex_size - win_w, win_h = glfw.get_window_size(self.window) - fb_w, fb_h = glfw.get_framebuffer_size(self.window) - font_scaling_factor = max(fb_w / win_w, fb_h / win_h) - imgui.io.font_global_scale = 1 / font_scaling_factor - karlar_path = str(next(globals.self_path.glob("resources/fonts/Karla-Regular.*.ttf"))) - karlab_path = str(next(globals.self_path.glob("resources/fonts/Karla-Bold.*.ttf"))) - meslo_path = str(next(globals.self_path.glob("resources/fonts/MesloLGS-Regular.*.ttf"))) - noto_path = str(next(globals.self_path.glob("resources/fonts/NotoSans-Regular.*.ttf"))) - mdi_path = str(icons.font_path) - mixin_path = str(next(globals.self_path.glob("resources/fonts/custom-mixin.ttf"))) - merge = dict(merge_mode=True) - oversample = dict(oversample_h=2, oversample_v=2) - karla_config = imgui.core.FontConfig( glyph_offset_y=-0.5, **oversample) - meslo_config = imgui.core.FontConfig( **oversample) - noto_config = imgui.core.FontConfig(**merge, glyph_offset_y=-0.5, **oversample) - mdi_config = imgui.core.FontConfig(**merge, glyph_offset_y=+1.0) - mixin_config = imgui.core.FontConfig(**merge) - karla_range = imgui.core.GlyphRanges([0x1, 0x25ca, 0]) - meslo_range = imgui.core.GlyphRanges([0x1, 0x2e2e, 0]) - noto_range = imgui.core.GlyphRanges([0x1, 0xfffd, 0]) - mdi_range = imgui.core.GlyphRanges([icons.min_char, icons.max_char, 0]) - mixin_range = imgui.core.GlyphRanges([0x3000, 0x3000, 0]) - msgbox_range_values = [] - for icon in [icons.information, icons.alert_rhombus, icons.alert_octagon]: - msgbox_range_values += [ord(icon), ord(icon)] - msgbox_range_values.append(0) - msgbox_range = imgui.core.GlyphRanges(msgbox_range_values) - size_14 = round(self.scaled(14 * font_scaling_factor)) - size_15 = round(self.scaled(15 * font_scaling_factor)) - size_17 = round(self.scaled(17 * font_scaling_factor)) - size_18 = round(self.scaled(18 * font_scaling_factor)) - size_22 = round(self.scaled(22 * font_scaling_factor)) - size_28 = round(self.scaled(28 * font_scaling_factor)) - size_32 = round(self.scaled(32 * font_scaling_factor)) - size_69 = round(self.scaled(69 * font_scaling_factor)) - fonts = type("FontStore", (), {})() - imgui.fonts = fonts - add_font = imgui.io.fonts.add_font_from_file_ttf - # Default font + more glyphs + icons - fonts.default = add_font(karlar_path, size_18, font_config=karla_config, glyph_ranges=karla_range) - add_font( noto_path, size_18, font_config=noto_config, glyph_ranges=noto_range) - add_font( mdi_path, size_18, font_config=mdi_config, glyph_ranges=mdi_range) - add_font( mixin_path, size_18, font_config=mixin_config, glyph_ranges=mixin_range) - # Bold font + more glyphs + icons - fonts.bold = add_font(karlab_path, size_22, font_config=karla_config, glyph_ranges=karla_range) - add_font( noto_path, size_22, font_config=noto_config, glyph_ranges=noto_range) - add_font( mdi_path, size_18, font_config=mdi_config, glyph_ranges=mdi_range) - # Big font + more glyphs + icons - fonts.big = add_font(karlab_path, size_32, font_config=karla_config, glyph_ranges=karla_range) - add_font( noto_path, size_32, font_config=noto_config, glyph_ranges=noto_range) - add_font( mdi_path, size_28, font_config=mdi_config, glyph_ranges=mdi_range) - # Small font + more glyphs + icons - fonts.small = add_font(karlar_path, size_14, font_config=karla_config, glyph_ranges=karla_range) - add_font( noto_path, size_14, font_config=noto_config, glyph_ranges=noto_range) - add_font( mdi_path, size_14, font_config=mdi_config, glyph_ranges=mdi_range) - # Monospace font for some dates - fonts.mono = add_font(meslo_path, size_17, font_config=meslo_config, glyph_ranges=meslo_range) - # Small monospace font for more info dropdowns - fonts.mono_sm = add_font(meslo_path, size_15, font_config=meslo_config, glyph_ranges=meslo_range) - # MsgBox type icons/thumbnails - fonts.msgbox = add_font(mdi_path, size_69, glyph_ranges=msgbox_range) - try: - self.impl.refresh_font_texture() - except Exception: - if globals.settings.interface_scaling == 1.0: - raise - globals.settings.interface_scaling = 1.0 - async_thread.run(db.update_settings("interface_scaling")) - return self.refresh_fonts() - self.type_label_width = None - - def save_filters(self): - with open(globals.data_path / "filters.pkl", "wb") as file: - pickle.dump(self.filters, file) - - def load_filters(self): - try: - with open(globals.data_path / "filters.pkl", "rb") as file: - self.filters = pickle.load(file) - for flt in self.filters: - match flt.mode: - case FilterMode.Label: - # Field added later, replace broken object with real one - if not hasattr(flt.match, "position"): - try: - flt.match = Label.get(flt.match.id) - except Exception: - self.filters.remove(flt) - except Exception: - self.filters = [] - - def load_styles_from_toml(self): - if not (path := pathlib.Path(globals.data_path / 'styles.toml')).is_file(): - return - try: - with open(path, 'rb') as file: - styles = tomllib.load(file) - globals.settings.style_corner_radius = styles.get("corner_radius", DefaultStyle.corner_radius) - globals.settings.style_accent = colors.hex_to_rgba_0_1(styles.get("accent", DefaultStyle.accent)) - globals.settings.style_bg = colors.hex_to_rgba_0_1(styles.get("bg", DefaultStyle.bg)) - globals.settings.style_alt_bg = colors.hex_to_rgba_0_1(styles.get("alt_bg", DefaultStyle.alt_bg)) - globals.settings.style_border = colors.hex_to_rgba_0_1(styles.get("border", DefaultStyle.border)) - globals.settings.style_text = colors.hex_to_rgba_0_1(styles.get("text", DefaultStyle.text)) - globals.settings.style_text_dim = colors.hex_to_rgba_0_1(styles.get("text_dim", DefaultStyle.text_dim)) - self.new_styles = True - self.refresh_styles() - except Exception: - pass - - def close(self, *_, **__): - glfw.set_window_should_close(self.window, True) - - def char_callback(self, window: glfw._GLFWwindow, char: int): - self.impl.char_callback(window, char) - self.poll_chars.append(char) - - def close_callback(self, window: glfw._GLFWwindow): - if globals.settings.background_on_close: - self.hide() - glfw.set_window_should_close(self.window, False) - - def minimize_callback(self, window: glfw._GLFWwindow, minimized: int): - self.minimized = minimized - - def focus_callback(self, window: glfw._GLFWwindow, focused: int): - self.focused = focused - - def pos_callback(self, window: glfw._GLFWwindow, x: int, y: int): - if not glfw.get_window_attrib(self.window, glfw.ICONIFIED): - self.screen_pos = (x, y) - - def drop_callback(self, window: glfw._GLFWwindow, items: list[str]): - paths = [pathlib.Path(item) for item in items] - if globals.popup_stack and isinstance(picker := getattr(globals.popup_stack[-1].func, "__self__", None), filepicker.FilePicker): - path = paths[0] - if (picker.dir_picker and path.is_dir()) or (not picker.dir_picker and path.is_file()): - picker.selected = str(path) - if picker.callback: - picker.callback(picker.selected) - picker.active = False - else: - for path in paths: - if path.suffix and path.suffix.lower() == ".html": - async_thread.run(api.import_browser_bookmarks(path)) - elif path.suffix and path.suffix.lower() == ".url": - async_thread.run(api.import_url_shortcut(path)) - - @property - def hidden(self): - return not glfw.get_window_attrib(self.window, glfw.VISIBLE) - - def _hide(self, *_, **__): - self.screen_pos = glfw.get_window_pos(self.window) - glfw.hide_window(self.window) - self.tray.update_status() - - def hide(self, *_, **__): - self.call_soon.append(self._hide) - - def _show(self, *_, **__): - self.bg_mode_timer = None - self.bg_mode_notifs_timer = None - # if not self.hidden: - # glfw.hide_window(self.window) - glfw.show_window(self.window) - if utils.validate_geometry(*self.screen_pos, *self.prev_size): - glfw.set_window_pos(self.window, *self.screen_pos) - glfw.focus_window(self.window) - self.tray.update_status() - - def show(self, *_, **__): - self.call_soon.append(self._show) - - def scaled(self, size: int | float): - return _scaled(globals.settings.interface_scaling, size) - - def main_loop(self): - # Loop variables - prev_scaling = globals.settings.interface_scaling - self.scroll_energy = 0.0 - prev_any_hovered = None - prev_win_hovered = None - prev_mouse_pos = None - prev_minimized = None - prev_focused = None - any_hovered = False - win_hovered = None - prev_cursor = None - prev_hidden = None - draw_next = 5.0 - size = (0, 0) - cursor = -1 - first_frame = True - try: - # While window is open - while not glfw.window_should_close(self.window): - # Tick events and inputs - prev_mouse_pos = imgui.io.mouse_pos - prev_minimized = self.minimized - prev_win_hovered = win_hovered - prev_any_hovered = any_hovered - prev_focused = self.focused - prev_hidden = self.hidden - self.prev_size = size - prev_cursor = cursor - self.qt_app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents) - glfw.make_context_current(self.window) - if self.repeat_chars: - for char in self.input_chars: - imgui.io.add_input_character(char) - self.repeat_chars = False - self.input_chars.clear() - glfw.poll_events() - self.input_chars, self.poll_chars = self.poll_chars, self.input_chars - self.impl.process_inputs() - if self.call_soon: - while self.call_soon and (call_soon := self.call_soon.pop(0)): - call_soon() - # Window state handling - size = imgui.io.display_size - mouse_pos = imgui.io.mouse_pos - cursor = imgui.get_mouse_cursor() - any_hovered = imgui.is_any_item_hovered() - win_hovered = glfw.get_window_attrib(self.window, glfw.HOVERED) - if first_frame or (not self.hidden and not self.minimized): # Visible - - # Scroll modifiers (must be before new_frame()) - imgui.io.mouse_wheel *= globals.settings.scroll_amount - if globals.settings.scroll_smooth: - - if self.scroll_energy * imgui.io.mouse_wheel < 0: # fast check if signs are opposite - # we want to immediately reverse rather than slowly decelerating. - self.scroll_energy = 0.0 - - self.scroll_energy += imgui.io.mouse_wheel - if abs(self.scroll_energy) > 0.1: - scroll_now = self.scroll_energy * imgui.io.delta_time * globals.settings.scroll_smooth_speed - self.scroll_energy -= scroll_now - else: - scroll_now = 0.0 - self.scroll_energy = 0.0 - imgui.io.mouse_wheel = scroll_now - - # Redraw only when needed - launch_state_changed = callbacks.launch_state_changed - callbacks.launch_state_changed = False - popup_stack_changed = globals.popup_stack_changed - globals.popup_stack_changed = False - draw = ( - (api.downloads and any(dl.state in (dl.State.Verifying, dl.State.Extracting) for dl in api.downloads.values())) - or (imagehelper.redraw and globals.settings.play_gifs and (self.focused or globals.settings.play_gifs_unfocused)) - or imgui.io.mouse_wheel or self.input_chars or any(imgui.io.mouse_down) or any(imgui.io.keys_down) - or (prev_mouse_pos != mouse_pos and (prev_win_hovered or win_hovered)) - or imagehelper.apply_queue or imagehelper.unload_queue - or prev_scaling != globals.settings.interface_scaling - or prev_minimized != self.minimized - or api.session.connector._acquired - or prev_focused != self.focused - or prev_hidden != self.hidden - or size != self.prev_size - or self.recalculate_ids - or launch_state_changed - or popup_stack_changed - or self.new_styles - or api.updating - ) - if draw: - draw_next = max(draw_next, imgui.io.delta_time + 1.0) # Draw for at least next half second - if draw_next > 0.0: # Visible and drawing - if not first_frame: - draw_next -= imgui.io.delta_time - draw_start = time.perf_counter() - - # Reactive mouse cursors - if cursor != prev_cursor or any_hovered != prev_any_hovered: - shape = glfw.ARROW_CURSOR - if cursor == imgui.MOUSE_CURSOR_TEXT_INPUT: - shape = glfw.IBEAM_CURSOR - elif any_hovered: - shape = glfw.HAND_CURSOR - glfw.set_cursor(self.window, glfw.create_standard_cursor(shape)) - - # Start drawing - prev_scaling = globals.settings.interface_scaling - imgui.new_frame() - self.new_styles = False - imagehelper.redraw = False - - # Imgui window is top left of display window, and has same size - imgui.set_next_window_position(0, 0, imgui.ONCE) - if size != self.prev_size and not self.minimized: - imgui.set_next_window_size(*size, imgui.ALWAYS) - - # Create main window - imgui.push_style_var(imgui.STYLE_WINDOW_BORDERSIZE, 0) - imgui.begin("F95Checker", closable=False, flags=self.window_flags) - imgui.pop_style_var() - sidebar_size = self.scaled(self.sidebar_size) - - # Main pane - imgui.begin_child("###main_frame", width=-sidebar_size) - self.hovered_game = None - # Tabbar - self.draw_tabbar() - # Games container - match globals.settings.display_mode.value: - case DisplayMode.list.value: - self.draw_games_list() - case DisplayMode.grid.value: - self.draw_games_grid() - case DisplayMode.kanban.value: - self.draw_games_kanban() - # Bottombar - self.draw_bottombar() - imgui.end_child() - - # Prepare bottom status / watermark text (done before sidebar to get text offset from bottom of window) - if (count := api.images_counter.count) > 0: - text = f"Downloading {count} image{'s' if count > 1 else ''}..." - elif (count := api.full_checks_counter.count) > 0: - text = f"Fetching {count} full thread{'s' if count > 1 else ''}..." - elif (count := api.fast_checks_counter) > 0: - text = f"Validating {count} cached item{'s' if count > 1 else ''}..." - elif globals.last_update_check is None: - text = "Checking for updates..." - elif (count := imagehelper.compress_counter) > 0: - text = "Compressing images..." if count == 1 else f"Compressing {count} frames..." - elif api.f95_ratelimit._waiters or api.f95_ratelimit_sleeping.count: - text = f"Waiting for F95zone ratelimit..." - else: - text = self.watermark_text - _3 = self.scaled(3) - _6 = self.scaled(6) - text_size = imgui.calc_text_size(text) - text_x = size.x - text_size.x - _6 - text_y = size.y - text_size.y - _6 - - # Sidebar - imgui.same_line(spacing=imgui.style.item_spacing.x) - imgui.begin_child("###sidebar_frame", width=sidebar_size - imgui.style.item_spacing.x + 1, height=-text_size.y) - self.draw_sidebar() - imgui.end_child() - - # Status / watermark text - imgui.set_cursor_screen_pos((text_x - _3, text_y)) - if imgui.invisible_button("", width=text_size.x + _6, height=text_size.y + _3): - utils.push_popup(self.draw_about_popup) - elif imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - callbacks.open_webpage(api.f95_host) - imgui.set_cursor_screen_pos((text_x, text_y)) - imgui.text(text) - - # Popups - open_popup_count = 0 - for popup in globals.popup_stack: - opened, closed = popup() - if closed: - globals.popup_stack.remove(popup) - globals.popup_stack_changed = True - open_popup_count += opened - # Popups are closed all at the end to allow stacking - for _ in range(open_popup_count): - imgui.end_popup() - - # Close main window (technically popups are inside the window, and inside one another - this gives proper stacking order) - imgui.end() - - # Render interface - imgui.render() - self.impl.render(imgui.get_draw_data()) - # Rescale fonts - if prev_scaling != globals.settings.interface_scaling: - self.refresh_fonts() - self.refresh_styles() - async_thread.run(db.update_settings("interface_scaling")) - imagehelper.post_draw(time.perf_counter() - draw_start) - # Wait idle time - glfw.swap_buffers(self.window) - if first_frame: - if not globals.settings.start_in_background: - glfw.show_window(self.window) - if globals.settings.start_refresh: - utils.start_refresh_task(api.refresh()) - first_frame = False - else: # Visible but not drawing - glfw.wait_events_timeout(1 / 15) - else: # Not visible - # Unload images if necessary - imagehelper.post_draw(0) - # Tray bg mode and not paused - if self.hidden and not self.bg_mode_paused and not utils.is_refreshing(): - if not self.bg_mode_timer: - # Schedule next refresh - self.bg_mode_timer = time.time() + globals.settings.bg_refresh_interval * 60 - self.tray.update_status() - elif self.bg_mode_timer and time.time() > self.bg_mode_timer: - # Run scheduled refresh - self.bg_mode_timer = None - utils.start_refresh_task(api.refresh(notifs=False), reset_bg_timers=False) - elif globals.settings.check_notifs: - if not self.bg_mode_notifs_timer: - # Schedule next notif check - self.bg_mode_notifs_timer = time.time() + globals.settings.bg_notifs_interval * 60 - self.tray.update_status() - elif self.bg_mode_notifs_timer and time.time() > self.bg_mode_notifs_timer: - # Run scheduled notif check - self.bg_mode_notifs_timer = None - utils.start_refresh_task(api.check_notifs(standalone=True), reset_bg_timers=False, notify_new_games=False) - # Wait idle time - if self.tray.menu_open: - time.sleep(1 / 60) - else: - time.sleep(1 / 8) # 8 FPS tray refresh icon - if utils.is_refreshing(): - globals.gui.tray.animate_refresh_icon() - finally: - # Main loop over, cleanup and close - self.save_filters() - async_thread.wait(db.update_settings("zoom_area", "zoom_times")) - imgui.save_ini_settings_to_disk(imgui.io.ini_file_name) - ini = imgui.save_ini_settings_to_memory() - try: - start = ini.find("[Window][F95Checker]") - assert start != -1 - end = ini.find("\n\n", start) - assert end != -1 - if "ScreenPos=" not in ini[start:end]: - insert = ini.find("\n", start) - new_ini = ini[:insert] + f"\nScreenPos={self.screen_pos[0]},{self.screen_pos[1]}" + ini[insert:] - else: - new_ini = ini - except Exception: - new_ini = ini - with open(imgui.io.ini_file_name, "w") as f: - f.write(new_ini) - self.impl.shutdown() - glfw.terminate() - - def draw_hover_text(self, hover_text: str, text="(?)", force=False): - if text: - imgui.text_disabled(text) - if force or imgui.is_item_hovered(): - imgui.begin_tooltip() - imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) - imgui.text(hover_text) - imgui.pop_text_wrap_pos() - imgui.end_tooltip() - - def begin_framed_text(self, color: tuple[float], interaction=True): - if interaction: - imgui.push_style_color(imgui.COLOR_BUTTON_ACTIVE, *color) - imgui.push_style_color(imgui.COLOR_BUTTON_HOVERED, *color) - else: - imgui.push_no_interaction() - imgui.push_style_color(imgui.COLOR_BUTTON, *color) - imgui.push_style_color(imgui.COLOR_TEXT, *colors.foreground_color(color)) - imgui.push_style_var(imgui.STYLE_FRAME_BORDERSIZE, 0) - - def end_framed_text(self, interaction=True): - imgui.pop_style_var() - if interaction: - imgui.pop_style_color(4) - else: - imgui.pop_style_color(2) - imgui.pop_no_interaction() - - def get_type_label_width(self): - if self.type_label_width is None: - self.type_label_width = 0 - for name in Type._member_names_: - self.type_label_width = max(self.type_label_width, imgui.calc_text_size(name).x) - self.type_label_width += 8 - return self.type_label_width - - def draw_type_widget(self, type: Type, wide=True, align=False): - quick_filter = globals.settings.quick_filters - self.begin_framed_text(type.color, interaction=quick_filter) - if wide: - x_padding = 4 - backup_y_padding = imgui.style.frame_padding.y - imgui.push_style_var(imgui.STYLE_FRAME_PADDING, (x_padding, 0)) - if align: - imgui.push_y(backup_y_padding) - clicked = imgui.button(type.name, width=self.get_type_label_width()) - if align: - imgui.pop_y() - imgui.pop_style_var() - else: - clicked = imgui.small_button(type.name) - if clicked and quick_filter: - flt = Filter(FilterMode.Type) - flt.match = type - self.filters.append(flt) - self.end_framed_text(interaction=quick_filter) - - def draw_tag_widget(self, tag: Tag, quick_filter=True, change_highlight=True): - quick_filter = quick_filter and globals.settings.quick_filters - interaction = quick_filter or change_highlight - color = (0.3, 0.3, 0.3, 1.0) - if globals.settings.highlight_tags: - if highlight := globals.settings.tags_highlights.get(tag): - color = highlight.color - self.begin_framed_text(color, interaction=interaction) - if imgui.small_button(tag.text) and quick_filter: - flt = Filter(FilterMode.Tag) - flt.match = tag - self.filters.append(flt) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_RIGHT) and change_highlight: - if tag not in globals.settings.tags_highlights: - globals.settings.tags_highlights[tag] = TagHighlight[TagHighlight._member_names_[0]] - else: - highlight = globals.settings.tags_highlights[tag] - if highlight is TagHighlight[TagHighlight._member_names_[-1]]: - del globals.settings.tags_highlights[tag] - else: - globals.settings.tags_highlights[tag] = TagHighlight(highlight + 1) - async_thread.run(db.update_settings("tags_highlights")) - self.end_framed_text(interaction=interaction) - - def draw_label_widget(self, label: Label, short=False): - quick_filter = globals.settings.quick_filters - self.begin_framed_text(label.color, interaction=quick_filter) - if imgui.small_button(label.short_name if short else label.name) and quick_filter: - flt = Filter(FilterMode.Label) - flt.match = label - self.filters.append(flt) - if short and imgui.is_item_hovered(): - imgui.begin_tooltip() - imgui.push_font(imgui.fonts.default) - self.draw_label_widget(label, short=False) - imgui.pop_font() - imgui.end_tooltip() - self.end_framed_text(interaction=quick_filter) - - def draw_tab_widget(self, tab: Tab): - color = (tab and tab.color) or globals.settings.style_accent - self.begin_framed_text(color, interaction=False) - if (tab and tab.icon) and (tab and (tab.name or 'New Tab')): - imgui.small_button(f"{tab.icon} {tab.name or 'New Tab'}") - else: - imgui.small_button(f"{Tab.first_tab_label()}") - self.end_framed_text(interaction=False) - - def draw_status_widget(self, status: Status): - quick_filter = globals.settings.quick_filters - if quick_filter: - imgui.begin_group() - pos = imgui.get_cursor_pos() - imgui.text_colored(getattr(icons, status.icon), *status.color) - if quick_filter: - imgui.set_cursor_pos(pos) - if imgui.invisible_button("", *imgui.get_item_rect_size()): - flt = Filter(FilterMode.Status) - flt.match = status - self.filters.append(flt) - imgui.end_group() - - def draw_game_update_icon(self, game: Game): - quick_filter = globals.settings.quick_filters - with imgui.begin_group(): - pos = imgui.get_cursor_pos() - imgui.text_colored(icons.star_circle, 0.85, 0.85, 0.00) - imgui.set_cursor_pos(pos) - imgui.invisible_button("", *imgui.get_item_rect_size()) - if imgui.is_item_hovered(): - imgui.begin_tooltip() - imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) - imgui.text("This game has been updated!") - imgui.text_disabled("Installed:") - imgui.same_line() - imgui.text(game.installed or 'N/A') - imgui.text_disabled("Latest:") - imgui.same_line() - imgui.text(game.version) - imgui.text( - "To remove this update marker:\n" - f"{icons.menu_right} Middle click\n" - f"{icons.menu_right} Alt + Left click\n" - f"{icons.menu_right} Mark game as installed" - ) - imgui.pop_text_wrap_pos() - imgui.end_tooltip() - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - # Middle click - remove update marker - game.updated = False - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_LEFT): - if imgui.is_key_down(glfw.KEY_LEFT_ALT): - # Alt + left click - remove update marker - game.updated = False - elif quick_filter: - # Left click - trigger quick filter - flt = Filter(FilterMode.Updated) - self.filters.append(flt) - - def draw_game_unknown_tags_icon(self, game: Game): - with imgui.begin_group(): - pos = imgui.get_cursor_pos() - imgui.text_colored(icons.progress_tag, 1.00, 0.65, 0.00) - imgui.set_cursor_pos(pos) - imgui.invisible_button("", *imgui.get_item_rect_size()) - if imgui.is_item_hovered(): - imgui.begin_tooltip() - imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) - imgui.text( - "This game has new tags that F95Checker failed to recognize:\n" - + "\n".join(f" - {tag}" for tag in game.unknown_tags) - ) - imgui.text( - "To copy them:\n" - f"{icons.menu_right} Shift + Left click\n" - f"{icons.menu_right} Use Copy button in Tags section\n" - ) - imgui.text( - "To remove this marker:\n" - f"{icons.menu_right} Middle click\n" - f"{icons.menu_right} Alt + Left click\n" - ) - imgui.pop_text_wrap_pos() - imgui.end_tooltip() - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - # Middle click - remove unknown tags marker - game.unknown_tags_flag = False - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_LEFT): - if imgui.is_key_down(glfw.KEY_LEFT_ALT): - # Alt + left click - remove unknown tags marker - game.unknown_tags_flag = False - elif imgui.is_key_down(glfw.KEY_LEFT_SHIFT): - # Shift + left click - copy tags to clipboard - callbacks.clipboard_copy(", ".join(game.unknown_tags)) - - def draw_game_archive_icon(self, game: Game): - quick_filter = globals.settings.quick_filters - if quick_filter: - imgui.begin_group() - pos = imgui.get_cursor_pos() - imgui.text_disabled(icons.archive) - if quick_filter: - imgui.set_cursor_pos(pos) - if imgui.invisible_button("", *imgui.get_item_rect_size()): - flt = Filter(FilterMode.Archived) - self.filters.append(flt) - imgui.end_group() - self.draw_hover_text( - "This game is archived!\n" - "In this state you won't receive update notifications for\n" - "this game and it will stay at the bottom of the list.\n" - "Middle click to remove it from the archive, alternatively\n" - "use the right click menu to do the same.", - text=None, - ) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - game.archived = False - - def draw_game_more_info_button(self, game: Game, label="", selectable=False, carousel_ids: list = None): - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if clicked: - if not game: - carousel_ids = [game.id for game in globals.games.values() if game.selected] - game = globals.games[carousel_ids[0]] - utils.push_popup(self.draw_game_info_popup, game, carousel_ids.copy() if carousel_ids else None) - - def draw_game_play_button(self, game: Game, label="", selectable=False, executable: str = None): - if game and not game.executables: - imgui.push_style_color(imgui.COLOR_TEXT, *imgui.style.colors[imgui.COLOR_TEXT_DISABLED][:3], 0.75) - elif game: - if executable: - try: - valid = game.executables_valids[game.executables.index(executable)] - except Exception: - valid = False - else: - valid = game.executables_valid - if not valid: - imgui.push_style_color(imgui.COLOR_TEXT, 0.87, 0.20, 0.20) - launch_state = game.launch_state if game else LaunchState.Idle - if launch_state is LaunchState.Starting: - label = label.replace(" Play", " Starting") - imgui.push_style_color(imgui.COLOR_TEXT, 0.95, 0.75, 0.20) - elif launch_state is LaunchState.Playing: - label = label.replace(" Play", " Playing") - imgui.push_style_color(imgui.COLOR_TEXT, 0.30, 0.85, 0.35) - else: - launch_state = LaunchState.Idle # Make sure we don't imgui.pop_style_color() after - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if launch_state is not LaunchState.Idle: - imgui.pop_style_color() - if game and (not game.executables or not valid): - imgui.pop_style_color() - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - if game: - callbacks.open_game_folder(game, executable=executable) - else: - for game in globals.games.values(): - if game.selected: - callbacks.open_game_folder(game) - elif clicked: - if game: - callbacks.launch_game(game, executable=executable) - else: - for game in globals.games.values(): - if game.selected: - callbacks.launch_game(game) - - def draw_game_name_text(self, game: Game): - if game.archived: - imgui.text_disabled(game.name) - elif game.finished == game.version: - imgui.text(game.name) - else: - imgui.text_colored(game.name, *globals.settings.style_accent) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - callbacks.clipboard_copy(game.name) - - def draw_game_finished_checkbox(self, game: Game, label=""): - if game: - installed_finished = game.finished == (game.installed or game.version) - if installed_finished: - checkbox = imgui.checkbox - else: - checkbox = imgui._checkbox - changed, _ = checkbox(f"{label}###{game.id}_finished", bool(game.finished)) - if changed: - if installed_finished: - game.finished = "" # Finished -> Not finished - else: - game.finished = (game.installed or game.version) # Not finished -> Finished, Outdated finished -> Finished - game.add_timeline_event(TimelineEventType.GameFinished, game.version) - if game.finished and not installed_finished and imgui.is_item_hovered(): - imgui.begin_tooltip() - imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) - imgui.text_disabled("Finished:") - imgui.same_line() - imgui.text(game.finished) - imgui.text_disabled("Installed:") - imgui.same_line() - imgui.text(game.installed or 'N/A') - imgui.text("Click to mark installed as finished.") - imgui.pop_text_wrap_pos() - imgui.end_tooltip() - else: - if imgui.small_button(icons.check): - for game in globals.games.values(): - if game.selected: - game.finished = (game.installed or game.version) - game.add_timeline_event(TimelineEventType.GameFinished, game.version) - imgui.same_line() - if imgui.small_button(icons.close): - for game in globals.games.values(): - if game.selected: - game.finished = "" - imgui.same_line() - imgui.text(label + " ") - - def draw_game_installed_checkbox(self, game: Game, label=""): - if game: - latest_installed = game.installed == game.version - if latest_installed: - checkbox = imgui.checkbox - else: - checkbox = imgui._checkbox - changed, _ = checkbox(f"{label}###{game.id}_installed", bool(game.installed)) - if changed: - if latest_installed: - game.installed = "" # Latest installed -> Not installed - else: - game.add_timeline_event(TimelineEventType.GameInstalled, game.version) - game.installed = game.version # Not installed -> Latest installed, Outdated installed -> Latest installed - game.updated = False - if game.installed and not latest_installed and imgui.is_item_hovered(): - imgui.begin_tooltip() - imgui.push_text_wrap_pos(min(imgui.get_font_size() * 35, imgui.io.display_size.x)) - imgui.text_disabled("Installed:") - imgui.same_line() - imgui.text(game.installed or 'N/A') - imgui.text_disabled("Latest:") - imgui.same_line() - imgui.text(game.version) - imgui.text("Click to mark latest as installed.") - imgui.pop_text_wrap_pos() - imgui.end_tooltip() - else: - if imgui.small_button(icons.check): - for game in globals.games.values(): - if game.selected: - game.add_timeline_event(TimelineEventType.GameInstalled, game.version) - game.installed = game.version - game.updated = False - imgui.same_line() - if imgui.small_button(icons.close): - for game in globals.games.values(): - if game.selected: - game.installed = "" - imgui.same_line() - imgui.text(label + " ") - - def draw_game_rating_widget(self, game: Game): - if not game: - imgui.text("Set:") - imgui.same_line() - if imgui.small_button(icons.close): - for game in globals.games.values(): - if game.selected: - game.rating = 0 - imgui.same_line() - changed, value = ratingwidget.ratingwidget("", game.rating if game else 0) - if changed: - if game: - game.rating = value - else: - for game in globals.games.values(): - if game.selected: - game.rating = value - - def draw_game_open_thread_button(self, game: Game, label="", selectable=False): - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if game and imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - if globals.settings.copy_urls_as_bbcode: - _url = lambda game: f"[URL='{game.url}']{game.name}[/URL]" - else: - _url = lambda game: game.url - if game: - text = _url(game) - else: - text = "\n".join(_url(game) for game in globals.games.values() if game.selected if game.url) - callbacks.clipboard_copy(text) - elif clicked: - if game: - callbacks.open_webpage(game.url) - else: - for game in globals.games.values(): - if game.selected: - callbacks.open_webpage(game.url) - - def draw_game_copy_link_button(self, game: Game, label="", selectable=False): - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if clicked: - if globals.settings.copy_urls_as_bbcode: - _url = lambda game: f"[URL='{game.url}']{game.name}[/URL]" - else: - _url = lambda game: game.url - if game: - text = _url(game) - else: - text = "\n".join(_url(game) for game in globals.games.values() if game.selected if game.url) - callbacks.clipboard_copy(text) - - def draw_game_archive_button(self, game: Game, label_off="", label_on="", selectable=False): - if game: - if selectable: - clicked = imgui.selectable(label_on if game.archived else label_off, False)[0] - else: - clicked = imgui.button(label_on if game.archived else label_off) - if clicked: - game.archived = not game.archived - if game.archived: - game.updated = False - else: - if imgui.small_button(icons.check): - for game in globals.games.values(): - if game.selected: - game.archived = True - game.updated = False - imgui.same_line() - if imgui.small_button(icons.close): - for game in globals.games.values(): - if game.selected: - game.archived = False - imgui.same_line() - imgui.text(label_off + " ") - - def draw_game_remove_button(self, game: Game, label="", selectable=False): - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if clicked: - if game: - callbacks.remove_game(game) - else: - callbacks.remove_game(*filter(lambda game: game.selected, globals.games.values())) - - def draw_game_add_exe_button(self, game: Game, label="", selectable=False): - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if clicked: - if game: - callbacks.add_game_exe(game) - else: - for game in globals.games.values(): - if game.selected: - callbacks.add_game_exe(game) - - def draw_game_clear_exes_button(self, game: Game, label="", selectable=False): - if game and not game.executables: - imgui.push_disabled() - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if game and not game.executables: - imgui.pop_disabled() - if clicked: - imgui.close_current_popup() - if game: - game.clear_executables() - else: - for game in globals.games.values(): - if game.selected: - game.clear_executables() - - def draw_game_open_folder_button(self, game: Game, label="", selectable=False, executable: str = None): - if game and not game.executables: - imgui.push_alpha(0.5) - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if game and not game.executables: - imgui.pop_alpha() - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - def _exe(exe): - if utils.is_uri(exe): - return exe - exe = pathlib.Path(exe) - if globals.settings.default_exe_dir.get(globals.os) and not exe.is_absolute(): - exe = pathlib.Path(globals.settings.default_exe_dir.get(globals.os)) / exe - return str(exe) - if game: - if executable: - text = _exe(executable) - else: - text = "\n".join(_exe(exe) for exe in game.executables) - else: - text = "\n".join("\n".join(_exe(exe) for exe in game.executables) for game in globals.games.values() if game.selected) - callbacks.clipboard_copy(text) - elif clicked: - if game: - callbacks.open_game_folder(game, executable=executable) - else: - for game in globals.games.values(): - if game.selected: - callbacks.open_game_folder(game) - - def draw_game_id_button(self, game: Game, label="", selectable=False): - if game.custom: - imgui.push_disabled() - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if clicked: - callbacks.clipboard_copy(str(game.id)) - self.draw_hover_text( - f"Thread ID: {game.id}\n" - "Click to copy!", - text=None, - ) - if game.custom: - imgui.pop_disabled() - - def draw_game_recheck_button(self, game: Game, label="", selectable=False): - if game and game.custom: - imgui.push_disabled() - if selectable: - clicked = imgui.selectable(label, False)[0] - else: - clicked = imgui.button(label) - if clicked: - games = [game] if game else list(filter(lambda g: g.selected, globals.games.values())) - for g in games: - g.add_timeline_event(TimelineEventType.RecheckUserReq) - utils.start_refresh_task(api.refresh(*games, full=True, notifs=False)) - if game and game.custom: - imgui.pop_disabled() - - def draw_game_tab_widget(self, game: Game): - self.draw_tab_widget(game.tab) - if imgui.begin_popup_context_item(f"###{game.id}_context_tab"): - imgui.text("Move To:") - imgui.separator() - self.draw_game_tab_select_widget(game) - imgui.end_popup() - - def draw_game_labels_select_widget(self, game: Game): - if Label.instances: - if game: - for label in Label.instances: - changed, value = imgui.checkbox(f"###{game.id}_label_{label.id}", label in game.labels) - if changed: - if value: - game.add_label(label) - else: - game.remove_label(label) - imgui.same_line() - self.draw_label_widget(label) - else: - for label in Label.instances: - if imgui.small_button(icons.check): - for game in globals.games.values(): - if game.selected: - game.add_label(label) - imgui.same_line() - if imgui.small_button(icons.close): - for game in globals.games.values(): - if game.selected: - game.remove_label(label) - imgui.same_line() - self.draw_label_widget(label) - else: - imgui.text_disabled("Make some labels first!") - - def draw_game_tab_select_widget(self, game: Game): - current_tab = game.tab if game else self.current_tab - new_tab = current_tab - if current_tab is None: - imgui.push_disabled() - if imgui.selectable(f"{Tab.first_tab_label()}###move_tab_-1", False)[0]: - new_tab = None - if current_tab is None: - imgui.pop_disabled() - for tab in Tab.instances: - if current_tab is tab: - imgui.push_disabled() - color = (tab and tab.color) or globals.settings.style_accent - imgui.push_style_color(imgui.COLOR_HEADER_HOVERED, *color) - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###move_tab_{tab.id}", False)[0]: - new_tab = tab - imgui.set_cursor_pos(pos) - if imgui.is_item_hovered(): - imgui.pop_style_color() - imgui.push_style_color(imgui.COLOR_TEXT, *colors.foreground_color(color)) - imgui.text(f"{tab.icon} {tab.name or 'New Tab'}") - imgui.pop_style_color() - if current_tab is tab: - imgui.pop_disabled() - if imgui.selectable(f"{icons.tab_plus} New Tab###move_tab_-2", False)[0]: - new_tab = async_thread.wait(db.create_tab()) - globals.settings.display_tab = new_tab - async_thread.run(db.update_settings("display_tab")) - self.recalculate_ids = True - imgui.close_current_popup() - if new_tab is not current_tab: - imgui.close_current_popup() - if game: - game.tab = new_tab - else: - for game in globals.games.values(): - if game.selected: - game.tab = new_tab - - def draw_game_tags_select_widget(self, game: Game): - for tag in Tag: - changed, value = imgui.checkbox(f"###{game.id}_tag_{tag.value}", tag in game.tags) - if changed: - if value: - game.tags = tuple(sorted(list(game.tags) + [tag], key=lambda tag: tag.name)) - else: - game.tags = tuple(sorted(filter(lambda x: x is not tag, game.tags), key=lambda tag: tag.name)) - imgui.same_line() - self.draw_tag_widget(tag, quick_filter=False, change_highlight=False) - - def draw_game_context_menu(self, game: Game = None): - if not game: - imgui.text(f"Selected games: {self.selected_games_count}") - self.draw_game_more_info_button(game, f"{icons.information_outline} More Info", selectable=True, carousel_ids=self.show_games_ids[self.current_tab]) - self.draw_game_recheck_button(game, f"{icons.reload_alert} Full Recheck", selectable=True) - imgui.separator() - self.draw_game_play_button(game, f"{icons.play} Play", selectable=True) - self.draw_game_open_thread_button(game, f"{icons.open_in_new} Open Thread", selectable=True) - self.draw_game_copy_link_button(game, f"{icons.content_copy} Copy Link", selectable=True) - imgui.separator() - self.draw_game_add_exe_button(game, f"{icons.folder_edit_outline} Add Exe", selectable=True) - self.draw_game_clear_exes_button(game, f"{icons.folder_remove_outline} Clear Exes", selectable=True) - self.draw_game_open_folder_button(game, f"{icons.folder_open_outline} Open Folder", selectable=True) - imgui.separator() - self.draw_game_finished_checkbox(game, f"{icons.flag_checkered} Finished") - self.draw_game_installed_checkbox(game, f"{icons.download} Installed") - imgui.separator() - self.draw_game_rating_widget(game) - if imgui.begin_menu(f"{icons.label_multiple_outline} Labels"): - self.draw_game_labels_select_widget(game) - imgui.end_menu() - if imgui.begin_menu(f"{icons.tab} Move to Tab"): - self.draw_game_tab_select_widget(game) - imgui.end_menu() - imgui.separator() - self.draw_game_archive_button(game, label_off=f"{icons.archive_outline} Archive", label_on=f"{icons.archive_off_outline} Unarchive", selectable=True) - self.draw_game_remove_button(game, f"{icons.trash_can_outline} Remove", selectable=True) - - def draw_game_notes_widget(self, game: Game, multiline=True, width: int | float = None): - if multiline: - changed, value = imgui.input_text_multiline( - f"###{game.id}_notes", - value=game.notes, - width=width or imgui.get_content_region_available_width(), - height=self.scaled(450) - ) - if changed: - game.notes = value - if imgui.begin_popup_context_item(f"###{game.id}_notes_context"): - utils.text_context(game, "notes") - imgui.end_popup() - else: - imgui.set_next_item_width(width or imgui.get_content_region_available_width()) - # Only show first line - if (offset := game.notes.find("\n")) != -1: - first_line = game.notes[:offset] - else: - first_line = game.notes - changed, first_line = imgui.input_text( - f"###{game.id}_notes_inline", - value=first_line - ) - def setter_extra(value: str): - # Merge with remaining lines - if (offset := game.notes.find("\n")) != -1: - value += game.notes[offset:] - game.notes = value - if changed: - setter_extra(first_line) - if imgui.begin_popup_context_item(f"###{game.id}_notes_inline_context"): - utils.text_context(type("_", (), dict(_=first_line))(), "_", setter_extra) - imgui.end_popup() - - def draw_game_reviews_widget(self, game: Game): - icon_coordinates: list[tuple[x1, y1, x2, y2]] = [] - text_coordinates: list[tuple[x1, y1, x2, y2]] = [] - line_coordinates: list[tuple[x1, y1, x2, y2]] = [] - - imgui.dummy(0, self.scaled(6)) - - for review in game.reviews: - if review.score > 3: - icon = icons.thumb_up - elif review.score == 3: - icon = icons.approximately_equal - else: - icon = icons.thumb_down - date = dt.datetime.fromtimestamp(review.timestamp) - # Draw icon - imgui.dummy(0, 0) - imgui.same_line() - cur = imgui.get_cursor_screen_pos() - imgui.text_colored(icon, *globals.settings.style_accent) - icon_size = imgui.get_item_rect_size() - icon_coordinates.append((cur.x, cur.y, cur.x + icon_size.x, cur.y + icon_size.y)) - # Draw header - imgui.same_line(spacing=self.scaled(15)) - pos_x, header_pos_y = imgui.get_cursor_screen_pos() - imgui.push_font(imgui.fonts.bold) - imgui.text_colored(review.user, *globals.settings.style_accent) - imgui.pop_font() - imgui.same_line() - imgui.push_y(self.scaled(3.5)) - imgui.text_disabled(date.strftime(globals.settings.datestamp_format)) - self.draw_hover_text(date.strftime(globals.settings.timestamp_format), text=None) - imgui.same_line() - imgui.push_no_interaction() - ratingwidget.ratingwidget("", review.score) - imgui.pop_no_interaction() - imgui.pop_y() - imgui.spacing() - # Draw message - message_pos_y = imgui.get_cursor_screen_pos().y - imgui.set_cursor_screen_pos((pos_x, message_pos_y)) - imgui.text(review.message) - # Draw footer - if review.likes: - footer_pos_y = imgui.get_cursor_screen_pos().y - imgui.set_cursor_screen_pos((pos_x, footer_pos_y)) - imgui.text_disabled(f"Liked by {review.likes} user{'' if review.likes == 1 else 's'}") - final_pos_x = imgui.get_cursor_screen_pos().x + imgui.get_content_region_available_width() - self.scaled(2) - final_pos_y = imgui.get_cursor_screen_pos().y - self.scaled(2) - text_coordinates.append((pos_x, header_pos_y, final_pos_x, final_pos_y)) - line_coordinates.append((pos_x, message_pos_y, final_pos_x, message_pos_y)) - imgui.dummy(0, self.scaled(16)) - - if len(game.reviews) < game.reviews_total: - imgui.dummy(0, 0) - imgui.same_line() - cur = imgui.get_cursor_screen_pos() - imgui.begin_group() - imgui.text_colored(icons.open_in_new, *globals.settings.style_accent) - imgui.same_line(spacing=self.scaled(10)) - imgui.text(f"Read {game.reviews_total - len(game.reviews)} more reviews in forum thread ") - imgui.end_group() - button_size = imgui.get_item_rect_size() - if imgui.is_item_clicked(): - callbacks.open_webpage(game.url + "/page-2/br-reviews") - icon_coordinates.append((cur.x, cur.y, cur.x + button_size.x, cur.y + button_size.y)) - imgui.set_cursor_screen_pos(cur) - imgui.invisible_button("", *button_size) - - thickness = imgui.style.frame_border_size - prev_rect = None - padding = self.scaled(3) - dl = imgui.get_window_draw_list() - color = imgui.get_color_u32_rgba(*globals.settings.style_border) - - # Draw timeline primitives - rounding = self.scaled(globals.settings.style_corner_radius) - for x1, y1, x2, y2 in icon_coordinates: - dl.add_rect(x1 - padding, y1 - padding, x2 + padding, y2 + padding, color, rounding=rounding, thickness=thickness) - if prev_rect: - dl.add_line((prev_rect[0] + prev_rect[2]) / 2, prev_rect[3] + padding, (prev_rect[0] + prev_rect[2]) / 2, y1 - padding, color, thickness=thickness) - prev_rect = (x1, y1, x2, y2) - for x1, y1, x2, y2 in text_coordinates: - dl.add_rect(x1 - padding - self.scaled(2), y1 - padding, x2 + padding + self.scaled(2), y2 + padding, color, rounding=rounding, thickness=thickness) - for x1, y1, x2, y2 in line_coordinates: - dl.add_line(x1 - padding - self.scaled(2), y1 - padding, x2 + padding + self.scaled(2), y2 - padding, color, thickness=thickness) - - def draw_game_tags_widget(self, game: Game): - pad = 2 * imgui.style.frame_padding.x + imgui.style.item_spacing.x - for tag in game.tags: - if imgui.get_content_region_available_width() < imgui.calc_text_size(tag.name).x + pad: - imgui.dummy(0, 0) - self.draw_tag_widget(tag) - imgui.same_line() - imgui.dummy(0, 0) - - def draw_game_labels_widget(self, game: Game, wrap=True, small=False, short=False, align=False): - pad = 2 * imgui.style.frame_padding.x + imgui.style.item_spacing.x - if small: - short = True - imgui.push_font(imgui.fonts.small) - if align: - imgui.push_y(self.scaled(2.5)) - popped_y = False - for label in game.labels: - if wrap and imgui.get_content_region_available_width() < imgui.calc_text_size(label.short_name if short else label.name).x + pad: - if small and align and not popped_y: - imgui.pop_y() - popped_y = True - imgui.dummy(0, 0) - self.draw_label_widget(label, short=short) - imgui.same_line() - if small and align and not popped_y: - imgui.pop_y() - elif wrap: - imgui.dummy(0, 0) - if small: - imgui.pop_font() - - def draw_timeline_filter_widget(self, game: Game): - label = f"###{game.id}_timeline_filter_popup" - if imgui.button(f"{icons.eye_off_outline} Hide events"): - imgui.open_popup(label) - if imgui.begin_popup(label): - for event_type in TimelineEventType: - changed, value = imgui.checkbox(f"###{game.id}_event_{event_type.value}", event_type in globals.settings.hidden_timeline_events) - if changed: - if value: - globals.settings.hidden_timeline_events.append(event_type) - else: - globals.settings.hidden_timeline_events.remove(event_type) - async_thread.run(db.update_settings("hidden_timeline_events")) - imgui.same_line() - imgui.text(f"{getattr(icons, event_type.icon)} {event_type.display}") - imgui.end_popup() - - def draw_game_timeline_widget(self, game: Game): - icon_coordinates: list[tuple[x1, y1, x2, y2]] = [] - text_coordinates: list[tuple[x1, y1, x2, y2]] = [] - - self.draw_timeline_filter_widget(game) - imgui.dummy(0, 0 if globals.settings.compact_timeline else self.scaled(6)) - - def draw_event(timestamp, type, args, spacing=True): - icon = getattr(icons, type.icon) - date = dt.datetime.fromtimestamp(timestamp) - message = type.template.format(*args, *["?" for _ in range(type.args_min - len(args))]) - # Short timeline variant - if globals.settings.compact_timeline: - imgui.push_font(imgui.fonts.mono) - imgui.text_disabled(date.strftime(globals.settings.timestamp_format)) - imgui.pop_font() - imgui.same_line() - imgui.text_colored(icon, *globals.settings.style_accent) - imgui.same_line() - imgui.text(message) - return - # Draw icon - imgui.dummy(0, 0) - imgui.same_line() - cur = imgui.get_cursor_screen_pos() - imgui.text_colored(icon, *globals.settings.style_accent) - icon_size = imgui.get_item_rect_size() - icon_coordinates.append((cur.x, cur.y, cur.x + icon_size.x, cur.y + icon_size.y)) - # Draw timestamp - imgui.same_line(spacing=self.scaled(15)) - timestamp_pos = imgui.get_cursor_screen_pos() - imgui.text_disabled(date.strftime(globals.settings.datestamp_format)) - timestamp_size = imgui.get_item_rect_size() - self.draw_hover_text(date.strftime(globals.settings.timestamp_format), text=None) - # Draw message - message_pos = (timestamp_pos.x, timestamp_pos.y + timestamp_size.y - self.scaled(2)) - imgui.set_cursor_screen_pos(message_pos) - imgui.text(message) - message_size = imgui.get_item_rect_size() - final_x = timestamp_pos.x + timestamp_size.x if timestamp_size.x > message_size.x else message_pos[0] + message_size.x - text_coordinates.append((timestamp_pos.x, timestamp_pos.y, final_x, message_pos[1] + message_size.y)) - if spacing: - imgui.dummy(0, self.scaled(10)) - - for event in game.timeline_events: - if event.type not in globals.settings.hidden_timeline_events: - draw_event(event.timestamp.value, event.type, event.arguments) - - if TimelineEventType.GameAdded not in globals.settings.hidden_timeline_events: - draw_event(game.added_on.value, TimelineEventType.GameAdded, [], spacing=False) - - thickness = imgui.style.frame_border_size - prev_rect = None - padding = self.scaled(3) - dl = imgui.get_window_draw_list() - color = imgui.get_color_u32_rgba(*globals.settings.style_border) - - # Draw timeline primitives - rounding = self.scaled(globals.settings.style_corner_radius) - for x1, y1, x2, y2 in icon_coordinates: - dl.add_rect(x1 - padding, y1 - padding, x2 + padding, y2 + padding, color, rounding=rounding, thickness=thickness) - if prev_rect: - dl.add_line((prev_rect[0] + prev_rect[2]) / 2, prev_rect[3] + padding, (x1 + x2) / 2, y1 - padding, color, thickness=thickness) - prev_rect = (x1, y1, x2, y2) - for x1, y1, x2, y2 in text_coordinates: - dl.add_rect(x1 - padding - self.scaled(2), y1 - padding, x2 + padding + self.scaled(2), y2 + padding, color, rounding=rounding, thickness=thickness) - - def draw_game_downloads_header(self, game: Game): - pad = 3 * imgui.style.item_spacing.x - def _cluster_text(name, text): - imgui.text_disabled(name[0]) - if imgui.is_item_hovered(): - imgui.set_tooltip(name[2:]) - imgui.same_line() - imgui.text(text) - imgui.same_line(spacing=pad) - _cluster_text(cols.name.name, game.name) - _cluster_text(cols.version.name, game.version) - _cluster_text(cols.last_updated.name, game.last_updated.display or "Unknown") - _cluster_text(cols.developer.name, game.developer) - imgui.spacing() - imgui.spacing() - - def draw_updates_popup(self, popup_uuid: str = ""): - def popup_content(): - indent = self.scaled(222) - width = indent - 3 * imgui.style.item_spacing.x - full_width = 3 * indent - img_pos_x = imgui.get_cursor_pos_x() - category = None - category_open = False - imgui.push_text_wrap_pos(full_width) - imgui.indent(indent) - for game_i, id in enumerate(globals.updated_games_sorted_ids): - if id not in globals.games: - globals.updated_games_sorted_ids.remove(id) - continue - old_game = globals.updated_games[id] - game = globals.games[id] - if category is not game.type.category: - category = game.type.category - imgui.push_font(imgui.fonts.big) - imgui.set_cursor_pos_x(img_pos_x - self.scaled(8)) - category_open = imgui.tree_node( - category.name, - flags=( - imgui.TREE_NODE_NO_TREE_PUSH_ON_OPEN | - imgui.TREE_NODE_SPAN_FULL_WIDTH | - imgui.TREE_NODE_DEFAULT_OPEN - ) - ) - imgui.pop_font() - if not category_open: - continue - img_pos_y = imgui.get_cursor_pos_y() - imgui.begin_group() - - imgui.push_font(imgui.fonts.big) - imgui.text(old_game.name) - imgui.pop_font() - - imgui.spacing() - imgui.text_disabled("Update date: ") - imgui.same_line() - imgui.text(game.last_updated.display) - - if Tab.instances: - imgui.spacing() - imgui.text_disabled("Tab: ") - imgui.same_line() - self.draw_game_tab_widget(game) - - for attr in ("name", "version"): - old_val = getattr(old_game, attr) or "Unknown" - new_val = getattr(game, attr) or "Unknown" - if new_val != old_val: - imgui.spacing() - imgui.text_disabled(f"{attr.title()}: ") - imgui.same_line() - # Hack: get fast line wrapping in ImGui but draw arrow in other color: - # Save position, draw full text with arrow in dimmed color, restore position, - # and draw full text in normal color like an overlay, with an Em Space instead - # of the arrow. This seems to be the only character that is whitespace but - # counts as "solid" for wrapping. This way, if arrow itself wraps to nextline, - # when drawing without arrow the Em Space will make ImGui leave space for it. - val_start = imgui.get_cursor_pos() - imgui.text_disabled(f"{old_val} -> {new_val}") - imgui.set_cursor_pos(val_start) - em_space = " " - imgui.text(f"{old_val} {em_space} {new_val}") - - if game.status is not old_game.status: - imgui.spacing() - imgui.text_disabled("Status: ") - imgui.same_line() - imgui.text(old_game.status.name) - imgui.same_line() - self.draw_status_widget(old_game.status) - imgui.same_line() - arrow_width = imgui.calc_text_size(" -> ").x + imgui.style.item_spacing.x - if full_width - imgui.get_cursor_pos_x() < arrow_width: - imgui.dummy(0, 0) - imgui.text_disabled(" -> ") - imgui.same_line() - imgui.text(game.status.name) - imgui.same_line() - self.draw_status_widget(game.status) - - imgui.spacing() - self.draw_game_open_thread_button(game, f"{icons.open_in_new} Thread") - imgui.same_line() - self.draw_game_copy_link_button(game, f"{icons.content_copy} Link") - imgui.same_line() - self.draw_game_more_info_button(game, f"{icons.information_outline} Info", carousel_ids=globals.updated_games_sorted_ids) - - imgui.end_group() - height = imgui.get_item_rect_size().y + imgui.style.item_spacing.y - crop = game.image.crop_to_ratio(width / height, fit=globals.settings.fit_images) - imgui.set_cursor_pos((img_pos_x, img_pos_y)) - game.image.render(width, height, *crop, rounding=self.scaled(globals.settings.style_corner_radius)) - - if game_i != len(globals.updated_games_sorted_ids) - 1: - imgui.text("\n") - imgui.unindent(indent) - imgui.pop_text_wrap_pos() - opened, closed = utils.popup( - f"{len(globals.updated_games_sorted_ids)} update{'' if len(globals.updated_games_sorted_ids) == 1 else 's'}", - popup_content, - buttons=True, - closable=True, - outside=False, - popup_uuid=popup_uuid - ) - if closed: - globals.updated_games_sorted_ids.clear() - globals.updated_games.clear() - return opened, closed - - def draw_game_image_error(self, game: Game, width: float, height: float): - if game.image.error == "Image file missing": - text = "Image missing!" - if game.custom: - hover_text = "Right click in More Info popup to add an image." - else: - hover_text = ( - "This image link blocks us! You can blame Imgur." if game.image_url == "blocked" else - "This thread does not seem to have an image!" if game.image_url == "missing" else - "This image link cannot be reached anymore!" if game.image_url == "dead" else - "Run a refresh/recheck to try downloading it again!" - ) - else: - text = "Image error!" - hover_text = game.image.error or "Unknown error" - - text_size = imgui.calc_text_size(text) - if text_size.x >= width: - hover_text = f"{text}\n{hover_text}" - text = "( ! )" - text_size = imgui.calc_text_size(text) - - pos = imgui.get_cursor_pos() - imgui.set_cursor_pos((pos.x + (width - text_size.x) / 2, pos.y + (height - text_size.y) / 2)) - self.draw_hover_text(text=text, hover_text=hover_text) - imgui.set_cursor_pos(pos) - imgui.dummy(width, height) - - def draw_game_info_popup(self, game: Game, carousel_ids: list = None, popup_uuid: str = ""): - def popup_content(): - # Image - imgui.indent(imgui.style.scrollbar_size) - image = game.image - avail = imgui.get_content_region_available() - if imgui.get_scroll_max_y() <= 0.0: - # Avoid shifing content with/without scroll bar - avail = avail._replace(x=avail.x - imgui.style.scrollbar_size) - close_image = False - zoom_popup = False - rounding = self.scaled(globals.settings.style_corner_radius) - out_height = (min(avail.y, self.scaled(690)) * self.scaled(0.4)) or 1 - out_width = avail.x or 1 - if image.error: - self.draw_game_image_error(game, out_width, out_height) - else: - aspect_ratio = image.height / image.width - if aspect_ratio > (out_height / out_width): - height = out_height - width = height * (1 / aspect_ratio) - margin_x = (out_width - width) / 2 - margin_y = 0 - else: - width = out_width - height = width * aspect_ratio - margin_x = 0 - margin_y = (out_height - height) / 2 - prev_pos = imgui.get_cursor_pos() - imgui.set_cursor_pos((prev_pos.x + margin_x, prev_pos.y + margin_y)) - image_pos = imgui.get_cursor_screen_pos() - - imgui.begin_child("###image_zoomer", width=width, height=height + 1.0, flags=imgui.WINDOW_NO_SCROLLBAR) - imgui.dummy(width + 2.0, height) - imgui.set_scroll_x(1.0) - imgui.set_cursor_screen_pos(image_pos) - image.render(width, height, rounding=rounding) - if imgui.is_item_hovered(): - # Image popup - if imgui.is_mouse_down(): - size = imgui.io.display_size - if aspect_ratio > size.y / size.x: - height = size.y - self.scaled(10) - width = height / aspect_ratio - else: - width = size.x - self.scaled(10) - height = width * aspect_ratio - x = (size.x - width) / 2 - y = (size.y - height) / 2 - flags = imgui.DRAW_ROUND_CORNERS_ALL - pos2 = (x + width, y + height) - fg_draw_list = imgui.get_foreground_draw_list() - fg_draw_list.add_image_rounded(image.texture_id, (x, y), pos2, rounding=rounding, flags=flags) - # Zoom - elif globals.settings.zoom_enabled: - if int(imgui.get_scroll_x() - 1.0): - if globals.settings.scroll_smooth: - diff = imgui.io.delta_time * self.scroll_energy * 30 - else: - diff = imgui.io.mouse_wheel * 3 - if imgui.is_key_down(glfw.KEY_LEFT_ALT): - globals.settings.zoom_area = min(max(globals.settings.zoom_area + diff, 1), 500) - else: - globals.settings.zoom_times = min(max(globals.settings.zoom_times * (diff / 50.0 + 1.0), 1), 20) - zoom_popup = True - out_size = min(*imgui.io.display_size) * globals.settings.zoom_area / 100 - in_size = out_size / globals.settings.zoom_times - mouse_pos = imgui.io.mouse_pos - off_x = utils.map_range(in_size, 0.0, width, 0.0, 1.0) / 2.0 - off_y = utils.map_range(in_size, 0.0, height, 0.0, 1.0) / 2.0 - x = utils.map_range(mouse_pos.x, image_pos.x, image_pos.x + width, 0.0, 1.0) - y = utils.map_range(mouse_pos.y, image_pos.y, image_pos.y + height, 0.0, 1.0) - imgui.set_next_window_position(*mouse_pos, pivot_x=0.5, pivot_y=0.5) - imgui.begin_tooltip() - image.render(out_size, out_size, (x - off_x, y - off_y), (x + off_x, y + off_y), rounding=rounding) - imgui.end_tooltip() - close_image = True - if imgui.begin_popup_context_item("###image_context"): - if imgui.selectable(f"{icons.folder_open_outline} Set custom image", False)[0]: - def select_callback(selected): - if selected: - game.image_url = "custom" - game.set_image_sync(pathlib.Path(selected).read_bytes()) - utils.push_popup(filepicker.FilePicker( - title=f"Select or drop image for {game.name}", - callback=select_callback - ).tick) - if imgui.selectable(f"{icons.trash_can_outline} Reset image", False)[0]: - game.delete_images() - game.refresh_image() - imgui.end_popup() - if close_image: - imgui.end_child() - imgui.set_cursor_pos(prev_pos) - imgui.dummy(out_width, out_height) - imgui.unindent(imgui.style.scrollbar_size) - - # The indexer stores these URLs separately from the cover image. - # Load them lazily so opening an info popup does not slow startup - # or download images for games the user never inspects. - if game.previews_urls: - if not game.previews_loaded and not game.previews_loading: - game.preview_load_future = async_thread.run(game.load_previews_async()) - if game.previews_loading: - download_count = api.images_counter.count - loading_text = ( - f" · Downloading {download_count} image" - f"{'s' if download_count != 1 else ''}" - if download_count else " · Loading..." - ) - else: - loading_text = "" - imgui.text(f"Previews ({len(game.preview_images)}/{len(game.previews_urls)}){loading_text}") - if game.preview_images: - # Keep each preview at a useful thumbnail size and put - # the row in a child with an explicit horizontal bar. - # Without a child, ImGui clips same-line items at the - # popup boundary and the parent only scrolls vertically. - preview_width = max(self.scaled(240), (out_width - imgui.style.item_spacing.x) / 2) - preview_height = self.scaled(260) - horizontal_flags = ( - getattr(imgui, "WINDOW_HORIZONTAL_SCROLLING_BAR", 0) | - getattr(imgui, "WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR", 0) - ) - imgui.begin_child( - "###game_previews_gallery", - width=out_width, - height=preview_height + imgui.get_frame_height() + imgui.style.item_spacing.y, - flags=horizontal_flags, - ) - first = True - for preview in game.preview_images: - aspect = preview.height / preview.width if preview.width else 1.0 - height = min(preview_height, preview_width * aspect) - if not first: - imgui.same_line() - preview_pos = imgui.get_cursor_pos() - preview.render(preview_width, height, rounding=rounding) - if preview.error: - # Keep failed previews in the gallery so users - # can see that an item failed rather than assuming - # it was silently omitted. - preview_hovered = imgui.is_item_hovered() - imgui.set_cursor_pos(preview_pos) - imgui.text_wrapped("Preview unavailable") - imgui.set_cursor_pos((preview_pos.x, preview_pos.y + height)) - if preview_hovered: - imgui.set_tooltip(preview.error) - first = False - imgui.end_child() - imgui.push_text_wrap_pos() - - imgui.push_font(imgui.fonts.big) - self.draw_game_name_text(game) - imgui.pop_font() - - self.draw_game_play_button(game, f"{icons.play} Play") - imgui.same_line() - self.draw_game_open_thread_button(game, f"{icons.open_in_new} Thread") - imgui.same_line() - self.draw_game_copy_link_button(game, f"{icons.content_copy} Link") - imgui.same_line() - self.draw_game_id_button(game, f"{icons.pound} ID") - imgui.same_line() - self.draw_game_finished_checkbox(game, f"{icons.flag_checkered} Finished") - _10 = self.scaled(10) - imgui.same_line(spacing=_10) - self.draw_game_installed_checkbox(game, f"{icons.download} Installed") - imgui.same_line(spacing=_10) - self.draw_game_recheck_button(game, f"{icons.reload_alert} Recheck") - imgui.same_line() - self.draw_game_archive_button(game, label_off=f"{icons.archive_outline} Archive", label_on=f"{icons.archive_off_outline} Unarchive") - imgui.same_line() - self.draw_game_remove_button(game, f"{icons.trash_can_outline} Remove") - - # Minimum width to avoid content shifting around and clipping (like Archive/Unarchive) - imgui.dummy(self.scaled(790), 0) - - if imgui.begin_table(f"###details", column=2, outer_size_width=avail.x): - imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) - imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) - - imgui.table_next_row() - - imgui.table_next_column() - imgui.text_disabled("Version:") - imgui.same_line() - if game.updated: - self.draw_game_update_icon(game) - imgui.same_line() - if game.unknown_tags_flag: - self.draw_game_unknown_tags_icon(game) - imgui.same_line() - imgui.text(game.version) - - imgui.table_next_column() - imgui.text_disabled("Added On:") - imgui.same_line() - imgui.text(game.added_on.display) - - imgui.table_next_row() - - imgui.table_next_column() - imgui.text_disabled("Developer:") - imgui.same_line() - imgui.text(game.developer) - - imgui.table_next_column() - imgui.text_disabled("Last Updated:") - imgui.same_line() - imgui.text(game.last_updated.display or "Unknown") - - imgui.table_next_row() - - imgui.table_next_column() - imgui.text_disabled("Status:") - imgui.same_line() - imgui.text(game.status.name) - imgui.same_line() - self.draw_status_widget(game.status) - - imgui.table_next_column() - imgui.text_disabled("Last Launched:") - imgui.same_line() - imgui.text(game.last_launched.display or "Never") - if imgui.is_item_clicked(): - game.last_launched = time.time() - game.add_timeline_event(TimelineEventType.GameLaunched, "date set manually") - self.draw_hover_text("Click to set as launched right now!", text=None) - - imgui.table_next_row() - - imgui.table_next_column() - imgui.text_disabled("Forum Score:") - imgui.same_line() - imgui.text(f"{game.score:.1f}/5") - imgui.same_line() - imgui.text_disabled(f"({game.votes})") - - imgui.table_next_column() - imgui.text_disabled("Playtime:") - imgui.same_line() - imgui.text(game.playtime_display or "None") - - imgui.table_next_row() - - imgui.table_next_column() - imgui.text_disabled("Type:") - imgui.same_line() - self.draw_type_widget(game.type) - - imgui.table_next_column() - imgui.text_disabled("Personal Rating:") - imgui.same_line() - self.draw_game_rating_widget(game) - - imgui.table_next_row() - - imgui.table_next_column() - imgui.align_text_to_frame_padding() - imgui.text_disabled("Executables:") - imgui.same_line() - self.draw_game_add_exe_button(game, f"{icons.folder_edit_outline} Add") - imgui.same_line() - self.draw_game_open_folder_button(game, f"{icons.folder_open_outline} Open Folder") - imgui.same_line() - self.draw_game_clear_exes_button(game, f"{icons.folder_remove_outline} Clear") - - imgui.table_next_column() - imgui.text_disabled("Tab:") - imgui.same_line() - self.draw_game_tab_widget(game) - - imgui.table_next_row() - - # Draw labels on right first, so executables on left can then overflow below to right - imgui.table_set_column_index(1) - imgui.begin_group() - imgui.align_text_to_frame_padding() - imgui.text_disabled("Labels:") - imgui.same_line() - if game.labels: - self.draw_game_labels_widget(game) - else: - imgui.button("Right click to add") - imgui.end_group() - labels_end_y = imgui.get_cursor_pos_y() - if imgui.begin_popup_context_item(f"###{game.id}_context_labels"): - self.draw_game_labels_select_widget(game) - imgui.end_popup() - - imgui.table_set_column_index(0) - imgui.align_text_to_frame_padding() - imgui.text_disabled("Exe Wrapper:") - imgui.same_line() - imgui.set_next_item_width(-1) - changed, value = imgui.input_text_with_hint( - f"###{game.id}_launch_wrapper", - f"Inherit default for {game.type.name}", - game.launch_wrapper.get(globals.os, "") - ) - if changed: - game.launch_wrapper[globals.os] = value - async_thread.run(db.update_game(game, "launch_wrapper")) - if imgui.begin_popup_context_item(f"###{game.id}_launch_wrapper_context"): - utils.text_context(game, "launch_wrapper", no_icons=True) - imgui.end_popup() - self.draw_hover_text( - "A custom command to wrap the executable path into. Useful for specifying custom ways to open it, like Wine/Proton for Windows games on Linux/macOS or a media player for animation/comic collections.\n" - "Can also be used to specify custom arguments, to the exe or to a wrapper.\n" - "\n" - "Use %command% to substitute in the executable path, for example:\n" - "- VLC.exe -LZ %command%\n" - "- %command% -dx12\n" - "- env WINEPREFIX=/path/to/prefix wine %command%\n" - "\n" - "This is a game-specific override. You can setup default wrappers in Settings > Manage > Exe Wrappers, which will also auto-detect Wine/Proton runners on Linux/macOS.", - text=None, - ) - ended_table = False - for executable in game.executables: - if not ended_table and (pos_y := imgui.get_cursor_pos_y()) >= labels_end_y: - imgui.end_table() - ended_table = True - imgui.set_cursor_pos_y(pos_y) - self.draw_game_play_button(game, icons.play, executable=executable) - imgui.same_line() - self.draw_game_open_folder_button(game, icons.folder_open_outline, executable=executable) - imgui.same_line() - if imgui.button(icons.folder_remove_outline): - game.remove_executable(executable) - imgui.same_line() - # Hack: make ImGui wrap arbitrarily by using an idegraphic space (U+3000) with 0-width font - ig_space = " " - imgui.text(executable.replace("/", f"/{ig_space}").replace("\\", f"\\{ig_space}")) - - if not ended_table: - pos_y = max(imgui.get_cursor_pos_y(), labels_end_y) - imgui.end_table() - imgui.set_cursor_pos_y(pos_y) - - imgui.spacing() - imgui.spacing() - - if imgui.begin_tab_bar("Details"): - - if imgui.begin_tab_item(( - icons.clipboard_text_outline if game.changelog else - icons.clipboard_text_off_outline - ) + " Changelog###changelog")[0]: - imgui.spacing() - if game.changelog: - imgui.text(game.changelog) - else: - imgui.text_disabled("Either this game doesn't have a changelog, or the thread is not formatted properly!") - imgui.end_tab_item() - - if imgui.begin_tab_item(( - icons.information_outline if game.description else - icons.information_off_outline - ) + " Description###description")[0]: - imgui.spacing() - if game.description: - imgui.text(game.description) - else: - imgui.text_disabled("Either this game doesn't have a description, or the thread is not formatted properly!") - imgui.end_tab_item() - - if not game.custom and imgui.begin_tab_item(icons.tray_arrow_down + " Downloads###downloads")[0]: - imgui.spacing() - imgui.text("RPDL Torrents:") - imgui.same_line() - if imgui.small_button(f"{icons.magnify}Search"): - rpdl.open_search_popup(game) - imgui.spacing() - imgui.text("F95zone Donor DDL:") - imgui.same_line() - if imgui.small_button(f"{icons.cloud_check_variant_outline}Check"): - api.open_ddl_popup(game) - imgui.spacing() - imgui.spacing() - imgui.spacing() - imgui.text("Regular Downloads:") - imgui.same_line() - self.draw_hover_text( - "Left clicking opens the webpage in your chosen browser.\n" - "Middle clicking copies the link to your clipboard.\n\n" - "There's 3 types of links that work slightly different:\n" - f"{icons.link}Direct: Plain download link, retrieved with automated integrated browser\n" - f"{icons.domino_mask}Masked: CAPTCHA protected link, unmasked with automated integrated browser\n" - f"{icons.open_in_app}Forum: Link internal to F95zone, no special processing\n" - ) - imgui.spacing() - if game.downloads: - can_add_spacing = False - _20 = self.scaled(20) - for name, mirrors in game.downloads: - if mirrors: - can_add_spacing = True - imgui.text(name + ":") - for mirror, link in mirrors: - imgui.same_line() - if imgui.get_content_region_available_width() < imgui.calc_text_size(icons.link + mirror).x + _20: - imgui.dummy(0, 0) - if not link: - # Use thread url when link is missing - link = game.url - if link.startswith("//"): - # XPath expression - if imgui.small_button(icons.link + mirror): - callbacks.redirect_xpath_link(game.url, link) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - callbacks.redirect_xpath_link(game.url, link, copy=True) - elif link.startswith(f"{api.f95_host}/masked/"): - # Masked link - if imgui.small_button(icons.domino_mask + mirror): - callbacks.redirect_masked_link(link) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - callbacks.redirect_masked_link(link, copy=True) - elif link.startswith(api.f95_host): - # F95zone link - if imgui.small_button(icons.open_in_app + mirror): - callbacks.open_webpage(link) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - callbacks.clipboard_copy(link) - else: - # Should never happen, but here for backwards compatibility - if imgui.small_button(icons.link + mirror): - callbacks.open_webpage(link) - if imgui.is_item_clicked(imgui.MOUSE_BUTTON_MIDDLE): - callbacks.clipboard_copy(link) - else: - if can_add_spacing: - imgui.text("") - imgui.text(name) - imgui.spacing() - imgui.spacing() - can_add_spacing = False - else: - imgui.text_disabled("Either this game doesn't have regular downloads, or the thread is not formatted properly!") - imgui.end_tab_item() - - if imgui.begin_tab_item(( - icons.draw_pen if game.notes else - icons.pencil_plus_outline - ) + " Notes###notes")[0]: - imgui.spacing() - self.draw_game_notes_widget(game) - imgui.end_tab_item() - - if not game.custom and imgui.begin_tab_item(( - icons.star_outline if game.reviews else - icons.star_off_outline - ) + " Reviews###reviews")[0]: - imgui.spacing() - if game.reviews: - self.draw_game_reviews_widget(game) - else: - imgui.text_disabled("This game doesn't have any reviews yet!") - imgui.end_tab_item() - - if imgui.begin_tab_item(( - icons.tag_multiple_outline if len(game.tags) > 1 else - icons.tag_outline if len(game.tags) == 1 else - icons.tag_off_outline - ) + " Tags###tags")[0]: - imgui.spacing() - if game.tags: - self.draw_game_tags_widget(game) - else: - imgui.text_disabled("This game has no tags!") - if utags := game.unknown_tags: - imgui.spacing() - if imgui.tree_node(f"Unknown tags ({len(utags)})"): - if imgui.button(f"{icons.tag_multiple_outline} Copy"): - callbacks.clipboard_copy(", ".join(utags)) - for tag in utags: - imgui.text(f"- {tag}") - imgui.tree_pop() - imgui.end_tab_item() - - if imgui.begin_tab_item(icons.timeline_clock_outline + " Timeline###timeline")[0]: - imgui.spacing() - self.draw_game_timeline_widget(game) - imgui.end_tab_item() - - if imgui.begin_tab_item(( - icons.puzzle_check_outline if game.custom else - icons.puzzle_remove_outline - ) + " Custom###custom")[0]: - if game.custom: - imgui.text( - "This is a custom game. You can edit all its details below (except downloads). " - "If you wish to convert it back to a F95zone game, then make sure to fill the " - "game url with a valid F95zone thread url before pressing the button below." - ) - if imgui.button(f"{icons.puzzle_remove} Convert to F95zone game"): - callbacks.convert_custom_to_f95zone(game) - imgui.text("") - imgui.push_item_width(-imgui.FLOAT_MIN) - pos_x = imgui.get_cursor_pos_x() + imgui.calc_text_size("Description:").x - - imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("URL:").x) - imgui.align_text_to_frame_padding() - imgui.text("URL:") - imgui.same_line() - changed, value = imgui.input_text("###url", game.url) - if changed: - game.url = value - if imgui.begin_popup_context_item("###url_context"): - utils.text_context(game, "url", no_icons=True) - imgui.end_popup() - - imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Name:").x) - imgui.align_text_to_frame_padding() - imgui.text("Name:") - imgui.same_line() - imgui.set_next_item_width(imgui.get_content_region_available_width() - self.scaled(120)) - changed, value = imgui.input_text("###name", game.name) - if changed: - game.name = value - if imgui.begin_popup_context_item("###name_context"): - utils.text_context(game, "name", no_icons=True) - imgui.end_popup() - imgui.same_line(spacing=imgui.style.item_spacing.x * 2) - imgui.text("Score:") - imgui.same_line() - changed, value = imgui.drag_float("###score", game.score, change_speed=0.01, min_value=0, max_value=5, format="%.1f/5") - if changed: - game.score = value - - imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Version:").x) - imgui.align_text_to_frame_padding() - imgui.text("Version:") - imgui.same_line() - imgui.set_next_item_width(imgui.get_content_region_available_width() / 3.5) - changed, value = imgui.input_text("###version", game.version) - if changed: - game.version = value or "N/A" - if imgui.begin_popup_context_item("###version_context"): - utils.text_context(game, "version", no_icons=True) - imgui.end_popup() - imgui.same_line(spacing=imgui.style.item_spacing.x * 2) - imgui.text("Developer:") - imgui.same_line() - imgui.set_next_item_width(imgui.get_content_region_available_width() / 2.1) - changed, value = imgui.input_text("###developer", game.developer) - if changed: - game.developer = value - if imgui.begin_popup_context_item("###developer_context"): - utils.text_context(game, "developer", no_icons=True) - imgui.end_popup() - imgui.same_line(spacing=imgui.style.item_spacing.x * 2) - imgui.text("Status:") - imgui.same_line() - if imgui.begin_combo("###status", game.status.name): - for status in Status: - selected = status is game.status - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###status_{status.value}", selected)[0]: - game.status = status - if selected: - imgui.set_item_default_focus() - imgui.set_cursor_pos(pos) - self.draw_status_widget(status) - imgui.same_line() - imgui.text(status.name) - imgui.end_combo() - - imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Tags:").x) - imgui.align_text_to_frame_padding() - imgui.text("Tags:") - imgui.same_line() - imgui.button("Right click") - if imgui.begin_popup_context_item("###tags_context"): - self.draw_game_tags_select_widget(game) - imgui.end_popup() - imgui.same_line(spacing=imgui.style.item_spacing.x * 2) - imgui.text("Updated:") - imgui.same_line() - changed, value = imgui.checkbox("###updated", game.updated) - if changed: - game.updated = value - imgui.same_line(spacing=imgui.style.item_spacing.x * 2) - imgui.text("Last Updated:") - date = dt.datetime.fromtimestamp(game.last_updated.value) - day, month, year = date.day, date.month, date.year - frame_height = imgui.get_frame_height() - imgui.same_line() - imgui.set_next_item_width(1.5 * frame_height) - day = min(max(imgui.drag_int("###last_updated_day", day, change_speed=0.05, min_value=1, max_value=31)[1], 1), 31) - imgui.same_line() - imgui.set_next_item_width(1.5 * frame_height) - month = min(max(imgui.drag_int("###last_updated_month", month, change_speed=0.03, min_value=1, max_value=12)[1], 1), 12) - imgui.same_line() - imgui.set_next_item_width(2 * frame_height) - year = min(max(imgui.drag_int("###last_updated_year", year, change_speed=0.05, min_value=1970, max_value=9999)[1], 1970), 9999) - if day != date.day or month != date.month or year != date.year: - for _ in range(5): - try: - date = date.replace(day=day, month=month, year=year) - game.last_updated = parser.datestamp(date.timestamp()) - except (ValueError, OSError): - day -= 1 - continue - break - imgui.same_line(spacing=imgui.style.item_spacing.x * 2) - imgui.text("Type:") - imgui.same_line() - if imgui.begin_combo("###type", game.type.name): - category = None - for type in Type: - if category is not type.category: - category = type.category - imgui.text(category.name) - selected = type is game.type - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###type_{type.value}", selected)[0]: - game.type = type - if selected: - imgui.set_item_default_focus() - imgui.set_cursor_pos(pos) - self.draw_type_widget(type) - imgui.end_combo() - - imgui.align_text_to_frame_padding() - imgui.text("Description:") - imgui.same_line() - changed, value = imgui.input_text_multiline("###description", value=game.description) - if changed: - game.description = value - if imgui.begin_popup_context_item("###description_context"): - utils.text_context(game, "description") - imgui.end_popup() - - imgui.set_cursor_pos_x(pos_x - imgui.calc_text_size("Changelog:").x) - imgui.align_text_to_frame_padding() - imgui.text("Changelog:") - imgui.same_line() - changed, value = imgui.input_text_multiline("###changelog", value=game.changelog) - if changed: - game.changelog = value - if imgui.begin_popup_context_item("###changelog_context"): - utils.text_context(game, "changelog") - imgui.end_popup() - - imgui.pop_item_width() - else: - imgui.text( - "Here you have the option to convert this game to a custom game. Custom games are not checked for updates and become untied from F95zone. " - "This is useful for games that have been removed for breaking forum rules, or for adding games from other platforms to your library. Custom " - "games allow you to edit all their details (except downloads) that would normally be fetched from F95zone." - ) - if imgui.button(f"{icons.puzzle_check} Convert to custom game"): - callbacks.convert_f95zone_to_custom(game) - imgui.end_tab_item() - - imgui.end_tab_bar() - imgui.pop_text_wrap_pos() - change_id = None - if carousel_ids and len(carousel_ids) > 1 and game.id in carousel_ids and imgui.is_topmost() and not imgui.is_any_item_active(): - # Has and is in carousel ids, is not the only one in them, is topmost popup and no item is active - pos = imgui.get_window_position() - size = imgui.get_window_size() - if size and pos: - imgui.push_font(imgui.fonts.big) - text_size = imgui.calc_text_size(icons.arrow_left_drop_circle) - offset = self.scaled(10) - mouse_pos = imgui.get_mouse_pos() - mouse_clicked = imgui.is_mouse_clicked() - y = pos.y + (size.y + text_size.y) / 2 - x1 = pos.x - offset - text_size.x - x2 = pos.x + size.x + offset - if not zoom_popup: - draw_list = imgui.get_foreground_draw_list() - col = imgui.get_color_u32_rgba(*globals.settings.style_text_dim) - draw_list.add_text(x1, y, col, icons.arrow_left_drop_circle) - draw_list.add_text(x2, y, col, icons.arrow_right_drop_circle) - y_ok = y <= mouse_pos.y <= y + text_size.y - clicked_left = mouse_clicked and x1 <= mouse_pos.x <= x1 + text_size.x and y_ok - clicked_right = mouse_clicked and x2 <= mouse_pos.x <= x2 + text_size.x and y_ok - imgui.pop_font() - idx = carousel_ids.index(game.id) - if imgui.is_key_pressed(glfw.KEY_LEFT, repeat=True) or clicked_left: - idx -= 1 - if idx == -1: - idx = len(carousel_ids) - 1 - change_id = carousel_ids[idx] - if imgui.is_key_pressed(glfw.KEY_RIGHT, repeat=True) or clicked_right: - idx += 1 - if idx == len(carousel_ids): - idx = 0 - change_id = carousel_ids[idx] - if change_id is not None: - # Swiping to another game closes this popup's gallery. Keep - # the downloaded files, but release decoded/GPU image data. - game.cancel_preview_loading() - game.unload_previews() - utils.push_popup(self.draw_game_info_popup, globals.games[change_id], carousel_ids).uuid = popup_uuid - return True - elif utils.close_weak_popup(): - return True - if game.id not in globals.games: - game.cancel_preview_loading() - return 0, True - result = utils.popup(game.name, popup_content, closable=True, outside=False, resize=False, popup_uuid=popup_uuid) - if result[1]: - # A closed info popup is no longer a visible owner of its - # textures. The URL cache stays on disk for cheap reopening. - game.cancel_preview_loading() - game.unload_previews() - return result - - def draw_about_popup(self, popup_uuid: str = ""): - def popup_content(): - _60 = self.scaled(60) - _230 = self.scaled(230) - imgui.begin_group() - imgui.dummy(_60, _230) - imgui.same_line() - self.logo_texture.render(_230, _230, rounding=self.scaled(globals.settings.style_corner_radius)) - imgui.same_line() - imgui.begin_group() - imgui.push_font(imgui.fonts.big) - imgui.text("F95Checker") - imgui.pop_font() - imgui.text(f"Version {globals.version_name}") - imgui.text("Made with <3 by WillyJL") - imgui.text("") - imgui.text(f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") - imgui.text(f"OpenGL {gl.glGetInteger(gl.GL_MAJOR_VERSION)}.{gl.glGetInteger(gl.GL_MINOR_VERSION)}, Py {OpenGL.__version__}") - imgui.text(f"GLFW {'.'.join(str(num) for num in glfw.get_version())}, Py {glfw.__version__}") - imgui.text(f"ImGui {imgui.get_version()}, Py {imgui.__version__}") - imgui.text(f"Qt {QtCore.QT_VERSION_STR}, Py {QtCore.PYQT_VERSION_STR}") - if globals.os is Os.Linux: - imgui.text(f"{platform.system()} {platform.release()}") - elif globals.os is Os.Windows: - imgui.text(f"{platform.system()} {platform.release()} {platform.version()}") - elif globals.os is Os.MacOS: - imgui.text(f"{platform.system()} {platform.release()}") - imgui.end_group() - imgui.same_line() - imgui.dummy(_60, _230) - imgui.end_group() - imgui.spacing() - width = imgui.get_content_region_available_width() - btn_width = (width - 2 * imgui.style.item_spacing.x) / 3 - if imgui.button(f"{icons.open_in_new} F95zone Thread", width=btn_width): - callbacks.open_webpage(tool_page) - imgui.same_line() - if imgui.button(f"{icons.github} GitHub Repo", width=btn_width): - callbacks.open_webpage(github_page) - imgui.same_line() - if imgui.button(f"{icons.link_variant} Donate + Links", width=btn_width): - callbacks.open_webpage(developer_page) - imgui.spacing() - imgui.spacing() - imgui.push_text_wrap_pos(width) - imgui.text("This software is licensed under the 3rd revision of the GNU General Public License (GPLv3) and is provided to you for free. " - "Furthermore, due to its license, it is also free as in freedom: you are free to use, study, modify and share this software " - "in whatever way you wish as long as you keep the same license.") - imgui.spacing() - imgui.spacing() - imgui.text("However, F95Checker is actively developed by one person only, WillyJL, and not with the aim of profit but out of personal " - "interest and benefit for the whole F95zone community. Donations are although greatly appreciated and aid the development " - "of this software. You can find donation links above.") - imgui.spacing() - imgui.spacing() - imgui.text("If you find bugs or have some feedback, don't be afraid to let me know either on GitHub (using issues or pull requests) " - "or on F95zone (in the thread comments or in direct messages).") - imgui.spacing() - imgui.spacing() - imgui.text("Please note that this software is not ( yet ;) ) officially affiliated with the F95zone platform.") - imgui.spacing() - imgui.spacing() - imgui.text("") - imgui.push_font(imgui.fonts.big) - size = imgui.calc_text_size("Cool people") - imgui.set_cursor_pos_x((width - size.x + imgui.style.scrollbar_size) / 2) - imgui.text("Cool people") - imgui.pop_font() - imgui.spacing() - imgui.spacing() - imgui.text("Supporters:") - for name in [ - "FaceCrap", - "WhiteVanDaycare", - "ascsd", - "GioBol", - "Jarulf", - "salkrim", - "rozzic", - "Belfaier", - "warez_gamez", - "DeadMoan", - "And 4 anons" - ]: - if imgui.get_content_region_available_width() < imgui.calc_text_size(name).x + self.scaled(20): - imgui.dummy(0, 0) - imgui.bullet_text(name) - imgui.same_line(spacing=16) - imgui.spacing() - imgui.spacing() - imgui.text("Contributors:") - imgui.bullet() - imgui.text("r37r05p3C7: Tab idea and customization, timeline, many extension features") - imgui.bullet() - imgui.text("littleraisins: Fixes, features and misc ideas from the (defunct) 'X' fork") - imgui.bullet() - imgui.text("FaceCrap: Multiple small fixes, improvements and finetuning") - imgui.bullet() - imgui.text("blackop: Proxy support, temporary ratelimit fix, linux login fix") - imgui.bullet() - imgui.text("cicklolwut: Security fixes, Linux wine/proton config, playtime stats") - imgui.bullet() - imgui.text("Sam: Support from F95zone side to make much this possible") - imgui.bullet() - imgui.text("GR3ee3N: Optimized build workflows and other PRs") - imgui.bullet() - imgui.text("batblue: MacOS suppport and feedback guy") - imgui.bullet() - imgui.text("unroot: Linux support and feedback guy") - imgui.bullet() - imgui.text("ploper26: Suggested HEAD checks (no longer used)") - imgui.bullet() - imgui.text("ascsd: Helped with brainstorming on some issues and gave some tips") - imgui.spacing() - imgui.spacing() - imgui.text("Community:") - for name in [ - "abada25", - "AtotehZ", - "bitogno", - "BrockLanders", - "d_pedestrian", - "Danv", - "DarK x Duke", - "Dukez", - "GrammerCop", - "harem.king", - "MillenniumEarl", - "simple_human", - "SmurfyBlue", - "WhiteVanDaycare", - "yohudood", - "And others that I might be forgetting" - ]: - if imgui.get_content_region_available_width() < imgui.calc_text_size(name).x + self.scaled(20): - imgui.dummy(0, 0) - imgui.bullet_text(name) - imgui.same_line(spacing=16) - imgui.pop_text_wrap_pos() - return utils.popup("About F95Checker", popup_content, closable=True, outside=True, popup_uuid=popup_uuid) - - def draw_tag_highlights_popup(self, popup_uuid: str = ""): - def popup_content(): - imgui.text_disabled("Right click tags to cycle highlighting options") - search = "" - imgui.set_next_item_width(-imgui.FLOAT_MIN) - _, search = imgui.input_text_with_hint(f"###tag_highlights_input", "Search tags...", search) - imgui.begin_child(f"###tag_highlights_frame", width=min(imgui.io.display_size.x * 0.5, self.scaled(600)), height=imgui.io.display_size.y * 0.5) - pad = 2 * imgui.style.frame_padding.x + imgui.style.item_spacing.x - for tag in Tag: - if not search or search in tag.text or search in tag.name: - if imgui.get_content_region_available_width() < imgui.calc_text_size(tag.name).x + pad: - imgui.dummy(0, 0) - self.draw_tag_widget(tag, quick_filter=False) - imgui.same_line() - imgui.end_child() - return utils.popup("Tag highlight preferences", popup_content, closable=True, outside=True, popup_uuid=popup_uuid) - - def draw_tabbar(self): - display_tab = globals.settings.display_tab - select_tab = self.current_tab is not display_tab - if not self.sorts: - # Fixes incorrect tab for first frame, causing un-shown images to load - # Sorting would happen later otherwise and tabs would be hidden due to that - self.current_tab = display_tab - self.tick_list_columns() - save_new_tab = False - elif self.dragging_tab: - # Keep current tab while resetting tabbar due to dragging - save_new_tab = False - select_tab = True - else: - save_new_tab = True - new_tab = None - if Tab.instances and not (globals.settings.filter_all_tabs and self.filtering): - if imgui.begin_tab_bar( - f"###tabbar_{','.join(str(tab.id) for tab in Tab.instances)}", - flags=self.tabbar_flags - ): - hide = globals.settings.hide_empty_tabs - count = len(self.show_games_ids.get(None, ())) - if (count or not hide) and imgui.begin_tab_item( - f"{Tab.first_tab_label()} ({count})###tab_-1", - flags=imgui.TAB_ITEM_NONE - )[0]: - new_tab = None - imgui.end_tab_item() - swap = None - for tab_i, tab in enumerate(Tab.instances): - count = len(self.show_games_ids.get(tab, ())) - if hide and not count: - continue - if tab.color: - imgui.push_style_color(imgui.COLOR_TAB, *tab.color[:3], 0.5) - imgui.push_style_color(imgui.COLOR_TAB_ACTIVE, *tab.color) - imgui.push_style_color(imgui.COLOR_TAB_HOVERED, *tab.color) - imgui.push_style_color(imgui.COLOR_TEXT, *colors.foreground_color(tab.color)) - if imgui.begin_tab_item( - f"{tab.icon} {tab.name or 'New Tab'} ({count})###tab_{tab.id}", - flags=imgui.TAB_ITEM_SET_SELECTED if select_tab and tab is display_tab else 0 - )[0]: - new_tab = tab - imgui.end_tab_item() - if tab.color: - imgui.pop_style_color(4) - if self.dragging_tab is tab and imgui.is_mouse_released(): - self.dragging_tab = None - elif imgui.is_item_active() or self.dragging_tab is tab: - mouse_pos = imgui.get_mouse_pos() - if tab_i > 0 and imgui.get_item_rect_min().x > 0 and mouse_pos.x < imgui.get_item_rect_min().x: - if imgui.get_mouse_drag_delta().x < 0: - self.dragging_tab = tab - swap = (tab_i, tab_i - 1) - imgui.reset_mouse_drag_delta() - elif tab_i < len(Tab.instances) - 1 and imgui.get_item_rect_max().x > 0 and mouse_pos.x > imgui.get_item_rect_max().x: - if imgui.get_mouse_drag_delta().x > 0: - self.dragging_tab = tab - swap = (tab_i, tab_i + 1) - imgui.reset_mouse_drag_delta() - context_id = f"###tab_{tab.id}_context" - set_focus = not imgui.is_popup_open(context_id) - if imgui.begin_popup_context_item(context_id): - imgui.set_next_item_width(imgui.get_content_region_available_width()) - if set_focus: - imgui.set_keyboard_focus_here() - changed, value = imgui.input_text_with_hint(f"###tab_name_{tab.id}", "Tab name", tab.name) - setter_extra = functools.partial(lambda t, _=None: async_thread.run(db.update_tab(t, "name")), tab) - if changed: - tab.name = value - setter_extra() - if imgui.begin_popup_context_item(f"###tab_name_{tab.id}_context"): - utils.text_context(tab, "name", setter_extra) - imgui.end_popup() - set_focus = False - if imgui.button(tab.icon): - imgui.open_popup(f"###tab_icon_{tab.id}") - set_focus = True - if imgui.begin_popup(f"###tab_icon_{tab.id}"): - search = "" - imgui.set_next_item_width(-imgui.FLOAT_MIN) - if set_focus: - imgui.set_keyboard_focus_here() - _, search = imgui.input_text_with_hint(f"###tab_icons_{tab.id}_search", "Search icons...", search) - imgui.begin_child(f"###tab_icons_{tab.id}_frame", width=self.scaled(250), height=imgui.io.display_size.y * 0.35) - for name, icon in icons.names.items(): - if not search or search in name or search in icon: - if imgui.selectable(f"{icon} {name}")[0]: - tab.icon = icon - async_thread.run(db.update_tab(tab, "icon")) - imgui.close_current_popup() - imgui.end_child() - imgui.end_popup() - imgui.same_line() - if imgui.button("Reset icon", width=imgui.get_content_region_available_width()): - tab.icon = Tab.base_icon() - async_thread.run(db.update_tab(tab, "icon")) - color = tab.color[:3] if tab.color else (0.0, 0.0, 0.0) - changed, value = imgui.color_edit3(f"###tab_color_{tab.id}", *color, flags=imgui.COLOR_EDIT_NO_INPUTS) - if changed: - tab.color = (*value, 1.0) - async_thread.run(db.update_tab(tab, "color")) - imgui.same_line() - if imgui.button("Reset color", width=imgui.get_content_region_available_width()): - tab.color = None - async_thread.run(db.update_tab(tab, "color")) - if imgui.button(f"{icons.trash_can_outline} Close (keeps games)"): - close_callback = functools.partial(lambda t: async_thread.run(db.delete_tab(t)), tab) - if globals.settings.confirm_on_remove: - buttons = { - f"{icons.check} Yes": close_callback, - f"{icons.cancel} No": None - } - utils.push_popup( - msgbox.msgbox, f"Close tab {tab.icon} {tab.name or 'New Tab'}", - "Are you sure you want to close this tab?\n" - f"The games will go back to {Tab.first_tab_label()} tab.", - MsgBox.warn, - buttons - ) - else: - close_callback() - imgui.end_popup() - imgui.end_tab_bar() - if swap: - Tab.instances[swap[0]], Tab.instances[swap[1]] = Tab.instances[swap[1]], Tab.instances[swap[0]] - Tab.update_positions() - async def _update_tab_positions(): - for tab in Tab.instances: - await db.update_tab(tab, "position") - async_thread.run(_update_tab_positions()) - if new_tab is not self.current_tab and save_new_tab: - for game in globals.games.values(): - game.selected = False - self.current_tab = new_tab - self.recalculate_ids = True - globals.settings.display_tab = new_tab - async_thread.run(db.update_settings("display_tab")) - - def calculate_ids(self, table_id: str, sorts: imgui.core._ImGuiTableSortSpecs): - manual_sort = cols.manual_sort.enabled - if manual_sort != self.prev_manual_sort: - self.prev_manual_sort = manual_sort - self.recalculate_ids = True - if self.prev_filters != self.filters: - self.prev_filters = self.filters.copy() - self.recalculate_ids = True - if sorts.specs_dirty: - new_sorts = [] - for sort_spec in sorts.specs: - new_sorts.insert(0, SortSpec(index=sort_spec.column_index, reverse=bool(sort_spec.sort_direction - 1))) - self.sorts[table_id] = new_sorts - sorts.specs_dirty = False - self.recalculate_ids = True - if self.recalculate_ids: - self.recalculate_ids = False - # Pick base ID list - if manual_sort: - changed = False - for id in globals.settings.manual_sort_list.copy(): - if id not in globals.games: - globals.settings.manual_sort_list.remove(id) - changed = True - for id in globals.games: - if id not in globals.settings.manual_sort_list: - globals.settings.manual_sort_list.insert(0, id) - changed = True - if changed: - async_thread.run(db.update_settings("manual_sort_list")) - base_ids = globals.settings.manual_sort_list - else: - base_ids = globals.games.keys() - filtering = False - # Filter globally by filters - for flt in self.filters: - match flt.mode.value: - case FilterMode.Archived.value: - key = lambda game, f: (game.archived is True) - case FilterMode.Custom.value: - key = lambda game, f: (game.custom is True) - case FilterMode.Exe_State.value: - key = lambda game, f: ( - (not game.executables) if f.match is ExeState.Unset else - (bool(game.executables) and (game.executables_valid != (f.match is ExeState.Invalid))) - ) - case FilterMode.Finished.value: - key = lambda game, f: ( - (game.finished != "") if f.match else - (game.finished == (game.installed or game.version)) - ) - case FilterMode.Installed.value: - key = lambda game, f: ( - (game.installed != "") if f.match else - (game.installed == game.version) - ) - case FilterMode.Label.value: - key = lambda game, f: (f.match in game.labels) - case FilterMode.Rating.value: - key = lambda game, f: (game.rating == f.match) - case FilterMode.Score.value: - key = lambda game, f: (game.score >= f.match) - case FilterMode.Status.value: - key = lambda game, f: (game.status is f.match) - case FilterMode.Tag.value: - key = lambda game, f: (f.match in game.tags) - case FilterMode.Type.value: - key = lambda game, f: (game.type is f.match) - case FilterMode.Updated.value: - key = lambda game, f: (game.updated is True) - case _: - key = None - if key is not None: - base_ids = filter(functools.partial(lambda f, k, id: f.invert != k(globals.games[id], f), flt, key), base_ids) - filtering = True - # Filter globally by search - if self.add_box_text: - if self.add_box_valid: - id_matches = [match.id for match in utils.extract_thread_matches(self.add_box_text)] - base_ids = filter(lambda id: id in id_matches, base_ids) - filtering = True - else: - search = self.add_box_text.lower() - def key(id): - game = globals.games[id] - return search in game.version.lower() or search in game.developer.lower() or search in game.name.lower() or search in game.notes.lower() - base_ids = filter(key, base_ids) - filtering = True - self.filtering = filtering - # Finally consume the iterators (was lazy up until now) - base_ids = list(base_ids) - # Sort globally by sortspecs - if not manual_sort: - for sort_spec in self.sorts[table_id]: - match sort_spec.index: - case cols.type.index: - key = lambda id: globals.games[id].type.name - case cols.developer.index: - key = lambda id: globals.games[id].developer.lower() - case cols.playtime.index: - key = lambda id: - globals.games[id].playtime - case cols.last_updated.index: - key = lambda id: - globals.games[id].last_updated.value - case cols.last_launched.index: - key = lambda id: - globals.games[id].last_launched.value - case cols.added_on.index: - key = lambda id: - globals.games[id].added_on.value - case cols.finished.index: - key = lambda id: 2 if not globals.games[id].finished else 1 if globals.games[id].finished == (globals.games[id].installed or globals.games[id].version) else 0 - case cols.installed.index: - key = lambda id: 2 if not globals.games[id].installed else 1 if globals.games[id].installed == globals.games[id].version else 0 - case cols.rating.index: - key = lambda id: - globals.games[id].rating - case cols.notes.index: - key = lambda id: globals.games[id].notes.lower() or "z" - case cols.status_standalone.index: - key = lambda id: globals.games[id].status.value - case cols.score.index: - if globals.settings.weighted_score: - key = lambda id: - utils.bayesian_average(globals.games[id].score, globals.games[id].votes) - else: - key = lambda id: - globals.games[id].score - case _: # Name and all others - key = lambda id: globals.games[id].name.lower() - base_ids.sort(key=key, reverse=sort_spec.reverse) - base_ids.sort(key=lambda id: globals.games[id].archived) - base_ids.sort(key=lambda id: globals.games[id].type is not Type.Unchecked) - # Loop all tabs and filter by them - self.show_games_ids = { - tab: ( - base_ids if filtering and globals.settings.filter_all_tabs else - list(filter(lambda id: tab is globals.games[id].tab, base_ids)) - ) - for tab in (None, *Tab.instances) - } - tab_games_ids = self.show_games_ids[self.current_tab] - # Deselect things that arent't visible anymore - for game in globals.games.values(): - if game.selected and game.id not in tab_games_ids: - game.selected = False - else: - tab_games_ids = self.show_games_ids[self.current_tab] - - def handle_game_hitbox_events(self, game: Game, drag_drop: bool = False): - manual_sort = cols.manual_sort.enabled - if imgui.is_item_hovered(imgui.HOVERED_ALLOW_WHEN_BLOCKED_BY_ACTIVE_ITEM): - # Hover = image on refresh button - self.hovered_game = game - if imgui.is_item_clicked(): - self.game_hitbox_click = True - if self.game_hitbox_click and not imgui.is_mouse_down(): - self.game_hitbox_click = False - if imgui.is_key_down(glfw.KEY_LEFT_SHIFT): - # Shift + Left click = multi select - if self.selected_games_count and self.last_selected_game.selected: - tab_games_ids = self.show_games_ids[self.current_tab] - start = tab_games_ids.index(self.last_selected_game.id) - end = tab_games_ids.index(game.id) - if start > end: - start, end = end, start - for select in tab_games_ids[start:end + 1]: - globals.games[select].selected = True - else: - game.selected = True - elif imgui.is_key_down(glfw.KEY_LEFT_CONTROL): - # Ctrl + Left click = single select - game.selected = not game.selected - else: - if any(game.selected for game in globals.games.values()): - for game in globals.games.values(): - game.selected = False - else: - # Left click = open game info popup - utils.push_popup(self.draw_game_info_popup, game, self.show_games_ids[self.current_tab].copy()) - # Left click drag = swap if in manual sort mode - if imgui.begin_drag_drop_source(flags=self.game_hitbox_drag_drop_flags): - self.game_hitbox_click = False - payload = (globals.settings.manual_sort_list.index(game.id) if manual_sort else 0) + 1 - payload = payload.to_bytes(payload.bit_length(), sys.byteorder) - imgui.set_drag_drop_payload("manual_swap", payload) - imgui.end_drag_drop_source() - if drag_drop and manual_sort: - if imgui.begin_drag_drop_target(): - if payload := imgui.accept_drag_drop_payload("manual_swap", flags=self.game_hitbox_drag_drop_flags): - payload = int.from_bytes(payload, sys.byteorder) - payload = payload - 1 - lst = globals.settings.manual_sort_list - switch = lst.index(game.id) - lst[switch], lst[payload] = lst[payload], lst[switch] - async_thread.run(db.update_settings("manual_sort_list")) - self.recalculate_ids = True - imgui.end_drag_drop_target() - context_id = f"###{game.id}_context" - if (imgui.is_topmost() or imgui.is_popup_open(context_id)) and imgui.begin_popup_context_item(context_id): - # Right click = context menu - if game.selected: - self.draw_game_context_menu() - else: - self.draw_game_context_menu(game) - imgui.end_popup() - - def sync_scroll(self): - if (scroll_max_y := imgui.get_scroll_max_y()) > 1.0: - if self.switched_display_mode: - imgui.set_scroll_y(self.scroll_percent * scroll_max_y) - self.switched_display_mode = False - else: - self.scroll_percent = imgui.get_scroll_y() / scroll_max_y - - def games_table_id(self): - tab_id = self.current_tab.id if self.current_tab else -1 - return f"###game_list{tab_id if globals.settings.independent_tab_views else ''}" - - def draw_games_list(self): - table_id = self.games_table_id() - # Hack: custom toggles in table header right click menu by adding tiny empty "ghost" columns and hiding them - # by starting the table render before the content region. - ghost_column_size = (imgui.style.frame_padding.x + imgui.style.cell_padding.x * 2) - offset = ghost_column_size * self.ghost_columns_enabled_count - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() - offset) - if imgui.begin_table( - table_id, - column=cols.count, - flags=self.game_list_table_flags, - outer_size_height=-imgui.get_frame_height_with_spacing() # Bottombar - ): - # Setup columns - self.ghost_columns_enabled_count = 0 - can_sort = 0 - for column in cols.items: - imgui.table_setup_column(column.name, column.flags | (can_sort * column.sortable)) - # Enabled columns - column.enabled = bool(imgui.table_get_column_flags(column.index) & imgui.TABLE_COLUMN_IS_ENABLED) - # Ghosts count - if column.ghost and column.enabled: - self.ghost_columns_enabled_count += 1 - # Set sorting condition - if column is cols.manual_sort: - can_sort = imgui.TABLE_COLUMN_NO_SORT * cols.manual_sort.enabled - imgui.table_setup_scroll_freeze(0, 1) # Sticky column headers - self.calculate_ids(table_id, imgui.table_get_sort_specs()) - - # Column headers - imgui.table_next_row(imgui.TABLE_ROW_HEADERS) - for column in cols.items: - imgui.table_set_column_index(column.index) - imgui.table_header(column.header) - - # Loop rows - self.sync_scroll() - frame_height = imgui.get_frame_height() - notes_width = None - for id in self.show_games_ids[self.current_tab]: - game = globals.games[id] - imgui.table_next_row() - imgui.table_set_column_index(cols.separator.index) - # Skip if outside view - if not imgui.is_rect_visible(imgui.io.display_size.x, frame_height): - imgui.dummy(0, frame_height) - continue - # Base row height with a buttom to align the following text calls to center vertically - imgui.button("", width=imgui.FLOAT_MIN) - # Loop columns - for column in cols.items: - if not column.enabled or column.ghost: - continue - imgui.table_set_column_index(column.index) - match column.index: - case cols.play_button.index: - self.draw_game_play_button(game, cols.play_button.name[0]) - case cols.type.index: - self.draw_type_widget(game.type, align=True) - case cols.name.index: - if globals.settings.show_remove_btn: - self.draw_game_remove_button(game, icons.trash_can_outline) - imgui.same_line() - if game.archived: - self.draw_game_archive_icon(game) - imgui.same_line() - if game.updated: - self.draw_game_update_icon(game) - imgui.same_line() - if game.unknown_tags_flag: - self.draw_game_unknown_tags_icon(game) - imgui.same_line() - self.draw_game_name_text(game) - if game.notes: - imgui.same_line() - imgui.text_colored(icons.draw_pen, 0.85, 0.20, 0.85) - self.draw_hover_text(game.notes, text=None) - if game.labels: - imgui.same_line() - self.draw_game_labels_widget(game, wrap=False, small=True, align=True) - if cols.status.enabled and game.status is not Status.Normal: - imgui.same_line() - self.draw_status_widget(game.status) - if cols.version.enabled or cols.finished_version.enabled or cols.installed_version.enabled: - imgui.same_line() - versions = [] - if cols.version.enabled: - if cols.finished_version.enabled and game.finished and game.finished != (game.installed or game.version): - versions.append(f"{cols.finished_version.name[0]} {game.finished}") - if cols.installed_version.enabled and game.installed and game.installed != game.version: - versions.append(f"{cols.installed_version.name[0]} {game.installed}") - versions.append(f"{cols.version.name[0]} {game.version}") - elif game.finished == game.installed != "": - if cols.finished_version.enabled: - versions.append(f"{cols.finished_version.name[0]} {game.finished}") - elif cols.installed_version.enabled: - versions.append(f"{cols.installed_version.name[0]} {game.installed}") - else: - if cols.finished_version.enabled and game.finished: - versions.append(f"{cols.finished_version.name[0]} {game.finished}") - if cols.installed_version.enabled and game.installed: - versions.append(f"{cols.installed_version.name[0]} {game.installed}") - imgui.text_disabled(" | ".join(versions)) - case cols.developer.index: - imgui.text(game.developer or "Unknown") - case cols.playtime.index: - imgui.push_font(imgui.fonts.mono) - imgui.text(game.playtime_display or "None") - imgui.pop_font() - case cols.last_updated.index: - imgui.push_font(imgui.fonts.mono) - imgui.text(game.last_updated.display or "Unknown") - imgui.pop_font() - case cols.last_launched.index: - imgui.push_font(imgui.fonts.mono) - imgui.text(game.last_launched.display or "Never") - imgui.pop_font() - case cols.added_on.index: - imgui.push_font(imgui.fonts.mono) - imgui.text(game.added_on.display) - imgui.pop_font() - case cols.finished.index: - self.draw_game_finished_checkbox(game) - case cols.installed.index: - self.draw_game_installed_checkbox(game) - case cols.rating.index: - self.draw_game_rating_widget(game) - case cols.notes.index: - if notes_width is None: - notes_width = imgui.get_content_region_available_width() - 2 * imgui.style.item_spacing.x - self.draw_game_notes_widget(game, multiline=False, width=notes_width) - case cols.open_thread.index: - self.draw_game_open_thread_button(game, cols.open_thread.name[0]) - case cols.copy_link.index: - self.draw_game_copy_link_button(game, cols.copy_link.name[0]) - case cols.open_folder.index: - self.draw_game_open_folder_button(game, cols.open_folder.name[0]) - case cols.status_standalone.index: - self.draw_status_widget(game.status) - case cols.score.index: - with imgui.begin_group(): - imgui.text(f"{game.score:.1f}") - imgui.same_line() - imgui.text_disabled(f"({game.votes})") - self.draw_hover_text(f"Weighted: {utils.bayesian_average(game.score, game.votes):.2f}", text=None) - # Row hitbox - imgui.same_line() - imgui.set_cursor_pos_y(imgui.get_cursor_pos_y() - imgui.style.frame_padding.y) - if game.selected: - imgui.push_alpha(0.5) - imgui.get_window_draw_list().add_rect_filled( - 0, pos_y := imgui.get_cursor_screen_pos().y + 1, - imgui.io.display_size.x, pos_y + frame_height + 2 * imgui.style.cell_padding.y, - imgui.get_color_u32_rgba(*globals.settings.style_accent) - ) - imgui.pop_alpha() - imgui.push_alpha(0.25) - imgui.selectable(f"###{game.id}_hitbox", False, flags=imgui.SELECTABLE_SPAN_ALL_COLUMNS, height=frame_height) - imgui.pop_alpha() - self.handle_game_hitbox_events(game, drag_drop=True) - - imgui.end_table() - - def tick_list_columns(self): - # Hack: get sort and column specs for list mode in grid and kanban mode - table_id = self.games_table_id() - # Hack: custom toggles in table header right click menu by adding tiny empty "ghost" columns and hiding them - # by starting the table render before the content region. - ghost_column_size = (imgui.style.frame_padding.x + imgui.style.cell_padding.x * 2) - offset = ghost_column_size * self.ghost_columns_enabled_count - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() - offset) - pos_y = imgui.get_cursor_pos_y() - header_h = imgui.get_text_line_height_with_spacing() if globals.settings.table_header_outside_list else 1 - if imgui.begin_table( - table_id, - column=cols.count, - flags=self.game_list_table_flags, - outer_size_height=header_h - ): - # Setup columns - self.ghost_columns_enabled_count = 0 - can_sort = 0 - for column in cols.items: - imgui.table_setup_column(column.name, column.flags | (can_sort * column.sortable) | imgui.TABLE_COLUMN_NO_REORDER) - # Enabled columns - column.enabled = bool(imgui.table_get_column_flags(column.index) & imgui.TABLE_COLUMN_IS_ENABLED) - # Ghosts count - if column.ghost and column.enabled: - self.ghost_columns_enabled_count += 1 - # Set sorting condition - if column is cols.manual_sort: - can_sort = imgui.TABLE_COLUMN_NO_SORT * cols.manual_sort.enabled - self.calculate_ids(table_id, imgui.table_get_sort_specs()) - - if globals.settings.table_header_outside_list: - # Column headers - imgui.table_next_row(imgui.TABLE_ROW_HEADERS) - for column in cols.items: - imgui.table_set_column_index(column.index) - imgui.table_header(column.header) - - imgui.end_table() - if not globals.settings.table_header_outside_list: - imgui.set_cursor_pos_y(pos_y + header_h) - - def get_game_cell_config(self): - side_indent = imgui.style.item_spacing.x * 2 - checkboxes = cols.finished.enabled + cols.installed.enabled - buttons = cols.play_button.enabled + cols.open_folder.enabled + cols.open_thread.enabled + cols.copy_link.enabled - action_items = checkboxes + buttons - cluster_pad = imgui.style.item_spacing.x * 3 - bg_col = imgui.get_color_u32_rgba(*imgui.style.colors[imgui.COLOR_TABLE_ROW_BACKGROUND_ALT]) - frame_height = imgui.get_frame_height() - - actions_width = ( - imgui.style.item_spacing.x * action_items + # Spacing * 6 action items - imgui.style.frame_padding.x * 2 * buttons + # Button padding * 2 sides * 4 buttons - imgui.style.item_inner_spacing.x * checkboxes + # Checkbox to label spacing * 2 checkboxes - frame_height * checkboxes + # (Checkbox height = width) * 2 checkboxes - imgui.calc_text_size( # Text - cols.play_button.name[0] * cols.play_button.enabled + - cols.open_folder.name[0] * cols.open_folder.enabled + - cols.open_thread.name[0] * cols.open_thread.enabled + - cols.copy_link.name[0] * cols.copy_link.enabled + - cols.finished.name[0] * cols.finished.enabled + - cols.installed.name[0] * cols.installed.enabled - ).x - ) - - base_width = max( - ( # Type / Version line - 2 * self.get_type_label_width() - ), - ( # Clustered data - imgui.style.item_spacing.x + # Between text - imgui.calc_text_size(f"{cols.last_updated.name[0]}00/00/0000").x # Text - ) - ) - - min_cell_width = ( - side_indent * 2 + # Side indent * 2 sides - max(actions_width, base_width) - ) - - min_expand_width = ( - side_indent * 2 + # Side indent * 2 sides - max( - ( # Expanded actions text - actions_width + - imgui.calc_text_size( - " Play" * cols.play_button.enabled + - " Folder" * cols.open_folder.enabled + - " Thread" * cols.open_thread.enabled + - " Link" * cols.copy_link.enabled - ).x - ), - base_width - ) - ) - - config = (side_indent, action_items, cluster_pad, bg_col, frame_height) - return min_cell_width, min_expand_width, config - - def draw_game_cell(self, game: Game, drag_drop: bool, draw_list, cell_width: float, expand: bool, img_height: float, config: tuple): - (side_indent, action_items, cluster_pad, bg_col, frame_height) = config - draw_list.channels_split(2) - draw_list.channels_set_current(1) - pos = imgui.get_cursor_pos() - rounding = self.scaled(globals.settings.style_corner_radius) - imgui.begin_group() - # Image - if game.image.error: - showed_img = imgui.is_rect_visible(cell_width, img_height) - self.draw_game_image_error(game, cell_width, img_height) - else: - crop = game.image.crop_to_ratio(globals.settings.cell_image_ratio, fit=globals.settings.fit_images) - showed_img = game.image.render(cell_width, img_height, *crop, rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_TOP) - # Alignments - imgui.indent(side_indent) - wrap_pos = pos.x + cell_width - side_indent - imgui.push_text_wrap_pos(wrap_pos) - imgui.spacing() - imgui.spacing() - # Image overlays - if showed_img: - old_pos = imgui.get_cursor_pos() - # Remove button - if globals.settings.show_remove_btn: - imgui.set_cursor_pos((pos.x + imgui.style.item_spacing.x, pos.y + imgui.style.item_spacing.y)) - self.draw_game_remove_button(game, icons.trash_can_outline) - # Type - if cols.type.enabled: - imgui.set_cursor_pos((pos.x + imgui.style.item_spacing.x, pos.y + img_height + imgui.style.item_spacing.y - frame_height / 2)) - self.draw_type_widget(game.type, wide=False) - # Version - if cols.version.enabled: - cut = len(game.version) - while (w := imgui.calc_text_size(game.version[:cut]).x) > cell_width / 2: - cut -= 1 - imgui.set_cursor_pos((pos.x + cell_width - side_indent - imgui.style.item_spacing.x - w, pos.y + img_height + imgui.style.item_spacing.y - frame_height / 2)) - self.begin_framed_text((0.3, 0.3, 0.3, 1.0), interaction=True) - imgui.small_button(game.version[:cut]) - if imgui.is_item_hovered() and cut != len(game.version): - imgui.set_tooltip(game.version) - self.end_framed_text(interaction=True) - imgui.set_cursor_pos(old_pos) - # Name line - imgui.push_font(imgui.fonts.bold) - self.draw_game_name_text(game) - imgui.pop_font() - # Buttons line - if action_items: - if imgui.is_rect_visible(cell_width, frame_height): - # Play Button - did_newline = False - if cols.play_button.enabled: - if did_newline: - imgui.same_line() - self.draw_game_play_button(game, f"{cols.play_button.name[0]}{' Play' if expand else ''}") - did_newline = True - # Open Folder - if cols.open_folder.enabled: - if did_newline: - imgui.same_line() - self.draw_game_open_folder_button(game, f"{cols.open_folder.name[0]}{' Folder' if expand else ''}") - did_newline = True - # Open Thread - if cols.open_thread.enabled: - if did_newline: - imgui.same_line() - self.draw_game_open_thread_button(game, f"{cols.open_thread.name[0]}{' Thread' if expand else ''}") - did_newline = True - # Copy Link - if cols.copy_link.enabled: - if did_newline: - imgui.same_line() - self.draw_game_copy_link_button(game, f"{cols.copy_link.name[0]}{' Link' if expand else ''}") - did_newline = True - # Finished - if cols.finished.enabled: - if did_newline: - imgui.same_line() - self.draw_game_finished_checkbox(game, cols.finished.name[0]) - did_newline = True - # Installed - if cols.installed.enabled: - if did_newline: - imgui.same_line() - self.draw_game_installed_checkbox(game, cols.installed.name[0]) - did_newline = True - else: - # Skip if outside view - imgui.dummy(0, frame_height) - # Cluster data - cluster = False - if game.archived: - self.draw_game_archive_icon(game) - imgui.same_line() - cluster = True - if game.updated: - self.draw_game_update_icon(game) - imgui.same_line() - cluster = True - if game.unknown_tags_flag: - self.draw_game_unknown_tags_icon(game) - imgui.same_line() - cluster = True - if game.notes: - imgui.text_colored(icons.draw_pen, 0.85, 0.20, 0.85) - self.draw_hover_text(game.notes, text=None) - imgui.same_line() - cluster = True - if (cols.status.enabled or cols.status_standalone.enabled) and game.status is not Status.Normal: - self.draw_status_widget(game.status) - imgui.same_line() - cluster = True - if game.labels: - self.draw_game_labels_widget(game, short=True, align=False) - imgui.same_line() - cluster = True - def _cluster_text(name, text): - nonlocal cluster - if imgui.get_cursor_pos_x() + imgui.style.item_spacing.x + imgui.calc_text_size(name[0] + text).x > wrap_pos: - imgui.dummy(0, 0) - imgui.text_disabled(name[0]) - if imgui.is_item_hovered(): - imgui.set_tooltip(name[2:]) - imgui.same_line() - imgui.text(text) - imgui.same_line(spacing=cluster_pad) - cluster = True - if cols.score.enabled: - _cluster_text(cols.score.name, f"{game.score:.1f} ({game.votes})") - self.draw_hover_text(f"Weighted: {utils.bayesian_average(game.score, game.votes):.2f}", text=None) - if cols.playtime.enabled and game.playtime_display: - _cluster_text(cols.playtime.name, game.playtime_display) - if cols.last_updated.enabled: - _cluster_text(cols.last_updated.name, game.last_updated.display or "Unknown") - if cols.last_launched.enabled: - _cluster_text(cols.last_launched.name, game.last_launched.display or "Never") - if cols.added_on.enabled: - _cluster_text(cols.added_on.name, game.added_on.display) - if cols.rating.enabled: - if imgui.get_cursor_pos_x() + imgui.calc_text_size(icons.star * 5).x > wrap_pos: - imgui.dummy(0, 0) - self.draw_game_rating_widget(game) - imgui.same_line(spacing=cluster_pad) - cluster = True - if cols.developer.enabled: - _cluster_text(cols.developer.name, game.developer) - if cols.finished_version.enabled or cols.installed_version.enabled: - if cols.version.enabled: - if cols.finished_version.enabled and game.finished and game.finished != (game.installed or game.version): - _cluster_text(cols.finished_version.name, game.finished) - if cols.installed_version.enabled and game.installed and game.installed != game.version: - _cluster_text(cols.installed_version.name, game.installed) - elif game.finished == game.installed != "": - if cols.finished_version.enabled: - _cluster_text(cols.finished_version.name, game.finished) - elif cols.installed_version.enabled: - _cluster_text(cols.installed_version.name, game.installed) - else: - if cols.finished_version.enabled and game.finished: - _cluster_text(cols.finished_version.name, game.finished) - if cols.installed_version.enabled and game.installed: - _cluster_text(cols.installed_version.name, game.installed) - if cluster: - imgui.dummy(0, 0) - # Notes line - if cols.notes.enabled: - if imgui.is_rect_visible(cell_width, frame_height): - self.draw_game_notes_widget(game, multiline=False, width=cell_width - 2 * side_indent) - else: - # Skip if outside view - imgui.dummy(0, frame_height) - # Cell hitbox - imgui.pop_text_wrap_pos() - imgui.spacing() - imgui.spacing() - imgui.end_group() - draw_list.channels_set_current(0) - imgui.set_cursor_pos(pos) - cell_height = imgui.get_item_rect_size().y - if imgui.is_rect_visible(cell_width, cell_height): - # Skip if outside view - imgui.invisible_button(f"###{game.id}_hitbox", cell_width, cell_height) - self.handle_game_hitbox_events(game, drag_drop=drag_drop) - rect_min = imgui.get_item_rect_min() - rect_max = imgui.get_item_rect_max() - if game.selected: - imgui.push_alpha(0.5) - draw_list.add_rect_filled( - *rect_min, *rect_max, imgui.get_color_u32_rgba(*globals.settings.style_accent), - rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_ALL - ) - imgui.pop_alpha() - else: - draw_list.add_rect_filled(*rect_min, *rect_max, bg_col, rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_ALL) - else: - imgui.dummy(cell_width, cell_height) - draw_list.channels_merge() - - def draw_games_grid(self): - # Configure table - self.tick_list_columns() - min_cell_width, min_expand_width, cell_config = self.get_game_cell_config() - padding = self.scaled(8) - avail = imgui.get_content_region_available_width() - column_count = globals.settings.grid_columns - while (cell_width := (avail - padding * 2 * column_count) / column_count) < min_cell_width and column_count > 1: - column_count -= 1 - expand = cell_width > min_expand_width - img_height = cell_width / globals.settings.cell_image_ratio - imgui.push_style_var(imgui.STYLE_CELL_PADDING, (padding, padding)) - if imgui.begin_table( - "###game_grid", - column=column_count, - flags=self.game_grid_table_flags, - outer_size_height=-imgui.get_frame_height_with_spacing() # Bottombar - ): - # Setup - for _ in range(column_count): - imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) - - # Loop cells - self.sync_scroll() - draw_list = imgui.get_window_draw_list() - for id in self.show_games_ids[self.current_tab]: - game = globals.games[id] - imgui.table_next_column() - self.draw_game_cell(game, True, draw_list, cell_width, expand, img_height, cell_config) - - imgui.end_table() - imgui.pop_style_var() - - def draw_games_kanban(self): - # Configure table - self.tick_list_columns() - cell_width, _, cell_config = self.get_game_cell_config() - padding = self.scaled(4) - imgui.push_style_var(imgui.STYLE_CELL_PADDING, (padding, padding)) - img_height = cell_width / globals.settings.cell_image_ratio - cells_per_column = 1 - column_count = len(Label.instances) + 1 - avail = imgui.get_content_region_available_width() - table_width = lambda: (padding * 2 + (cell_width + imgui.style.item_spacing.x) * cells_per_column + imgui.style.scrollbar_size) * column_count - while table_width() < avail: - cells_per_column += 1 - cells_per_column = max(cells_per_column - 1, 1) - if imgui.begin_table( - "###game_kanban", - column=column_count, - flags=self.game_kanban_table_flags, - inner_width=table_width(), - outer_size_width=-imgui.style.scrollbar_size, - outer_size_height=-imgui.get_frame_height_with_spacing() # Bottombar - ): - # Setup columns - not_labelled = len(Label.instances) - for label in Label.instances: - imgui.table_setup_column(label.name, imgui.TABLE_COLUMN_WIDTH_STRETCH) - imgui.table_setup_column("Not Labelled", imgui.TABLE_COLUMN_WIDTH_STRETCH) - tab_games_ids = self.show_games_ids[self.current_tab] - - # Column headers - imgui.table_setup_scroll_freeze(0, 1) # Sticky column headers - imgui.table_next_row(imgui.TABLE_ROW_HEADERS) - for label_i, label in enumerate(Label.instances): - imgui.table_set_column_index(label_i) - count = 0 - for id in tab_games_ids: - if label in globals.games[id].labels: - count += 1 - imgui.table_header(f"{label.name} ({count})") - imgui.table_set_column_index(not_labelled) - count = 0 - for id in tab_games_ids: - if not globals.games[id].labels: - count += 1 - imgui.table_header(f"Not Labelled ({count})") - - # Loop cells - for label_i, label in (*enumerate(Label.instances), (not_labelled, None)): - imgui.table_next_column() - imgui.begin_child(f"###game_kanban_{label_i}", height=-padding) - draw_list = imgui.get_window_draw_list() - wrap = cells_per_column - imgui.begin_group() - for id in tab_games_ids: - game = globals.games[id] - if label_i == not_labelled: - if game.labels: - continue - elif label not in game.labels: - continue - self.draw_game_cell(game, False, draw_list, cell_width, False, img_height, cell_config) - wrap -= 1 - if wrap: - imgui.same_line() - else: - imgui.end_group() - wrap = cells_per_column - imgui.begin_group() - imgui.end_group() - imgui.end_child() - - imgui.end_table() - imgui.pop_style_var() - - def draw_bottombar(self): - new_display_mode = None - - for display_mode in DisplayMode: - if globals.settings.display_mode is display_mode: - imgui.push_style_color(imgui.COLOR_BUTTON, *imgui.style.colors[imgui.COLOR_BUTTON_HOVERED]) - if imgui.button(getattr(icons, display_mode.icon)): - new_display_mode = display_mode - self.switched_display_mode = True - if globals.settings.display_mode is display_mode: - imgui.pop_style_color() - imgui.same_line() - - if new_display_mode is not None: - globals.settings.display_mode = new_display_mode - async_thread.run(db.update_settings("display_mode")) - - if self.add_box_valid: - imgui.set_next_item_width(-(imgui.calc_text_size("Add!").x + 2 * imgui.style.frame_padding.x) - imgui.style.item_spacing.x) - else: - imgui.set_next_item_width(-imgui.FLOAT_MIN) - any_active_old = imgui.is_any_item_active() - any_active = False - def setter_extra(_=None): - self.add_box_valid = len(utils.extract_thread_matches(self.add_box_text)) > 0 - self.recalculate_ids = True - if not globals.popup_stack and not any_active_old and (self.input_chars or any(imgui.io.keys_down)): - if imgui.is_key_pressed(glfw.KEY_BACKSPACE): - self.add_box_text = self.add_box_text[:-1] - setter_extra() - if imgui.is_key_down(glfw.KEY_LEFT_CONTROL) and imgui.is_key_pressed(glfw.KEY_A): - tab_games_ids = self.show_games_ids[self.current_tab] - selected = not any(globals.games[id].selected for id in tab_games_ids) - for id in tab_games_ids: - globals.games[id].selected = selected - if self.input_chars: - self.repeat_chars = True - imgui.set_keyboard_focus_here() - any_active = True - activated, value = imgui.input_text_with_hint( - "###bottombar", - "Type to filter the list, press enter to add a game (link/search)", - self.add_box_text, - flags=imgui.INPUT_TEXT_ENTER_RETURNS_TRUE - ) - changed = value != self.add_box_text - activated = bool(activated and value) - any_active = any_active or imgui.is_any_item_active() - if any_active_old != any_active and imgui.is_key_pressed(glfw.KEY_ESCAPE): - # Changed active state, and escape is pressed, so clear textbox - value = "" - changed = True - for game in globals.games.values(): - game.selected = False - if changed: - self.add_box_text = value - setter_extra() - if imgui.begin_popup_context_item("###bottombar_context"): - # Right click = more options context menu - utils.text_context(self, "add_box_text", setter_extra, no_icons=True) - imgui.separator() - if imgui.selectable(f"{icons.information_outline} More info", False)[0]: - utils.push_popup( - msgbox.msgbox, "About the bottom bar", - "This is the filter/add bar. By typing inside it you can search your game list.\n" - "Pressing enter will search F95zone for a matching thread and ask if you wish to\n" - "add it to your list.\n" - "\n" - "When you instead paste a link to a F95zone thread, the 'Add!' button will show\n" - "up, allowing you to add that thread to your list. When a link is detected you\n" - "can also press enter on your keyboard to trigger the 'Add!' button.", - MsgBox.info - ) - imgui.end_popup() - if self.add_box_valid: - imgui.same_line() - if imgui.button("Add!") or activated: - async_thread.run(callbacks.add_games(*utils.extract_thread_matches(self.add_box_text))) - self.add_box_text = "" - self.add_box_valid = False - self.recalculate_ids = True - elif activated: - api.open_search_popup(self.add_box_text) - self.add_box_text = "" - self.add_box_valid = False - self.recalculate_ids = True - - def draw_sidebar(self): - set = globals.settings - right_width = self.scaled(90) - frame_height = imgui.get_frame_height() - checkbox_offset = right_width - frame_height - - def draw_settings_section(name: str, collapsible=True): - if collapsible: - header = imgui.collapsing_header(name)[0] - else: - header = True - opened = header and imgui.begin_table(f"###settings_{name}", column=2, flags=imgui.TABLE_NO_CLIP) - if opened: - imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_STRETCH) - imgui.table_setup_column("", imgui.TABLE_COLUMN_WIDTH_FIXED) - imgui.table_next_row() - imgui.table_set_column_index(1) # Right - imgui.dummy(right_width, 1) - imgui.push_item_width(right_width) - return opened - - def draw_settings_label(label: str, tooltip: str = None): - imgui.table_next_row() - imgui.table_next_column() - imgui.text(label) - if tooltip: - imgui.same_line() - self.draw_hover_text(tooltip) - imgui.table_next_column() - - def draw_settings_checkbox(setting: str): - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) - changed, value = imgui.checkbox(f"###{setting}", getattr(set, setting)) - if changed: - setattr(set, setting, value) - async_thread.run(db.update_settings(setting)) - return changed - - def draw_settings_color(setting: str): - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) - changed, value = imgui.color_edit3(f"###{setting}", *getattr(set, setting)[:3], flags=imgui.COLOR_EDIT_NO_INPUTS) - if changed: - setattr(set, setting, (*value, 1.0)) - self.refresh_styles() - async_thread.run(db.update_settings(setting)) - return changed - - width = imgui.get_content_region_available_width() - height = self.scaled(100) - if utils.is_refreshing(): - # Refresh progress bar - ratio = globals.refresh_progress / globals.refresh_total - self.refresh_ratio_smooth += (ratio - self.refresh_ratio_smooth) * imgui.io.delta_time * 8 - imgui.progress_bar(self.refresh_ratio_smooth, (width, height)) - draw_list = imgui.get_window_draw_list() - screen_pos = imgui.get_cursor_screen_pos() - col = imgui.get_color_u32_rgba(1, 1, 1, 1) - if imgui.is_item_clicked(): - # Click = cancel - globals.refresh_task.cancel() - if imgui.is_item_hovered(): - text = "Click to cancel!" - text_size = imgui.calc_text_size(text) - text_x = screen_pos.x + (width - text_size.x) / 2 - text_y = screen_pos.y - text_size.y - 3 * imgui.style.item_spacing.y - draw_list.add_text(text_x, text_y, col, text) - text = f"{ratio:.0%}" - text_size = imgui.calc_text_size(text) - text_x = screen_pos.x + (width - text_size.x) / 2 - text_y = screen_pos.y - (height + text_size.y) / 2 - imgui.style.item_spacing.y - draw_list.add_text(text_x, text_y, col, text) - elif self.hovered_game: - # Hover = show image - game = self.hovered_game - if game.image.error: - if game.image.error == "Image file missing": - text = "Image missing!" - else: - text = "Invalid image!" - imgui.button(text, width=width, height=height) - else: - crop = game.image.crop_to_ratio(width / height, fit=globals.settings.fit_images) - game.image.render(width, height, *crop, rounding=self.scaled(globals.settings.style_corner_radius)) - else: - # Normal button - if imgui.button("Refresh!", width=width, height=height): - utils.start_refresh_task(api.refresh()) - elif imgui.is_item_hovered(): - draw_list = imgui.get_window_draw_list() - screen_pos = imgui.get_cursor_screen_pos() - col = imgui.get_color_u32_rgba(1, 1, 1, 1) - text = str(f"Last refresh: {set.last_successful_refresh.display or 'Never'}") - text_size = imgui.calc_text_size(text) - text_x = screen_pos.x + (width - text_size.x) / 2 - text_y = screen_pos.y - text_size.y - 3 * imgui.style.item_spacing.y - draw_list.add_text(text_x, text_y, col, text) - if imgui.begin_popup_context_item("###refresh_context"): - # Right click = more options context menu - if imgui.selectable(f"{icons.bell_badge_outline} Check notifs", False)[0]: - utils.start_refresh_task(api.check_notifs(standalone=True)) - if imgui.selectable(f"{icons.reload_alert} Full Refresh", False)[0]: - utils.start_refresh_task(api.refresh(full=True)) - if not globals.settings.refresh_completed_games or not globals.settings.refresh_archived_games: - imgui.separator() - if not globals.settings.refresh_archived_games: - if imgui.selectable(f"{icons.reload_alert} Full Refresh (incl. archived)", False)[0]: - utils.start_refresh_task(api.refresh(full=True, force_archived=True)) - if not globals.settings.refresh_completed_games: - if imgui.selectable(f"{icons.reload_alert} Full Refresh (incl. completed)", False)[0]: - utils.start_refresh_task(api.refresh(full=True, force_completed=True)) - if not globals.settings.refresh_completed_games and not globals.settings.refresh_archived_games: - if imgui.selectable(f"{icons.reload_alert} Full Refresh (incl. everything)", False)[0]: - utils.start_refresh_task(api.refresh(full=True, force_archived=True, force_completed=True)) - imgui.separator() - if imgui.selectable(f"{icons.monitor_arrow_down_variant} Check for app updates", False)[0]: - utils.start_update_check() - imgui.separator() - if imgui.selectable(f"{icons.information_outline} More info", False)[0]: - utils.push_popup( - msgbox.msgbox, "About refreshing", - "Refreshing is the process by which F95Checker goes through your games and checks\n" - "if they have received updates, aswell as syncing other game info.\n" - "To keep it fast and smooth while avoiding excessive stress on F95zone servers, this\n" - "is done using a dedicated F95Checker Cache API.\n" - "\n" - "This Cache API gets data from F95zone, parses the relevant details, then saves them for\n" - "up to 7 days. To make sure you don't fall behind, it also monitors the F95zone Latest\n" - "Updates to invalidate cache when games are updated / some details change. However,\n" - "not all details are tracked by Latest Updates, so games are still periodically checked.\n" - "There's a bit more complexity to it, but that's the gist of it. Check the OP / README for\n" - "a more detailed explanation and all the caveats and behaviors.\n" - "\n" - "All this data can become quite large if you have lots of games (~2MiB for 100 games) so\n" - "fetching everything at each refresh would be quite expensive. Instead F95Checker will\n" - "ask the Cache API when each game last changed any of its details (which will also update\n" - "the cache if needed) 10 games at a time, then fetch the full game details only for those\n" - "that have changed since the last refresh.\n" - "You can force full rechecks to fetch all cached game data again, either for single games\n" - "or for the whole list, with the right click menu on the game and on the refresh button,\n" - "but this usually should not be necessary.", - MsgBox.info - ) - imgui.end_popup() - - imgui.begin_child("Settings") - - if draw_settings_section("Filter", collapsible=False): - draw_settings_label(f"Total games count: {len(globals.games)}") - imgui.text("") - imgui.spacing() - - if self.selected_games_count: - draw_settings_label(f"Selected games count: {self.selected_games_count}") - imgui.text("") - imgui.spacing() - - if self.filtering: - if Tab.instances: - if globals.settings.filter_all_tabs: - draw_settings_label(f"Total filtered count: {len(self.show_games_ids.get(None, ()))}") - else: - draw_settings_label(f"Total filtered count: {sum(len(ids) for ids in self.show_games_ids.values())}") - else: - draw_settings_label(f"Filtered games count: {len(self.show_games_ids.get(None, ()))}") - imgui.text("") - imgui.spacing() - - draw_settings_label("Add filter:") - changed, value = imgui.combo("###add_filter", 0, FilterMode._member_names_) - if changed and value > 0: - flt = Filter(FilterMode[FilterMode._member_names_[value]]) - match flt.mode.value: - case FilterMode.Exe_State.value: - flt.match = ExeState[ExeState._member_names_[0]] - case FilterMode.Finished.value: - flt.match = True - case FilterMode.Installed.value: - flt.match = True - case FilterMode.Label.value: - if Label.instances: - flt.match = Label.instances[0] - case FilterMode.Rating.value: - flt.match = 0 - case FilterMode.Score.value: - flt.match = 0.0 - case FilterMode.Status.value: - flt.match = Status[Status._member_names_[0]] - case FilterMode.Tag.value: - flt.match = Tag[Tag._member_names_[0]] - case FilterMode.Type.value: - flt.match = Type[Type._member_names_[0]] - self.filters.append(flt) - - text_width = imgui.calc_text_size("Invrt ").x - buttons_offset = right_width - (2 * frame_height + text_width + imgui.style.item_spacing.x) - for flt in self.filters: - imgui.text("") - draw_settings_label(f"Filter by {flt.mode.name}:") - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + buttons_offset) - if imgui.button((icons.invert_colors if flt.invert else icons.invert_colors_off) + "Invrt", width=frame_height + text_width): - flt.invert = not flt.invert - self.recalculate_ids = True - imgui.same_line() - if imgui.button(icons.trash_can_outline, width=frame_height): - self.filters.remove(flt) - - match flt.mode.value: - case FilterMode.Exe_State.value: - draw_settings_label("Executable state:") - changed, value = imgui.combo(f"###filter_{flt.id}_value", flt.match._index_, ExeState._member_names_) - if changed: - flt.match = ExeState[ExeState._member_names_[value]] - self.recalculate_ids = True - case FilterMode.Finished.value: - draw_settings_label("Include outdated:") - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) - changed, value = imgui.checkbox(f"###filter_{flt.id}_value", flt.match) - if changed: - flt.match = value - self.recalculate_ids = True - case FilterMode.Installed.value: - draw_settings_label("Include outdated:") - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) - changed, value = imgui.checkbox(f"###filter_{flt.id}_value", flt.match) - if changed: - flt.match = value - self.recalculate_ids = True - case FilterMode.Label.value: - if Label.instances: - if flt.match is None: - flt.match = Label.instances[0] - draw_settings_label("Label value:") - if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.name): - for label in Label.instances: - selected = label is flt.match - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###filter_{flt.id}_value_{label.id}", selected)[0]: - flt.match = label - self.recalculate_ids = True - if selected: - imgui.set_item_default_focus() - imgui.set_cursor_pos(pos) - self.draw_label_widget(label) - imgui.end_combo() - else: - draw_settings_label("Make some labels first!") - imgui.text("") - imgui.spacing() - case FilterMode.Rating.value: - draw_settings_label("Rating value:") - changed, value = ratingwidget.ratingwidget(f"filter_{flt.id}_value", flt.match) - if changed: - flt.match = value - self.recalculate_ids = True - imgui.spacing() - case FilterMode.Score.value: - draw_settings_label("Score value:") - changed, value = imgui.drag_float(f"###filter_{flt.id}_value", flt.match, change_speed=0.01, min_value=0, max_value=5, format="%.1f/5") - if changed: - flt.match = value - self.recalculate_ids = True - case FilterMode.Status.value: - draw_settings_label("Status value:") - if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.name): - for status in Status: - selected = status is flt.match - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###filter_{flt.id}_value_{status.value}", selected)[0]: - flt.match = status - self.recalculate_ids = True - if selected: - imgui.set_item_default_focus() - imgui.set_cursor_pos(pos) - self.draw_status_widget(status) - imgui.same_line() - imgui.text(status.name) - imgui.end_combo() - case FilterMode.Tag.value: - draw_settings_label("Tag value:") - if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.text): - for tag in Tag: - selected = tag is flt.match - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###filter_{flt.id}_value_{tag.value}", selected)[0]: - flt.match = tag - self.recalculate_ids = True - if selected: - imgui.set_item_default_focus() - imgui.set_cursor_pos(pos) - self.draw_tag_widget(tag, quick_filter=False, change_highlight=False) - imgui.end_combo() - case FilterMode.Type.value: - draw_settings_label("Type value:") - if imgui.begin_combo(f"###filter_{flt.id}_value", flt.match.name): - category = None - for type in Type: - if category is not type.category: - category = type.category - imgui.text(category.name) - selected = type is flt.match - pos = imgui.get_cursor_pos() - if imgui.selectable(f"###filter_{flt.id}_value_{type.value}", selected)[0]: - flt.match = type - self.recalculate_ids = True - if selected: - imgui.set_item_default_focus() - imgui.set_cursor_pos(pos) - self.draw_type_widget(type) - imgui.end_combo() - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Browser"): - draw_settings_label( - "Browser:", - "All the options you select here ONLY affect how F95Checker opens links for you, it DOES NOT affect how this tool " - "operates internally. F95Checker DOES NOT interact with your browsers in any meaningful way, it uses a separate " - "session just for itself." - ) - changed, value = imgui.combo("###browser", set.browser.index, Browser.avail_list) - if changed: - set.browser = Browser.get(Browser.avail_list[value]) - async_thread.run(db.update_settings("browser")) - - if set.browser.custom: - draw_settings_label("Custom browser:") - if imgui.button("Configure", width=right_width): - def popup_content(): - imgui.text("Executable: ") - imgui.same_line() - pos = imgui.get_cursor_pos_x() - changed, value = imgui.input_text("###browser_custom_executable", set.browser_custom_executable) - setter_extra = lambda _=None: async_thread.run(db.update_settings("browser_custom_executable")) - if changed: - set.browser_custom_executable = value - setter_extra() - if imgui.begin_popup_context_item("###browser_custom_executable_context"): - utils.text_context(set, "browser_custom_executable", setter_extra, no_icons=True) - imgui.end_popup() - imgui.same_line() - clicked = imgui.button(icons.folder_open_outline) - imgui.same_line(spacing=0) - args_width = imgui.get_cursor_pos_x() - pos - imgui.dummy(0, 0) - if clicked: - def callback(selected: str): - if selected: - set.browser_custom_executable = selected - async_thread.run(db.update_settings("browser_custom_executable")) - utils.push_popup(filepicker.FilePicker( - title="Select or drop browser executable", - start_dir=set.browser_custom_executable, - callback=callback - ).tick) - imgui.text("Arguments: ") - imgui.same_line() - imgui.set_cursor_pos_x(pos) - imgui.set_next_item_width(args_width) - changed, value = imgui.input_text("###browser_custom_arguments", set.browser_custom_arguments) - setter_extra = lambda _=None: async_thread.run(db.update_settings("browser_custom_arguments")) - if changed: - set.browser_custom_arguments = value - setter_extra() - if imgui.begin_popup_context_item("###browser_custom_arguments_context"): - utils.text_context(set, "browser_custom_arguments", setter_extra, no_icons=True) - imgui.end_popup() - utils.push_popup( - utils.popup, "Configure custom browser", - popup_content, - buttons=True, - closable=True, - outside=False - ) - else: - draw_settings_label("Use private mode:") - draw_settings_checkbox("browser_private") - - draw_settings_label( - "Download pages:", - "With this enabled links will first be downloaded by F95Checker and then opened as simple HTML files in your " - "browser. This might be useful if you use private mode because the page will load as if you were logged in, " - "allowing you to see links and spoiler content without actually logging in." - ) - draw_settings_checkbox("browser_html") - - draw_settings_label( - "Software Webview:", - "Forces software mode for integrated browser webview, disables GPU acceleration. Enable if you have issues with " - "the integrated browser not rendering correctly." - ) - draw_settings_checkbox("software_webview") - - draw_settings_label("Copy game links as BBcode:") - draw_settings_checkbox("copy_urls_as_bbcode") - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Extension"): - draw_settings_label( - "RPC enabled:", - f"The RPC allows other programs on your pc to interact with F95Checker via the API on {globals.rpc_url}. " - "Essentially this is what makes the web browser extension work. Disable this if you are having issues with the RPC, " - "but do note that doing so will prevent the extension from working at all." - ) - if draw_settings_checkbox("rpc_enabled"): - if set.rpc_enabled: - rpc_thread.start() - else: - rpc_thread.stop() - - draw_settings_label("Install extension:") - cant_install_extension = set.browser.integrated or not set.rpc_enabled - def cant_install_extension_tooltip(): - self.draw_hover_text( - "You have selected the Integrated browser, this already includes the extension!" - if set.browser.integrated else - "RPC must be enabled for the browser extension to work!" - if not set.rpc_enabled else - "Huh?", - text=None, - ) - if cant_install_extension: - imgui.push_disabled() - if imgui.button(icons.google_chrome, width=(right_width - imgui.style.item_spacing.x) / 2): - buttons={ - f"{icons.check} Ok": lambda: async_thread.run(callbacks.default_open(globals.self_path / "browser")), - f"{icons.cancel} Cancel": None - } - utils.push_popup( - msgbox.msgbox, "Chrome extension", - "Unfortunately, the F95Checker extension is banned from the Chrome Webstore.\n" - "Therefore, you must install it manually via developer mode:\n" - " - Open your Chromium-based browser\n" - " - Navigate to 'chrome://extensions/'\n" - " - Enable the 'Developer mode' toggle\n" - " - Refresh the page\n" - " - Click Ok below and drag 'chrome.zip' into your browser window\n", - MsgBox.info, - buttons - ) - if cant_install_extension: - imgui.pop_disabled() - cant_install_extension_tooltip() - imgui.push_disabled() - imgui.same_line() - if imgui.button(icons.firefox, width=(right_width - imgui.style.item_spacing.x) / 2): - if globals.release: - callbacks.open_webpage("https://addons.mozilla.org/firefox/addon/f95checker-browser-addon/") - else: - callbacks.open_webpage("https://addons.mozilla.org/firefox/addon/f95checker-beta-browser-addon/") - if cant_install_extension: - imgui.pop_disabled() - cant_install_extension_tooltip() - - draw_settings_label( - "Icon glow:", - "Icons in some locations will cast colored shadow to improve visibility." - ) - draw_settings_checkbox("ext_icon_glow") - - draw_settings_label( - "Highlight tags:", - "To change tag preferences go to 'Interface' > 'Tags to highlight'." - ) - draw_settings_checkbox("ext_highlight_tags") - - draw_settings_label( - "Add in background:", - "Don't open F95Checker window after adding a game." - ) - draw_settings_checkbox("ext_background_add") - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Images"): - draw_settings_label( - "Cell ratio:", - "The aspect ratio to use for images in grid and kanban view. This is width:height, AKA how many times wider the image " - "is compared to its height. Default is 3:1." - ) - changed, value = imgui.drag_float("###cell_image_ratio", set.cell_image_ratio, change_speed=0.02, min_value=0.5, max_value=5, format="%.1f:1") - set.cell_image_ratio = min(max(value, 0.5), 5) - if changed: - async_thread.run(db.update_settings("cell_image_ratio")) - - draw_settings_label( - "Fit images:", - "Fit images instead of cropping. When cropping the images fill all the space they have available, cutting " - "off the sides a bit. When fitting the images you see the whole image but it has some empty space at the sides." - ) - draw_settings_checkbox("fit_images") - - draw_settings_label( - "Zoom on hover:", - "Allow zooming header images inside info popups.\n" - "Tip: hold shift and scroll while hovering the image to change the zoom amount, or hold shift and alt while " - "scrolling to change the zoom area." - ) - draw_settings_checkbox("zoom_enabled") - - if not set.zoom_enabled: - imgui.push_disabled() - - draw_settings_label( - "Zoom area:", - "The size of the zoom popup compared to the main window size (uses the shorter of the two window dimensions). " - "Default 50%." - ) - changed, value = imgui.drag_int("###zoom_area", set.zoom_area, change_speed=0.2, min_value=1, max_value=500, format="%d%%") - set.zoom_area = min(max(value, 1), 500) - if changed: - async_thread.run(db.update_settings("zoom_area")) - - draw_settings_label( - "Zoom times:", - "How many times to magnify the zoomed area of the image. Default 4x." - ) - changed, value = imgui.drag_float("###zoom_times", set.zoom_times, change_speed=0.02, min_value=1, max_value=20, format="%.1fx") - set.zoom_times = min(max(value, 1), 20) - if changed: - async_thread.run(db.update_settings("zoom_times")) - - if not set.zoom_enabled: - imgui.pop_disabled() - - draw_settings_label("Play GIFs:") - if draw_settings_checkbox("play_gifs"): - for image in imagehelper.ImageHelper.instances: - image.reload() - - if not set.play_gifs: - imgui.push_disabled() - draw_settings_label("Play GIFs unfocused:") - draw_settings_checkbox("play_gifs_unfocused") - if not set.play_gifs: - imgui.pop_disabled() - - draw_settings_label( - "Tex compress:", - "Compress textures using ASTC (6x6/80) or BC7. If supported by GPU, results in dramatically faster image loading " - "with no perceptible loss in visual quality. Depending on GPU model and drivers it might also decrease VRAM " - "usage. Disk usage should be roughly the same (some images compress better than others, it averages out).\n\n" - "ASTC:\n" - "+ when supported takes 9x less VRAM\n" - "+ takes 20% less disk space than original images\n" - "+ compresses slightly faster than BC7\n" - "- very limited GPU support, may not work at all\n" - "- when unsupported may use same VRAM as uncompressed (decompressed on-the-fly)\n" - "- may heavily stutter when loading (due to decompressing on-the-fly)\n" - "BC7:\n" - "+ when supported takes 4x less VRAM\n" - "+ supported by most GPUs\n" - "+ more likely to decrease VRAM usage than ASTC\n" - "- takes 60% more disk space than original images\n" - "- compresses slightly slower than ASTC\n" - "- not supported on MacOS\n" - "Visual quality tends to be equivalent.\n\n" - "Images are compressed when first shown, and it takes some time, especially so for GIFs. After compressing, the " - "result is saved to file, and next loads will be instantaneous.\n" - "If you're looking to compare VRAM usage, make sure to restart the tool (fully quit and reopen) between " - "measurements. This is because the GPU does not release VRAM until something else needs it, it's just marked " - "as unused, which would give the same VRAM usage number between compressed and not.\n" - "If only a compressed image is found it will be used even if this option is disabled (for example, if you enabled " - "Compress replace, the replaced images will continue to use the compressed file even if this setting is off)." - ) - changed, value = imgui.combo("###tex_compress", set.tex_compress._index_, TexCompress._member_names_) - if changed: - set.tex_compress = TexCompress[TexCompress._member_names_[value]] - async_thread.run(db.update_settings("tex_compress")) - for image in imagehelper.ImageHelper.instances: - image.reload() - - if set.tex_compress is TexCompress.Disabled: - imgui.push_disabled() - draw_settings_label( - "Compress replace:", - "Remove original images after texture compression. Enabling this is retro-active: it will delete source images for " - "ones already compressed. Not enabling this option means roughly double disk space usage due to duplicate " - "images files." - ) - if draw_settings_checkbox("tex_compress_replace"): - for image in imagehelper.ImageHelper.instances: - image.reload() - if set.tex_compress is TexCompress.Disabled: - imgui.pop_disabled() - - draw_settings_label( - "Unload off-screen:", - "Unloads images from VRAM when they are not visible. Will be loaded again when next visible. Works best " - "together with Tex compress, so image load times are less noticeable." - ) - draw_settings_checkbox("unload_offscreen_images") - - draw_settings_label( - "Preload nearby images:", - "Starts loading images when they aren't yet visible but are less than a window width/height scroll away. Works " - "best together with Tex compress, so image load times are very short and completely unnoticeable due to preloading." - ) - draw_settings_checkbox("preload_nearby_images") - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Interface"): - draw_settings_label("Scaling:") - changed, value = imgui.drag_float("###interface_scaling", set.interface_scaling, change_speed=imgui.FLOAT_MIN, min_value=0.5, max_value=4, format="%.2fx") - if imgui.is_item_deactivated(): # Only change when editing by text input and accepting the edit - set.interface_scaling = min(max(value, 0.5), 4) - async_thread.run(db.update_settings("interface_scaling")) - - draw_settings_label( - "BG on close:", - "When closing the window F95Checker will instead switch to background mode. Quit the app via the tray icon." - ) - draw_settings_checkbox("background_on_close") - - draw_settings_label( - "Table header ouside list:", - "Shows the table header in all view modes, allowing to change sorting and shown elements ouside of list mode." - ) - draw_settings_checkbox("table_header_outside_list") - - draw_settings_label( - "Grid columns:", - "How many games will show in each row in grid view. It is a maximum value because when there is insufficient " - "space to show all these columns, the number will be internally reduced to render each grid cell properly." - ) - changed, value = imgui.drag_int("###grid_columns", set.grid_columns, change_speed=0.05, min_value=1, max_value=10) - set.grid_columns = min(max(value, 1), 10) - if changed: - async_thread.run(db.update_settings("grid_columns")) - - draw_settings_label("Smooth scrolling:") - draw_settings_checkbox("scroll_smooth") - - if not set.scroll_smooth: - imgui.push_disabled() - - draw_settings_label( - "Smoothness:", - "How fast or slow the smooth scrolling animation is. Default is 8." - ) - changed, value = imgui.drag_float("###scroll_smooth_speed", set.scroll_smooth_speed, change_speed=0.25, min_value=0.1, max_value=50) - set.scroll_smooth_speed = min(max(value, 0.1), 50) - if changed: - async_thread.run(db.update_settings("scroll_smooth_speed")) - - if not set.scroll_smooth: - imgui.pop_disabled() - - draw_settings_label( - "Scroll mult:", - "Multiplier for how much a single scroll event should actually scroll. Default is 1." - ) - changed, value = imgui.drag_float("###scroll_amount", set.scroll_amount, change_speed=0.05, min_value=0.1, max_value=10, format="%.2fx") - set.scroll_amount = min(max(value, 0.1), 10) - if changed: - async_thread.run(db.update_settings("scroll_amount")) - - draw_settings_label( - "Quick filters:", - "When this is enabled you can click on some widgets to quickly add filters for them. This includes: type, tag and " - "label widgets, and status and update icons." - ) - draw_settings_checkbox("quick_filters") - - draw_settings_label("Compact timeline:") - draw_settings_checkbox("compact_timeline") - - draw_settings_label("Highlight tags:") - draw_settings_checkbox("highlight_tags") - - draw_settings_label("Tags to highlight:") - if imgui.button("Select", width=right_width): - utils.push_popup(self.draw_tag_highlights_popup) - - draw_settings_label( - "Time format:", - "The format expression to use for full timestamps. Uses the strftime specification. Default is '%d/%m/%Y %H:%M'." - ) - changed, value = imgui.input_text("###timestamp_format", set.timestamp_format) - def setter_extra(_=None): - async_thread.run(db.update_settings("timestamp_format")) - for timestamp in Timestamp.instances: - timestamp.update() - if changed: - set.timestamp_format = value - setter_extra() - if imgui.begin_popup_context_item("###timestamp_format_context"): - utils.text_context(set, "timestamp_format", setter_extra) - imgui.end_popup() - - now = dt.datetime.now() - try: - timestamp = now.strftime(set.timestamp_format) - except Exception: - timestamp = "Bad format!" - draw_settings_label(f"Time: {timestamp}") - imgui.text("") - imgui.spacing() - - draw_settings_label( - "Date format:", - "The format expression to use for short datestamps. Uses the strftime specification. Default is '%b %d, %Y'." - ) - changed, value = imgui.input_text("###datestamp_format", set.datestamp_format) - def setter_extra(_=None): - async_thread.run(db.update_settings("datestamp_format")) - for datestamp in Datestamp.instances: - datestamp.update() - if changed: - set.datestamp_format = value - setter_extra() - if imgui.begin_popup_context_item("###datestamp_format_context"): - utils.text_context(set, "datestamp_format", setter_extra) - imgui.end_popup() - - try: - datestamp = now.strftime(set.datestamp_format) - except Exception: - datestamp = "Bad format!" - draw_settings_label(f"Date: {datestamp}") - imgui.text("") - imgui.spacing() - - draw_settings_label( - "Vsync ratio:", - "Vsync means that the framerate should be synced to the one your monitor uses. The ratio modifies this behavior. " - "A ratio of 1:0 means uncapped framerate, while all other numbers indicate the ratio between screen and app FPS. " - "For example a ratio of 1:2 means the app refreshes every 2nd monitor frame, resulting in half the framerate." - ) - changed, value = imgui.drag_int("###vsync_ratio", set.vsync_ratio, change_speed=0.05, min_value=0, max_value=10, format="1:%d") - set.vsync_ratio = min(max(value, 0), 10) - if changed: - glfw.swap_interval(set.vsync_ratio) - async_thread.run(db.update_settings("vsync_ratio")) - - draw_settings_label(f"Current framerate: {round(imgui.io.framerate, 3)}") - imgui.text("") - imgui.spacing() - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Labels"): - swap = None - for label_i, label in enumerate(Label.instances): - imgui.table_next_row() - imgui.table_next_column() - if imgui.button(icons.filter_plus_outline, width=frame_height): - flt = Filter(FilterMode.Label) - flt.match = label - self.filters.append(flt) - imgui.same_line() - if label_i == 0: - imgui.push_disabled() - if imgui.button(icons.arrow_up, width=frame_height): - swap = (label_i, label_i - 1) - if label_i == 0: - imgui.pop_disabled() - imgui.same_line() - if label_i == len(Label.instances) - 1: - imgui.push_disabled() - if imgui.button(icons.arrow_down, width=frame_height): - swap = (label_i, label_i + 1) - if label_i == len(Label.instances) - 1: - imgui.pop_disabled() - imgui.same_line() - changed, value = imgui.color_edit3(f"###label_color_{label.id}", *label.color[:3], flags=imgui.COLOR_EDIT_NO_INPUTS) - if changed: - label.color = (*value, 1.0) - async_thread.run(db.update_label(label, "color")) - imgui.same_line() - imgui.set_next_item_width(width - frame_height * 5 - imgui.style.cell_padding.x * 5 - imgui.style.scrollbar_size * (imgui.get_scroll_max_y() > 0.0)) - changed, value = imgui.input_text_with_hint(f"###label_name_{label.id}", "Label name", label.name) - setter_extra = lambda _=None: async_thread.run(db.update_label(label, "name")) - if changed: - label.name = value - setter_extra() - if imgui.begin_popup_context_item(f"###label_name_{label.id}_context"): - utils.text_context(label, "name", setter_extra) - imgui.end_popup() - imgui.same_line() - if imgui.button(icons.trash_can_outline, width=frame_height): - async_thread.run(db.delete_label(label)) - - if swap: - Label.instances[swap[0]], Label.instances[swap[1]] = Label.instances[swap[1]], Label.instances[swap[0]] - Label.update_positions() - for game in globals.games.values(): - game.labels.sort(key=lambda label: label.position) - async_thread.run(db.update_label_positions()) - - draw_settings_label("New label:") - if imgui.button("Add", width=right_width): - if len(Label.instances) == 63: - utils.push_popup( - msgbox.msgbox, "Alert!", - "You have reached the maximum number (63) of labels.", - MsgBox.warn - ) - else: - async_thread.run(db.create_label()) - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Manage"): - imgui.table_next_row() - imgui.table_next_column() - pos = imgui.get_cursor_pos() - imgui.table_next_column() - imgui.set_cursor_pos(pos) - imgui.begin_group() - if imgui.tree_node("Import", flags=imgui.TREE_NODE_SPAN_AVAILABLE_WIDTH): - offset = imgui.get_cursor_pos_x() - pos.x - if imgui.button("Thread links", width=-offset): - thread_links = builtins.type("_", (), dict(_=""))() - def popup_content(): - nonlocal thread_links - imgui.text( - "Any kind of F95zone thread link, preferably 1 per line. Will be parsed and cleaned,\n" - "so don't worry about tidiness and paste like it's anarchy!" - ) - _, thread_links._ = imgui.input_text_multiline( - "###import_links", - value=thread_links._, - width=min(self.scaled(600), imgui.io.display_size.x * 0.6), - height=imgui.io.display_size.y * 0.6 - ) - if imgui.begin_popup_context_item("###import_links_context"): - utils.text_context(thread_links, "_", no_icons=True) - imgui.end_popup() - buttons={ - f"{icons.check} Import": lambda: async_thread.run(callbacks.add_games(*utils.extract_thread_matches(thread_links._))), - f"{icons.cancel} Cancel": None - } - utils.push_popup( - utils.popup, "Import thread links", - popup_content, - buttons, - closable=True, - outside=False - ) - if imgui.button("F95 bookmarks", width=-offset): - utils.start_refresh_task(api.import_f95_bookmarks(), reset_bg_timers=False, notify_new_games=False) - if imgui.button("F95 watched threads", width=-offset): - utils.start_refresh_task(api.import_f95_watched_threads(), reset_bg_timers=False, notify_new_games=False) - if imgui.button("Browser bookmarks", width=-offset): - def callback(selected): - if selected: - async_thread.run(api.import_browser_bookmarks(selected)) - buttons={ - f"{icons.check} Ok": lambda: utils.push_popup(filepicker.FilePicker( - title="Select or drop bookmark file", - callback=callback - ).tick), - f"{icons.cancel} Cancel": None - } - utils.push_popup( - msgbox.msgbox, "Bookmark file", - "F95Checker can import your browser bookmarks using an exported bookmark HTML.\n" - "Exporting such a file may vary between browsers, but generally speaking you need to:\n" - " - Open your browser's bookmark manager\n" - " - Find an import / export section, menu or dropdown\n" - " - Click export as HTML\n" - " - Save the file in some place you can find easily\n" - "\n" - "Once you have done this click Ok and select this file.", - MsgBox.info, - buttons - ) - file_hover = imgui.is_item_hovered() - if imgui.button("URL Shortcut file", width=-offset): - def callback(selected): - if selected: - async_thread.run(api.import_url_shortcut(selected)) - utils.push_popup(filepicker.FilePicker( - title="Select or drop shortcut file", - callback=callback - ).tick), - file_hover = file_hover or imgui.is_item_hovered() - if file_hover: - self.draw_hover_text("You can also drag and drop .html and .url files into the window for this!", text=None, force=True) - imgui.tree_pop() - if imgui.tree_node("Export", flags=imgui.TREE_NODE_SPAN_AVAILABLE_WIDTH): - offset = imgui.get_cursor_pos_x() - pos.x - if imgui.button("Thread links", width=-offset): - thread_links = builtins.type("_", (), dict(_="\n".join(game.url for game in globals.games.values())))() - def popup_content(): - imgui.input_text_multiline( - "###export_links", - value=thread_links._, - width=min(self.scaled(600), imgui.io.display_size.x * 0.6), - height=imgui.io.display_size.y * 0.6, - flags=imgui.INPUT_TEXT_READ_ONLY - ) - if imgui.begin_popup_context_item("###export_links_context"): - utils.text_context(thread_links, "_", editable=False) - imgui.end_popup() - utils.push_popup( - utils.popup, "Export thread links", - popup_content, - buttons=True, - closable=True, - outside=False - ) - if imgui.button("Unknown tags", width=-offset): - unknown_tags = builtins.type("_", (), dict(_="\n".join(builtins.set(itertools.chain.from_iterable(game.unknown_tags for game in globals.games.values())))))() - def popup_content(): - imgui.input_text_multiline( - "###export_unknown_tags", - value=unknown_tags._, - width=min(self.scaled(600), imgui.io.display_size.x * 0.6), - height=imgui.io.display_size.y * 0.6, - flags=imgui.INPUT_TEXT_READ_ONLY - ) - if imgui.begin_popup_context_item("###export_unknown_tags_context"): - utils.text_context(unknown_tags, "_", editable=False) - imgui.end_popup() - utils.push_popup( - utils.popup, "Export unknown tags", - popup_content, - buttons=True, - closable=True, - outside=False - ) - imgui.tree_pop() - if imgui.tree_node("Clear", flags=imgui.TREE_NODE_SPAN_AVAILABLE_WIDTH): - offset = imgui.get_cursor_pos_x() - pos.x - if imgui.button("All cookies", width=-offset): - buttons = { - f"{icons.check} Yes": lambda: async_thread.run(db.update_cookies({})), - f"{icons.cancel} No": None - } - utils.push_popup( - msgbox.msgbox, "Clear cookies", - "Are you sure you want to clear your session cookies?\n" - "This will invalidate your login session, but might help\n" - "if you are having issues.", - MsgBox.warn, - buttons - ) - if imgui.button("RPDL session", width=-offset): - def clear_callback(): - globals.settings.rpdl_username = "" - globals.settings.rpdl_password = "" - globals.settings.rpdl_token = "" - async_thread.run(db.update_settings("rpdl_username", "rpdl_password", "rpdl_token")) - buttons = { - f"{icons.check} Yes": clear_callback, - f"{icons.cancel} No": None - } - utils.push_popup( - msgbox.msgbox, "Clear RPDL session", - "Are you sure you want to clear your RPDL session?\n" - "You will need to sign in to RPDL again to use torrents.", - MsgBox.warn, - buttons - ) - imgui.tree_pop() - imgui.end_group() - imgui.spacing() - - draw_settings_label( - "Ask exe on add:", - "When this is enabled you will be asked to select a game executable right after adding the game to F95Checker." - ) - draw_settings_checkbox("select_executable_after_add") - - draw_settings_label( - "Installed on add:", - "When this is enabled games will be marked as installed by default when first added to F95Checker." - ) - draw_settings_checkbox("mark_installed_after_add") - - draw_settings_label( - "Set exe dir:", - "This setting indicates what folder you keep all your games in (if you're organized). Executables inside this folder are " - "remembered relatively, this means you can select the executables with this setting on, then move your entire folder, update this " - "setting, and all the executables are still valid.\n" - "This setting is OS dependent, it has different values for different operating systems you use F95Checker on.\n\n" - f"Current value: {set.default_exe_dir.get(globals.os) or 'Unset'}" - ) - if imgui.button("Choose", width=right_width): - def select_callback(selected): - set.default_exe_dir[globals.os] = selected or "" - async_thread.run(db.update_settings("default_exe_dir")) - for game in globals.games.values(): - game.validate_executables() - utils.push_popup(filepicker.DirPicker( - title="Select or drop default exe dir", - start_dir=set.default_exe_dir.get(globals.os), - callback=select_callback - ).tick) - - draw_settings_label( - "Exe Wrappers:", - "Customize how executables are launched by default for each game engine / media type.\n" - "Useful for specifying custom ways to open them, like Wine/Proton for Windows games on Linux/macOS or a media player for animation/comic collections.\n" - "Can also be used to specify custom arguments, to the exe or to a wrapper." - ) - if imgui.button("Configure", width=right_width): - if wine.is_supported() and not wine.cache: - wine.refresh() - def popup_content(): - imgui.text( - "Customize how executables are launched by default for each game engine / media type.\n" - "Useful for specifying custom ways to open them, like Wine/Proton for Windows games\n" - "on Linux/macOS or a media player for animation/comic collections.\n" - "Can also be used to specify custom arguments, to the exe or to a wrapper.\n" - "\n" - "Use %command% to substitute in the executable path, for example:\n" - "- VLC.exe -LZ %command%\n" - "- %command% -dx12\n" - "- env WINEPREFIX=/path/to/prefix wine %command%\n" - "\n" - "Each engine/type can have a wrapper configured for it below, and individual games can\n" - "have it overridden from their info panel." - ) - - if wine.is_supported(): - imgui.spacing() - imgui.spacing() - imgui.push_font(imgui.fonts.bold) - imgui.text("Wine/Proton runners") - imgui.pop_font() - - imgui.text( - "Run games through a compatibility runner instead of leaving Windows builds to the system\n" - "wine. By default F95Checker will also specify separate prefixes for each engine." - ) - - imgui.spacing() - - imgui.align_text_to_frame_padding() - imgui.text(f"Runners found: {len(wine.cache)}") - imgui.same_line() - if imgui.button(f"{icons.reload_alert} Rescan"): - wine.refresh() - - if set.wine_extra_runners_dirs.get(globals.os) is None: - set.wine_extra_runners_dirs[globals.os] = [] - imgui.align_text_to_frame_padding() - imgui.text("Extra runner folders:") - imgui.same_line() - self.draw_hover_text( - "Only needed if a Steam install or a set of runners lives somewhere\n" - "unusual (eg Heroic, Lutris...). Steam's own libraries, including ones\n" - "on other drives, arealready found on their own.\n" - "\n" - "Accepts either a Steam installation or a folder holding runners." - ) - imgui.same_line() - if imgui.button(icons.plus): - set.wine_extra_runners_dirs[globals.os].append("") - async_thread.run(db.update_settings("wine_extra_runners_dirs")) - for i in reversed(range(len(set.wine_extra_runners_dirs[globals.os]))): - if imgui.button(icons.trash_can_outline): - del set.wine_extra_runners_dirs[globals.os][i] - async_thread.run(db.update_settings("wine_extra_runners_dirs")) - continue - imgui.same_line() - imgui.set_next_item_width(-imgui.FLOAT_MIN) - changed, value = imgui.input_text_with_hint( - f"###wine_extra_runners_dirs_{i}", - "A Steam install or runner folder found elsewhere", - set.wine_extra_runners_dirs[globals.os][i] - ) - if changed: - set.wine_extra_runners_dirs[globals.os][i] = value - async_thread.run(db.update_settings("wine_extra_runners_dirs")) - - imgui.spacing() - - imgui.align_text_to_frame_padding() - imgui.text("Prefixes folder:") - imgui.same_line() - self.draw_hover_text( - "Where Wine/Proton keep their files, this is also where saves will (usually) be buried.\n" - "These reach hundreds of MB each, so somewhere with room is worth picking.\n" - "\n" - "F95Checker will default to a separate prefix for each engine/type that is configured\n" - "below to use a Wine/Proton runner.\n" - "\n" - "Only applies to commands filled in after changing it." - ) - imgui.same_line() - imgui.set_next_item_width(-imgui.FLOAT_MIN) - changed, value = imgui.input_text_with_hint( - "###wine_prefixes_dir", str(wine.prefix_root()), set.wine_prefixes_dir.get(globals.os, "") - ) - if changed: - set.wine_prefixes_dir[globals.os] = value - async_thread.run(db.update_settings("wine_prefixes_dir")) - - if set.default_launch_wrapper.get(globals.os) is None: - set.default_launch_wrapper[globals.os] = {} - category = None - for type in Type: - if type.category is Category.Misc: - continue - if category is not type.category: - category = type.category - imgui.spacing() - imgui.spacing() - imgui.push_font(imgui.fonts.bold) - imgui.text(type.category.name) - imgui.pop_font() - current = set.default_launch_wrapper[globals.os].get(type, None) - wine_match = wine.match_runner(current) if wine.is_supported() else None - self.draw_type_widget(type) - imgui.same_line() - column_x = imgui.get_cursor_pos_x() - imgui.set_next_item_width(self.scaled(240)) - if imgui.begin_combo(f"###wrapper_{type.value}", wine_match or ("Custom" if current is not None else "None")): - if imgui.selectable("None", current is None)[0]: - set.default_launch_wrapper[globals.os].pop(type, None) - current = None - async_thread.run(db.update_settings("default_launch_wrapper")) - if imgui.selectable("Custom", current is not None and not wine_match)[0]: - set.default_launch_wrapper[globals.os][type] = "%command%" - current = set.default_launch_wrapper[globals.os][type] - async_thread.run(db.update_settings("default_launch_wrapper")) - if wine.is_supported(): - for name, path in wine.cache: - if imgui.selectable(name, name == wine_match)[0]: - set.default_launch_wrapper[globals.os][type] = wine.build_wrapper( - path, wine.prefix_for(type.name) - ) - async_thread.run(db.update_settings("default_launch_wrapper")) - imgui.end_combo() - if current is not None: - imgui.set_cursor_pos_x(column_x) - imgui.align_text_to_frame_padding() - imgui.set_next_item_width(-imgui.FLOAT_MIN) - changed, value = imgui.input_text(f"###wrapper_text_{type.value}", current) - if changed: - set.default_launch_wrapper[globals.os][type] = value - async_thread.run(db.update_settings("default_launch_wrapper")) - utils.push_popup( - utils.popup, "Exe wrappers", - popup_content, - {f"{icons.check} Done": None}, - closable=True, - outside=True - ) - - draw_settings_label( - "Downloads dir:", - "Where downloads will be saved to. Currently, only F95zone Donor DDL downloads are supported in F95Checker, but this " - "setting is also used for saving RPDL torrent files.\n" - "This setting is OS dependent, it has different values for different operating systems you use F95Checker on.\n\n" - f"Current value: {set.downloads_dir.get(globals.os) or pathlib.Path.home() / 'Downloads'}\n\n" - "For other download types, you may want to consider a download manager like JDownloader2, and configure it to monitor the " - "clipboard: then you will be able to copy links and have them automatically download in your external download manager." - ) - if imgui.button("Choose", width=right_width): - def select_callback(selected): - set.downloads_dir[globals.os] = selected or "" - async_thread.run(db.update_settings("downloads_dir")) - utils.push_popup(filepicker.DirPicker( - title="Select or drop downloads dir", - start_dir=set.downloads_dir.get(globals.os), - callback=select_callback - ).tick) - - draw_settings_label("Show remove button:") - draw_settings_checkbox("show_remove_btn") - - draw_settings_label("Confirm when removing:") - draw_settings_checkbox("confirm_on_remove") - - draw_settings_label( - "Weighted score:", - "Use weighted rating algorithm when sorting table by forum score.\n" - "You can see the final value used by hovering over the score number." - ) - if draw_settings_checkbox("weighted_score"): - self.recalculate_ids = True - - draw_settings_label( - "Custom game:", - "Add a custom game that is untied from F95zone. Useful for games removed for breaking forum rules, or for adding games " - "from other platforms. Custom games are not checked for updates and you have to add the core details (name, url, version...) " - "yourself. You can later convert a custom game to an F95zone game from the info popup." - ) - if imgui.button("Add", width=right_width): - game_id = async_thread.wait(db.create_game(custom=True)) - async_thread.wait(db.load_games(game_id)) - game = globals.games[game_id] - utils.push_popup(self.draw_game_info_popup, game, None) - if globals.settings.mark_installed_after_add: - game.installed = game.version - if globals.settings.select_executable_after_add: - callbacks.add_game_exe(game) - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Proxy"): - draw_settings_label( - "Type:", - "All listed proxy types work with the main F95Checker functionality.\n\n" - "The integrated browser (also used for login) instead has some limitations due to Qt:\n" - "- SOCKS4 is not supported at all\n" - "- SOCKS5 with authentication won't work\n" - "- HTTP with authentication is not implemented" - ) - changed, value = imgui.combo("###proxy_type", set.proxy_type._index_, ProxyType._member_names_) - if changed: - set.proxy_type = ProxyType[ProxyType._member_names_[value]] - async_thread.run(db.update_settings("proxy_type")) - api.make_session() - - if set.proxy_type is ProxyType.Disabled: - imgui.push_disabled() - - draw_settings_label( - "Host:", - "Domain or IP address of proxy server.\n" - "For example: 127.0.0.1, myproxy.example.com" - ) - changed, value = imgui.input_text_with_hint("###proxy_host", "Domain/IP", set.proxy_host) - if changed: - set.proxy_host = value - async_thread.run(db.update_settings("proxy_host")) - api.make_session() - - draw_settings_label("Port:") - changed, value = imgui.drag_int("###proxy_port", set.proxy_port, change_speed=0.5, min_value=1, max_value=65535) - set.proxy_port = min(max(value, 1), 65535) - if changed: - set.proxy_port = int(value) - async_thread.run(db.update_settings("proxy_port")) - api.make_session() - - draw_settings_label("Username:", "Leave empty if proxy does not require authentication") - changed, value = imgui.input_text("###proxy_username", set.proxy_username) - if changed: - set.proxy_username = value - async_thread.run(db.update_settings("proxy_username")) - api.make_session() - - draw_settings_label("Password:", "Leave empty if proxy does not require authentication") - changed, value = imgui.input_text( - "###proxy_password", - set.proxy_password, - flags=imgui.INPUT_TEXT_PASSWORD, - ) - if changed: - set.proxy_password = value - async_thread.run(db.update_settings("proxy_password")) - api.make_session() - - if set.proxy_type is ProxyType.Disabled: - imgui.pop_disabled() - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Refresh"): - draw_settings_label("Check alerts and inbox:") - draw_settings_checkbox("check_notifs") - - draw_settings_label( - "Update notifs banners:", - "Whether to include a banner image when sending desktop notifications for updates." - ) - draw_settings_checkbox("notifs_show_update_banner") - - draw_settings_label("Refresh if archived:") - draw_settings_checkbox("refresh_archived_games") - - draw_settings_label("Refresh if completed:") - draw_settings_checkbox("refresh_completed_games") - - draw_settings_label( - "Connections:", - "Games are checked 10 at a time for updates, and of those only those with new data are fetched for all game " - "info from the F95Checker Cache API. This setting determines how many of those can be fetched simultaneously. " - "In most cases 10 should be fine, but lower it if your internet struggles when doing a full refresh." - ) - changed, value = imgui.drag_int("###max_connections", set.max_connections, change_speed=0.5, min_value=1, max_value=10) - set.max_connections = min(max(value, 1), 10) - if changed: - async_thread.run(db.update_settings("max_connections")) - - draw_settings_label( - "Timeout:", - "To check for updates, notifications and other functionality, F95Checker sends web requests (to its dedicated " - "Cache API, to F95zone itself, and to other third-parties like RPDL.net if you so choose). However this can sometimes " - "go wrong. The timeout is the maximum amount of seconds that a request can try to connect for before it fails.\n" - "A timeout of 10-30 seconds is most typical." - ) - changed, value = imgui.drag_int("###request_timeout", set.request_timeout, change_speed=0.6, min_value=1, max_value=120, format="%d sec") - set.request_timeout = min(max(value, 1), 120) - if changed: - async_thread.run(db.update_settings("request_timeout")) - - draw_settings_label( - "Retries:", - "While refreshing, a lot of web requests are made quite quickly, so some of them might fail. This setting " - "determines how many times a failed request will be reattempted before failing completely. However these " - "connection errors are often caused by misconfigured connections and timeout values, so try to tinker with those " - "instead of the retries value. This setting should only be used if you know your connection is very unreliable.\n" - "Usually 2 max retries are fine for stable connections." - ) - changed, value = imgui.drag_int("###max_retries", set.max_retries, change_speed=0.05, min_value=0, max_value=10) - set.max_retries = min(max(value, 0), 10) - if changed: - async_thread.run(db.update_settings("max_retries")) - - draw_settings_label( - "No semaphore timeout:", - "If you are having connection issues specifically with 'WinError 121' and 'The semaphore timeout period has expired' " - "then try to enable this option, it will suppress these errors and retry all connections as if they never happened. " - "However this type of error is usually caused by hardware or driver issues, or some bad Windows updates. It is recommended " - "you first try to repair your system with sfc and DISM (Google them) and update your drivers. Use this option as a last resort." - ) - draw_settings_checkbox("ignore_semaphore_timeouts") - - draw_settings_label(f"Insecure SSL:") - draw_settings_checkbox("insecure_ssl") - - draw_settings_label(f"Async tasks count: {sum((0 if task.done() else 1) for task in asyncio.all_tasks(loop=async_thread.loop))}") - imgui.text("") - imgui.spacing() - - draw_settings_label( - "BG interval:", - "When F95Checker is in background mode it automatically refreshes your games periodically. This " - "controls how often (in minutes) this happens." - ) - changed, value = imgui.drag_int("###bg_refresh_interval", set.bg_refresh_interval, change_speed=4.0, min_value=30, max_value=1440, format="%d min") - set.bg_refresh_interval = min(max(value, 30), 1440) - if changed: - async_thread.run(db.update_settings("bg_refresh_interval")) - - if not set.check_notifs: - imgui.push_disabled() - - draw_settings_label( - "BG notifs intv:", - "When F95Checker is in background mode it automatically checks your notifications periodically. This " - "controls how often (in minutes) this happens." - ) - changed, value = imgui.drag_int("###bg_notifs_interval", set.bg_notifs_interval, change_speed=4.0, min_value=15, max_value=1440, format="%d min") - set.bg_notifs_interval = min(max(value, 15), 1440) - if changed: - async_thread.run(db.update_settings("bg_notifs_interval")) - - if not set.check_notifs: - imgui.pop_disabled() - - draw_settings_label(f"Last refresh: {set.last_successful_refresh.display or 'Never'}") - imgui.text("") - imgui.spacing() - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Startup"): - draw_settings_label("Refresh at start:") - draw_settings_checkbox("start_refresh") - - draw_settings_label( - "Start in BG:", - "F95Checker will start in background mode, hidden in the system tray." - ) - draw_settings_checkbox("start_in_background") - - draw_settings_label("Start with system:") - imgui.set_cursor_pos_x(imgui.get_cursor_pos_x() + checkbox_offset) - changed, value = imgui.checkbox("###start_with_system", globals.start_with_system) - if changed: - callbacks.update_start_with_system(value) - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Style"): - draw_settings_label("Corner radius:") - changed, value = imgui.drag_int("###style_corner_radius", set.style_corner_radius, change_speed=0.04, min_value=0, max_value=6, format="%d px") - set.style_corner_radius = min(max(value, 0), 6) - if changed: - imgui.style.window_rounding = imgui.style.frame_rounding = imgui.style.tab_rounding = \ - imgui.style.child_rounding = imgui.style.grab_rounding = imgui.style.popup_rounding = \ - imgui.style.scrollbar_rounding = globals.settings.style_corner_radius - async_thread.run(db.update_settings("style_corner_radius")) - - draw_settings_label("Accent:") - draw_settings_color("style_accent") - - draw_settings_label("Background:") - draw_settings_color("style_bg") - - draw_settings_label("Alt background:") - draw_settings_color("style_alt_bg") - - draw_settings_label("Border:") - draw_settings_color("style_border") - - draw_settings_label("Text:") - draw_settings_color("style_text") - - draw_settings_label("Text dim:") - draw_settings_color("style_text_dim") - - draw_settings_label("Defaults:") - if imgui.button("Restore", width=right_width): - set.style_corner_radius = DefaultStyle.corner_radius - set.style_accent = colors.hex_to_rgba_0_1(DefaultStyle.accent) - set.style_alt_bg = colors.hex_to_rgba_0_1(DefaultStyle.alt_bg) - set.style_bg = colors.hex_to_rgba_0_1(DefaultStyle.bg) - set.style_border = colors.hex_to_rgba_0_1(DefaultStyle.border) - set.style_text = colors.hex_to_rgba_0_1(DefaultStyle.text) - set.style_text_dim = colors.hex_to_rgba_0_1(DefaultStyle.text_dim) - self.refresh_styles() - async_thread.run(db.update_settings( - "style_corner_radius", - "style_accent", - "style_alt_bg", - "style_bg", - "style_border", - "style_text", - "style_text_dim", - )) - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Tabs"): - draw_settings_label("Filter all tabs:") - if draw_settings_checkbox("filter_all_tabs"): - self.recalculate_ids = True - - draw_settings_label("Hide empty tabs:") - draw_settings_checkbox("hide_empty_tabs") - - draw_settings_label( - f"Default {icons.arrow_left_right} New", - "Change 'Default' tab to 'New'. Only a visual change.\n" - "Can be useful if you only store new games in default tab." - ) - draw_settings_checkbox("default_tab_is_new") - - draw_settings_label( - "Independent views:", - "Each tab will have its own sorting/column preferences." - ) - if draw_settings_checkbox("independent_tab_views"): - self.recalculate_ids = True - - imgui.end_table() - imgui.spacing() - - if draw_settings_section("Background", collapsible=False): - draw_settings_label( - "BG mode:", - "When in background mode, F95Checker hides the main window and only keeps the icon in the system tray. The main feature of " - "background mode is periodic refreshing: your list will be automatically refreshed at regular intervals and you will receive " - "a desktop notification if some updates / notifications have been found." - ) - if imgui.button("Switch", width=right_width): - self.hide() - - imgui.end_table() - - if api.downloads: - imgui.text("\nDonor DDL:\n") - to_remove = [] - for download_id, download in api.downloads.items(): - if not download: - continue - if download.state == download.State.Removed: - to_remove.append(download_id) - continue - imgui.spacing() - imgui.spacing() - imgui.spacing() - imgui.text(download.name) - errored = download.error or download.progress != download.total - - if download.state == download.State.Downloading: - space_after = ( - 1 * ( - 2 * imgui.style.frame_padding.y + - imgui.style.frame_border_size + - imgui.style.item_spacing.x - ) + - imgui.calc_text_size(icons.stop).x + - imgui.style.frame_border_size - ) - elif download.state == download.State.Stopped: - if imgui.button(icons.folder_open_outline): - extracted_dir = download.extracted if not errored else None - async_thread.run(callbacks.default_open(extracted_dir or download.path.parent)) - imgui.same_line() - if not errored: - if imgui.button(icons.open_in_app): - async_thread.run(callbacks.default_open(download.path)) - imgui.same_line() - space_after = ( - 2 * ( - 2 * imgui.style.frame_padding.y + - imgui.style.frame_border_size + - imgui.style.item_spacing.x - ) + - imgui.calc_text_size(icons.cancel + icons.trash_can_outline).x + - imgui.style.frame_border_size - ) - else: - space_after = 0 - - ratio = download.progress / (download.total or 1) - downd = download.progress - speed = downd / ((download.current - download.start) or 1) - width = imgui.get_content_region_available_width() - space_after - height = imgui.get_frame_height() - imgui.progress_bar(ratio, (width, height)) - if download.state == download.State.Downloading: - text = f"{utils.sizeof_fmt(downd):>7} {min(ratio, 99):>3.0%} ({utils.sizeof_fmt(speed):>7}/s)" - elif download.state == download.State.Stopped: - if not errored: - text = "Done!" - else: - text = "Error!" - self.draw_hover_text( - download.error or f"Received less data than expected ({download.progress} != {download.total})", - text=None, - ) - if download.traceback and imgui.is_item_clicked(): - utils.push_popup( - msgbox.msgbox, f"Error downloading {download.name}", - download.error, - MsgBox.error, - more=download.traceback, - ) - else: - text = f"{download.state.name}..." - imgui.same_line() - imgui.push_font(imgui.fonts.mono_sm) - draw_list = imgui.get_window_draw_list() - col = imgui.get_color_u32_rgba(1, 1, 1, 1) - text_size = imgui.calc_text_size(text) - screen_pos = imgui.get_cursor_screen_pos() - text_x = screen_pos.x - (width + text_size.x) / 2 - imgui.style.item_spacing.x - text_y = screen_pos.y + (height - text_size.y) / 2 - draw_list.add_text(text_x, text_y, col, text) - imgui.pop_font() - - if download.state == download.State.Downloading: - if was_canceling := download.cancel: - imgui.push_disabled() - if imgui.button(icons.stop): - download.cancel = True - if was_canceling: - imgui.pop_disabled() - elif download.state == download.State.Stopped: - if imgui.button(icons.cancel): - to_remove.append(download_id) - imgui.same_line() - if imgui.button(icons.trash_can_outline): - async_thread.run(download.delete()) - for download_id in to_remove: - del api.downloads[download_id] - - imgui.spacing() - imgui.spacing() - imgui.spacing() - imgui.end_child() - - -class TrayIcon(QtWidgets.QSystemTrayIcon): - def __init__(self, main_gui: MainGUI): - self.show_gui_events = [ - QtWidgets.QSystemTrayIcon.ActivationReason.DoubleClick, - QtWidgets.QSystemTrayIcon.ActivationReason.MiddleClick, - QtWidgets.QSystemTrayIcon.ActivationReason.Trigger - ] - self.main_gui = main_gui - self.idle_icon = QtGui.QIcon(str(globals.self_path / 'resources/icons/logo.png')) - self.paused_icon = QtGui.QIcon(str(globals.self_path / 'resources/icons/paused.png')) - super().__init__(self.idle_icon) - - self.watermark = QtGui.QAction(f"F95Checker {globals.version_name}") - self.watermark.triggered.connect(lambda *_: self.main_gui.show()) - - self.next_refresh = QtGui.QAction("Next Refresh: N/A") - self.next_refresh.setEnabled(False) - - self.refresh_btn = QtGui.QAction("Refresh Now!") - self.refresh_btn.triggered.connect(lambda *_: globals.refresh_task.cancel() if utils.is_refreshing() else utils.start_refresh_task(api.refresh())) - - def update_pause(*_): - self.main_gui.bg_mode_paused = not self.main_gui.bg_mode_paused - if self.main_gui.bg_mode_paused: - self.main_gui.bg_mode_timer = None - self.main_gui.bg_mode_notifs_timer = None - self.update_status() - self.toggle_pause = QtGui.QAction("Pause Auto Refresh") - self.toggle_pause.triggered.connect(update_pause) - - self.toggle_gui = QtGui.QAction("Toggle GUI") - self.toggle_gui.triggered.connect(lambda *_: self.main_gui.show() if self.main_gui.hidden else self.main_gui.hide()) - - self.quit = QtGui.QAction("Quit") - self.quit.triggered.connect(self.main_gui.close) - - self.menu = QtWidgets.QMenu() - self.menu.addAction(self.watermark) - self.menu.addAction(self.next_refresh) - self.menu.addAction(self.refresh_btn) - self.menu.addAction(self.toggle_pause) - self.menu.addAction(self.toggle_gui) - self.menu.addAction(self.quit) - if globals.os is not Os.MacOS: - self.setContextMenu(self.menu) - self.menu_open = False - self.menu.aboutToShow.connect(self.showing_menu) - self.menu.aboutToHide.connect(self.hiding_menu) - self.menu.aboutToShow.connect(self.update_menu) - - self.activated.connect(self.activated_filter) - - self.show() - - def update_icon(self, *_): - if self.main_gui.bg_mode_paused and self.main_gui.hidden: - self.setIcon(self.paused_icon) - else: - self.setIcon(self.idle_icon) - - def animate_refresh_icon(self): - icn = 1 + (int(time.time() * 8) % 16) - self.setIcon(QtGui.QIcon(str(globals.self_path / f"resources/icons/refreshing{icn}.png"))) - - def showing_menu(self, *_): - self.menu_open = True - - def hiding_menu(self, *_): - self.menu_open = False - - def update_menu(self, *_): - if self.main_gui.hidden: - if self.main_gui.bg_mode_paused: - next_refresh = "Paused" - elif self.main_gui.bg_mode_timer or self.main_gui.bg_mode_notifs_timer: - next_refresh = dt.datetime.fromtimestamp( - min( - self.main_gui.bg_mode_timer or globals.settings.bg_refresh_interval, - self.main_gui.bg_mode_notifs_timer or globals.settings.bg_notifs_interval - ) - ).strftime("%H:%M") - elif utils.is_refreshing(): - next_refresh = "Now" - else: - next_refresh = "N/A" - self.next_refresh.setText(f"Next Refresh: {next_refresh}") - self.next_refresh.setVisible(True) - else: - self.next_refresh.setVisible(False) - - if utils.is_refreshing(): - self.refresh_btn.setText("Cancel Refresh") - else: - self.refresh_btn.setText("Refresh Now!") - - if self.main_gui.hidden: - if self.main_gui.bg_mode_paused: - self.toggle_pause.setText("Unpause Auto Refresh") - else: - self.toggle_pause.setText("Pause Auto Refresh") - self.toggle_pause.setVisible(True) - else: - self.toggle_pause.setVisible(False) - - if self.main_gui.hidden: - self.toggle_gui.setText("Switch to GUI") - else: - self.toggle_gui.setText("Switch to BG") - - def update_status(self, *_): - self.update_menu() - self.update_icon() - - def activated_filter(self, reason: QtWidgets.QSystemTrayIcon.ActivationReason): - if globals.os is Os.MacOS and reason == QtWidgets.QSystemTrayIcon.ActivationReason.Context: - self.menu.popup(QtGui.QCursor.pos()) - elif reason in self.show_gui_events: - self.main_gui.show() From 8bd3a18ee2e383bd3862a857f60935e373f0b4ae Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:10:49 +0200 Subject: [PATCH 09/25] Preview implementation change --- common/structs.py | 61 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/common/structs.py b/common/structs.py index 2f8e0ec0..7bc404b2 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1005,6 +1005,7 @@ class Game: preview_images : list["imagehelper.ImageHelper"] = dataclasses.field(default_factory=list) previews_loading : bool = False previews_loaded : bool = False + preview_load_future: typing.Any = dataclasses.field(default=None, init=False, repr=False, compare=False) executables_valids : list[bool] = None executables_valid : bool = None timeline_events : list[TimelineEvent] = dataclasses.field(default_factory=list) @@ -1045,30 +1046,57 @@ async def load_previews_async(self): try: from external import imagehelper from modules import api, globals, utils - self.preview_images.clear() + # A retry or resumed load may already have partially populated + # this list. Use the normal cleanup path so existing textures and + # decoded image data are released before rebuilding it. + self.unload_previews() for index, url in enumerate(self.previews_urls): if not isinstance(url, str) or not url.startswith(("http://", "https://")): continue - digest = hashlib.sha1(url.encode("utf-8")).hexdigest()[:16] - glob = f"preview-{self.id}-{digest}.*" - paths = list(globals.images_path.glob(glob)) + digest = hashlib.sha1(url.encode("utf-8")).hexdigest() + preview_dir = globals.images_path / "previews" / str(self.id) + glob = f"{digest}.*" + paths = list(preview_dir.glob(glob)) + if not paths: + # Migrate the previous flat, 16-character cache name + # instead of downloading the same preview again. + legacy_paths = list(globals.images_path.glob(f"preview-{self.id}-{digest[:16]}.*")) + if legacy_paths: + preview_dir.mkdir(parents=True, exist_ok=True) + for legacy_path in legacy_paths: + try: + migrated_path = preview_dir / f"{digest}{''.join(legacy_path.suffixes)}" + shutil.move(legacy_path, migrated_path) + except Exception: + pass + paths = list(preview_dir.glob(glob)) if not paths: try: - data = await api.fetch( - "GET", url, - timeout=globals.settings.request_timeout * 4, - raise_for_status=True, - ) + with api.images_counter: + data = await api.fetch( + "GET", url, + timeout=globals.settings.request_timeout * 4, + raise_for_status=True, + ) if data: - path = globals.images_path / f"preview-{self.id}-{digest}.{utils.image_ext(data)}" + preview_dir.mkdir(parents=True, exist_ok=True) + path = preview_dir / f"{digest}.{utils.image_ext(data)}" path.write_bytes(data) except Exception: continue - self.preview_images.append(imagehelper.ImageHelper(globals.images_path, glob=glob)) + self.preview_images.append(imagehelper.ImageHelper(preview_dir, glob=glob)) self.previews_loaded = True finally: self.previews_loading = False + def cancel_preview_loading(self): + """Cancel an in-flight preview request when its popup is dismissed.""" + future = self.preview_load_future + if future is not None and not future.done(): + future.cancel() + self.preview_load_future = None + self.previews_loading = False + def delete_images(self): from modules import globals self.unload_previews() @@ -1077,11 +1105,22 @@ def delete_images(self): img.unlink() except Exception: pass + # Remove caches made by the pre-subfolder preview implementation. for img in globals.images_path.glob(f"preview-{self.id}-*"): try: img.unlink() except Exception: pass + preview_dir = globals.images_path / "previews" / str(self.id) + for img in preview_dir.glob("*"): + try: + img.unlink() + except Exception: + pass + try: + preview_dir.rmdir() + except OSError: + pass def unload_previews(self): """Release decoded preview data and GPU textures, keeping disk cache.""" From 1cfed1c56592171bbcc6a8c2a1a0e25cf057bd8f Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:12:19 +0200 Subject: [PATCH 10/25] Preview implementation change --- modules/gui.py | 61 +++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/modules/gui.py b/modules/gui.py index 9d9940bc..4fb1422f 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -749,24 +749,17 @@ def save_filters(self): def load_filters(self): try: - # TODO: replace this with a better system, pickle is a disaster waiting to happen (and already kinda did, hence matching by IDs below) with open(globals.data_path / "filters.pkl", "rb") as file: self.filters = pickle.load(file) for flt in self.filters: - try: - match flt.mode: - case FilterMode.Exe_State: - flt.match = ExeState(flt.match.value) - case FilterMode.Label: - flt.match = Label.get(flt.match.id) - case FilterMode.Status: - flt.match = Status(flt.match.value) - case FilterMode.Tag: - flt.match = Tag(flt.match.value) - case FilterMode.Type: - flt.match = Type(flt.match.value) - except Exception: - self.filters.remove(flt) + match flt.mode: + case FilterMode.Label: + # Field added later, replace broken object with real one + if not hasattr(flt.match, "position"): + try: + flt.match = Label.get(flt.match.id) + except Exception: + self.filters.remove(flt) except Exception: self.filters = [] @@ -2282,9 +2275,18 @@ def select_callback(selected): # or download images for games the user never inspects. if game.previews_urls: if not game.previews_loaded and not game.previews_loading: - async_thread.run(game.load_previews_async()) + game.preview_load_future = async_thread.run(game.load_previews_async()) + if game.previews_loading: + download_count = api.images_counter.count + loading_text = ( + f" · Downloading {download_count} image" + f"{'s' if download_count != 1 else ''}" + if download_count else " · Loading..." + ) + else: + loading_text = "" + imgui.text(f"Previews ({len(game.preview_images)}/{len(game.previews_urls)}){loading_text}") if game.preview_images: - imgui.text(f"Previews ({len(game.preview_images)})") # Keep each preview at a useful thumbnail size and put # the row in a child with an explicit horizontal bar. # Without a child, ImGui clips same-line items at the @@ -2303,17 +2305,24 @@ def select_callback(selected): ) first = True for preview in game.preview_images: - if preview.error: - continue aspect = preview.height / preview.width if preview.width else 1.0 height = min(preview_height, preview_width * aspect) if not first: imgui.same_line() + preview_pos = imgui.get_cursor_pos() preview.render(preview_width, height, rounding=rounding) + if preview.error: + # Keep failed previews in the gallery so users + # can see that an item failed rather than assuming + # it was silently omitted. + preview_hovered = imgui.is_item_hovered() + imgui.set_cursor_pos(preview_pos) + imgui.text_wrapped("Preview unavailable") + imgui.set_cursor_pos((preview_pos.x, preview_pos.y + height)) + if preview_hovered: + imgui.set_tooltip(preview.error) first = False imgui.end_child() - elif game.previews_loading: - imgui.text("Loading previews...") imgui.push_text_wrap_pos() imgui.push_font(imgui.fonts.big) @@ -2860,17 +2869,20 @@ def select_callback(selected): if change_id is not None: # Swiping to another game closes this popup's gallery. Keep # the downloaded files, but release decoded/GPU image data. + game.cancel_preview_loading() game.unload_previews() utils.push_popup(self.draw_game_info_popup, globals.games[change_id], carousel_ids).uuid = popup_uuid return True elif utils.close_weak_popup(): return True if game.id not in globals.games: + game.cancel_preview_loading() return 0, True result = utils.popup(game.name, popup_content, closable=True, outside=False, resize=False, popup_uuid=popup_uuid) if result[1]: # A closed info popup is no longer a visible owner of its # textures. The URL cache stays on disk for cheap reopening. + game.cancel_preview_loading() game.unload_previews() return result @@ -5547,13 +5559,6 @@ def select_callback(selected): if imgui.button(icons.open_in_app): async_thread.run(callbacks.default_open(download.path)) imgui.same_line() - else: - if imgui.button(icons.refresh): - async def _retry(download): - await download.delete() - async_thread.run(api.download_file(download)) - async_thread.run(_retry(download)) - imgui.same_line() space_after = ( 2 * ( 2 * imgui.style.frame_padding.y + From f53d33d0777828da8715f093a81603dd1ed9c069 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 22:47:17 +0200 Subject: [PATCH 11/25] Fix merge --- modules/gui.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/modules/gui.py b/modules/gui.py index a520542f..7befce62 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -749,17 +749,24 @@ def save_filters(self): def load_filters(self): try: + # TODO: replace this with a better system, pickle is a disaster waiting to happen (and already kinda did, hence matching by IDs below) with open(globals.data_path / "filters.pkl", "rb") as file: self.filters = pickle.load(file) for flt in self.filters: - match flt.mode: - case FilterMode.Label: - # Field added later, replace broken object with real one - if not hasattr(flt.match, "position"): - try: - flt.match = Label.get(flt.match.id) - except Exception: - self.filters.remove(flt) + try: + match flt.mode: + case FilterMode.Exe_State: + flt.match = ExeState(flt.match.value) + case FilterMode.Label: + flt.match = Label.get(flt.match.id) + case FilterMode.Status: + flt.match = Status(flt.match.value) + case FilterMode.Tag: + flt.match = Tag(flt.match.value) + case FilterMode.Type: + flt.match = Type(flt.match.value) + except Exception: + self.filters.remove(flt) except Exception: self.filters = [] @@ -5561,6 +5568,13 @@ def select_callback(selected): if imgui.button(icons.open_in_app): async_thread.run(callbacks.default_open(download.path)) imgui.same_line() + else: + if imgui.button(icons.refresh): + async def _retry(download): + await download.delete() + async_thread.run(api.download_file(download)) + async_thread.run(_retry(download)) + imgui.same_line() space_after = ( 2 * ( 2 * imgui.style.frame_padding.y + From 15fbff0fff7fc093682c951812f097fb14c556a6 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:08:37 +0200 Subject: [PATCH 12/25] Use old_id for consistency --- modules/db.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/db.py b/modules/db.py index 5ae56099..6e17e2a9 100644 --- a/modules/db.py +++ b/modules/db.py @@ -561,17 +561,17 @@ async def update_game_id(game: Game, new_id): UPDATE games SET id={new_id} - WHERE id={game.id} + WHERE id={old_id} """) globals.games[new_id] = game - if game.id != new_id: - del globals.games[game.id] + if old_id != new_id: + del globals.games[old_id] await connection.execute(f""" UPDATE timeline_events SET game_id={new_id} - WHERE game_id={game.id} + WHERE game_id={old_id} """) for event in game.timeline_events: event.game_id = new_id From 417455fec1ddb7a3195fe01a19907f2ed3bce65d Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:09:27 +0200 Subject: [PATCH 13/25] Get enums directly --- modules/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gui.py b/modules/gui.py index 7befce62..e41b0e86 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -2303,8 +2303,8 @@ def select_callback(selected): preview_width = max(self.scaled(240), (out_width - imgui.style.item_spacing.x) / 2) preview_height = self.scaled(260) horizontal_flags = ( - getattr(imgui, "WINDOW_HORIZONTAL_SCROLLING_BAR", 0) | - getattr(imgui, "WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR", 0) + imgui.WINDOW_HORIZONTAL_SCROLLING_BAR | + imgui.WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR ) imgui.begin_child( "###game_previews_gallery", From 6019925f3ca8cb0e57f556c93bae968eaecc1000 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:09:53 +0200 Subject: [PATCH 14/25] Move preview_dir out of the loop --- common/structs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/structs.py b/common/structs.py index 7bc404b2..e67cdf8c 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1050,11 +1050,11 @@ async def load_previews_async(self): # this list. Use the normal cleanup path so existing textures and # decoded image data are released before rebuilding it. self.unload_previews() - for index, url in enumerate(self.previews_urls): - if not isinstance(url, str) or not url.startswith(("http://", "https://")): + preview_dir = globals.images_path / f"previews/{self.id}" + for url in self.previews_urls: + if not url.startswith(("http://", "https://")): continue digest = hashlib.sha1(url.encode("utf-8")).hexdigest() - preview_dir = globals.images_path / "previews" / str(self.id) glob = f"{digest}.*" paths = list(preview_dir.glob(glob)) if not paths: From 498fa0e71eb0060097ad856f12494adb540ca060 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:23:37 +0200 Subject: [PATCH 15/25] Remove compatibility with prior PR implementation --- common/structs.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/common/structs.py b/common/structs.py index e67cdf8c..efe2b8c4 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1057,19 +1057,6 @@ async def load_previews_async(self): digest = hashlib.sha1(url.encode("utf-8")).hexdigest() glob = f"{digest}.*" paths = list(preview_dir.glob(glob)) - if not paths: - # Migrate the previous flat, 16-character cache name - # instead of downloading the same preview again. - legacy_paths = list(globals.images_path.glob(f"preview-{self.id}-{digest[:16]}.*")) - if legacy_paths: - preview_dir.mkdir(parents=True, exist_ok=True) - for legacy_path in legacy_paths: - try: - migrated_path = preview_dir / f"{digest}{''.join(legacy_path.suffixes)}" - shutil.move(legacy_path, migrated_path) - except Exception: - pass - paths = list(preview_dir.glob(glob)) if not paths: try: with api.images_counter: @@ -1105,12 +1092,6 @@ def delete_images(self): img.unlink() except Exception: pass - # Remove caches made by the pre-subfolder preview implementation. - for img in globals.images_path.glob(f"preview-{self.id}-*"): - try: - img.unlink() - except Exception: - pass preview_dir = globals.images_path / "previews" / str(self.id) for img in preview_dir.glob("*"): try: From ac6f829798e6af2d7254f5ebc5ccbe95ebb46310 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:25:16 +0200 Subject: [PATCH 16/25] Rename dir as-is when changing ID --- modules/db.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/modules/db.py b/modules/db.py index 6e17e2a9..2e38e2f7 100644 --- a/modules/db.py +++ b/modules/db.py @@ -581,19 +581,10 @@ async def update_game_id(game: Game, new_id): shutil.move(img, img.with_name(f"{new_id}{''.join(img.suffixes)}")) except Exception: pass - old_preview_dir = globals.images_path / "previews" / str(old_id) - new_preview_dir = globals.images_path / "previews" / str(new_id) - if old_preview_dir.is_dir(): - new_preview_dir.mkdir(parents=True, exist_ok=True) - for img in old_preview_dir.iterdir(): - try: - shutil.move(img, new_preview_dir / img.name) - except Exception: - pass - try: - old_preview_dir.rmdir() - except OSError: - pass + try: + shutil.move(globals.images_path / f"previews/{old_id}", globals.images_path / f"previews/{new_id}") + except Exception: + pass game.id = new_id game.refresh_image() From 3ed7329232bf4a8dab81cf4a71573fb8fc3bd322 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:25:34 +0200 Subject: [PATCH 17/25] Some more unloads/cancels --- common/structs.py | 1 + modules/db.py | 2 ++ modules/gui.py | 1 + 3 files changed, 4 insertions(+) diff --git a/common/structs.py b/common/structs.py index efe2b8c4..56f49ded 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1086,6 +1086,7 @@ def cancel_preview_loading(self): def delete_images(self): from modules import globals + self.cancel_preview_loading() self.unload_previews() for img in globals.images_path.glob(f"{self.id}.*"): try: diff --git a/modules/db.py b/modules/db.py index 2e38e2f7..61b646b9 100644 --- a/modules/db.py +++ b/modules/db.py @@ -557,6 +557,8 @@ def py_to_sql(value: enum.Enum | Timestamp | bool | list | tuple | typing.Any): async def update_game_id(game: Game, new_id): old_id = game.id + game.cancel_preview_loading() + game.unload_previews() await connection.execute(f""" UPDATE games SET diff --git a/modules/gui.py b/modules/gui.py index e41b0e86..fbb3cb85 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -2886,6 +2886,7 @@ def select_callback(selected): return True if game.id not in globals.games: game.cancel_preview_loading() + game.unload_previews() return 0, True result = utils.popup(game.name, popup_content, closable=True, outside=False, resize=False, popup_uuid=popup_uuid) if result[1]: From 9375c5d0e36fde6f0372ae9d5017444fc6df20a0 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:34:01 +0200 Subject: [PATCH 18/25] Don't delete previews when resetting cover image --- common/structs.py | 23 ++++++++++++----------- modules/callbacks.py | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/common/structs.py b/common/structs.py index 56f49ded..2f706f53 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1084,25 +1084,26 @@ def cancel_preview_loading(self): self.preview_load_future = None self.previews_loading = False - def delete_images(self): + def delete_images(self, cover_only=True): from modules import globals - self.cancel_preview_loading() - self.unload_previews() for img in globals.images_path.glob(f"{self.id}.*"): try: img.unlink() except Exception: pass - preview_dir = globals.images_path / "previews" / str(self.id) - for img in preview_dir.glob("*"): + if not cover_only: + self.cancel_preview_loading() + self.unload_previews() + preview_dir = globals.images_path / "previews" / str(self.id) + for img in preview_dir.glob("*"): + try: + img.unlink() + except Exception: + pass try: - img.unlink() - except Exception: + preview_dir.rmdir() + except OSError: pass - try: - preview_dir.rmdir() - except OSError: - pass def unload_previews(self): """Release decoded preview data and GPU textures, keeping disk cache.""" diff --git a/modules/callbacks.py b/modules/callbacks.py index 14ca17c3..3004ae45 100644 --- a/modules/callbacks.py +++ b/modules/callbacks.py @@ -718,7 +718,7 @@ def remove_game(*games: list[Game], bypass_confirm=False): def remove_callback(): for game in games: id = game.id - game.delete_images() + game.delete_images(cover_only=False) del globals.games[id] globals.gui.recalculate_ids = True async_thread.run(db.delete_game(id)) From 4717e50bfbfbb10dd02301e2d54ef5ee13318096 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 25 Aug 2026 23:55:04 +0200 Subject: [PATCH 19/25] Show total downloading count, play better with api.py ratelimits --- common/structs.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/common/structs.py b/common/structs.py index 2f706f53..4ad3e6b8 100644 --- a/common/structs.py +++ b/common/structs.py @@ -1046,14 +1046,15 @@ async def load_previews_async(self): try: from external import imagehelper from modules import api, globals, utils + import aiofiles # A retry or resumed load may already have partially populated # this list. Use the normal cleanup path so existing textures and # decoded image data are released before rebuilding it. self.unload_previews() preview_dir = globals.images_path / f"previews/{self.id}" - for url in self.previews_urls: + async def _maybe_fetch_preview(url: str): if not url.startswith(("http://", "https://")): - continue + return digest = hashlib.sha1(url.encode("utf-8")).hexdigest() glob = f"{digest}.*" paths = list(preview_dir.glob(glob)) @@ -1068,10 +1069,12 @@ async def load_previews_async(self): if data: preview_dir.mkdir(parents=True, exist_ok=True) path = preview_dir / f"{digest}.{utils.image_ext(data)}" - path.write_bytes(data) + async with aiofiles.open(path, "wb") as f: + await f.write(data) except Exception: - continue + return self.preview_images.append(imagehelper.ImageHelper(preview_dir, glob=glob)) + await asyncio.gather(*(_maybe_fetch_preview(url) for url in self.previews_urls)) self.previews_loaded = True finally: self.previews_loading = False From 95c7a1ac2758a7bb0fbf0658f33eea7eef797495 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 26 Aug 2026 00:01:41 +0200 Subject: [PATCH 20/25] Show compressing status too --- modules/gui.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/gui.py b/modules/gui.py index 52f6f5e3..0db8865d 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -2285,13 +2285,10 @@ def select_callback(selected): if game.previews_urls: if not game.previews_loaded and not game.previews_loading: game.preview_load_future = async_thread.run(game.load_previews_async()) - if game.previews_loading: - download_count = api.images_counter.count - loading_text = ( - f" · Downloading {download_count} image" - f"{'s' if download_count != 1 else ''}" - if download_count else " · Loading..." - ) + if (count := imagehelper.compress_counter) > 0: + loading_text = " · Compressing images..." if count == 1 else f" · Compressing {count} frames..." + elif game.previews_loading and (count := api.images_counter.count) > 0: + loading_text = f" · Downloading {count} image{'s' if count > 1 else ''}..." else: loading_text = "" imgui.text(f"Previews ({len(game.preview_images)}/{len(game.previews_urls)}){loading_text}") From 19eee5501ef071a05a27186a6413530ccde0bfaa Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 26 Aug 2026 00:37:25 +0200 Subject: [PATCH 21/25] Improve preview scaling and error display --- modules/gui.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/modules/gui.py b/modules/gui.py index 0db8865d..af1a116f 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -2154,8 +2154,8 @@ def popup_content(): globals.updated_games.clear() return opened, closed - def draw_game_image_error(self, game: Game, width: float, height: float): - if game.image.error == "Image file missing": + def draw_game_image_error(self, game: Game, image: imagehelper.ImageHelper, width: float, height: float): + if image is game.image and image.error == "Image file missing": text = "Image missing!" if game.custom: hover_text = "Right click in More Info popup to add an image." @@ -2168,7 +2168,7 @@ def draw_game_image_error(self, game: Game, width: float, height: float): ) else: text = "Image error!" - hover_text = game.image.error or "Unknown error" + hover_text = image.error or "Unknown error" text_size = imgui.calc_text_size(text) if text_size.x >= width: @@ -2197,7 +2197,7 @@ def popup_content(): out_height = (min(avail.y, self.scaled(690)) * self.scaled(0.4)) or 1 out_width = avail.x or 1 if image.error: - self.draw_game_image_error(game, out_width, out_height) + self.draw_game_image_error(game, game.image, out_width, out_height) else: aspect_ratio = image.height / image.width if aspect_ratio > (out_height / out_width): @@ -2298,35 +2298,27 @@ def select_callback(selected): # Without a child, ImGui clips same-line items at the # popup boundary and the parent only scrolls vertically. preview_width = max(self.scaled(240), (out_width - imgui.style.item_spacing.x) / 2) - preview_height = self.scaled(260) + preview_height = preview_width / (16 / 9) horizontal_flags = ( imgui.WINDOW_HORIZONTAL_SCROLLING_BAR | - imgui.WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR + imgui.WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR | + imgui.WINDOW_NO_SCROLLBAR ) imgui.begin_child( "###game_previews_gallery", width=out_width, - height=preview_height + imgui.get_frame_height() + imgui.style.item_spacing.y, + height=preview_height + 2 * imgui.style.window_padding.y, flags=horizontal_flags, ) first = True for preview in game.preview_images: - aspect = preview.height / preview.width if preview.width else 1.0 - height = min(preview_height, preview_width * aspect) if not first: imgui.same_line() - preview_pos = imgui.get_cursor_pos() - preview.render(preview_width, height, rounding=rounding) if preview.error: - # Keep failed previews in the gallery so users - # can see that an item failed rather than assuming - # it was silently omitted. - preview_hovered = imgui.is_item_hovered() - imgui.set_cursor_pos(preview_pos) - imgui.text_wrapped("Preview unavailable") - imgui.set_cursor_pos((preview_pos.x, preview_pos.y + height)) - if preview_hovered: - imgui.set_tooltip(preview.error) + self.draw_game_image_error(game, preview, preview_width, preview_height) + else: + crop = preview.crop_to_ratio(preview_width / preview_height, fit=globals.settings.fit_images) + preview.render(preview_width, preview_height, *crop, rounding=rounding) first = False imgui.end_child() imgui.push_text_wrap_pos() @@ -3677,7 +3669,7 @@ def draw_game_cell(self, game: Game, drag_drop: bool, draw_list, cell_width: flo # Image if game.image.error: showed_img = imgui.is_rect_visible(cell_width, img_height) - self.draw_game_image_error(game, cell_width, img_height) + self.draw_game_image_error(game, game.image, cell_width, img_height) else: crop = game.image.crop_to_ratio(globals.settings.cell_image_ratio, fit=globals.settings.fit_images) showed_img = game.image.render(cell_width, img_height, *crop, rounding=rounding, flags=imgui.DRAW_ROUND_CORNERS_TOP) From 9e1754f777aabedce7f9c9978b5438c8a263b770 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 26 Aug 2026 00:48:43 +0200 Subject: [PATCH 22/25] Add opt-in setting --- common/structs.py | 1 + modules/db.py | 1 + modules/gui.py | 10 +++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/common/structs.py b/common/structs.py index 4ad3e6b8..ab176131 100644 --- a/common/structs.py +++ b/common/structs.py @@ -909,6 +909,7 @@ class Settings: play_gifs : bool play_gifs_unfocused : bool preload_nearby_images : bool + previews_enabled : bool proxy_type : ProxyType proxy_host : str proxy_port : int diff --git a/modules/db.py b/modules/db.py index 61b646b9..6182e476 100644 --- a/modules/db.py +++ b/modules/db.py @@ -235,6 +235,7 @@ async def connect(): "play_gifs": f'INTEGER DEFAULT {int(True)}', "play_gifs_unfocused": f'INTEGER DEFAULT {int(False)}', "preload_nearby_images": f'INTEGER DEFAULT {int(False)}', + "previews_enabled": f'INTEGER DEFAULT {int(False)}', "proxy_type": f'INTEGER DEFAULT {ProxyType.Disabled}', "proxy_host": f'TEXT DEFAULT ""', "proxy_port": f'INTEGER DEFAULT 8080', diff --git a/modules/gui.py b/modules/gui.py index af1a116f..d8ff5c1f 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -2282,7 +2282,7 @@ def select_callback(selected): # The indexer stores these URLs separately from the cover image. # Load them lazily so opening an info popup does not slow startup # or download images for games the user never inspects. - if game.previews_urls: + if globals.settings.previews_enabled and game.previews_urls: if not game.previews_loaded and not game.previews_loading: game.preview_load_future = async_thread.run(game.load_previews_async()) if (count := imagehelper.compress_counter) > 0: @@ -4580,6 +4580,14 @@ def cant_install_extension_tooltip(): if not set.zoom_enabled: imgui.pop_disabled() + draw_settings_label( + "Preview images:", + "Downloads preview images when opening the More Info popup for games and shows them below the main image. They are " + "saved to disk too, and as such it can start taking up a lot of space. Disabling this option will not delete " + "previously downloaded preview images from disk. Default is off." + ) + draw_settings_checkbox("previews_enabled") + draw_settings_label("Play GIFs:") if draw_settings_checkbox("play_gifs"): for image in imagehelper.ImageHelper.instances: From cb027f0f22e48260d9c8367ff29dc2daabe27a33 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 26 Aug 2026 00:58:35 +0200 Subject: [PATCH 23/25] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 985211c2..1d4c2d16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ ### Added: +- Preview images in More Info popup (#285 by @Crykon & @WillyJL): + - Opt-in from Settings > Images > Preview images + - Downloads on-demand only while the game's More Info popup is open, resuming/interrupting as it's opened/closed + - Caches to disk, which can take a lot of space, so off by default - Exe launch wrappers (#289 by @cicklolwut & @WillyJL): - Allows setting custom arguments and wrapper commands for launching - Support for Wine/Proton on Linux/macOS From c824f1c19032e68764fda2fa301b5f5aba8d5163 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 26 Aug 2026 01:11:05 +0200 Subject: [PATCH 24/25] Handle case where downloaded previews are removed/replaced on F95zone Otherwise, they would remain downloaded, but never be shown To keep thinsg simple just remove extraneous previews --- common/structs.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/common/structs.py b/common/structs.py index ab176131..677a7c77 100644 --- a/common/structs.py +++ b/common/structs.py @@ -8,6 +8,7 @@ import os import pathlib import shutil +import string import sys import time import typing @@ -1053,10 +1054,9 @@ async def load_previews_async(self): # decoded image data are released before rebuilding it. self.unload_previews() preview_dir = globals.images_path / f"previews/{self.id}" - async def _maybe_fetch_preview(url: str): + async def _maybe_fetch_preview(url: str, digest: str): if not url.startswith(("http://", "https://")): return - digest = hashlib.sha1(url.encode("utf-8")).hexdigest() glob = f"{digest}.*" paths = list(preview_dir.glob(glob)) if not paths: @@ -1075,7 +1075,19 @@ async def _maybe_fetch_preview(url: str): except Exception: return self.preview_images.append(imagehelper.ImageHelper(preview_dir, glob=glob)) - await asyncio.gather(*(_maybe_fetch_preview(url) for url in self.previews_urls)) + digests = [hashlib.sha1(url.encode("utf-8")).hexdigest() for url in self.previews_urls] + for img in preview_dir.glob("*"): + digest = img.stem + if len(digest) != 40 or not all(c in string.hexdigits for c in digest): + # Not a digest + continue + if digest not in digests: + # Old preview + try: + img.unlink() + except Exception: + pass + await asyncio.gather(*(_maybe_fetch_preview(url, digest) for url, digest in zip(self.previews_urls, digests))) self.previews_loaded = True finally: self.previews_loading = False From 6e21b8585dba88066776466cc06436626c9e22a9 Mon Sep 17 00:00:00 2001 From: 4lexez <66371974+Crykon@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:20:47 +0200 Subject: [PATCH 25/25] Previews proper width to height ratio While checking the preview, I discovered a small issue: the image was fixed at a 240:260 pixel ratio, so in comics (for example) with its vertical 4:3 aspect ratio it appeared "stretched." Now all previews (squared, vertical ones) display normally and logically. But needs futher testing with other extraordinary aspect ratios --- modules/gui.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/gui.py b/modules/gui.py index d8ff5c1f..95277338 100644 --- a/modules/gui.py +++ b/modules/gui.py @@ -2297,8 +2297,8 @@ def select_callback(selected): # the row in a child with an explicit horizontal bar. # Without a child, ImGui clips same-line items at the # popup boundary and the parent only scrolls vertically. - preview_width = max(self.scaled(240), (out_width - imgui.style.item_spacing.x) / 2) - preview_height = preview_width / (16 / 9) + max_preview_width = max(self.scaled(240), (out_width - imgui.style.item_spacing.x) / 2) + max_preview_height = self.scaled(260) horizontal_flags = ( imgui.WINDOW_HORIZONTAL_SCROLLING_BAR | imgui.WINDOW_ALWAYS_HORIZONTAL_SCROLLBAR | @@ -2307,18 +2307,29 @@ def select_callback(selected): imgui.begin_child( "###game_previews_gallery", width=out_width, - height=preview_height + 2 * imgui.style.window_padding.y, + height=max_preview_height + 2 * imgui.style.window_padding.y, flags=horizontal_flags, ) first = True for preview in game.preview_images: if not first: imgui.same_line() + # Fit each preview inside a bounding box without + # cropping or stretching portrait/tall images. + aspect_ratio = ( + preview.width / preview.height + if preview.loaded and preview.width > 0 and preview.height > 0 + else 16 / 9 + ) + preview_width = max_preview_width + preview_height = preview_width / aspect_ratio + if preview_height > max_preview_height: + preview_height = max_preview_height + preview_width = preview_height * aspect_ratio if preview.error: self.draw_game_image_error(game, preview, preview_width, preview_height) else: - crop = preview.crop_to_ratio(preview_width / preview_height, fit=globals.settings.fit_images) - preview.render(preview_width, preview_height, *crop, rounding=rounding) + preview.render(preview_width, preview_height, rounding=rounding) first = False imgui.end_child() imgui.push_text_wrap_pos()

c_!dQzUbs4k#K!iCwz73g`yxA z40BOT%p#!*YF~JP#gP)!{TC)!Y>L3l``9qM2|yPub|E6qq}YMh{9a?Rj|km71Hf;W zs5M>5a^uT$yskE(tNo?1_{HtFd*=6ImjW-P%d+`lQV=i7dQC7#A!M@!4nu9lNN*FeMY_YGasiL|peM(e_ zbcYylvkAP7c#Fs;*(!)CXb5M2ix$*el!cDKaZoT1!Y+un4?oyR?sq5)ZC^+4s+v5q z^?~oiAq_nTznjlO4MptEatybEV~ikY$#Y{s>u%Mq3@?%BY7pr3DQM#bdP#j$Q$)?u z<2otf7%fqL9W^e~_=rr?Qap+R&LPLWR}LN2tZIyQGws+z8Ryn0->lTYF^Za1kEuq? zQjF|#+#2;~t4ydWX;DkO5Q;09>LlFdJ6qp<{^R+kV7c_(d1;b=8 z6!3Dv#eksW0s()39L4Jq?~|D&*JC_ z4#Jp3F~b`O`)4CS_XG-*SSf#{GhI@?Vq7h0N;|4n_N+QKr%S6>j;xk$!Ep0xN%JkUuA=ClD4oFw zWNvreuu&%a((rmMWqgAEl(RyGpWWPZkbaZy+0R_-DFZNPQbEKgMEAddOfp0VL85z5 zIZB>7>&et9C8r9emnT*^nmqN2Q@0zYt#O#tLJ8Eqs86TL&Fq5y zK(~+fs^?U5YOhKXHU??8M#)s>f$6Zeaha&I%mNsTJk^ijP~^E>#sZANmKOnD<-T3O zz6BPO!yIy1!~I(t&M-&n?p$&h$_BZ}qcM(>B5@-X5|n*{h-7dVDHw51pVd*-AmZSk zBqj~2a(fllhQ>w^+2r;=iS2irHmIH4#tma;#+TYS&(G6hGqgFAGfX~tZHCb+RvHB?YlN7aIuh<_+h z?o-!NL7Fgk%V+juK@LQoDdz+WTp46CTpThZ$33ISI$^CR=x2Q#Hy>gBZ_#WwjO;MG z1AIY`%fT=%RW)Q8B$x?5mO@#a@J9tI6D;K+Xow`~GLr=risrcKdU5-j zzGB6m($}sP+b%uz!c!~u7b6$dX=CxFotJiA+?jsp;djdZ>g(65Rv+s7vq9ce6F1eQ zOs(nGhptWjRogpLtE~sJ!CTgB4&K(3ur*!rz7tB?dU;cC%+#BN+N*<^0)Jxw<<3YH!>VC()4*mUXH#NAOeox))fXDmg^q^7y zeuWz0>YlQ}GR+5D>A_Wj}nEMP)PXT&KW$8$@Mz+$}02M|ut`hRf(L zJ!`wZ+^p?~xf*7sEboCX15W+_d=_^zV^pWwt-0?^u6@v%9A;;EE{C4aIC*x0^>q!- zX4qqnZl zVdPof4Sd%k#azdjw``QX9M|kCp6_&C1ja1#&r&H;HsdVx8%+2+4kSKgmwoNPE_LV| zyUXD5em&i1(7)fHMtF05pGEUitr6g#me74h=BG9-!WEk^e5V=U57cy@S^EKl;U5@j z^v%S#X!~4-4@&92isBE-iV&_>q2FkLI1^B~?BrF#%P?8`R6ezjxqE@`rgyZB4HKrOZg*>78vtog zw=1(nFiVfog*NbHx&@_rk9ge#JzdGmOIQasqN5W??N) z<%}No6bfZGKLZZdrCy>udK{6ddaV2X<>^AZ=5pW3eBMpN$jReTsK23Qa<53~`*r^9 zqz2iPvZM{Q%{UnI`}Ua$ws^LwJO+YeC3*SDoL0&WWIjCaVx*0t)0C3C$~9B_K|Te% z5OWpVNk-=y?uSLK*6TEv9Ub6kb_rrMOU1Hm+ix zkTTDyQ#NLe^ga8`#++3t^2EK)Jg1zYa>{q+MTA!vcb%dyP@u|lBMo^0@G7kuab-Qb z!TLQ6V=lTewKRVIHi8FD(wC8D3CcbJuUe$HkyZ)HK9#gsu(u13jG zZ<%&-`uod|P0}&x|pO zR!UQVBFV^3#Y+Yp!F|g-*RJ4ar&aCp3;|fJ8OIGp;yn8CE(w+}U&iE~JdSoiikW=^ zelP`FSr9`#@(h#HBZsp2Nl>;&a$-V_@?8#(?#us5QNW{3+f-v3w^`!5a{DrKNY4E6 z;MB5FT?$C-;(T$Rx)ewBiqRjqyEXUd7sXghX>$4nMd&;Sb+K%dY5SgY(TjOF@Go-@ z>Bk(bbmny}yaETeZouCf?oK69-~;imf0=s}9r!urf|3o!=Kt5Zryy5$+X`~<2j_&p zU%A2V(J6T3#&Uo*qKyB~YxXt#UT5FJ^5G!`8D+qG%rYCzc>kPc-+Rt@_nm!}tE>l~ zeUIF~2ghJH(&ZQ({>((QPJ9y+k-2bmQR_U4Cmo>5pqUK$rnulCSc5GZPGk-^_AZ(m z8ym#~2|V5)2NsKlk<77(@-uGqlB0}8owFB}NJ5}krI`=|u;wI@;aiJv`cf%I}FX-%m0)Tfm!g()%{u_jV>QK~+1$jxML7yf(I~Vn0YVn*0SD$bq z13Eq^2eYI2YQ>bwyhs-c_QS&?Cq})a{ijB~Bi$a)$>Z)mp@EbeK@FcM*#?Y+y}t9l zU}I zN(9;oGM0h~R0v4VV$Pt6DdLZJHZ%u14AM8~bypD{IzyL}sIT4C-wP-qHIK~HK( zZP_ze4caU2bdvGVO9;WC3<+8Y=$4i;dRbnq42;%hScSi|RF_l{aom7&oo_0dMR`8aqC* zICx8A!qRwk?#-v)c>2oBJ3C`ddt<&}Z020jckYADe9se!o+o1S7gCmm*y7W?Sx1Z+AUGZ|)wZ>$5$1?K?d@OpYXk}l@ zx&565_^w)bTBnO!H0F+RSS-1~Igx@%eYS~*|oidVW)mAigvuUd0B;RG(>Xu9g=H}6Vp-gVuQ+&q+W z9KJ`!N<62U3@^`z9&?lt4bBMe8r(e#i3+H&$1?6 zx#?=@o7Hbr^Ywe;^?UgGeewEztCjn%pIfasw5(aP*RBMvdU(4lVRx-LDtSkH!qI;1 zM9Q%p-Yy?`>B!1l%CVVzX-hcTQjUi{u~%H~da3K`jyJpB=(^JJjxOfh{jP?$48ef3y^0}_qwafS-<5~1osT(pUJvm0zNEb`T~oi@Pr9ZdVQolRn-o~WZ`l*yvM1KO z_qr?A)R(mMzw6^IL#vjr3=LwBK9TZH^4>_?8{xg*jC;SC-1`TgtJ4dk;Dg}7-DSg= z52zkRL>N$c5fSi#i16g+LxfD7_NJ`OiZQugotoSD=ADV=ov~dMDK#dfiXoPe@k;BEt;tF501ML6X8f-W)_<%0ge z)OTM+_csSYMeHQRVlChaMTFNA1}SL72NMBhFmiSz=!iOfFd}F;P)D0}Y1lu)l+U9_ zPN9%Dv(JIY;bDT)dFap~q8>&jQQ}4M+zk`jt8Iz7_9Scf z^5uKst+@2k%nLIsy~$!HuXiT&&UFi%SFJyzqVx^xeQK(7GiV}I?E6^n{FFNi_5WjQ zubzH|?xmPN@3sN>o~so719Y#7dGBBq!0%Vmy$thyRV~81=w2=J{%!-nKULA_GsM@T zKLPp44z_p1gX;ePz?VI!o&xm~IIz%3xj&Kt;FyI{9yh_pfE1;$3K~q1`A*u9%BY~B z2DW(A(n5|`)WZX#pgo8u5u)k}3SJ^2I1qxPBTCdA1S<`Y>Y^DflW#%z|9~I2AH2L% z%4Xi!95Xhr7298&x@5j+2GtVYQWv+>@s?Kj7qhf}Y_Is(Xj#`$hO$pNEaKUY?rQoS zy1SD3K_fu#m1@AxQCYwa$KTDp;BBPTn^nTkHyB_mAvs$E-u;vo{wI8wZ367{!|N1y zS9iSq$X|95u(09+soqB_tX}q@ZA@{rCvCwPkD=0t|#;Z934V__dno*Z7hvj zdw-2ZQfN6RoCBAGw6>PIY#cu>p?em;Op|kX{qd8-Z zm_^)OV@+uM9zAUAl#hlA*2W~^!aiE++Ksr)9dPicJnVxysYvgm`Z{rNR18O= z`OaM|r#lm@CDCqAPkOIK8A_$sx9gEfz;j>|| zf&kM7ZWc_j0*jl-qLc&i-sua26-4gm4m8mThEYckAZ8{4=@6&UKxYok6$tZzegoCK zC<}%Spjb@~ffo&(Qk-A^Z*Z;>aUn#^8;ZczfsJzWL9i$5hP0x3of;Nd#OQ%-#V|w@ zwheRE_~&M3L38^|AUqNA2et{iGyVWVf>wMIs|bNP6N!XiMx6}>qo68|C?V*iN+J3+ zXB6=&r+u8)2R2vGs^A3Z@WJv*tUHTZVuZ73pjY#ZCQ#Ah!1D)Ins{Sf!dSOvD^sXe zSZtT(pPyfzPg?3?joVX}?H`xdfcPK>0#l6xUv1mRTe=dKF8I1;s}Rj88q(H>*F7g+8)r-_ZkZ^1HE(N3*cz^EPujNdwzjyf?b@T)?Md5y-qshl z^(Acs&+BrmFF@Vn@~&_1TG@GJThiXl+gs!I)@yr{_MN=_K+JyN`nKy$@6xNbp67MH zvN+a^mSx>#^S8|_XOq^}Yui?>uq^A0F`ZdHig<<&yOQwHCIB{K^P&Y9vE5*IL44Tm zP=m(&{W$yYAeyX>W}+?Y6nIxT^2pW(A`yq!_#FIh;`T|tV#sOvfCDTb{sfM`=y9w$ zaT0%k8Jt$m;CE3k%*W#yl@3&bQ1Cm0lvnl%N+4=U6{VBPDrQKf^@5#-q!gB4G8}&6 zMhS5_$F3+*=7q|&ge_g*(0_f>>!m+dz%uY4{x?vWAw7)G&+N zq;JtIN)ABxx@oZblqlO}1U*;WRbWxT5UnCaJuAtkGOK5W>`f>kN?1s5UMU~kF($N@ zC)I!w79t7zEP6=5xk^}M`$w{mK-&)X`w)lV3kT73{Uz0{ngr4StUF^ts83eFF+&{|$Keqb4Jeg@T$ok&HS~xf33D zQVtpgQ@~l%0?BrQUt?Y82GkZlUk0 ztf5A>W`*WviT0%VzK{)+6bEQfKDaGpHc?1zvQX_6xj3?Ik|1gR28*n$;s~MS4P_#A zHtn&^_m6!)smQI)Q3+6=+y)22%(h%b0Z>L5k)TDi{54Q$WE?SltkFSzz}(1 zCYU7-$a(!i7VHIJ2X@vMoel-hfOP`^0y7;!GkdWxK{Im};~D2!*xL0H!)O*cwP5lG zCVgPl=EW@o*c~8Dkq9&lwu1d@jHJs{pKKAJ0a-@8DdBwQ17IaiEL**VP5wuSPZ+_c z0Fb3%rR!R2vT_G+-w`wIxY+x1(+=1&STowe3?X5xhSj^#$s3#E#-=MLuboL6xAMjv zapR76rhdY`yZ6V>@CQ%C51vRKJef3};*H+8(VH~-mipEl)k-S_-mx?8*!jcxA1(f1 zG3n@8I`&JQW38g@b<=lESDr{#Y=7ST3BJ8*;wxP73RkkC?b_MZiai(2>Eh}u+AGts zO`WU7onWuV82@zm4~KcCBFlQ3ua-B$$rhlv9qK>t&Rt^HUBd&1WtBwe+{?{tD(LBS7El+YbKEO6dM-=4Un? z!qqhRzYo@2rDV+-+OfrK>=6cBW>3^1QGz8Gr;%Qu_~h;zeI1%wtx zd;^;_n%Jbt8i!zfsv41rHIoP)N@54iSoo84(_xcD7Dx)2uh&zj3TnJ>ME*xFpvF0l zGP=9%kdAhVR!G1istJqG3=69HGwi>E*aKj{qZKc(fb*xhNw~-YmlvS`7>DW!Cx8_W zTylZ*B-n<53jL$-8ex6x!T^dQ*aL9thNMi0n8j@;_Q&8!QVk(sdNNn0DM1p?rPhxi z4Z)2t0QaF*LkVwaN*J21jQ+6g-KS!YpXLqTRf9J+;ZN%gmvqnTmeqe|x&sE-Ptu=A z)N!Tc&9XPjl0_{%(~@9X)(t=%|7h?+%Flkqw0z3#h9aM}^wa~1^b`S!0Q9{U@ZQtf zdQ6)4D%t>kUrqPaG4C@vgiXet8pHeL27tdD&A^ha3JMpPDd<~}Xi!kUpJ3B~)B`He zumrh7`Dr8{^k3@}pLY`wq8w&=vmH|UGOFZ0%@QDHMB}DMAT-~(RpoGBL_t$hc}0*- zgJdGf7}@(FB%}qkZFPhJrE?0^&c`&{!2ppcp3|9kPz1;Kh;oHdaQ`SvnuRRJ-8wM! z)59O^kGPGyGLjRJ9L`h36;}WDsItfYQk}(l)n&>kb}M@b*#W3fQ#Zc>o+v58eGss2 zIJbdv%{Zw8dWEDyTp37i4*;1PH9C-;WNw%MQB8Tn1ltJFka*!j06al9`?!f<&S;*;F!aV|a(z1;UwUvhKLr)0O}*|skFuZy~9fo>Mm z-C_R(jHFCbdW4S%drb?M0#YQFh?)#3m{L9)}Sm1XG;em5$>&# z(4&xmd|HXVwrolXDyWlgmA+=5?Fs@@B85w-1zae9WC?k$P(79Uu~K_-dJn|$d3O01^i{qQ^&p<%K+#780kkWS3KZK@IEs6LIe7$nzk~OiCL$KZx{j zl;I>*1h0@5d_7f|9eA{Iu8Sm>Y|D_m@*MORxD}BK(qRF8cjdS% zazxoB&p|Jkzmi<#jrp8$__B4id?j_YekHk#+ZQzwqn|$PxlW##zq`<>3F)QU-YPjvvR&q}H8_f(|ceh3wD~aRy$x+w1Y|$}t z=x>%!v!S}sIyzDfA%T$L=G)}`I1JnB}mX$RMMD~fA;wmm2@gwf&_iZlD;P8 zlYQn$6Im7`M;JLRgECOC7Wcw|C*@=^P%!-&`Lhh%Lqd6Q-sP9Gl7XD|PRhYDkS$Ze zEep*>4mpVoj7-a*3~abUdPSr_mC@O7Y(5PF75{`+QcBIwBO#*(ZN0m z87*%=(}#qEsR1x+n08Shz@c6tA8X7!TZPCuol1m3Hug z7u_*^c%2>1PGO?I#9CG#haE;IXgfIP17~J^Qvtlu@(df92VF6Uh@-f;&jV)$QUjg1 z#|bawzD#nH9jJ!*ow!BfM4b&e-b5$}XC{lCVgb@_sQj5^Qy>B8QIa+Z+UY=WYC7uL zE1sbs4hmI%);I6PJwR{vG(|i&W^vzyoxxbeU?2e35u@HexvdY+%>~_zZz>#|Twv!g z9*05ZNfvXO_4)nK>1-6k99ioCP~FE4TYVVLxEapV4 zAz*lg5;3m(_(|_je|KNM+ar`HqGZZ4i9uo)6J48`1;I%6!@!~=9GK4>Ns*8Y24&9l zr86c(D)A0PR2UE}WC*7taOEl)BDmx?6q%h}VDT7AD3ONMI|G;>gsWEBkFYm!^_IPL zkwwu|Fc2-;NiaVEBN_1FlCYUbIKm#pk9ydr^OA4*rW^}*8Hy|j^ny^t!FK)xX|7N$ znx2s1@{Dv3d%KSw^@{!N5o#14`;HHfdV7x^_w;*5j*pxe@rZegcqQVmV8~cugaR&| zVDJHro=2@Bwhyam74NbI;}aNuKdZPgz|b zn{1z;8B0w9^q?V$-9hTy8Mk%b(J`i?TNHXlMBBP8VQRb9eeGPzv=s{BO-%_?(-rsI zrMzn}?ix&)hVnuW$6be0rXwWOnJ_u8mh$zx zC+xc)Fux~afgs86DabFUpAhYKzTWj}SIXK__}hV35iJXUJNW8B$)-rr_hO#=Ud@<5 z<&*~_aaHrC@eL#Id^qlWIAz_6EzC=Cx%j2xl+}q}GUii~oygTWe#`#Emi@^s2S3>S zVcUCce9zb8Jzoc#t$5E9t6QE(Sx=LiIullB+EKQmS*xn!tM6ej_=;k>;s5{45Dp1h{XR%!5N%%E=1;>q2O5%=0N(B z#MgQk0~)l%#*kzG1PWmP6g<3%KLRIaa2+}G6kKXdPOkBG<=K!A^n6hTk-doC^XSP! zkV`90XKL&k?1=oH!kCk2zakp22;zCSa`s|TGliVy{vpKu-4X>Rf}r$aD!w%M{9xQZ zcV+a=$KQCI-@G#p)X81%Pi^i=+UJs{IWTXrmM$H>qjJ-lP3v$rquF$4QpISt0oTUHT z0spfF(VQfgkBX`Ty399GdBBJz;OL9Cv&9JE7zjqPNbpBy;KF&3A8^Gm9f8tu!-D-f zdWX=v2A)tV#tlTlQkBbUW!}IDZ=r|h1_Z-J%OzYyi-slNLg;n$zK(;bGPK@0%zb`eE4 z+)+udcZzg0hza1`uu+zpC1cuDwo<)nYFaX+D;r{)cdl0Mj8S$1?^&(f6Qjxqe0;U? zc#LuoxO=s-TMFO3TDdz$l|uLx?`q{EF{Z zPzIiwW0^d=}Op zD>zyhDm+>gDmq#mDn42gDmhvjDm_{jDy!n2)H*tpDkYyzLlL(<{u-)qHaLw%+&CYq zbef7dX9mL7P?fXMnaN`9pOJ?duoi+%aWU*pU{d(}Fvh{1^7}(Ur_Q!XAxo>oW<}Poh9&_oTczLIm-Z*?<{ZAIx7&WjKep722NFgu@g)J zr_Ii4#MLkiTAs{yoKLob5<={klyLIdY)pm$aUuDpgw{ZKl=$RR4H0U461{wpui~7W zoVCEIE?$@I&JE7;R^C~UcTH%Aldt6(n}1E2^fmI5{@8?j)Z3_)3@3x5hx}J0y~7`v z3P|RGF~RHhoOFkV#{gz=2d_>JyFx6o(I9Ev0iTpH>>eM#;2yphl#HX}{tNE$prrQ= z`zM2vVInws0lDQ)`a`}E->^I6^G~`0f`51z<2~VM7SQj(33xNydCz%H*ml<1tWQ92 zXKU4O&B+hk**x`IvpM+*HROLy?RArD)Jd7{OQS9U7)*{z8Iw~Ju1o%qHz@IECB7@D z15|yz-7e(^h2fzIzh`RP8yuPn`No4Sfvb}Bg8QmB=yOlH+?TupT1qHH0y};|@@BY? z%-M7N%+o7bh09sB3;g%=%a)eKT|e5lZ0-!}Iv)Ur8|KN6I|H7q#)lEWtmho+uW=wJ zU&IY56oMP%w#Q+tiCLw@pr#rrwFIhB8-5S^;MH(dC}n`_DdV^RKNQcO;~eT+>E}8>#g9p46YLNsFvHI_3GA%AE*Sh2J&AqXT7}hg`E-G;dcsd1>Y%++73s}-CacFB zV<`u@^O}yYbDRfp<=kn0jCZOUw-wEGQ}b&|Bk4lkE1{b}4D|(mh7rFo;SMnrYvL%n zT<%Fs6L-k#atWJ}s0qKIjTQ8e)A5D($-CCY9!b>jojIZ-3^fgqb!%q0^?OCH$3JRq^#j_F9-yMgWgb(N~*Wdu+A0i z1XeBsq#yCr#=%l}zhOhZcqr0zXuj^GM zJL#F$4h1(gnj{0VCCZlN4i5W#u5oWD5XoDBk*}NFM)g5a(6fGW#<_^sqU&ZIbR|~Hj=5_N= zMRT``xjQ4dJEOU~W{$6z3&Oerd6>tC4a(v;S`w&fAKZlp!l|=zJ>4K`njvM5stBc% z@5saK1ghnTb!ymKHod9I>Umxnm;vsjc_^{yBnfT+sV&@~20z}yZPQL^sxTCgBi~#M zG~zD6^&XK2%D=oUr&cZr16j{T@u*zL+`)L@@q?1)abKf}eUvo4K1p*Dza#h!;OD@v z8^3cttm#_!1+2(|e`?YrX?>F+iURG$n3)blQ(zfrDs+P%dU6L=YDhI746-^lIykK z9Glaw6qd}LxxW2+{e0oPDQs(8vNW!k%(It%KkG&9{Twd$z&$I6_cLFAW;NS3*L6Mj z8xt>R?`G%D)qZ1aR=bj&J9}oXW^QoqXxLm4)>R~_p3>PwHP~<%V7RSMke!+&)pKf9 zVVx?hPlj9dL`Q@MWG$I|ldend@hLA01w(EjBpDFrMVn^$r$SiBD9T7d#DT`^N5=i` zP$Ms}sZ9vWl%COc<{d*^a0^5HA!pzV?vr;ze~%t-7qG`W^4ohG*jwzJi^0h4kQk`Q zfu;y+hY^E$)FLR>dDy^4q?I_0E>n-TzCTCj^Y>G z2@m|0&a$j4o;kXr%UsqK&h%i+;l*0CY^|L+wvt!6oL4)4bvbv-;<07xKE#;w!@B%O zhfAWF>R7Jp4;Na$8#QwG>pbp|n>zKvRWOa~bt+Xqv1;r2ockQSYu)UTL56 zlr%oSWIRNTdh8UNDUyENf7vTY8QoWgy#d-Upbws;1X`?1sSKr$HZ3k%u0{dCTApU_ z40lhX)#tBf=U&_JTGOjdH=M7Z`}Vm7U9@6zG_OU>+Y!mzA?EE5=k32$5Y0XyW_L%j zyQA4hX0`VWoF!-G_-{VZbC!ZnI9{K>l5KlIyJF6sIet%%*MAJsy!yQZoh95)H3j6C zWOX)af7-~yAI7JIOZ4m_qCSrsQ~JPjkS$J$nVnpWoE-K7iOkdS?diBOrMDjCxXyFR zqIaHG(}7yXt1wZcdC=NLA#Hdkd3atav zmFJ)6+1twnjGK!&vJV>ku8-k*-4F6$c*_%hix{gR)hgi2SQnyd6JFrxD;Qbui`)2E- z?V&?z*3sW)*)U-rca#qhiPj9!SLP-OhPk1{LXvK&pd}^!=1d_o%Ag4FPHho*@$?|wH}CMpo4im30QJB$qx-xeSI9Skg=r(>^iq-}$&?B(K$*xAhE;Q( zXs(Nx>z2&TD-D}oI&#Od;l7r$Y-W9i-J3BkabhjN5EGt5a={2s(h5_P*j*BC@lu0Q zuG`~r(MWPB!Z(aIYCg%Lu8Z(hLW26&uJBlSkD@Qk0$`jVX@bshf2k{5vDbb5NK99; zYO4}$wM(|z`7W_;$5P#nXx+}Jtz+iI#~O{k8Ox(kWa)cagW z!ic%^syrtXgy}q=ENL>q8DcI_2QZh8i=3nZ#@ryM$ZrDLA*}4Ob6^c$>p%vy)rmGx zrgE~J$pn{~j7wLxgVwAy&OzO@#wo-F!~#Tx$(eK{XBxK#bgB}FC0OGeH2c=LL7jpz z@u{E(dYK*!tKq2Uzlq<2F?foUsGmE66i?yX6@Es0LZ1mTy8f~OsTHr3_Gnrv-N>D& z!_Op3Rr~%{M&hFb7~@7JY6o|$4~~1i0SxdL@s&{}S*RpgXg0Vm3+_MwWT`=(*Xp$< zNtV=*L|B5fYB5REN_O7MnX{Rz*#$38yf|^aE1GQ=P4*>|9rP)~_P)vf+aQTJzGm3Q zzreTXUaZ*0zr}CSEf}})i~KhJk-5%@^}ohkH;hb$rUZzRg*lWj8G7W`^z(fE+N&k2 zU#Hp=4^P}ujlTLmc{fI1pZwM&3(BM*G1UP`!c11HbC@i9c@owqyFIzUB;(Q!_QaG# zZgH1ROsX2}DoJv>9FtSO-$1=du3O`YuHj}Z@)xzQK#!thQ#hZ*RxSyxz#`{AS_8dK z&Yox1z^2LB0io(zr)tAAxhqo%(~kOsx%E>xNbj)5DJ`tzcAjf4O@gE-N7CTnprNzI zDLGbgCtK2Dmag{y9Ly5LQB~{TF>eylJGDFGi$XG=$;4Ja8RsZpQ&GG^+6sXVI6IAl zc5rLlP_+`F$|bRIDc`uHfT1QCr-Y6x0{C`LHP6!F;@F_1Nj}xQW$)Se22?ks3}fzJ z1H;d$TfA*26ARM%@DAGFt#JyvqiD~R8Jeao9nEVJ<28}~JxUL8j7r$)Xn7RgvE|V? zB@Yi|FTf!=W<_dW$&)eC9nOrmuC(0(O__D^?j-tWE{T=0k)o%a8#=5&IC_*&^65C5 z3@ags3Bk0In6QE|UkPylHj(WrYc4)61}euF6^Al-WDNVfvhj%kgn<(Qxf zYL`rW5&ZC=y}cTM;2-7--ci4BRnjvdlJFG1ske!yz2okQ3m*4=Y0rAhSjx67$)hQW zU#47XlFe|j;itsmKx8-&emb~391@4GMux9G#F*o|H238PdpoB2nBIH_Z2m}ES|Hr~N$ zz1BeAxF`Z`@yu)7KC9lUGuVOVs zO!|;7G!DHcOip0y5em77$0od!A>Wqo72Ea(vD8~(4~U=s+1MqETg+ZQ$Z;Q z`MSr4r^ZPb;PYUcJ?Xt1-*bChvJ^3%M0qvHIY_#3Uoa%;(C$#Rk)~9Dpvy0WydJ4o z28pL|$+0YZbAXI&NOd#}!O+Om3-5&gl2^(MOhJ?+$LXe~ zd>+X#=JR+UN;i)9ho^!l&;(%xnYjd|5@vT3bOp!!N`9^jFf+h@!7$<*hl*G-Ga))k zq5ja3{f=byc!L*1{(wui+5u2zXQDlYI$$UeyC50Z_p4HdR}s}ih3uako)QE!Jn&&G zdr->ma!(F>$H%>%Zb9%1OtgUxAZZFo9vY3pA5eDs5uboI)XR0#GrZ#HG3=;ep~DPG zR=Hdkbq?zkS(5}2hdr)vm$FhOnbi1&=i}FbN$}g4CBK1TNLB4i#+a#m)s*wH`319R zDvp?nMN@smR39@ntkgHXWTg0*srEy2S=e0mW|3IE8~@?z-S3Qw`}^@9+26mKQz+&% z;6I$xuxc$5ts5fN4WhLs1cYZ(!5$?7fYIBCCzjC6TfNbu2C-;Qq-f7OXMggRl}sQ$~(yzq>eQ;q*{PW9VG-!HpaCN}QH zf4Fh)N^v>RwUt836tUHcwte^y+xD&2)W3Nt(sC{~G%ODJBSU_1C=?kAg|9ptzWkMY z9QPdGslnqCe@OFyDD3hqpZ3IdcvdZycPzBwZujyL^A^%RSxeDc5o)t7 zk(@2iL}l0{OWr*#p8psmq0D>r);=Tm7sjf-ChcD|^6(q8ncP?=bZ^sy%DOP>f?&}b z7hL8muW_Ovi!-f?!~j-#qYz&Pr7R^T1Z$*mDPMUd+d020IN|rh{?CX41ez=Y(Ny7k zP8KLB)&Gdc4LZ z`7-RbeudqZ(aEQzQI`}?3oh80;9*UJF#c6@#CYe`@4zDiaT;|T-nHs?z+|rFtOsFC ztcDrPOnq-w)uiJcg*Wc?z?5nYj}9(5B`0;twWV}`>z75Wy)UqwQtFbbzC_xTufQxS#ODuEq~5tbEm* z>#|?C=!Q{kqgmpwNIba7%RUd*Uc;EzH#!!QGB5jJOL-Yfd{8pS!(@%axK1qh&=s(3 zB}+WuqTD32@LJi+W&;W z=j}Ms?J)vsiA9?I2GgLW*>hkfQXj<~cyii$tZ}I@PFR{cH9O;!fJEcq)^*HQMW77? z36KLmSU9_`D1obeu!#g$1@2e401(OI@daTIc-6&5S20m)nCFIE$?@kS^9$i401GrE zpuE?y0R^JQ#Z7kdhQaE21$9aREC%;q@e4wPgLYgsTVL*Yp+hv=BWC-|;Z0O;$6fmFNrh?CC}ItCO;W`<`aj+y_xA^2GtG3Kvf|Sq{9okFIz<3i!AbVDeL=16 zB8z(LT42BzO!kTJF%qy%SiD0D5@0EPf_)W)*2iT+tG2#On`6gVtjr;nF zYV{JOl;v3~?6xEdNCc7?%DN%d7)v8ENI4Ufa}Ig3p_DHHY=*zivHF~JyZ%%F`t6zrEL zg}+4grOe*$$6ek1U7e0@$u>Oh4`MpX+uZn8+=?6~{MdfGkniwC?80rd=&zxrQjRK; zCUtTmONEIX#<0&PV{Vv)3`6!JX|bdW*x7K*R7&BNtuh*yz3z)L4uTeR`8@(7k62}k zXe?SX7Kz4^sIla(LWV0M=87Ah*Ztq}i{=(Al6Nh+qGiL9Wkbxe@qJs-YF3_@RlAf` zD`st2%Gz)@uSm?>w3N3gnzwnjYt>dP+8UN@4N+Uu?BSIh+sl_;xO83f(lggXuV4Ds zr3KB6XYLl2i$!fqMQw{+f7bnj?ptj?+418Yw`+f~;intkt@~B|FYABa5_X;uoo6G? zv*B~+#dBYZocq$U^Go5;aj|G3S~PJtzeLR6yp+E=mf!k*SyjTcg;pKKQJ7I4S{6tp z7Z=;fhBY&Yd_*us(y5Q=1C65@F4BLN5wxBZ6=q_UaWJ6u%@tro1G*VcC`@;CTCJr9~GPnUA zX*H|)?EBd%e3(1%ZjIaEHo8qSAS^Hq zfWa`S-2Dc6g9=_tN0abwQjy3+Qvi*)gOTdGjRLv2W}C*TRTj(CJ%KXOJ>`siCHSUv z+)*{xZ%7hMWhVpH-j@%(eo(Bf|4V^ZyYoX8V5}nsW?D7ZVKp!lG@YKL>|!AIJ4Ma_RcuJj-?;< zQ+|!sVFj3;G@5PrJ;0FU_G-ZLB#2T!-=8p!RCEYD4n%q(TE>|#jbH?*V1UP{_>64C z!DQxAB^|jT{*IoHY6Siog#~Fa{$_@|cJ_Mzd|j+yqdbWd#p5eYXog%10(-S7$Z7g9 zag}E3hy70Sa7zpLQ(5H!bU}_7x;jaV{b3Sk!9<8t_3LNUs0-PM|DT9vRwBUf#>V;D z`Ordtv~=qa_TJV;+YU!ceRw-YoGY`v6K0TO zO2Zow!RkhW09H3d$S$4H7zmH@nM=19XiPt`;zJ;j6ahL7>E!!$>GlOmdtSyA2yp$n z$po=em7ZoRhddMNPlY^r>eyf>xjJ+GIyy1u)F%~&syx>vA1R8%G^7Bf8-@~-G94e6 z&d9-;5HG3lNW(`u5|c}*3Dm&l$*>Y~n$&wLXNIbt3Bv-1U4c$M9kt1@651XQKwJi~ zBlU)mm%hgmt&O!3gf4Bb4)VK@-za$D&xBB)6NJg6szuO?YaQkq4vsl9(^s>QCZ>*YSJ$py)VgT5UO&qJgs#}(g^~X1z|$=ba`!U zt(q{o*qLsZY`#66)6dMf?JTmTrDeKqJ#dA%vFSYk_S-b+m-Cx0OP_8;z}W!bd61X5 zKtmuhBm51#X^q|917QSCSh+`oeT@Y&6K_lGnn{-k)|;ei89gUXbq=^r9_y3xeUky4 z&SF)%>9TNTwNzl z^>@1*ryOS-{c@?SAe=@c+hj>cIY~Ndd&z($1}vn2A;FC6*}+euWTXv?(`qS8RW)eP zQu;Ny;G_WkA7l}1K>Pm&CivC@(b~9VZG3yj_Yd4W5N_HXv+h}~tQ9LemMS~Kd%(zF z3{M2biK)oMRJ8KaE9N)pKi`!!xciw9MNym>nZinvd>0K?e%X)x?(89>#p~cYRNVs|5t*Q6A#S@c}6O*wr ze>m{8SSH*l6W%Ycd87RgI~F|OJ#eSI?Y@z#ZU4l?l=Yc*Sur_KGWTiLuA zfIQfEVJZ}&yAVvYUt_&ZhXjK@k9-OMY8xi~vC~Lr!vuDnU<4+KAqZR*$ox>srp@XF zzgzIINLYi%K?1arN;%2Tazrk66VMV5<2*9T8i#?jR1m@mjF2zjk`GPAqh)=w&f~+L z;~kVF`~|$m492I(t3NILlF94wv5_j+2+fV;Z6J>ZoluWZknkKC8Np$`g9i`Fr=Cj* zg1nFLEm(!XKf$;>YvWAMUuAXA^d$DDH+G0+n_dXKCyD=QsvHQ<*sGhu9#(aO6vAVReQK{=i;Ss`TpDbsJZ9%)1vuU z%zO-~=eE6c{6n)1p_C-MBUQV@m3wZXz(coD;PH1+;K`WzQZB0qqkQh!6>9}_o+~BfKQtS%vhP=MmOdV*VNLNP9B3zwe&n#0 z$@^SdSg;ve*L+@WDzBXq#y|JykRHV(&MIp77CJH1a-)_#9_24}3NBX|E~@o}uw3Ja zZ%UpPa;P_(aY!7yj@L54uYZhtLQ+P%MrRusclDhtDKPROkf1`~@eGa9|0bI%=eL z=?W^ZFK6`_ICc;7nd7xYy#m&M+K2ARvRm_nBiLJei4LZH>f(R zpltFL9!%zB=&pujz0yfeQb!VaJcwojngKqY`ScKB!yF7$6TGw%$8pIqm*(6nKa`_D zen#p9*RG6Qs9@q_Ux~qODNdQ+Vz*Ferz;Yp^}ucn*r660vO_J62O5Svn;O~X2V?Rk zN)|4{!(ku@BYlwclg*6WgEX`y(+Jdp;2dDNLk9no9@eKq2ijWbrG)WI9E`YE1ZRc7 zX0Sl~4u`mMppeO#hQS1}(eH}G3Wt#k8v-N937Wrb>&%f=bHSYFMiuM{+{2_+%XvBbh3sq3EEJGY$KvTfsT3Rcg&X(X+V->B+umPX{OQH8 zv;WU0qleDKiU#kP&cenaubcnKoHx^#97)P(NoW&ZWk`JSd69012YDQgdFGOoDk-l^ zm!>U6A?{S&3ec*<9Mj83!S#^FdB^27uo1Y!B8ypZku4TfaGOx1@D1{)L4|Gb)|+^= zJO&Q{oF?8DzVtqM*D&$$cysVA*BdStd?)m?uJ;OmRrSlN@Zran4?NByKY{RHGYU&U zz{<^;kdYxnBYtdRPNN~PbU=o8ockg&Fl<(MNM|&yJnX!oSyXQ;294@Bs4#RWg}L-o zHz{UFOnXRhAcae&Q{x=ZOD&$jc0?C|{X!rkIk}Y$AGW%QNv?`Z=ACw<{J1vF}aFSwjO$%VFxz?$-V}pG1jruT&olHWj zxB*kZq{6UpZEk8?-;}zf*}x~ug7j3A=rxEjpk&f3?~tbcJ;j{oU?q}>^NZ2W+&-q$j0@?kB)LmT zRR-gE6WH%1m+^qhCq@9$so&Fgny#;(ITcXM}WCzTv$Pjn$Sev zCU|uJ9xi_bD-8ox(!w8MkxZcudwObZDDce!+2bj4M4%V1Q3lzav&zYs;9N-j{!7|F zkf9wpNZ-iGk`R)rFwqKQ_YPt_fyp*z&((eHxw|>}WFI{{alI{UshZEfr`6apXN~s^ zTvpy(?=qsAmMzUNy(5uF<&wGb`jlwiuw>q_YRZB|`jV;mdO^%owUU*GTamJ|u@Tjk z#B^n=x<`k@0nP8@Esj6+UCt9^ftlAf;+J~E&=FA`F6yob@eo=%bN3`fz zH2>I2LE#PC^|Sx3e7Ru5+xqXDZ<@u1&PYS2*w7Ve=vpr5`c%sobn|x$N?&{8l_$i4 z&5?r5VnJ)9p!J7siDcH$SIEH)G--k{gSnQ{%Xv+6}4ayTbHa` zW7al=U(0+o^Ll&CT0>z-{9@K>s!aXnw^mFf-G4ROKeo%{{=v$&1z53^w^zEW5&7H$g{Zj0Hs zMD5#V2k)6UOWy4Mu&(5up3Ae%8dh=(;F)t-9OL&f==~5bP7GssF^6Hy5kE}>@qq^1 zzgh`6$hU%9I>hU69uV|deR!Uz?;4j_fBqZM`}H?Y>3Dx7{b=7ab9 zI&EmS7*`il<7kc}`wtNo@aJr?RES;hD8MR-AfZcvs@K26bLk$g!EqlmszvS~?wui>N zcBoVQLd$dsmIoX{8})dwFzH{v||E{ zxa?#>MrR)y^90%}Bo#%%f1%f(u;k>?cok?GO1jH#0aw|wqJ&!%gL?*pULhoOq8xVe zO85mq|C~V|BOOo%bV(!ZkQAg8qX2_>k%04GhKtB-G{}}7mWUZY)ipIM3)4H45qT$( zD7YCO?8Yp)bKAp~n(OTmOU+C-TUqE9tY@BoX6Bhyi|ys!7kWiYCH}*fO8LBySicMZ z;rd-a)80P(=Na#I{wymz@L2fqbISuy#9Us{H6C$|M+e5k!OP(*SC@m+pK#nkXm7yG z9MtsESw?yw@}#DpB2Q`_r$~B0&+{CAj=qKPxVz>w(Y$A2>vu=*aoTO!pK#iZ+4rH3 zDOxEgneF+|n1yp&qNyxmDicjL5mU|l)_L!;srl}j*c~xbBLgmx^$aU{#Vgi>tGU%; zZv9ej{hOD?jUADVP;w0}Zyb!~p2cyWjLdtPDjRaNT0&q?UC5*^2N6goux|x_oaAWP zyv|IWzk6`SE^QG>*e#27>2he;0hYN)jS3+kcqsR{a$PW9g19lb7b(^gG!c?Pnr}1Q z^Vwg`e&{jjc^n&s_fkJyPN@0VYvTAEwyxmXY@*0e)AENJH3I{UhQ5ITCaRSE#+`zI z{MgML!Y=_SWzgY8(l7;O&1X3xWs&?&3cW`j3B@F> z%&B2mI8#H)a|Yro(m-7S!-i_hCy%~HUkOL1ACspQEfMlOA^`#p3*rQ*d{H;M@tNIP z%PuozmrJrM1KDM7?7lJi?9?n}@d0^%M;-Z1?pvarm|VN8HJ-msiB!I zqpVbuOnc?KqV@}4!5923P1q_NpRmuaNie+)JC!;ag(TpBnYsfrP)G51Vm9H0dxV>LU%jr zq?1T?Y)N*clfGCH+s!i8Ge}&y3Zb%vd?&Bq#w=(PjC&XT}+yoIhtiWwkA5 z`9E{-D-;SS7AaDaC-M1tB;I@V*88^m?tSmR_nI@(%@nAg2fZhL;iRbl3qK^q#4V3% zG!%7?Izl-#d#JEzYQh@wtqp65sSE3fNr!1->ce_4wR;T1#;|di2{Xf{uxZ#FHV>zT z(<(3x-5$$udN`ff={*_4nc>U|%At1{97gyt4pY0%VJ@H?X$91*bygd;IxO&&j$h#{ zM@9h^&UR!vG!83-&H^*rp=qZbIS?i{9wWz**G@U|!CmVp0QbVUd#BGDW5;=V}0HUA#*rQn?;`vj{WW}5Q56u2^dl;0Af_g0B&IgV5K|X4fvMd?59@>GVM9>r zQ_$CfmSIy6`jtyt4+*03R$!@J>2`!mpZUIjf)=B1nJ2}Dao$5#iegCN^TSt)VmD!Ciz>kRZ?w~i?O##-?9m9-X?!ZHmN?x zG!AA|Gua`z+c!!!*{q~ov=n{~VfAv@O$LX_rAze2bkcuKcByD`7Ssq}g?0^1?FLML z0>7a-}7I!`RELri|47@=dxKO{1Ab%re@j70iPk z-(0{?5L$ZN&IYD@p%8fLJi!^kI6WIU>X{7*MhUrP#4T=bV9qn+7wFK;f=|!|1EGbC zI09<*l4J!#DC`Z)hZ{=-qbGFMKQ$8&^fPnQfPeL&{$K)Q1POB-Oh z`KfS-MSR5;f+iT__>ILO&f*AUaqtRe#JO=Obbzu2-HabJOy3-uVtqcp8=w-jkgdf# z6AI3H&bkS(nlpmttdI^t5Ivqe?O~@wLVo;H2FdL^SW>7Oz zOu=Gl^}AzlkDWK1Kg%&ii`8}SZ+~z5c|#<#f@5qyF=k$My?glW!`BVbvi3+``=yEd zdF?;8WL`2}NxPiJTkAM$-L+TmSsSmv_~F4D2l>{1uCJ< zYtQZaB&(3mYQLM+zC;bShRsHuKQ2hN``g-d!cKQ&OM^o!=t znJ;xk!O4hDjO4UA$`t?91Wr(0H4T@{S2Dkpd41wx8oE#hi{aehV=Kj2zMoliwI-Tb z5n(F+4<_%DH^!7KX6BqvdsqZv{ytO-P4eq~8M|vWA7vGy-ZWGU#&>DW?rQqG`ZTm> z<$#_-AJ7mB=iNt$be=>XIJ8N$f);269h(8spiBCW&QA+WC~zzco!SS~0(~?Pm~GUs z+2Ajd45a@MihyW+=z((`k_G9S0nJ0Aztgou%n!Q6Z#EBtOd;NoR085#;Bt;~Q1@6o^+p?G}az)=6t=8-7RZahb4eXY~M`5QpWEm&Jy zeSYFH8;)K2$=xVv6*T2t6}%CSH}xloiE(Fp{^nqlaHj;ltR zZaoNu?N)@Nf|^VaqYip9K{Be5nGXGyIE)bMDc~MCK;Tg!RtY>Xmk@YXPuV8dj${k! zN1C9VRnoU?l5}DI1rmDoRBPlok}U}Aq)TsCAt4>c6Mfg0 zamSN(m{$m^LMpcFfh^r1!36O(N_N>?AvG03Z-tPX<+LQ*B+mFmL7Z;ME}M?DWVS*g z2$N#z?1(uON+q0n>K3)sT^joiMS}3TLYa0h4=LsD&^a!2lcfd2L6}+Q!33EF7De^$BzsQWEInflwI8PzWI~ zu)x9kJ#(H(xo`0T9DeRNu|h&Im@uDbwz zPu$5AoFUuY-kCWeNBo`&uznxwW|WFed$A8}khd=!hN(Ivn8nDXUYJ2~YWK3Z948n@1Elr^gIL9a9*TurNa$>c@IOA5 zjr(m9(wu>*lc7yw1bTR4!Y-JG!{K1ufz)t3FH-w)J86Rq5r#xd#J{EX5NPkrRG3t+ zV3mjzcbp#+GUH9+j@LPxg|M4KzS(2$peKA>Fk!`u)=YN#sK?#m-rx=g+{b-q*aKKC zFu76eA=D0|h9pI!L9DyfYGaW!W!<2K44{c@i%4^}qYrB1=*z&ocaHiY-SAjT>kSVn z(3UI|lgS$kIAZ~Ctm2GS*Jk*-tz6yKsB!CJLCNPVKhCS-^IEvP7Cx_w%j-ICUd+hj zGs?M)az3Mx%V=E8s=QRhXI0+Ms{ElfFOuK$7uDZw`l#uib>u-=6JOTFm38rDy^ihh^ZE5$eto2=?_PdiG`oK>3u4rBS@qYAe8X0*VJqLz$2Igtv-)p$-tLcN z?Ox0);q#igyyok>`IcU;rI&Bn!?o;*=Iy=Hd8a>;H@28l#OE|}IgQur`KE5Jshe*a z;+lq{IlFF$?r0-9`xXnz_<~lhp!NDR-?p7=+s?O*aBU;eg3&vnh}{t>a4uHV@fGb{ zMLS)h+ij!Bsi|8h4h)dKY_DzMYQ=7aZ287#sLhEI&!(m9W8tm5-V*{YA#&~`P{6i7YkntR&HJJ~I{^PWaB|Uxu ztDcd)WF%IGO0&MDd&_x=`9k}Wi8z@lbLQ`a-WoW6<_i@|X~f9_V`(X!STm^1oTW@+ zwPG?^#F|Y(;yJ{cOIdT4@`yDbtp&tdNM&X(6%lJOm6N|zLae1!R_;<6v6fSLg-aF0 zYNLwFmMV$0ipnXtn)|!W53BJrl^;TW1y^WDmyH0&>oIyvo=i`L$L6W@RC%gBHJ;jY z)FgCfQ5_iSig(!x%9)v9k$;L5w=>g`vIH%6E;R<(p6KLK+7z|*G;k^3Bh))WM3h93 z2CXiu9hMW7YV}-{FeMKniLwJ8&uULv?v`?ZxGBrSDdAyZRH-0*${>})gO4sb=R~~$ zCk9&$Ry*>f3Z;ybk{Z2gYB+x?6r`3CM@}uN{t_IM92pjBt#Vl4kM(JLI6u2hPE)c;=>{q%s}pWjGv(k>MYYJuGq;P;wIabQH72$eU zNJWKLgO=4+QZ%dwBrVskmIkaIC>5lk6Vq5B-yUTQXdAiD^e*>>I#I0$sh%Vgu#aju zHC|6@8GGOI{Eony5B_zPRxz1(^z0|6E#H1g%Nw=Hr` z3boScRD;s4lB3kllDV$-R!MKwc6pZ3uGR>QO!JbIhipD$ZliKalI{N&oca7qVwd2T&0o=yIOGyY zwqMbb;KGSQ{fd?Z7k(8=E=khXKC~UIPpESYYV+x8wB>UjBQnHESXEv*OO$UtbAXiQ z+RN<6wQr)<9>;z}iX)qLk8GE0jx-6Lby0k3sZU91%H|R6b4%SPrz+Wwc%LfuUtSMV zsTCQ*i7flH1i5TFE9^7!mt=DpB;rmK^mSN3#3fz5^;f6PR`>NM)-cr5wx5t7l1=-Z zY?o{jcenOb{E2msq_s*B7p$}*2UI)AYJkAFE3;GM$OMsXX4V?aUVEPQSqWa*wEJbd zWV632*<{m|Ew|(|<`|Mwm26K@e~JD4XM_gV^%d$Vpy`&9ct&Vom078t0-6N(ybfr> z%6viM=j?K~r+_B0mgcZR->rncn-%Z7Ij$Ua2>rM#LX^6{>bN{5#gR?>>#|+40mk{P zWS33*IoU4R?7uGAWYdu?VU!i;3pJea{9$`jhDoyhtAXveB#2~F0ow)HN3uane_pc7 zrqtg+03Kx3BqigbBwA}_;3`Poc~M(GSHog{locUr@|%*){y#}J*<6w0R>3|}O|890 z`x7+&+T+-7$mY5!QZOE&voNH*D&p~1s}u1c2?`Y;>@B-pKR9VPwWH zHGWoY?OKI*w%?KRmQ7VCkiNj@lEw-8r;|i^} zR)|zqCD#d2&2xqbxKgEBD@3khxn3N#aSE%Bz-JAXkov19?O3`(t*Q0OyGm$)Q&Hns zOXR#)NPVqs;!?>mrsAdsMX3yhs{;B<1D2PRyfI!6Wh+3ZR`#$;uJ={k)F3HSK+=?e zq&W^r`8ps`n^70n2uZmN$qG6CV0lW87MB_1xm#Tp(A!*Tptrji&^uh|pl=|)8(kS- z7sGTC_bw80)5Zk#ysp_<;%$JW`K#-l}5Wi^v?h*j$ zYc?<&nx`SwqnmGhL9)E{GtuSpC4t&CDwua|+ca?8H+9m-rpUyipdTI!edGo0Niwl& zhoay_A!bCBn%E%7&N7mJyABFXizf~>ZXw+>A3hE;`7=|VaDWxGZa0X8;?AN#kH5&y zvdHcV8RLD)p@_)n3c{g6sdx~#t#>vs1t*WUwqZTbiyrq(}R9t z?t69H?^||=b*F(S_(P?IAL(&h!SXacPQ2qHer|PYHW2c`$wn-okTyPmpF^jhQ?1ZP zSX`O82fq-AcS82i74&V;%=)%>YWd=3uDF?e$Y4V#q7q=g37@3NRgbO8e6v%dHeA*! zZL20tE2r(Cs;L_qaytngYYB!0!|55nH*i`oN(VRSpl8}AqxWdlj;!{esr;|eq z?kTuGX4>Z!%9J&q;!TQ{2T72Tmw9pTg}uCm3^m_IwNacL}DWo4b7esel&_#UfT03|=4kN)reD5X%PGMx&sEWY`ZN zCi_j)Zjv;hQymY$!QciZQjA77%qd*cGraMfFG8E(_xwR~i`6TY|88996;QoV(*cEf+H+8voX zwpi10ed@y#H%{ED`-?ih-*LC!5jix;AM$XAJkdi_ymKnr@8$bXa{VWx{d19O|6?6h z^NL1{|9$6F;uBn=)$V`%q@;>3Y2!-TKBY7ONk!#XO-nkk{Zr^Ifblmp10|Z@>>1Ef zA5ohA3g#n?4(;@Y{#yM78E*&l7r2m8mCba4N;NYh_!__&xxdpSn^xI%;WN)3jo zCiYrG{|?i~&6@tUjE`H)Xy2kmeH)4Of)-=#z+C=Zn=??T`|~_>`SSt|+6#4H2UKpJ z3C;MSSNNxV;`v)cyshELHr{+Bwj+JoGZSuPNDH#Kw9o!8sF8;L4%)D_#l|NX9NDsC zplmy(RyM>Nx#9_p{3*l?4TJVy&rzS`6x_|Jeo#_-&HKRsU(#{6qyuX8s%f#L;@zIN zd%pImO_NSyex!K!cOIg*ALZ16R;+qu`H~@P+IDs85rh9eXGaL8Gbi0quu%YlQu*d!Yc*9H;nhk_urXsVE$Vf85v#*Ao;4CoX zE)P570dDz!1;B+o2o1w*X3FgehuIl8IO@YWE-OCW;gRSm-|Q?nrN^DbPvEcq2;)nx z7!YzKLn<*Z?o8AuBM}+wdl2itz%R5Pw14<*7z>MqHRty{)@sv-wM%efG54bTb8fgv z$^Ewb`mqlKHv+d#edpJ{{cDi}FY^bEaR-jY(x-o>wWN1F(oq@5HILD{M1e-?j>RQG zfdSk=xHff~`#CUN)D3r`Y%C$`{mNG2l!1Z(s0l%*;9B3E0v{3s(NuSqi3m=CoH~{6Hw1opU z#gd+seqsd8f=jO!#*aE*t2Tas+)*mK%2v>#JL>gp z7j`J;qpA5&I53BIa^O84?m3@--W?~9;L4E1@}HDII%1(#NE_IWn*-n4`;EPP*DkJW z7vHsy>)OY69pbtUMJBxw-*L`+GIDY*a@Zg13J~0sRPDH+@~^SlS=@ABzmFPj8l+Sr zi%az^uE=L4*MaU^=5Lt!j()DAU%J5JMXuw;$iY`49xwOGbYyxa;yMxQI7#YYv@u<7 zNTEn<3@4ByUJvUX5Zhn`nn1`0P7yB4C5?g(4vA*P9*Zlw4-gvcV}fzKkK9fxm|*su zp2q8N@uEmrx0r%!T%Z@ly=iijV%nT1I1$)QE|hFEl1T`8A^$x4$C#K7!i#BRS0c9% z5E8b|W|N7VvQ;~c(7LWZM&>mds`T$+9?!|Y(tWv`&#C2dYWbWUT+WVYPT%=Kg?lfT z(;Lm%4(qtzT`uPDk4I z-I<6MJ1z|@<`!HTxje$>HgdU*_nHE++~8tv;g!+Lqt`lPx%IzT%&NOt{jH{NH1Qh; zxs8MT#$j&baCGBde&Z;&aWuNo9%;5;?~G-QKgch+GWVsqYlD%d-FI@M`J6g=aYb|H3y;pZKW}RKM=3dFToN+Z2vsN!!tQYrO*mG&>%8AP-`1}Sgzk$#1;_|z0 zI-~j9?pwN7nZ&;b?Xk+z+OwgcwWGDwrt4MwmZLZ_)?k+c7y4wp#tQg}of}A_H_)zJ z>?7lF`4W_bpz?5V{su$@1a(LEH|D=I^TUU^;luo}n;UlX!*ks5TqJlVa`rV?30cq# z;z|e_-1T;sqpHkEwjaw_W zCcu(VIA94$wjJ;@u8BFQfYwQ^nJ816R8-am)QGATa#2}1P`wB+Gf{?wMIuM!Nq2=j z*4j6%iKG*jUqVS#NV?}>(6#r1LNQLVZZApx675HxU+QJc zQ~DT$^hTD5M_z-tkGmK%wa;55#$B;z>=xc1)vB=Ko5ObO3ab4)eQG;mA<&vbH3XDUrL z%5{zMT?e_YgVC@JuVmv|jhbm=0Cn7LI9&F>OCe%U!g@H)F~< zOF3_;<}B5`Wdmo~aI^8grT3wZvgFD0V|?7md!RK~!)v{&8Y)Kej zenmpW`0Dkk)@C*-HQBUR%XZ1;Ogij{I|Ae-5iJY!72KJhtw8qdPr;degxZ%;`xLdG zq4smsfV897U!eB)sQm+KxRx&H<~(QEf5OkVP(#uau(t-ryNtq9ct9bY;O4|Uuxz6_ z#hV}wWN!d>D^TCB3flf)>SpE57yoE3#`G#mAfooCMBY%*L?y`K{W3YMTgOm&7IJv) zUH0D)=CiJQtrcM=k$LOr1#0L^jU0Y{=s1CTrH(AG&7vqnVqF>*Vu2i9drw;{ISi}M zEppaL<#K>nApRQZUF_=+VjH3Nu+5d*0q`P4OV2L`;6_Mc512FQ!v&e*P!|v4!;bAKMQChYNrx3H$e&15` z#j#IPnFy5YidpKvZz=rZ=yGB`(H#3B>2)ryty2Ax6q_z-aSKV58z5;qMI?v4>dRW} z{|ljZVp$QVL0Ruq|4q|hHt~BWxxJJ8UJtj|6WcrWFIo;lQITB$th~4wthhFg!1pBs z@BEv?e|eZ6adRVXeq@>(nU0Oj$iPELq7sd;6+WND!Kn#?qHyC4t$4HU14skx$ZoT5 zg0>oCH-NC>W|Szq3XGi=7`yy;OWrOaRvAgNv zI0dE1a;CxVRYH=pM7foasS3e4B%7b41%FqE#>6XI%0aj`vx4!3a24(v)h?K&nrcNmA4T10v#nB!NKMtZ4jz z@2GgZWH9a;->-vSo(-7Q+`5_A#t1mE*cn`879RJId9L zk`K8I5R53q1_TfN6U4^uI2reVhQ_g|P(&0^r5h?c!0LD=`PGr2Mm~z7CVZ$ScnEv^ zY8_K$(ceK$V(Ewi>4bR8p}dCLyFn?Ga2&6lhFV~&lODS~PGmx)L|3Y%#G*qG#In^E z?33W4?J9hS1gm6%LKva(YhrY`gH$5KYr)qZQut0O7;g znWOM39=|dm(DW}vP<~vdM>-3&)&jiZ@H!Wj``bWe(PRt>)t#2 zVuvOpFZ-g0jzx~2jQCIS{?|GG>-Ubo9?kyj1><@8M`@W43d*m%_NCXZ4MqzZ&krwJ za(PQ5XKDPP`ucH{ON%ybk6CsQ=LXKwaJ}%uvKwVF3%pd3dvWZ-*nLacLlfMmFC&2X zPbcloDtD?UkmVccCOA&K4kbIKcW(Z zlpj9VNl2+6veglnJgii?6}Axjpd}&n0#h8IZLFGQM7ofRy;3|Ml+^KRlpRkk6se~g zNL77SyH?LvZRV;rN2|8pXSR_VTWB|9k4IGv>^nJ}Xlt^4CgF-cxXm@q2Il?VZreg$ zPl_LTg$SJQr8?s?Gp+arXSZ!bS1_FFc@*9i^0BSri#^@8j^G(Je-)28cxQ9Qui_B| zkrLPfQ~11|fK)EqI2*pSarL zaWVEh2E2d)amMZo!fU&5k{H-D7?Eer3VQq5@$hCs#j>BlpoCtqj3)(Ty%-xG+daji z91wYYR}@f7AKwKSXAn+g;1LU<01iz`$)m?E9EkX0dp63drTT$zn|MB(up#hpJg#F3 z=>VfROOa1JrG(EP({q7SK73Fsyx(&co;(JE#?>D<6aO&4%fslH4gLd=Es!Un5rVVe zZ830j20c@NrU*|%au_>$0@{|4N2joCY2(9kYH8#41a=X(pMH*o{sjg!jZ0%yWN}@@ z5eg?31s=Ok!gFLhiX3!A83Pv_Pk<*&$@i|AGd}OIZ)W;9KDFixk9f|^%+1fiG48}{ z?ShBqg)I3S(me(woGeZylmxK>lg@oZo8r_Cw4j&$TLk%2gbv<;6Js91uQc)an-hpd zAA1E|ERcVnB;7A$%Q-rw=h-X(lpLaLKOP&8SMz|!-v>u)Lm{L9VRj7(6{_A2cm_Kx z2FL@k*1+s+pMPc!546WcI7sI_aNOsc6}_>VLxT0R!iD`B0ht{79Ds8ea3oZevey#= zSD*typI@+yfj$Dqe0IS@tpekKqdsH)*|R9o;qms(&JuY@_CG*UBSfYXA&?FOOeiXDiJViHKdLJxvULWtl|avf zOGB;}WKxN63K3P2x~o9Gc1b$|W>}1g*Dr`86Bged=kOCuhn!b75Jf4ZC1i^O*@=$` zI-v>0p@|YXB0V#JZz#ga2x2zYJ?}@e@R2|m8X`XX9JUemboL!=sH~*(z9&LBq=fVo zCvr%H{m&QyMOVe$WfVPRQDBE{MXeJxWc2I*;||Mwe{c2ABZu7;KwsX@I3j|mTRZ(6}QEh z_IU71v)4A?%WsM?&5LOTSIvA{bu0~pTC*;WTo{4C3-^jQ#w?wwe!QiOvy@$(z21GV zd^7Mi512fjX}rrcUatj_pvN@4jQ**aO3UTVw!3EA*K^)4e6KKSZaO#cAhYoEpE)=1 z<}e&@<*n76wVJo~aMqqb8{&J%xZW|o_bAtUG%__C?VXES{gFWMp7qspdmdyJ!wY?P zv#O$5H4uC6gEZ^Kp3n7M3P;mQ&kZipMtn+{r%O4y^y);6uKJNNEwOr7bQcQUb(gXI z$Y_o`Ud;Sl=GC0LOj#mkAx9U!Q^Xgw-7RXn9{Oe+H$ul!JqEqw(g5o?T^tTiooR@T^^-vJl)CBoj3b#zIvEYio^Ism#t$0#rX6>icHGu| z*Zh&0-|6IbI{BS1aXVj%HciIpmsKOyadcgjZs6%2j_$eDck9*LmA3)H-Kz%{ivyMX zpuU5z@4Z{!d&_m(`Q5`G9p;A)azh9Cp;x$}SEBWvWY~EcVTJMXps6zssX>w%eu*1? zi65TghNq%UUbRT|4;nY}joa@wZohTvcIdkcA1&~^4|BT@^Sh67yN^a2r_>_VJ*aEv z>$csk+jgt^J5Aqi;=4zte*ZMLe>&Q7TrFDT>h-meqc`5{{8sljy7|ssT<0#nbCl~Gc)u$zb@O|2SDA$h0BUqlfjCX^hTa zEU$d`#M>wM@(!-NgD>C2m2ZlcZ;>h@pQH0*bjgp%#B`W1?1&Y1z=2J;HAuW62##%< zEX%Jy>EW0jo*Cenfym&27;|v3vgZBb_lo(-POh?(uiVO2ZsjX?a+N!ymHnw%m9Hmg zRqgvt?=|sNU0hWcU$u>^+QwJ)b5;G(s=?G$D;8@T-aq`_VZL??SG$F;-NDuF;A{7B zwfmyABdI~F9@B6Y4wMcw165Wp`o`#@Poyab>qJCBJl(?4E!W+A>loKM#?g&1jca`kV*VN&o5q-fYmdott!yNGCPtry%y_zlqf4$1 z@TDDGX-BkltrL(!Vu_i}fiH~o-l`~j1{$mELG?8KPT$J%TY9RCIFty5P%d-=1UqV-`NRp0&x zC(i5SE*WF0AJlH(YhSoq`@*fM@0|Gd34Xhs+is86j;98zN>q)wXN6dVXEt!mhMP^v z5&7+hxb25x%wa`%@!}sMG)<1{^KCo1ww-+2FxNI5WA-RQW7~Q?P?_;e&U$R7@mXXLs#I7QBQp3?T;-KT{E{^WHziBE) zdlQK!2d(GmdJ$Rk^cIfZa)0Ygj6R_n6iFAJZsX`Sp5DySn{S?q(c1yb>0dxJ@WRgw z8vOx{V)!HVCQ?h+o0rc8BD7-JH*oX@p5DgM+itbR=sk~V*e861n}ti(L>a}J#94r+ zJ2<+7r?+zS*2uPfF?#=FEv(Bw!o_(72sp@-!H#Uf;E6y;DFggfMV}N^UNyr2ug+b} zEx**eL@~A^$h)}iYQ_?!t1kgGS6X|ud5O~3s;OT7r9JQoD={`3LhK zzV?T&-5QRz1OKxt0egZJ$7;S-En@jiT-ByquSV#~7~Pj>N7axxAKdbgkUJOCGS2NK z>+&oL7qX?Gk>mEjh7pAkR`+Tpd-ehp0pX}O4M*W%!=w6rx7g!A+DkQ^#6$i~5I z!Lk(PZL)Wps4n%i91tUACpZof+%ce8O?t*k?fN1`k^N#9?8>T$W2o+LtBGBx9Fd!} zk|Kd-M>zn6f|rBkQen#|6x5JjZ5O&L9!{#gC#GUpODfV40+&{@RZ#mb0lufosdd!M zs^{}zKU}?zu@ftlR7w)3OyLQivOhT-p!){~uha^4x3`b7C_U1cNtQ@taTP(2I}RaP zqJ=#GorEkHi`RBZ*JO_R!lwi5NzqX-kN2Mq`}$bcbC&%W(iQg$>Co}OY4#_Wk5MrW z*)13f*LwurN#9v=pg=rCT~pHnt2$&Ehdl~;6O9ZziH&|rjhR*<44_hN8OQErubJ*aIc(*zA_sfor?wLfwm6)U7VS}1+{Mzh5&R{}&R11sW=~G$|dM#;6;9hYqD&y#~t1!7% z#WC$~kG(M#qx0}<_>JM;5;3Y^oP$NhInQhgLdCH`5NZvn95p$eB(w-ZfGnQOXK~1_ zhW_4$K#B8QS&05Xdkd~M1S1vEK|q4!2%;MXa6B~Nbs16Ri-;--`GKS@+>ND`?)Pd* z8q`7UBo>A!hXIhJc9nRzzYl^v&`WULD{Cuqz1K33MFXAz18F$R>l65wvDz{wfBJ`-gp*bC6FR~R60btVZK zk_E7nFaG^~96E^Lk#f-KYaXKZ&J16^nJeE+K2iq|grsQrk&=8078LaWVGSHu3MFB4 zwL3x+eh9uoIb<3Fj=`-q(?3JXTDaIxjWSS?jy;gwnUEq&$jAOW02<|EiR9BC!-u%9 zq=9-M`=o*{I3I>X8DByN5{P(x1wx94G7@r6=>b37)q<_*h7CoKq*OLnia%*D>|4J8 z5!uYVi=Vmhna{t@XI65VmC?* zxqcvO-GuuEX&0Fb4Bk|G$$RPL`%E=y>G(`*Ws&AoOt;E;HomLa=k><79>tw5a=k0= z!Lb1-yc!a?XVLh?i#Aeq*j?&i z%H1i>xP(wfjm!XlEmjLz^~-pxW-+&DF~0;Z0{PTP=a_#+(I)eGvN4gxna3_|erND{ z-mTg@xv|pGdpV=vq@150Yhfq=8w47o;8H}!Hi<2KJw^{D7!PEx-f_Kq=`G{*OaheA0E#OkX_#lTE-wVek4xkX; z#N=NAzXBey@M#iGL_<;$unKUlBlCC{ASAqv*JJXSp-N;nBcIIX`c-CS1S_tB`lQ*M zf!SP7=4n&XY=-=xLZM{0M6?HhO;WHt@>!tUgylihNt9hREr-rUtFk;aTxNzc7z!n! z(Il2<+G$!ddxKMnLa9lWPbL^0HDO&~dE`<-jVl#UG6uT}3n9TS)!q|RF|H*QiDq!o zl1*lLka~hAcQh_DTr^8N^l@5>Sx1ebShqsiRcHrS((^(|cWD3@0cmwpnV26QfjQ!1&>ww{KC zoN4UAaFjyA!%2bFu9sY$e3M2#w3dvsAv?VI5$;gZs1j(1^%ZKZw;MSV*YSzxVNiBB zAwPVBVMifdQONCgp&mpctz7SvZok-lw2?f7frkYg_S!*%0&~TcL>$$ogn3si9g3~08<6&2B~LXx8V0$kq=<|-V^kAY$)9E;hL=CM2{p2d{lSc^?AdshFA}~r!F$+kFdh^N9-lVA zeSUG8utyV2JYMaE(E}HES4$CGCVQ=9mreOrLy00hqG?H@^8yy9#$klG5_2s_O;G$A zxTSMMdxQ#DfVYCOV}$J!LK%#8GDa2fm`cQIdopKalR2XW^W;knliudW$(L(J zQ@R4OHH1w%1s1&NSN%!>Ro)o8kwU3$Y{+bqQwJBwYd7j;Oe0wNBiu~;jsz# z#3B2T7+Qr9T8YgHt%uJ7U{wpz7V>+7uqz+l1H1eU3-2^8Q;0d*3nla5MV~6j|fzZ_?@P|eQ`9mhoAByr6rO%N)6tt`lFBI{xaBdN2QbCWW zeu6AqnYMP`F{B@=W=V%A9rZP z?!Fss?Wp0IE>SXwFrK)6LAo3nfP~%<&ww$#;#*GJ+5ZSZ;GYWp1T=VmOQZP#mHq=N z?FW?U2UG^p&9Ek*0m)s|GAK>c-%>S8mU>O@^@$}4jN2gn48|SbV>Es?p-I;aXnty| z*BF*2G-I?T^OLN+B|TbDNV90kNURK1R=H#%Rx?# Date: Mon, 24 Aug 2026 22:19:46 +0200 Subject: [PATCH 02/25] Remove pycache files --- common/__pycache__/meta.cpython-312.pyc | Bin 1050 -> 0 bytes common/__pycache__/meta.cpython-314.pyc | Bin 1124 -> 0 bytes common/__pycache__/parser.cpython-314.pyc | Bin 31432 -> 0 bytes common/__pycache__/structs.cpython-314.pyc | Bin 79852 -> 0 bytes modules/__pycache__/api.cpython-314.pyc | Bin 103292 -> 0 bytes modules/__pycache__/callbacks.cpython-314.pyc | Bin 40066 -> 0 bytes modules/__pycache__/colors.cpython-314.pyc | Bin 2645 -> 0 bytes modules/__pycache__/db.cpython-314.pyc | Bin 57442 -> 0 bytes modules/__pycache__/globals.cpython-314.pyc | Bin 12403 -> 0 bytes modules/__pycache__/gui.cpython-314.pyc | Bin 403476 -> 0 bytes modules/__pycache__/icons.cpython-314.pyc | Bin 4373 -> 0 bytes modules/__pycache__/msgbox.cpython-314.pyc | Bin 6122 -> 0 bytes .../notification_proc.cpython-314.pyc | Bin 9046 -> 0 bytes modules/__pycache__/patches.cpython-314.pyc | Bin 4406 -> 0 bytes modules/__pycache__/rpc_thread.cpython-314.pyc | Bin 10405 -> 0 bytes modules/__pycache__/rpdl.cpython-314.pyc | Bin 26354 -> 0 bytes modules/__pycache__/utils.cpython-314.pyc | Bin 25006 -> 0 bytes modules/__pycache__/webview.cpython-314.pyc | Bin 48038 -> 0 bytes 18 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 common/__pycache__/meta.cpython-312.pyc delete mode 100644 common/__pycache__/meta.cpython-314.pyc delete mode 100644 common/__pycache__/parser.cpython-314.pyc delete mode 100644 common/__pycache__/structs.cpython-314.pyc delete mode 100644 modules/__pycache__/api.cpython-314.pyc delete mode 100644 modules/__pycache__/callbacks.cpython-314.pyc delete mode 100644 modules/__pycache__/colors.cpython-314.pyc delete mode 100644 modules/__pycache__/db.cpython-314.pyc delete mode 100644 modules/__pycache__/globals.cpython-314.pyc delete mode 100644 modules/__pycache__/gui.cpython-314.pyc delete mode 100644 modules/__pycache__/icons.cpython-314.pyc delete mode 100644 modules/__pycache__/msgbox.cpython-314.pyc delete mode 100644 modules/__pycache__/notification_proc.cpython-314.pyc delete mode 100644 modules/__pycache__/patches.cpython-314.pyc delete mode 100644 modules/__pycache__/rpc_thread.cpython-314.pyc delete mode 100644 modules/__pycache__/rpdl.cpython-314.pyc delete mode 100644 modules/__pycache__/utils.cpython-314.pyc delete mode 100644 modules/__pycache__/webview.cpython-314.pyc diff --git a/common/__pycache__/meta.cpython-312.pyc b/common/__pycache__/meta.cpython-312.pyc deleted file mode 100644 index 10f7f90a7af8ba0346146fcb96433ebd46b5a7f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1050 zcmZ`%O-vI(6rSDfcH5CjCOA#F@gHh19-_gw=E+6yiok$tDP=mnbSCtY}+o=|8? zESAbDC>A5mLL4L2AZ{HgshB+q;UP+xY2wrksF}vmi7+MtL`Bt96ymVEz5|E$=6}QE zqjE&UQ}Sdsf%?ZZm{rsAM0j{CgOXDS_eZiS)TVV=$4NP<<@1^<=jm+0LW#KU_M{{H7g^u1?VE3%&#dr!?w+#qTmR6U zJQsN(KbIG-E(aHbU%h>sfz7s$fvv!HW-t73KRj6tPwso7RZq0+u6bL(c)RAOE3PYL zz9xvztf$sO>vG4djurgITD9IMH*)K_4Q*Z9)_1KPYj<{MwlW;4UXRo~{wL!H9O!+_ I-~iq4A5l99ivR!s diff --git a/common/__pycache__/meta.cpython-314.pyc b/common/__pycache__/meta.cpython-314.pyc deleted file mode 100644 index 664d97dbdcef2bd37cec9be98ae1ed7d2e78f1ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1124 zcmaJ6@0_S`0JH~QbmvL|b$s>EN)66JSD{;%|g`Of9<}feu*eiKuFZEdLlLd-qC7;BN(i--{ z4RV81SLvwgFwv{E+{NAIMMD=OHQQ01x+edN7jQ7q&ddCIF&vZ|?lVqTwQ0L9{HH;H zn`k#TE?09n%5-%SHro3i%w!bmuuD)I*N27{= z_M@?%xBiuGzc_|rPJr(O1WthPO=!0GiqJ^6#-Jrun%h>}He1(*-(||7v7On^Pqt7w zo;*-8!I~fDYSt$&9~-<^!EJL{I<5G?dtpHp1Hrgw_LtEd(<~u5u08<{LO*> KXAEkmBmM=;2=pQV diff --git a/common/__pycache__/parser.cpython-314.pyc b/common/__pycache__/parser.cpython-314.pyc deleted file mode 100644 index 57ade37a92f5f2b9fb3c2cbed1dba40a6406c31d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31432 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 diff --git a/common/__pycache__/structs.cpython-314.pyc b/common/__pycache__/structs.cpython-314.pyc deleted file mode 100644 index 7898166c4dc030d3007a25a99701ff5d2006b0b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79852 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 L8Xm5cr3e2H`qu6_ diff --git a/modules/__pycache__/colors.cpython-314.pyc b/modules/__pycache__/colors.cpython-314.pyc deleted file mode 100644 index fbc8993a176951ff833084f76f1f8b0a1ffbab0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2645 zcmc&$O-vg{6rR~#8z(kkFogU#B|soJwP^WKn<5kk2x%*|s9C_MlJUwIV_F&8owZd$ zTFF(ALZw#P15{205(g?ZDy>kINE~wLrKk2_2`r&42l}H%s25VI=GOGRu?Z|qfS&rK z{bu&fdoyp|yl-cpFDg7rK>msky?b9I}q78 zg2-Y8iP&56+Jx{7O9?DZ221f7mS(UlFjz`5tsySU2CmT^q=5LKU`!bC)>(iQ@e;pq zVSe%@(K$j}<#8Xw74_ zz%nV~+uj32Efe=iZo|QEm+8y-Z2KR(tve%eYVZ;c=_MGv#6xe`U@^h zL4HqTehsrqhn!Ve;opj1{8rJEcP6H|`>dt^L3l0^-$t#JN!oq_A zFl_ zIc-U^67H&!Hx6g zrP**;oE9R(6zCFWrQ0RwrbIFx39&Lzx+p{6965LndGT{7!GT(U90QzK`p%i!d2zmH zp=Lo{?EQK?bLN%o$yPqFvsv)qf@i}wdy#WOHq?~g$co2qw9dev zUAS^Zr!@?}OF#!FH0&5c1;TNJ69E6B#Wo+18$c0O1xCm=^(H`ytXqnv99nO>YPvtI zP7f@X)MPC+srJ?Xq+SDZwA{F+HBNlEYAc=UNtdMqvlVmAv!|EK&t+}rkREoRM`6+A zdy_$}N^@vewazrv)Qt61Mmn`qK>5!R`Cr*042FR#3_gZLHw{N(fy91XK{K_UC-V`J zXg__(ai)AGIUic+yW6#N`Q96u%1hZ3?YWVi-GUPrEOt(pb?Bs^oS@_lSAvl;UfPoX zKJ2EBY`6~m29iA8!_61Zfh_L+?E4KAIoLZccz3NXOFJH%B(OIi2^6zzhJr2#e|`&v zUJBp;Oe-FL>3dK33wNjU_n&|Bga<~y`}@xIJDzYZtaXn|dT_n$n^%7tpZBmjQ0AZ% zdM=4!vj_0@kH_A3iL3?&byGMnIvUWWNOClwVvga;$XytA_&ErLSu7bHQuyBhI}gLi zD|H9}s9`Hkb*)=VraIF?x^dQ&PA-?#X05efn(sOmej!u_IgGk+pVw>-`~^ zk$W?J-mKi8dE39-tz^5DtW`-}UadR7(6i`R41J}n)U{=-)fvf~>F@&bN^qIP3&zR+og9t(K~nPv3U8w9|Csyn*RXt$->G2 diff --git a/modules/__pycache__/db.cpython-314.pyc b/modules/__pycache__/db.cpython-314.pyc deleted file mode 100644 index cac6b890cf285179609721af1c12d8b377347a8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57442 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 diff --git a/modules/__pycache__/globals.cpython-314.pyc b/modules/__pycache__/globals.cpython-314.pyc deleted file mode 100644 index 5c89d46782cc2399809f90836f076fe00515e8ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12403 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 diff --git a/modules/__pycache__/gui.cpython-314.pyc b/modules/__pycache__/gui.cpython-314.pyc deleted file mode 100644 index ca3a7d1c6e72c99093d427a742de5a7a9c086817..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403476 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�&luNtJI4U2`I<=TgKF{KP5ggUfwNVI+ID5yz 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 e?HI`AO)3kQ0mkz|kA4ss9D@;TQJ+ diff --git a/modules/__pycache__/icons.cpython-314.pyc b/modules/__pycache__/icons.cpython-314.pyc deleted file mode 100644 index a1acc8b6ab8927f5c5ea23db250044ec6bc9e95c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4373 zcmbVPU2Gf25#Brg9mS(4@+eUvDa(@e6Umn3*a~V#KX5D8u_eclDB$F_G>H>QNo*+c za(8rPB`H`SZJ8iI^xzf>(4qv|04ibwd@@^foTegF0A-j=OD-I<=0Y(#*BPU7K|aKXPty?^^F+jl`A!&F$cW;D zga@_)kLFP~TIiTF&cU=_#AbLa>M*7ob0>@`=odofk+Dj&@KiVAEeoB`;_MC@QxUJ<|;xf za6Vx)3wXQGo3s-hL09b}gZ;@fFWLoop~KMCcX9p?9hSp&h)ttNbeX*CG_-ksj==qC2K4Gm(mK7J`1JW*HugqsZwi=X$yWRx{H%Wmw&XmfoGNWzG3h8t;@ z9Z|0_-&nynkJ0+9Y>#qveqDo!@0i^{2%pRgqTFF1GevoQ&KwB`~*f`$f!ct0P&kv2P{M?f@W@OZz00?-Zt+5na{uNB+*_Gq{+PxSEZJjVy- zx{TRIA8CrXjEE#V0&E;}5j#{4Hi0Wot@2r) z{}DP`vlgL!-(;F$xo?Bj)Q_3K%S!6}hEa|KQ^~%1Slb|xm z7(tKbm!(8>Etkw^QgW1(s1fmo#>V49Hk(tRfW_llAU-mY$t9r1O^uit_F+ms50$hR!TA+9|R`U$Ty+Kc1{yiGB=-`_2nOt@yj?a{UlCfleL5Ha^6B%J` zDJe{8oC&KjNjAff0qrLjN@Lr>^70qq$(KO}r5cdIbKn_d7o`B`tAW$Kzvi`pA}qmG)*Pgdr!_AT(ML5w^OT+pG#8jE z8H(&HQZ6sbnp^LUX?7)ZLtK@K9k2mm!Un8P)O0i0-gq{@rZGf3o=$2m>{dgIUDpE3 zu(tZFo`N(NF}%J75lL%KJWEbivtm-S;$0*hQW8$T6246&5~WU{W)?Rye|Aq6zKfd+ zg!@Ekx zhg@ILu@fHH?kl_Q3V(F{VNE?bdvB$(c(wAqq`H`_th}u*zO7!7)RR&rfBgwU>(n&; zFNCh^-K*2|K0?RO@z21A505F3}HNJ&#%Mo)h`|pRkDQ1t4zU z=6K0l8vnFY`?LI=K3zzc5}VdUgd^BxK4k_?0J#Dd(V$R`HLOv<~C3w+_3FIusLQ)Rk~>`wR6 zx3`hPjkg5OWjL4YFG2IXx5Oy?x^28B&Vp1DG9kRRA@cPvigl2$L7k?}Wp$j6P`Ktp znW829dXYw<8MJ7jcpQG=CeunfmlZO``vC?rJRz*fbV4CH4vI1h3-srQehqO|kZ^+y zk&i-CKtK2V#PgIzROkT;zOQ^VeUmF*{OOPHqu|$WM70px2b-l?kd=mU<==3B3^;Oq8CCvkeK)ejZ zH%}}SMg0YN3Z6e9_dihROSWY*@W|V9-`n%ZJ8<7SP@6V4U}-;BOI9XWzU=g7`ErTwb^)mcir7f zfK<3v$`iYg#Qijfi}ky6$D)&7XoM5gzlR%6GZ+%Nc;a#2vlZtH6ezLMVym5$kG2O5+ex&;`U& z5lW#*Yg25{+9LFEMq#$$IXYrL?ob@Zor?3gOL1*O=WVe@eOt4I(Pp!!L;X%2LU>(^ z+0&s%FxvESvwZ}k!<^9}D~3{k9;lwBwjtKdGOdWUw<6ImQc6Pr^&r*(BhK^wVOm?O zY+0gwin9iC&~GQY ztVj~(O=Z$TN>Lrk<(!b2QC&xqqL`c#c`U2xtTdNYWGs&DYCx7!lXKa0UgYITIA$t) zX&`r5^>JJ>lgTPch37a48$bX)nT2W|Jw&~fNPSJ}w|noC_SR9Yecz$ApE94u|K9hH z!OsU*!)Ho|&uXJZLDYb65jtWE+q6A4U{{nfL z+i>M9B8wSn0rYC=K@*My&4Mx$cGkAPx;u!LPU$GP#lF`B-^Ms7>>xc4#$c+V*1j=A z?RdFXVpiI#WBt1CLO+M%o~Nqg=?}1(O=1p7nIzti!MG=eP4kg5T;(liG0Ir|BS_Hg zK^D6GKb)`S{lit4*k)Z@<38v?2};M!S>;pXX27@u6!22xP8g&1I(iU;Hl1g237zU5 zG+YHYVRq{CW(%jmJ-ELXA5WNAI`o)*YjPa-=^V$p_4Q_(qvKvfqx-Q&6SPJC+f7Uz zGS9Z)J*m%{t%=uf4x;`#vy-D=qKvbr@YyffYSEaW`wiPd=m3>tE}&=MhKuevu|Q+P zSf2qJKo8u;3Fd;W-)jt6ZKQ_3)%1&QbF~gxY0_5kcLcDs z!g|<-$&-37?q$9DO{+&@=4}PIKDKT@ezl=&qHa_hv)1OiO_)FaEx>`nBjquB#vVa~ z?qe`JYhgb2eR<5Sc>(Dfhgh~A;tjt9kp+Ul%PHrQDIATVEACSeyLAhh=`4)fr^V!q zJoEdTxP0~e%rq(gdH#y8x3?Duc^pBcRr{+#TDhn?F7m?6MMc8oUTL=Ca1in;{3S)D zxF98MgZX2yzl%=`3J(6FcTq41f$`i-UQq3_av4HPpP=w_oK6{a2?A&yqCN;Cv7M<5 zj-pj}E-zo?reSj+7HU~_XrZE7pGt};4xh=%!WGz#fT^qqWT#a-0aO>nsM>0`oSfr1 zQONMBcZ#18GTe-m&F56=vf2aZY8&BcPUDe#RZuP()OBH$X$fypYucX9&&^#{86p9< zBJn(z$)}WYlIMlgn4BpUaEz$ALL)jznXp$GP$F zOXD0H4vlbQ(a|$%o5r^mwpaDVVI@fT$}$9~>5+l>otL)Y2{)ffb7Yn3BD?{t4H6Ls z>m=+v5_WafjkTuH=K)0#hy%%_h3RRPA&PPn!y+Kf?cOJK;l(&)lJJW!;c!F3_oa-X zZllc4&rA4EK&`Vd0A|ru{dG7fWv?< z8?3Z-mD~E)+WMEr*V}e4L;$Podt%M^#0_QDxBE8#-t3*(qHi}|99@hSeVx^9Zog3S z^;cYVWmo%}tG(#jS_yQN1HrXGaJhFqFi;K*6#_#Go-e!&_k1n4c9c6pYaOBWj-#LQ zT=a#2h&Mcoo~1q48epe|VZ7|b;)x~cTC~#8{KnPA zt2bWzWJh^-qOdzrZkQ-GOjKGsuU~reQn|Ii(AvMmRQ!SK4R1BPYk$vs$6M~+Q|R7P z^bc1$d&-?dYn?+MHLrK>TXI)gx-?1eEVS%gZd`Bae?R}>Yjho>JGqN6eobA3Ii> z{^(n|QW}b^x1U&Y06}H{t~LLz<;j(i&jLmMbn5C;J58$Zfb|EwH2FYg`K7 z^T92+<({wUp3lGGb+mXEnBRJcyn?jmBA6mV2@IIj6FTYWcr@rz;q(lCtvS`haaxiI zQ^Ik-$S1|>OasSF3zDqhClelBPIZG7h*?F3ywh1YK@C73A`&6R;jl!2^cN3v>K2RL zqmuh;#m?qQ3AvVDV~x)%Q$H{Y}(s$SC@ z16q-`0&1{H;_-T%5SF$>1#w(<4uYNm$RFlYimc@jAq*mUK@tX$cu>18>n9@7k+BH~ zskl>PiLmN7`)oKKIU80R&5@&Hqobi%Jgl~vWARWTJi^7p|A1?3d4B5fLR3-a#&y4~xcC*FlZwLsAxIh;Xg~@cn(7qNp!W z-F)4b$oFs5^U%gnkbZ>fem$~#%1m23Bd>_O1WIz0LyD`M_?ocindo ywCx3E`vWI!@4fFL{oVr8`@qfEpS({a%C~_f{;(0JN537Ve3WM)_#J|V$p3#YievQv diff --git a/modules/__pycache__/notification_proc.cpython-314.pyc b/modules/__pycache__/notification_proc.cpython-314.pyc deleted file mode 100644 index 5dd8ab0f7744ebb399449b55d2ec6356acf70e28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9046 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 diff --git a/modules/__pycache__/patches.cpython-314.pyc b/modules/__pycache__/patches.cpython-314.pyc deleted file mode 100644 index ac8194f0d1c1d2e9eaf5d7bc61ed2b17b6c5083e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4406 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