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,415 +1,307 @@
1
- from typing import TYPE_CHECKING, Self, Callable
2
- from collections.abc import Iterable
3
- import batFramework as bf
4
- import pygame
5
-
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
6
3
  if TYPE_CHECKING:
7
- from .constraints.constraints import Constraint
4
+ from .constraints import Constraint
8
5
  from .root import Root
9
- MAX_CONSTRAINTS = 10
10
-
11
-
12
- class WidgetMeta(type):
13
- def __call__(cls, *args, **kwargs):
14
- """Called when you call MyNewClass()"""
15
- obj = type.__call__(cls, *args, **kwargs)
16
- # obj.new_init()
17
- bf.StyleManager().register_widget(obj)
18
- return obj
19
-
20
-
21
- class Widget(bf.Entity, metaclass=WidgetMeta):
22
- def __init__(self, *args, **kwargs) -> None:
23
- super().__init__(*args, **kwargs)
24
- self.children: list["Widget"] = []
25
- self.constraints: list[Constraint] = []
26
- self.parent: "Widget" = None
27
- self.do_sort_children = False
28
- self.clip_children: bool = True
29
- self.padding = (0, 0, 0, 0)
30
- self.dirty_surface: bool = True # if true will call paint before drawing
31
- self.dirty_shape: bool = (
32
- True # if true will call (build+paint) before drawing
33
- )
34
- self.dirty_constraints: bool = False
35
- self.is_root: bool = False
36
- self.autoresize_w, self.autoresize_h = True, True
37
- self.__constraint_iteration = 0
38
- self.__constraints_to_ignore = []
39
- self.__constraints_capture = None
40
-
41
- def show(self) -> Self:
42
- self.visit(lambda w: w.set_visible(True))
43
- return self
6
+ from typing import Self
44
7
 
45
- def hide(self) -> Self:
46
- self.visit(lambda w: w.set_visible(False))
47
- return self
8
+ import batFramework as bf
9
+ import pygame
10
+ from math import ceil
11
+
12
+
13
+
14
+ class Widget(bf.Entity):
15
+ def __init__(self,convert_alpha=True)->None:
16
+ super().__init__(convert_alpha=convert_alpha)
17
+ self.autoresize = False
18
+ self.parent : None|Self = None
19
+ self.is_root :bool = False
20
+ self.children : list["Widget"] = []
21
+ self.focusable :bool= False
22
+ self.constraints : list[Constraint] = []
23
+ self.gui_depth : int = 0
24
+ if self.surface : self.surface.fill("white")
25
+ self.set_debug_color("red")
26
+ self.padding :tuple[float|int,...]= (0,0,0,0)
27
+
28
+ def set_padding(self,value : float|int|tuple|list)->Self:
29
+ old_raw_size = (
30
+ self.rect.w - self.padding[0] - self.padding[2],
31
+ self.rect.h - self.padding[1] - self.padding[3]
32
+ )
33
+ if isinstance(value,list) or isinstance(value,tuple):
34
+ if len(value) > 4 : return self
35
+ if any(v<0 for v in value) : return self
36
+ if len(value) == 2:
37
+ self.padding = (value[0],value[1],value[0],value[1])
38
+ else:
39
+ self.padding = (*value, *self.padding[len(value):])
40
+ else:
41
+ self.padding = (value,)*4
48
42
 
49
- def set_clip_children(self, value: bool) -> Self:
50
- self.clip_children = value
51
- self.dirty_surface = True
43
+ self.set_size(
44
+ old_raw_size[0] + self.padding[0] + self.padding[2],
45
+ old_raw_size[1] + self.padding[1] + self.padding[3],
46
+ )
52
47
  return self
48
+
49
+ def inflate_rect_by_padding(self,rect:pygame.FRect)->pygame.FRect:
50
+ return pygame.FRect(
51
+ rect[0] - self.padding[0],
52
+ rect[1] - self.padding[1],
53
+ rect[2] + self.padding[0]+self.padding[2],
54
+ rect[3] + self.padding[1]+self.padding[3]
55
+ )
56
+ def get_content_left(self)->float:
57
+ return self.rect.left + self.padding[0]
53
58
 
54
- def __str__(self) -> str:
55
- return "Widget"
59
+ def get_content_top(self)->float:
60
+ return self.rect.top + self.padding[1]
56
61
 
57
- def set_autoresize(self, value: bool) -> Self:
58
- self.autoresize_w = self.autoresize_h = value
59
- self.dirty_shape = True
60
- return self
62
+ def get_content_right(self)->float:
63
+ return self.rect.right - self.padding[2]
61
64
 
62
- def set_autoresize_w(self, value: bool) -> Self:
63
- self.autoresize_w = value
64
- self.dirty_shape = True
65
- return self
65
+ def get_content_bottom(self)->float:
66
+ return self.rect.bottom - self.padding[3]
66
67
 
67
- def set_autoresize_h(self, value: bool) -> Self:
68
- self.autoresize_h = value
69
- self.dirty_shape = True
70
- return self
68
+ def get_content_width(self)->float:
69
+ return self.rect.w - self.padding[0] - self.padding[2]
71
70
 
72
- def set_render_order(self, render_order: int) -> Self:
73
- super().set_render_order(render_order)
74
- if self.parent:
75
- self.parent.do_sort_children = True
76
- return self
71
+ def get_content_height(self)->float:
72
+ return self.rect.h - self.padding[1] - self.padding[3]
77
73
 
78
- def inflate_rect_by_padding(
79
- self, rect: pygame.Rect | pygame.FRect
80
- ) -> pygame.Rect | pygame.FRect:
74
+ def get_content_rect(self)->pygame.FRect:
81
75
  return pygame.FRect(
82
- rect[0] - self.padding[0],
83
- rect[1] - self.padding[1],
84
- rect[2] + self.padding[0] + self.padding[2],
85
- rect[3] + self.padding[1] + self.padding[3],
76
+ self.rect.left + self.padding[0],
77
+ self.rect.top + self.padding[1],
78
+ self.get_content_width(),
79
+ self.get_content_height()
86
80
  )
87
81
 
88
- def set_position(self, x, y) -> Self:
89
- if x is None:
90
- x = self.rect.x
91
- if y is None:
92
- y = self.rect.y
93
- if (x, y) == self.rect.topleft:
94
- return self
95
- dx, dy = x - self.rect.x, y - self.rect.y
96
- self.rect.topleft = x, y
97
- _ = [c.set_position(c.rect.x + dx, c.rect.y + dy) for c in self.children]
98
- return self
99
-
100
- def set_center(self, x, y) -> Self:
101
- if x is None:
102
- x = self.rect.centerx
103
- if y is None:
104
- y = self.rect.centery
105
- if (x, y) == self.rect.center:
106
- return self
107
- dx, dy = x - self.rect.centerx, y - self.rect.centery
108
- self.rect.center = x, y
109
- _ = [
110
- c.set_center(c.rect.centerx + dx, c.rect.centery + dy)
111
- for c in self.children
112
- ]
113
- return self
82
+ def get_content_rect_rel(self)->pygame.FRect:
83
+ return self.get_content_rect().move(-self.rect.left,-self.rect.top)
114
84
 
115
- def set_parent_scene(self, parent_scene: bf.Scene | None) -> Self:
116
- super().set_parent_scene(parent_scene)
117
- if parent_scene is None:
118
- bf.StyleManager().remove_widget(self)
85
+ def get_content_center(self)->tuple[float,float]:
86
+ return self.get_content_rect().center
119
87
 
120
- for c in self.children:
121
- c.set_parent_scene(parent_scene)
122
- return self
88
+ def get_depth(self)->int:
89
+ if self.is_root or self.parent is None :
90
+ self.gui_depth = 0
91
+ else:
92
+ self.gui_depth = self.parent.get_depth() + 1
93
+ return self.gui_depth
123
94
 
124
- def set_parent(self, parent: "Widget") -> Self:
125
- if parent == self.parent:
95
+ def top_at(self, x: float, y: float) -> "None|Widget":
96
+ if self.children:
97
+ for child in reversed(self.children):
98
+ r = child.top_at(x,y)
99
+ if r is not None:
100
+ return r
101
+ if self.rect.collidepoint(x,y) and self.visible:
126
102
  return self
127
- # if self.parent is not None and self.parent != parent:
128
- # self.parent.remove(self)
129
- self.parent = parent
103
+ return None
104
+
105
+ def get_constraint(self,name:str)->Constraint|None:
106
+ return next((c for c in self.constraints if c.name == name), None)
107
+
108
+ def add_constraints(self,*constraints:Constraint)->Self:
109
+ for c in constraints:
110
+ self.add_constraint(c,False)
111
+ self.apply_constraints()
130
112
  return self
131
-
132
- def set_padding(self, value: float | int | tuple | list) -> Self:
133
- if isinstance(value, Iterable):
134
- if len(value) > 4:
135
- pass
136
- elif any(v < 0 for v in value):
137
- pass
138
- elif len(value) == 2:
139
- self.padding = (value[0], value[1], value[0], value[1])
140
- else:
141
- self.padding = (*value, *self.padding[len(value) :])
142
- else:
143
- self.padding = (value,) * 4
144
-
145
- self.dirty_shape = True
113
+
114
+ def add_constraint(self,constraint:Constraint,apply:bool=True)->Self:
115
+ c = self.get_constraint(constraint.name)
116
+ if c is not None:
117
+ self.constraints.remove(c)
118
+ self.constraints.append(constraint)
119
+ self.apply_constraints()
146
120
  return self
147
121
 
148
- def get_padded_rect(self) -> pygame.FRect:
149
- r = self.rect.inflate(
150
- -self.padding[0] - self.padding[2], -self.padding[1] - self.padding[3]
151
- )
152
- # r.normalize()
153
- return r
122
+ def has_constraint(self,name:str)->bool:
123
+ return any(c.name == name for c in self.constraints)
154
124
 
155
- def get_min_required_size(self) -> tuple[float, float]:
156
- return self.rect.size
125
+ def apply_all_constraints(self)->None:
126
+ # print("APPLY ALL CONSTRAINTS IN ",self.to_string())
127
+ self.apply_constraints()
128
+ for child in self.children : child.apply_all_constraints()
129
+
130
+ def apply_constraints(self, max_iterations: int = 10) -> None:
131
+ if not self.parent:
132
+ # print(f"Warning : can't apply constraints on {self.to_string()} without parent widget")
133
+ return
134
+ if not self.constraints:
135
+ return
136
+ # Sort constraints based on priority
137
+ self.constraints.sort(key=lambda c: c.priority)
157
138
 
158
- def get_padded_width(self) -> float:
159
- return self.rect.w - self.padding[0] - self.padding[2]
139
+ for iteration in range(max_iterations):
140
+ unsatisfied = [] # Initialize a flag
160
141
 
161
- def get_padded_height(self) -> float:
162
- return self.rect.h - self.padding[1] - self.padding[3]
142
+ for constraint in self.constraints:
143
+ if not constraint.evaluate(self.parent, self):
144
+ unsatisfied.append(constraint)
145
+ constraint.apply(self.parent, self)
146
+ if not unsatisfied:
147
+ # data = ''.join(f"\n\t->{c.to_string()}" for c in self.constraints)
148
+ # print(self.get_depth()*'\t'+f"Following constraints of {self.to_string()} were all satisfied :{data}")
149
+ break
150
+ # print(f"pass {iteration}/{max_iterations} : unsatisfied = {';'.join(c.to_string() for c in unsatisfied)}")
151
+ if iteration == max_iterations - 1:
152
+ raise ValueError(f"[WARNING] Following constraints for {self.to_string()} were not satisfied : \n\t{';'.join([c.to_string() for c in unsatisfied])}")
163
153
 
164
- def get_padded_left(self) -> float:
165
- return self.rect.left + self.padding[0]
154
+ # GETTERS
155
+ def get_root(self)-> Self|Root|None:
156
+ if self.is_root: return self
157
+ if self.parent_scene is not None : return self.parent_scene.root
158
+ return None if self.parent is None else self.parent.get_root()
166
159
 
167
- def get_padded_right(self) -> float:
168
- return self.rect.right + self.padding[2]
160
+ def get_size_int(self)->tuple[int,int]:
161
+ return (ceil(self.rect.width),ceil(self.rect.height))
169
162
 
170
- def get_padded_center(self) -> tuple[float, float]:
171
- return self.get_padded_rect().center
172
163
 
173
- def get_padded_top(self) -> float:
174
- return self.rect.y + self.padding[1]
164
+ def get_center(self)->tuple[float,float]:
165
+ return self.rect.center
175
166
 
176
- def get_padded_bottom(self) -> float:
177
- return self.rect.bottom - self.padding[3]
178
167
 
179
- def get_debug_outlines(self):
180
- if not self.visible:
181
- return
182
- yield (self.rect, self.debug_color)
183
- if any(self.padding):
184
- yield (self.get_padded_rect(), self.debug_color)
168
+ def get_bounding_box(self):
169
+ yield (self.rect,self._debug_color)
170
+ yield (self.get_content_rect(),"yellow")
185
171
  for child in self.children:
186
- yield from child.get_debug_outlines()
187
-
188
- def add_constraints(self, *constraints: "Constraint") -> Self:
189
- self.constraints.extend(constraints)
190
- seen = set()
191
- result = []
192
- for c in self.constraints:
193
- if not any(c == o for o in seen):
194
- result.append(c)
195
- seen.add(c.name)
196
- self.constraints = result
197
- self.constraints.sort(key=lambda c: c.priority)
198
- self.dirty_constraints = True
199
- self.__constraint_to_ignore = []
172
+ yield from child.get_bounding_box()
200
173
 
174
+ def set_autoresize(self,value:bool)-> Self:
175
+ self.autoresize = value
176
+ self.build()
201
177
  return self
202
178
 
179
+ def set_parent(self,parent:Self|None)->None:
180
+ if self.parent:
181
+ self.parent.remove_child(self)
182
+ self.parent = parent
183
+ self.apply_all_constraints()
184
+ # SETTERS
185
+
186
+ def set_root(self) -> Self:
187
+ self.is_root = True
188
+ return self
189
+
190
+ def set_parent_scene(self,scene)->None:
191
+ super().set_parent_scene(scene)
192
+ for child in self.children :
193
+ child.set_parent_scene(scene)
203
194
 
204
- def remove_constraints(self, *names: str) -> Self:
205
- for c in self.constraints:
206
- if c.name in names:
207
- c.on_removal(self)
208
- self.constraints = [c for c in self.constraints if c.name not in names]
209
- self.__constraint_to_ignore = []
195
+ def set_x(self,x:float)->Self:
196
+ delta = x - self.rect.x
197
+ self.rect.x = x
198
+ for child in self.children:
199
+ child.set_x(child.rect.x + delta)
200
+ self.apply_constraints()
210
201
  return self
211
202
 
212
- def resolve_constraints(self) -> None:
213
- if self.parent is None or not self.constraints:
214
- self.dirty_constraints = False
215
- return
203
+ def set_y(self,y:float)->Self:
204
+ delta = y - self.rect.y
205
+ self.rect.y = y
206
+ for child in self.children:
207
+ child.set_y(child.rect.y + delta)
208
+ self.apply_constraints()
216
209
 
217
- if not self.__constraint_iteration:
218
- self.__constraints_capture = None
219
- else:
220
- capture = tuple([c.priority for c in self.constraints])
221
- if capture != self.__constraints_capture:
222
- self.__constraints_capture = capture
223
- self.__constraint_to_ignore = []
224
-
225
- constraints = self.constraints.copy()
226
- # If all are resolved early exit
227
- if all(c.evaluate(self.parent,self) for c in constraints if c not in self.__constraint_to_ignore):
228
- self.dirty_constraints = False
229
- return
210
+ return self
211
+
212
+ def set_position(self,x:float,y:float)->Self:
213
+ delta_x = x - self.rect.x
214
+ delta_y = y - self.rect.y
215
+ self.rect.topleft = x,y
216
+ for child in self.children:
217
+ child.set_position(child.rect.x + delta_x,child.rect.y+delta_y)
218
+ self.apply_constraints()
219
+ return self
230
220
 
231
- # # Here there might be a conflict between 2 or more constraints
232
- # we have to determine which ones causes conflict and ignore the one with least priority
233
-
234
- stop = False
235
-
236
- while True:
237
- stop = True
238
- # first pass with 2 iterations to sort out the transformative constraints
239
- for _ in range(2):
240
- for c in constraints:
241
- if c in self.__constraints_to_ignore:continue
242
- if not c.evaluate(self.parent,self) :
243
- c.apply(self.parent,self)
244
- # second pass where we check conflicts
245
- for c in constraints:
246
- if c in self.__constraints_to_ignore:
247
- continue
248
- if not c.evaluate(self.parent,self):
249
- # first pass invalidated this constraint
250
- self.__constraints_to_ignore.append(c)
251
- stop = False
252
- break
253
-
254
- if stop:
255
- break
221
+ def set_center(self,x:float,y:float)->Self:
222
+ delta_x = x - self.rect.centerx
223
+ delta_y = y - self.rect.centery
224
+ self.rect.center = x,y
225
+ for child in self.children:
226
+ child.set_position(child.rect.x + delta_x,child.rect.y+delta_y)
227
+ self.apply_constraints()
228
+ return self
229
+
256
230
 
257
- if self.__constraints_to_ignore:
258
- print("Constraints ignored : ",[str(c) for c in self.__constraints_to_ignore])
231
+ def set_size(self, width : float, height: float) -> Self:
232
+ self.rect.size = (width,height)
233
+ self.build()
234
+ return self
259
235
 
260
236
 
261
- self.dirty_constraints = False
237
+ # Other Methods
262
238
 
263
- def has_constraint(self, name: str) -> bool:
264
- return any(c.name == name for c in self.constraints)
239
+ def print_tree(self,ident:int=0)->None:
240
+ print('\t'*ident+self.to_string()+(':' if self.children else ''))
241
+ for child in self.children :
242
+ child.print_tree(ident+1)
265
243
 
266
- def get_root(self) -> "Root":
267
- if self.is_root:
268
- return self
269
- if self.parent:
270
- return self.parent.get_root()
271
- return None
244
+ def to_string(self)->str:
245
+
246
+ return f"{self.to_string_id()}@{*self.rect.topleft,* self.rect.size}"
272
247
 
273
- def top_at(self, x: float | int, y: float | int) -> "None|Widget":
274
- if self.children:
275
- for child in reversed(self.children):
276
- if child.visible:
277
- r = child.top_at(x, y)
278
- if r is not None:
279
- return r
280
- return self if self.visible and self.rect.collidepoint(x, y) else None
281
-
282
- def add(self, *children: "Widget") -> Self:
283
- self.children.extend(children)
284
- i = len(self.children)
285
- for child in children:
286
-
287
- child.set_render_order(i).set_parent(self).set_parent_scene(
288
- self.parent_scene
289
- )
290
- i += 1
291
- if self.parent:
292
- self.parent.do_sort_children = True
293
- return self
294
248
 
295
- def remove(self, *children: "Widget") -> Self:
296
- for child in self.children:
297
- if child in children:
298
- child.set_parent(None).set_parent_scene(None)
299
- self.children.remove(child)
300
- if self.parent:
301
- self.parent.do_sort_children = True
249
+ def to_string_id(self)->str:
250
+ return "Widget"
302
251
 
303
- def set_size_if_autoresize(self, size: tuple[float, float]) -> Self:
304
- size = list(size)
305
- size[0] = size[0] if self.autoresize_w else None
306
- size[1] = size[1] if self.autoresize_h else None
307
- self.set_size(size)
308
- return self
309
252
 
310
- def set_size(self, size: tuple) -> Self:
311
- size = list(size)
312
- if size[0] is None:
313
- size[0] = self.rect.w
314
- if size[1] is None:
315
- size[1] = self.rect.h
316
- if size == self.rect.size:
317
- return self
318
- self.rect.size = size
319
- self.dirty_shape = True
320
- return self
253
+ def do_when_removed(self)->None:
254
+ if self.parent_scene and self.parent == self.parent_scene.root:
255
+ self.set_parent(None)
321
256
 
322
- def process_event(self, event: pygame.Event) -> bool:
323
- # First propagate to children
324
- for child in self.children:
325
- child.process_event(event)
326
- # return True if the method is blocking (no propagation to next children of the scene)
327
- super().process_event(event)
328
-
329
- def update(self, dt) -> None:
330
- if self.do_sort_children:
331
- self.children.sort(key=lambda c: c.render_order)
332
- self.do_sort_children = False
333
- _ = [c.update(dt) for c in self.children]
334
- super().update(dt)
335
-
336
- def build(self) -> None:
337
- new_size = tuple(map(int, self.rect.size))
338
- if self.surface.get_size() != new_size:
339
- old_alpha = self.surface.get_alpha()
340
- new_size = [max(0, i) for i in new_size]
341
- self.surface = pygame.Surface(new_size, self.surface_flags)
342
- if self.convert_alpha:
343
- self.surface = self.surface.convert_alpha()
344
- self.surface.set_alpha(old_alpha)
345
-
346
- def paint(self) -> None:
347
- self.surface.fill((0, 0, 0, 0))
348
- return self
257
+ # Methods on children
349
258
 
350
- def visit(self, func: Callable, top_down: bool = True, *args, **kwargs) -> None:
351
- if top_down:
352
- func(self, *args, **kwargs)
353
- for child in self.children:
354
- child.visit(func, top_down)
355
- if not top_down:
356
- func(self, *args, **kwargs)
259
+ def add_child(self,*child:"Widget")->None:
260
+ for c in child :
261
+ self.children.append(c)
262
+ c.set_parent(self)
263
+ c.set_parent_scene(self.parent_scene)
264
+ c.apply_constraints()
265
+ self.children_modified()
357
266
 
358
- def visit_up(self, func, *args, **kwargs) -> None:
359
- if func(self, *args, **kwargs):
360
- return
361
- if self.parent:
362
- self.parent.visit_up(func, *args, **kwargs)
363
-
364
- def selective_up(self, widget: "Widget"):
365
- # if returns True then stop climbing widget tree
366
- if widget.parent and widget.parent.dirty_constraints:
367
- return False
368
- widget.visit(self.selective_down)
369
- return True
370
-
371
- def selective_down(self, widget: "Widget"):
372
- if widget.constraints:
373
- widget.resolve_constraints()
374
- else:
375
- widget.dirty_constraints = False
376
- if widget.dirty_shape:
377
- widget.build()
378
- widget.dirty_shape = False
379
- self.dirty_surface = True
380
-
381
- def draw(self, camera: bf.Camera) -> None:
382
- if self.dirty_shape:
383
- self.dirty_constraints = True
384
- self.dirty_surface = True
385
- self.build()
386
- self.dirty_shape = False
387
- for child in self.children:
388
- child.dirty_constraints = True
389
-
390
- if self.dirty_constraints:
391
- if self.parent and self.parent.dirty_constraints:
392
- self.parent.visit_up(self.selective_up)
393
- else:
394
- self.visit(lambda c: c.resolve_constraints())
267
+ def remove_child(self,child:Self)->None:
268
+ self.children.remove(child)
269
+ child.set_parent(None)
270
+ child.set_parent_scene(None)
271
+ self.children_modified()
395
272
 
396
- if self.dirty_surface:
397
- self.paint()
398
- self.dirty_surface = False
399
273
 
400
- super().draw(camera)
401
274
 
402
- if self.clip_children:
403
275
 
404
- new_clip = camera.world_to_screen(self.get_padded_rect())
405
- old_clip = camera.surface.get_clip()
406
- new_clip = new_clip.clip(old_clip)
407
- camera.surface.set_clip(new_clip)
276
+ # if return True -> don't propagate to siblings or parents
277
+ def process_event(self, event: pygame.Event)->bool:
278
+ # First propagate to children
279
+ for child in self.children:
280
+ if child.process_event(event):
281
+ return True
282
+ #return True if the method is blocking (no propagation to next children of the scene)
283
+ return super().process_event(event)
408
284
 
409
- _ = [
410
- child.draw(camera)
411
- for child in sorted(self.children, key=lambda c: c.render_order)
412
- ]
413
285
 
414
- if self.clip_children:
415
- camera.surface.set_clip(old_clip)
286
+ def update(self,dt:float):
287
+ for child in self.children:
288
+ child.update(dt)
289
+
290
+ def draw(self, camera: bf.Camera) -> int:
291
+ self.children.sort(key=lambda e: (e.z_depth,e.render_order))
292
+ return super().draw(camera) + sum([child.draw(camera) for child in self.children])
293
+
294
+ def build(self)->None:
295
+ """
296
+ This function is called each time the widget's surface has to be updated
297
+ It usually has to be overriden if inherited to suit the needs of the new class
298
+ """
299
+ if not self.surface: return
300
+ if self.surface.get_size() != self.get_size_int():
301
+ self.surface = pygame.Surface(self.get_size_int())
302
+ if self.parent : self.parent.children_modified()
303
+
304
+ def children_modified(self)->None:
305
+ self.apply_constraints()
306
+ if self.parent and not self.is_root:
307
+ self.parent.children_modified()