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.
- batFramework/__init__.py +20 -11
- batFramework/action.py +1 -1
- batFramework/animatedSprite.py +47 -116
- batFramework/animation.py +30 -5
- batFramework/audioManager.py +8 -5
- batFramework/baseScene.py +240 -0
- batFramework/camera.py +4 -0
- batFramework/constants.py +6 -2
- batFramework/cutscene.py +221 -21
- batFramework/cutsceneManager.py +5 -2
- batFramework/drawable.py +7 -5
- batFramework/easingController.py +10 -11
- batFramework/entity.py +21 -2
- batFramework/enums.py +48 -33
- batFramework/gui/__init__.py +6 -3
- batFramework/gui/animatedLabel.py +10 -2
- batFramework/gui/button.py +4 -31
- batFramework/gui/clickableWidget.py +63 -50
- batFramework/gui/constraints/constraints.py +212 -136
- batFramework/gui/container.py +77 -58
- batFramework/gui/debugger.py +12 -17
- batFramework/gui/draggableWidget.py +21 -17
- batFramework/gui/image.py +3 -10
- batFramework/gui/indicator.py +56 -1
- batFramework/gui/interactiveWidget.py +127 -108
- batFramework/gui/label.py +73 -64
- batFramework/gui/layout.py +286 -445
- batFramework/gui/meter.py +42 -20
- batFramework/gui/radioButton.py +20 -69
- batFramework/gui/root.py +99 -29
- batFramework/gui/selector.py +250 -0
- batFramework/gui/shape.py +13 -5
- batFramework/gui/slider.py +262 -107
- batFramework/gui/syncedVar.py +49 -0
- batFramework/gui/textInput.py +46 -22
- batFramework/gui/toggle.py +70 -52
- batFramework/gui/tooltip.py +30 -0
- batFramework/gui/widget.py +222 -135
- batFramework/manager.py +7 -8
- batFramework/particle.py +4 -1
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +17 -50
- batFramework/resourceManager.py +43 -13
- batFramework/scene.py +15 -335
- batFramework/sceneLayer.py +138 -0
- batFramework/sceneManager.py +31 -36
- batFramework/scrollingSprite.py +8 -3
- batFramework/sprite.py +1 -1
- batFramework/templates/__init__.py +1 -2
- batFramework/templates/controller.py +97 -0
- batFramework/timeManager.py +76 -22
- batFramework/transition.py +37 -103
- batFramework/utils.py +125 -66
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/METADATA +24 -3
- batframework-1.0.9a9.dist-info/RECORD +67 -0
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/WHEEL +1 -1
- batFramework/character.py +0 -27
- batFramework/templates/character.py +0 -43
- batFramework/templates/states.py +0 -166
- batframework-1.0.9a7.dist-info/RECORD +0 -63
- /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a9.dist-info/LICENSE +0 -0
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/top_level.txt +0 -0
batFramework/gui/widget.py
CHANGED
@@ -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 #
|
29
|
-
self.dirty_shape: bool = True #
|
30
|
-
self.
|
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.
|
35
|
-
self.
|
36
|
-
self.
|
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
|
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
|
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
|
175
|
+
def get_inner_width(self) -> float:
|
156
176
|
return self.rect.w - self.padding[0] - self.padding[2]
|
157
177
|
|
158
|
-
def
|
178
|
+
def get_inner_height(self) -> float:
|
159
179
|
return self.rect.h - self.padding[1] - self.padding[3]
|
160
180
|
|
161
|
-
def
|
181
|
+
def get_inner_left(self) -> float:
|
162
182
|
return self.rect.left + self.padding[0]
|
163
183
|
|
164
|
-
def
|
165
|
-
return self.rect.right
|
184
|
+
def get_inner_right(self) -> float:
|
185
|
+
return self.rect.right - self.padding[2]
|
166
186
|
|
167
|
-
def
|
168
|
-
return self.
|
187
|
+
def get_inner_center(self) -> tuple[float, float]:
|
188
|
+
return self.get_inner_rect().center
|
169
189
|
|
170
|
-
def
|
190
|
+
def get_inner_top(self) -> float:
|
171
191
|
return self.rect.y + self.padding[1]
|
172
192
|
|
173
|
-
def
|
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.
|
180
|
-
else:
|
181
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
196
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
215
|
-
|
252
|
+
# If not currently resolving constraints, reset tracking lists
|
253
|
+
if not self._constraint_iteration:
|
254
|
+
self._constraints_capture = []
|
216
255
|
else:
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
self.
|
221
|
-
|
222
|
-
|
223
|
-
#
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
return self if
|
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
|
-
|
287
|
-
|
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)
|
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
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
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) ->
|
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.
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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.
|
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)
|
390
|
-
|
391
|
-
if
|
392
|
-
self.
|
393
|
-
self.
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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, *
|
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
|
22
|
-
self.init_scenes(*
|
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(
|