batframework 1.0.8a13__py3-none-any.whl → 1.0.9a1__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 (42) hide show
  1. batFramework/__init__.py +11 -15
  2. batFramework/animatedSprite.py +1 -1
  3. batFramework/camera.py +1 -1
  4. batFramework/character.py +1 -1
  5. batFramework/constants.py +10 -0
  6. batFramework/cutscene.py +10 -10
  7. batFramework/drawable.py +75 -0
  8. batFramework/dynamicEntity.py +2 -4
  9. batFramework/entity.py +93 -56
  10. batFramework/enums.py +1 -0
  11. batFramework/fontManager.py +3 -3
  12. batFramework/gui/__init__.py +2 -2
  13. batFramework/gui/{dialogueBox.py → animatedLabel.py} +18 -36
  14. batFramework/gui/button.py +30 -0
  15. batFramework/gui/clickableWidget.py +6 -1
  16. batFramework/gui/constraints/constraints.py +90 -1
  17. batFramework/gui/container.py +84 -93
  18. batFramework/gui/debugger.py +1 -1
  19. batFramework/gui/indicator.py +3 -2
  20. batFramework/gui/interactiveWidget.py +43 -24
  21. batFramework/gui/label.py +43 -49
  22. batFramework/gui/layout.py +378 -42
  23. batFramework/gui/root.py +2 -9
  24. batFramework/gui/shape.py +2 -0
  25. batFramework/gui/textInput.py +115 -78
  26. batFramework/gui/toggle.py +1 -4
  27. batFramework/gui/widget.py +50 -38
  28. batFramework/manager.py +65 -53
  29. batFramework/particle.py +1 -1
  30. batFramework/scene.py +1 -34
  31. batFramework/sceneManager.py +6 -34
  32. batFramework/scrollingSprite.py +1 -1
  33. batFramework/sprite.py +2 -2
  34. batFramework/{time.py → timeManager.py} +0 -2
  35. batFramework/utils.py +118 -19
  36. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/METADATA +1 -1
  37. batframework-1.0.9a1.dist-info/RECORD +63 -0
  38. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/WHEEL +1 -1
  39. batFramework/object.py +0 -123
  40. batframework-1.0.8a13.dist-info/RECORD +0 -63
  41. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/LICENCE +0 -0
  42. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/top_level.txt +0 -0
batFramework/gui/root.py CHANGED
@@ -70,10 +70,10 @@ class Root(InteractiveWidget):
70
70
  return res
71
71
 
72
72
  def focus_next_tab(self, widget):
73
- return True
73
+ return
74
74
 
75
75
  def focus_prev_tab(self, widget):
76
- return True
76
+ return
77
77
 
78
78
  def get_by_uid(self, uid: int) -> "Widget":
79
79
  def helper(w: "Widget", uid: int) -> "Widget":
@@ -91,12 +91,9 @@ class Root(InteractiveWidget):
91
91
  if not force:
92
92
  return self
93
93
  self.rect.size = size
94
- # self.build(resolve_constraints=True)
95
94
  return self
96
95
 
97
96
  def do_handle_event(self, event):
98
- if not self.parent_scene.get_sharedVar("player_has_control"):
99
- return
100
97
  if self.focused:
101
98
  if event.type == pygame.KEYDOWN:
102
99
  if self.focused.on_key_down(event.key):
@@ -130,8 +127,6 @@ class Root(InteractiveWidget):
130
127
 
131
128
  def update(self, dt: float) -> None:
132
129
  super().update(dt)
133
- if not self.parent_scene.get_sharedVar("player_has_control",True):
134
- return
135
130
  old = self.hovered
136
131
  transposed = self.drawing_camera.screen_to_world(pygame.mouse.get_pos())
137
132
  self.hovered = self.top_at(*transposed) if self.top_at(*transposed) else None
@@ -145,8 +140,6 @@ class Root(InteractiveWidget):
145
140
 
146
141
  def draw(self, camera: bf.Camera) -> None:
147
142
  super().draw(camera)
148
- if not self.parent_scene.get_sharedVar("player_has_control"):
149
- return
150
143
  if (
151
144
  self.parent_scene
152
145
  and self.parent_scene.active
batFramework/gui/shape.py CHANGED
@@ -47,6 +47,8 @@ class Shape(Widget):
47
47
  self.relief = relief
48
48
  return self
49
49
 
50
+
51
+
50
52
  def set_texture(
51
53
  self, surface: pygame.SurfaceType, subsize: tuple[int, int] | None = None
52
54
  ) -> Self:
@@ -58,7 +58,7 @@ class TextInput(Label, InteractiveWidget):
58
58
  def __init__(self) -> None:
59
59
  self.cursor_position = (0, 0)
60
60
  self.old_key_repeat = (0, 0)
61
- self.cursor_timer = bf.Timer(0.3, self._cursor_toggle, loop=True).start()
61
+ self.cursor_timer = bf.Timer(0.2, self._cursor_toggle, loop=True).start()
62
62
  self.cursor_timer.pause()
63
63
  self.show_cursor = False
64
64
  self.on_modify: Callable[[str], str] = None
@@ -110,6 +110,14 @@ class TextInput(Label, InteractiveWidget):
110
110
  return None
111
111
  return lines[line]
112
112
 
113
+ def get_debug_outlines(self):
114
+ if self.visible:
115
+ offset = self._get_outline_offset() if self.show_text_outline else (0,0)
116
+ yield (self.text_rect.move(self.rect.x - offset[0] - self.scroll.x,self.rect.y - offset[1] - self.scroll.y), "purple")
117
+ yield from super().get_debug_outlines()
118
+ yield (self.get_cursor_rect().move(-self.scroll+self.rect.topleft),"green")
119
+
120
+
113
121
  def set_cursor_position(self, position: tuple[int, int]) -> Self:
114
122
  x, y = position
115
123
 
@@ -117,12 +125,30 @@ class TextInput(Label, InteractiveWidget):
117
125
  y = max(0, min(y, len(lines) - 1))
118
126
  line_length = len(lines[y])
119
127
  x = max(0, min(x, line_length))
120
-
121
- self.cursor_position = (x, y)
122
128
  self.show_cursor = True
123
- self.dirty_shape = True
129
+ self.cursor_position = (x,y)
130
+ self.dirty_surface = True
131
+ offset = self._get_outline_offset() if self.show_text_outline else (0,0)
132
+ padded = self.get_padded_rect().move(-self.rect.x + offset[0], -self.rect.y + offset[1])
133
+ self.align_text(self.text_rect,padded,self.alignment)
124
134
  return self
125
135
 
136
+ def get_cursor_rect(self)->pygame.FRect:
137
+ if not self.font_object:
138
+ return pygame.FRect(0,0,0,0)
139
+
140
+ lines = self.text.split('\n')
141
+ line_x, line_y = self.cursor_position
142
+
143
+ height = self.font_object.get_point_size()
144
+
145
+ cursor_y = self.get_padded_rect().__getattribute__(self.alignment.value)[1] - self.rect.top
146
+ cursor_y += line_y * height
147
+ cursor_x = self.text_rect.x
148
+ cursor_x += self.font_object.size(lines[line_y][:line_x])[0] if line_x > 0 else 0
149
+ cursor_rect = pygame.Rect(cursor_x, cursor_y, 1, height)
150
+ return cursor_rect
151
+
126
152
  def cursor_to_absolute(self, position: tuple[int, int]) -> int:
127
153
  x, y = position
128
154
 
@@ -146,68 +172,72 @@ class TextInput(Label, InteractiveWidget):
146
172
  return (len(lines[-1]), len(lines) - 1)
147
173
 
148
174
  def do_handle_event(self, event):
149
- if not self.is_focused:
150
- return
151
-
152
- if event.type not in [pygame.TEXTINPUT, pygame.KEYDOWN]:
175
+ if not self.is_focused or event.type not in [pygame.TEXTINPUT, pygame.KEYDOWN]:
153
176
  return
154
177
 
155
178
  text = self.get_text()
156
- current = self.cursor_to_absolute(self.cursor_position)
179
+ current_pos = self.cursor_to_absolute(self.cursor_position)
180
+ pressed = pygame.key.get_pressed()
181
+
157
182
  if event.type == pygame.TEXTINPUT:
158
- self.set_text(text[:current] + event.text + text[current:])
159
- self.set_cursor_position(self.absolute_to_cursor(current + len(event.text)))
183
+ # Insert text at the current cursor position
184
+ self.set_text(f"{text[:current_pos]}{event.text}{text[current_pos:]}")
185
+ self.set_cursor_position(self.absolute_to_cursor(current_pos + len(event.text)))
186
+
160
187
  elif event.type == pygame.KEYDOWN:
161
- pressed = pygame.key.get_pressed()
162
- if event.key == pygame.K_ESCAPE:
163
- self.lose_focus()
164
- elif event.key == pygame.K_BACKSPACE:
165
- if current > 0:
166
- self.set_text(text[:current - 1] + text[current:])
167
- self.set_cursor_position(self.absolute_to_cursor(current - 1))
168
- elif event.key == pygame.K_DELETE:
169
- if current < len(text):
170
- self.set_text(text[:current] + text[current + 1:])
171
- elif event.key == pygame.K_RIGHT:
172
- if self.cursor_to_absolute(self.cursor_position)>=len(self.text):
188
+ match event.key:
189
+ case pygame.K_ESCAPE:
190
+ self.lose_focus()
191
+
192
+ case pygame.K_BACKSPACE if current_pos > 0:
193
+ # Remove the character before the cursor
194
+ self.set_text(f"{text[:current_pos - 1]}{text[current_pos:]}")
195
+ self.set_cursor_position(self.absolute_to_cursor(current_pos - 1))
196
+
197
+ case pygame.K_DELETE if current_pos < len(text):
198
+ # Remove the character at the cursor
199
+ self.set_text(f"{text[:current_pos]}{text[current_pos + 1:]}")
200
+
201
+ case pygame.K_RIGHT:
202
+ if current_pos < len(text):
203
+ self.handle_cursor_movement(pressed, current_pos, direction="right")
204
+
205
+ case pygame.K_LEFT:
206
+ if current_pos > 0:
207
+ self.handle_cursor_movement(pressed, current_pos, direction="left")
208
+
209
+ case pygame.K_UP:
210
+ # Move cursor up one line
211
+ self.set_cursor_position((self.cursor_position[0], self.cursor_position[1] - 1))
212
+
213
+ case pygame.K_DOWN:
214
+ # Move cursor down one line
215
+ self.set_cursor_position((self.cursor_position[0], self.cursor_position[1] + 1))
216
+
217
+ case pygame.K_RETURN:
218
+ # Insert a newline at the current cursor position
219
+ self.set_text(f"{text[:current_pos]}\n{text[current_pos:]}")
220
+ self.set_cursor_position(self.absolute_to_cursor(current_pos + 1))
221
+ case _ :
173
222
  return
174
- if self.cursor_position[0] == len(self.get_line(self.cursor_position[1])):
175
- self.set_cursor_position((0, self.cursor_position[1] + 1))
176
- else:
177
- if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
178
- index = find_next_word(self.text,current)
179
- if index ==-1 : index = current+1
180
- self.set_cursor_position(self.absolute_to_cursor(index))
181
- else:
182
- self.set_cursor_position(self.absolute_to_cursor(current + 1))
183
- elif event.key == pygame.K_LEFT:
184
-
185
-
186
- if self.cursor_position[0] == 0 and self.cursor_position[1] > 0:
187
- self.set_cursor_position((len(self.get_line(self.cursor_position[1] - 1)), self.cursor_position[1] - 1))
188
- else:
189
- if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
190
- index = find_prev_word(self.text,current-1)
191
- if index ==-1 : index = current-1
192
- self.set_cursor_position(self.absolute_to_cursor(index))
193
- else:
194
- self.set_cursor_position(self.absolute_to_cursor(current - 1))
195
- elif event.key == pygame.K_UP:
196
- x, y = self.cursor_position
197
- self.set_cursor_position((x, y - 1))
198
- elif event.key == pygame.K_DOWN:
199
- x, y = self.cursor_position
200
- self.set_cursor_position((x, y + 1))
201
- elif event.key == pygame.K_RETURN:
202
- self.set_text(text[:current] + '\n' + text[current:])
203
- self.set_cursor_position(self.absolute_to_cursor(current + 1))
204
- else:
205
- return
206
- else:
207
- return
208
223
 
209
224
  event.consumed = True
210
225
 
226
+ def handle_cursor_movement(self, pressed, current_pos, direction):
227
+ if direction == "right":
228
+ if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
229
+ next_word_pos = find_next_word(self.text, current_pos)
230
+ self.set_cursor_position(self.absolute_to_cursor(next_word_pos if next_word_pos != -1 else current_pos + 1))
231
+ else:
232
+ self.set_cursor_position(self.absolute_to_cursor(current_pos + 1))
233
+ elif direction == "left":
234
+ if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
235
+ prev_word_pos = find_prev_word(self.text, current_pos - 1)
236
+ self.set_cursor_position(self.absolute_to_cursor(prev_word_pos if prev_word_pos != -1 else current_pos - 1))
237
+ else:
238
+ self.set_cursor_position(self.absolute_to_cursor(current_pos - 1))
239
+
240
+
211
241
  def set_text(self, text: str) -> Self:
212
242
  if self.on_modify:
213
243
  text = self.on_modify(text)
@@ -216,32 +246,39 @@ class TextInput(Label, InteractiveWidget):
216
246
  def _paint_cursor(self) -> None:
217
247
  if not self.font_object or not self.show_cursor:
218
248
  return
249
+ cursor_rect = self.get_cursor_rect()
250
+ cursor_rect.move_ip(-self.scroll)
219
251
 
220
- lines = self.text.split('\n')
221
- line_x, line_y = self.cursor_position
222
-
223
- cursor_y = self.padding[1]
224
- cursor_y += line_y * self.font_object.get_linesize()
225
- cursor_x = self.padding[0]
226
- cursor_x += self.font_object.size(lines[line_y][:line_x])[0] if line_x > 0 else 0
252
+
253
+ pygame.draw.rect(self.surface, bf.color.CLOUD, cursor_rect.inflate(2,2))
227
254
 
228
- cursor_rect = pygame.Rect(cursor_x, cursor_y, 2, self.font_object.get_height())
229
255
  pygame.draw.rect(self.surface, self.text_color, cursor_rect)
230
256
 
231
257
  def paint(self) -> None:
232
258
  super().paint()
233
259
  self._paint_cursor()
234
260
 
235
- # def set_alignment(self, alignment: bf.alignment) -> Self:
236
- # return self
237
-
238
- # def align_text(
239
- # self, text_rect: pygame.FRect, area: pygame.FRect, alignment: bf.alignment
240
- # ):
241
- # if alignment == bf.alignment.LEFT:
242
- # alignment = bf.alignment.MIDLEFT
243
- # elif alignment == bf.alignment.MIDRIGHT:
244
- # alignment = bf.alignment.MIDRIGHT
245
- # pos = area.__getattribute__(alignment.value)
246
- # text_rect.__setattr__(alignment.value, pos)
247
- # return
261
+ def align_text(
262
+ self, text_rect: pygame.FRect, area: pygame.FRect, alignment: bf.alignment
263
+ ):
264
+ cursor_rect = self.get_cursor_rect()
265
+
266
+ if alignment == bf.alignment.LEFT:
267
+ alignment = bf.alignment.MIDLEFT
268
+ elif alignment == bf.alignment.MIDRIGHT:
269
+ alignment = bf.alignment.MIDRIGHT
270
+ pos = area.__getattribute__(alignment.value)
271
+ text_rect.__setattr__(alignment.value, pos)
272
+
273
+
274
+ if cursor_rect.right > area.right+self.scroll.x:
275
+ self.scroll.x=cursor_rect.right - area.right
276
+ elif cursor_rect.x < self.scroll.x+area.left:
277
+ self.scroll.x= cursor_rect.left - area.left
278
+ self.scroll.x = max(self.scroll.x,0)
279
+
280
+ if cursor_rect.bottom > area.bottom + self.scroll.y:
281
+ self.scroll.y = cursor_rect.bottom - area.bottom
282
+ elif cursor_rect.y < self.scroll.y + area.top:
283
+ self.scroll.y = cursor_rect.top - area.top
284
+ self.scroll.y = max(self.scroll.y, 0)
@@ -14,7 +14,6 @@ class Toggle(Button):
14
14
  super().__init__(text, callback)
15
15
  self.add(self.indicator)
16
16
  self.set_clip_children(False)
17
- # self.set_gap(int(max(4, self.get_padded_width() / 3)))
18
17
 
19
18
  def set_visible(self, value: bool) -> Self:
20
19
  self.indicator.set_visible(value)
@@ -77,7 +76,6 @@ class Toggle(Button):
77
76
  0, 0, self.text_rect.w + gap + self.indicator.rect.w, self.text_rect.h
78
77
  )
79
78
 
80
-
81
79
  if self.autoresize_h or self.autoresize_w:
82
80
  target_rect = self.inflate_rect_by_padding(joined_rect)
83
81
  if not self.autoresize_w:
@@ -86,8 +84,7 @@ class Toggle(Button):
86
84
  target_rect.h = self.rect.h
87
85
  if self.rect.size != target_rect.size:
88
86
  self.set_size(target_rect.size)
89
- self.build()
90
- return
87
+ self.apply_updates()
91
88
 
92
89
  # ------------------------------------ size is ok
93
90
 
@@ -11,14 +11,12 @@ MAX_CONSTRAINTS = 10
11
11
 
12
12
  class WidgetMeta(type):
13
13
  def __call__(cls, *args, **kwargs):
14
- """Called when you call MyNewClass()"""
15
14
  obj = type.__call__(cls, *args, **kwargs)
16
- # obj.new_init()
17
15
  bf.StyleManager().register_widget(obj)
18
16
  return obj
19
17
 
20
18
 
21
- class Widget(bf.Entity, metaclass=WidgetMeta):
19
+ class Widget(bf.Drawable, metaclass=WidgetMeta):
22
20
  def __init__(self, *args, **kwargs) -> None:
23
21
  super().__init__(*args, **kwargs)
24
22
  self.children: list["Widget"] = []
@@ -28,10 +26,9 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
28
26
  self.clip_children: bool = True
29
27
  self.padding = (0, 0, 0, 0)
30
28
  self.dirty_surface: bool = True # if true will call paint before drawing
31
- self.dirty_shape: bool = (
32
- True # if true will call (build+paint) before drawing
33
- )
34
- self.dirty_constraints: bool = False
29
+ self.dirty_shape: bool = True # if true will call (build+paint) before drawing
30
+ self.dirty_constraints: bool = False # if true will call resolve_constraints
31
+
35
32
  self.is_root: bool = False
36
33
  self.autoresize_w, self.autoresize_h = True, True
37
34
  self.__constraint_iteration = 0
@@ -221,7 +218,7 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
221
218
  if capture != self.__constraints_capture:
222
219
  self.__constraints_capture = capture
223
220
  self.__constraint_to_ignore = []
224
-
221
+
225
222
  constraints = self.constraints.copy()
226
223
  # If all are resolved early exit
227
224
  if all(c.evaluate(self.parent,self) for c in constraints if c not in self.__constraint_to_ignore):
@@ -240,6 +237,7 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
240
237
  for c in constraints:
241
238
  if c in self.__constraints_to_ignore:continue
242
239
  if not c.evaluate(self.parent,self) :
240
+ # print(c," is applied")
243
241
  c.apply(self.parent,self)
244
242
  # second pass where we check conflicts
245
243
  for c in constraints:
@@ -256,9 +254,10 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
256
254
 
257
255
  if self.__constraints_to_ignore:
258
256
  print("Constraints ignored : ",[str(c) for c in self.__constraints_to_ignore])
259
-
260
-
257
+
261
258
  self.dirty_constraints = False
259
+ # print(self,self.uid,"resolve constraints : Success")
260
+
262
261
 
263
262
  def has_constraint(self, name: str) -> bool:
264
263
  return any(c.name == name for c in self.constraints)
@@ -313,17 +312,16 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
313
312
  size[0] = self.rect.w
314
313
  if size[1] is None:
315
314
  size[1] = self.rect.h
316
- if size == self.rect.size:
315
+ if (int(size[0]),int(size[1])) == (int(self.rect.w),int(self.rect.h)):
317
316
  return self
318
317
  self.rect.size = size
319
318
  self.dirty_shape = True
320
319
  return self
321
320
 
322
- def process_event(self, event: pygame.Event) -> bool:
321
+ def process_event(self, event: pygame.Event) -> None:
323
322
  # First propagate to children
324
323
  for child in self.children:
325
324
  child.process_event(event)
326
- # return True if the method is blocking (no propagation to next children of the scene)
327
325
  super().process_event(event)
328
326
 
329
327
  def update(self, dt) -> None:
@@ -351,7 +349,7 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
351
349
  if top_down:
352
350
  func(self, *args, **kwargs)
353
351
  for child in self.children:
354
- child.visit(func, top_down)
352
+ child.visit(func, top_down,*args,**kwargs)
355
353
  if not top_down:
356
354
  func(self, *args, **kwargs)
357
355
 
@@ -361,55 +359,69 @@ class Widget(bf.Entity, metaclass=WidgetMeta):
361
359
  if self.parent:
362
360
  self.parent.visit_up(func, *args, **kwargs)
363
361
 
364
- def selective_up(self, widget: "Widget"):
365
- # if returns True then stop climbing widget tree
366
- if widget.parent and widget.parent.dirty_constraints:
367
- return False
368
- widget.visit(self.selective_down)
369
- return True
362
+ """
363
+ 1 :
364
+ bottom up -> only build children
370
365
 
371
- def selective_down(self, widget: "Widget"):
372
- if widget.constraints:
373
- widget.resolve_constraints()
374
- else:
375
- widget.dirty_constraints = False
366
+ """
367
+ def update_children_size(self, widget: "Widget"):
368
+ # print(widget,widget.uid,"constraints resolve in update size func")
369
+
370
+ widget.resolve_constraints()
376
371
  if widget.dirty_shape:
372
+ # print(widget,widget.uid,"build in update size func")
377
373
  widget.build()
378
374
  widget.dirty_shape = False
379
- self.dirty_surface = True
375
+ widget.dirty_surface = True
376
+
377
+ def find_highest_dirty_constraints_widget(self) -> "Widget":
378
+ w = self
379
+ tmp = w
380
+ while not tmp.is_root:
381
+ if tmp.dirty_constraints or tmp.dirty_shape:
382
+ w = tmp
383
+ if not tmp.parent:
384
+ break
385
+ tmp = tmp.parent
386
+ return w
380
387
 
381
- def draw(self, camera: bf.Camera) -> None:
388
+
389
+ def apply_updates(self) -> None:
390
+ # Step 1: Build shape if needed
382
391
  if self.dirty_shape:
383
- self.dirty_constraints = True
384
- self.dirty_surface = True
385
- self.build()
392
+ self.build() # Finalize widget size
386
393
  self.dirty_shape = False
394
+ self.dirty_surface = True
395
+ # Propagate dirty_constraints to children in case size affects their position
387
396
  for child in self.children:
388
397
  child.dirty_constraints = True
389
398
 
399
+ # Step 2: Resolve constraints now that size is finalized
390
400
  if self.dirty_constraints:
391
- if self.parent and self.parent.dirty_constraints:
392
- self.parent.visit_up(self.selective_up)
393
- else:
394
- self.visit(lambda c: c.resolve_constraints())
401
+ self.resolve_constraints() # Finalize positioning based on final size
402
+ self.dirty_constraints = False
395
403
 
404
+ # Step 3: Paint the surface if flagged as dirty
396
405
  if self.dirty_surface:
397
406
  self.paint()
398
407
  self.dirty_surface = False
408
+
399
409
 
410
+
411
+ def draw(self, camera: bf.Camera) -> None:
412
+ self.apply_updates()
413
+ # Draw widget and handle clipping if necessary
400
414
  super().draw(camera)
401
415
 
402
416
  if self.clip_children:
403
-
404
417
  new_clip = camera.world_to_screen(self.get_padded_rect())
405
418
  old_clip = camera.surface.get_clip()
406
419
  new_clip = new_clip.clip(old_clip)
407
420
  camera.surface.set_clip(new_clip)
408
421
 
409
- _ = [
422
+ # Draw each child widget, sorted by render order
423
+ for child in sorted(self.children, key=lambda c: c.render_order):
410
424
  child.draw(camera)
411
- for child in sorted(self.children, key=lambda c: c.render_order)
412
- ]
413
425
 
414
426
  if self.clip_children:
415
427
  camera.surface.set_clip(old_clip)
batFramework/manager.py CHANGED
@@ -1,23 +1,25 @@
1
1
  import batFramework as bf
2
2
  import pygame
3
3
  import asyncio
4
- import random
5
4
 
6
5
  class Manager(bf.SceneManager):
7
6
  def __init__(self, *initial_scene_list) -> None:
8
- # random.seed("random")
9
7
  super().__init__()
10
- self._screen: pygame.Surface | None = bf.const.SCREEN
11
- self._timeManager = bf.TimeManager()
12
- self._cutsceneManager = bf.CutsceneManager()
13
- self._cutsceneManager.set_manager(self)
14
- self._clock: pygame.Clock = pygame.Clock()
15
- self._is_async_running : bool = False
16
- self._running = False
8
+ self.debug_mode: bf.enums.debugMode = bf.debugMode.HIDDEN
9
+ self.screen: pygame.Surface | None = bf.const.SCREEN
10
+ self.timeManager = bf.TimeManager()
11
+ self.cutsceneManager = bf.CutsceneManager()
12
+ self.cutsceneManager.set_manager(self)
13
+ self.clock: pygame.Clock = pygame.Clock()
14
+ self.is_async_running : bool = False
15
+ self.running = False
17
16
  pygame.mouse.set_cursor(bf.const.DEFAULT_CURSOR)
17
+ bf.ResourceManager().set_sharedVar("clock", self.clock)
18
+ bf.ResourceManager().set_sharedVar("debug_mode", self.debug_mode)
19
+
18
20
  self.do_pre_init()
19
- self.init_scenes(*initial_scene_list)
20
- self.set_sharedVar("clock", self._clock)
21
+ if initial_scene_list:
22
+ self.init_scenes(*initial_scene_list)
21
23
  self.do_init()
22
24
 
23
25
  @staticmethod
@@ -39,14 +41,14 @@ class Manager(bf.SceneManager):
39
41
  print("=" * 50)
40
42
 
41
43
  # Print the timers information
42
- print(self._timeManager)
44
+ print(self.timeManager)
43
45
 
44
46
  # End with a visual separator
45
47
  print("=" * 50 + "\n")
46
48
 
47
49
 
48
50
  def get_fps(self) -> float:
49
- return self._clock.get_fps()
51
+ return self.clock.get_fps()
50
52
 
51
53
  def do_init(self) -> None:
52
54
  pass
@@ -55,67 +57,77 @@ class Manager(bf.SceneManager):
55
57
  pass
56
58
 
57
59
  def stop(self) -> None:
58
- self._running = False
60
+ self.running = False
61
+
62
+ def process_event(self, event: pygame.Event):
63
+ event.consumed = False
64
+ keys = pygame.key.get_pressed()
65
+ if (
66
+ bf.const.ALLOW_DEBUG and
67
+ keys[pygame.K_LCTRL]
68
+ and keys[pygame.K_LSHIFT]
69
+ and event.type == pygame.KEYDOWN
70
+ ):
71
+ if event.key == pygame.K_d:
72
+ self.cycle_debug_mode()
73
+ return
74
+ if event.key == pygame.K_p:
75
+ self.print_status()
76
+ return
77
+ super().process_event(event)
78
+ if not event.consumed:
79
+ if event.type == pygame.QUIT:
80
+ self.running = False
81
+ elif event.type == pygame.VIDEORESIZE and not (
82
+ bf.const.FLAGS & pygame.SCALED
83
+ ):
84
+ bf.const.set_resolution((event.w, event.h))
85
+
86
+ def update(self, dt: float) -> None:
87
+ self.timeManager.update(dt)
88
+ self.cutsceneManager.update(dt)
89
+ super().update(dt)
90
+
59
91
 
60
92
  async def run_async(self):
61
- if self._running:
62
- print("Error : Already running")
93
+ if len(self.scenes) == 0:
94
+ raise Exception("Manager can't start without scenes")
95
+ if self.running:
96
+ raise Exception("Error : Already running")
63
97
  return
64
- self._is_async_running = True
65
- self._running = True
98
+ self.is_async_running = True
99
+ self.running = True
66
100
  dt: float = 0
67
- while self._running:
101
+ while self.running:
68
102
  for event in pygame.event.get():
69
- event.consumed = False
70
103
  self.process_event(event)
71
- if not event.consumed:
72
- if event.type == pygame.QUIT:
73
- self._running = False
74
- break
75
- if event.type == pygame.VIDEORESIZE and not (
76
- bf.const.FLAGS & pygame.SCALED
77
- ):
78
- bf.const.set_resolution((event.w, event.h))
79
104
  # update
80
- self._timeManager.update(dt)
81
- self._cutsceneManager.update(dt)
82
105
  self.update(dt)
83
106
  # render
84
- self._screen.fill((0, 0, 0))
85
- self.draw(self._screen)
107
+ self.draw(self.screen)
86
108
  pygame.display.flip()
87
- dt = self._clock.tick(bf.const.FPS) / 1000
88
- # dt = min(dt, 0.02) dirty fix for dt being too high when window not focused for a long time
109
+ dt = self.clock.tick(bf.const.FPS) / 1000
110
+ dt = min(dt, 0.02) # dirty fix for dt being too high when window not focused for a long time
89
111
  await asyncio.sleep(0)
90
112
  pygame.quit()
91
113
 
92
114
 
93
115
  def run(self) -> None:
94
- if self._running:
95
- print("Error : Already running")
116
+ if len(self.scenes) == 0:
117
+ raise Exception("Manager can't start without scenes")
118
+ if self.running:
119
+ raise Exception("Error : Already running")
96
120
  return
97
- self._running = True
121
+ self.running = True
98
122
  dt: float = 0
99
- while self._running:
123
+ while self.running:
100
124
  for event in pygame.event.get():
101
- event.consumed = False
102
125
  self.process_event(event)
103
- if not event.consumed:
104
- if event.type == pygame.QUIT:
105
- self._running = False
106
- break
107
- if event.type == pygame.VIDEORESIZE and not (
108
- bf.const.FLAGS & pygame.SCALED
109
- ):
110
- bf.const.set_resolution((event.w, event.h))
111
126
  # update
112
- self._timeManager.update(dt)
113
- self._cutsceneManager.update(dt)
114
127
  self.update(dt)
115
128
  # render
116
- self._screen.fill((0, 0, 0))
117
- self.draw(self._screen)
129
+ self.draw(self.screen)
118
130
  pygame.display.flip()
119
- dt = self._clock.tick(bf.const.FPS) / 1000
120
- # dt = min(dt, 0.02) dirty fix for dt being too high when window not focused for a long time
131
+ dt = self.clock.tick(bf.const.FPS) / 1000
132
+ dt = min(dt, 0.02) # dirty fix for dt being too high when window not focused for a long time
121
133
  pygame.quit()