pygkit 1.0.8__tar.gz → 1.2.1__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 (64) hide show
  1. {pygkit-1.0.8 → pygkit-1.2.1}/PKG-INFO +17 -3
  2. {pygkit-1.0.8 → pygkit-1.2.1}/README.md +8 -2
  3. {pygkit-1.0.8 → pygkit-1.2.1}/pyproject.toml +16 -1
  4. pygkit-1.2.1/src/pygkit/__init__.py +223 -0
  5. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/inventory/__init__.py +1 -16
  6. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/inventory/assets.py +1 -4
  7. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/inventory/core.py +1 -2
  8. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/inventory/data.py +1 -2
  9. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/lighting/__init__.py +4 -4
  10. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/lighting/lightmap.py +1 -1
  11. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/lighting/lights.py +3 -3
  12. pygkit-1.2.1/src/pygkit/protocols.py +49 -0
  13. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/ui/__init__.py +12 -0
  14. pygkit-1.2.1/src/pygkit/ui/anchors.py +80 -0
  15. pygkit-1.2.1/src/pygkit/ui/button.py +134 -0
  16. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/ui/dialog.py +1 -26
  17. pygkit-1.2.1/src/pygkit/ui/menubar.py +117 -0
  18. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit.egg-info/PKG-INFO +17 -3
  19. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit.egg-info/SOURCES.txt +7 -0
  20. pygkit-1.2.1/src/pygkit.egg-info/requires.txt +16 -0
  21. pygkit-1.2.1/tests/test_anchors.py +39 -0
  22. pygkit-1.2.1/tests/test_button.py +88 -0
  23. pygkit-1.2.1/tests/test_menubar.py +132 -0
  24. pygkit-1.2.1/tests/test_packaging.py +106 -0
  25. pygkit-1.0.8/src/pygkit/__init__.py +0 -95
  26. pygkit-1.0.8/src/pygkit/protocols.py +0 -29
  27. pygkit-1.0.8/src/pygkit.egg-info/requires.txt +0 -1
  28. {pygkit-1.0.8 → pygkit-1.2.1}/LICENSE +0 -0
  29. {pygkit-1.0.8 → pygkit-1.2.1}/setup.cfg +0 -0
  30. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/animation/__init__.py +0 -0
  31. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/animation/player.py +0 -0
  32. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/animation/sheet.py +0 -0
  33. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/audio/__init__.py +0 -0
  34. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/audio/soundmanager.py +0 -0
  35. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/inventory/ui.py +0 -0
  36. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/lighting/blend.py +0 -0
  37. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/lighting/sprites.py +0 -0
  38. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/signals/__init__.py +0 -0
  39. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/signals/core.py +0 -0
  40. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/transitions/__init__.py +0 -0
  41. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/transitions/base.py +0 -0
  42. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/transitions/effects.py +0 -0
  43. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/transitions/fades.py +0 -0
  44. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/transitions/wipes.py +0 -0
  45. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/ui/base.py +0 -0
  46. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/ui/container.py +0 -0
  47. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/ui/cooldown.py +0 -0
  48. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/ui/progressbar.py +0 -0
  49. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/utils/__init__.py +0 -0
  50. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/utils/interpolation.py +0 -0
  51. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/utils/text.py +0 -0
  52. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit/utils/timer.py +0 -0
  53. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit.egg-info/dependency_links.txt +0 -0
  54. {pygkit-1.0.8 → pygkit-1.2.1}/src/pygkit.egg-info/top_level.txt +0 -0
  55. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_animation.py +0 -0
  56. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_box_model.py +0 -0
  57. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_dialog.py +0 -0
  58. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_interpolation.py +0 -0
  59. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_inventory.py +0 -0
  60. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_lighting.py +0 -0
  61. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_signals.py +0 -0
  62. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_text.py +0 -0
  63. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_timer.py +0 -0
  64. {pygkit-1.0.8 → pygkit-1.2.1}/tests/test_transitions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygkit
3
- Version: 1.0.8
3
+ Version: 1.2.1
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
@@ -694,6 +694,14 @@ Requires-Python: >=3.10
694
694
  Description-Content-Type: text/markdown
695
695
  License-File: LICENSE
696
696
  Requires-Dist: pygame-ce>=2.5
697
+ Provides-Extra: ui
698
+ Provides-Extra: audio
699
+ Provides-Extra: animation
700
+ Provides-Extra: lighting
701
+ Provides-Extra: transitions
702
+ Provides-Extra: inventory
703
+ Provides-Extra: all
704
+ Requires-Dist: pygkit[animation,audio,inventory,lighting,transitions,ui]; extra == "all"
697
705
  Dynamic: license-file
698
706
 
699
707
  # pygkit
@@ -708,7 +716,7 @@ import pygkit
708
716
 
709
717
  ## Features
710
718
 
711
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
719
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system, buttons, menubar container, anchor/row/grid placement helpers
712
720
  - **Animation**: Multi-state `AnimationPlayer` where every state owns its own spritesheet — mixed image sizes and grids (e.g. `idle_spritesheet.png` vs `run_spritesheet.png`) in one player; pure image API, no JSON
713
721
  - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
714
722
  - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
@@ -722,5 +730,11 @@ import pygkit
722
730
  ## Installation
723
731
 
724
732
  ```bash
725
- pip install pygkit
733
+ pip install pygkit # core: signals, utils, protocols
734
+ pip install "pygkit[ui]" # widgets, dialog, cooldowns
735
+ pip install "pygkit[all]" # everything
726
736
  ```
737
+
738
+ Available extras: `ui`, `audio`, `animation`, `lighting`,
739
+ `transitions`, `inventory`, `all`. `import pygkit` alone loads
740
+ core only; subsystems load lazily on first use.
@@ -10,7 +10,7 @@ import pygkit
10
10
 
11
11
  ## Features
12
12
 
13
- - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system
13
+ - **UI**: Box-model widgets with plugin compositing — progress bars, cooldown overlays, containers, dialog system, buttons, menubar container, anchor/row/grid placement helpers
14
14
  - **Animation**: Multi-state `AnimationPlayer` where every state owns its own spritesheet — mixed image sizes and grids (e.g. `idle_spritesheet.png` vs `run_spritesheet.png`) in one player; pure image API, no JSON
15
15
  - **Dialog**: Typewriter effect with customizable speeds, speaker names, and progress indicators
16
16
  - **Inventory**: Grid-based item management with stacking, weight tracking, events, and serialization
@@ -24,5 +24,11 @@ import pygkit
24
24
  ## Installation
25
25
 
26
26
  ```bash
27
- pip install pygkit
27
+ pip install pygkit # core: signals, utils, protocols
28
+ pip install "pygkit[ui]" # widgets, dialog, cooldowns
29
+ pip install "pygkit[all]" # everything
28
30
  ```
31
+
32
+ Available extras: `ui`, `audio`, `animation`, `lighting`,
33
+ `transitions`, `inventory`, `all`. `import pygkit` alone loads
34
+ core only; subsystems load lazily on first use.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygkit"
7
- version = "1.0.8"
7
+ version = "1.2.1"
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"
@@ -26,6 +26,21 @@ classifiers = [
26
26
  "Topic :: Software Development :: Libraries",
27
27
  ]
28
28
 
29
+ [project.optional-dependencies]
30
+ # One extra per subsystem. Empty for now: every subsystem is pure
31
+ # pygame-ce + stdlib. Slots are reserved for future speedups
32
+ # (lighting color math -> numpy, sprite pack -> pillow) without
33
+ # taxing core installs.
34
+ ui = []
35
+ audio = []
36
+ animation = []
37
+ lighting = []
38
+ transitions = []
39
+ inventory = []
40
+ all = [
41
+ "pygkit[ui, audio, animation, lighting, transitions, inventory]",
42
+ ]
43
+
29
44
  [project.urls]
30
45
  Homepage = "https://github.com/FluffyBrudy/pygkit"
31
46
  Repository = "https://github.com/FluffyBrudy/pygkit"
@@ -0,0 +1,223 @@
1
+ """pygkit - Pygame CE runtime kit.
2
+
3
+ Core (signals, utils, protocols) imports eagerly. Subsystems load
4
+ lazily on first attribute use, so ``import pygkit`` stays cheap::
5
+
6
+ pip install pygkit # core only
7
+ pip install "pygkit[ui]" # + widgets, dialog, cooldowns
8
+ pip install "pygkit[all]" # everything
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib
14
+ from typing import Any
15
+
16
+ from .protocols import Clickable, Drawable, Renderable, UIElement, Updateable, Widget
17
+ from .signals import Signal, SignalBus, signal
18
+ from .utils import (
19
+ SimpleInterpolation,
20
+ Timer,
21
+ ease_in,
22
+ ease_in_out,
23
+ ease_out,
24
+ fit,
25
+ lerp,
26
+ load_font,
27
+ outline,
28
+ render_multiline,
29
+ shadow,
30
+ smoothstep,
31
+ wrap,
32
+ )
33
+
34
+ _LAZY_ATTRS: dict[str, str] = {
35
+ "SoundManager": "audio",
36
+ "SFX_CHANNELS": "audio",
37
+ "AnimationPlayer": "animation",
38
+ "AnimationSheet": "animation",
39
+ "PackOptions": "animation",
40
+ "pack_spritesheet": "animation",
41
+ "LightMap": "lighting",
42
+ "PointLight": "lighting",
43
+ "Spotlight": "lighting",
44
+ "DynamicLight": "lighting",
45
+ "glow_sprite": "lighting",
46
+ "spotlight_sprite": "lighting",
47
+ "dynamic_glow_sprite": "lighting",
48
+ "clear_sprite_cache": "lighting",
49
+ "RGB_ADD": "lighting",
50
+ "RGB_SUB": "lighting",
51
+ "RGB_MULT": "lighting",
52
+ "RGB_MIN": "lighting",
53
+ "RGB_MAX": "lighting",
54
+ "RGBA_ADD": "lighting",
55
+ "RGBA_SUB": "lighting",
56
+ "RGBA_MULT": "lighting",
57
+ "RGBA_MIN": "lighting",
58
+ "RGBA_MAX": "lighting",
59
+ "LIGHT_ADD": "lighting",
60
+ "SCENE_MULT": "lighting",
61
+ "TransitionRunner": "transitions",
62
+ "TransitionState": "transitions",
63
+ "FadeToBlack": "transitions",
64
+ "FadeFromBlack": "transitions",
65
+ "Crossfade": "transitions",
66
+ "Flash": "transitions",
67
+ "IrisIn": "transitions",
68
+ "IrisOut": "transitions",
69
+ "Slide": "transitions",
70
+ "PixelDissolve": "transitions",
71
+ "Shake": "transitions",
72
+ "Container": "ui",
73
+ "CooldownOverlay": "ui",
74
+ "ProgressBarUI": "ui",
75
+ "UIBase": "ui",
76
+ "UIOptions": "ui",
77
+ "BoxModel": "ui",
78
+ "BoxModelResult": "ui",
79
+ "generate_box_model": "ui",
80
+ "DialogBox": "ui",
81
+ "DialogConfig": "ui",
82
+ "DialogColors": "ui",
83
+ "DialogLine": "ui",
84
+ "TypewriterSpeed": "ui",
85
+ "Button": "ui",
86
+ "ButtonStyle": "ui",
87
+ "Menubar": "ui",
88
+ "ANCHORS": "ui",
89
+ "anchor_point": "ui",
90
+ "place": "ui",
91
+ "row": "ui",
92
+ "column": "ui",
93
+ "grid": "ui",
94
+ "ItemData": "inventory",
95
+ "InventoryConfig": "inventory",
96
+ "ItemStack": "inventory",
97
+ "InventoryEvent": "inventory",
98
+ "RARITY_COLORS": "inventory",
99
+ "DEFAULT_INVENTORY_CONFIG": "inventory",
100
+ "Inventory": "inventory",
101
+ "AssetCache": "inventory",
102
+ "get_cache": "inventory",
103
+ "generate_procedural_icon": "inventory",
104
+ "render_quantity_text": "inventory",
105
+ "Tooltip": "inventory",
106
+ "DragDropState": "inventory",
107
+ "InventoryWidget": "inventory",
108
+ "InventoryManager": "inventory",
109
+ }
110
+
111
+ __all__ = [
112
+ "SoundManager",
113
+ "SFX_CHANNELS",
114
+ "AnimationPlayer",
115
+ "AnimationSheet",
116
+ "PackOptions",
117
+ "pack_spritesheet",
118
+ "LightMap",
119
+ "PointLight",
120
+ "Spotlight",
121
+ "DynamicLight",
122
+ "glow_sprite",
123
+ "spotlight_sprite",
124
+ "dynamic_glow_sprite",
125
+ "clear_sprite_cache",
126
+ "RGB_ADD",
127
+ "RGB_SUB",
128
+ "RGB_MULT",
129
+ "RGB_MIN",
130
+ "RGB_MAX",
131
+ "RGBA_ADD",
132
+ "RGBA_SUB",
133
+ "RGBA_MULT",
134
+ "RGBA_MIN",
135
+ "RGBA_MAX",
136
+ "LIGHT_ADD",
137
+ "SCENE_MULT",
138
+ "TransitionRunner",
139
+ "TransitionState",
140
+ "FadeToBlack",
141
+ "FadeFromBlack",
142
+ "Crossfade",
143
+ "Flash",
144
+ "IrisIn",
145
+ "IrisOut",
146
+ "Slide",
147
+ "PixelDissolve",
148
+ "Shake",
149
+ "Container",
150
+ "CooldownOverlay",
151
+ "ProgressBarUI",
152
+ "UIBase",
153
+ "UIOptions",
154
+ "BoxModel",
155
+ "BoxModelResult",
156
+ "generate_box_model",
157
+ "DialogBox",
158
+ "DialogConfig",
159
+ "DialogColors",
160
+ "DialogLine",
161
+ "TypewriterSpeed",
162
+ "Button",
163
+ "ButtonStyle",
164
+ "Menubar",
165
+ "ANCHORS",
166
+ "anchor_point",
167
+ "place",
168
+ "row",
169
+ "column",
170
+ "grid",
171
+ "ItemData",
172
+ "InventoryConfig",
173
+ "ItemStack",
174
+ "InventoryEvent",
175
+ "RARITY_COLORS",
176
+ "DEFAULT_INVENTORY_CONFIG",
177
+ "Inventory",
178
+ "AssetCache",
179
+ "get_cache",
180
+ "generate_procedural_icon",
181
+ "render_quantity_text",
182
+ "Tooltip",
183
+ "DragDropState",
184
+ "InventoryWidget",
185
+ "InventoryManager",
186
+ "SimpleInterpolation",
187
+ "Timer",
188
+ "lerp",
189
+ "smoothstep",
190
+ "ease_in_out",
191
+ "ease_out",
192
+ "ease_in",
193
+ "load_font",
194
+ "outline",
195
+ "shadow",
196
+ "wrap",
197
+ "render_multiline",
198
+ "fit",
199
+ "Signal",
200
+ "SignalBus",
201
+ "signal",
202
+ "Renderable",
203
+ "UIElement",
204
+ "Drawable",
205
+ "Updateable",
206
+ "Clickable",
207
+ "Widget",
208
+ ]
209
+
210
+
211
+ def __getattr__(name: str) -> Any:
212
+ try:
213
+ sub = _LAZY_ATTRS[name]
214
+ except KeyError:
215
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
216
+ module = importlib.import_module(f"{__name__}.{sub}")
217
+ value = getattr(module, name)
218
+ globals()[name] = value
219
+ return value
220
+
221
+
222
+ def __dir__() -> list[str]:
223
+ return sorted(__all__)
@@ -1,20 +1,5 @@
1
1
  """
2
- Pygkit Inventory System - Production-grade inventory management for Pygame.
3
-
4
- A complete, generic, and optimized inventory system with:
5
- - Data-driven item definitions
6
- - Grid-based slot management
7
- - Drag-and-drop interactions
8
- - Tooltips and visual feedback
9
- - Asset caching for performance
10
- - Event-driven architecture
11
- - Save/load serialization support
12
-
13
- Modules:
14
- data: Type definitions and data structures
15
- core: Inventory logic and state management
16
- assets: Asset caching and procedural generation
17
- ui: UI rendering and user interaction
2
+ Grid inventory: data definitions, slot logic, icon assets, widgets.
18
3
 
19
4
  Usage:
20
5
  from pygkit.inventory import Inventory, InventoryWidget, ItemData
@@ -1,8 +1,5 @@
1
1
  """
2
- Asset caching and procedural generation for the Inventory System.
3
-
4
- Provides optimized caching of static UI elements and procedural
5
- generation of item icons to avoid runtime overhead.
2
+ Slot surface cache and item icon generation.
6
3
  """
7
4
 
8
5
  from __future__ import annotations
@@ -19,8 +19,7 @@ from .data import (
19
19
 
20
20
 
21
21
  class Inventory:
22
- """
23
- Generic inventory container with full item management capabilities.
22
+ """Grid slot container with stacking, weight, events, serialization.
24
23
 
25
24
  Features:
26
25
  - Grid-based slot system with configurable size
@@ -14,8 +14,7 @@ from pygame.typing import ColorLike
14
14
 
15
15
 
16
16
  class ItemData(TypedDict, total=False):
17
- """
18
- Flexible item definition with required core fields and optional metadata.
17
+ """Item definition with required core fields and optional metadata.
19
18
 
20
19
  Required:
21
20
  id: Unique string identifier (e.g., "potion_health_small")
@@ -18,7 +18,7 @@ the scene) with game-agnostic lights. Straight imports, no game wiring::
18
18
  lightmap.apply(scene, blend=RGBA_MULT)
19
19
  """
20
20
 
21
- from pygkit.lighting.blend import (
21
+ from .blend import (
22
22
  LIGHT_ADD,
23
23
  RGB_ADD,
24
24
  RGB_MAX,
@@ -32,9 +32,9 @@ from pygkit.lighting.blend import (
32
32
  RGBA_SUB,
33
33
  SCENE_MULT,
34
34
  )
35
- from pygkit.lighting.lightmap import LightMap
36
- from pygkit.lighting.lights import DynamicLight, PointLight, Spotlight
37
- from pygkit.lighting.sprites import (
35
+ from .lightmap import LightMap
36
+ from .lights import DynamicLight, PointLight, Spotlight
37
+ from .sprites import (
38
38
  clear_sprite_cache,
39
39
  dynamic_glow_sprite,
40
40
  glow_sprite,
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
  import pygame
24
24
  from pygame import Surface
25
25
 
26
- from pygkit.lighting.blend import LIGHT_ADD, SCENE_MULT
26
+ from .blend import LIGHT_ADD, SCENE_MULT
27
27
 
28
28
  __all__ = ["LightMap"]
29
29
 
@@ -17,9 +17,9 @@ import random
17
17
  import pygame
18
18
  from pygame import Surface
19
19
 
20
- from pygkit.lighting.blend import LIGHT_ADD
21
- from pygkit.lighting.lightmap import LightMap
22
- from pygkit.lighting.sprites import dynamic_glow_sprite, glow_sprite, spotlight_sprite
20
+ from .blend import LIGHT_ADD
21
+ from .lightmap import LightMap
22
+ from .sprites import dynamic_glow_sprite, glow_sprite, spotlight_sprite
23
23
 
24
24
  __all__ = ["PointLight", "Spotlight", "DynamicLight"]
25
25
 
@@ -0,0 +1,49 @@
1
+ from typing import Any, Protocol, runtime_checkable
2
+
3
+ from pygame import Surface
4
+
5
+ from .signals import Signal
6
+
7
+
8
+ class Renderable(Protocol):
9
+ def render(self, screen: Surface, pos_offset: tuple[int, int] = (0, 0)): ...
10
+
11
+ def update(self): ...
12
+
13
+
14
+ class UIElement(Protocol):
15
+ def render(self, screen: Surface): ...
16
+
17
+ def update(self): ...
18
+
19
+ @property
20
+ def size(self) -> tuple[int, int]: ...
21
+
22
+ @property
23
+ def pos(self) -> tuple[int, int]: ...
24
+
25
+
26
+ class Drawable(Protocol):
27
+ def draw(self, surface: Surface): ...
28
+
29
+
30
+ class Updateable(Protocol):
31
+ def update(self): ...
32
+
33
+
34
+ @runtime_checkable
35
+ class Widget(Renderable, Protocol):
36
+ @property
37
+ def size(self) -> tuple[int, int]: ...
38
+
39
+
40
+ class Clickable(Widget, Protocol):
41
+ # contains/press/activate take screen coords; widgets track origin at render.
42
+
43
+ on_press: Signal[Any]
44
+
45
+ def contains(self, pos: tuple[int, int]) -> bool: ...
46
+
47
+ def press(self, pos: tuple[int, int]) -> bool: ...
48
+
49
+ def activate(self, pos: tuple[int, int]) -> bool: ...
@@ -1,7 +1,10 @@
1
+ from .anchors import ANCHORS, anchor_point, column, grid, place, row
1
2
  from .base import UIBase, UIOptions, BoxModel, BoxModelResult, generate_box_model
3
+ from .button import Button, ButtonStyle
2
4
  from .container import Container
3
5
  from .cooldown import CooldownOverlay
4
6
  from .dialog import DialogBox, DialogConfig, DialogColors, DialogLine, TypewriterSpeed
7
+ from .menubar import Menubar
5
8
  from .progressbar import ProgressBarUI
6
9
 
7
10
  __all__ = [
@@ -18,4 +21,13 @@ __all__ = [
18
21
  "DialogColors",
19
22
  "DialogLine",
20
23
  "TypewriterSpeed",
24
+ "Button",
25
+ "ButtonStyle",
26
+ "Menubar",
27
+ "ANCHORS",
28
+ "anchor_point",
29
+ "place",
30
+ "row",
31
+ "column",
32
+ "grid",
21
33
  ]
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from pygame import Rect
4
+
5
+ ANCHORS = (
6
+ "topleft",
7
+ "midtop",
8
+ "topright",
9
+ "midleft",
10
+ "center",
11
+ "midright",
12
+ "bottomleft",
13
+ "midbottom",
14
+ "bottomright",
15
+ )
16
+
17
+
18
+ def anchor_point(rect: Rect, anchor: str) -> tuple[int, int]:
19
+ if anchor not in ANCHORS:
20
+ raise ValueError(f"unknown anchor {anchor!r}; pick from {list(ANCHORS)}")
21
+ point = getattr(rect, anchor)
22
+ return (point[0], point[1])
23
+
24
+
25
+ def place(
26
+ size: tuple[int, int],
27
+ within: Rect,
28
+ anchor: str = "topleft",
29
+ dx: int = 0,
30
+ dy: int = 0,
31
+ ) -> Rect:
32
+ rect = Rect((0, 0), (max(0, size[0]), max(0, size[1])))
33
+ ax, ay = anchor_point(within, anchor)
34
+ setattr(rect, anchor, (ax + dx, ay + dy))
35
+ return rect
36
+
37
+
38
+ def row(
39
+ sizes: list[tuple[int, int]],
40
+ *,
41
+ gap: int = 0,
42
+ origin: tuple[int, int] = (0, 0),
43
+ ) -> list[Rect]:
44
+ rects: list[Rect] = []
45
+ x = origin[0]
46
+ for w, h in sizes:
47
+ rects.append(Rect(x, origin[1], max(0, w), max(0, h)))
48
+ x += max(0, w) + gap
49
+ return rects
50
+
51
+
52
+ def column(
53
+ sizes: list[tuple[int, int]],
54
+ *,
55
+ gap: int = 0,
56
+ origin: tuple[int, int] = (0, 0),
57
+ ) -> list[Rect]:
58
+ rects: list[Rect] = []
59
+ y = origin[1]
60
+ for w, h in sizes:
61
+ rects.append(Rect(origin[0], y, max(0, w), max(0, h)))
62
+ y += max(0, h) + gap
63
+ return rects
64
+
65
+
66
+ def grid(
67
+ count: int,
68
+ cols: int,
69
+ cell: tuple[int, int],
70
+ *,
71
+ gap: int = 0,
72
+ origin: tuple[int, int] = (0, 0),
73
+ ) -> list[Rect]:
74
+ if cols <= 0:
75
+ raise ValueError(f"cols must be > 0, got {cols}")
76
+ cw, ch = max(0, cell[0]), max(0, cell[1])
77
+ return [
78
+ Rect(origin[0] + (i % cols) * (cw + gap), origin[1] + (i // cols) * (ch + gap), cw, ch)
79
+ for i in range(max(0, count))
80
+ ]