batframework 1.0.9a7__py3-none-any.whl → 1.0.9a9__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 (62) hide show
  1. batFramework/__init__.py +20 -11
  2. batFramework/action.py +1 -1
  3. batFramework/animatedSprite.py +47 -116
  4. batFramework/animation.py +30 -5
  5. batFramework/audioManager.py +8 -5
  6. batFramework/baseScene.py +240 -0
  7. batFramework/camera.py +4 -0
  8. batFramework/constants.py +6 -2
  9. batFramework/cutscene.py +221 -21
  10. batFramework/cutsceneManager.py +5 -2
  11. batFramework/drawable.py +7 -5
  12. batFramework/easingController.py +10 -11
  13. batFramework/entity.py +21 -2
  14. batFramework/enums.py +48 -33
  15. batFramework/gui/__init__.py +6 -3
  16. batFramework/gui/animatedLabel.py +10 -2
  17. batFramework/gui/button.py +4 -31
  18. batFramework/gui/clickableWidget.py +63 -50
  19. batFramework/gui/constraints/constraints.py +212 -136
  20. batFramework/gui/container.py +77 -58
  21. batFramework/gui/debugger.py +12 -17
  22. batFramework/gui/draggableWidget.py +21 -17
  23. batFramework/gui/image.py +3 -10
  24. batFramework/gui/indicator.py +56 -1
  25. batFramework/gui/interactiveWidget.py +127 -108
  26. batFramework/gui/label.py +73 -64
  27. batFramework/gui/layout.py +286 -445
  28. batFramework/gui/meter.py +42 -20
  29. batFramework/gui/radioButton.py +20 -69
  30. batFramework/gui/root.py +99 -29
  31. batFramework/gui/selector.py +250 -0
  32. batFramework/gui/shape.py +13 -5
  33. batFramework/gui/slider.py +262 -107
  34. batFramework/gui/syncedVar.py +49 -0
  35. batFramework/gui/textInput.py +46 -22
  36. batFramework/gui/toggle.py +70 -52
  37. batFramework/gui/tooltip.py +30 -0
  38. batFramework/gui/widget.py +222 -135
  39. batFramework/manager.py +7 -8
  40. batFramework/particle.py +4 -1
  41. batFramework/propertyEaser.py +79 -0
  42. batFramework/renderGroup.py +17 -50
  43. batFramework/resourceManager.py +43 -13
  44. batFramework/scene.py +15 -335
  45. batFramework/sceneLayer.py +138 -0
  46. batFramework/sceneManager.py +31 -36
  47. batFramework/scrollingSprite.py +8 -3
  48. batFramework/sprite.py +1 -1
  49. batFramework/templates/__init__.py +1 -2
  50. batFramework/templates/controller.py +97 -0
  51. batFramework/timeManager.py +76 -22
  52. batFramework/transition.py +37 -103
  53. batFramework/utils.py +125 -66
  54. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/METADATA +24 -3
  55. batframework-1.0.9a9.dist-info/RECORD +67 -0
  56. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/WHEEL +1 -1
  57. batFramework/character.py +0 -27
  58. batFramework/templates/character.py +0 -43
  59. batFramework/templates/states.py +0 -166
  60. batframework-1.0.9a7.dist-info/RECORD +0 -63
  61. /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a9.dist-info/LICENSE +0 -0
  62. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Self, Callable
1
+ from typing import TYPE_CHECKING, Self, Callable, Any
2
2
  from collections.abc import Iterable
3
3
  import batFramework as bf
4
4
  import pygame
@@ -6,13 +6,13 @@ import pygame
6
6
  if TYPE_CHECKING:
7
7
  from .constraints.constraints import Constraint
8
8
  from .root import Root
9
- MAX_CONSTRAINTS = 10
10
9
 
10
+ MAX_ITERATIONS = 10
11
11
 
12
12
  class WidgetMeta(type):
13
13
  def __call__(cls, *args, **kwargs):
14
14
  obj = type.__call__(cls, *args, **kwargs)
15
- bf.StyleManager().register_widget(obj)
15
+ bf.gui.StyleManager().register_widget(obj)
16
16
  return obj
17
17
 
18
18
 
@@ -25,15 +25,21 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
25
25
  self.do_sort_children = False
26
26
  self.clip_children: bool = True
27
27
  self.padding = (0, 0, 0, 0)
28
- self.dirty_surface: bool = True # if true will call paint before drawing
29
- self.dirty_shape: bool = True # if true will call (build+paint) before drawing
30
- self.dirty_constraints: bool = False # if true will call resolve_constraints
28
+ self.dirty_surface: bool = True # If true, will call paint before drawing
29
+ self.dirty_shape: bool = True # If true, will call (build+paint) before drawing
30
+ self.dirty_position_constraints: bool = True # Flag for position-related constraints
31
+ self.dirty_size_constraints: bool = True # Flag for size-related constraints
31
32
 
33
+ self.tooltip_text: str | None = None # If not None, will display a text when hovered
32
34
  self.is_root: bool = False
33
- self.autoresize_w, self.autoresize_h = True, True
34
- self.__constraint_iteration = 0
35
- self.__constraints_to_ignore = []
36
- self.__constraints_capture = None
35
+ self.autoresize_w, self.autoresize_h = True, True # If True, the widget will have dynamic size depending on its contents
36
+ self._constraint_iteration = 0
37
+ self._constraints_to_ignore: list[Constraint] = []
38
+ self._constraints_capture: list[Constraint] = []
39
+
40
+ def set_tooltip_text(self,text:str|None)->Self:
41
+ self.tooltip_text = text
42
+ return self
37
43
 
38
44
  def show(self) -> Self:
39
45
  self.visit(lambda w: w.set_visible(True))
@@ -43,6 +49,14 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
43
49
  self.visit(lambda w: w.set_visible(False))
44
50
  return self
45
51
 
52
+ def kill(self):
53
+ if self.parent:
54
+ self.parent.remove(self)
55
+ if self.parent_scene:
56
+ self.parent_scene.remove(self.parent_layer.name,self)
57
+
58
+ return super().kill()
59
+
46
60
  def set_clip_children(self, value: bool) -> Self:
47
61
  self.clip_children = value
48
62
  self.dirty_surface = True
@@ -72,7 +86,7 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
72
86
  self.parent.do_sort_children = True
73
87
  return self
74
88
 
75
- def inflate_rect_by_padding(
89
+ def expand_rect_with_padding(
76
90
  self, rect: pygame.Rect | pygame.FRect
77
91
  ) -> pygame.Rect | pygame.FRect:
78
92
  return pygame.FRect(
@@ -112,12 +126,18 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
112
126
  def set_parent_scene(self, parent_scene: bf.Scene | None) -> Self:
113
127
  super().set_parent_scene(parent_scene)
114
128
  if parent_scene is None:
115
- bf.StyleManager().remove_widget(self)
129
+ bf.gui.StyleManager().remove_widget(self)
116
130
 
117
131
  for c in self.children:
118
132
  c.set_parent_scene(parent_scene)
119
133
  return self
120
134
 
135
+ def set_parent_layer(self, layer):
136
+ super().set_parent_layer(layer)
137
+ for c in self.children:
138
+ c.set_parent_layer(layer)
139
+ return self
140
+
121
141
  def set_parent(self, parent: "Widget") -> Self:
122
142
  if parent == self.parent:
123
143
  return self
@@ -142,7 +162,7 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
142
162
  self.dirty_shape = True
143
163
  return self
144
164
 
145
- def get_padded_rect(self) -> pygame.FRect:
165
+ def get_inner_rect(self) -> pygame.FRect:
146
166
  r = self.rect.inflate(
147
167
  -self.padding[0] - self.padding[2], -self.padding[1] - self.padding[3]
148
168
  )
@@ -152,48 +172,53 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
152
172
  def get_min_required_size(self) -> tuple[float, float]:
153
173
  return self.rect.size
154
174
 
155
- def get_padded_width(self) -> float:
175
+ def get_inner_width(self) -> float:
156
176
  return self.rect.w - self.padding[0] - self.padding[2]
157
177
 
158
- def get_padded_height(self) -> float:
178
+ def get_inner_height(self) -> float:
159
179
  return self.rect.h - self.padding[1] - self.padding[3]
160
180
 
161
- def get_padded_left(self) -> float:
181
+ def get_inner_left(self) -> float:
162
182
  return self.rect.left + self.padding[0]
163
183
 
164
- def get_padded_right(self) -> float:
165
- return self.rect.right + self.padding[2]
184
+ def get_inner_right(self) -> float:
185
+ return self.rect.right - self.padding[2]
166
186
 
167
- def get_padded_center(self) -> tuple[float, float]:
168
- return self.get_padded_rect().center
187
+ def get_inner_center(self) -> tuple[float, float]:
188
+ return self.get_inner_rect().center
169
189
 
170
- def get_padded_top(self) -> float:
190
+ def get_inner_top(self) -> float:
171
191
  return self.rect.y + self.padding[1]
172
192
 
173
- def get_padded_bottom(self) -> float:
193
+ def get_inner_bottom(self) -> float:
174
194
  return self.rect.bottom - self.padding[3]
175
195
 
176
196
  def get_debug_outlines(self):
177
197
  if self.visible:
178
198
  if any(self.padding):
179
- yield (self.get_padded_rect(), self.debug_color)
180
- else:
181
- yield (self.rect, self.debug_color)
199
+ yield (self.get_inner_rect(), self.debug_color)
200
+ # else:
201
+ yield (self.rect, self.debug_color)
182
202
  for child in self.children:
183
203
  yield from child.get_debug_outlines()
184
204
 
185
205
  def add_constraints(self, *constraints: "Constraint") -> Self:
186
- self.constraints.extend(constraints)
187
- seen = set()
188
- result = []
189
- for c in self.constraints:
190
- if not any(c == o for o in seen):
191
- result.append(c)
192
- seen.add(c.name)
193
- self.constraints = result
206
+ # Add constraints without duplicates
207
+ existing_names = {c.name for c in self.constraints}
208
+ new_constraints = [c for c in constraints if c.name not in existing_names]
209
+ self.constraints.extend(new_constraints)
210
+
211
+ # Sort constraints by priority
194
212
  self.constraints.sort(key=lambda c: c.priority)
195
- self.dirty_constraints = True
196
- self.__constraint_to_ignore = []
213
+
214
+ # Update dirty flags based on the new constraints
215
+ if any(c.affects_size for c in new_constraints):
216
+ self.dirty_size_constraints = True
217
+ if any(c.affects_position for c in new_constraints):
218
+ self.dirty_position_constraints = True
219
+
220
+ # Clear ignored constraints
221
+ self._constraints_to_ignore = []
197
222
 
198
223
  return self
199
224
 
@@ -202,61 +227,90 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
202
227
  for c in self.constraints:
203
228
  if c.name in names:
204
229
  c.on_removal(self)
205
- self.constraints = [c for c in self.constraints if c.name not in names]
206
- self.__constraint_to_ignore = []
230
+ self.constraints = [c for c in self.constraints if c.name not in names]
231
+ self._constraints_to_ignore = []
232
+ self.dirty_size_constraints = True
233
+ self.dirty_position_constraints= True
234
+
207
235
  return self
208
236
 
209
- def resolve_constraints(self) -> None:
237
+
238
+ def resolve_constraints(self, size_only: bool = False, position_only: bool = False) -> None:
239
+ """
240
+ Resolve constraints affecting size and/or position independently.
241
+
242
+ This system attempts to apply constraints iteratively until a stable solution is found,
243
+ or until MAX_ITERATIONS is reached.
244
+ """
210
245
  if self.parent is None or not self.constraints:
211
- self.dirty_constraints = False
246
+ if size_only:
247
+ self.dirty_size_constraints = False
248
+ if position_only:
249
+ self.dirty_position_constraints = False
212
250
  return
213
251
 
214
- if not self.__constraint_iteration:
215
- self.__constraints_capture = None
252
+ # If not currently resolving constraints, reset tracking lists
253
+ if not self._constraint_iteration:
254
+ self._constraints_capture = []
216
255
  else:
217
- capture = tuple([c.priority for c in self.constraints])
218
- if capture != self.__constraints_capture:
219
- self.__constraints_capture = capture
220
- self.__constraint_to_ignore = []
221
-
222
- constraints = self.constraints.copy()
223
- # If all are resolved early exit
224
- if all(c.evaluate(self.parent,self) for c in constraints if c not in self.__constraint_to_ignore):
225
- self.dirty_constraints = False
226
- return
227
-
228
- # # Here there might be a conflict between 2 or more constraints
229
- # we have to determine which ones causes conflict and ignore the one with least priority
230
-
231
- stop = False
232
-
233
- while True:
234
- stop = True
235
- # first pass with 2 iterations to sort out the transformative constraints
236
- for _ in range(2):
237
- for c in constraints:
238
- if c in self.__constraints_to_ignore:continue
239
- if not c.evaluate(self.parent,self) :
240
- # print(c," is applied")
241
- c.apply(self.parent,self)
242
- # second pass where we check conflicts
243
- for c in constraints:
244
- if c in self.__constraints_to_ignore:
245
- continue
246
- if not c.evaluate(self.parent,self):
247
- # first pass invalidated this constraint
248
- self.__constraints_to_ignore.append(c)
249
- stop = False
250
- break
251
-
252
- if stop:
253
- break
256
+ # Detect constraint priority changes since last resolution
257
+ current_priorities = [c.priority for c in self.constraints]
258
+ if current_priorities != self._constraints_capture:
259
+ self._constraints_capture = current_priorities
260
+ self._constraints_to_ignore = []
261
+
262
+ # Filter constraints based on what needs resolving
263
+ def is_relevant(c: "Constraint") -> bool:
264
+ return (
265
+ c.affects_size if size_only else
266
+ c.affects_position if position_only else
267
+ True
268
+ )
254
269
 
255
- if self.__constraints_to_ignore:
256
- print("Constraints ignored : ",[str(c) for c in self.__constraints_to_ignore])
257
-
258
- self.dirty_constraints = False
259
- # print(self,self.uid,"resolve constraints : Success")
270
+ active_constraints = [c for c in self.constraints if is_relevant(c)]
271
+ active_constraints.sort(key=lambda c: c.priority, reverse=True)
272
+
273
+ resolved = []
274
+ for iteration in range(MAX_ITERATIONS):
275
+ self._constraint_iteration += 1
276
+ changed = False
277
+
278
+ for constraint in active_constraints:
279
+ if constraint in resolved:
280
+ # Re-evaluate to confirm the constraint is still satisfied
281
+ if not constraint.evaluate(self.parent, self):
282
+ resolved.remove(constraint)
283
+ changed = True
284
+ else:
285
+ # Try applying unresolved constraint
286
+ if constraint.apply(self.parent, self):
287
+ resolved.append(constraint)
288
+ changed = True
289
+
290
+ if not changed:
291
+ break # All constraints stable — done
292
+
293
+ # If solution is still unstable, record the unresolved ones
294
+ if self._constraint_iteration >= MAX_ITERATIONS:
295
+ self._constraints_to_ignore += [
296
+ c for c in active_constraints if c not in resolved
297
+ ]
298
+
299
+ # Record final resolved constraints for debugging/tracking
300
+ self._constraints_capture.clear()
301
+ self._constraints_capture.extend(
302
+ (c, self._constraint_iteration) for c in resolved
303
+ )
304
+
305
+ # Clear appropriate dirty flags
306
+ if size_only:
307
+ self.dirty_size_constraints = False
308
+ if position_only:
309
+ self.dirty_position_constraints = False
310
+
311
+ # Debug print for ignored constraints
312
+ # if self._constraints_to_ignore:
313
+ # print(f"{self} ignored constraints: {[str(c) for c in self._constraints_to_ignore]}")
260
314
 
261
315
 
262
316
  def has_constraint(self, name: str) -> bool:
@@ -269,42 +323,50 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
269
323
  return self.parent.get_root()
270
324
  return None
271
325
 
326
+ def get_by_tags(self,*tags: str)->list["Widget"]:
327
+ #use self.has_tags(*tags) for check
328
+ result = []
329
+ self.visit(lambda w: result.append(w) if w.has_tags(*tags) else None)
330
+ return result
331
+
272
332
  def top_at(self, x: float | int, y: float | int) -> "None|Widget":
273
333
  if self.children:
274
334
  for child in reversed(self.children):
275
- # if child.visible:
276
- r = child.top_at(x, y)
277
- if r is not None:
278
- return r
279
- return self if self.rect.collidepoint(x, y) else None
335
+ if child.visible:
336
+ r = child.top_at(x, y)
337
+ if r is not None:
338
+ return r
339
+ return self if self.visible and self.rect.collidepoint(x, y) else None
280
340
 
281
341
  def add(self, *children: "Widget") -> Self:
282
342
  self.children.extend(children)
283
343
  i = len(self.children)
284
344
  for child in children:
285
-
286
- child.set_render_order(i).set_parent(self).set_parent_scene(
287
- self.parent_scene
288
- )
345
+ if child.render_order == 0:
346
+ child.set_render_order(i+1)
347
+ child.set_parent(self)
348
+ child.set_parent_layer(self.parent_layer)
349
+ child.set_parent_scene(self.parent_scene)
289
350
  i += 1
290
351
  if self.parent:
291
352
  self.parent.do_sort_children = True
292
353
  return self
293
354
 
294
355
  def remove(self, *children: "Widget") -> Self:
295
- for child in self.children:
356
+ for child in self.children.copy():
296
357
  if child in children:
297
- child.set_parent(None).set_parent_scene(None)
358
+ child.set_parent(None)
359
+ child.set_parent_scene(None)
360
+ child.set_parent_layer(None)
298
361
  self.children.remove(child)
299
362
  if self.parent:
300
363
  self.parent.do_sort_children = True
301
364
 
302
- def set_size_if_autoresize(self, size: tuple[float, float]) -> Self:
303
- size = list(size)
304
- size[0] = size[0] if self.autoresize_w else None
305
- size[1] = size[1] if self.autoresize_h else None
306
- self.set_size(size)
307
- return self
365
+ def resolve_size(self, target_size):
366
+ return (
367
+ target_size[0] if self.autoresize_w else self.rect.w,
368
+ target_size[1] if self.autoresize_h else self.rect.h
369
+ )
308
370
 
309
371
  def set_size(self, size: tuple) -> Self:
310
372
  size = list(size)
@@ -312,8 +374,7 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
312
374
  size[0] = self.rect.w
313
375
  if size[1] is None:
314
376
  size[1] = self.rect.h
315
- if (int(size[0]),int(size[1])) == (int(self.rect.w),int(self.rect.h)):
316
- return self
377
+ if size[0] == self.rect.w and size[1] == self.rect.h : return self
317
378
  self.rect.size = size
318
379
  self.dirty_shape = True
319
380
  return self
@@ -331,21 +392,28 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
331
392
  _ = [c.update(dt) for c in self.children]
332
393
  super().update(dt)
333
394
 
334
- def build(self) -> None:
395
+ def build(self) -> bool:
396
+ """
397
+ Updates the size of the widget.
398
+ return True if size changed
399
+ """
335
400
  new_size = tuple(map(int, self.rect.size))
336
- if self.surface.get_size() != new_size:
337
- old_alpha = self.surface.get_alpha()
338
- new_size = [max(0, i) for i in new_size]
339
- self.surface = pygame.Surface(new_size, self.surface_flags)
340
- if self.convert_alpha:
341
- self.surface = self.surface.convert_alpha()
342
- self.surface.set_alpha(old_alpha)
401
+ if self.surface.size == new_size:
402
+ return False
403
+
404
+ old_alpha = self.surface.get_alpha()
405
+ new_size = [max(0, i) for i in new_size]
406
+ self.surface = pygame.Surface(new_size, self.surface_flags)
407
+ if self.convert_alpha:
408
+ self.surface = self.surface.convert_alpha()
409
+ self.surface.set_alpha(old_alpha)
410
+ return True
343
411
 
344
412
  def paint(self) -> None:
345
413
  self.surface.fill((0, 0, 0, 0))
346
414
  return self
347
415
 
348
- def visit(self, func: Callable, top_down: bool = True, *args, **kwargs) -> None:
416
+ def visit(self, func: Callable[["Widget"],Any], top_down: bool = True, *args, **kwargs) -> None:
349
417
  if top_down:
350
418
  func(self, *args, **kwargs)
351
419
  for child in self.children:
@@ -378,7 +446,7 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
378
446
  w = self
379
447
  tmp = w
380
448
  while not tmp.is_root:
381
- if tmp.dirty_constraints or tmp.dirty_shape:
449
+ if tmp.dirty_size_constraints or tmp.dirty_shape:
382
450
  w = tmp
383
451
  if not tmp.parent:
384
452
  break
@@ -386,49 +454,68 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
386
454
  return w
387
455
 
388
456
 
389
- def apply_updates(self) -> None:
390
-
391
- if self.dirty_constraints:
392
- self.resolve_constraints() # Finalize positioning based on final size
393
- self.dirty_constraints = False
457
+ def apply_updates(self,pass_type):
458
+ # print(f"Apply updates {pass_type} called on {self}")
459
+ if pass_type == "pre":
460
+ self.apply_pre_updates()
461
+ for child in self.children:
462
+ child.apply_updates("pre")
463
+ elif pass_type == "post":
464
+ for child in self.children:
465
+ child.apply_updates("post")
466
+ self.apply_post_updates()
467
+
468
+ def apply_pre_updates(self):
469
+ """
470
+ TOP TO BOTTOM
471
+ Resolves size-related constraints before propagating updates to children.
472
+ """
473
+ if self.dirty_size_constraints:
474
+ self.resolve_constraints(size_only=True)
475
+ self.dirty_size_constraints = False
476
+ self.dirty_position_constraints = True
477
+
478
+ def apply_post_updates(self, skip_draw: bool = False):
479
+ """
480
+ BOTTOM TO TOP
481
+ Resolves position-related constraints after propagating updates from children.
482
+ """
394
483
 
395
- # Build shape if needed
396
484
  if self.dirty_shape:
397
- self.build() # Finalize widget size
485
+
486
+ if self.build():
487
+ self.dirty_size_constraints = True
488
+ self.dirty_position_constraints = True
489
+ if self.parent :
490
+ # trigger layout or constraint updates in parent
491
+ from .container import Container
492
+ if self.parent and isinstance(self.parent, Container):
493
+ self.parent.dirty_layout = True
494
+ self.parent.dirty_shape = True
398
495
  self.dirty_shape = False
399
496
  self.dirty_surface = True
400
- self.dirty_constraints = True
401
- # Propagate dirty_constraints to children in case size affects their position
402
- for child in self.children:
403
- child.dirty_constraints = True
404
-
405
- # Resolve constraints now that size is finalized
406
- if self.dirty_constraints:
407
- self.resolve_constraints() # Finalize positioning based on final size
408
- self.dirty_constraints = False
409
497
 
498
+ if self.dirty_position_constraints:
499
+ self.resolve_constraints(position_only=True)
410
500
 
411
- # Step 3: Paint the surface if flagged as dirty
412
- if self.dirty_surface:
501
+ if self.dirty_surface and not skip_draw:
413
502
  self.paint()
414
503
  self.dirty_surface = False
415
-
416
504
 
417
505
 
418
506
  def draw(self, camera: bf.Camera) -> None:
419
- self.apply_updates()
420
507
  # Draw widget and handle clipping if necessary
421
508
  super().draw(camera)
422
509
 
423
510
  if self.clip_children:
424
- new_clip = camera.world_to_screen(self.get_padded_rect())
511
+ new_clip = camera.world_to_screen(self.get_inner_rect())
425
512
  old_clip = camera.surface.get_clip()
426
513
  new_clip = new_clip.clip(old_clip)
427
514
  camera.surface.set_clip(new_clip)
428
515
 
429
516
  # Draw each child widget, sorted by render order
430
517
  for child in sorted(self.children, key=lambda c: c.render_order):
431
- child.draw(camera)
432
-
518
+ if (not self.clip_children) or (child.rect.colliderect(self.rect) or not child.rect):
519
+ child.draw(camera)
433
520
  if self.clip_children:
434
521
  camera.surface.set_clip(old_clip)
batFramework/manager.py CHANGED
@@ -3,7 +3,7 @@ import pygame
3
3
  import asyncio
4
4
 
5
5
  class Manager(bf.SceneManager):
6
- def __init__(self, *initial_scene_list) -> None:
6
+ def __init__(self, *initial_scenes) -> None:
7
7
  super().__init__()
8
8
  self.debug_mode: bf.enums.debugMode = bf.debugMode.HIDDEN
9
9
  self.screen: pygame.Surface | None = bf.const.SCREEN
@@ -18,8 +18,8 @@ class Manager(bf.SceneManager):
18
18
  bf.ResourceManager().set_sharedVar("debug_mode", self.debug_mode)
19
19
 
20
20
  self.do_pre_init()
21
- if initial_scene_list:
22
- self.init_scenes(*initial_scene_list)
21
+ if initial_scenes:
22
+ self.init_scenes(*initial_scenes)
23
23
  self.do_init()
24
24
 
25
25
  @staticmethod
@@ -75,15 +75,15 @@ class Manager(bf.SceneManager):
75
75
  self.print_status()
76
76
  return
77
77
  self.cutsceneManager.process_event(event)
78
+ if event.type == pygame.VIDEORESIZE and not (bf.const.FLAGS & pygame.SCALED):
79
+ bf.const.set_resolution((event.w, event.h))
80
+
78
81
  if event.consumed: return
82
+
79
83
  super().process_event(event)
80
84
  if not event.consumed:
81
85
  if event.type == pygame.QUIT:
82
86
  self.running = False
83
- elif event.type == pygame.VIDEORESIZE and not (
84
- bf.const.FLAGS & pygame.SCALED
85
- ):
86
- bf.const.set_resolution((event.w, event.h))
87
87
 
88
88
  def update(self, dt: float) -> None:
89
89
  self.timeManager.update(dt)
@@ -96,7 +96,6 @@ class Manager(bf.SceneManager):
96
96
  raise Exception("Manager can't start without scenes")
97
97
  if self.running:
98
98
  raise Exception("Error : Already running")
99
- return
100
99
  self.is_async_running = True
101
100
  self.running = True
102
101
  dt: float = 0
batFramework/particle.py CHANGED
@@ -82,6 +82,7 @@ class ParticleGenerator(bf.Drawable):
82
82
  def __init__(self) -> None:
83
83
  super().__init__((0, 0))
84
84
  self.particles: list[Particle] = []
85
+ self.count = 0
85
86
 
86
87
  def get_debug_outlines(self):
87
88
  return
@@ -96,10 +97,11 @@ class ParticleGenerator(bf.Drawable):
96
97
  particle.generator = self
97
98
  particle.do_when_added()
98
99
  self.particles.append(particle)
100
+ self.count += 1
99
101
 
100
102
  def clear(self):
101
103
  self.particles = []
102
-
104
+ self.count = 0
103
105
  def update(self, dt: float):
104
106
  particles_to_remove = []
105
107
  for particle in self.particles:
@@ -108,6 +110,7 @@ class ParticleGenerator(bf.Drawable):
108
110
  particles_to_remove.append(particle)
109
111
  for p in particles_to_remove:
110
112
  self.particles.remove(p)
113
+ self.count -= 1
111
114
 
112
115
  def draw(self, camera) -> None:
113
116
  camera.surface.fblits(