pygkit 1.0.2__tar.gz → 1.0.4__tar.gz

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.
Files changed (44) hide show
  1. {pygkit-1.0.2 → pygkit-1.0.4}/PKG-INFO +5 -2
  2. {pygkit-1.0.2 → pygkit-1.0.4}/README.md +4 -1
  3. {pygkit-1.0.2 → pygkit-1.0.4}/pyproject.toml +1 -1
  4. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/__init__.py +4 -0
  5. pygkit-1.0.4/src/pygkit/signals/__init__.py +3 -0
  6. pygkit-1.0.4/src/pygkit/signals/core.py +87 -0
  7. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/ui/dialog.py +9 -13
  8. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit.egg-info/PKG-INFO +5 -2
  9. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit.egg-info/SOURCES.txt +3 -0
  10. pygkit-1.0.4/tests/test_signals.py +222 -0
  11. {pygkit-1.0.2 → pygkit-1.0.4}/LICENSE +0 -0
  12. {pygkit-1.0.2 → pygkit-1.0.4}/setup.cfg +0 -0
  13. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/audio/__init__.py +0 -0
  14. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/audio/soundmanager.py +0 -0
  15. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/inventory/__init__.py +0 -0
  16. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/inventory/assets.py +0 -0
  17. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/inventory/core.py +0 -0
  18. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/inventory/data.py +0 -0
  19. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/inventory/ui.py +0 -0
  20. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/protocols.py +0 -0
  21. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/transitions/__init__.py +0 -0
  22. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/transitions/base.py +0 -0
  23. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/transitions/effects.py +0 -0
  24. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/transitions/fades.py +0 -0
  25. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/transitions/wipes.py +0 -0
  26. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/ui/__init__.py +0 -0
  27. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/ui/base.py +0 -0
  28. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/ui/container.py +0 -0
  29. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/ui/cooldown.py +0 -0
  30. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/ui/progressbar.py +0 -0
  31. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/utils/__init__.py +0 -0
  32. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/utils/interpolation.py +0 -0
  33. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/utils/text.py +0 -0
  34. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit/utils/timer.py +0 -0
  35. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit.egg-info/dependency_links.txt +0 -0
  36. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit.egg-info/requires.txt +0 -0
  37. {pygkit-1.0.2 → pygkit-1.0.4}/src/pygkit.egg-info/top_level.txt +0 -0
  38. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_box_model.py +0 -0
  39. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_dialog.py +0 -0
  40. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_interpolation.py +0 -0
  41. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_inventory.py +0 -0
  42. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_text.py +0 -0
  43. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_timer.py +0 -0
  44. {pygkit-1.0.2 → pygkit-1.0.4}/tests/test_transitions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygkit
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: General-purpose Pygame CE runtime kit: UI widgets, audio, timers, interpolation, and more.
5
5
  Author: pygkit contributors
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -708,8 +708,11 @@ import pygkit
708
708
 
709
709
  ## Features
710
710
 
711
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers
711
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
712
+ - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
713
+ - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
712
714
  - **Audio**: SoundManager with channel pools and explicit targeting
715
+ - **Transitions**: Screen transitions with fade, wipe, and custom effects
713
716
  - **Utils**: Timer, interpolation/easing functions
714
717
 
715
718
  ## Installation
@@ -10,8 +10,11 @@ import pygkit
10
10
 
11
11
  ## Features
12
12
 
13
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers
13
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
14
+ - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
15
+ - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
14
16
  - **Audio**: SoundManager with channel pools and explicit targeting
17
+ - **Transitions**: Screen transitions with fade, wipe, and custom effects
15
18
  - **Utils**: Timer, interpolation/easing functions
16
19
 
17
20
  ## Installation
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygkit"
7
- version = "1.0.2"
7
+ version = "1.0.4"
8
8
  description = "General-purpose Pygame CE runtime kit: UI widgets, audio, timers, interpolation, and more."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -2,6 +2,7 @@ from .audio import *
2
2
  from .transitions import *
3
3
  from .ui import *
4
4
  from .utils import *
5
+ from .signals import *
5
6
 
6
7
  __all__ = [
7
8
  "SoundManager",
@@ -43,4 +44,7 @@ __all__ = [
43
44
  "wrap",
44
45
  "render_multiline",
45
46
  "fit",
47
+ "Signal",
48
+ "SignalBus",
49
+ "signal",
46
50
  ]
@@ -0,0 +1,3 @@
1
+ from .core import Signal, SignalBus, signal
2
+
3
+ __all__ = ["Signal", "SignalBus", "signal"]
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Generic, TypeVar
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class Signal(Generic[T]):
9
+ def __init__(self, name: str = ""):
10
+ self.name = name
11
+ self._connections: list[Callable[..., Any]] = []
12
+
13
+ def connect(self, callback: Callable[..., Any]) -> None:
14
+ if callback in self._connections:
15
+ return
16
+ self._connections.append(callback)
17
+
18
+ def disconnect(self, callback: Callable[..., Any]) -> None:
19
+ self._connections = [c for c in self._connections if c is not callback]
20
+
21
+ def disconnect_all(self) -> None:
22
+ self._connections.clear()
23
+
24
+ def emit(self, *args: Any, **kwargs: Any) -> None:
25
+ for callback in list(self._connections):
26
+ try:
27
+ callback(*args, **kwargs)
28
+ except Exception as e:
29
+ print(f"Error in signal {self.name}: {e}")
30
+
31
+ def __iadd__(self, callback: Callable[..., Any]) -> Signal[T]:
32
+ self.connect(callback)
33
+ return self
34
+
35
+ def __isub__(self, callback: Callable[..., Any]) -> Signal[T]:
36
+ self.disconnect(callback)
37
+ return self
38
+
39
+ def __call__(self, *args: Any, **kwargs: Any) -> None:
40
+ self.emit(*args, **kwargs)
41
+
42
+
43
+ class SignalBus:
44
+ def __init__(self):
45
+ self._signals: dict[str, Signal[Any]] = {}
46
+
47
+ def create_signal(self, name: str) -> Signal[Any]:
48
+ if name not in self._signals:
49
+ self._signals[name] = Signal(name)
50
+ return self._signals[name]
51
+
52
+ def get_signal(self, name: str) -> Signal[Any] | None:
53
+ return self._signals.get(name)
54
+
55
+ def emit(self, name: str, *args: Any, **kwargs: Any) -> None:
56
+ sig = self.get_signal(name)
57
+ if sig:
58
+ sig.emit(*args, **kwargs)
59
+
60
+ def connect(self, name: str, callback: Callable[..., Any]) -> None:
61
+ sig = self.get_signal(name)
62
+ if sig:
63
+ sig.connect(callback)
64
+
65
+ def disconnect(self, name: str, callback: Callable[..., Any]) -> None:
66
+ sig = self.get_signal(name)
67
+ if sig:
68
+ sig.disconnect(callback)
69
+
70
+
71
+ class signal:
72
+ def __init__(self, name: str | None = None):
73
+ self.name = name
74
+
75
+ def __set_name__(self, owner, name):
76
+ if self.name is None:
77
+ self.name = name
78
+
79
+ def __get__(self, obj, objtype=None):
80
+ if obj is None:
81
+ return self
82
+ try:
83
+ return obj.__dict__[self.name]
84
+ except KeyError:
85
+ sig = Signal(self.name)
86
+ obj.__dict__[self.name] = sig
87
+ return sig
@@ -275,25 +275,21 @@ class DialogBox(UIBase):
275
275
  return
276
276
 
277
277
  if not self._is_complete:
278
- self._typewriter_timer.reset()
279
-
280
- chars_to_add = 0
278
+ text_len = len(self._current_line.text)
281
279
  elapsed = self._typewriter_timer.elapsed()
282
280
  interval = self._typewriter_timer.interval
283
281
 
284
- if interval > 0:
282
+ if elapsed >= interval and interval > 0:
285
283
  chars_to_add = max(1, int(elapsed / interval))
286
284
 
287
- text_len = len(self._current_line.text)
288
- if self._char_index < text_len:
289
- old_index = self._char_index
290
- self._char_index = min(text_len, self._char_index + chars_to_add)
291
- self._displayed_text = self._current_line.text[: self._char_index]
285
+ if self._char_index < text_len:
286
+ old_index = self._char_index
287
+ self._char_index = min(text_len, self._char_index + chars_to_add)
288
+ self._displayed_text = self._current_line.text[: self._char_index]
292
289
 
293
- if self._char_index != old_index:
294
- self._needs_rebuild = True
290
+ if self._char_index != old_index:
291
+ self._needs_rebuild = True
295
292
 
296
- if self._char_index < text_len:
297
293
  self._typewriter_timer.reset()
298
294
 
299
295
  if self._char_index >= text_len:
@@ -400,7 +396,7 @@ class DialogBox(UIBase):
400
396
  pos_offset: Position offset (x, y) on the screen
401
397
  """
402
398
 
403
- if self._needs_rebuild and self._is_complete:
399
+ if self._needs_rebuild:
404
400
  self._rebuild_text_surfaces()
405
401
 
406
402
  self.draw_base()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygkit
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: General-purpose Pygame CE runtime kit: UI widgets, audio, timers, interpolation, and more.
5
5
  Author: pygkit contributors
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -708,8 +708,11 @@ import pygkit
708
708
 
709
709
  ## Features
710
710
 
711
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers
711
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
712
+ - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
713
+ - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
712
714
  - **Audio**: SoundManager with channel pools and explicit targeting
715
+ - **Transitions**: Screen transitions with fade, wipe, and custom effects
713
716
  - **Utils**: Timer, interpolation/easing functions
714
717
 
715
718
  ## Installation
@@ -15,6 +15,8 @@ src/pygkit/inventory/assets.py
15
15
  src/pygkit/inventory/core.py
16
16
  src/pygkit/inventory/data.py
17
17
  src/pygkit/inventory/ui.py
18
+ src/pygkit/signals/__init__.py
19
+ src/pygkit/signals/core.py
18
20
  src/pygkit/transitions/__init__.py
19
21
  src/pygkit/transitions/base.py
20
22
  src/pygkit/transitions/effects.py
@@ -34,6 +36,7 @@ tests/test_box_model.py
34
36
  tests/test_dialog.py
35
37
  tests/test_interpolation.py
36
38
  tests/test_inventory.py
39
+ tests/test_signals.py
37
40
  tests/test_text.py
38
41
  tests/test_timer.py
39
42
  tests/test_transitions.py
@@ -0,0 +1,222 @@
1
+ from pygkit.signals import Signal, SignalBus, signal
2
+
3
+
4
+ class TestSignal:
5
+ def test_connect_and_emit(self):
6
+ sig = Signal("test")
7
+ results = []
8
+ sig.connect(lambda: results.append("called"))
9
+ sig.emit()
10
+ assert results == ["called"]
11
+
12
+ def test_connect_multiple(self):
13
+ sig = Signal("test")
14
+ results = []
15
+ sig.connect(lambda: results.append("a"))
16
+ sig.connect(lambda: results.append("b"))
17
+ sig.emit()
18
+ assert results == ["a", "b"]
19
+
20
+ def test_disconnect(self):
21
+ sig = Signal("test")
22
+ results = []
23
+ cb = lambda: results.append("called")
24
+ sig.connect(cb)
25
+ sig.disconnect(cb)
26
+ sig.emit()
27
+ assert results == []
28
+
29
+ def test_disconnect_all(self):
30
+ sig = Signal("test")
31
+ results = []
32
+ sig.connect(lambda: results.append("a"))
33
+ sig.connect(lambda: results.append("b"))
34
+ sig.disconnect_all()
35
+ sig.emit()
36
+ assert results == []
37
+
38
+ def test_duplicate_connect(self):
39
+ sig = Signal("test")
40
+ results = []
41
+ cb = lambda: results.append("called")
42
+ sig.connect(cb)
43
+ sig.connect(cb)
44
+ sig.emit()
45
+ assert results == ["called"]
46
+
47
+ def test_emit_args(self):
48
+ sig = Signal("test")
49
+ results = []
50
+ sig.connect(lambda x, y: results.append((x, y)))
51
+ sig.emit(1, 2)
52
+ assert results == [(1, 2)]
53
+
54
+ def test_emit_kwargs(self):
55
+ sig = Signal("test")
56
+ results = []
57
+ sig.connect(lambda a=0, b=0: results.append((a, b)))
58
+ sig.emit(a=3, b=4)
59
+ assert results == [(3, 4)]
60
+
61
+ def test_iadd(self):
62
+ sig = Signal("test")
63
+ results = []
64
+ sig += lambda: results.append("called")
65
+ sig.emit()
66
+ assert results == ["called"]
67
+
68
+ def test_isub(self):
69
+ sig = Signal("test")
70
+ results = []
71
+ cb = lambda: results.append("called")
72
+ sig += cb
73
+ sig -= cb
74
+ sig.emit()
75
+ assert results == []
76
+
77
+ def test_call_syntax(self):
78
+ sig = Signal("test")
79
+ results = []
80
+ sig += lambda: results.append("called")
81
+ sig()
82
+ assert results == ["called"]
83
+
84
+ def test_name(self):
85
+ sig = Signal("my_signal")
86
+ assert sig.name == "my_signal"
87
+
88
+ def test_emit_error_does_not_break(self):
89
+ sig = Signal("test")
90
+ results = []
91
+ sig.connect(lambda: (_ for _ in ()).throw(ValueError("bad")))
92
+ sig.connect(lambda: results.append("ok"))
93
+ sig.emit()
94
+ assert results == ["ok"]
95
+
96
+ def test_weak_ref_allows_gc(self):
97
+ sig = Signal("test")
98
+ results = []
99
+ def cb():
100
+ results.append("called")
101
+ sig.connect(cb)
102
+ sig.emit()
103
+ assert results == ["called"]
104
+
105
+
106
+ class TestSignalBus:
107
+ def test_create_signal(self):
108
+ bus = SignalBus()
109
+ sig = bus.create_signal("test")
110
+ assert isinstance(sig, Signal)
111
+ assert sig.name == "test"
112
+
113
+ def test_create_signal_idempotent(self):
114
+ bus = SignalBus()
115
+ sig1 = bus.create_signal("test")
116
+ sig2 = bus.create_signal("test")
117
+ assert sig1 is sig2
118
+
119
+ def test_get_signal(self):
120
+ bus = SignalBus()
121
+ bus.create_signal("test")
122
+ sig = bus.get_signal("test")
123
+ assert sig is not None
124
+ assert sig.name == "test"
125
+
126
+ def test_get_signal_missing(self):
127
+ bus = SignalBus()
128
+ assert bus.get_signal("nonexistent") is None
129
+
130
+ def test_emit_via_bus(self):
131
+ bus = SignalBus()
132
+ bus.create_signal("test")
133
+ results = []
134
+ bus.connect("test", lambda: results.append("called"))
135
+ bus.emit("test")
136
+ assert results == ["called"]
137
+
138
+ def test_emit_missing_signal(self):
139
+ bus = SignalBus()
140
+ bus.emit("nonexistent")
141
+
142
+ def test_connect_missing_signal(self):
143
+ bus = SignalBus()
144
+ bus.connect("nonexistent", lambda: None)
145
+
146
+ def test_disconnect_missing_signal(self):
147
+ bus = SignalBus()
148
+ bus.disconnect("nonexistent", lambda: None)
149
+
150
+ def test_disconnect_via_bus(self):
151
+ bus = SignalBus()
152
+ bus.create_signal("test")
153
+ results = []
154
+ cb = lambda: results.append("called")
155
+ bus.connect("test", cb)
156
+ bus.disconnect("test", cb)
157
+ bus.emit("test")
158
+ assert results == []
159
+
160
+ def test_bus_with_args(self):
161
+ bus = SignalBus()
162
+ bus.create_signal("test")
163
+ results = []
164
+ bus.connect("test", lambda x: results.append(x))
165
+ bus.emit("test", 42)
166
+ assert results == [42]
167
+
168
+
169
+ class TestSignalDescriptor:
170
+ def test_descriptor_is_signal_on_instance(self):
171
+ class Obj:
172
+ sig = signal()
173
+
174
+ obj = Obj()
175
+ assert isinstance(obj.sig, Signal)
176
+
177
+ def test_descriptor_unique_per_instance(self):
178
+ class Obj:
179
+ sig = signal()
180
+
181
+ a = Obj()
182
+ b = Obj()
183
+ assert a.sig is not b.sig
184
+
185
+ def test_descriptor_connect_and_emit(self):
186
+ class Obj:
187
+ sig = signal()
188
+
189
+ obj = Obj()
190
+ results = []
191
+ obj.sig += lambda: results.append("called")
192
+ obj.sig()
193
+ assert results == ["called"]
194
+
195
+ def test_descriptor_assign_allows_override(self):
196
+ class Obj:
197
+ sig = signal()
198
+
199
+ obj = Obj()
200
+ sig = obj.sig
201
+ obj.sig = "nope"
202
+ assert obj.sig == "nope"
203
+
204
+ def test_descriptor_multiple_signals(self):
205
+ class Obj:
206
+ a = signal()
207
+ b = signal()
208
+
209
+ obj = Obj()
210
+ results = []
211
+ obj.a += lambda: results.append("a")
212
+ obj.b += lambda: results.append("b")
213
+ obj.a()
214
+ assert results == ["a"]
215
+ obj.b()
216
+ assert results == ["a", "b"]
217
+
218
+ def test_descriptor_class_access(self):
219
+ class Obj:
220
+ sig = signal()
221
+
222
+ assert isinstance(Obj.sig, signal)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes