batframework 1.0.9a11__py3-none-any.whl → 1.1.0__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 +52 -76
- batFramework/action.py +99 -126
- batFramework/actionContainer.py +9 -53
- batFramework/animatedSprite.py +114 -56
- batFramework/audioManager.py +36 -82
- batFramework/camera.py +69 -263
- batFramework/constants.py +53 -29
- batFramework/cutscene.py +109 -243
- batFramework/cutsceneBlocks.py +176 -0
- batFramework/debugger.py +48 -0
- batFramework/dynamicEntity.py +9 -16
- batFramework/easing.py +71 -0
- batFramework/entity.py +85 -92
- batFramework/gui/__init__.py +3 -14
- batFramework/gui/button.py +78 -12
- batFramework/gui/constraints.py +204 -0
- batFramework/gui/container.py +31 -188
- batFramework/gui/debugger.py +43 -126
- batFramework/gui/frame.py +19 -0
- batFramework/gui/image.py +20 -55
- batFramework/gui/indicator.py +22 -95
- batFramework/gui/interactiveWidget.py +12 -229
- batFramework/gui/label.py +77 -311
- batFramework/gui/layout.py +66 -414
- batFramework/gui/root.py +35 -203
- batFramework/gui/shape.py +57 -247
- batFramework/gui/toggle.py +48 -114
- batFramework/gui/widget.py +243 -457
- batFramework/manager.py +29 -113
- batFramework/particles.py +77 -0
- batFramework/scene.py +217 -22
- batFramework/sceneManager.py +129 -161
- batFramework/stateMachine.py +8 -11
- batFramework/time.py +75 -0
- batFramework/transition.py +124 -129
- batFramework/transitionManager.py +0 -0
- batFramework/triggerZone.py +4 -4
- batFramework/utils.py +144 -266
- {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/METADATA +24 -22
- batframework-1.1.0.dist-info/RECORD +43 -0
- batFramework/animation.py +0 -77
- batFramework/baseScene.py +0 -240
- batFramework/cutsceneManager.py +0 -34
- batFramework/drawable.py +0 -77
- batFramework/easingController.py +0 -58
- batFramework/enums.py +0 -135
- batFramework/fontManager.py +0 -65
- batFramework/gui/animatedLabel.py +0 -89
- batFramework/gui/clickableWidget.py +0 -244
- batFramework/gui/constraints/__init__.py +0 -1
- batFramework/gui/constraints/constraints.py +0 -980
- batFramework/gui/draggableWidget.py +0 -44
- batFramework/gui/meter.py +0 -96
- batFramework/gui/radioButton.py +0 -35
- batFramework/gui/selector.py +0 -250
- batFramework/gui/slider.py +0 -397
- batFramework/gui/style.py +0 -10
- batFramework/gui/styleManager.py +0 -54
- batFramework/gui/syncedVar.py +0 -49
- batFramework/gui/textInput.py +0 -306
- batFramework/gui/tooltip.py +0 -30
- batFramework/particle.py +0 -118
- batFramework/propertyEaser.py +0 -79
- batFramework/renderGroup.py +0 -34
- batFramework/resourceManager.py +0 -130
- batFramework/sceneLayer.py +0 -138
- batFramework/scrollingSprite.py +0 -115
- batFramework/sprite.py +0 -51
- batFramework/templates/__init__.py +0 -1
- batFramework/templates/controller.py +0 -97
- batFramework/tileset.py +0 -46
- batFramework/timeManager.py +0 -213
- batframework-1.0.9a11.dist-info/RECORD +0 -67
- {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/LICENSE +0 -0
- {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/WHEEL +0 -0
- {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/top_level.txt +0 -0
batFramework/gui/widget.py
CHANGED
@@ -1,521 +1,307 @@
|
|
1
|
-
from
|
2
|
-
from
|
3
|
-
import batFramework as bf
|
4
|
-
import pygame
|
5
|
-
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import TYPE_CHECKING
|
6
3
|
if TYPE_CHECKING:
|
7
|
-
from .constraints
|
4
|
+
from .constraints import Constraint
|
8
5
|
from .root import Root
|
6
|
+
from typing import Self
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
self.children: list["Widget"] = []
|
23
|
-
self.
|
24
|
-
self.
|
25
|
-
self.
|
26
|
-
self.
|
27
|
-
self.
|
28
|
-
self.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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 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()
|
8
|
+
import batFramework as bf
|
9
|
+
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
|
59
42
|
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
+
)
|
63
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]
|
55
|
+
)
|
56
|
+
def get_content_left(self)->float:
|
57
|
+
return self.rect.left + self.padding[0]
|
64
58
|
|
65
|
-
def
|
66
|
-
return
|
59
|
+
def get_content_top(self)->float:
|
60
|
+
return self.rect.top + self.padding[1]
|
67
61
|
|
68
|
-
def
|
69
|
-
self.
|
70
|
-
self.dirty_shape = True
|
71
|
-
return self
|
62
|
+
def get_content_right(self)->float:
|
63
|
+
return self.rect.right - self.padding[2]
|
72
64
|
|
73
|
-
def
|
74
|
-
self.
|
75
|
-
self.dirty_shape = True
|
76
|
-
return self
|
65
|
+
def get_content_bottom(self)->float:
|
66
|
+
return self.rect.bottom - self.padding[3]
|
77
67
|
|
78
|
-
def
|
79
|
-
self.
|
80
|
-
self.dirty_shape = True
|
81
|
-
return self
|
68
|
+
def get_content_width(self)->float:
|
69
|
+
return self.rect.w - self.padding[0] - self.padding[2]
|
82
70
|
|
83
|
-
def
|
84
|
-
|
85
|
-
if self.parent:
|
86
|
-
self.parent.do_sort_children = True
|
87
|
-
return self
|
71
|
+
def get_content_height(self)->float:
|
72
|
+
return self.rect.h - self.padding[1] - self.padding[3]
|
88
73
|
|
89
|
-
def
|
90
|
-
self, rect: pygame.Rect | pygame.FRect
|
91
|
-
) -> pygame.Rect | pygame.FRect:
|
74
|
+
def get_content_rect(self)->pygame.FRect:
|
92
75
|
return pygame.FRect(
|
93
|
-
rect
|
94
|
-
rect
|
95
|
-
|
96
|
-
|
76
|
+
self.rect.left + self.padding[0],
|
77
|
+
self.rect.top + self.padding[1],
|
78
|
+
self.get_content_width(),
|
79
|
+
self.get_content_height()
|
97
80
|
)
|
98
81
|
|
99
|
-
def
|
100
|
-
|
101
|
-
x = self.rect.x
|
102
|
-
if y is None:
|
103
|
-
y = self.rect.y
|
104
|
-
if (x, y) == self.rect.topleft:
|
105
|
-
return self
|
106
|
-
dx, dy = x - self.rect.x, y - self.rect.y
|
107
|
-
self.rect.topleft = x, y
|
108
|
-
_ = [c.set_position(c.rect.x + dx, c.rect.y + dy) for c in self.children]
|
109
|
-
return self
|
110
|
-
|
111
|
-
def set_center(self, x, y) -> Self:
|
112
|
-
if x is None:
|
113
|
-
x = self.rect.centerx
|
114
|
-
if y is None:
|
115
|
-
y = self.rect.centery
|
116
|
-
if (x, y) == self.rect.center:
|
117
|
-
return self
|
118
|
-
dx, dy = x - self.rect.centerx, y - self.rect.centery
|
119
|
-
self.rect.center = x, y
|
120
|
-
_ = [
|
121
|
-
c.set_center(c.rect.centerx + dx, c.rect.centery + dy)
|
122
|
-
for c in self.children
|
123
|
-
]
|
124
|
-
return self
|
82
|
+
def get_content_rect_rel(self)->pygame.FRect:
|
83
|
+
return self.get_content_rect().move(-self.rect.left,-self.rect.top)
|
125
84
|
|
126
|
-
def
|
127
|
-
|
128
|
-
if parent_scene is None:
|
129
|
-
bf.gui.StyleManager().remove_widget(self)
|
85
|
+
def get_content_center(self)->tuple[float,float]:
|
86
|
+
return self.get_content_rect().center
|
130
87
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
for c in self.children:
|
138
|
-
c.set_parent_layer(layer)
|
139
|
-
return self
|
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
|
140
94
|
|
141
|
-
def
|
142
|
-
if
|
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:
|
143
102
|
return self
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
return self
|
148
|
-
|
149
|
-
def set_padding(self, value: float | int | tuple | list) -> Self:
|
150
|
-
if isinstance(value, Iterable):
|
151
|
-
if len(value) > 4:
|
152
|
-
pass
|
153
|
-
elif any(v < 0 for v in value):
|
154
|
-
pass
|
155
|
-
elif len(value) == 2:
|
156
|
-
self.padding = (value[0], value[1], value[0], value[1])
|
157
|
-
else:
|
158
|
-
self.padding = (*value, *self.padding[len(value) :])
|
159
|
-
else:
|
160
|
-
self.padding = (value,) * 4
|
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)
|
161
107
|
|
162
|
-
|
108
|
+
def add_constraints(self,*constraints:Constraint)->Self:
|
109
|
+
for c in constraints:
|
110
|
+
self.add_constraint(c,False)
|
111
|
+
self.apply_constraints()
|
112
|
+
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()
|
163
120
|
return self
|
164
121
|
|
165
|
-
def
|
166
|
-
|
167
|
-
-self.padding[0] - self.padding[2], -self.padding[1] - self.padding[3]
|
168
|
-
)
|
169
|
-
# r.normalize()
|
170
|
-
return r
|
122
|
+
def has_constraint(self,name:str)->bool:
|
123
|
+
return any(c.name == name for c in self.constraints)
|
171
124
|
|
172
|
-
def
|
173
|
-
|
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)
|
174
138
|
|
175
|
-
|
176
|
-
|
139
|
+
for iteration in range(max_iterations):
|
140
|
+
unsatisfied = [] # Initialize a flag
|
177
141
|
|
178
|
-
|
179
|
-
|
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])}")
|
180
153
|
|
181
|
-
|
182
|
-
|
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()
|
183
159
|
|
184
|
-
def
|
185
|
-
return self.rect.
|
160
|
+
def get_size_int(self)->tuple[int,int]:
|
161
|
+
return (ceil(self.rect.width),ceil(self.rect.height))
|
186
162
|
|
187
|
-
def get_inner_center(self) -> tuple[float, float]:
|
188
|
-
return self.get_inner_rect().center
|
189
163
|
|
190
|
-
def
|
191
|
-
return self.rect.
|
164
|
+
def get_center(self)->tuple[float,float]:
|
165
|
+
return self.rect.center
|
192
166
|
|
193
|
-
def get_inner_bottom(self) -> float:
|
194
|
-
return self.rect.bottom - self.padding[3]
|
195
167
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
yield (self.get_inner_rect(), self.debug_color)
|
200
|
-
# else:
|
201
|
-
yield (self.rect, self.debug_color)
|
168
|
+
def get_bounding_box(self):
|
169
|
+
yield (self.rect,self._debug_color)
|
170
|
+
yield (self.get_content_rect(),"yellow")
|
202
171
|
for child in self.children:
|
203
|
-
yield from child.
|
172
|
+
yield from child.get_bounding_box()
|
204
173
|
|
205
|
-
def
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
self.constraints.extend(new_constraints)
|
174
|
+
def set_autoresize(self,value:bool)-> Self:
|
175
|
+
self.autoresize = value
|
176
|
+
self.build()
|
177
|
+
return self
|
210
178
|
|
211
|
-
|
212
|
-
self.
|
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
|
213
189
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
self.dirty_position_constraints = True
|
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)
|
219
194
|
|
220
|
-
|
221
|
-
|
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
|
202
|
+
|
203
|
+
def set_y(self,y:float)->Self:
|
204
|
+
delta = y - self.rect.y
|
205
|
+
self.rect.y = y
|
206
|
+
for child in self.children:
|
207
|
+
child.set_y(child.rect.y + delta)
|
208
|
+
self.apply_constraints()
|
222
209
|
|
223
210
|
return self
|
224
211
|
|
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()
|
228
|
+
return self
|
225
229
|
|
226
|
-
def remove_constraints(self, *names: str) -> Self:
|
227
|
-
for c in self.constraints:
|
228
|
-
if c.name in names:
|
229
|
-
c.on_removal(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
230
|
|
231
|
+
def set_size(self, width : float, height: float) -> Self:
|
232
|
+
self.rect.size = (width,height)
|
233
|
+
self.build()
|
235
234
|
return self
|
235
|
+
|
236
236
|
|
237
|
+
# Other Methods
|
237
238
|
|
238
|
-
def
|
239
|
-
|
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
243
|
|
242
|
-
|
243
|
-
|
244
|
-
""
|
245
|
-
if self.parent is None or not self.constraints:
|
246
|
-
if size_only:
|
247
|
-
self.dirty_size_constraints = False
|
248
|
-
if position_only:
|
249
|
-
self.dirty_position_constraints = False
|
250
|
-
return
|
244
|
+
def to_string(self)->str:
|
245
|
+
|
246
|
+
return f"{self.to_string_id()}@{*self.rect.topleft,* self.rect.size}"
|
251
247
|
|
252
|
-
# If not currently resolving constraints, reset tracking lists
|
253
|
-
if not self._constraint_iteration:
|
254
|
-
self._constraints_capture = []
|
255
|
-
else:
|
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
|
-
)
|
269
|
-
|
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
248
|
|
305
|
-
|
306
|
-
|
307
|
-
self.dirty_size_constraints = False
|
308
|
-
if position_only:
|
309
|
-
self.dirty_position_constraints = False
|
249
|
+
def to_string_id(self)->str:
|
250
|
+
return "Widget"
|
310
251
|
|
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]}")
|
314
252
|
|
253
|
+
def do_when_removed(self)->None:
|
254
|
+
if self.parent_scene and self.parent == self.parent_scene.root:
|
255
|
+
self.set_parent(None)
|
315
256
|
|
316
|
-
|
317
|
-
return any(c.name == name for c in self.constraints)
|
257
|
+
# Methods on children
|
318
258
|
|
319
|
-
def
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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()
|
325
266
|
|
326
|
-
def
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
def top_at(self, x: float | int, y: float | int) -> "None|Widget":
|
333
|
-
if self.children:
|
334
|
-
for child in reversed(self.children):
|
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
|
340
|
-
|
341
|
-
def add(self, *children: "Widget") -> Self:
|
342
|
-
self.children.extend(children)
|
343
|
-
i = len(self.children)
|
344
|
-
for child in children:
|
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)
|
350
|
-
i += 1
|
351
|
-
if self.parent:
|
352
|
-
self.parent.do_sort_children = True
|
353
|
-
return self
|
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()
|
354
272
|
|
355
|
-
def remove(self, *children: "Widget") -> Self:
|
356
|
-
for child in self.children.copy():
|
357
|
-
if child in children:
|
358
|
-
child.set_parent(None)
|
359
|
-
child.set_parent_scene(None)
|
360
|
-
child.set_parent_layer(None)
|
361
|
-
self.children.remove(child)
|
362
|
-
if self.parent:
|
363
|
-
self.parent.do_sort_children = True
|
364
273
|
|
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
|
-
)
|
370
274
|
|
371
|
-
def set_size(self, size: tuple) -> Self:
|
372
|
-
size = list(size)
|
373
|
-
if size[0] is None:
|
374
|
-
size[0] = self.rect.w
|
375
|
-
if size[1] is None:
|
376
|
-
size[1] = self.rect.h
|
377
|
-
if size[0] == self.rect.w and size[1] == self.rect.h : return self
|
378
|
-
self.rect.size = size
|
379
|
-
self.dirty_shape = True
|
380
|
-
return self
|
381
275
|
|
382
|
-
|
276
|
+
# if return True -> don't propagate to siblings or parents
|
277
|
+
def process_event(self, event: pygame.Event)->bool:
|
383
278
|
# First propagate to children
|
384
279
|
for child in self.children:
|
385
|
-
child.process_event(event)
|
386
|
-
|
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)
|
387
284
|
|
388
|
-
def update(self, dt) -> None:
|
389
|
-
if self.do_sort_children:
|
390
|
-
self.children.sort(key=lambda c: c.render_order)
|
391
|
-
self.do_sort_children = False
|
392
|
-
_ = [c.update(dt) for c in self.children]
|
393
|
-
super().update(dt)
|
394
|
-
|
395
|
-
def build(self) -> bool:
|
396
|
-
"""
|
397
|
-
Updates the size of the widget.
|
398
|
-
return True if size changed
|
399
|
-
"""
|
400
|
-
new_size = tuple(map(int, self.rect.size))
|
401
|
-
if self.surface.get_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
|
411
|
-
|
412
|
-
def paint(self) -> None:
|
413
|
-
self.surface.fill((0, 0, 0, 0))
|
414
|
-
return self
|
415
285
|
|
416
|
-
def
|
417
|
-
if top_down:
|
418
|
-
func(self, *args, **kwargs)
|
286
|
+
def update(self,dt:float):
|
419
287
|
for child in self.children:
|
420
|
-
child.
|
421
|
-
if not top_down:
|
422
|
-
func(self, *args, **kwargs)
|
288
|
+
child.update(dt)
|
423
289
|
|
424
|
-
def
|
425
|
-
|
426
|
-
|
427
|
-
if self.parent:
|
428
|
-
self.parent.visit_up(func, *args, **kwargs)
|
429
|
-
|
430
|
-
"""
|
431
|
-
1 :
|
432
|
-
bottom up -> only build children
|
433
|
-
|
434
|
-
"""
|
435
|
-
def update_children_size(self, widget: "Widget"):
|
436
|
-
# print(widget,widget.uid,"constraints resolve in update size func")
|
437
|
-
|
438
|
-
widget.resolve_constraints()
|
439
|
-
if widget.dirty_shape:
|
440
|
-
# print(widget,widget.uid,"build in update size func")
|
441
|
-
widget.build()
|
442
|
-
widget.dirty_shape = False
|
443
|
-
widget.dirty_surface = True
|
444
|
-
|
445
|
-
def find_highest_dirty_constraints_widget(self) -> "Widget":
|
446
|
-
w = self
|
447
|
-
tmp = w
|
448
|
-
while not tmp.is_root:
|
449
|
-
if tmp.dirty_size_constraints or tmp.dirty_shape:
|
450
|
-
w = tmp
|
451
|
-
if not tmp.parent:
|
452
|
-
break
|
453
|
-
tmp = tmp.parent
|
454
|
-
return w
|
455
|
-
|
456
|
-
|
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
|
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])
|
477
293
|
|
478
|
-
def
|
294
|
+
def build(self)->None:
|
479
295
|
"""
|
480
|
-
|
481
|
-
|
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
|
482
298
|
"""
|
483
|
-
|
484
|
-
if self.
|
485
|
-
|
486
|
-
if self.
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
if self.parent and isinstance(self.parent, Container):
|
493
|
-
self.parent.dirty_layout = True
|
494
|
-
self.parent.dirty_shape = True
|
495
|
-
self.dirty_shape = False
|
496
|
-
self.dirty_surface = True
|
497
|
-
|
498
|
-
if self.dirty_position_constraints:
|
499
|
-
self.resolve_constraints(position_only=True)
|
500
|
-
|
501
|
-
if self.dirty_surface and not skip_draw:
|
502
|
-
self.paint()
|
503
|
-
self.dirty_surface = False
|
504
|
-
|
505
|
-
|
506
|
-
def draw(self, camera: bf.Camera) -> None:
|
507
|
-
# Draw widget and handle clipping if necessary
|
508
|
-
super().draw(camera)
|
509
|
-
|
510
|
-
if self.clip_children:
|
511
|
-
new_clip = camera.world_to_screen(self.get_inner_rect())
|
512
|
-
old_clip = camera.surface.get_clip()
|
513
|
-
new_clip = new_clip.clip(old_clip)
|
514
|
-
camera.surface.set_clip(new_clip)
|
515
|
-
|
516
|
-
# Draw each child widget, sorted by render order
|
517
|
-
for child in sorted(self.children, key=lambda c: c.render_order):
|
518
|
-
if (not self.clip_children) or (child.rect.colliderect(self.rect) or not child.rect):
|
519
|
-
child.draw(camera)
|
520
|
-
if self.clip_children:
|
521
|
-
camera.surface.set_clip(old_clip)
|
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()
|