batframework 1.0.6__py3-none-any.whl → 1.0.8a1__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 (59) hide show
  1. batFramework/__init__.py +23 -14
  2. batFramework/action.py +95 -106
  3. batFramework/actionContainer.py +11 -8
  4. batFramework/animatedSprite.py +60 -43
  5. batFramework/audioManager.py +52 -22
  6. batFramework/camera.py +87 -72
  7. batFramework/constants.py +19 -41
  8. batFramework/cutscene.py +12 -11
  9. batFramework/cutsceneBlocks.py +12 -14
  10. batFramework/dynamicEntity.py +5 -4
  11. batFramework/easingController.py +58 -0
  12. batFramework/entity.py +37 -130
  13. batFramework/enums.py +93 -3
  14. batFramework/fontManager.py +15 -7
  15. batFramework/gui/__init__.py +5 -1
  16. batFramework/gui/button.py +6 -144
  17. batFramework/gui/clickableWidget.py +206 -0
  18. batFramework/gui/constraints/__init__.py +1 -0
  19. batFramework/gui/constraints/constraints.py +378 -0
  20. batFramework/gui/container.py +147 -34
  21. batFramework/gui/debugger.py +39 -22
  22. batFramework/gui/dialogueBox.py +69 -43
  23. batFramework/gui/draggableWidget.py +38 -0
  24. batFramework/gui/image.py +33 -28
  25. batFramework/gui/indicator.py +30 -16
  26. batFramework/gui/interactiveWidget.py +72 -20
  27. batFramework/gui/label.py +240 -98
  28. batFramework/gui/layout.py +125 -53
  29. batFramework/gui/meter.py +76 -0
  30. batFramework/gui/radioButton.py +62 -0
  31. batFramework/gui/root.py +72 -48
  32. batFramework/gui/shape.py +257 -49
  33. batFramework/gui/slider.py +217 -2
  34. batFramework/gui/textInput.py +106 -60
  35. batFramework/gui/toggle.py +85 -42
  36. batFramework/gui/widget.py +259 -288
  37. batFramework/manager.py +30 -16
  38. batFramework/object.py +115 -0
  39. batFramework/{particles.py → particle.py} +37 -34
  40. batFramework/renderGroup.py +62 -0
  41. batFramework/resourceManager.py +36 -24
  42. batFramework/scene.py +94 -88
  43. batFramework/sceneManager.py +140 -57
  44. batFramework/scrollingSprite.py +113 -0
  45. batFramework/sprite.py +35 -23
  46. batFramework/tileset.py +34 -52
  47. batFramework/time.py +105 -56
  48. batFramework/transition.py +213 -1
  49. batFramework/utils.py +38 -18
  50. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/METADATA +1 -1
  51. batframework-1.0.8a1.dist-info/RECORD +56 -0
  52. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/WHEEL +1 -1
  53. batFramework/easing.py +0 -76
  54. batFramework/gui/constraints.py +0 -277
  55. batFramework/gui/frame.py +0 -25
  56. batFramework/transitionManager.py +0 -0
  57. batframework-1.0.6.dist-info/RECORD +0 -50
  58. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/LICENCE +0 -0
  59. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/top_level.txt +0 -0
@@ -1,39 +1,94 @@
1
- from __future__ import annotations
2
- from typing import TYPE_CHECKING
3
-
4
- if TYPE_CHECKING:
5
- from .constraints import Constraint
6
- from .root import Root
7
- from .interactiveWidget import InteractiveWidget
8
- from typing import Self
9
-
1
+ from typing import TYPE_CHECKING,Self,Callable
2
+ from collections.abc import Iterable
10
3
  import batFramework as bf
11
4
  import pygame
12
- from math import ceil
13
-
14
- MAX_CONSTRAINT_ITERATION = 10
15
-
5
+ if TYPE_CHECKING :
6
+ from .constraints.constraints import Constraint
7
+ from .root import Root
8
+ MAX_CONSTRAINTS = 10
16
9
  class Widget(bf.Entity):
17
- def __init__(self, convert_alpha=True) -> None:
18
- super().__init__(convert_alpha=convert_alpha)
19
- self.autoresize = False
20
- self.parent: None | Self = None
21
- self.is_root: bool = False
10
+ def __init__(self,*args,**kwargs) -> None:
11
+ super().__init__(*args,**kwargs)
22
12
  self.children: list["Widget"] = []
23
- self.focusable: bool = False
24
- self.constraints: list[Constraint] = []
25
- self.gui_depth: int = 0
26
- if self.surface:
27
- self.surface.fill("white")
28
- self.set_debug_color("red")
29
- self.padding: tuple[float | int, ...] = (0, 0, 0, 0)
13
+ self.constraints : list[Constraint] = []
14
+ self.parent : "Widget" = None
15
+ self.do_sort_children =False
16
+ self.clip_children : bool = True
17
+ self.padding = (0, 0, 0, 0)
18
+ self.dirty_surface : bool = True # if true will call paint before drawing
19
+ self.dirty_shape : bool = True # if true will call (build+paint) before drawing
20
+ self.dirty_constraints : bool = False
21
+ self.is_root : bool = False
22
+ self.autoresize_w,self.autoresize_h = True,True
23
+ self.__constraint_iteration = 0
24
+
25
+ def set_clip_children(self,value:bool)->Self:
26
+ self.clip_children = value
27
+ self.dirty_surface = True
28
+ return self
30
29
 
31
- def set_padding(self, value: float | int | tuple | list) -> Self:
32
- old_raw_size = (
33
- self.rect.w - self.padding[0] - self.padding[2],
34
- self.rect.h - self.padding[1] - self.padding[3],
30
+ def __str__(self)->str:
31
+ return "Widget"
32
+
33
+ def set_autoresize(self,value:bool)->Self:
34
+ self.autoresize_w = self.autoresize_h = value
35
+ self.dirty_shape = True
36
+ return self
37
+
38
+ def set_autoresize_w(self,value:bool)->Self:
39
+ self.autoresize_w = value
40
+ self.dirty_shape = True
41
+ return self
42
+
43
+ def set_autoresize_h(self,value:bool)->Self:
44
+ self.autoresize_h = value
45
+ self.dirty_shape = True
46
+ return self
47
+
48
+ def set_render_order(self,render_order:int)->Self:
49
+ super().set_render_order(render_order)
50
+ if self.parent:self.parent.do_sort_children = True
51
+ return self
52
+
53
+
54
+ def inflate_rect_by_padding(self,rect: pygame.Rect|pygame.FRect)->pygame.Rect|pygame.FRect:
55
+ return pygame.FRect(
56
+ rect[0] - self.padding[0],
57
+ rect[1] - self.padding[1],
58
+ rect[2] + self.padding[0] + self.padding[2],
59
+ rect[3] + self.padding[1] + self.padding[3]
35
60
  )
36
- if isinstance(value, list) or isinstance(value, tuple):
61
+
62
+ def set_position(self, x, y) -> Self:
63
+ if x is None: x = self.rect.x
64
+ if y is None: y = self.rect.y
65
+ if (x,y) == self.rect.topleft : return self
66
+ dx,dy = x-self.rect.x,y-self.rect.y
67
+ self.rect.topleft = x,y
68
+ _ = [c.set_position(c.rect.x+dx,c.rect.y+dy) for c in self.children]
69
+ return self
70
+
71
+ def set_center(self,x,y)->Self:
72
+ if x is None : x = self.rect.centerx
73
+ if y is None : y = self.rect.centery
74
+ if (x,y) == self.rect.center : return self
75
+ dx,dy = x-self.rect.centerx,y-self.rect.centery
76
+ self.rect.center = x,y
77
+ _ = [c.set_center(c.rect.centerx+dx,c.rect.centery+dy) for c in self.children]
78
+ return self
79
+
80
+ def set_parent_scene(self,parent_scene:bf.Scene)->Self:
81
+ super().set_parent_scene(parent_scene)
82
+ for c in self.children :
83
+ c.set_parent_scene(parent_scene)
84
+ return self
85
+
86
+ def set_parent(self,parent:"Widget")->Self:
87
+ self.parent = parent
88
+ return self
89
+
90
+ def set_padding(self, value: float | int | tuple | list) -> Self:
91
+ if isinstance(value, Iterable):
37
92
  if len(value) > 4:
38
93
  pass
39
94
  elif any(v < 0 for v in value):
@@ -45,306 +100,222 @@ class Widget(bf.Entity):
45
100
  else:
46
101
  self.padding = (value,) * 4
47
102
 
48
- self.set_size(
49
- old_raw_size[0] + self.padding[0] + self.padding[2],
50
- old_raw_size[1] + self.padding[1] + self.padding[3],
51
- )
103
+ self.dirty_shape = True
52
104
  return self
53
105
 
106
+ def get_padded_rect(self)->pygame.FRect:
107
+ r = self.rect.inflate(-self.padding[0]-self.padding[2],-self.padding[1]-self.padding[3])
108
+ # r.normalize()
109
+ return r
54
110
 
55
- def inflate_rect_by_padding(self, rect: pygame.FRect) -> pygame.FRect:
56
- return pygame.FRect(
57
- rect[0] - self.padding[0],
58
- rect[1] - self.padding[1],
59
- rect[2] + self.padding[0] + self.padding[2],
60
- rect[3] + self.padding[1] + self.padding[3],
61
- )
62
111
  def get_min_required_size(self)->tuple[float,float]:
63
112
  return self.rect.size
64
113
 
65
- def get_content_left(self) -> float:
66
- return self.rect.left + self.padding[0]
67
-
68
- def get_content_top(self) -> float:
69
- return self.rect.top + self.padding[1]
70
-
71
- def get_content_right(self) -> float:
72
- return self.rect.right - self.padding[2]
73
-
74
- def get_content_bottom(self) -> float:
75
- return self.rect.bottom - self.padding[3]
76
-
77
- def get_content_width(self) -> float:
114
+ def get_padded_width(self)->float:
78
115
  return self.rect.w - self.padding[0] - self.padding[2]
79
-
80
- def get_content_height(self) -> float:
116
+
117
+ def get_padded_height(self)->float:
81
118
  return self.rect.h - self.padding[1] - self.padding[3]
119
+
120
+ def get_padded_left(self)->float:
121
+ return self.rect.left + self.padding[0]
82
122
 
83
- def get_content_rect(self) -> pygame.FRect:
84
- return pygame.FRect(
85
- self.rect.left + self.padding[0],
86
- self.rect.top + self.padding[1],
87
- self.get_content_width(),
88
- self.get_content_height(),
89
- )
90
-
91
- def get_content_rect_rel(self) -> pygame.FRect:
92
- return self.get_content_rect().move(-self.rect.left, -self.rect.top)
93
-
94
- def get_content_center(self) -> tuple[float, float]:
95
- return self.get_content_rect().center
96
-
97
- def get_depth(self) -> int:
98
- if self.is_root or self.parent is None:
99
- self.gui_depth = 0
100
- else:
101
- self.gui_depth = self.parent.get_depth() + 1
102
- return self.gui_depth
103
-
104
- def top_at(self, x: float|int, y: float|int) -> "None|Widget":
105
- if self.visible and self.rect.collidepoint(x, y):
106
- if self.children:
107
- for child in reversed(self.children):
108
- r = child.top_at(x, y)
109
- if r is not None:
110
- return r
111
- return self
112
-
113
- return None
123
+ def get_padded_right(self)->float:
124
+ return self.rect.right + self.padding[2]
114
125
 
115
- def get_constraint(self, name: str) -> Constraint | None:
116
- return next((c for c in self.constraints if c.name == name), None)
126
+ def get_padded_center(self)->tuple[float,float]:
127
+ return self.get_padded_rect().center
117
128
 
118
- def add_constraints(self, *constraints: Constraint) -> Self:
119
- for constraint in constraints:
120
- c = self.get_constraint(constraint.name)
121
- if c is not None:
122
- self.constraints.remove(c)
123
- self.constraints.append(constraint)
124
- self.apply_constraints()
125
- return self
129
+ def get_padded_top(self)->float:
130
+ return self.rect.y + self.padding[1]
126
131
 
127
- def has_constraint(self, name: str) -> bool:
128
- return any(c.name == name for c in self.constraints)
132
+ def get_padded_bottom(self)->float:
133
+ return self.rect.bottom - self.padding[3]
129
134
 
130
- def apply_all_constraints(self) -> None:
135
+ def get_debug_outlines(self):
136
+ yield (self.rect, self.debug_color)
137
+ if any(self.padding) : yield (self.get_padded_rect(),"gray")
131
138
  for child in self.children:
132
- child.apply_all_constraints()
133
- self.apply_constraints()
139
+ yield from child.get_debug_outlines()
140
+
141
+ def add_constraints(self,*constraints:"Constraint")->Self:
142
+ self.constraints.extend(constraints)
143
+ seen = set()
144
+ result = []
145
+ for c in self.constraints:
146
+ if c.name not in seen:
147
+ result.append(c)
148
+ seen.add(c.name)
149
+ self.constraints = result
150
+ self.constraints.sort(key=lambda c : c.priority)
151
+ self.dirty_constraints = True
152
+ return self
134
153
 
135
- def apply_constraints(self) -> None:
136
- if not self.parent:
137
- # print(f"Warning : can't apply constraints on {self.to_string()} without parent widget")
138
- return
139
- if not self.constraints:
154
+ def resolve_constraints(self)->None:
155
+ if self.parent is None or not self.constraints:
156
+ self.dirty_constraints = False
140
157
  return
141
- # Sort constraints based on priority
142
- self.constraints.sort(key=lambda c: c.priority)
143
-
144
- for iteration in range(MAX_CONSTRAINT_ITERATION):
145
- unsatisfied = [] # Initialize a flag
146
-
158
+ all_good = False
159
+ constraint_iteration = 0
160
+ while not all_good:
147
161
  for constraint in self.constraints:
148
- if not constraint.evaluate(self.parent, self):
149
- unsatisfied.append(constraint)
150
- constraint.apply(self.parent, self)
151
- if not unsatisfied:
152
- # data = ''.join(f"\n\t->{c.to_string()}" for c in self.constraints)
153
- # print(self.get_depth()*'\t'+f"Following constraints of {self.to_string()} were all satisfied :{data}")
154
- break
155
- # print(f"pass {iteration}/{max_iterations} : unsatisfied = {';'.join(c.to_string() for c in unsatisfied)}")
156
- if iteration == MAX_CONSTRAINT_ITERATION - 1:
157
- unsatisfied_constraints = '\n\t'.join([c.to_string() for c in unsatisfied])
158
- raise ValueError(
159
- f"[WARNING] Following constraints for {self.to_string()} were not satisfied : \n{unsatisfied_constraints}"
160
- )
161
-
162
- # GETTERS
163
- def get_by_tags(self, *tags :str):
164
- for c in self.children:
165
- yield from c.get_by_tags(*tags)
166
- if any(self.has_tags(t) for t in tags):
167
- yield self
168
-
169
- def count_children_recursive(self):
170
- return 1 + sum(child.count_children_recursive() for child in self.children)
171
-
172
-
173
- def propagate_function(self,function):
174
- function(self)
175
- for child in self.children:
176
- child.propagate_function(function)
177
-
178
- def get_root(self) -> Root | None:
179
- if self.parent_scene is not None:
180
- return self.parent_scene.root
181
- return None if self.parent is None else self.parent.get_root()
182
-
183
- def get_size_int(self) -> tuple[int, int]:
184
- return (ceil(self.rect.width), ceil(self.rect.height))
162
+ if not constraint.evaluate(self.parent,self):
163
+ constraint.apply(self.parent,self)
164
+ # print(constraint.name,"Applied")
165
+ constraint_iteration += 1
166
+ if all(c.evaluate(self.parent,self) for c in self.constraints):
167
+ all_good = True
168
+ break
169
+ elif self.__constraint_iteration > MAX_CONSTRAINTS:
170
+ print(self,"CONSTRAINTS ERROR",list(c.name for c in self.constraints if not c.evaluate(self.parent,self)))
171
+ self.dirty_constraints = False
172
+ return
173
+ # print("DONE")
174
+ self.dirty_constraints = False
175
+
176
+ def remove_constraints(self,*names:str)->Self:
177
+ self.constraints = [c for c in self.constraints if c.name not in names]
178
+ return self
185
179
 
186
- def get_center(self) -> tuple[float, float]:
187
- return self.rect.center
180
+ def has_constraint(self,name:str)->bool:
181
+ return any(c.name == name for c in self.constraints )
188
182
 
189
- def get_bounding_box(self):
190
- yield (self.rect, self.debug_color)
191
- yield (self.get_content_rect(), "yellow")
183
+ def visit(self,func:Callable,top_down:bool = True)->None:
184
+ if top_down : func(self)
192
185
  for child in self.children:
193
- yield from child.get_bounding_box()
186
+ child.visit(func,top_down)
187
+ if not top_down: func(self)
194
188
 
195
- def set_autoresize(self, value: bool) -> Self:
196
- self.autoresize = value
197
- self.build()
198
- return self
189
+ def get_root(self)->"Root":
190
+ if self.is_root:
191
+ return self
192
+ return self.parent.get_root()
199
193
 
200
- def set_parent(self, parent: Self | None) -> Self:
194
+ def top_at(self, x: float | int, y: float | int) -> "None|Widget":
195
+ if self.children:
196
+ for child in reversed(self.children):
197
+ if child.visible:
198
+ r = child.top_at(x, y)
199
+ if r is not None:
200
+ return r
201
+ return self if self.visible and self.rect.collidepoint(x, y) else None
202
+
203
+ def add(self,*children:"Widget")->Self:
204
+ self.children.extend(children)
205
+ i = len(self.children)
206
+ for child in children:
207
+ child.set_render_order(i).set_parent(self).set_parent_scene(self.parent_scene)
208
+ i += 1
201
209
  if self.parent:
202
- self.parent.remove_child(self)
203
- self.parent = parent
204
- self.apply_all_constraints()
210
+ self.parent.do_sort_children = True
205
211
  return self
206
212
 
207
- # SETTERS
208
-
209
- def set_root(self) -> Self:
210
- self.is_root = True
211
- return self
212
-
213
- def set_parent_scene(self, scene) -> Self:
214
- super().set_parent_scene(scene)
215
- for child in self.children:
216
- child.set_parent_scene(scene)
217
- return self
218
- def set_x(self, x: float) -> Self:
219
- if x == self.rect.x : return self
220
- delta = x - self.rect.x
221
- self.rect.x = x
222
- for child in self.children:
223
- child.set_x(child.rect.x + delta)
224
- self.apply_constraints()
225
- return self
213
+ def remove(self,*children:"Widget")->Self:
214
+ for child in self.children[:]:
215
+ if child in children:
216
+ child.set_parent(None).set_parent_scene(None)
217
+ self.children.remove(child)
218
+ if self.parent:
219
+ self.parent.do_sort_children = True
226
220
 
227
- def set_y(self, y: float) -> Self:
228
- if y == self.rect.y : return self
229
- delta = y - self.rect.y
230
- self.rect.y = y
231
- for child in self.children:
232
- child.set_y(child.rect.y + delta)
233
- self.apply_constraints()
234
- return self
221
+ def set_size_if_autoresize(self,size:tuple[float,float])->Self:
222
+ size = list(size)
223
+ size[0] = size[0] if self.autoresize_w else None
224
+ size[1] = size[1] if self.autoresize_h else None
235
225
 
236
- def set_position(self, x: float, y: float) -> Self:
237
- if self.rect.topleft == (x,y) : return self
238
- delta_x = x - self.rect.x
239
- delta_y = y - self.rect.y
240
- self.rect.topleft = x, y
241
- for child in self.children:
242
- child.set_position(child.rect.x + delta_x, child.rect.y + delta_y)
243
- self.apply_constraints()
244
- return self
226
+ self.set_size(size)
245
227
 
246
- def set_center(self, x: float, y: float) -> Self:
247
- if self.rect.center == (x,y) : return self
248
- delta_x = x - self.rect.centerx
249
- delta_y = y - self.rect.centery
250
- self.rect.center = x, y
251
- for child in self.children:
252
- child.set_position(child.rect.x + delta_x, child.rect.y + delta_y)
253
- self.apply_constraints()
254
228
  return self
255
229
 
256
- def set_size(self, width: float, height: float) -> Self:
257
- if width is None : width = self.rect.w
258
- if height is None : height = self.rect.h
259
- if self.rect.size == (width,height) : return self
260
- self.rect.size = (width, height)
261
- self.build()
262
- self.apply_constraints()
263
- self.children_modified()
230
+ def set_size(self,size:tuple)->Self:
231
+ size = list(size)
232
+ if size[0] is None : size[0] = self.rect.w
233
+ if size[1] is None : size[1] = self.rect.h
234
+ if size == self.rect.size : return self
235
+ self.rect.size = size
236
+ self.dirty_shape = True
264
237
  return self
238
+
265
239
 
266
- # Other Methods
267
-
268
- def print_tree(self, ident: int = 0) -> None:
269
- print("\t" * ident + self.to_string() + (":" if self.children else ""))
240
+ def process_event(self, event: pygame.Event) -> bool:
241
+ # First propagate to children
270
242
  for child in self.children:
271
- child.print_tree(ident + 1)
272
-
273
- def to_string(self) -> str:
274
- return f"{self.to_string_id()}@{*self.rect.topleft,* self.rect.size}"
275
-
276
- def to_string_id(self) -> str:
277
- return "Widget"
243
+ child.process_event(event)
244
+ # return True if the method is blocking (no propagation to next children of the scene)
245
+ super().process_event(event)
278
246
 
279
- def do_when_removed(self) -> None:
280
- if self.parent_scene and self.parent == self.parent_scene.root:
281
- self.set_parent(None)
282
-
283
- # Methods on children
284
-
285
- def add_child(self, *child: "Widget") -> None:
286
- for c in child:
287
- self.children.append(c)
288
- c.set_parent(self)
289
- c.set_parent_scene(self.parent_scene)
290
- c.do_when_added()
291
- self._sort_children()
292
- self.apply_all_constraints()
293
- self.children_modified()
294
-
295
- def remove_child(self, child: "Widget") -> None:
296
- try :
297
- self.children.remove(child)
298
- except ValueError:
299
- return
300
- child.set_parent(None)
301
- child.do_when_removed()
302
- child.set_parent_scene(None)
303
- self._sort_children()
304
- self.apply_all_constraints()
305
- self.children_modified()
306
247
 
307
- def _sort_children(self):
308
- self.children.sort(key=lambda child : child.render_order)
248
+ def update(self,dt)->None:
249
+ if self.do_sort_children:
250
+ self.children.sort(key = lambda c : c.render_order)
251
+ self.do_sort_children = False
252
+ _ = [c.update(dt) for c in self.children]
253
+ super().update(dt)
309
254
 
310
255
 
311
- # if return True -> don't propagate to siblings or parents
312
- def process_event(self, event: pygame.Event) -> bool:
313
- # First propagate to children
314
- for child in self.children:
315
- if child.process_event(event):
316
- return True
317
- # return True if the method is blocking (no propagation to next children of the scene)
318
- return super().process_event(event)
256
+ def build(self)->None:
257
+ new_size =tuple(map(int,self.rect.size))
258
+ if self.surface.get_size() != new_size:
259
+ new_size = [max(0,i) for i in new_size]
260
+ self.surface = pygame.Surface(new_size,self.surface_flags)
261
+ if self.convert_alpha : self.surface = self.surface.convert_alpha()
319
262
 
320
- def update(self, dt: float):
321
- for child in self.children:
322
- child.update(dt)
323
- self.do_update(dt)
263
+ def paint(self)->None:
264
+ self.surface.fill((0,0,0,0))
265
+ return self
324
266
 
325
- def draw(self, camera: bf.Camera) -> int:
326
- return super().draw(camera) + sum(
327
- [child.draw(camera) for child in self.children]
328
- )
267
+ def visit_up(self,func)->None:
268
+ if func(self) : return
269
+ if self.parent:
270
+ self.parent.visit_up(func)
271
+
272
+ def selective_up(self,widget:"Widget"):
273
+ # if returns True then stop climbing widget tree
274
+ if widget.parent and widget.parent.dirty_constraints:
275
+ return False
276
+ widget.visit(self.selective_down)
277
+ return True
278
+
279
+ def selective_down(self,widget: "Widget"):
280
+ if widget.constraints :
281
+ widget.resolve_constraints()
282
+ else:
283
+ widget.dirty_constraints = False
284
+ if widget.dirty_shape:
285
+ widget.build()
286
+ widget.dirty_shape = False
287
+ self.dirty_surface = True
288
+
289
+
290
+ def draw(self, camera: bf.Camera) -> None:
291
+ if self.dirty_shape:
292
+ self.dirty_constraints = True
293
+ self.dirty_surface = True
294
+ self.build()
295
+ self.dirty_shape = False
296
+ for child in self.children :
297
+ child.dirty_constraints = True
298
+
299
+ if self.dirty_constraints:
300
+ if self.parent and self.parent.dirty_constraints:
301
+ self.parent.visit_up(self.selective_up)
302
+ else:
303
+ self.visit(lambda c : c.resolve_constraints())
329
304
 
330
- def build(self) -> None:
331
- """
332
- This function is called each time the widget's surface has to be updated
333
- It usually has to be overriden if inherited to suit the needs of the new class
334
- """
335
- if not self.surface:
336
- return
337
- if self.surface.get_size() != self.get_size_int():
338
- self.surface = pygame.Surface(self.get_size_int())
305
+ if self.dirty_surface :
306
+ self.paint()
307
+ self.dirty_surface = False
339
308
 
340
309
 
341
- def build_all(self) -> None:
342
- self.build()
343
- for child in self.children:
344
- child.build_all()
345
310
 
346
- def children_modified(self) -> None:
347
- if self.parent and not self.is_root:
348
- self.parent.children_modified()
311
+ super().draw(camera)
312
+ if self.clip_children:
349
313
 
314
+ new_clip = camera.world_to_screen(self.get_padded_rect())
315
+ old_clip = camera.surface.get_clip()
316
+ new_clip = new_clip.clip(old_clip)
317
+ camera.surface.set_clip(new_clip)
350
318
 
319
+ _ = [child.draw(camera) for child in sorted(self.children, key=lambda c: c.render_order)]
320
+ if self.clip_children:
321
+ camera.surface.set_clip(old_clip)