responds-forms 0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ # pyflutter_ui/scheduler.py
2
+ from __future__ import annotations
3
+
4
+ import queue
5
+ import threading
6
+ import tkinter as tk
7
+ from typing import Callable, Optional
8
+
9
+
10
+ class MainThreadScheduler:
11
+
12
+ _root: Optional[tk.Tk] = None
13
+ _ui_thread_id: Optional[int] = None
14
+
15
+ _queue: queue.Queue[Callable[[], None]] = queue.Queue()
16
+
17
+ _polling: bool = False
18
+ _poll_interval_ms: int = 15
19
+
20
+ # ============================================================
21
+ # CICLO DE VIDA
22
+ # ============================================================
23
+
24
+ @classmethod
25
+ def attach(cls, root: tk.Tk) -> None:
26
+ """
27
+ Registra a janela Tk e a thread responsável pela UI.
28
+
29
+ Deve ser chamado na mesma thread que criou o tk.Tk().
30
+ """
31
+
32
+ cls._root = root
33
+ cls._ui_thread_id = threading.get_ident()
34
+
35
+ if not cls._polling:
36
+ cls._polling = True
37
+ cls._poll()
38
+
39
+ @classmethod
40
+ def detach(cls) -> None:
41
+ """
42
+ Remove o scheduler e descarta callbacks pendentes.
43
+ """
44
+
45
+ cls._polling = False
46
+ cls._root = None
47
+ cls._ui_thread_id = None
48
+
49
+ while True:
50
+ try:
51
+ cls._queue.get_nowait()
52
+ except queue.Empty:
53
+ break
54
+
55
+ @classmethod
56
+ def is_attached(cls) -> bool:
57
+ return (
58
+ cls._root is not None
59
+ and cls._ui_thread_id is not None
60
+ )
61
+
62
+ @classmethod
63
+ def is_ui_thread(cls) -> bool:
64
+ return (
65
+ cls._ui_thread_id is not None
66
+ and threading.get_ident() == cls._ui_thread_id
67
+ )
68
+
69
+ # ============================================================
70
+ # EXECUÇÃO
71
+ # ============================================================
72
+
73
+ @classmethod
74
+ def run(cls, callback: Callable[[], None]) -> None:
75
+ """
76
+ Executa callback na thread da interface.
77
+
78
+ Se já estiver na thread do Tk:
79
+ executa imediatamente.
80
+
81
+ Se estiver em uma worker thread:
82
+ coloca na fila para execução pelo Tk.
83
+ """
84
+
85
+ if not cls.is_attached():
86
+ raise RuntimeError(
87
+ f"{cls.__name__}: scheduler não está anexado. "
88
+ f"Crie o Tk() e chame attach(root) antes de usar run()."
89
+ )
90
+
91
+ if cls.is_ui_thread():
92
+ callback()
93
+ return
94
+
95
+ cls._queue.put(callback)
96
+
97
+ root = cls._root
98
+
99
+ if root is None:
100
+ return
101
+
102
+ try:
103
+ root.after(0, cls._drain)
104
+ except (tk.TclError, RuntimeError):
105
+ pass
106
+
107
+ # ============================================================
108
+ # FILA
109
+ # ============================================================
110
+
111
+ @classmethod
112
+ def _drain(cls) -> None:
113
+ """
114
+ Executa os callbacks pendentes na thread do Tk.
115
+ """
116
+
117
+ if not cls.is_ui_thread():
118
+ return
119
+
120
+ while True:
121
+
122
+ try:
123
+ callback = cls._queue.get_nowait()
124
+ except queue.Empty:
125
+ break
126
+
127
+ try:
128
+ callback()
129
+ except Exception as e:
130
+ print(
131
+ f"{cls.__name__} "
132
+ f"erro ao executar callback: {e}"
133
+ )
134
+
135
+ @classmethod
136
+ def _poll(cls) -> None:
137
+ """
138
+ Mantém o processamento da fila ativo.
139
+ """
140
+
141
+ root = cls._root
142
+
143
+ if root is None or not cls._polling:
144
+ return
145
+
146
+ cls._drain()
147
+
148
+ try:
149
+ root.after(
150
+ cls._poll_interval_ms,
151
+ cls._poll
152
+ )
153
+ except (tk.TclError, RuntimeError):
154
+ cls._polling = False