batframework 1.0.8a8__py3-none-any.whl → 1.0.8a10__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 +68 -51
  2. batFramework/action.py +126 -99
  3. batFramework/actionContainer.py +53 -9
  4. batFramework/animatedSprite.py +141 -82
  5. batFramework/audioManager.py +69 -26
  6. batFramework/camera.py +259 -69
  7. batFramework/character.py +27 -0
  8. batFramework/constants.py +16 -54
  9. batFramework/cutscene.py +39 -29
  10. batFramework/cutsceneBlocks.py +36 -43
  11. batFramework/dynamicEntity.py +18 -9
  12. batFramework/easingController.py +58 -0
  13. batFramework/entity.py +48 -97
  14. batFramework/enums.py +113 -0
  15. batFramework/fontManager.py +65 -0
  16. batFramework/gui/__init__.py +10 -2
  17. batFramework/gui/button.py +9 -78
  18. batFramework/gui/clickableWidget.py +220 -0
  19. batFramework/gui/constraints/__init__.py +1 -0
  20. batFramework/gui/constraints/constraints.py +815 -0
  21. batFramework/gui/container.py +174 -32
  22. batFramework/gui/debugger.py +131 -43
  23. batFramework/gui/dialogueBox.py +99 -0
  24. batFramework/gui/draggableWidget.py +40 -0
  25. batFramework/gui/image.py +56 -20
  26. batFramework/gui/indicator.py +38 -21
  27. batFramework/gui/interactiveWidget.py +192 -13
  28. batFramework/gui/label.py +309 -74
  29. batFramework/gui/layout.py +231 -63
  30. batFramework/gui/meter.py +74 -0
  31. batFramework/gui/radioButton.py +84 -0
  32. batFramework/gui/root.py +134 -38
  33. batFramework/gui/shape.py +237 -57
  34. batFramework/gui/slider.py +240 -0
  35. batFramework/gui/style.py +10 -0
  36. batFramework/gui/styleManager.py +48 -0
  37. batFramework/gui/textInput.py +247 -0
  38. batFramework/gui/toggle.py +101 -51
  39. batFramework/gui/widget.py +358 -250
  40. batFramework/manager.py +52 -19
  41. batFramework/object.py +123 -0
  42. batFramework/particle.py +115 -0
  43. batFramework/renderGroup.py +67 -0
  44. batFramework/resourceManager.py +100 -0
  45. batFramework/scene.py +281 -123
  46. batFramework/sceneManager.py +178 -116
  47. batFramework/scrollingSprite.py +114 -0
  48. batFramework/sprite.py +51 -0
  49. batFramework/stateMachine.py +11 -8
  50. batFramework/templates/__init__.py +2 -0
  51. batFramework/templates/character.py +44 -0
  52. batFramework/templates/states.py +166 -0
  53. batFramework/tileset.py +46 -0
  54. batFramework/time.py +145 -58
  55. batFramework/transition.py +195 -124
  56. batFramework/triggerZone.py +1 -1
  57. batFramework/utils.py +112 -147
  58. batframework-1.0.8a10.dist-info/LICENCE +21 -0
  59. batframework-1.0.8a10.dist-info/METADATA +43 -0
  60. batframework-1.0.8a10.dist-info/RECORD +62 -0
  61. batFramework/debugger.py +0 -48
  62. batFramework/easing.py +0 -71
  63. batFramework/gui/constraints.py +0 -204
  64. batFramework/gui/frame.py +0 -19
  65. batFramework/particles.py +0 -77
  66. batFramework/transitionManager.py +0 -0
  67. batframework-1.0.8a8.dist-info/METADATA +0 -53
  68. batframework-1.0.8a8.dist-info/RECORD +0 -42
  69. {batframework-1.0.8a8.dist-info → batframework-1.0.8a10.dist-info}/WHEEL +0 -0
  70. {batframework-1.0.8a8.dist-info → batframework-1.0.8a10.dist-info}/top_level.txt +0 -0
@@ -1,307 +1,415 @@
1
- from __future__ import annotations
2
- from typing import TYPE_CHECKING
3
- if TYPE_CHECKING:
4
- from .constraints import Constraint
5
- from .root import Root
6
- from typing import Self
7
-
1
+ from typing import TYPE_CHECKING, Self, Callable
2
+ from collections.abc import Iterable
8
3
  import batFramework as bf
9
4
  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
42
5
 
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],
6
+ if TYPE_CHECKING:
7
+ from .constraints.constraints import Constraint
8
+ 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
46
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))
47
43
  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]
58
44
 
59
- def get_content_top(self)->float:
60
- return self.rect.top + self.padding[1]
45
+ def hide(self) -> Self:
46
+ self.visit(lambda w: w.set_visible(False))
47
+ return self
61
48
 
62
- def get_content_right(self)->float:
63
- return self.rect.right - self.padding[2]
49
+ def set_clip_children(self, value: bool) -> Self:
50
+ self.clip_children = value
51
+ self.dirty_surface = True
52
+ return self
64
53
 
65
- def get_content_bottom(self)->float:
66
- return self.rect.bottom - self.padding[3]
54
+ def __str__(self) -> str:
55
+ return "Widget"
67
56
 
68
- def get_content_width(self)->float:
69
- return self.rect.w - self.padding[0] - self.padding[2]
57
+ def set_autoresize(self, value: bool) -> Self:
58
+ self.autoresize_w = self.autoresize_h = value
59
+ self.dirty_shape = True
60
+ return self
70
61
 
71
- def get_content_height(self)->float:
72
- return self.rect.h - self.padding[1] - self.padding[3]
62
+ def set_autoresize_w(self, value: bool) -> Self:
63
+ self.autoresize_w = value
64
+ self.dirty_shape = True
65
+ return self
66
+
67
+ def set_autoresize_h(self, value: bool) -> Self:
68
+ self.autoresize_h = value
69
+ self.dirty_shape = True
70
+ return self
71
+
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
73
77
 
74
- def get_content_rect(self)->pygame.FRect:
78
+ def inflate_rect_by_padding(
79
+ self, rect: pygame.Rect | pygame.FRect
80
+ ) -> pygame.Rect | pygame.FRect:
75
81
  return pygame.FRect(
76
- self.rect.left + self.padding[0],
77
- self.rect.top + self.padding[1],
78
- self.get_content_width(),
79
- self.get_content_height()
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],
80
86
  )
81
87
 
82
- def get_content_rect_rel(self)->pygame.FRect:
83
- return self.get_content_rect().move(-self.rect.left,-self.rect.top)
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
84
99
 
85
- def get_content_center(self)->tuple[float,float]:
86
- return self.get_content_rect().center
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
87
114
 
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
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)
94
119
 
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:
120
+ for c in self.children:
121
+ c.set_parent_scene(parent_scene)
122
+ return self
123
+
124
+ def set_parent(self, parent: "Widget") -> Self:
125
+ if parent == self.parent:
102
126
  return self
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()
127
+ # if self.parent is not None and self.parent != parent:
128
+ # self.parent.remove(self)
129
+ self.parent = parent
112
130
  return self
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()
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
120
146
  return self
121
147
 
122
- def has_constraint(self,name:str)->bool:
123
- return any(c.name == name for c in self.constraints)
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
124
154
 
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)
155
+ def get_min_required_size(self) -> tuple[float, float]:
156
+ return self.rect.size
138
157
 
139
- for iteration in range(max_iterations):
140
- unsatisfied = [] # Initialize a flag
158
+ def get_padded_width(self) -> float:
159
+ return self.rect.w - self.padding[0] - self.padding[2]
141
160
 
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])}")
161
+ def get_padded_height(self) -> float:
162
+ return self.rect.h - self.padding[1] - self.padding[3]
153
163
 
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()
164
+ def get_padded_left(self) -> float:
165
+ return self.rect.left + self.padding[0]
159
166
 
160
- def get_size_int(self)->tuple[int,int]:
161
- return (ceil(self.rect.width),ceil(self.rect.height))
167
+ def get_padded_right(self) -> float:
168
+ return self.rect.right + self.padding[2]
162
169
 
170
+ def get_padded_center(self) -> tuple[float, float]:
171
+ return self.get_padded_rect().center
163
172
 
164
- def get_center(self)->tuple[float,float]:
165
- return self.rect.center
173
+ def get_padded_top(self) -> float:
174
+ return self.rect.y + self.padding[1]
166
175
 
176
+ def get_padded_bottom(self) -> float:
177
+ return self.rect.bottom - self.padding[3]
167
178
 
168
- def get_bounding_box(self):
169
- yield (self.rect,self._debug_color)
170
- yield (self.get_content_rect(),"yellow")
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)
171
185
  for child in self.children:
172
- yield from child.get_bounding_box()
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 = []
173
200
 
174
- def set_autoresize(self,value:bool)-> Self:
175
- self.autoresize = value
176
- self.build()
177
201
  return self
178
202
 
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)
194
203
 
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()
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 = []
201
210
  return self
202
211
 
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()
209
-
210
- return self
212
+ def resolve_constraints(self) -> None:
213
+ if self.parent is None or not self.constraints:
214
+ self.dirty_constraints = False
215
+ return
211
216
 
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
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
220
230
 
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
-
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
230
256
 
231
- def set_size(self, width : float, height: float) -> Self:
232
- self.rect.size = (width,height)
233
- self.build()
234
- return self
257
+ if self.__constraints_to_ignore:
258
+ print("Constraints ignored : ",[str(c) for c in self.__constraints_to_ignore])
235
259
 
236
260
 
237
- # Other Methods
261
+ self.dirty_constraints = False
238
262
 
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)
263
+ def has_constraint(self, name: str) -> bool:
264
+ return any(c.name == name for c in self.constraints)
243
265
 
244
- def to_string(self)->str:
245
-
246
- return f"{self.to_string_id()}@{*self.rect.topleft,* self.rect.size}"
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
247
272
 
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
248
294
 
249
- def to_string_id(self)->str:
250
- return "Widget"
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
251
302
 
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
252
309
 
253
- def do_when_removed(self)->None:
254
- if self.parent_scene and self.parent == self.parent_scene.root:
255
- self.set_parent(None)
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
256
321
 
257
- # Methods on children
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
258
349
 
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()
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)
266
357
 
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()
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())
272
395
 
396
+ if self.dirty_surface:
397
+ self.paint()
398
+ self.dirty_surface = False
273
399
 
400
+ super().draw(camera)
274
401
 
402
+ if self.clip_children:
275
403
 
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)
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)
284
408
 
409
+ _ = [
410
+ child.draw(camera)
411
+ for child in sorted(self.children, key=lambda c: c.render_order)
412
+ ]
285
413
 
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()
414
+ if self.clip_children:
415
+ camera.surface.set_clip(old_clip)