batframework 1.1.0__py3-none-any.whl → 2.0.0a1__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 (81) hide show
  1. batFramework/__init__.py +84 -52
  2. batFramework/action.py +280 -252
  3. batFramework/actionContainer.py +105 -38
  4. batFramework/animatedSprite.py +81 -117
  5. batFramework/animation.py +91 -0
  6. batFramework/audioManager.py +156 -85
  7. batFramework/baseScene.py +249 -0
  8. batFramework/camera.py +245 -123
  9. batFramework/constants.py +57 -75
  10. batFramework/cutscene.py +239 -119
  11. batFramework/cutsceneManager.py +34 -0
  12. batFramework/drawable.py +107 -0
  13. batFramework/dynamicEntity.py +30 -23
  14. batFramework/easingController.py +58 -0
  15. batFramework/entity.py +130 -123
  16. batFramework/enums.py +171 -0
  17. batFramework/fontManager.py +65 -0
  18. batFramework/gui/__init__.py +28 -14
  19. batFramework/gui/animatedLabel.py +90 -0
  20. batFramework/gui/button.py +18 -84
  21. batFramework/gui/clickableWidget.py +244 -0
  22. batFramework/gui/collapseContainer.py +98 -0
  23. batFramework/gui/constraints/__init__.py +1 -0
  24. batFramework/gui/constraints/constraints.py +1066 -0
  25. batFramework/gui/container.py +220 -49
  26. batFramework/gui/debugger.py +140 -47
  27. batFramework/gui/draggableWidget.py +63 -0
  28. batFramework/gui/image.py +61 -23
  29. batFramework/gui/indicator.py +116 -40
  30. batFramework/gui/interactiveWidget.py +243 -22
  31. batFramework/gui/label.py +147 -110
  32. batFramework/gui/layout.py +442 -81
  33. batFramework/gui/meter.py +155 -0
  34. batFramework/gui/radioButton.py +43 -0
  35. batFramework/gui/root.py +228 -60
  36. batFramework/gui/scrollingContainer.py +282 -0
  37. batFramework/gui/selector.py +232 -0
  38. batFramework/gui/shape.py +286 -86
  39. batFramework/gui/slider.py +353 -0
  40. batFramework/gui/style.py +10 -0
  41. batFramework/gui/styleManager.py +49 -0
  42. batFramework/gui/syncedVar.py +43 -0
  43. batFramework/gui/textInput.py +331 -0
  44. batFramework/gui/textWidget.py +308 -0
  45. batFramework/gui/toggle.py +140 -62
  46. batFramework/gui/tooltip.py +35 -0
  47. batFramework/gui/widget.py +546 -307
  48. batFramework/manager.py +131 -50
  49. batFramework/particle.py +118 -0
  50. batFramework/propertyEaser.py +79 -0
  51. batFramework/renderGroup.py +34 -0
  52. batFramework/resourceManager.py +130 -0
  53. batFramework/scene.py +31 -226
  54. batFramework/sceneLayer.py +134 -0
  55. batFramework/sceneManager.py +200 -165
  56. batFramework/scrollingSprite.py +115 -0
  57. batFramework/sprite.py +46 -0
  58. batFramework/stateMachine.py +49 -51
  59. batFramework/templates/__init__.py +2 -0
  60. batFramework/templates/character.py +15 -0
  61. batFramework/templates/controller.py +158 -0
  62. batFramework/templates/stateMachine.py +39 -0
  63. batFramework/tileset.py +46 -0
  64. batFramework/timeManager.py +213 -0
  65. batFramework/transition.py +162 -157
  66. batFramework/triggerZone.py +22 -22
  67. batFramework/utils.py +306 -184
  68. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/LICENSE +20 -20
  69. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/METADATA +7 -2
  70. batframework-2.0.0a1.dist-info/RECORD +72 -0
  71. batFramework/cutsceneBlocks.py +0 -176
  72. batFramework/debugger.py +0 -48
  73. batFramework/easing.py +0 -71
  74. batFramework/gui/constraints.py +0 -204
  75. batFramework/gui/frame.py +0 -19
  76. batFramework/particles.py +0 -77
  77. batFramework/time.py +0 -75
  78. batFramework/transitionManager.py +0 -0
  79. batframework-1.1.0.dist-info/RECORD +0 -43
  80. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/WHEEL +0 -0
  81. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/top_level.txt +0 -0
@@ -1,81 +1,442 @@
1
- import batFramework as bf
2
- from .widget import Widget
3
- from .constraints import *
4
- from typing import Self
5
-
6
- class Layout:
7
- def __init__(self, parent: Widget=None):
8
- self.parent = parent
9
- self.child_constraints : list[Constraint] = []
10
-
11
- def set_child_constraints(self,*constraints)->Self:
12
- self.child_constraints = constraints
13
- self.arrange()
14
- return self
15
-
16
- def set_parent(self,parent:Widget):
17
- self.parent = parent
18
- self.arrange()
19
-
20
- def arrange(self)->None:
21
- raise NotImplementedError("Subclasses must implement arrange method")
22
-
23
- class Column(Layout):
24
- def __init__(self,gap:int=0,shrink:bool=False):
25
- super().__init__()
26
- self.gap = gap
27
- self.shrink :bool = shrink
28
-
29
- def arrange(self)->None:
30
- if not self.parent or not self.parent.children : return
31
- if self.shrink:
32
- len_children = len(self.parent.children)
33
- parent_height = sum(c.rect.h for c in self.parent.children)
34
- parent_width = max(c.rect.w for c in self.parent.children)
35
- if self.gap : parent_height += (len_children-1) * self.gap
36
- # print(self.parent.to_string(),len_children,parent_height)
37
- c = self.parent.get_constraint("height")
38
- if not c or c.height != parent_height :
39
- self.parent.add_constraint(ConstraintHeight(parent_height))
40
- c = self.parent.get_constraint("width")
41
- if not c or c.width != parent_width :
42
- self.parent.add_constraint(ConstraintWidth(parent_width))
43
- current_y = self.parent.rect.top
44
-
45
- for child in self.parent.children:
46
- child.set_position(self.parent.rect.x,current_y)
47
- current_y += child.rect.h + self.gap
48
- for c in self.child_constraints:
49
- if not child.has_constraint(c.name):
50
- child.add_constraint(c)
51
-
52
- class Row(Layout):
53
- def __init__(self, gap: int = 0, shrink: bool = False):
54
- super().__init__()
55
- self.gap = gap
56
- self.shrink = shrink
57
-
58
- def arrange(self) -> None:
59
- if not self.parent:
60
- return
61
- if self.shrink and self.parent.children:
62
- len_children = len(self.parent.children)
63
- parent_width = sum(c.rect.w for c in self.parent.children)
64
- parent_height = max(c.rect.h for c in self.parent.children)
65
- if self.gap:
66
- parent_width += (len_children - 1) * self.gap
67
-
68
- c = self.parent.get_constraint("width")
69
- if not c or c.width != parent_width:
70
- self.parent.add_constraint(ConstraintWidth(parent_width))
71
- c = self.parent.get_constraint("height")
72
- if not c or c.height != parent_height:
73
- self.parent.add_constraint(ConstraintHeight(parent_height))
74
-
75
- current_x = self.parent.rect.left
76
- for child in self.parent.children:
77
- child.set_position(current_x,self.parent.rect.y)
78
- current_x += child.rect.w + self.gap
79
- for c in self.child_constraints:
80
- if not child.has_constraint(c.name):
81
- child.add_constraint(c)
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()
@@ -0,0 +1,155 @@
1
+ import math
2
+ import batFramework as bf
3
+ from .shape import Shape
4
+ from typing import Self
5
+ from .syncedVar import SyncedVar
6
+
7
+ def custom_top_at(self, x, y):
8
+ if Shape.top_at(self, x, y) == self:
9
+ return self.parent
10
+ return None
11
+
12
+ class Meter(Shape):
13
+ def __init__(self, min_value: float = 0, max_value: float = None, step: float = 0.1, synced_var: SyncedVar = None):
14
+ super().__init__()
15
+ self.min_value, self.max_value = min_value, max_value
16
+ self.step = step
17
+ self.snap: bool = False
18
+ self.synced_var = synced_var or SyncedVar(min_value)
19
+ if self.max_value is None:
20
+ self.max_value = self.synced_var.value
21
+ self.synced_var.bind(self, self._on_synced_var_update)
22
+ self.set_debug_color("pink")
23
+
24
+ def __str__(self) -> str:
25
+ return "Meter"
26
+
27
+ def set_snap(self,snap:bool)->Self:
28
+ self.snap = snap
29
+ self.set_value(self.get_value())
30
+ return self
31
+
32
+ def set_step(self, step: float) -> Self:
33
+ self.step = step
34
+ self.set_value(self.get_value())
35
+ return self
36
+
37
+ def set_range(self, range_min: float, range_max: float) -> Self:
38
+ if range_min >= range_max:
39
+ return self
40
+ self.min_value = range_min
41
+ self.max_value = range_max
42
+ self.dirty_shape = True
43
+ return self
44
+
45
+ def set_value(self, value: float) -> Self:
46
+ """
47
+ Sets the value of the meter and updates the synced variable.
48
+ """
49
+ value = max(self.min_value, min(self.max_value, value))
50
+ value = round(value / self.step) * self.step
51
+ value = round(value, 10)
52
+ self.synced_var.value = value # Update the synced variable
53
+ return self
54
+
55
+ def get_value(self) -> float:
56
+ """
57
+ Gets the current value from the synced variable.
58
+ """
59
+ return self.synced_var.value
60
+
61
+ def get_range(self) -> float:
62
+ return self.max_value - self.min_value
63
+
64
+ def get_ratio(self) -> float:
65
+ if self.max_value <= self.min_value:
66
+ return 0
67
+ return (self.get_value() - self.min_value) / (self.max_value - self.min_value)
68
+
69
+ def _on_synced_var_update(self, value: float) -> None:
70
+ """
71
+ Updates the meter's internal state when the synced variable changes.
72
+ """
73
+ self.dirty_shape = True
74
+
75
+ def set_synced_var(self, synced_var: SyncedVar) -> Self:
76
+ """
77
+ Rebinds the meter to a new SyncedVar.
78
+ """
79
+ if self.synced_var:
80
+ self.synced_var.unbind(self)
81
+ self.synced_var = synced_var
82
+ self.synced_var.bind(self, self._on_synced_var_update)
83
+ return self
84
+
85
+ class BarMeter(Meter):
86
+ def __init__(self, min_value = 0, max_value = None, step = 0.1, synced_var = None):
87
+ super().__init__(min_value, max_value, step, synced_var)
88
+ self.axis: bf.axis = bf.axis.HORIZONTAL
89
+ self.direction = bf.direction.RIGHT # Direction determines which side is the max range
90
+ self.content = Shape((4, 4)).set_color(bf.color.BLUE)
91
+ self.content.set_debug_color("cyan")
92
+ self.content.top_at = lambda x, y: custom_top_at(self.content, x, y)
93
+ self.add(self.content)
94
+ self.set_padding(4)
95
+ self.set_color("gray20")
96
+ self.set_outline_width(1)
97
+ self.set_outline_color(bf.color.BLACK)
98
+ self.set_debug_color("pink")
99
+
100
+ def __str__(self) -> str:
101
+ return "BarMeter"
102
+
103
+ def set_direction(self, direction: bf.direction) -> Self:
104
+ """
105
+ Sets the direction of the BarMeter.
106
+ """
107
+ self.direction = direction
108
+ if self.axis == bf.axis.HORIZONTAL and self.direction in [bf.direction.UP, bf.direction.DOWN]:
109
+ self.set_axis(bf.axis.VERTICAL)
110
+ elif self.axis == bf.axis.VERTICAL and self.direction in [bf.direction.LEFT, bf.direction.RIGHT]:
111
+ self.set_axis(bf.axis.HORIZONTAL)
112
+ self.dirty_shape = True
113
+ return self
114
+
115
+ def set_axis(self,axis:bf.axis)->Self:
116
+ self.axis = axis
117
+ if axis==bf.axis.HORIZONTAL and self.direction not in [bf.direction.LEFT,bf.direction.RIGHT]:
118
+ self.direction = bf.direction.RIGHT
119
+ elif axis == bf.axis.VERTICAL and self.direction not in [bf.direction.UP, bf.direction.DOWN]:
120
+ self.direction = bf.direction.UP
121
+
122
+ self.dirty_shape = True
123
+ return self
124
+
125
+ def _build_content(self) -> None:
126
+ padded = self.get_inner_rect()
127
+ ratio = self.get_ratio()
128
+
129
+ self.content.set_border_radius(*[round(b / 2) for b in self.border_radius])
130
+
131
+ if self.axis == bf.axis.HORIZONTAL:
132
+ width = (padded.width - self.outline_width * 2) * ratio
133
+ width = max(width,0)
134
+ self.content.set_size((width, padded.height - self.outline_width * 2))
135
+ if self.direction == bf.direction.RIGHT:
136
+ self.content.rect.topleft = padded.move(self.outline_width, self.outline_width).topleft
137
+ else: # bf.direction.LEFT
138
+ self.content.rect.topright = padded.move(-self.outline_width, self.outline_width).topright
139
+
140
+ else: # vertical
141
+ height = (padded.height - self.outline_width * 2) * ratio
142
+ height = round(height)
143
+ height = max(height,0)
144
+
145
+ self.content.set_size((padded.width - self.outline_width * 2, height))
146
+ if self.direction == bf.direction.UP:
147
+
148
+
149
+ self.content.rect.bottomleft = (padded.left + self.outline_width, padded.bottom - self.outline_width)
150
+ else: # bf.direction.DOWN
151
+ self.content.rect.topleft = padded.move(self.outline_width, self.outline_width).topleft
152
+
153
+ def build(self) -> None:
154
+ self._build_content()
155
+ super().build()