diff --git a/hPyT/hPyT.py b/hPyT/hPyT.py index 04767c4..28052f6 100644 --- a/hPyT/hPyT.py +++ b/hPyT/hPyT.py @@ -98,6 +98,103 @@ class NCCALCSIZE_PARAMS(ctypes.Structure): WINDOWS_VERSION = float(platform.version().split(".")[0]) +# --- Synchronization Globals --- +sync_color_thread: Optional[threading.Thread] = None +sync_is_running: bool = False +sync_current_color: Optional[int] = None +sync_lock = threading.Lock() # Lock for thread-safe access to sync_current_color + +def stop_sync_if_last(): + """Checks if any element is still running in sync mode and stops the master if none are.""" + + # Check if any window is still listed in either sync dictionary + is_title_bar_sync_running = bool(rainbow_title_bar._sync_timer_threads) + is_border_sync_running = bool(rainbow_border._sync_timer_threads) + + if not is_title_bar_sync_running and not is_border_sync_running: + synchronized_rainbow.stop() + +# *New Synchronization Class (Invisible Master)* + +class synchronized_rainbow: + """Manages the single, module-level thread for synchronized color cycling.""" + + @classmethod + def _color_changer_task(cls, interval: int, color_stops: int) -> None: + """The continuous color cycle task for synchronization""" + + global sync_current_color, sync_is_running + r, g, b = 200, 0, 0 + + while sync_is_running: + + if r < 255 and g == 0 and b == 0: + r = min(255, r + color_stops) + elif r == 255 and g < 255 and b == 0: + g = min(255, g + color_stops) + elif r > 0 and g == 255 and b == 0: + r = max(0, r - color_stops) + elif g == 255 and b < 255 and r == 0: + b = min(255, b + color_stops) + elif g > 0 and b == 255 and r == 0: + g = max(0, g - color_stops) + elif b == 255 and r < 255 and g == 0: + r = min(255, r + color_stops) + elif b > 0 and r == 255 and g == 0: + b = max(0, b - color_stops) + + with sync_lock: + sync_current_color = (r << 16) | (g << 8) | b + + ctypes.windll.kernel32.Sleep(interval) + + # When stopping, reset the global color + with sync_lock: + sync_current_color = None + + @classmethod + def get_current_color(cls) -> Tuple[int, int, int]: + """Gets the current RGB color from the synchronized master thread.""" + global sync_current_color + with sync_lock: + color = sync_current_color + + if color is None: + return (0, 0, 0) + + # Convert integer color (BGR format) back to RGB tuple + b = (color >> 16) & 0xFF + g = (color >> 8) & 0xFF + r = color & 0xFF + return (r, g, b) + + @classmethod + def start(cls, interval: int, color_stops: int) -> None: + """Starts the synchronized color thread (Invisible Master).""" + global sync_color_thread, sync_is_running + + # Only start if not running + if not sync_is_running: + sync_is_running = True + + # Use a dummy hwnd (0) as DWM calls are handled externally in sync mode + sync_color_thread = threading.Thread( + target=cls._color_changer_task, + args=(interval, color_stops), + daemon=True + ) + sync_color_thread.start() + + @classmethod + def stop(cls) -> None: + """Stops the synchronized color thread.""" + global sync_is_running, sync_color_thread + if sync_is_running: + sync_is_running = False + # Wait for the thread to finish cleanly + if threading.current_thread() is not sync_color_thread: + sync_color_thread.join(timeout=0.1) + sync_color_thread = None class title_bar: """Hide or unhide the title bar of a window.""" @@ -783,26 +880,13 @@ class rainbow_title_bar: """Add a rainbow effect to the title bar of a window.""" current_color = None + _sync_timer_threads: Dict[int, threading.Thread] = {} # New: Tracks sync mode threads @classmethod def start(cls, window: Any, interval: int = 5, color_stops: int = 5) -> None: """ - Start the rainbow effect on the title bar of the specified window. - - Args: - window (object): The window object to modify (e.g., a Tk instance in Tkinter). - interval (int): The interval between each color change in milliseconds. Default is 5. - color_stops (int): The number of color stops between each RGB value. Default is 5. - - Example: - >>> rainbow_title_bar.start(window, interval=10, color_stops=10) - - Notes: - The `interval` parameter controls the speed of the rainbow effect, and the `color_stops` parameter controls the number of color stops between each RGB value. - Higher values for `interval` will result in a slower rainbow effect. - Higher values for `color_stops` might skip most of the colors defying the purpose of the rainbow effect. + Start the standard, non-synchronized rainbow effect on the title bar. """ - def color_changer(hwnd: int, interval: int) -> None: r, g, b = 200, 0, 0 while hwnd in rnbtbs: @@ -855,13 +939,6 @@ def color_changer(hwnd: int, interval: int) -> None: def get_current_color(cls) -> Tuple[int, int, int]: """ Get the current RGB color value of the title bar, which is being displayed by the rainbow effect. - Can be useful if you want to synchronize the title bar's rainbow effect with other elements of the window. - - Returns: - tuple: A tuple containing the RGB color values of the title bar. - - Notes: - Example implementation of this feature available at [examples/rainbow-synchronization-example.py](https://github.com/Zingzy/hPyT/blob/main/examples/rainbow-synchronization-example.py). """ color = cls.current_color @@ -875,10 +952,7 @@ def get_current_color(cls) -> Tuple[int, int, int]: @classmethod def stop(cls, window: Any) -> None: """ - Stop the rainbow effect on the title bar of the specified window. - - Args: - window (object): The window object to modify (e.g., a Tk instance in Tkinter). + Stop the standard rainbow effect on the title bar of the specified window. """ hwnd: int = module_find(window) @@ -887,31 +961,56 @@ def stop(cls, window: Any) -> None: else: raise ValueError("Rainbow title bar is not running on this window.") + # New Synchronization Feature + @classmethod + def start_sync(cls, window: Any, interval: int = 5, color_stops: int = 5) -> None: + """ + Starts the Title Bar in synchronization mode (as a slave). + It uses the synchronized_rainbow master thread for color data. + """ + hwnd: int = module_find(window) + if hwnd in cls._sync_timer_threads: + raise RuntimeError("Title bar is already running in synchronization mode.") + + # 1. Start the invisible color manager master thread (if not already running) + synchronized_rainbow.start(interval, color_stops) + + # 2. Start a timer thread for this window to periodically apply the master color + def sync_task(h: int, i: int): + while h in cls._sync_timer_threads: + rgb = synchronized_rainbow.get_current_color() + # Apply color using standard setter + title_bar_color.set(h, rgb) + ctypes.windll.kernel32.Sleep(i) + + cls._sync_timer_threads[hwnd] = threading.Thread(target=sync_task, args=(hwnd, interval), daemon=True) + cls._sync_timer_threads[hwnd].start() + + @classmethod + def stop_sync(cls, window: Any) -> None: + """ + Stops the Title Bar synchronization mode. + """ + hwnd: int = module_find(window) + if hwnd in cls._sync_timer_threads: + del cls._sync_timer_threads[hwnd] + title_bar_color.reset(window) + stop_sync_if_last() # Check if master thread should stop + else: + raise ValueError("Synchronized rainbow title bar is not running on this window.") + class rainbow_border: """Add a rainbow effect to the border of a window.""" current_color = None + _sync_timer_threads: Dict[int, threading.Thread] = {} # New: Tracks sync mode threads @classmethod def start(cls, window: Any, interval: int = 5, color_stops: int = 5) -> None: """ - Start the rainbow effect on the border of the specified window. - - Args: - window (object): The window object to modify (e.g., a Tk instance in Tkinter). - interval (int): The interval between each color change in milliseconds. Default is 5. - color_stops (int): The number of color stops between each RGB value. Default is 5. - - Example: - >>> rainbow_border.start(window, interval=10, color_stops=10) - - Notes: - The `interval` parameter controls the speed of the rainbow effect, and the `color_stops` parameter controls the number of color stops between each RGB value. - Higher values for `interval` will result in a slower rainbow effect. - Higher values for `color_stops` might skip most of the colors defying the purpose of the rainbow effect. + Start the standard, non-synchronized rainbow effect on the border. """ - def color_changer(hwnd, interval: int): r, g, b = 200, 0, 0 while hwnd in rnbbcs: @@ -964,13 +1063,6 @@ def color_changer(hwnd, interval: int): def get_current_color(cls) -> Tuple[int, int, int]: """ Get the current RGB color value of the border, which is being displayed by the rainbow effect. - Can be useful if you want to synchronize the border's rainbow effect with other elements of the window. - - Returns: - tuple: A tuple containing the RGB color values of the border. - - Notes: - Example implementation of this feature available at [examples/rainbow-synchronization-example.py](https://github.com/Zingzy/hPyT/blob/main/examples/rainbow-synchronization-example.py """ color = cls.current_color @@ -984,10 +1076,7 @@ def get_current_color(cls) -> Tuple[int, int, int]: @classmethod def stop(cls, window: Any) -> None: """ - Stop the rainbow effect on the border of the specified window. - - Args: - window (object): The window object to modify (e.g., a Tk instance in Tkinter). + Stop the standard rainbow effect on the border of the specified window. """ hwnd: int = module_find(window) @@ -996,6 +1085,44 @@ def stop(cls, window: Any) -> None: else: raise ValueError("Rainbow border is not running on this window.") + # New Synchronization Feature + @classmethod + def start_sync(cls, window: Any, interval: int = 5, color_stops: int = 5) -> None: + """ + Starts the Border in synchronization mode (as a slave). + It uses the synchronized_rainbow master thread for color data. + """ + hwnd: int = module_find(window) + if hwnd in cls._sync_timer_threads: + raise RuntimeError("Rainbow border is already running in synchronization mode.") + + # 1. Start the invisible color manager master thread (if not already running) + synchronized_rainbow.start(interval, color_stops) + + # 2. Start a timer thread for this window to periodically apply the master color + def sync_task(h: int, i: int): + while h in cls._sync_timer_threads: + rgb = synchronized_rainbow.get_current_color() + # Apply color using standard setter + border_color.set(h, rgb) + ctypes.windll.kernel32.Sleep(i) + + cls._sync_timer_threads[hwnd] = threading.Thread(target=sync_task, args=(hwnd, interval), daemon=True) + cls._sync_timer_threads[hwnd].start() + + @classmethod + def stop_sync(cls, window: Any) -> None: + """ + Stops the Border synchronization mode. + """ + hwnd: int = module_find(window) + if hwnd in cls._sync_timer_threads: + del cls._sync_timer_threads[hwnd] + border_color.reset(window) + stop_sync_if_last() # Check if master thread should stop + else: + raise ValueError("Synchronized rainbow border is not running on this window.") + class window_frame: """Change the position, size, and state of a window.""" @@ -1517,7 +1644,7 @@ def stylize_text(text: str, style: int) -> str: "๐Ÿ…ฐ๐Ÿ…ฑ๐Ÿ…ฒ๐Ÿ…ณ๐Ÿ…ด๐Ÿ…ต๐Ÿ…ถ๐Ÿ…ท๐Ÿ…ธ๐Ÿ…น๐Ÿ…บ๐Ÿ…ป๐Ÿ…ผ๐Ÿ…ฝ๐Ÿ…พ๐Ÿ…ฟ๐Ÿ†€๐Ÿ†๐Ÿ†‚๐Ÿ†ƒ๐Ÿ†„๐Ÿ†…๐Ÿ††๐Ÿ†‡๐Ÿ†ˆ๐Ÿ†‰๐Ÿ…ฐ๐Ÿ…ฑ๐Ÿ…ฒ๐Ÿ…ณ๐Ÿ…ด๐Ÿ…ต๐Ÿ…ถ๐Ÿ…ท๐Ÿ…ธ๐Ÿ…น๐Ÿ…บ๐Ÿ…ป๐Ÿ…ผ๐Ÿ…ฝ๐Ÿ…พ๐Ÿ…ฟ๐Ÿ†€๐Ÿ†๐Ÿ†‚๐Ÿ†ƒ๐Ÿ†„๐Ÿ†…๐Ÿ††๐Ÿ†‡๐Ÿ†ˆ๐Ÿ†‰1234567890", "โ“โ“‘โ“’โ““โ“”โ“•โ“–โ“—โ“˜โ“™โ“šโ“›โ“œโ“โ“žโ“Ÿโ“ โ“กโ“ขโ“ฃโ“คโ“ฅโ“ฆโ“งโ“จโ“ฉโ’ถโ’ทโ’ธโ’นโ’บโ’ปโ’ผโ’ฝโ’พโ’ฟโ“€โ“โ“‚โ“ƒโ“„โ“…โ“†โ“‡โ“ˆโ“‰โ“Šโ“‹โ“Œโ“โ“Žโ“โ‘ โ‘กโ‘ขโ‘ฃโ‘คโ‘ฅโ‘ฆโ‘งโ‘จโ“ช", "แ—ฉแ—ทแ‘•แ—ชEแ–ดGแ•ผIแ’Kแ’ชแ—ฐแ‘ŽOแ‘ญแ‘ซแ–‡แ”•Tแ‘Œแฏแ—ฏแ™ญYแ˜”แ—ฉแ—ทแ‘•แ—ชEแ–ดGแ•ผIแ’Kแ’ชแ—ฐแ‘ŽOแ‘ญแ‘ซแ–‡แ”•Tแ‘Œแฏแ—ฏแ™ญYแ˜”1234567890", - "๐•’๐•“๐•”๐••๐•–๐•—๐•˜๐•™๐•š๐•›๐•œ๐•๐•ž๐•Ÿ๐• ๐•ก๐•ข๐•ฃ๐•ค๐•ฅ๐•ฆ๐•ง๐•จ๐•ฉ๐•ช๐•ซ๐”ธ๐”นโ„‚๐”ป๐”ผ๐”ฝ๐”พโ„๐•€๐•๐•‚๐•ƒ๐•„โ„•๐•†โ„™โ„šโ„๐•Š๐•‹๐•Œ๐•๐•Ž๐•๐•โ„ค๐Ÿ™๐Ÿš๐Ÿ›๐Ÿœ๐Ÿ๐Ÿž๐ŸŸ๐Ÿ ๐Ÿก๐Ÿ˜", + "๐•’๐•“๐•”๐••๐•–๐•—๐•˜๐•™๐•š๐•›๐•œ๐•๐•ž๐•Ÿ๐• ๐•ก๐•ข๐•ฃ๐•ค๐•ฅ๐•ฆ๐•ง๐•จ๐•ฉ๐•ช๐•ซ๐”ธ๐”นโ„‚๐”ป๐”ผ๐”ฝ๐”พโ„๐•€๐•๐•‚๐•ƒ๐•„โ„•๐•†โ„™โ„šโ„๐•Š๐•‹๐•Œ๐•๐•Ž๐•๐•โ„ค๐Ÿ™๐š ๐Ÿ›๐Ÿœ๐Ÿ๐Ÿž๐ŸŸ๐Ÿ ๐Ÿก๐Ÿ˜", "โ‚ณเธฟโ‚ตฤษ†โ‚ฃโ‚ฒโฑงล‚Jโ‚ญโฑ โ‚ฅโ‚ฆร˜โ‚ฑQโฑคโ‚ดโ‚ฎษ„Vโ‚ฉำพษŽโฑซโ‚ณเธฟโ‚ตฤษ†โ‚ฃโ‚ฒโฑงล‚Jโ‚ญโฑ โ‚ฅโ‚ฆร˜โ‚ฑQโฑคโ‚ดโ‚ฎษ„Vโ‚ฉำพษŽโฑซ1234567890", "ๅ‚ไนƒๅŒšแ—ชไน‡ๅƒแŽถๅ„ไธจ๏พŒาœใ„ฅ็ˆชๅ‡ ใ„–ๅฉษŠๅฐบไธ‚ใ„’ใ„ฉแฏๅฑฑไน‚ใ„šไน™ๅ‚ไนƒๅŒšแ—ชไน‡ๅƒแŽถๅ„ไธจ๏พŒาœใ„ฅ็ˆชๅ‡ ใ„–ๅฉษŠๅฐบไธ‚ใ„’ใ„ฉแฏๅฑฑไน‚ใ„šไน™1234567890", ]