batframework 1.0.9a6__py3-none-any.whl → 1.0.9a8__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 (61) hide show
  1. batFramework/__init__.py +20 -11
  2. batFramework/action.py +1 -1
  3. batFramework/animatedSprite.py +47 -116
  4. batFramework/animation.py +30 -5
  5. batFramework/audioManager.py +16 -13
  6. batFramework/baseScene.py +240 -0
  7. batFramework/camera.py +4 -0
  8. batFramework/constants.py +6 -1
  9. batFramework/cutscene.py +221 -21
  10. batFramework/cutsceneManager.py +5 -2
  11. batFramework/drawable.py +7 -5
  12. batFramework/easingController.py +10 -11
  13. batFramework/entity.py +21 -2
  14. batFramework/enums.py +48 -33
  15. batFramework/gui/__init__.py +3 -1
  16. batFramework/gui/animatedLabel.py +10 -2
  17. batFramework/gui/button.py +4 -31
  18. batFramework/gui/clickableWidget.py +42 -30
  19. batFramework/gui/constraints/constraints.py +212 -136
  20. batFramework/gui/container.py +72 -48
  21. batFramework/gui/debugger.py +12 -17
  22. batFramework/gui/draggableWidget.py +8 -11
  23. batFramework/gui/image.py +3 -10
  24. batFramework/gui/indicator.py +73 -1
  25. batFramework/gui/interactiveWidget.py +117 -100
  26. batFramework/gui/label.py +73 -63
  27. batFramework/gui/layout.py +221 -452
  28. batFramework/gui/meter.py +21 -7
  29. batFramework/gui/radioButton.py +0 -1
  30. batFramework/gui/root.py +99 -29
  31. batFramework/gui/selector.py +257 -0
  32. batFramework/gui/shape.py +13 -5
  33. batFramework/gui/slider.py +260 -93
  34. batFramework/gui/textInput.py +45 -21
  35. batFramework/gui/toggle.py +70 -52
  36. batFramework/gui/tooltip.py +30 -0
  37. batFramework/gui/widget.py +203 -125
  38. batFramework/manager.py +7 -8
  39. batFramework/particle.py +4 -1
  40. batFramework/propertyEaser.py +79 -0
  41. batFramework/renderGroup.py +17 -50
  42. batFramework/resourceManager.py +43 -13
  43. batFramework/scene.py +15 -335
  44. batFramework/sceneLayer.py +138 -0
  45. batFramework/sceneManager.py +31 -36
  46. batFramework/scrollingSprite.py +8 -3
  47. batFramework/sprite.py +1 -1
  48. batFramework/templates/__init__.py +1 -2
  49. batFramework/templates/controller.py +97 -0
  50. batFramework/timeManager.py +76 -22
  51. batFramework/transition.py +37 -103
  52. batFramework/utils.py +121 -3
  53. {batframework-1.0.9a6.dist-info → batframework-1.0.9a8.dist-info}/METADATA +24 -3
  54. batframework-1.0.9a8.dist-info/RECORD +66 -0
  55. {batframework-1.0.9a6.dist-info → batframework-1.0.9a8.dist-info}/WHEEL +1 -1
  56. batFramework/character.py +0 -27
  57. batFramework/templates/character.py +0 -43
  58. batFramework/templates/states.py +0 -166
  59. batframework-1.0.9a6.dist-info/RECORD +0 -63
  60. /batframework-1.0.9a6.dist-info/LICENCE → /batframework-1.0.9a8.dist-info/LICENSE +0 -0
  61. {batframework-1.0.9a6.dist-info → batframework-1.0.9a8.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ from .constraints.constraints import *
4
4
  from typing import Self, TYPE_CHECKING
5
5
  from abc import ABC,abstractmethod
6
6
  import pygame
7
+ from .interactiveWidget import InteractiveWidget
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from .container import Container
@@ -15,8 +16,15 @@ class Layout(ABC):
15
16
  self.child_constraints: list[Constraint] = []
16
17
  self.children_rect = pygame.FRect(0, 0, 0, 0)
17
18
 
19
+ def get_free_space(self)->tuple[float,float]:
20
+ """
21
+ return the space available for Growing widgets to use
22
+ """
23
+ return self.parent.get_inner_rect()
24
+
18
25
  def set_child_constraints(self, *constraints) -> Self:
19
26
  self.child_constraints = list(constraints)
27
+ self.update_child_constraints()
20
28
  self.notify_parent()
21
29
  return self
22
30
 
@@ -28,14 +36,35 @@ class Layout(ABC):
28
36
  if self.parent:
29
37
  self.parent.dirty_layout = True
30
38
 
39
+ def update_children_rect(self):
40
+ if self.parent.children:
41
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft,*self.parent.children[0].get_min_required_size())
42
+
43
+ self.children_rect.unionall(
44
+ [pygame.FRect(0,0,*c.get_min_required_size()) for c in self.parent.children[1:]]
45
+ )
46
+ else:
47
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 0, 0)
48
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
49
+
50
+ def update_child_constraints(self):
51
+ if self.parent:
52
+ for child in self.parent.children:
53
+ child.add_constraints(*self.child_constraints)
54
+
31
55
  def arrange(self) -> None:
56
+ """
57
+ updates the position of children in the parent
58
+ """
32
59
  return
33
60
 
34
- def get_raw_size(self) -> tuple[float, float]:
61
+ def get_raw_size(self):
35
62
  """
36
- Returns the supposed size the container should have to encapsulate perfectly all of its widgets
63
+ Returns the size the container should have to encapsulate perfectly all of its widgets
37
64
  """
38
- return self.parent.rect.size if self.parent else (0, 0)
65
+ # print(self,self.parent,len(self.parent.children))
66
+ self.update_children_rect()
67
+ return self.children_rect.size
39
68
 
40
69
  def get_auto_size(self) -> tuple[float, float]:
41
70
  """
@@ -43,342 +72,229 @@ class Layout(ABC):
43
72
  """
44
73
  target_size = list(self.get_raw_size())
45
74
  if not self.parent.autoresize_w:
46
- target_size[0] = self.parent.rect.w
75
+ target_size[0] = self.parent.get_inner_width()
47
76
  if not self.parent.autoresize_h:
48
- target_size[1] = self.parent.rect.h
49
- return target_size
77
+ target_size[1] = self.parent.get_inner_height()
78
+
79
+ return self.parent.expand_rect_with_padding((0,0,*target_size)).size
80
+ # return target_size
81
+
82
+ def scroll_to_widget(self, widget: "Widget"):
83
+ """
84
+ Scrolls parent containers so that the widget becomes visible.
85
+ Handles deeply nested widgets and large widgets gracefully.
86
+ """
87
+ target = widget
88
+ container = self.parent
89
+
90
+ while container and not container.is_root:
91
+ target_rect = target.rect # global
92
+ padded_rect = container.get_inner_rect() # global
93
+
94
+ dx = dy = 0
95
+
96
+ # --- Horizontal ---
97
+ if target_rect.width <= padded_rect.width:
98
+ if target_rect.left < padded_rect.left:
99
+ dx = target_rect.left - padded_rect.left
100
+ elif target_rect.right > padded_rect.right:
101
+ dx = target_rect.right - padded_rect.right
102
+ else:
103
+ # Widget is wider than viewport: align left side
104
+ if target_rect.left < padded_rect.left:
105
+ dx = target_rect.left - padded_rect.left
106
+ elif target_rect.right > padded_rect.right:
107
+ dx = target_rect.right - padded_rect.right
108
+
109
+ # --- Vertical ---
110
+ if target_rect.height <= padded_rect.height:
111
+ if target_rect.top < padded_rect.top:
112
+ dy = target_rect.top - padded_rect.top
113
+ elif target_rect.bottom > padded_rect.bottom:
114
+ dy = target_rect.bottom - padded_rect.bottom
115
+ else:
116
+ # Widget is taller than viewport: align top side
117
+ if target_rect.top < padded_rect.top:
118
+ dy = target_rect.top - padded_rect.top
119
+ elif target_rect.bottom > padded_rect.bottom:
120
+ dy = target_rect.bottom - padded_rect.bottom
121
+
122
+ # Convert global delta into local scroll delta for container
123
+ container.scroll_by((dx, dy))
124
+
125
+ # Now the target for the next iteration is the container itself
126
+ target = container
127
+ container = container.parent
50
128
 
51
- def scroll_to_widget(self, widget: Widget) -> None:
52
- padded = self.parent.get_padded_rect() # le carré intérieur
53
- r = widget.rect # le carré du widget = Button
54
- if padded.contains(r): # le widget ne depasse pas -> OK
55
- return
56
- clamped = r.clamp(padded)
57
- # clamped.move_ip(-self.parent.rect.x,-self.parent.rect.y)
58
- dx,dy = clamped.x - r.x,clamped.y-r.y
59
129
 
60
- self.parent.scroll_by((-dx, -dy)) # on scroll la différence pour afficher le bouton en entier
61
-
62
130
  def handle_event(self, event):
63
131
  pass
64
132
 
133
+ class FreeLayout(Layout):...
134
+
65
135
  class SingleAxisLayout(Layout):
136
+
137
+ def __init__(self, parent = None):
138
+ super().__init__(parent)
139
+
66
140
  def focus_next_child(self) -> None:
67
141
  l = self.parent.get_interactive_children()
68
142
  self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
69
143
  focused = l[self.parent.focused_index]
70
144
  focused.get_focus()
71
- self.scroll_to_widget(focused)
72
145
 
73
146
  def focus_prev_child(self) -> None:
74
147
  l = self.parent.get_interactive_children()
75
148
  self.parent.focused_index = max(self.parent.focused_index - 1, 0)
76
149
  focused = l[self.parent.focused_index]
77
150
  focused.get_focus()
78
- self.scroll_to_widget(focused)
79
151
 
80
152
 
153
+ class DoubleAxisLayout(Layout):
154
+ """Abstract layout class for layouts that arrange widgets in two dimensions."""
155
+
156
+ def focus_up_child(self) -> None:...
157
+ def focus_down_child(self) -> None:...
158
+ def focus_right_child(self) -> None:...
159
+ def focus_left_child(self) -> None:...
160
+
81
161
 
82
162
  class Column(SingleAxisLayout):
83
- def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
163
+ def __init__(self, gap: int = 0):
84
164
  super().__init__()
85
165
  self.gap = gap
86
- self.spacing = spacing
87
-
88
- def set_gap(self, value: float) -> Self:
89
- self.gap = value
90
- self.notify_parent()
91
- return self
92
166
 
93
- def set_spacing(self, spacing: bf.spacing) -> Self:
94
- self.spacing = spacing
95
- self.notify_parent()
96
- return self
167
+ def update_children_rect(self):
168
+ if self.parent.children:
169
+ width = max(child.get_min_required_size()[0] for child in self.parent.children )
170
+ height = sum(child.get_min_required_size()[1] for child in self.parent.children) + self.gap * (len(self.parent.children) - 1)
97
171
 
98
- def get_raw_size(self) -> tuple[float, float]:
99
- len_children = len(self.parent.children)
100
- if not len_children:
101
- return self.parent.rect.size
102
- parent_height = sum(c.get_min_required_size()[1] for c in self.parent.children)
103
- parent_width = max(c.get_min_required_size()[0] for c in self.parent.children)
104
- if self.gap:
105
- parent_height += (len_children - 1) * self.gap
106
- target_rect = self.parent.inflate_rect_by_padding(
107
- (0, 0, parent_width, parent_height)
108
- )
109
- return target_rect.size
172
+ # width = max(child.rect.w for child in self.parent.children )
173
+ # height = sum(child.rect.h for child in self.parent.children) + self.gap * (len(self.parent.children) - 1)
110
174
 
111
- def get_auto_size(self) -> tuple[float, float]:
112
- target_size = list(self.get_raw_size())
113
- if not self.parent.autoresize_w:
114
- target_size[0] = self.parent.rect.w
115
- if not self.parent.autoresize_h:
116
- target_size[1] = self.parent.rect.h
117
- return target_size
118
175
 
119
- def arrange(self) -> None:
120
- if not self.parent or not self.parent.children:
121
- return
122
- if self.child_constraints:
123
- for child in self.parent.children:
124
- child.add_constraints(*self.child_constraints)
125
- self.children_rect = self.parent.get_padded_rect()
126
-
127
- width, height = self.get_auto_size()
128
- if self.parent.autoresize_w and self.parent.rect.w !=width:
129
- self.parent.set_size((width,None))
130
- if self.parent.autoresize_h and self.parent.rect.h !=height:
131
- self.parent.set_size((None,height))
132
-
133
- # if self.parent.dirty_shape:
134
- # print("parent set dirty shape")
135
- # self.parent.dirty_layout = True
136
- # self.parent.apply_updates()
137
- # self.arrange()
138
- # return
139
-
140
- self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
141
- y = self.children_rect.top
142
- for child in self.parent.children:
143
- child.set_position(self.children_rect.x, y)
144
- y += child.get_min_required_size()[1] + self.gap
176
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
177
+ else:
178
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, 10)
179
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
145
180
 
146
181
  def handle_event(self, event):
147
- if not self.parent.children:
182
+ if not self.parent.children or not self.parent.children_has_focus():
148
183
  return
149
184
 
150
185
  if event.type == pygame.KEYDOWN:
151
- if event.key == pygame.K_DOWN:
152
- self.focus_next_child()
153
- elif event.key == pygame.K_UP:
154
- self.focus_prev_child()
155
- else:
156
- return
157
- elif event.type == pygame.MOUSEBUTTONDOWN:
158
- r = self.parent.get_root()
159
- if not r:
160
- return
161
-
162
- if self.parent.rect.collidepoint(
163
- r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
164
- ):
165
- if event.button == 4:
166
- self.parent.scroll_by((0, -10))
167
- elif event.button == 5:
168
- self.parent.scroll_by((0, 10))
169
- else:
170
- return
171
- self.parent.clamp_scroll()
172
- else:
173
- return
174
- else:
175
- return
176
- event.consumed = True
186
+ if event.key in (pygame.K_DOWN, pygame.K_UP):
187
+ self.focus_next_child() if event.key == pygame.K_DOWN else self.focus_prev_child()
188
+ event.consumed = True
189
+ event.consumed = True
190
+
191
+
192
+ def arrange(self) -> None:
193
+ self.update_children_rect()
194
+ y = self.children_rect.y
195
+ for child in self.parent.children:
196
+ child.set_position(self.children_rect.x, y)
197
+ y += child.rect.height + self.gap
177
198
 
178
199
  class Row(SingleAxisLayout):
179
- def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
200
+ def __init__(self, gap: int = 0):
180
201
  super().__init__()
181
202
  self.gap = gap
182
- self.spacing = spacing
183
203
 
184
- def set_gap(self, value: float) -> Self:
185
- self.gap = value
186
- self.notify_parent()
187
- return self
204
+ def update_children_rect(self):
205
+ if self.parent.children:
206
+ height = max(child.get_min_required_size()[1] for child in self.parent.children)
207
+ width = sum(child.get_min_required_size()[0] for child in self.parent.children) + self.gap * (len(self.parent.children) - 1)
208
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
209
+ else:
210
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10,10)
211
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
188
212
 
189
- def set_spacing(self, spacing: bf.spacing) -> Self:
190
- self.spacing = spacing
191
- self.notify_parent()
192
- return self
213
+ def handle_event(self, event):
214
+ if not self.parent.children or not self.parent.children_has_focus():
215
+ return
193
216
 
194
- def get_raw_size(self) -> tuple[float, float]:
195
- len_children = len(self.parent.children)
196
- if not len_children:
197
- return self.parent.rect.size
198
- parent_width = sum(c.get_min_required_size()[0] for c in self.parent.children)
199
- parent_height = max(c.get_min_required_size()[1] for c in self.parent.children)
200
- if self.gap:
201
- parent_width += (len_children - 1) * self.gap
202
- target_rect = self.parent.inflate_rect_by_padding(
203
- (0, 0, parent_width, parent_height)
204
- )
217
+ if event.type == pygame.KEYDOWN:
218
+ if event.key in (pygame.K_RIGHT, pygame.K_LEFT):
219
+ self.focus_next_child() if event.key == pygame.K_RIGHT else self.focus_prev_child()
220
+ event.consumed = True
205
221
 
206
- return target_rect.size
207
-
208
- def get_auto_size(self) -> tuple[float, float]:
209
- target_size = list(self.get_raw_size())
210
- if not self.parent.autoresize_w:
211
- target_size[0] = self.parent.rect.w
212
- if not self.parent.autoresize_h:
213
- target_size[1] = self.parent.rect.h
214
- return target_size
215
222
 
216
223
  def arrange(self) -> None:
217
- if not self.parent or not self.parent.children:
218
- return
219
- if self.child_constraints:
220
- for child in self.parent.children:
221
- child.add_constraints(*self.child_constraints)
222
- self.children_rect = self.parent.get_padded_rect()
223
-
224
- if self.parent.autoresize_w or self.parent.autoresize_h:
225
- width, height = self.get_auto_size()
226
- if self.parent.rect.size != (width, height):
227
- self.parent.set_size((width, height))
228
- self.parent.build()
229
- self.arrange()
230
- return
231
- self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
232
- x = self.children_rect.left
224
+ self.update_children_rect()
225
+ x = self.children_rect.x
233
226
  for child in self.parent.children:
234
- child.set_position(x, self.children_rect.y)
235
- x += child.get_min_required_size()[0] + self.gap
227
+ child.set_position(x,self.children_rect.y)
228
+ x += child.rect.width + self.gap
236
229
 
237
- def handle_event(self, event):
238
- if not self.parent.children:
239
- return
240
230
 
241
- if event.type == pygame.KEYDOWN:
242
- if event.key == pygame.K_RIGHT:
243
- self.focus_next_child()
244
- elif event.key == pygame.K_LEFT:
245
- self.focus_prev_child()
246
- else:
247
- return
248
-
249
- elif event.type == pygame.MOUSEBUTTONDOWN:
250
- r = self.parent.get_root()
251
- if not r:
252
- return
253
- if self.parent.rect.collidepoint(
254
- r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
255
- ):
256
- if event.button == 4:
257
- self.parent.scroll_by((-10, 0))
258
- elif event.button == 5:
259
- self.parent.scroll_by((10, 0))
260
- else:
261
- return
262
- self.parent.clamp_scroll()
263
- else:
264
- return
265
- else:
266
- return
267
231
 
232
+ class RowFill(Row):
268
233
 
269
- event.consumed = True
234
+ def update_children_rect(self):
235
+ parent_width = self.parent.get_inner_width()
236
+ if self.parent.children:
237
+ height = max(child.get_min_required_size()[1] for child in self.parent.children)
238
+ width = parent_width
239
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
240
+ else:
241
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, parent_width,10)
242
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
270
243
 
271
- class RowFill(Row):
272
- def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
273
- super().__init__(gap, spacing)
274
244
 
275
245
  def arrange(self) -> None:
276
- if self.parent.autoresize_w :
277
- super().arrange()
278
- return
279
- if not self.parent or not self.parent.children:
280
- return
281
-
282
- if self.child_constraints:
283
- for child in self.parent.children:
284
- child.add_constraints(*self.child_constraints)
285
- self.children_rect = self.parent.get_padded_rect()
246
+ """
247
+ Arranges children in a row and resizes them to fill the parent's height,
248
+ accounting for the gap between children.
249
+ """
250
+ self.update_children_rect()
251
+ for child in self.parent.children:
252
+ child.set_autoresize_w(False)
253
+ x = self.children_rect.x
254
+ # available_height = self.children_rect.height
286
255
 
287
- if self.parent.autoresize_w or self.parent.autoresize_h:
288
- width, height = self.get_auto_size()
289
- if self.parent.rect.size != (width, height):
290
- self.parent.set_size((width, height))
291
- self.parent.build()
292
- self.arrange()
293
- return
256
+ # Calculate the width available for each child
257
+ total_gap = self.gap * (len(self.parent.children) - 1)
258
+ available_width = max(0, self.children_rect.width - total_gap)
259
+ child_width = available_width / len(self.parent.children) if self.parent.children else 0
294
260
 
295
- self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
296
-
297
- # Calculate the width each child should fill
298
- available_width = self.children_rect.width - (len(self.parent.children) - 1) * self.gap
299
- child_width = available_width / len(self.parent.children)
300
-
301
- x = self.children_rect.left
302
261
  for child in self.parent.children:
303
- child.set_position(x, self.children_rect.y)
304
- child.set_autoresize_w(False)
305
- child.set_size((child_width, None))
262
+ child.set_size((child_width, None)) # Resize child to fill height
263
+ child.set_position(x, self.children_rect.y) # Position child
306
264
  x += child_width + self.gap
307
265
 
308
- def get_raw_size(self) -> tuple[float, float]:
309
- """Calculate total size with children widths filling the available space."""
310
- if self.parent.autoresize_h :
311
- return super().get_raw_size()
312
- len_children = len(self.parent.children)
313
- if not len_children:
314
- return self.parent.rect.size
315
- parent_height = max(c.get_min_required_size()[1] for c in self.parent.children)
316
- target_rect = self.parent.inflate_rect_by_padding((0, 0, self.children_rect.width, parent_height))
317
- return target_rect.size
318
-
319
266
 
320
267
  class ColumnFill(Column):
321
- def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
322
- super().__init__(gap, spacing)
323
-
324
- def arrange(self) -> None:
325
- if self.parent.autoresize_h :
326
- super().arrange()
327
- return
328
- if not self.parent or not self.parent.children:
329
- return
330
- if self.child_constraints:
331
- for child in self.parent.children:
332
- child.add_constraints(*self.child_constraints)
333
- self.children_rect = self.parent.get_padded_rect()
334
-
335
- if self.parent.autoresize_w or self.parent.autoresize_h:
336
- width, height = self.get_auto_size()
337
- if self.parent.rect.size != (width, height):
338
- self.parent.set_size((width, height))
339
- self.parent.build()
340
- self.arrange()
341
- return
342
268
 
269
+ def update_children_rect(self):
270
+ parent_height = self.parent.get_inner_height()
271
+ if self.parent.children:
272
+ width = max(child.get_min_required_size()[0] for child in self.parent.children)
273
+ height = parent_height
274
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
275
+ else:
276
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, parent_height)
343
277
  self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
344
278
 
345
- # Calculate the height each child should fill
346
- available_height = self.children_rect.height - (len(self.parent.children) - 1) * self.gap
347
- child_height = available_height / len(self.parent.children)
348
-
349
- y = self.children_rect.top
279
+ def arrange(self) -> None:
280
+ """
281
+ Arranges children in a column and resizes them to fill the parent's width,
282
+ accounting for the gap between children.
283
+ """
284
+ self.update_children_rect()
350
285
  for child in self.parent.children:
351
- child.set_position(self.children_rect.x, y)
352
286
  child.set_autoresize_h(False)
353
- child.set_size((None, child_height))
354
- y += child_height + self.gap
355
-
356
- def get_raw_size(self) -> tuple[float, float]:
357
- """Calculate total size with children heights filling the available space."""
358
- if self.parent.autoresize_w :
359
- return super().get_raw_size()
360
- len_children = len(self.parent.children)
361
- if not len_children:
362
- return self.parent.rect.size
363
- parent_width = max(c.get_min_required_size()[0] for c in self.parent.children)
364
- target_rect = self.parent.inflate_rect_by_padding((0, 0, parent_width, self.children_rect.height))
365
- return target_rect.size
366
-
367
-
368
-
369
- class DoubleAxisLayout(Layout):
370
- """Abstract layout class for layouts that arrange widgets in two dimensions."""
371
-
372
- @abstractmethod
373
- def arrange(self) -> None:
374
- """Arrange child widgets across both axes, implementation required in subclasses."""
375
- pass
287
+ y = self.children_rect.y
376
288
 
377
- def focus_up_child(self) -> None:...
378
- def focus_down_child(self) -> None:...
379
- def focus_right_child(self) -> None:...
380
- def focus_left_child(self) -> None:...
289
+ # Calculate the height available for each child
290
+ total_gap = self.gap * (len(self.parent.children) - 1)
291
+ available_height = max(0, self.children_rect.height - total_gap)
292
+ child_height = available_height / len(self.parent.children) if self.parent.children else 0
381
293
 
294
+ for child in self.parent.children:
295
+ child.set_size((None, child_height)) # Resize child to fill width
296
+ child.set_position(self.children_rect.x, y) # Position child
297
+ y += child_height + self.gap
382
298
 
383
299
 
384
300
  class Grid(DoubleAxisLayout):
@@ -388,198 +304,51 @@ class Grid(DoubleAxisLayout):
388
304
  self.cols = cols
389
305
  self.gap = gap
390
306
 
391
- def set_gap(self, value: int) -> Self:
392
- self.gap = value
393
- self.notify_parent()
394
- return self
395
-
396
- def set_dimensions(self, rows: int, cols: int) -> Self:
397
- self.rows = rows
398
- self.cols = cols
399
- self.notify_parent()
400
- return self
401
-
402
- def get_raw_size(self) -> tuple[float, float]:
403
- """Calculate raw size based on the max width and height needed to fit all children."""
404
- if not self.parent.children:
405
- return self.parent.rect.size
406
-
407
- # Calculate necessary width and height for the grid
408
- max_child_width = max(child.get_min_required_size()[0] for child in self.parent.children)
409
- max_child_height = max(child.get_min_required_size()[1] for child in self.parent.children)
410
-
411
- grid_width = self.cols * max_child_width + (self.cols - 1) * self.gap
412
- grid_height = self.rows * max_child_height + (self.rows - 1) * self.gap
413
- target_rect = self.parent.inflate_rect_by_padding((0, 0, grid_width, grid_height))
414
-
415
- return target_rect.size
307
+ def update_children_rect(self):
308
+ if self.parent.children:
309
+ cell_width = max(child.get_min_required_size()[0] for child in self.parent.children)
310
+ cell_height = max(child.get_min_required_size()[1] for child in self.parent.children)
311
+ width = self.cols * cell_width + self.gap * (self.cols - 1)
312
+ height = self.rows * cell_height + self.gap * (self.rows - 1)
313
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
314
+ else:
315
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, 10)
316
+ self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
416
317
 
417
318
  def arrange(self) -> None:
418
- """Arrange widgets in a grid with specified rows and columns."""
419
- if not self.parent or not self.parent.children:
319
+ self.update_children_rect()
320
+ if not self.parent.children:
420
321
  return
421
322
 
422
- if self.child_constraints:
423
- for child in self.parent.children:
424
- child.add_constraints(*self.child_constraints)
425
-
426
-
427
- if self.parent.autoresize_w or self.parent.autoresize_h:
428
- width, height = self.get_auto_size()
429
- if self.parent.rect.size != (width, height):
430
- self.parent.set_size((width, height))
431
- self.parent.build()
432
- self.arrange()
433
- return
434
-
435
- self.child_rect = self.parent.get_padded_rect()
436
-
437
- # Calculate cell width and height based on parent size and gaps
438
- cell_width = (self.child_rect.width - (self.cols - 1) * self.gap) / self.cols
439
- cell_height = (self.child_rect.height - (self.rows - 1) * self.gap) / self.rows
323
+ cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols
324
+ cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows
440
325
 
441
326
  for i, child in enumerate(self.parent.children):
442
327
  row = i // self.cols
443
328
  col = i % self.cols
444
- x = self.child_rect.left + col * (cell_width + self.gap)
445
- y = self.child_rect.top + row * (cell_height + self.gap)
446
-
447
- child.set_position(x, y)
329
+ x = self.children_rect.x + col * (cell_width + self.gap)
330
+ y = self.children_rect.y + row * (cell_height + self.gap)
448
331
  child.set_size((cell_width, cell_height))
449
-
450
- def handle_event(self, event):
451
-
452
- if event.type == pygame.KEYDOWN:
453
- if event.key == pygame.K_DOWN:
454
- self.focus_down_child()
455
- elif event.key == pygame.K_UP:
456
- self.focus_up_child()
457
- elif event.key == pygame.K_LEFT:
458
- self.focus_left_child()
459
- elif event.key == pygame.K_RIGHT:
460
- self.focus_right_child()
461
- else:
462
- return
463
- elif event.type == pygame.MOUSEBUTTONDOWN:
464
- r = self.parent.get_root()
465
- if not r:
466
- return
467
-
468
- if self.parent.rect.collidepoint(
469
- r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
470
- ):
471
- if event.button == 4:
472
- self.parent.scroll_by((0, -10))
473
- elif event.button == 5:
474
- self.parent.scroll_by((0, 10))
475
- else:
476
- return
477
- self.parent.clamp_scroll()
478
- else:
479
- return
480
- else:
481
- return
482
- event.consumed = True
483
-
484
- def focus_down_child(self) -> None:
485
- l = self.parent.get_interactive_children()
486
- new_index = self.parent.focused_index + self.cols
487
- if new_index >= len(l):
488
- return
489
- self.parent.focused_index = new_index
490
- focused = l[self.parent.focused_index]
491
- focused.get_focus()
492
- self.scroll_to_widget(focused)
493
-
494
- def focus_up_child(self) -> None:
495
- l = self.parent.get_interactive_children()
496
- new_index = self.parent.focused_index - self.cols
497
- if new_index < 0:
498
- return
499
- self.parent.focused_index = new_index
500
- focused = l[self.parent.focused_index]
501
- focused.get_focus()
502
- self.scroll_to_widget(focused)
503
-
504
- def focus_left_child(self) -> None:
505
- l = self.parent.get_interactive_children()
506
- new_index = (self.parent.focused_index % self.cols) -1
507
- if new_index < 0:
508
- return
509
- self.parent.focused_index -=1
510
- focused = l[self.parent.focused_index]
511
- focused.get_focus()
512
- self.scroll_to_widget(focused)
513
-
514
- def focus_right_child(self) -> None:
515
- l = self.parent.get_interactive_children()
516
- new_index = (self.parent.focused_index % self.cols) +1
517
- if new_index >= self.cols or self.parent.focused_index+1 >= len(l):
518
- return
519
- self.parent.focused_index += 1
520
- focused = l[self.parent.focused_index]
521
- focused.get_focus()
522
- self.scroll_to_widget(focused)
332
+ child.set_position(x, y)
523
333
 
524
334
 
525
335
  class GridFill(Grid):
526
- def __init__(self, rows: int, cols: int, gap: int = 0):
527
- super().__init__(rows,cols,gap)
528
-
529
336
  def arrange(self) -> None:
530
- """Arrange widgets to fill each grid cell, adjusting to available space."""
531
- if not self.parent or not self.parent.children:
337
+ """
338
+ Arranges children in a grid and resizes them to fill the parent's available space,
339
+ accounting for the gap between children.
340
+ """
341
+ self.update_children_rect()
342
+ if not self.parent.children:
532
343
  return
533
344
 
534
- if self.child_constraints:
535
- for child in self.parent.children:
536
- child.add_constraints(*self.child_constraints)
345
+ cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols
346
+ cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows
537
347
 
538
- self.child_rect = self.parent.get_padded_rect()
539
-
540
- # If autoresize is enabled, calculate required dimensions
541
- if self.parent.autoresize_w or self.parent.autoresize_h:
542
- width, height = self.get_auto_size()
543
- if self.parent.rect.size != (width, height):
544
- self.parent.set_size((width, height))
545
- self.parent.build()
546
- self.arrange()
547
- return
548
-
549
- # Adjust for scrolling offset
550
- self.child_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
551
-
552
- # Calculate cell dimensions based on available space
553
- available_width = self.child_rect.width - (self.cols - 1) * self.gap
554
- available_height = self.child_rect.height - (self.rows - 1) * self.gap
555
- cell_width = available_width / self.cols
556
- cell_height = available_height / self.rows
557
-
558
- # Position each child in the grid
559
- for index, child in enumerate(self.parent.children):
560
- row = index // self.cols
561
- col = index % self.cols
562
- x = self.child_rect.left + col * (cell_width + self.gap)
563
- y = self.child_rect.top + row * (cell_height + self.gap)
564
-
565
- child.set_position(x, y)
566
- child.set_autoresize_w(False)
567
- child.set_autoresize_h(False)
348
+ for i, child in enumerate(self.parent.children):
349
+ row = i // self.cols
350
+ col = i % self.cols
351
+ x = self.children_rect.x + col * (cell_width + self.gap)
352
+ y = self.children_rect.y + row * (cell_height + self.gap)
568
353
  child.set_size((cell_width, cell_height))
569
-
570
- def get_raw_size(self) -> tuple[float, float]:
571
- """Calculate the grid’s raw size based on child minimums and the grid dimensions."""
572
- if not self.parent.children:
573
- return self.parent.rect.size
574
-
575
- # Determine minimum cell size required by the largest child
576
- max_child_width = max(child.get_min_required_size()[0] for child in self.parent.children)
577
- max_child_height = max(child.get_min_required_size()[1] for child in self.parent.children)
578
-
579
- # Calculate total required size for the grid
580
- grid_width = self.cols * max_child_width + (self.cols - 1) * self.gap
581
- grid_height = self.rows * max_child_height + (self.rows - 1) * self.gap
582
-
583
- # Adjust for padding and return
584
- target_rect = self.parent.inflate_rect_by_padding((0, 0, grid_width, grid_height))
585
- return target_rect.size
354
+ child.set_position(x, y)