batframework 1.0.8a2__py3-none-any.whl → 1.0.8a3__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 +115 -65
  5. batFramework/audioManager.py +69 -26
  6. batFramework/camera.py +259 -69
  7. batFramework/constants.py +16 -54
  8. batFramework/cutscene.py +36 -29
  9. batFramework/cutsceneBlocks.py +37 -42
  10. batFramework/dynamicEntity.py +9 -7
  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 +219 -0
  18. batFramework/gui/constraints/__init__.py +1 -0
  19. batFramework/gui/constraints/constraints.py +590 -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 +288 -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 +128 -38
  32. batFramework/gui/shape.py +253 -57
  33. batFramework/gui/slider.py +246 -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 +115 -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 +84 -0
  44. batFramework/scene.py +242 -114
  45. batFramework/sceneManager.py +145 -107
  46. batFramework/scrollingSprite.py +115 -0
  47. batFramework/sprite.py +51 -0
  48. batFramework/stateMachine.py +2 -2
  49. batFramework/tileset.py +46 -0
  50. batFramework/time.py +117 -57
  51. batFramework/transition.py +184 -126
  52. batFramework/utils.py +31 -156
  53. batframework-1.0.8a3.dist-info/LICENCE +21 -0
  54. batframework-1.0.8a3.dist-info/METADATA +55 -0
  55. batframework-1.0.8a3.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.8a3.dist-info}/WHEEL +0 -0
  65. {batframework-1.0.8a2.dist-info → batframework-1.0.8a3.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],
46
- )
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]
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
55
33
  )
56
- def get_content_left(self)->float:
57
- return self.rect.left + self.padding[0]
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
58
38
 
59
- def get_content_top(self)->float:
60
- return self.rect.top + self.padding[1]
39
+ def show(self) -> Self:
40
+ self.visit(lambda w: w.set_visible(True))
41
+ return self
61
42
 
62
- def get_content_right(self)->float:
63
- return self.rect.right - self.padding[2]
43
+ def hide(self) -> Self:
44
+ self.visit(lambda w: w.set_visible(False))
45
+ return self
64
46
 
65
- def get_content_bottom(self)->float:
66
- return self.rect.bottom - self.padding[3]
47
+ def set_clip_children(self, value: bool) -> Self:
48
+ self.clip_children = value
49
+ self.dirty_surface = True
50
+ return self
67
51
 
68
- def get_content_width(self)->float:
69
- return self.rect.w - self.padding[0] - self.padding[2]
52
+ def __str__(self) -> str:
53
+ return "Widget"
70
54
 
71
- def get_content_height(self)->float:
72
- return self.rect.h - self.padding[1] - self.padding[3]
55
+ def set_autoresize(self, value: bool) -> Self:
56
+ self.autoresize_w = self.autoresize_h = value
57
+ self.dirty_shape = True
58
+ return self
73
59
 
74
- def get_content_rect(self)->pygame.FRect:
75
- 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
- )
60
+ def set_autoresize_w(self, value: bool) -> Self:
61
+ self.autoresize_w = value
62
+ self.dirty_shape = True
63
+ return self
81
64
 
82
- def get_content_rect_rel(self)->pygame.FRect:
83
- return self.get_content_rect().move(-self.rect.left,-self.rect.top)
65
+ def set_autoresize_h(self, value: bool) -> Self:
66
+ self.autoresize_h = value
67
+ self.dirty_shape = True
68
+ return self
84
69
 
85
- def get_content_center(self)->tuple[float,float]:
86
- return self.get_content_rect().center
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
87
75
 
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
76
+ def inflate_rect_by_padding(
77
+ self, rect: pygame.Rect | pygame.FRect
78
+ ) -> pygame.Rect | pygame.FRect:
79
+ return pygame.FRect(
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],
84
+ )
94
85
 
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:
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:
102
92
  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()
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]
112
96
  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()
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
+ ]
120
111
  return self
121
112
 
122
- def has_constraint(self,name:str)->bool:
123
- return any(c.name == name for c in self.constraints)
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)
124
117
 
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)
118
+ for c in self.children:
119
+ c.set_parent_scene(parent_scene)
120
+ return self
138
121
 
139
- for iteration in range(max_iterations):
140
- unsatisfied = [] # Initialize a flag
122
+ def set_parent(self, parent: "Widget") -> Self:
123
+ if parent == self.parent:
124
+ return self
125
+ if self.parent is not None:
126
+ self.parent.remove(self)
127
+ self.parent = parent
128
+ return self
141
129
 
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])}")
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
153
142
 
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()
143
+ self.dirty_shape = True
144
+ return self
159
145
 
160
- def get_size_int(self)->tuple[int,int]:
161
- return (ceil(self.rect.width),ceil(self.rect.height))
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
162
152
 
153
+ def get_min_required_size(self) -> tuple[float, float]:
154
+ return self.rect.size
163
155
 
164
- def get_center(self)->tuple[float,float]:
165
- return self.rect.center
156
+ def get_padded_width(self) -> float:
157
+ return self.rect.w - self.padding[0] - self.padding[2]
166
158
 
159
+ def get_padded_height(self) -> float:
160
+ return self.rect.h - self.padding[1] - self.padding[3]
167
161
 
168
- def get_bounding_box(self):
169
- yield (self.rect,self._debug_color)
170
- yield (self.get_content_rect(),"yellow")
171
- for child in self.children:
172
- yield from child.get_bounding_box()
162
+ def get_padded_left(self) -> float:
163
+ return self.rect.left + self.padding[0]
173
164
 
174
- def set_autoresize(self,value:bool)-> Self:
175
- self.autoresize = value
176
- self.build()
177
- return self
165
+ def get_padded_right(self) -> float:
166
+ return self.rect.right + self.padding[2]
178
167
 
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
168
+ def get_padded_center(self) -> tuple[float, float]:
169
+ return self.get_padded_rect().center
189
170
 
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)
171
+ def get_padded_top(self) -> float:
172
+ return self.rect.y + self.padding[1]
194
173
 
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()
201
- return self
174
+ def get_padded_bottom(self) -> float:
175
+ return self.rect.bottom - self.padding[3]
202
176
 
203
- def set_y(self,y:float)->Self:
204
- delta = y - self.rect.y
205
- self.rect.y = y
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)
206
183
  for child in self.children:
207
- child.set_y(child.rect.y + delta)
208
- self.apply_constraints()
209
-
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 c.name not 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
210
197
  return self
211
198
 
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()
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]
228
231
  return self
229
232
 
233
+ def has_constraint(self, name: str) -> bool:
234
+ return any(c.name == name for c in self.constraints)
230
235
 
231
- def set_size(self, width : float, height: float) -> Self:
232
- self.rect.size = (width,height)
233
- self.build()
234
- return self
235
-
236
-
237
- # Other Methods
236
+ def get_root(self) -> "Root":
237
+ if self.is_root:
238
+ return self
239
+ return self.parent.get_root()
238
240
 
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)
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
260
+ return self
243
261
 
244
- def to_string(self)->str:
245
-
246
- return f"{self.to_string_id()}@{*self.rect.topleft,* self.rect.size}"
262
+ def remove(self, *children: "Widget") -> Self:
263
+ for child in self.children:
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
247
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
248
274
 
249
- def to_string_id(self)->str:
250
- return "Widget"
275
+ self.set_size(size)
251
276
 
277
+ return self
252
278
 
253
- def do_when_removed(self)->None:
254
- if self.parent_scene and self.parent == self.parent_scene.root:
255
- self.set_parent(None)
279
+ def set_size(self, size: tuple) -> Self:
280
+ size = list(size)
281
+ if size[0] is None:
282
+ size[0] = self.rect.w
283
+ if size[1] is None:
284
+ size[1] = self.rect.h
285
+ if size == self.rect.size:
286
+ return self
287
+ self.rect.size = size
288
+ self.dirty_shape = True
289
+ return self
256
290
 
257
- # Methods on children
291
+ def process_event(self, event: pygame.Event) -> bool:
292
+ # First propagate to children
293
+ for child in self.children:
294
+ child.process_event(event)
295
+ # return True if the method is blocking (no propagation to next children of the scene)
296
+ super().process_event(event)
297
+
298
+ def update(self, dt) -> None:
299
+ if self.do_sort_children:
300
+ self.children.sort(key=lambda c: c.render_order)
301
+ self.do_sort_children = False
302
+ _ = [c.update(dt) for c in self.children]
303
+ super().update(dt)
304
+
305
+ def build(self) -> None:
306
+ new_size = tuple(map(int, self.rect.size))
307
+ if self.surface.get_size() != new_size:
308
+ new_size = [max(0, i) for i in new_size]
309
+ self.surface = pygame.Surface(new_size, self.surface_flags)
310
+ if self.convert_alpha:
311
+ self.surface = self.surface.convert_alpha()
312
+
313
+ def paint(self) -> None:
314
+ self.surface.fill((0, 0, 0, 0))
315
+ return self
258
316
 
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)