11from abc import ABCMeta as _ABCMeta , abstractmethod as _abstractmethod
22from json import dumps as _dumps
33from time import time as _time
4- from tkinter import Misc as _Misc , Event as _Event , PhotoImage as _PhotoImage
4+ from tkinter import Misc as _Misc , Event as _Event , PhotoImage as _PhotoImage , TclError as _TclError
55from typing import Callable as _Callable , Self as _Self , TypeVar as _TypeVar , Generic as _Generic , Any as _Any , \
66 Literal as _Literal , override as _override
77
@@ -173,7 +173,10 @@ def attach(self, callback: _Callable[[], None]) -> None:
173173
174174 def unique (_ , __ , ___ ) -> None :
175175 if (v := self ._variable .get ()) != self ._last_value :
176- callback ()
176+ try :
177+ callback ()
178+ except _TclError :
179+ self .detach ()
177180 self ._last_value = v
178181
179182 self ._trace_cb_name = self ._variable .trace_add ("write" , unique )
@@ -214,7 +217,6 @@ def attempt(self) -> bool:
214217
215218class _RuntimeData (object ):
216219 def __init__ (self ) -> None :
217- self .protected_pot : Window | None = None
218220 self .start_time : int = int (_time ())
219221 self .comm : _Server | None = None
220222 self .comm_stream : _Server | None = None
@@ -247,66 +249,35 @@ def __new__(cls, *args, **kwargs) -> _RuntimeData:
247249 return super ().__new__ (cls , * args , ** kwargs )
248250
249251
250- T = _TypeVar ("T" , bound = RuntimeData )
251-
252-
253- class Window (_Generic [T ]):
252+ class Window (object ):
254253 def __init__ (self ,
255- width : int ,
256- height : int ,
257- refresh_rate : int ,
258- runtime_data : T ,
259- on_refresh : _Callable [[_Self ], None ] = lambda _ : None ,
254+ master : _Misc | None = None ,
255+ width : int = 720 ,
256+ height : int = 480 ,
260257 title : str = "LEADS" ,
261258 fullscreen : bool = False ,
262259 no_title_bar : bool = True ,
263- theme_mode : _Literal ["system" , "light" , "dark" ] = "system" ,
264- display : int = 0 ) -> None :
265- self ._refresh_rate : int = refresh_rate
266- self ._runtime_data : T = runtime_data
267- self ._on_refresh : _Callable [[Window ], None ] = on_refresh
268- self ._frequency_generators : dict [str , FrequencyGenerator ] = {}
269- self ._display : int = display
270-
271- pot = runtime_data .protected_pot
272- popup = False
273-
274- if pot :
275- self ._master : _CTkToplevel = _CTkToplevel (pot ._master )
276- popup = display == pot ._display
277- if not popup :
278- self ._master .bind ("<Leave>" , lambda _ : pot ._master .focus_force ())
279- self .show ()
260+ display : int = 0 ,
261+ popup : bool = False ) -> None :
262+ self ._pot_master : _Misc | None = master
263+ if master :
264+ self ._master : _CTk | _CTkToplevel = _CTkToplevel (master )
265+ elif self .__class__ is not Pot :
266+ raise TypeError ("Use `Pot` for root windows" )
280267 else :
281- self ._master : _CTk = _CTk ()
282- runtime_data . protected_pot = self
268+ self ._master : _CTk | _CTkToplevel = _CTk ()
269+ popup = False
283270 screen = _get_monitors ()[display ]
284- self ._master .title (title )
285- self ._master .wm_iconbitmap ()
286- self ._master .iconphoto (True , _PhotoImage (master = self ._master , file = f"{ _ASSETS_PATH } /logo.png" ))
287- self ._master .overrideredirect (no_title_bar )
288- _set_appearance_mode (theme_mode )
271+ self ._screen_x : int = screen .x
272+ self ._screen_y : int = screen .y
289273 self ._screen_width : int = screen .width
290274 self ._screen_height : int = screen .height
291275 self ._width : int = self ._screen_width if fullscreen else width
292276 self ._height : int = self ._screen_height if fullscreen else height
293-
294- x , y = int ((self ._screen_width - self ._width ) * .5 ) + screen .x , int ((self ._screen_height - self ._height ) * .5 )
295- if popup :
296- x = int ((pot ._width - self ._width ) * .5 + pot ._master .winfo_rootx ())
297- y = int ((pot ._height - self ._height ) * .5 + pot ._master .winfo_rooty ())
298- self ._master .geometry (f"{ self ._width } x{ self ._height } +{ x } +{ y } " )
299- self ._master .resizable (False , False )
300-
301- self ._active : bool = isinstance (self ._master , _CTkToplevel )
302- self ._performance_checker : PerformanceChecker = PerformanceChecker ()
303- self ._last_interval : float = 0
304-
305- def root (self ) -> _CTk :
306- return self ._master
307-
308- def is_pot (self ) -> bool :
309- return isinstance (self ._master , _CTk )
277+ self ._title : str = title
278+ self ._no_title_bar : bool = no_title_bar
279+ self ._display : int = display
280+ self ._popup : bool = popup
310281
311282 def screen_index (self ) -> int :
312283 return self ._display
@@ -323,6 +294,55 @@ def width(self) -> int:
323294 def height (self ) -> int :
324295 return self ._height
325296
297+ def root (self ) -> _CTk :
298+ return self ._master
299+
300+ def show (self ) -> None :
301+ self ._master .title (self ._title )
302+ self ._master .wm_iconbitmap ()
303+ self ._master .iconphoto (True , _PhotoImage (master = self ._master , file = f"{ _ASSETS_PATH } /logo.png" ))
304+ self ._master .overrideredirect (self ._no_title_bar )
305+ x , y = (int ((self ._screen_width - self ._width ) * .5 ) + self ._screen_x ,
306+ int ((self ._screen_height - self ._height ) * .5 ))
307+ if self ._popup :
308+ x = int ((self ._pot_master .winfo_width () - self ._width ) * .5 + self ._pot_master .winfo_rootx ())
309+ y = int ((self ._pot_master .winfo_height () - self ._height ) * .5 + self ._pot_master .winfo_rooty ())
310+ self ._master .transient (self ._pot_master )
311+ elif self ._pot_master :
312+ y += self ._pot_master .winfo_screenheight () - self ._screen_height - self ._screen_y
313+ self ._master .geometry (f"{ self ._width } x{ self ._height } +{ x } +{ y } " )
314+ self ._master .resizable (False , False )
315+
316+ def kill (self ) -> None :
317+ self ._master .destroy ()
318+
319+
320+ T = _TypeVar ("T" , bound = RuntimeData )
321+
322+
323+ class Pot (Window , _Generic [T ]):
324+ def __init__ (self ,
325+ width : int ,
326+ height : int ,
327+ refresh_rate : int ,
328+ runtime_data : T ,
329+ on_refresh : _Callable [[_Self ], None ] = lambda _ : None ,
330+ title : str = "LEADS" ,
331+ fullscreen : bool = False ,
332+ no_title_bar : bool = True ,
333+ theme_mode : _Literal ["system" , "light" , "dark" ] = "system" ,
334+ display : int = 0 ) -> None :
335+ Window .__init__ (self , None , width , height , title , fullscreen , no_title_bar , display )
336+ self ._refresh_rate : int = refresh_rate
337+ self ._runtime_data : T = runtime_data
338+ self ._on_refresh : _Callable [[Pot ], None ] = on_refresh
339+ self ._frequency_generators : dict [str , FrequencyGenerator ] = {}
340+ _set_appearance_mode (theme_mode )
341+
342+ self ._active : bool = isinstance (self ._master , _CTkToplevel )
343+ self ._performance_checker : PerformanceChecker = PerformanceChecker ()
344+ self ._last_interval : float = 0
345+
326346 def frame_rate (self ) -> float :
327347 return self ._performance_checker .frame_rate ()
328348
@@ -359,16 +379,9 @@ def clear_frequency_generators(self) -> None:
359379 def active (self ) -> bool :
360380 return self ._active
361381
382+ @_override
362383 def show (self ) -> None :
363- try :
364- if isinstance (self ._master , _CTkToplevel ):
365- pot = self ._runtime_data .protected_pot
366- if self ._display == pot ._display :
367- self ._master .transient (pot ._master )
368- return
369- finally :
370- self ._active = True
371-
384+ super ().show ()
372385 def wrapper (init : bool ) -> None :
373386 if not init :
374387 self ._on_refresh (self )
@@ -383,26 +396,24 @@ def wrapper(init: bool) -> None:
383396 self ._master .after (int ((ni := self ._performance_checker .next_interval ()) * 1000 ), wrapper , init )
384397 self ._last_interval = ni
385398
399+ self ._active = True
386400 self ._master .after (1 , wrapper , True )
387401 self ._master .mainloop ()
388402 self ._active = False
389403
390- def kill (self ) -> None :
391- self ._master .destroy ()
392-
393404
394405class ContextManager (object ):
395406 def __init__ (self , * windows : Window ) -> None :
396407 pot = None
397408 self ._windows : dict [int , Window ] = {}
398409 for window in windows :
399- if window . is_pot ( ):
410+ if isinstance ( window , Pot ):
400411 pot = window
401412 else :
402413 self .add_window (window )
403414 if not pot :
404415 raise LookupError ("No root window" )
405- self ._pot : Window = pot
416+ self ._pot : Pot = pot
406417 self ._widgets : dict [str , _Widget ] = {}
407418
408419 def num_windows (self ) -> int :
@@ -418,12 +429,13 @@ def _allocate_window(self) -> int:
418429
419430 def add_window (self , window : Window ) -> int :
420431 self ._windows [index := self ._allocate_window ()] = window
432+ window .show ()
421433 return index
422434
423435 def remove_window (self , index : int ) -> None :
424436 self ._windows .pop (index ).kill ()
425437
426- def index_of_window (self , window : Window ) -> int :
438+ def index_of_window (self , window : Pot ) -> int :
427439 for k , v in self ._windows .items ():
428440 if v == window :
429441 return k
@@ -466,7 +478,7 @@ def layout(self, layout: list[list[str | _Widget | None]], padding: float = .005
466478 widget .configure (width = screen_width )
467479 widget .grid (row = i , column = j * s , sticky = "NSEW" , columnspan = s , ipadx = p , ipady = p , padx = p , pady = p )
468480
469- def window (self , index : int = - 1 ) -> Window :
481+ def window (self , index : int = - 1 ) -> Pot :
470482 return self ._pot if index < 0 else self ._windows [index ]
471483
472484 def show (self ) -> None :
0 commit comments