batframework 1.0.9a11__py3-none-any.whl → 1.0.9a13__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 (73) hide show
  1. batFramework/__init__.py +3 -11
  2. batFramework/action.py +280 -279
  3. batFramework/actionContainer.py +105 -82
  4. batFramework/animatedSprite.py +80 -58
  5. batFramework/animation.py +91 -77
  6. batFramework/audioManager.py +156 -131
  7. batFramework/baseScene.py +249 -240
  8. batFramework/camera.py +245 -317
  9. batFramework/constants.py +57 -51
  10. batFramework/cutscene.py +239 -253
  11. batFramework/cutsceneManager.py +34 -34
  12. batFramework/drawable.py +107 -77
  13. batFramework/dynamicEntity.py +30 -30
  14. batFramework/easingController.py +58 -58
  15. batFramework/entity.py +130 -130
  16. batFramework/enums.py +171 -135
  17. batFramework/fontManager.py +65 -65
  18. batFramework/gui/__init__.py +28 -25
  19. batFramework/gui/animatedLabel.py +90 -89
  20. batFramework/gui/button.py +17 -17
  21. batFramework/gui/clickableWidget.py +244 -244
  22. batFramework/gui/collapseContainer.py +98 -0
  23. batFramework/gui/constraints/__init__.py +1 -1
  24. batFramework/gui/constraints/constraints.py +1066 -980
  25. batFramework/gui/container.py +220 -206
  26. batFramework/gui/debugger.py +140 -130
  27. batFramework/gui/draggableWidget.py +63 -44
  28. batFramework/gui/image.py +61 -58
  29. batFramework/gui/indicator.py +116 -113
  30. batFramework/gui/interactiveWidget.py +243 -239
  31. batFramework/gui/label.py +147 -344
  32. batFramework/gui/layout.py +442 -429
  33. batFramework/gui/meter.py +155 -96
  34. batFramework/gui/radioButton.py +43 -35
  35. batFramework/gui/root.py +228 -228
  36. batFramework/gui/scrollingContainer.py +282 -0
  37. batFramework/gui/selector.py +232 -250
  38. batFramework/gui/shape.py +286 -276
  39. batFramework/gui/slider.py +353 -397
  40. batFramework/gui/style.py +10 -10
  41. batFramework/gui/styleManager.py +49 -54
  42. batFramework/gui/syncedVar.py +43 -49
  43. batFramework/gui/textInput.py +331 -306
  44. batFramework/gui/textWidget.py +308 -0
  45. batFramework/gui/toggle.py +140 -128
  46. batFramework/gui/tooltip.py +35 -30
  47. batFramework/gui/widget.py +546 -521
  48. batFramework/manager.py +131 -134
  49. batFramework/particle.py +118 -118
  50. batFramework/propertyEaser.py +79 -79
  51. batFramework/renderGroup.py +34 -34
  52. batFramework/resourceManager.py +130 -130
  53. batFramework/scene.py +31 -31
  54. batFramework/sceneLayer.py +134 -138
  55. batFramework/sceneManager.py +200 -197
  56. batFramework/scrollingSprite.py +115 -115
  57. batFramework/sprite.py +46 -51
  58. batFramework/stateMachine.py +49 -54
  59. batFramework/templates/__init__.py +2 -1
  60. batFramework/templates/character.py +15 -0
  61. batFramework/templates/controller.py +158 -97
  62. batFramework/templates/stateMachine.py +39 -0
  63. batFramework/tileset.py +46 -46
  64. batFramework/timeManager.py +213 -213
  65. batFramework/transition.py +162 -162
  66. batFramework/triggerZone.py +22 -22
  67. batFramework/utils.py +306 -306
  68. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/LICENSE +20 -20
  69. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/METADATA +24 -17
  70. batframework-1.0.9a13.dist-info/RECORD +72 -0
  71. batframework-1.0.9a11.dist-info/RECORD +0 -67
  72. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/WHEEL +0 -0
  73. {batframework-1.0.9a11.dist-info → batframework-1.0.9a13.dist-info}/top_level.txt +0 -0
@@ -1,429 +1,442 @@
1
- import batFramework as bf
2
- from .widget import Widget
3
- from .constraints.constraints import *
4
- from typing import Self, TYPE_CHECKING
5
- from abc import ABC,abstractmethod
6
- import pygame
7
- from .interactiveWidget import InteractiveWidget
8
-
9
- if TYPE_CHECKING:
10
- from .container import Container
11
-
12
-
13
- class Layout(ABC):
14
- def __init__(self, parent: "Container" = None):
15
- self.parent = parent
16
- self.child_constraints: list[Constraint] = []
17
- self.children_rect = pygame.FRect(0, 0, 0, 0)
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
-
25
- def set_child_constraints(self, *constraints) -> Self:
26
- self.child_constraints = list(constraints)
27
- self.update_child_constraints()
28
- self.notify_parent()
29
- return self
30
-
31
- def set_parent(self, parent: Widget):
32
- self.parent = parent
33
- self.notify_parent()
34
-
35
- def notify_parent(self) -> None:
36
- if self.parent:
37
- self.parent.dirty_layout = True
38
-
39
- def update_children_rect(self):
40
- if self.parent.get_layout_children():
41
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft,*self.parent.get_layout_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.get_layout_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.get_layout_children():
53
- child.add_constraints(*self.child_constraints)
54
-
55
- def arrange(self) -> None:
56
- """
57
- updates the position of children in the parent
58
- """
59
- return
60
-
61
- def get_raw_size(self):
62
- """
63
- Returns the size the container should have to encapsulate perfectly all of its widgets
64
- """
65
- # print(self,self.parent,len(self.parent.get_layout_children()))
66
- self.update_children_rect()
67
- return self.children_rect.size
68
-
69
- def get_auto_size(self) -> tuple[float, float]:
70
- """
71
- Returns the final size the container should have (while keeping the width and height if they are non-resizable)
72
- """
73
- target_size = list(self.get_raw_size())
74
- if not self.parent.autoresize_w:
75
- target_size[0] = self.parent.get_inner_width()
76
- if not self.parent.autoresize_h:
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
- if not hasattr(container,"scroll"):
92
- container = container.parent
93
- continue
94
- target_rect = target.rect # global
95
- padded_rect = container.get_inner_rect() # global
96
-
97
- dx = dy = 0
98
-
99
- # --- Horizontal ---
100
- if target_rect.width <= padded_rect.width:
101
- if target_rect.left < padded_rect.left:
102
- dx = target_rect.left - padded_rect.left
103
- elif target_rect.right > padded_rect.right:
104
- dx = target_rect.right - padded_rect.right
105
- else:
106
- # Widget is wider than viewport: align left side
107
- if target_rect.left < padded_rect.left:
108
- dx = target_rect.left - padded_rect.left
109
- elif target_rect.right > padded_rect.right:
110
- dx = target_rect.right - padded_rect.right
111
-
112
- # --- Vertical ---
113
- if target_rect.height <= padded_rect.height:
114
- if target_rect.top < padded_rect.top:
115
- dy = target_rect.top - padded_rect.top
116
- elif target_rect.bottom > padded_rect.bottom:
117
- dy = target_rect.bottom - padded_rect.bottom
118
- else:
119
- # Widget is taller than viewport: align top side
120
- if target_rect.top < padded_rect.top:
121
- dy = target_rect.top - padded_rect.top
122
- elif target_rect.bottom > padded_rect.bottom:
123
- dy = target_rect.bottom - padded_rect.bottom
124
-
125
- # Convert global delta into local scroll delta for container
126
- container.scroll_by((dx, dy))
127
-
128
- # Now the target for the next iteration is the container itself
129
- target = container
130
- container = container.parent
131
-
132
-
133
- def handle_event(self, event):
134
- pass
135
-
136
- class FreeLayout(Layout):...
137
-
138
- class SingleAxisLayout(Layout):
139
-
140
- def __init__(self, parent = None):
141
- super().__init__(parent)
142
-
143
- def focus_next_child(self) -> None:
144
- l = self.parent.get_interactive_children()
145
- self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
146
- focused = l[self.parent.focused_index]
147
- focused.get_focus()
148
-
149
- def focus_prev_child(self) -> None:
150
- l = self.parent.get_interactive_children()
151
- self.parent.focused_index = max(self.parent.focused_index - 1, 0)
152
- focused = l[self.parent.focused_index]
153
- focused.get_focus()
154
-
155
-
156
- class DoubleAxisLayout(Layout):
157
- """Abstract layout class for layouts that arrange widgets in two dimensions."""
158
-
159
- def focus_up_child(self) -> None:...
160
- def focus_down_child(self) -> None:...
161
- def focus_right_child(self) -> None:...
162
- def focus_left_child(self) -> None:...
163
-
164
-
165
- class Column(SingleAxisLayout):
166
- def __init__(self, gap: int = 0):
167
- super().__init__()
168
- self.gap = gap
169
-
170
- def update_children_rect(self):
171
- if self.parent.get_layout_children():
172
- width = max(child.get_min_required_size()[0] for child in self.parent.get_layout_children() )
173
- height = sum(child.get_min_required_size()[1] for child in self.parent.get_layout_children()) + self.gap * (len(self.parent.get_layout_children()) - 1)
174
-
175
- # width = max(child.rect.w for child in self.parent.get_layout_children() )
176
- # height = sum(child.rect.h for child in self.parent.get_layout_children()) + self.gap * (len(self.parent.get_layout_children()) - 1)
177
-
178
-
179
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
180
- else:
181
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, 10)
182
- self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
183
-
184
- def handle_event(self, event):
185
- if not self.parent.get_layout_children() or not self.parent.children_has_focus():
186
- return
187
-
188
- if event.type == pygame.KEYDOWN:
189
- if event.key in (pygame.K_DOWN, pygame.K_UP):
190
- self.focus_next_child() if event.key == pygame.K_DOWN else self.focus_prev_child()
191
- event.consumed = True
192
- event.consumed = True
193
-
194
-
195
- def arrange(self) -> None:
196
- self.update_children_rect()
197
- y = self.children_rect.y
198
- for child in self.parent.get_layout_children():
199
- child.set_position(self.children_rect.x, y)
200
- y += child.rect.height + self.gap
201
-
202
- class Row(SingleAxisLayout):
203
- def __init__(self, gap: int = 0):
204
- super().__init__()
205
- self.gap = gap
206
-
207
- def update_children_rect(self):
208
- if self.parent.get_layout_children():
209
- height = max(child.get_min_required_size()[1] for child in self.parent.get_layout_children())
210
- width = sum(child.get_min_required_size()[0] for child in self.parent.get_layout_children()) + self.gap * (len(self.parent.get_layout_children()) - 1)
211
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
212
- else:
213
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10,10)
214
- self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
215
-
216
- def handle_event(self, event):
217
- if not self.parent.get_layout_children() or not self.parent.children_has_focus():
218
- return
219
-
220
- if event.type == pygame.KEYDOWN:
221
- if event.key in (pygame.K_RIGHT, pygame.K_LEFT):
222
- self.focus_next_child() if event.key == pygame.K_RIGHT else self.focus_prev_child()
223
- event.consumed = True
224
-
225
-
226
- def arrange(self) -> None:
227
- self.update_children_rect()
228
- x = self.children_rect.x
229
- for child in self.parent.get_layout_children():
230
- child.set_position(x,self.children_rect.y)
231
- x += child.rect.width + self.gap
232
-
233
-
234
-
235
- class RowFill(Row):
236
-
237
- def update_children_rect(self):
238
- parent_width = self.parent.get_inner_width()
239
- if self.parent.get_layout_children():
240
- height = max(child.get_min_required_size()[1] for child in self.parent.get_layout_children())
241
- width = parent_width
242
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
243
- else:
244
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, parent_width,10)
245
- self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
246
-
247
-
248
- def arrange(self) -> None:
249
- """
250
- Arranges children in a row and resizes them to fill the parent's height,
251
- accounting for the gap between children.
252
- """
253
- self.update_children_rect()
254
- for child in self.parent.get_layout_children():
255
- child.set_autoresize_w(False)
256
- x = self.children_rect.x
257
- # available_height = self.children_rect.height
258
-
259
- # Calculate the width available for each child
260
- total_gap = self.gap * (len(self.parent.get_layout_children()) - 1)
261
- available_width = max(0, self.children_rect.width - total_gap)
262
- child_width = available_width / len(self.parent.get_layout_children()) if self.parent.get_layout_children() else 0
263
-
264
- for child in self.parent.get_layout_children():
265
- child.set_size((child_width, None)) # Resize child to fill height
266
- child.set_position(x, self.children_rect.y) # Position child
267
- x += child_width + self.gap
268
-
269
-
270
- class ColumnFill(Column):
271
-
272
- def update_children_rect(self):
273
- parent_height = self.parent.get_inner_height()
274
- if self.parent.get_layout_children():
275
- width = max(child.get_min_required_size()[0] for child in self.parent.get_layout_children())
276
- height = parent_height
277
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
278
- else:
279
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, parent_height)
280
- self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
281
-
282
- def arrange(self) -> None:
283
- """
284
- Arranges children in a column and resizes them to fill the parent's width,
285
- accounting for the gap between children.
286
- """
287
- self.update_children_rect()
288
- for child in self.parent.get_layout_children():
289
- child.set_autoresize_h(False)
290
- y = self.children_rect.y
291
-
292
- # Calculate the height available for each child
293
- total_gap = self.gap * (len(self.parent.get_layout_children()) - 1)
294
- available_height = max(0, self.children_rect.height - total_gap)
295
- child_height = available_height / len(self.parent.get_layout_children()) if self.parent.get_layout_children() else 0
296
-
297
- for child in self.parent.get_layout_children():
298
- child.set_size((None, child_height)) # Resize child to fill width
299
- child.set_position(self.children_rect.x, y) # Position child
300
- y += child_height + self.gap
301
-
302
-
303
- class Grid(DoubleAxisLayout):
304
- def __init__(self, rows: int, cols: int, gap: int = 0):
305
- super().__init__()
306
- self.rows = rows
307
- self.cols = cols
308
- self.gap = gap
309
-
310
- def focus_up_child(self) -> None:
311
- l = self.parent.get_interactive_children()
312
- if not l:
313
- return
314
- current_index = self.parent.focused_index
315
- if current_index == -1:
316
- return
317
- current_row = current_index // self.cols
318
- target_index = max(0, current_index - self.cols)
319
- if target_index // self.cols < current_row:
320
- self.parent.focused_index = target_index
321
- l[target_index].get_focus()
322
-
323
- def focus_down_child(self) -> None:
324
- l = self.parent.get_interactive_children()
325
- if not l:
326
- return
327
- current_index = self.parent.focused_index
328
- if current_index == -1:
329
- return
330
- current_row = current_index // self.cols
331
- target_index = min(len(l) - 1, current_index + self.cols)
332
- if target_index // self.cols > current_row:
333
- self.parent.focused_index = target_index
334
- l[target_index].get_focus()
335
-
336
- def focus_left_child(self) -> None:
337
- l = self.parent.get_interactive_children()
338
- if not l:
339
- return
340
- current_index = self.parent.focused_index
341
- if current_index == -1:
342
- return
343
- target_index = max(0, current_index - 1)
344
- if target_index // self.cols == current_index // self.cols:
345
- self.parent.focused_index = target_index
346
- l[target_index].get_focus()
347
-
348
- def focus_right_child(self) -> None:
349
- l = self.parent.get_interactive_children()
350
- if not l:
351
- return
352
- current_index = self.parent.focused_index
353
- if current_index == -1:
354
- return
355
- target_index = min(len(l) - 1, current_index + 1)
356
- if target_index // self.cols == current_index // self.cols:
357
- self.parent.focused_index = target_index
358
- l[target_index].get_focus()
359
-
360
- def handle_event(self, event):
361
- if not self.parent.get_layout_children() or not self.parent.children_has_focus():
362
- return
363
-
364
- if event.type == pygame.KEYDOWN:
365
- if event.key in (pygame.K_RIGHT, pygame.K_LEFT, pygame.K_UP, pygame.K_DOWN):
366
- if event.key == pygame.K_RIGHT:
367
- self.focus_right_child()
368
- elif event.key == pygame.K_LEFT:
369
- self.focus_left_child()
370
- elif event.key == pygame.K_UP:
371
- self.focus_up_child()
372
- elif event.key == pygame.K_DOWN:
373
- self.focus_down_child()
374
-
375
- event.consumed = True
376
-
377
- def update_children_rect(self):
378
- if self.parent.get_layout_children():
379
- cell_width = max(child.get_min_required_size()[0] for child in self.parent.get_layout_children())
380
- cell_height = max(child.get_min_required_size()[1] for child in self.parent.get_layout_children())
381
- width = self.cols * cell_width + self.gap * (self.cols - 1)
382
- height = self.rows * cell_height + self.gap * (self.rows - 1)
383
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
384
- else:
385
- self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, 10)
386
- self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
387
-
388
- def arrange(self) -> None:
389
- self.update_children_rect()
390
- if not self.parent.get_layout_children():
391
- return
392
-
393
- cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols
394
- cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows
395
-
396
- for i, child in enumerate(self.parent.get_layout_children()):
397
- row = i // self.cols
398
- col = i % self.cols
399
- x = self.children_rect.x + col * (cell_width + self.gap)
400
- y = self.children_rect.y + row * (cell_height + self.gap)
401
- child.set_size((cell_width, cell_height))
402
- child.set_position(x, y)
403
-
404
-
405
- class GridFill(Grid):
406
- def update_children_rect(self):
407
- self.children_rect = self.parent.get_inner_rect()
408
- def arrange(self) -> None:
409
- """
410
- Arranges children in a grid and resizes them to fill the parent's available space,
411
- accounting for the gap between children.
412
- """
413
- self.update_children_rect()
414
-
415
- if not self.parent.get_layout_children():
416
- return
417
- for child in self.parent.get_layout_children():
418
- child.set_autoresize(False)
419
-
420
- cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols
421
- cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows
422
-
423
- for i, child in enumerate(self.parent.get_layout_children()):
424
- row = i // self.cols
425
- col = i % self.cols
426
- x = self.children_rect.x + col * (cell_width + self.gap)
427
- y = self.children_rect.y + row * (cell_height + self.gap)
428
- child.set_size((cell_width, cell_height))
429
- child.set_position(x, y)
1
+ import batFramework as bf
2
+ from .widget import Widget
3
+ from .constraints.constraints import *
4
+ from typing import Self, TYPE_CHECKING
5
+ from abc import ABC,abstractmethod
6
+ import pygame
7
+ from .interactiveWidget import InteractiveWidget
8
+
9
+ if TYPE_CHECKING:
10
+ from .container import Container
11
+
12
+
13
+ class Layout(ABC):
14
+ def __init__(self, parent: "Container" = None):
15
+ self.parent = parent
16
+ self.child_constraints: list[Constraint] = []
17
+ self.children_rect = pygame.FRect(0, 0, 0, 0)
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
+
25
+ def set_child_constraints(self, *constraints) -> Self:
26
+ self.child_constraints = list(constraints)
27
+ self.update_child_constraints()
28
+ self.notify_parent()
29
+ return self
30
+
31
+ def set_parent(self, parent: Widget):
32
+ self.parent = parent
33
+ self.notify_parent()
34
+
35
+ def notify_parent(self) -> None:
36
+ if self.parent:
37
+ self.parent.dirty_layout = True
38
+
39
+ def update_children_rect(self):
40
+ if self.parent.get_layout_children():
41
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft,*self.parent.get_layout_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.get_layout_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.get_layout_children():
53
+ child.add_constraints(*self.child_constraints)
54
+
55
+ def arrange(self) -> None:
56
+ """
57
+ updates the position of children in the parent
58
+ """
59
+ return
60
+
61
+ def scroll_children(self)->None:
62
+ return
63
+
64
+ def get_raw_size(self):
65
+ """
66
+ Returns the size the container should have to encapsulate perfectly all of its widgets
67
+ """
68
+ # print(self,self.parent,len(self.parent.get_layout_children()))
69
+ self.update_children_rect() # TODO: find a way to call this fewer times
70
+ return self.children_rect.size
71
+
72
+ def get_auto_size(self) -> tuple[float, float]:
73
+ """
74
+ Returns the final size the container should have (while keeping the width and height if they are non-resizable)
75
+ """
76
+ target_size = list(self.get_raw_size())
77
+ if not self.parent.autoresize_w:
78
+ target_size[0] = self.parent.get_inner_width()
79
+ if not self.parent.autoresize_h:
80
+ target_size[1] = self.parent.get_inner_height()
81
+
82
+ return self.parent.expand_rect_with_padding((0,0,*target_size)).size
83
+ # return target_size
84
+
85
+ def scroll_to_widget(self, widget: "Widget"):
86
+ """
87
+ Scrolls parent container so that the widget becomes visible.
88
+ If the widget is bigger than the container, aligns top/left.
89
+ """
90
+ inner = self.parent.get_inner_rect()
91
+
92
+ if self.parent.clip_children and not inner.contains(widget.rect):
93
+ scroll = pygame.Vector2(0, 0)
94
+
95
+ # Horizontal
96
+ if widget.rect.w > inner.w:
97
+ # Widget is too wide: just align left
98
+ if widget.rect.left < inner.left:
99
+ scroll.x = inner.left - widget.rect.left
100
+ else:
101
+ if widget.rect.left < inner.left:
102
+ scroll.x = inner.left - widget.rect.left
103
+ elif widget.rect.right > inner.right:
104
+ scroll.x = inner.right - widget.rect.right
105
+
106
+ # Vertical
107
+ if widget.rect.h > inner.h:
108
+ # Widget is too tall: just align top
109
+ if widget.rect.top < inner.top:
110
+ scroll.y = inner.top - widget.rect.top
111
+ else:
112
+ if widget.rect.top < inner.top:
113
+ scroll.y = inner.top - widget.rect.top
114
+ elif widget.rect.bottom > inner.bottom:
115
+ scroll.y = inner.bottom - widget.rect.bottom
116
+
117
+ # Apply
118
+ self.parent.scroll_by(-scroll)
119
+
120
+ # stop at root
121
+ if self.parent.is_root:
122
+ return
123
+
124
+ # recurse upwards if parent has a layout
125
+ if hasattr(self.parent.parent, "layout"):
126
+ self.parent.parent.layout.scroll_to_widget(widget)
127
+ def handle_event(self, event):
128
+ pass
129
+
130
+ class FreeLayout(Layout):...
131
+
132
+ class SingleAxisLayout(Layout):
133
+
134
+ def __init__(self, parent = None):
135
+ super().__init__(parent)
136
+
137
+ def focus_next_child(self) -> None:
138
+ l = self.parent.get_interactive_children()
139
+ self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
140
+ focused = l[self.parent.focused_index]
141
+ focused.get_focus()
142
+
143
+ def focus_prev_child(self) -> None:
144
+ l = self.parent.get_interactive_children()
145
+ self.parent.focused_index = max(self.parent.focused_index - 1, 0)
146
+ focused = l[self.parent.focused_index]
147
+ focused.get_focus()
148
+
149
+ class DoubleAxisLayout(Layout):
150
+ """Abstract layout class for layouts that arrange widgets in two dimensions."""
151
+
152
+ def focus_up_child(self) -> None:...
153
+ def focus_down_child(self) -> None:...
154
+ def focus_right_child(self) -> None:...
155
+ def focus_left_child(self) -> None:...
156
+
157
+ class Column(SingleAxisLayout):
158
+ def __init__(self, gap: int = 0):
159
+ super().__init__()
160
+ self.gap = gap
161
+
162
+ def handle_event(self, event):
163
+ if not self.parent.get_layout_children() or not self.parent.children_has_focus():
164
+ return
165
+
166
+ if event.type == pygame.KEYDOWN:
167
+ if event.key in (pygame.K_DOWN, pygame.K_UP):
168
+ self.focus_next_child() if event.key == pygame.K_DOWN else self.focus_prev_child()
169
+ event.consumed = True
170
+
171
+ def update_children_rect(self):
172
+ print("Update children rect")
173
+ layout_children = self.parent.get_layout_children()
174
+ if layout_children:
175
+ width = max(child.get_min_required_size()[0] if child.autoresize_h else child.rect.w for child in layout_children )
176
+ height = sum(child.get_min_required_size()[1]if child.autoresize_w else child.rect.h for child in layout_children) + self.gap * (len(layout_children) - 1)
177
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
178
+ else:
179
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, 10)
180
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
181
+
182
+ def scroll_children(self):
183
+ topleft = list(self.parent.get_inner_rect().topleft)
184
+ topleft[0] -= round(self.parent.scroll.x)
185
+ topleft[1] -= round(self.parent.scroll.y)
186
+ layout_children = self.parent.get_layout_children()
187
+ for child in layout_children:
188
+ child.set_position(*topleft)
189
+ topleft[1] += child.rect.height + self.gap
190
+
191
+ def arrange(self) -> None:
192
+ self.update_children_rect()
193
+ self.scroll_children()
194
+
195
+ class Row(SingleAxisLayout):
196
+ def __init__(self, gap: int = 0):
197
+ super().__init__()
198
+ self.gap = gap
199
+
200
+ def handle_event(self, event):
201
+ if not self.parent.get_layout_children() or not self.parent.children_has_focus():
202
+ return
203
+
204
+ if event.type == pygame.KEYDOWN:
205
+ if event.key in (pygame.K_RIGHT, pygame.K_LEFT):
206
+ self.focus_next_child() if event.key == pygame.K_RIGHT else self.focus_prev_child()
207
+ event.consumed = True
208
+
209
+ def update_children_rect(self):
210
+ layout_children = self.parent.get_layout_children()
211
+ if layout_children:
212
+ width = sum(child.get_min_required_size()[0] if child.autoresize_w else child.rect.w for child in layout_children ) + self.gap * (len(layout_children) - 1)
213
+ height = max(child.get_min_required_size()[1] if child.autoresize_h else child.rect.h for child in layout_children )
214
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
215
+ else:
216
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10,10)
217
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
218
+
219
+ def scroll_children(self):
220
+ topleft = list(self.parent.get_inner_rect().topleft)
221
+ topleft[0] -= round(self.parent.scroll.x)
222
+ topleft[1] -= round(self.parent.scroll.y)
223
+ layout_children = self.parent.get_layout_children()
224
+ for child in layout_children:
225
+ child.set_position(*topleft)
226
+ topleft[0] += child.rect.width + self.gap
227
+
228
+ def arrange(self) -> None:
229
+ self.update_children_rect()
230
+ self.scroll_children()
231
+
232
+ class Grid(DoubleAxisLayout):
233
+ def __init__(self, rows: int, cols: int, gap: int = 0):
234
+ super().__init__()
235
+ self.rows = rows
236
+ self.cols = cols
237
+ self.gap = gap
238
+
239
+ def focus_up_child(self) -> None:
240
+ l = self.parent.get_interactive_children()
241
+ if not l:
242
+ return
243
+ current_index = self.parent.focused_index
244
+ if current_index == -1:
245
+ return
246
+ current_row = current_index // self.cols
247
+ target_index = max(0, current_index - self.cols)
248
+ if target_index // self.cols < current_row:
249
+ self.parent.focused_index = target_index
250
+ l[target_index].get_focus()
251
+
252
+ def focus_down_child(self) -> None:
253
+ l = self.parent.get_interactive_children()
254
+ if not l:
255
+ return
256
+ current_index = self.parent.focused_index
257
+ if current_index == -1:
258
+ return
259
+ current_row = current_index // self.cols
260
+ target_index = min(len(l) - 1, current_index + self.cols)
261
+ if target_index // self.cols > current_row:
262
+ self.parent.focused_index = target_index
263
+ l[target_index].get_focus()
264
+
265
+ def focus_left_child(self) -> None:
266
+ l = self.parent.get_interactive_children()
267
+ if not l:
268
+ return
269
+ current_index = self.parent.focused_index
270
+ if current_index == -1:
271
+ return
272
+ target_index = max(0, current_index - 1)
273
+ if target_index // self.cols == current_index // self.cols:
274
+ self.parent.focused_index = target_index
275
+ l[target_index].get_focus()
276
+
277
+ def focus_right_child(self) -> None:
278
+ l = self.parent.get_interactive_children()
279
+ if not l:
280
+ return
281
+ current_index = self.parent.focused_index
282
+ if current_index == -1:
283
+ return
284
+ target_index = min(len(l) - 1, current_index + 1)
285
+ if target_index // self.cols == current_index // self.cols:
286
+ self.parent.focused_index = target_index
287
+ l[target_index].get_focus()
288
+
289
+ def handle_event(self, event):
290
+ if not self.parent.get_layout_children() or not self.parent.children_has_focus():
291
+ return
292
+
293
+ if event.type == pygame.KEYDOWN:
294
+ if event.key in (pygame.K_RIGHT, pygame.K_LEFT, pygame.K_UP, pygame.K_DOWN):
295
+ if event.key == pygame.K_RIGHT:
296
+ self.focus_right_child()
297
+ elif event.key == pygame.K_LEFT:
298
+ self.focus_left_child()
299
+ elif event.key == pygame.K_UP:
300
+ self.focus_up_child()
301
+ elif event.key == pygame.K_DOWN:
302
+ self.focus_down_child()
303
+
304
+ event.consumed = True
305
+
306
+ def update_children_rect(self):
307
+ layout_children = self.parent.get_layout_children()
308
+ if layout_children:
309
+ cell_width = max(child.get_min_required_size()[0] for child in layout_children)
310
+ cell_height = max(child.get_min_required_size()[1] for child in layout_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)
317
+
318
+ def scroll_children(self):
319
+ topleft = list(self.parent.get_inner_rect().topleft)
320
+ topleft[0] -= round(self.parent.scroll.x)
321
+ topleft[1] -= round(self.parent.scroll.y)
322
+ layout_children = self.parent.get_layout_children()
323
+ cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols if self.cols else 0
324
+ cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows if self.rows else 0
325
+ for i, child in enumerate(layout_children):
326
+ row = i // self.cols
327
+ col = i % self.cols
328
+ x = topleft[0] + col * (cell_width + self.gap)
329
+ y = topleft[1] + row * (cell_height + self.gap)
330
+ child.set_position(x, y)
331
+
332
+ def arrange(self) -> None:
333
+ self.update_children_rect()
334
+ self.scroll_children()
335
+
336
+ class RowFill(Row):
337
+
338
+ def update_children_rect(self):
339
+ parent_width = self.parent.get_inner_width()
340
+ if self.parent.get_layout_children():
341
+ height = max(child.get_min_required_size()[1] for child in self.parent.get_layout_children())
342
+ width = parent_width
343
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
344
+ else:
345
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, parent_width,10)
346
+ self.children_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
347
+
348
+ def scroll_children(self):
349
+ topleft = list(self.parent.get_inner_rect().topleft)
350
+ topleft[0] -= round(self.parent.scroll.x)
351
+ topleft[1] -= round(self.parent.scroll.y)
352
+ layout_children = self.parent.get_layout_children()
353
+ total_gap = self.gap * (len(layout_children) - 1)
354
+ available_width = max(0, self.children_rect.width - total_gap)
355
+ child_width = available_width / len(layout_children) if layout_children else 0
356
+ for child in layout_children:
357
+ child.set_position(*topleft)
358
+ topleft[0] += child_width + self.gap
359
+
360
+ def resize_children(self):
361
+ layout_children = self.parent.get_layout_children()
362
+ total_gap = self.gap * (len(layout_children) - 1)
363
+ available_width = max(0, self.children_rect.width - total_gap)
364
+ child_width = available_width / len(layout_children) if layout_children else 0
365
+ for child in layout_children:
366
+ child.set_autoresize_w(False)
367
+ child.set_size((child_width, None))
368
+
369
+ def arrange(self) -> None:
370
+ self.update_children_rect()
371
+ self.resize_children()
372
+ self.scroll_children()
373
+
374
+ class ColumnFill(Column):
375
+
376
+ def update_children_rect(self):
377
+ parent_height = self.parent.get_inner_height()
378
+ if self.parent.get_layout_children():
379
+ width = max(child.get_min_required_size()[0] for child in self.parent.get_layout_children())
380
+ height = parent_height
381
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, width, height)
382
+ else:
383
+ self.children_rect = pygame.FRect(*self.parent.get_inner_rect().topleft, 10, parent_height)
384
+ self.children_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
385
+
386
+ def scroll_children(self):
387
+ topleft = list(self.parent.get_inner_rect().topleft)
388
+ topleft[0] -= round(self.parent.scroll.x)
389
+ topleft[1] -= round(self.parent.scroll.y)
390
+ layout_children = self.parent.get_layout_children()
391
+ total_gap = self.gap * (len(layout_children) - 1)
392
+ available_height = max(0, self.children_rect.height - total_gap)
393
+ child_height = available_height / len(layout_children) if layout_children else 0
394
+ for child in layout_children:
395
+ child.set_position(*topleft)
396
+ topleft[1] += child_height + self.gap
397
+
398
+ def resize_children(self):
399
+ layout_children = self.parent.get_layout_children()
400
+ total_gap = self.gap * (len(layout_children) - 1)
401
+ available_height = max(0, self.children_rect.height - total_gap)
402
+ child_height = available_height / len(layout_children) if layout_children else 0
403
+ for child in layout_children:
404
+ child.set_autoresize_h(False)
405
+ child.set_size((None, child_height))
406
+
407
+ def arrange(self) -> None:
408
+ self.update_children_rect()
409
+ self.resize_children()
410
+ self.scroll_children()
411
+
412
+ class GridFill(Grid):
413
+ def update_children_rect(self):
414
+ self.children_rect = self.parent.get_inner_rect()
415
+
416
+ def scroll_children(self):
417
+ topleft = list(self.parent.get_inner_rect().topleft)
418
+ topleft[0] -= round(self.parent.scroll.x)
419
+ topleft[1] -= round(self.parent.scroll.y)
420
+ layout_children = self.parent.get_layout_children()
421
+ cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols if self.cols else 0
422
+ cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows if self.rows else 0
423
+ for i, child in enumerate(layout_children):
424
+ row = i // self.cols
425
+ col = i % self.cols
426
+ x = round(topleft[0] + col * (cell_width + self.gap))
427
+ y = round(topleft[1] + row * (cell_height + self.gap))
428
+ child.set_position(x, y)
429
+
430
+ def resize_children(self):
431
+ layout_children = self.parent.get_layout_children()
432
+ cell_width = (self.children_rect.width - self.gap * (self.cols - 1)) / self.cols if self.cols else 0
433
+ cell_height = (self.children_rect.height - self.gap * (self.rows - 1)) / self.rows if self.rows else 0
434
+ for child in layout_children:
435
+ child.set_autoresize(False)
436
+ child.set_size((cell_width, cell_height))
437
+
438
+ def arrange(self) -> None:
439
+ print("arrange grid fill")
440
+ self.update_children_rect()
441
+ self.resize_children()
442
+ self.scroll_children()