batframework 1.0.8a2__py3-none-any.whl → 1.0.8a4__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 (65) hide show
  1. batFramework/__init__.py +53 -50
  2. batFramework/action.py +126 -99
  3. batFramework/actionContainer.py +53 -9
  4. batFramework/animatedSprite.py +117 -73
  5. batFramework/audioManager.py +69 -26
  6. batFramework/camera.py +259 -69
  7. batFramework/constants.py +16 -54
  8. batFramework/cutscene.py +39 -29
  9. batFramework/cutsceneBlocks.py +36 -43
  10. batFramework/dynamicEntity.py +17 -9
  11. batFramework/easingController.py +58 -0
  12. batFramework/entity.py +48 -97
  13. batFramework/enums.py +113 -0
  14. batFramework/fontManager.py +65 -0
  15. batFramework/gui/__init__.py +10 -2
  16. batFramework/gui/button.py +9 -78
  17. batFramework/gui/clickableWidget.py +221 -0
  18. batFramework/gui/constraints/__init__.py +1 -0
  19. batFramework/gui/constraints/constraints.py +730 -0
  20. batFramework/gui/container.py +174 -32
  21. batFramework/gui/debugger.py +131 -43
  22. batFramework/gui/dialogueBox.py +99 -0
  23. batFramework/gui/draggableWidget.py +40 -0
  24. batFramework/gui/image.py +54 -18
  25. batFramework/gui/indicator.py +38 -21
  26. batFramework/gui/interactiveWidget.py +177 -13
  27. batFramework/gui/label.py +292 -74
  28. batFramework/gui/layout.py +219 -60
  29. batFramework/gui/meter.py +71 -0
  30. batFramework/gui/radioButton.py +84 -0
  31. batFramework/gui/root.py +134 -38
  32. batFramework/gui/shape.py +259 -57
  33. batFramework/gui/slider.py +230 -0
  34. batFramework/gui/style.py +10 -0
  35. batFramework/gui/styleManager.py +48 -0
  36. batFramework/gui/textInput.py +137 -0
  37. batFramework/gui/toggle.py +103 -51
  38. batFramework/gui/widget.py +329 -254
  39. batFramework/manager.py +40 -19
  40. batFramework/object.py +114 -0
  41. batFramework/particle.py +101 -0
  42. batFramework/renderGroup.py +67 -0
  43. batFramework/resourceManager.py +100 -0
  44. batFramework/scene.py +281 -123
  45. batFramework/sceneManager.py +141 -108
  46. batFramework/scrollingSprite.py +114 -0
  47. batFramework/sprite.py +51 -0
  48. batFramework/stateMachine.py +2 -2
  49. batFramework/tileset.py +46 -0
  50. batFramework/time.py +123 -58
  51. batFramework/transition.py +195 -124
  52. batFramework/utils.py +87 -151
  53. batframework-1.0.8a4.dist-info/LICENCE +21 -0
  54. batframework-1.0.8a4.dist-info/METADATA +55 -0
  55. batframework-1.0.8a4.dist-info/RECORD +58 -0
  56. batFramework/debugger.py +0 -48
  57. batFramework/easing.py +0 -71
  58. batFramework/gui/constraints.py +0 -204
  59. batFramework/gui/frame.py +0 -19
  60. batFramework/particles.py +0 -77
  61. batFramework/transitionManager.py +0 -0
  62. batframework-1.0.8a2.dist-info/METADATA +0 -58
  63. batframework-1.0.8a2.dist-info/RECORD +0 -42
  64. {batframework-1.0.8a2.dist-info → batframework-1.0.8a4.dist-info}/WHEEL +0 -0
  65. {batframework-1.0.8a2.dist-info → batframework-1.0.8a4.dist-info}/top_level.txt +0 -0
@@ -1,307 +1,382 @@
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
+
39
+ def show(self) -> Self:
40
+ self.visit(lambda w: w.set_visible(True))
47
41
  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
42
 
59
- def get_content_top(self)->float:
60
- return self.rect.top + self.padding[1]
43
+ def hide(self) -> Self:
44
+ self.visit(lambda w: w.set_visible(False))
45
+ return self
61
46
 
62
- def get_content_right(self)->float:
63
- return self.rect.right - self.padding[2]
47
+ def set_clip_children(self, value: bool) -> Self:
48
+ self.clip_children = value
49
+ self.dirty_surface = True
50
+ return self
64
51
 
65
- def get_content_bottom(self)->float:
66
- return self.rect.bottom - self.padding[3]
52
+ def __str__(self) -> str:
53
+ return "Widget"
67
54
 
68
- def get_content_width(self)->float:
69
- return self.rect.w - self.padding[0] - self.padding[2]
55
+ def set_autoresize(self, value: bool) -> Self:
56
+ self.autoresize_w = self.autoresize_h = value
57
+ self.dirty_shape = True
58
+ return self
70
59
 
71
- def get_content_height(self)->float:
72
- return self.rect.h - self.padding[1] - self.padding[3]
60
+ def set_autoresize_w(self, value: bool) -> Self:
61
+ self.autoresize_w = value
62
+ self.dirty_shape = True
63
+ return self
64
+
65
+ def set_autoresize_h(self, value: bool) -> Self:
66
+ self.autoresize_h = value
67
+ self.dirty_shape = True
68
+ return self
69
+
70
+ def set_render_order(self, render_order: int) -> Self:
71
+ super().set_render_order(render_order)
72
+ if self.parent:
73
+ self.parent.do_sort_children = True
74
+ return self
73
75
 
74
- def get_content_rect(self)->pygame.FRect:
76
+ def inflate_rect_by_padding(
77
+ self, rect: pygame.Rect | pygame.FRect
78
+ ) -> pygame.Rect | pygame.FRect:
75
79
  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()
80
+ rect[0] - self.padding[0],
81
+ rect[1] - self.padding[1],
82
+ rect[2] + self.padding[0] + self.padding[2],
83
+ rect[3] + self.padding[1] + self.padding[3],
80
84
  )
81
85
 
82
- def get_content_rect_rel(self)->pygame.FRect:
83
- return self.get_content_rect().move(-self.rect.left,-self.rect.top)
86
+ def set_position(self, x, y) -> Self:
87
+ if x is None:
88
+ x = self.rect.x
89
+ if y is None:
90
+ y = self.rect.y
91
+ if (x, y) == self.rect.topleft:
92
+ return self
93
+ dx, dy = x - self.rect.x, y - self.rect.y
94
+ self.rect.topleft = x, y
95
+ _ = [c.set_position(c.rect.x + dx, c.rect.y + dy) for c in self.children]
96
+ return self
97
+
98
+ def set_center(self, x, y) -> Self:
99
+ if x is None:
100
+ x = self.rect.centerx
101
+ if y is None:
102
+ y = self.rect.centery
103
+ if (x, y) == self.rect.center:
104
+ return self
105
+ dx, dy = x - self.rect.centerx, y - self.rect.centery
106
+ self.rect.center = x, y
107
+ _ = [
108
+ c.set_center(c.rect.centerx + dx, c.rect.centery + dy)
109
+ for c in self.children
110
+ ]
111
+ return self
84
112
 
85
- def get_content_center(self)->tuple[float,float]:
86
- return self.get_content_rect().center
113
+ def set_parent_scene(self, parent_scene: bf.Scene) -> Self:
114
+ super().set_parent_scene(parent_scene)
115
+ if parent_scene is None:
116
+ bf.StyleManager().remove_widget(self)
87
117
 
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
118
+ for c in self.children:
119
+ c.set_parent_scene(parent_scene)
120
+ return self
94
121
 
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:
122
+ def set_parent(self, parent: "Widget") -> Self:
123
+ if parent == self.parent:
102
124
  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()
125
+ if self.parent is not None:
126
+ self.parent.remove(self)
127
+ self.parent = parent
112
128
  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()
129
+
130
+ def set_padding(self, value: float | int | tuple | list) -> Self:
131
+ if isinstance(value, Iterable):
132
+ if len(value) > 4:
133
+ pass
134
+ elif any(v < 0 for v in value):
135
+ pass
136
+ elif len(value) == 2:
137
+ self.padding = (value[0], value[1], value[0], value[1])
138
+ else:
139
+ self.padding = (*value, *self.padding[len(value) :])
140
+ else:
141
+ self.padding = (value,) * 4
142
+
143
+ self.dirty_shape = True
120
144
  return self
121
145
 
122
- def has_constraint(self,name:str)->bool:
123
- return any(c.name == name for c in self.constraints)
146
+ def get_padded_rect(self) -> pygame.FRect:
147
+ r = self.rect.inflate(
148
+ -self.padding[0] - self.padding[2], -self.padding[1] - self.padding[3]
149
+ )
150
+ # r.normalize()
151
+ return r
124
152
 
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)
153
+ def get_min_required_size(self) -> tuple[float, float]:
154
+ return self.rect.size
138
155
 
139
- for iteration in range(max_iterations):
140
- unsatisfied = [] # Initialize a flag
156
+ def get_padded_width(self) -> float:
157
+ return self.rect.w - self.padding[0] - self.padding[2]
141
158
 
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])}")
159
+ def get_padded_height(self) -> float:
160
+ return self.rect.h - self.padding[1] - self.padding[3]
153
161
 
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()
162
+ def get_padded_left(self) -> float:
163
+ return self.rect.left + self.padding[0]
159
164
 
160
- def get_size_int(self)->tuple[int,int]:
161
- return (ceil(self.rect.width),ceil(self.rect.height))
165
+ def get_padded_right(self) -> float:
166
+ return self.rect.right + self.padding[2]
162
167
 
168
+ def get_padded_center(self) -> tuple[float, float]:
169
+ return self.get_padded_rect().center
163
170
 
164
- def get_center(self)->tuple[float,float]:
165
- return self.rect.center
171
+ def get_padded_top(self) -> float:
172
+ return self.rect.y + self.padding[1]
166
173
 
174
+ def get_padded_bottom(self) -> float:
175
+ return self.rect.bottom - self.padding[3]
167
176
 
168
- def get_bounding_box(self):
169
- yield (self.rect,self._debug_color)
170
- yield (self.get_content_rect(),"yellow")
177
+ def get_debug_outlines(self):
178
+ if not self.visible:
179
+ return
180
+ yield (self.rect, self.debug_color)
181
+ if any(self.padding):
182
+ yield (self.get_padded_rect(), self.debug_color)
171
183
  for child in self.children:
172
- yield from child.get_bounding_box()
173
-
174
- def set_autoresize(self,value:bool)-> Self:
175
- self.autoresize = value
176
- self.build()
184
+ yield from child.get_debug_outlines()
185
+
186
+ def add_constraints(self, *constraints: "Constraint") -> Self:
187
+ self.constraints.extend(constraints)
188
+ seen = set()
189
+ result = []
190
+ for c in self.constraints:
191
+ if not any(c == o for o in seen):
192
+ result.append(c)
193
+ seen.add(c.name)
194
+ self.constraints = result
195
+ self.constraints.sort(key=lambda c: c.priority)
196
+ self.dirty_constraints = True
177
197
  return self
178
198
 
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
199
+ def resolve_constraints(self) -> None:
200
+ if self.parent is None or not self.constraints:
201
+ self.dirty_constraints = False
202
+ return
203
+ all_good = False
204
+ constraint_iteration = 0
205
+ while not all_good:
206
+ for constraint in self.constraints:
207
+ if not constraint.evaluate(self.parent, self):
208
+ constraint.apply(self.parent, self)
209
+ # print(constraint.name,"Applied")
210
+ constraint_iteration += 1
211
+ if all(c.evaluate(self.parent, self) for c in self.constraints):
212
+ all_good = True
213
+ break
214
+ elif self.__constraint_iteration > MAX_CONSTRAINTS:
215
+ print(
216
+ self,
217
+ "CONSTRAINTS ERROR",
218
+ list(
219
+ c.name
220
+ for c in self.constraints
221
+ if not c.evaluate(self.parent, self)
222
+ ),
223
+ )
224
+ self.dirty_constraints = False
225
+ return
226
+ # print("DONE")
227
+ self.dirty_constraints = False
228
+
229
+ def remove_constraints(self, *names: str) -> Self:
230
+ self.constraints = [c for c in self.constraints if c.name not in names]
188
231
  return self
189
232
 
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)
233
+ def has_constraint(self, name: str) -> bool:
234
+ return any(c.name == name for c in self.constraints)
194
235
 
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()
236
+ def get_root(self) -> "Root":
237
+ if self.is_root:
238
+ return self
239
+ return self.parent.get_root()
240
+
241
+ def top_at(self, x: float | int, y: float | int) -> "None|Widget":
242
+ if self.children:
243
+ for child in reversed(self.children):
244
+ if child.visible:
245
+ r = child.top_at(x, y)
246
+ if r is not None:
247
+ return r
248
+ return self if self.visible and self.rect.collidepoint(x, y) else None
249
+
250
+ def add(self, *children: "Widget") -> Self:
251
+ self.children.extend(children)
252
+ i = len(self.children)
253
+ for child in children:
254
+ child.set_render_order(i).set_parent(self).set_parent_scene(
255
+ self.parent_scene
256
+ )
257
+ i += 1
258
+ if self.parent:
259
+ self.parent.do_sort_children = True
201
260
  return self
202
261
 
203
- def set_y(self,y:float)->Self:
204
- delta = y - self.rect.y
205
- self.rect.y = y
262
+ def remove(self, *children: "Widget") -> Self:
206
263
  for child in self.children:
207
- child.set_y(child.rect.y + delta)
208
- self.apply_constraints()
264
+ if child in children:
265
+ self.children.remove(child)
266
+ child.set_parent(None).set_parent_scene(None)
267
+ if self.parent:
268
+ self.parent.do_sort_children = True
209
269
 
270
+ def set_size_if_autoresize(self, size: tuple[float, float]) -> Self:
271
+ size = list(size)
272
+ size[0] = size[0] if self.autoresize_w else None
273
+ size[1] = size[1] if self.autoresize_h else None
274
+ self.set_size(size)
210
275
  return self
211
276
 
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
220
-
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()
277
+ def set_size(self, size: tuple) -> Self:
278
+ size = list(size)
279
+ if size[0] is None:
280
+ size[0] = self.rect.w
281
+ if size[1] is None:
282
+ size[1] = self.rect.h
283
+ if size == self.rect.size:
284
+ return self
285
+ self.rect.size = size
286
+ self.dirty_shape = True
228
287
  return self
229
288
 
230
-
231
- def set_size(self, width : float, height: float) -> Self:
232
- self.rect.size = (width,height)
233
- self.build()
289
+ def process_event(self, event: pygame.Event) -> bool:
290
+ # First propagate to children
291
+ for child in self.children:
292
+ child.process_event(event)
293
+ # return True if the method is blocking (no propagation to next children of the scene)
294
+ super().process_event(event)
295
+
296
+ def update(self, dt) -> None:
297
+ if self.do_sort_children:
298
+ self.children.sort(key=lambda c: c.render_order)
299
+ self.do_sort_children = False
300
+ _ = [c.update(dt) for c in self.children]
301
+ super().update(dt)
302
+
303
+ def build(self) -> None:
304
+ new_size = tuple(map(int, self.rect.size))
305
+ if self.surface.get_size() != new_size:
306
+ old_alpha = self.surface.get_alpha()
307
+ new_size = [max(0, i) for i in new_size]
308
+ self.surface = pygame.Surface(new_size, self.surface_flags)
309
+ if self.convert_alpha:
310
+ self.surface = self.surface.convert_alpha()
311
+ self.surface.set_alpha(old_alpha)
312
+
313
+ def paint(self) -> None:
314
+ self.surface.fill((0, 0, 0, 0))
234
315
  return self
235
-
236
-
237
- # Other Methods
238
-
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)
243
-
244
- def to_string(self)->str:
245
-
246
- return f"{self.to_string_id()}@{*self.rect.topleft,* self.rect.size}"
247
-
248
-
249
- def to_string_id(self)->str:
250
- return "Widget"
251
-
252
-
253
- def do_when_removed(self)->None:
254
- if self.parent_scene and self.parent == self.parent_scene.root:
255
- self.set_parent(None)
256
316
 
257
- # Methods on children
258
-
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()
317
+ def visit(self, func: Callable, top_down: bool = True, *args, **kwargs) -> None:
318
+ if top_down:
319
+ func(self, *args, **kwargs)
320
+ for child in self.children:
321
+ child.visit(func, top_down)
322
+ if not top_down:
323
+ func(self, *args, **kwargs)
266
324
 
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()
325
+ def visit_up(self, func, *args, **kwargs) -> None:
326
+ if func(self, *args, **kwargs):
327
+ return
328
+ if self.parent:
329
+ self.parent.visit_up(func, *args, **kwargs)
330
+
331
+ def selective_up(self, widget: "Widget"):
332
+ # if returns True then stop climbing widget tree
333
+ if widget.parent and widget.parent.dirty_constraints:
334
+ return False
335
+ widget.visit(self.selective_down)
336
+ return True
337
+
338
+ def selective_down(self, widget: "Widget"):
339
+ if widget.constraints:
340
+ widget.resolve_constraints()
341
+ else:
342
+ widget.dirty_constraints = False
343
+ if widget.dirty_shape:
344
+ widget.build()
345
+ widget.dirty_shape = False
346
+ self.dirty_surface = True
347
+
348
+ def draw(self, camera: bf.Camera) -> None:
349
+ if self.dirty_shape:
350
+ self.dirty_constraints = True
351
+ self.dirty_surface = True
352
+ self.build()
353
+ self.dirty_shape = False
354
+ for child in self.children:
355
+ child.dirty_constraints = True
356
+
357
+ if self.dirty_constraints:
358
+ if self.parent and self.parent.dirty_constraints:
359
+ self.parent.visit_up(self.selective_up)
360
+ else:
361
+ self.visit(lambda c: c.resolve_constraints())
272
362
 
363
+ if self.dirty_surface:
364
+ self.paint()
365
+ self.dirty_surface = False
273
366
 
367
+ super().draw(camera)
274
368
 
369
+ if self.clip_children:
275
370
 
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)
371
+ new_clip = camera.world_to_screen(self.get_padded_rect())
372
+ old_clip = camera.surface.get_clip()
373
+ new_clip = new_clip.clip(old_clip)
374
+ camera.surface.set_clip(new_clip)
284
375
 
376
+ _ = [
377
+ child.draw(camera)
378
+ for child in sorted(self.children, key=lambda c: c.render_order)
379
+ ]
285
380
 
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()
381
+ if self.clip_children:
382
+ camera.surface.set_clip(old_clip)