batframework 1.0.8a7__py3-none-any.whl → 1.0.8a9__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.
Files changed (70) hide show
  1. batFramework/__init__.py +51 -68
  2. batFramework/action.py +99 -126
  3. batFramework/actionContainer.py +9 -53
  4. batFramework/animatedSprite.py +82 -141
  5. batFramework/audioManager.py +26 -69
  6. batFramework/camera.py +69 -259
  7. batFramework/constants.py +54 -16
  8. batFramework/cutscene.py +29 -39
  9. batFramework/cutsceneBlocks.py +43 -36
  10. batFramework/debugger.py +48 -0
  11. batFramework/dynamicEntity.py +9 -18
  12. batFramework/easing.py +71 -0
  13. batFramework/entity.py +97 -48
  14. batFramework/gui/__init__.py +2 -10
  15. batFramework/gui/button.py +78 -9
  16. batFramework/gui/constraints.py +204 -0
  17. batFramework/gui/container.py +32 -174
  18. batFramework/gui/debugger.py +43 -131
  19. batFramework/gui/frame.py +19 -0
  20. batFramework/gui/image.py +20 -56
  21. batFramework/gui/indicator.py +21 -38
  22. batFramework/gui/interactiveWidget.py +13 -192
  23. batFramework/gui/label.py +74 -309
  24. batFramework/gui/layout.py +63 -231
  25. batFramework/gui/root.py +38 -134
  26. batFramework/gui/shape.py +57 -237
  27. batFramework/gui/toggle.py +51 -101
  28. batFramework/gui/widget.py +250 -358
  29. batFramework/manager.py +19 -52
  30. batFramework/particles.py +77 -0
  31. batFramework/scene.py +123 -281
  32. batFramework/sceneManager.py +116 -178
  33. batFramework/stateMachine.py +8 -11
  34. batFramework/time.py +58 -145
  35. batFramework/transition.py +124 -195
  36. batFramework/transitionManager.py +0 -0
  37. batFramework/triggerZone.py +1 -1
  38. batFramework/utils.py +147 -112
  39. batframework-1.0.8a9.dist-info/METADATA +53 -0
  40. batframework-1.0.8a9.dist-info/RECORD +42 -0
  41. {batframework-1.0.8a7.dist-info → batframework-1.0.8a9.dist-info}/WHEEL +1 -1
  42. batFramework/character.py +0 -27
  43. batFramework/easingController.py +0 -58
  44. batFramework/enums.py +0 -113
  45. batFramework/fontManager.py +0 -65
  46. batFramework/gui/clickableWidget.py +0 -220
  47. batFramework/gui/constraints/__init__.py +0 -1
  48. batFramework/gui/constraints/constraints.py +0 -815
  49. batFramework/gui/dialogueBox.py +0 -99
  50. batFramework/gui/draggableWidget.py +0 -40
  51. batFramework/gui/meter.py +0 -74
  52. batFramework/gui/radioButton.py +0 -84
  53. batFramework/gui/slider.py +0 -240
  54. batFramework/gui/style.py +0 -10
  55. batFramework/gui/styleManager.py +0 -48
  56. batFramework/gui/textInput.py +0 -247
  57. batFramework/object.py +0 -123
  58. batFramework/particle.py +0 -115
  59. batFramework/renderGroup.py +0 -67
  60. batFramework/resourceManager.py +0 -100
  61. batFramework/scrollingSprite.py +0 -114
  62. batFramework/sprite.py +0 -51
  63. batFramework/templates/__init__.py +0 -2
  64. batFramework/templates/character.py +0 -44
  65. batFramework/templates/states.py +0 -166
  66. batFramework/tileset.py +0 -46
  67. batframework-1.0.8a7.dist-info/LICENCE +0 -21
  68. batframework-1.0.8a7.dist-info/METADATA +0 -43
  69. batframework-1.0.8a7.dist-info/RECORD +0 -62
  70. {batframework-1.0.8a7.dist-info → batframework-1.0.8a9.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,6 @@
1
1
  import batFramework as bf
2
- from .cutscene import Cutscene, CutsceneManager
3
- from .transition import *
4
- from typing import Optional,Callable
2
+ from .cutscene import Cutscene,CutsceneManager
3
+
5
4
 
6
5
  # Define the base CutsceneBlock class
7
6
  class CutsceneBlock:
@@ -18,18 +17,18 @@ class CutsceneBlock:
18
17
  self.ended = False
19
18
  self.started = False
20
19
 
21
- def get_scene_at(self, index):
22
- return bf.CutsceneManager().manager.scenes[index]
23
-
24
- def set_scene(self, name, index=0):
25
- return CutsceneManager().manager.set_scene(name, index)
20
+ def get_scene_at(self,index):
21
+ return bf.CutsceneManager().manager._scenes[index]
26
22
 
23
+ def set_scene(self,name,index=0):
24
+ return CutsceneManager().manager.set_scene(name,index)
25
+
27
26
  def get_current_scene(self):
28
27
  return CutsceneManager().manager.get_current_scene()
29
-
30
- def get_scene(self, name):
28
+
29
+ def get_scene(self,name):
31
30
  return CutsceneManager().manager.get_scene(name)
32
-
31
+
33
32
  # Set the parent cutscene for this block
34
33
  def set_parent_cutscene(self, parent):
35
34
  """
@@ -91,13 +90,17 @@ class ParallelBlock(CutsceneBlock):
91
90
  Represents a parallel execution block for multiple Cutscene blocks.
92
91
  """
93
92
 
93
+ # Constructor for ParallelBlock, taking a variable number of blocks as arguments
94
94
  def __init__(self, *blocks) -> None:
95
95
  super().__init__()
96
96
  # List of blocks to run in parallel
97
- self.blocks: list[CutsceneBlock] = list(blocks)
97
+ self.blocks: list[CutsceneBlock] = blocks
98
98
 
99
99
  # Start the parallel block (override the base class method)
100
100
  def start(self):
101
+ """
102
+ Start the parallel execution block.
103
+ """
101
104
  super().start()
102
105
  # Start each block in parallel
103
106
  for block in self.blocks:
@@ -105,14 +108,32 @@ class ParallelBlock(CutsceneBlock):
105
108
 
106
109
  # Process an event for each block in parallel
107
110
  def process_event(self, event):
111
+ """
112
+ Process an event for each block in the parallel execution block.
113
+
114
+ Args:
115
+ event: The event to be processed.
116
+ """
108
117
  _ = [b.process_event(event) for b in self.blocks]
109
118
 
110
119
  # Update each block in parallel
111
120
  def update(self, dt):
121
+ """
122
+ Update each block in the parallel execution block.
123
+
124
+ Args:
125
+ dt: Time elapsed since the last update.
126
+ """
112
127
  _ = [b.update(dt) for b in self.blocks]
113
128
 
114
129
  # Check if all blocks have ended
115
130
  def has_ended(self):
131
+ """
132
+ Check if all blocks in the parallel execution block have ended.
133
+
134
+ Returns:
135
+ bool: True if all blocks have ended, False otherwise.
136
+ """
116
137
  return all(b.has_ended() for b in self.blocks)
117
138
 
118
139
 
@@ -123,16 +144,15 @@ class SceneTransitionBlock(CutsceneBlock):
123
144
  """
124
145
 
125
146
  # Constructor for SceneTransitionBlock
126
- def __init__(
127
- self, scene, transition: Transition = Fade(0.1), index: int = 0
128
- ) -> None:
147
+ def __init__(self, scene, transition, duration, **kwargs) -> None:
129
148
  super().__init__()
130
149
  # Target scene, transition type, duration, and additional keyword arguments
131
150
  self.target_scene = scene
132
151
  self.transition = transition
133
- self.index = index
152
+ self.duration = duration
153
+ self.kwargs = kwargs
134
154
  # Timer to handle the end of the transition
135
- self.timer = bf.Timer(transition.duration, self.end)
155
+ self.timer = bf.Timer(name = "scene_transition_block",duration=duration, end_callback=self.end)
136
156
 
137
157
  # Start the scene transition block
138
158
  def start(self):
@@ -140,30 +160,17 @@ class SceneTransitionBlock(CutsceneBlock):
140
160
  Start the scene transition block.
141
161
  """
142
162
  super().start()
163
+ print(f"transition to {scene}")
164
+
143
165
  # Initiate the scene transition
144
- if self.get_current_scene().name == self.target_scene:
166
+ if self.get_current_scene()._name == self.target_scene:
145
167
  self.end()
146
168
  return
147
169
  CutsceneManager().manager.transition_to_scene(
148
- self.target_scene, self.transition, self.index
170
+ self.target_scene, self.transition, duration=self.duration, **self.kwargs
149
171
  )
150
172
  # Start the timer to handle the end of the transition
151
173
  self.timer.start()
152
174
 
153
- class DelayBlock(CutsceneBlock):
154
- def __init__(self, duration) -> None:
155
- super().__init__()
156
- self.timer = bf.Timer(duration=duration, end_callback=self.end)
157
-
158
- def start(self):
159
- super().start()
160
- self.timer.start()
161
-
162
- class FunctionBlock(CutsceneBlock):
163
- def __init__(self, func : Optional[Callable]) -> None:
164
- self.function = func
165
-
166
- def start(self):
167
- super().start()
168
- if self.function : self.function()
169
- self.end()
175
+ def end(self):
176
+ return super().end()
@@ -0,0 +1,48 @@
1
+ import batFramework as bf
2
+ import pygame
3
+
4
+
5
+ class Debugger(bf.Label):
6
+ def __init__(self, manager) -> None:
7
+ super().__init__()
8
+ self.manager: bf.Manager = manager
9
+ self.refresh_rate = 0
10
+ self._refresh_counter = 0
11
+ self.dynamic_data = {}
12
+ self.static_data = {}
13
+ self.render_order = 99
14
+ self.set_outline_color((20, 20, 20))
15
+ # self.set_background_color((0,0,0,0))
16
+ self.set_text_color("white")
17
+ # self.set_padding((30,30))
18
+ self.set_refresh_rate(20)
19
+ self.add_dynamic_data("FPS", lambda: str(round(self.manager.get_fps())))
20
+ self.add_dynamic_data("BLITS", lambda: str(self.parent_scene.blit_calls))
21
+ self.set_visible(False)
22
+
23
+ def set_refresh_rate(self, val: int):
24
+ self.refresh_rate = val
25
+
26
+ def update(self, dt: float):
27
+ visible = self.manager._debugging == 1
28
+ self.set_visible(visible)
29
+ if not visible:
30
+ return
31
+ self._refresh_counter -= dt * 60
32
+ if self._refresh_counter < 0:
33
+ self._refresh_counter = self.refresh_rate
34
+ self._refresh_debug_info()
35
+
36
+ def add_dynamic_data(self, key, func):
37
+ self.dynamic_data[key] = func
38
+
39
+ def set_static_data(self, key, value):
40
+ self.static_data[key] = value
41
+
42
+ def _refresh_debug_info(self):
43
+ lines = []
44
+ lines.extend([f"{key}:{value}" for key, value in self.static_data.items()])
45
+ lines.extend([f"{key}:{func()}" for key, func in self.dynamic_data.items()])
46
+ debug_text = "\n".join(lines)
47
+ self.set_text(debug_text)
48
+ self.update_surface() # Update the surface after modifying the text
@@ -2,31 +2,22 @@ import pygame
2
2
  import batFramework as bf
3
3
  from typing import Self
4
4
 
5
-
6
5
  class DynamicEntity(bf.Entity):
7
6
  def __init__(
8
7
  self,
9
- size: None | tuple[int, int] = None,
10
- surface_flags: int = 0,
11
- convert_alpha: bool = False,*args,**kwargs
8
+ size : None|tuple[int,int]=None,
9
+ no_surface : bool =False,
10
+ surface_flags : int =0,
11
+ convert_alpha : bool=False
12
12
  ) -> None:
13
- super().__init__(size, surface_flags, convert_alpha,*args,**kwargs)
13
+ super().__init__(size,no_surface,surface_flags,convert_alpha)
14
14
  self.velocity = pygame.math.Vector2(0, 0)
15
- self.ignore_collisions : bool = False
16
15
 
17
- def on_collideX(self, collider: "DynamicEntity"):
18
- """
19
- Return true if collision
20
- """
16
+ def on_collideX(self, collider: Self):
21
17
  return False
22
18
 
23
- def on_collideY(self, collider: "DynamicEntity"):
24
- """
25
- Return true if collision
26
- """
19
+ def on_collideY(self, collider: Self):
27
20
  return False
28
21
 
29
- def move_by_velocity(self, dt) -> None:
30
- self.set_position(
31
- self.rect.x + self.velocity.x * dt, self.rect.y + self.velocity.y * dt
32
- )
22
+ def move_by_velocity(self)->None:
23
+ self.set_position(self.rect.x + self.velocity.x, self.rect.y + self.velocity.y)
batFramework/easing.py ADDED
@@ -0,0 +1,71 @@
1
+ from enum import Enum
2
+ import pygame
3
+ import batFramework as bf
4
+
5
+ class Easing(Enum):
6
+ EASE_IN = (0.12, 0, 0.39, 0)
7
+ EASE_OUT = (0.61, 1, 0.88, 1)
8
+ EASE_IN_OUT = (0.37, 0, 0.63, 1)
9
+ EASE_IN_OUT_ELASTIC = (.7,-0.5,.3,1.5)
10
+ LINEAR = (1, 1, 0, 0)
11
+ # Add more easing functions as needed
12
+
13
+ def __init__(self, *control_points):
14
+ self.control_points = control_points
15
+
16
+ class EasingAnimation(bf.Timer):
17
+ _cache = {}
18
+ def __init__(
19
+ self,
20
+ name:str=None,
21
+ easing_function:Easing=Easing.LINEAR,
22
+ duration:int=100,
23
+ update_callback=None,
24
+ end_callback=None,
25
+ loop:bool=False,
26
+ reusable:bool=False
27
+ ):
28
+ self.easing_function = easing_function
29
+ self.update_callback = update_callback
30
+ self.value = 0.0
31
+ super().__init__(name,duration,loop,end_callback,reusable)
32
+
33
+ def get_value(self):
34
+ return self.value
35
+
36
+ def start(self):
37
+ self.value = 0
38
+ super().start() # self.elapsed_progress set to 0 here
39
+
40
+ def update(self)->bool:
41
+ if super().update():
42
+ return True# If timer ended now, end() is called. So don't process value.
43
+ self._process_value()
44
+ # if self.name == 0: print("UPDATING (callback) in easing")
45
+ if self.update_callback: self.update_callback(self.value)
46
+ return False
47
+
48
+ def end(self):
49
+ # Call update 1 last time with the last value
50
+
51
+ self.elapsed_progress = 1
52
+ self._process_value()
53
+ if self.update_callback: self.update_callback(self.value)
54
+ self.value = 0
55
+ super().end() # sets elapsed_progress to 0
56
+
57
+ def _process_value(self):
58
+ p0, p1, p2, p3 = self.easing_function.control_points
59
+ cache_key = (self.elapsed_progress, p0, p1, p2, p3)
60
+ if cache_key in EasingAnimation._cache:
61
+ y = EasingAnimation._cache[cache_key]
62
+ else:
63
+ t = self.elapsed_progress
64
+ t_inv = 1.0 - t
65
+ t2 = t * t
66
+ t3 = t * t2
67
+ t_inv2 = t_inv * t_inv
68
+
69
+ y = 3 * t_inv2 * t * p1 + 3 * t_inv * t2 * p3 + t3
70
+ EasingAnimation._cache[cache_key] = y
71
+ self.value = y
batFramework/entity.py CHANGED
@@ -1,74 +1,123 @@
1
- from typing import Any, Self
2
1
  import pygame
3
2
  import batFramework as bf
4
- from .object import Object
5
-
6
-
7
- class Entity(Object):
8
- """
9
- Basic entity class
10
- """
3
+ from typing import Any
11
4
 
5
+ class Entity:
6
+ instance_num = 0
12
7
  def __init__(
13
8
  self,
14
- size: None | tuple[int, int] = None,
15
- surface_flags: int = 0,
16
- convert_alpha: bool = False,
17
- *args,
18
- **kwargs,
9
+ size : None|tuple[int,int]=None,
10
+ no_surface : bool =False,
11
+ surface_flags : int =0,
12
+ convert_alpha : bool=False
19
13
  ) -> None:
20
- super().__init__()
21
- self.visible: bool = True
22
- self.rect.size = (10, 10) if size is None else size
23
- self.convert_alpha: bool = convert_alpha
24
- self.surface_flags: int = surface_flags
25
- self.blit_flags: int = 0
26
- self.surface: pygame.Surface = pygame.Surface(self.rect.size, surface_flags)
27
- if convert_alpha:
14
+ self.convert_alpha = convert_alpha
15
+ if size is None:
16
+ size= (100,100)
17
+
18
+ if no_surface:
19
+ self.surface = None
20
+ else:
21
+ self.surface = (pygame.Surface(size, surface_flags))
22
+
23
+ if convert_alpha and self.surface is not None:
28
24
  self.surface = self.surface.convert_alpha()
29
- self.surface.fill((0, 0, 0, 0))
25
+ self.surface.fill((0,0,0,0))
30
26
 
31
- def set_alpha(self, alpha: int) -> Self:
32
- self.surface.set_alpha(min(max(0, alpha), 255))
33
- return self
27
+ self.uid: Any = Entity.instance_num
28
+ self.tags: list[str] = []
29
+ self.parent_scene: bf.Scene | None = None
30
+ self.rect = pygame.FRect(0, 0, *size)
31
+
32
+ self.visible = True
33
+ self._debug_color : tuple = bf.color.DARK_RED
34
+ self.render_order = 0
35
+ self.z_depth = 1
36
+ Entity.instance_num += 1
37
+
38
+ def get_bounding_box(self):
39
+ yield (self.rect,self._debug_color)
40
+
41
+ def set_debug_color(self, color):
42
+ self._debug_color = color
43
+
44
+ def set_visible(self, value: bool):
45
+ self.visible = value
46
+
47
+ def set_parent_scene(self, scene):
48
+ self.parent_scene = scene
49
+
50
+ def do_when_added(self):
51
+ pass
34
52
 
35
- def get_alpha(self) -> int:
36
- return self.surface.get_alpha()
53
+ def do_when_removed(self):
54
+ pass
37
55
 
38
- def set_surface_flags(self, surface_flags: int) -> Self:
39
- self.surface_flags = surface_flags
56
+ def set_position(self, x, y):
57
+ self.rect.topleft = (x, y)
40
58
  return self
41
59
 
42
- def set_convert_alpha(self, value: bool) -> Self:
43
- self.convert_alpha = value
60
+ def set_x(self,x):
61
+ self.rect.x = x
44
62
  return self
45
63
 
46
- def set_blit_flags(self, blit_flags: int) -> Self:
47
- self.blit_flags = blit_flags
64
+ def set_y(self,y):
65
+ self.rect.y = y
48
66
  return self
49
67
 
50
- def get_debug_outlines(self):
51
- if self.visible:
52
- yield (self.rect, self.debug_color)
68
+ def set_center(self, x, y):
69
+ self.rect.center = (x, y)
70
+ return self
53
71
 
54
- def set_render_order(self, render_order: int) -> Self:
55
- self.render_order = render_order
56
- if self.parent_scene:
57
- self.parent_scene.sort_entities()
72
+ def set_uid(self, uid):
73
+ self.uid = uid
58
74
  return self
59
75
 
60
- def set_visible(self, value: bool) -> Self:
61
- self.visible = value
76
+ def add_tag(self, *tags):
77
+ for tag in tags:
78
+ if tag not in self.tags:
79
+ self.tags.append(tag)
80
+ self.tags.sort()
62
81
  return self
63
82
 
64
- def draw(self, camera: bf.Camera) -> None:
83
+ def remove_tag(self, *tags):
84
+ self.tags = [tag for tag in self.tags if tag not in tags]
85
+ self.tags.sort()
86
+
87
+ def has_tag(self, tag) -> bool:
88
+ return tag in self.tags
89
+
90
+ def process_event(self, event: pygame.Event)->bool:
65
91
  """
66
- Draw the entity onto the camera surface
92
+ Returns bool : True if the method is blocking (no propagation to next children of the scene)
67
93
  """
68
- if not self.visible or not camera.rect.colliderect(self.rect):
69
- return
94
+ self.do_process_actions(event)
95
+ res = self.do_handle_event(event)
96
+ self.do_reset_actions()
97
+ return res
98
+
99
+ def do_process_actions(self,event : pygame.Event)->None:
100
+ pass
101
+
102
+ def do_reset_actions(self)->None:
103
+ pass
104
+
105
+ def do_handle_event(self, event: pygame.Event) -> bool:
106
+ return False
107
+
108
+ def update(self, dt: float):
109
+ self.do_update(dt)
110
+
111
+ def do_update(self,dt:float):
112
+ pass
113
+
114
+ def draw(self, camera: bf.Camera) -> int:
115
+ if not self.visible:
116
+ return False
117
+ if not self.surface or not camera.intersects(self.rect):
118
+ return False
70
119
  camera.surface.blit(
71
120
  self.surface,
72
- camera.world_to_screen(self.rect),
73
- special_flags=self.blit_flags,
121
+ tuple(round(i * self.z_depth) for i in camera.transpose(self.rect).topleft),
74
122
  )
123
+ return True
@@ -1,22 +1,14 @@
1
1
  from .constraints import *
2
2
  from .widget import Widget
3
- from .styleManager import StyleManager
4
- from .style import Style
5
3
  from .image import Image
6
4
  from .interactiveWidget import InteractiveWidget
7
- from .draggableWidget import DraggableWidget
8
- from .clickableWidget import ClickableWidget
9
5
  from .root import Root
10
6
  from .shape import Shape
11
- from .meter import Meter
7
+ from .frame import Frame
12
8
  from .label import Label
13
- from .dialogueBox import DialogueBox
14
- from .textInput import TextInput
15
9
  from .button import Button
16
- from .debugger import *
10
+ from .debugger import Debugger
17
11
  from .layout import *
18
12
  from .container import Container
19
13
  from .indicator import *
20
14
  from .toggle import Toggle
21
- from .radioButton import RadioButton, RadioVariable
22
- from .slider import Slider
@@ -1,15 +1,84 @@
1
1
  from .label import Label
2
2
  import batFramework as bf
3
- from typing import Self, Callable
4
- from .clickableWidget import ClickableWidget
3
+ from types import FunctionType
4
+ from typing import Self
5
+ from .interactiveWidget import InteractiveWidget
5
6
  import pygame
6
- from math import ceil
7
7
 
8
-
9
- class Button(Label, ClickableWidget):
10
- def __init__(self, text: str = "", callback: None | Callable = None) -> None:
8
+ class Button(Label,InteractiveWidget):
9
+ _cache = {}
10
+
11
+ def __init__(self, text: str, callback : FunctionType =None) -> None:
12
+ # Label.__init__(self,text)
13
+ self.callback = callback
14
+ self.click_action = bf.Action("click").add_mouse_control(1)
15
+ self.hover_action = bf.Action("hover").add_mouse_control(pygame.MOUSEMOTION)
16
+ self.is_hovered : bool = False
17
+ self.is_clicking : bool = False
11
18
  super().__init__(text=text)
12
- self.set_callback(callback)
19
+ self.set_debug_color("cyan")
20
+
21
+
22
+ def set_callback(self,callback : FunctionType = None)->Self:
23
+ self.callback = callback
24
+ return self
25
+
26
+ def on_get_focus(self):
27
+ super().on_get_focus()
28
+ self.build()
29
+
30
+ def on_lose_focus(self):
31
+ super().on_lose_focus()
32
+ self.build()
33
+
34
+ def to_string_id(self)->str:
35
+ return f"Button({self._text})"
36
+
37
+ def click(self)->None:
38
+ if self.callback and not self.is_clicking:
39
+ self.is_clicking = True
40
+ self.callback()
41
+ self.is_clicking = False
42
+
43
+
44
+ def do_process_actions(self,event):
45
+ self.click_action.process_event(event)
46
+ self.hover_action.process_event(event)
47
+
48
+ def do_reset_actions(self):
49
+ self.click_action.reset()
50
+ self.hover_action.reset()
51
+
52
+ def do_handle_event(self,event)->None:
53
+ res = False
54
+ if self.click_action.is_active():
55
+ root = self.get_root()
56
+ if root.hovered == self:
57
+ if not self.is_focused : self.get_focus()
58
+ self.click()
59
+ elif self.hover_action.is_active():
60
+ root = self.get_root()
61
+ if root:
62
+ if self.is_hovered and root.hovered != self:
63
+ self.is_hovered = False
64
+ self.build()
65
+ if not self.is_hovered and root.hovered == self:
66
+ self.is_hovered = True
67
+ self.build()
68
+ return res
69
+
70
+
71
+
72
+ def build(self)->None:
73
+ super().build()
74
+ if self.is_hovered:
75
+ hover_surf = Button._cache.get(self.surface.get_size(),None)
76
+ if hover_surf is None:
77
+ hover_surf = pygame.Surface(self.surface.get_size()).convert_alpha()
78
+ hover_surf.fill((30,30,30,0))
79
+ Button._cache[self.surface.get_size()] = hover_surf
80
+ self.surface.blit(hover_surf,(0,0),special_flags = pygame.BLEND_ADD)
81
+
13
82
 
14
- def __str__(self) -> str:
15
- return f"Button({self.text})"
83
+ def apply_contraints(self)->None:
84
+ super().apply_contraints()