batframework 1.0.9a10__py3-none-any.whl → 1.0.9a12__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 +2 -0
- batFramework/action.py +280 -279
- batFramework/actionContainer.py +105 -82
- batFramework/animatedSprite.py +80 -58
- batFramework/animation.py +91 -77
- batFramework/audioManager.py +156 -131
- batFramework/baseScene.py +249 -240
- batFramework/camera.py +245 -317
- batFramework/constants.py +57 -51
- batFramework/cutscene.py +239 -253
- batFramework/cutsceneManager.py +34 -34
- batFramework/drawable.py +107 -77
- batFramework/dynamicEntity.py +30 -30
- batFramework/easingController.py +58 -58
- batFramework/entity.py +130 -130
- batFramework/enums.py +171 -135
- batFramework/fontManager.py +65 -65
- batFramework/gui/__init__.py +28 -25
- batFramework/gui/animatedLabel.py +90 -89
- batFramework/gui/button.py +17 -17
- batFramework/gui/clickableWidget.py +244 -245
- batFramework/gui/collapseContainer.py +98 -0
- batFramework/gui/constraints/__init__.py +1 -1
- batFramework/gui/constraints/constraints.py +1066 -980
- batFramework/gui/container.py +220 -201
- batFramework/gui/debugger.py +140 -130
- batFramework/gui/draggableWidget.py +63 -44
- batFramework/gui/image.py +61 -58
- batFramework/gui/indicator.py +116 -113
- batFramework/gui/interactiveWidget.py +243 -239
- batFramework/gui/label.py +147 -344
- batFramework/gui/layout.py +442 -426
- batFramework/gui/meter.py +155 -96
- batFramework/gui/radioButton.py +43 -35
- batFramework/gui/root.py +228 -228
- batFramework/gui/scrollingContainer.py +282 -0
- batFramework/gui/selector.py +232 -250
- batFramework/gui/shape.py +286 -276
- batFramework/gui/slider.py +353 -397
- batFramework/gui/style.py +10 -10
- batFramework/gui/styleManager.py +49 -54
- batFramework/gui/syncedVar.py +43 -49
- batFramework/gui/textInput.py +331 -306
- batFramework/gui/textWidget.py +308 -0
- batFramework/gui/toggle.py +140 -128
- batFramework/gui/tooltip.py +35 -30
- batFramework/gui/widget.py +546 -521
- batFramework/manager.py +131 -134
- batFramework/particle.py +118 -118
- batFramework/propertyEaser.py +79 -79
- batFramework/renderGroup.py +34 -34
- batFramework/resourceManager.py +130 -130
- batFramework/scene.py +31 -31
- batFramework/sceneLayer.py +134 -138
- batFramework/sceneManager.py +200 -197
- batFramework/scrollingSprite.py +115 -115
- batFramework/sprite.py +46 -51
- batFramework/stateMachine.py +49 -54
- batFramework/templates/__init__.py +2 -1
- batFramework/templates/character.py +15 -0
- batFramework/templates/controller.py +158 -97
- batFramework/templates/stateMachine.py +39 -0
- batFramework/tileset.py +46 -46
- batFramework/timeManager.py +213 -213
- batFramework/transition.py +162 -162
- batFramework/triggerZone.py +22 -22
- batFramework/utils.py +306 -306
- {batframework-1.0.9a10.dist-info → batframework-1.0.9a12.dist-info}/LICENSE +20 -20
- {batframework-1.0.9a10.dist-info → batframework-1.0.9a12.dist-info}/METADATA +24 -17
- batframework-1.0.9a12.dist-info/RECORD +72 -0
- batframework-1.0.9a10.dist-info/RECORD +0 -67
- {batframework-1.0.9a10.dist-info → batframework-1.0.9a12.dist-info}/WHEEL +0 -0
- {batframework-1.0.9a10.dist-info → batframework-1.0.9a12.dist-info}/top_level.txt +0 -0
batFramework/gui/widget.py
CHANGED
@@ -1,521 +1,546 @@
|
|
1
|
-
from typing import TYPE_CHECKING, Self, Callable, Any
|
2
|
-
from collections.abc import Iterable
|
3
|
-
import batFramework as bf
|
4
|
-
import pygame
|
5
|
-
|
6
|
-
if TYPE_CHECKING:
|
7
|
-
from .constraints.constraints import Constraint
|
8
|
-
from .root import Root
|
9
|
-
|
10
|
-
MAX_ITERATIONS = 10
|
11
|
-
|
12
|
-
class WidgetMeta(type):
|
13
|
-
def __call__(cls, *args, **kwargs):
|
14
|
-
obj = type.__call__(cls, *args, **kwargs)
|
15
|
-
bf.gui.StyleManager().register_widget(obj)
|
16
|
-
return obj
|
17
|
-
|
18
|
-
|
19
|
-
class Widget(bf.Drawable, metaclass=WidgetMeta):
|
20
|
-
def __init__(self, *args, **kwargs) -> None:
|
21
|
-
super().__init__(*args, **kwargs)
|
22
|
-
self.children: list["Widget"] = []
|
23
|
-
self.constraints: list[Constraint] = []
|
24
|
-
self.parent: "Widget" = None
|
25
|
-
self.do_sort_children = False
|
26
|
-
self.clip_children: bool = True
|
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_position_constraints: bool = True # Flag for position-related constraints
|
31
|
-
self.dirty_size_constraints: bool = True # Flag for size-related constraints
|
32
|
-
|
33
|
-
self.tooltip_text: str | None = None # If not None, will display a text when hovered
|
34
|
-
self.is_root: bool = False
|
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
|
43
|
-
|
44
|
-
def show(self) -> Self:
|
45
|
-
self.visit(lambda w: w.set_visible(True))
|
46
|
-
return self
|
47
|
-
|
48
|
-
def hide(self) -> Self:
|
49
|
-
self.visit(lambda w: w.set_visible(False))
|
50
|
-
return self
|
51
|
-
|
52
|
-
def
|
53
|
-
if self.
|
54
|
-
self.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
return
|
64
|
-
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
return
|
72
|
-
|
73
|
-
def
|
74
|
-
self.autoresize_w = value
|
75
|
-
self.dirty_shape = True
|
76
|
-
return self
|
77
|
-
|
78
|
-
def
|
79
|
-
self.
|
80
|
-
self.dirty_shape = True
|
81
|
-
return self
|
82
|
-
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
self
|
91
|
-
|
92
|
-
return
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
return self
|
148
|
-
|
149
|
-
def
|
150
|
-
if
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
def
|
188
|
-
return self.
|
189
|
-
|
190
|
-
def
|
191
|
-
return self.rect.
|
192
|
-
|
193
|
-
def
|
194
|
-
return self.rect.
|
195
|
-
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
def
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
self.
|
210
|
-
|
211
|
-
|
212
|
-
self.
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
self.
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
#
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
return
|
340
|
-
|
341
|
-
def
|
342
|
-
self.
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
child.
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
self.
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
if
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
def
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
def
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
""
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
self.
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
1
|
+
from typing import TYPE_CHECKING, Self, Callable, Any
|
2
|
+
from collections.abc import Iterable
|
3
|
+
import batFramework as bf
|
4
|
+
import pygame
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from .constraints.constraints import Constraint
|
8
|
+
from .root import Root
|
9
|
+
|
10
|
+
MAX_ITERATIONS = 10
|
11
|
+
|
12
|
+
class WidgetMeta(type):
|
13
|
+
def __call__(cls, *args, **kwargs):
|
14
|
+
obj = type.__call__(cls, *args, **kwargs)
|
15
|
+
bf.gui.StyleManager().register_widget(obj)
|
16
|
+
return obj
|
17
|
+
|
18
|
+
|
19
|
+
class Widget(bf.Drawable, metaclass=WidgetMeta):
|
20
|
+
def __init__(self, *args, **kwargs) -> None:
|
21
|
+
super().__init__(*args, **kwargs)
|
22
|
+
self.children: list["Widget"] = []
|
23
|
+
self.constraints: list[Constraint] = []
|
24
|
+
self.parent: "Widget" = None
|
25
|
+
self.do_sort_children = False
|
26
|
+
self.clip_children: bool = True
|
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_position_constraints: bool = True # Flag for position-related constraints
|
31
|
+
self.dirty_size_constraints: bool = True # Flag for size-related constraints
|
32
|
+
|
33
|
+
self.tooltip_text: str | None = None # If not None, will display a text when hovered
|
34
|
+
self.is_root: bool = False
|
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
|
43
|
+
|
44
|
+
def show(self) -> Self:
|
45
|
+
self.visit(lambda w: w.set_visible(True))
|
46
|
+
return self
|
47
|
+
|
48
|
+
def hide(self) -> Self:
|
49
|
+
self.visit(lambda w: w.set_visible(False))
|
50
|
+
return self
|
51
|
+
|
52
|
+
def set_visible(self, value):
|
53
|
+
if self.visible != value and value==True:
|
54
|
+
self.dirty_surface = True
|
55
|
+
return super().set_visible(value)
|
56
|
+
|
57
|
+
def kill(self):
|
58
|
+
if self.parent:
|
59
|
+
self.parent.remove(self)
|
60
|
+
if self.parent_scene and self.parent_layer is not None:
|
61
|
+
self.parent_scene.remove(self.parent_layer.name,self)
|
62
|
+
|
63
|
+
return super().kill()
|
64
|
+
|
65
|
+
def set_clip_children(self, value: bool) -> Self:
|
66
|
+
self.clip_children = value
|
67
|
+
self.dirty_surface = True
|
68
|
+
return self
|
69
|
+
|
70
|
+
def __str__(self) -> str:
|
71
|
+
return "Widget"
|
72
|
+
|
73
|
+
def set_autoresize(self, value: bool) -> Self:
|
74
|
+
self.autoresize_w = self.autoresize_h = value
|
75
|
+
self.dirty_shape = True
|
76
|
+
return self
|
77
|
+
|
78
|
+
def set_autoresize_w(self, value: bool) -> Self:
|
79
|
+
self.autoresize_w = value
|
80
|
+
self.dirty_shape = True
|
81
|
+
return self
|
82
|
+
|
83
|
+
def set_autoresize_h(self, value: bool) -> Self:
|
84
|
+
self.autoresize_h = value
|
85
|
+
self.dirty_shape = True
|
86
|
+
return self
|
87
|
+
|
88
|
+
def set_render_order(self, render_order: int) -> Self:
|
89
|
+
super().set_render_order(render_order)
|
90
|
+
if self.parent:
|
91
|
+
self.parent.do_sort_children = True
|
92
|
+
return self
|
93
|
+
|
94
|
+
def expand_rect_with_padding(
|
95
|
+
self, rect: pygame.typing.RectLike
|
96
|
+
) -> pygame.Rect | pygame.FRect:
|
97
|
+
return pygame.FRect(
|
98
|
+
rect[0] - self.padding[0],
|
99
|
+
rect[1] - self.padding[1],
|
100
|
+
rect[2] + self.padding[0] + self.padding[2],
|
101
|
+
rect[3] + self.padding[1] + self.padding[3],
|
102
|
+
)
|
103
|
+
|
104
|
+
def set_position(self, x=None, y=None) -> Self:
|
105
|
+
if x is None:
|
106
|
+
x = self.rect.x
|
107
|
+
if y is None:
|
108
|
+
y = self.rect.y
|
109
|
+
if (x, y) == self.rect.topleft:
|
110
|
+
return self
|
111
|
+
dx, dy = x - self.rect.x, y - self.rect.y
|
112
|
+
self.rect.topleft = x, y
|
113
|
+
_ = [c.set_position(c.rect.x + dx, c.rect.y + dy) for c in self.children]
|
114
|
+
self.dirty_position_constraints: bool = True
|
115
|
+
return self
|
116
|
+
|
117
|
+
def set_center(self, x=None, y=None) -> Self:
|
118
|
+
if x is None:
|
119
|
+
x = self.rect.centerx
|
120
|
+
if y is None:
|
121
|
+
y = self.rect.centery
|
122
|
+
if (x, y) == self.rect.center:
|
123
|
+
return self
|
124
|
+
dx, dy = x - self.rect.centerx, y - self.rect.centery
|
125
|
+
self.rect.center = x, y
|
126
|
+
_ = [
|
127
|
+
c.set_center(c.rect.centerx + dx, c.rect.centery + dy)
|
128
|
+
for c in self.children
|
129
|
+
]
|
130
|
+
self.dirty_position_constraints: bool = True
|
131
|
+
|
132
|
+
return self
|
133
|
+
|
134
|
+
def set_parent_scene(self, parent_scene: bf.Scene | None) -> Self:
|
135
|
+
super().set_parent_scene(parent_scene)
|
136
|
+
if parent_scene is None and self.parent_scene != None:
|
137
|
+
bf.gui.StyleManager().remove_widget(self)
|
138
|
+
|
139
|
+
for c in self.children:
|
140
|
+
c.set_parent_scene(parent_scene)
|
141
|
+
return self
|
142
|
+
|
143
|
+
def set_parent_layer(self, layer):
|
144
|
+
super().set_parent_layer(layer)
|
145
|
+
for c in self.children:
|
146
|
+
c.set_parent_layer(layer)
|
147
|
+
return self
|
148
|
+
|
149
|
+
def set_parent(self, parent: "Widget") -> Self:
|
150
|
+
if parent == self.parent:
|
151
|
+
return self
|
152
|
+
# if self.parent is not None and self.parent != parent:
|
153
|
+
# self.parent.remove(self)
|
154
|
+
self.parent = parent
|
155
|
+
return self
|
156
|
+
|
157
|
+
def set_padding(self, value: float | int | tuple | list) -> Self:
|
158
|
+
if isinstance(value, Iterable):
|
159
|
+
if len(value) > 4:
|
160
|
+
pass
|
161
|
+
elif any(v < 0 for v in value):
|
162
|
+
pass
|
163
|
+
elif len(value) == 2:
|
164
|
+
self.padding = (value[0], value[1], value[0], value[1])
|
165
|
+
else:
|
166
|
+
self.padding = (*value, *self.padding[len(value) :])
|
167
|
+
else:
|
168
|
+
self.padding = (value,) * 4
|
169
|
+
|
170
|
+
self.dirty_shape = True
|
171
|
+
return self
|
172
|
+
|
173
|
+
def get_inner_rect(self) -> pygame.FRect:
|
174
|
+
r = self.rect.inflate(
|
175
|
+
-self.padding[0] - self.padding[2], -self.padding[1] - self.padding[3]
|
176
|
+
)
|
177
|
+
return r
|
178
|
+
|
179
|
+
def get_local_inner_rect(self)->pygame.FRect:
|
180
|
+
return pygame.FRect(
|
181
|
+
self.padding[0],
|
182
|
+
self.padding[1],
|
183
|
+
self.rect.w - self.padding[2] - self.padding[0],
|
184
|
+
self.rect.h - self.padding[1] - self.padding[3],
|
185
|
+
)
|
186
|
+
|
187
|
+
def get_min_required_size(self) -> tuple[float, float]:
|
188
|
+
return self.rect.size
|
189
|
+
|
190
|
+
def get_inner_width(self) -> float:
|
191
|
+
return self.rect.w - self.padding[0] - self.padding[2]
|
192
|
+
|
193
|
+
def get_inner_height(self) -> float:
|
194
|
+
return self.rect.h - self.padding[1] - self.padding[3]
|
195
|
+
|
196
|
+
def get_inner_left(self) -> float:
|
197
|
+
return self.rect.left + self.padding[0]
|
198
|
+
|
199
|
+
def get_inner_right(self) -> float:
|
200
|
+
return self.rect.right - self.padding[2]
|
201
|
+
|
202
|
+
def get_inner_center(self) -> tuple[float, float]:
|
203
|
+
return self.get_inner_rect().center
|
204
|
+
|
205
|
+
def get_inner_top(self) -> float:
|
206
|
+
return self.rect.y + self.padding[1]
|
207
|
+
|
208
|
+
def get_inner_bottom(self) -> float:
|
209
|
+
return self.rect.bottom - self.padding[3]
|
210
|
+
|
211
|
+
def get_debug_outlines(self):
|
212
|
+
if self.visible:
|
213
|
+
if any(self.padding):
|
214
|
+
yield (self.get_inner_rect(), self.debug_color)
|
215
|
+
# else:
|
216
|
+
yield (self.rect, self.debug_color)
|
217
|
+
for child in self.children:
|
218
|
+
yield from child.get_debug_outlines()
|
219
|
+
|
220
|
+
def add_constraints(self, *constraints: "Constraint") -> Self:
|
221
|
+
# Add constraints without duplicates
|
222
|
+
existing_names = {c.name for c in self.constraints}
|
223
|
+
new_constraints = [c for c in constraints if c.name not in existing_names]
|
224
|
+
self.constraints.extend(new_constraints)
|
225
|
+
|
226
|
+
# Sort constraints by priority
|
227
|
+
self.constraints.sort(key=lambda c: c.priority)
|
228
|
+
|
229
|
+
# Update dirty flags based on the new constraints
|
230
|
+
if any(c.affects_size for c in new_constraints):
|
231
|
+
self.dirty_size_constraints = True
|
232
|
+
if any(c.affects_position for c in new_constraints):
|
233
|
+
self.dirty_position_constraints = True
|
234
|
+
|
235
|
+
# Clear ignored constraints
|
236
|
+
self._constraints_to_ignore = []
|
237
|
+
|
238
|
+
return self
|
239
|
+
|
240
|
+
|
241
|
+
def remove_constraints(self, *names: str) -> Self:
|
242
|
+
for c in self.constraints:
|
243
|
+
if c.name in names:
|
244
|
+
c.on_removal(self)
|
245
|
+
self.constraints = [c for c in self.constraints if c.name not in names]
|
246
|
+
self._constraints_to_ignore = []
|
247
|
+
self.dirty_size_constraints = True
|
248
|
+
self.dirty_position_constraints= True
|
249
|
+
|
250
|
+
return self
|
251
|
+
|
252
|
+
|
253
|
+
def resolve_constraints(self, size_only: bool = False, position_only: bool = False) -> None:
|
254
|
+
"""
|
255
|
+
Resolve constraints affecting size and/or position independently.
|
256
|
+
|
257
|
+
This system attempts to apply constraints iteratively until a stable solution is found,
|
258
|
+
or until MAX_ITERATIONS is reached.
|
259
|
+
"""
|
260
|
+
if self.parent is None or not self.constraints:
|
261
|
+
if size_only:
|
262
|
+
self.dirty_size_constraints = False
|
263
|
+
if position_only:
|
264
|
+
self.dirty_position_constraints = False
|
265
|
+
return
|
266
|
+
|
267
|
+
# If not currently resolving constraints, reset tracking lists
|
268
|
+
if not self._constraint_iteration:
|
269
|
+
self._constraints_capture = []
|
270
|
+
else:
|
271
|
+
# Detect constraint priority changes since last resolution
|
272
|
+
current_priorities = [c.priority for c in self.constraints]
|
273
|
+
if current_priorities != self._constraints_capture:
|
274
|
+
self._constraints_capture = current_priorities
|
275
|
+
self._constraints_to_ignore = []
|
276
|
+
|
277
|
+
# Filter constraints based on what needs resolving
|
278
|
+
def is_relevant(c: "Constraint") -> bool:
|
279
|
+
return (
|
280
|
+
c.affects_size if size_only else
|
281
|
+
c.affects_position if position_only else
|
282
|
+
True
|
283
|
+
)
|
284
|
+
|
285
|
+
active_constraints = [c for c in self.constraints if is_relevant(c)]
|
286
|
+
active_constraints.sort(key=lambda c: c.priority, reverse=True)
|
287
|
+
|
288
|
+
resolved = []
|
289
|
+
for iteration in range(MAX_ITERATIONS):
|
290
|
+
self._constraint_iteration += 1
|
291
|
+
changed = False
|
292
|
+
|
293
|
+
for constraint in active_constraints:
|
294
|
+
if constraint in resolved:
|
295
|
+
# Re-evaluate to confirm the constraint is still satisfied
|
296
|
+
if not constraint.evaluate(self.parent, self):
|
297
|
+
resolved.remove(constraint)
|
298
|
+
changed = True
|
299
|
+
else:
|
300
|
+
# Try applying unresolved constraint
|
301
|
+
if constraint.apply(self.parent, self):
|
302
|
+
resolved.append(constraint)
|
303
|
+
changed = True
|
304
|
+
|
305
|
+
if not changed:
|
306
|
+
break # All constraints stable — done
|
307
|
+
|
308
|
+
# If solution is still unstable, record the unresolved ones
|
309
|
+
if self._constraint_iteration >= MAX_ITERATIONS:
|
310
|
+
self._constraints_to_ignore += [
|
311
|
+
c for c in active_constraints if c not in resolved
|
312
|
+
]
|
313
|
+
|
314
|
+
# Record final resolved constraints for debugging/tracking
|
315
|
+
self._constraints_capture.clear()
|
316
|
+
self._constraints_capture.extend(
|
317
|
+
(c, self._constraint_iteration) for c in resolved
|
318
|
+
)
|
319
|
+
|
320
|
+
# Clear appropriate dirty flags
|
321
|
+
if size_only:
|
322
|
+
self.dirty_size_constraints = False
|
323
|
+
if position_only:
|
324
|
+
self.dirty_position_constraints = False
|
325
|
+
|
326
|
+
# Debug print for ignored constraints
|
327
|
+
# if self._constraints_to_ignore:
|
328
|
+
# print(f"{self} ignored constraints: {[str(c) for c in self._constraints_to_ignore]}")
|
329
|
+
|
330
|
+
|
331
|
+
def has_constraint(self, name: str) -> bool:
|
332
|
+
return any(c.name == name for c in self.constraints)
|
333
|
+
|
334
|
+
def get_root(self) -> "Root":
|
335
|
+
if self.is_root:
|
336
|
+
return self
|
337
|
+
if self.parent:
|
338
|
+
return self.parent.get_root()
|
339
|
+
return None
|
340
|
+
|
341
|
+
def get_by_tags(self,*tags: str)->list["Widget"]:
|
342
|
+
#use self.has_tags(*tags) for check
|
343
|
+
result = []
|
344
|
+
self.visit(lambda w: result.append(w) if w.has_tags(*tags) else None)
|
345
|
+
return result
|
346
|
+
|
347
|
+
def top_at(self, x: float | int, y: float | int) -> "None|Widget":
|
348
|
+
for child in reversed(self.children):
|
349
|
+
if child.visible:
|
350
|
+
r = child.top_at(x, y)
|
351
|
+
if r is not None:
|
352
|
+
return r
|
353
|
+
|
354
|
+
return self if self.visible and self.rect.collidepoint(x, y) else None
|
355
|
+
|
356
|
+
def add(self, *children: "Widget") -> Self:
|
357
|
+
self.children.extend(children)
|
358
|
+
i = len(self.children)
|
359
|
+
for child in children:
|
360
|
+
if child.render_order == 0:
|
361
|
+
child.set_render_order(i+1)
|
362
|
+
child.set_parent(self)
|
363
|
+
child.set_parent_layer(self.parent_layer)
|
364
|
+
child.set_parent_scene(self.parent_scene)
|
365
|
+
i += 1
|
366
|
+
if self.parent:
|
367
|
+
self.parent.do_sort_children = True
|
368
|
+
return self
|
369
|
+
|
370
|
+
def remove(self, *children: "Widget") -> Self:
|
371
|
+
for child in self.children.copy():
|
372
|
+
if child in children:
|
373
|
+
child.set_parent(None)
|
374
|
+
child.set_parent_scene(None)
|
375
|
+
child.set_parent_layer(None)
|
376
|
+
self.children.remove(child)
|
377
|
+
if self.parent:
|
378
|
+
self.parent.do_sort_children = True
|
379
|
+
|
380
|
+
def resolve_size(self, target_size):
|
381
|
+
return (
|
382
|
+
target_size[0] if self.autoresize_w else self.rect.w,
|
383
|
+
target_size[1] if self.autoresize_h else self.rect.h
|
384
|
+
)
|
385
|
+
|
386
|
+
def set_size(self, size: tuple) -> Self:
|
387
|
+
size = list(size)
|
388
|
+
if size[0] is None:
|
389
|
+
size[0] = self.rect.w
|
390
|
+
if size[1] is None:
|
391
|
+
size[1] = self.rect.h
|
392
|
+
if size[0] == self.rect.w and size[1] == self.rect.h : return self
|
393
|
+
self.rect.size = size
|
394
|
+
self.dirty_shape = True
|
395
|
+
return self
|
396
|
+
|
397
|
+
def process_event(self, event: pygame.Event) -> None:
|
398
|
+
# First propagate to children
|
399
|
+
for child in self.children:
|
400
|
+
child.process_event(event)
|
401
|
+
super().process_event(event)
|
402
|
+
|
403
|
+
def update(self, dt) -> None:
|
404
|
+
if self.do_sort_children:
|
405
|
+
self.children.sort(key=lambda c: c.render_order)
|
406
|
+
self.do_sort_children = False
|
407
|
+
_ = [c.update(dt) for c in self.children]
|
408
|
+
super().update(dt)
|
409
|
+
|
410
|
+
|
411
|
+
def _resize_surface(self)->bool:
|
412
|
+
"""
|
413
|
+
returns True if size changed
|
414
|
+
"""
|
415
|
+
new_size = tuple(map(int, self.rect.size))
|
416
|
+
if self.surface.get_size() == new_size:
|
417
|
+
return False
|
418
|
+
|
419
|
+
old_alpha = self.surface.get_alpha()
|
420
|
+
new_size = [max(0, i) for i in new_size]
|
421
|
+
self.surface = pygame.Surface(new_size, self.surface_flags)
|
422
|
+
if self.convert_alpha:
|
423
|
+
self.surface = self.surface.convert_alpha()
|
424
|
+
self.surface.set_alpha(old_alpha)
|
425
|
+
return True
|
426
|
+
|
427
|
+
|
428
|
+
def build(self) -> bool:
|
429
|
+
"""
|
430
|
+
Updates the size of the widget.
|
431
|
+
return True if size changed
|
432
|
+
"""
|
433
|
+
return False
|
434
|
+
|
435
|
+
def paint(self) -> None:
|
436
|
+
self._resize_surface()
|
437
|
+
self.surface.fill((0, 0, 0, 0))
|
438
|
+
|
439
|
+
def visit(self, func: Callable[["Widget"],Any], top_down: bool = True, *args, **kwargs) -> None:
|
440
|
+
if top_down:
|
441
|
+
func(self, *args, **kwargs)
|
442
|
+
for child in self.children:
|
443
|
+
child.visit(func, top_down,*args,**kwargs)
|
444
|
+
if not top_down:
|
445
|
+
func(self, *args, **kwargs)
|
446
|
+
|
447
|
+
def visit_up(self, func, *args, **kwargs) -> None:
|
448
|
+
if func(self, *args, **kwargs):
|
449
|
+
return
|
450
|
+
if self.parent:
|
451
|
+
self.parent.visit_up(func, *args, **kwargs)
|
452
|
+
|
453
|
+
"""
|
454
|
+
1 :
|
455
|
+
bottom up -> only build children
|
456
|
+
|
457
|
+
"""
|
458
|
+
def update_children_size(self, widget: "Widget"):
|
459
|
+
# print(widget,widget.uid,"constraints resolve in update size func")
|
460
|
+
|
461
|
+
widget.resolve_constraints()
|
462
|
+
if widget.dirty_shape:
|
463
|
+
# print(widget,widget.uid,"build in update size func")
|
464
|
+
widget.build()
|
465
|
+
widget.dirty_shape = False
|
466
|
+
widget.dirty_surface = True
|
467
|
+
|
468
|
+
def find_highest_dirty_constraints_widget(self) -> "Widget":
|
469
|
+
w = self
|
470
|
+
tmp = w
|
471
|
+
while not tmp.is_root:
|
472
|
+
if tmp.dirty_size_constraints or tmp.dirty_shape:
|
473
|
+
w = tmp
|
474
|
+
if not tmp.parent:
|
475
|
+
break
|
476
|
+
tmp = tmp.parent
|
477
|
+
return w
|
478
|
+
|
479
|
+
|
480
|
+
def apply_updates(self,pass_type):
|
481
|
+
# print(f"Apply updates {pass_type} called on {self}")
|
482
|
+
if pass_type == "pre":
|
483
|
+
self.apply_pre_updates()
|
484
|
+
for child in self.children:
|
485
|
+
child.apply_updates("pre")
|
486
|
+
elif pass_type == "post":
|
487
|
+
for child in self.children:
|
488
|
+
child.apply_updates("post")
|
489
|
+
self.apply_post_updates(skip_draw=not self.visible)
|
490
|
+
|
491
|
+
def apply_pre_updates(self):
|
492
|
+
"""
|
493
|
+
TOP TO BOTTOM
|
494
|
+
Resolves size-related constraints before propagating updates to children.
|
495
|
+
"""
|
496
|
+
if self.dirty_size_constraints:
|
497
|
+
self.resolve_constraints(size_only=True)
|
498
|
+
self.dirty_size_constraints = False
|
499
|
+
self.dirty_position_constraints = True
|
500
|
+
|
501
|
+
def apply_post_updates(self, skip_draw: bool = False):
|
502
|
+
"""
|
503
|
+
BOTTOM TO TOP
|
504
|
+
Resolves position-related constraints after propagating updates from children.
|
505
|
+
"""
|
506
|
+
|
507
|
+
if self.dirty_shape:
|
508
|
+
|
509
|
+
if self.build():
|
510
|
+
self.dirty_size_constraints = True
|
511
|
+
self.dirty_position_constraints = True
|
512
|
+
if self.parent :
|
513
|
+
# trigger layout or constraint updates in parent
|
514
|
+
from .container import Container
|
515
|
+
from .scrollingContainer import ScrollingContainer
|
516
|
+
if self.parent and (isinstance(self.parent, Container) and (self.parent.autoresize_h or self.parent.autoresize_w) \
|
517
|
+
or isinstance(self.parent,ScrollingContainer)):
|
518
|
+
self.parent.dirty_layout = True
|
519
|
+
self.parent.dirty_shape = True
|
520
|
+
self.dirty_shape = False
|
521
|
+
self.dirty_surface = True
|
522
|
+
|
523
|
+
if self.dirty_position_constraints:
|
524
|
+
self.resolve_constraints(position_only=True)
|
525
|
+
|
526
|
+
if self.dirty_surface and not skip_draw:
|
527
|
+
self.paint()
|
528
|
+
self.dirty_surface = False
|
529
|
+
|
530
|
+
|
531
|
+
def draw(self, camera: bf.Camera) -> None:
|
532
|
+
# Draw widget and handle clipping if necessary
|
533
|
+
super().draw(camera)
|
534
|
+
|
535
|
+
if self.clip_children:
|
536
|
+
new_clip = camera.world_to_screen(self.get_inner_rect())
|
537
|
+
old_clip = camera.surface.get_clip()
|
538
|
+
new_clip = new_clip.clip(old_clip)
|
539
|
+
camera.surface.set_clip(new_clip)
|
540
|
+
|
541
|
+
# Draw each child widget, sorted by render order
|
542
|
+
for child in self.children:
|
543
|
+
if (not self.clip_children) or (child.rect.colliderect(self.rect) or not child.rect):
|
544
|
+
child.draw(camera)
|
545
|
+
if self.clip_children:
|
546
|
+
camera.surface.set_clip(old_clip)
|