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.
- batFramework/__init__.py +84 -52
- batFramework/action.py +280 -252
- batFramework/actionContainer.py +105 -38
- batFramework/animatedSprite.py +81 -117
- batFramework/animation.py +91 -0
- batFramework/audioManager.py +156 -85
- batFramework/baseScene.py +249 -0
- batFramework/camera.py +245 -123
- batFramework/constants.py +57 -75
- batFramework/cutscene.py +239 -119
- batFramework/cutsceneManager.py +34 -0
- batFramework/drawable.py +107 -0
- batFramework/dynamicEntity.py +30 -23
- batFramework/easingController.py +58 -0
- batFramework/entity.py +130 -123
- batFramework/enums.py +171 -0
- batFramework/fontManager.py +65 -0
- batFramework/gui/__init__.py +28 -14
- batFramework/gui/animatedLabel.py +90 -0
- batFramework/gui/button.py +18 -84
- batFramework/gui/clickableWidget.py +244 -0
- batFramework/gui/collapseContainer.py +98 -0
- batFramework/gui/constraints/__init__.py +1 -0
- batFramework/gui/constraints/constraints.py +1066 -0
- batFramework/gui/container.py +220 -49
- batFramework/gui/debugger.py +140 -47
- batFramework/gui/draggableWidget.py +63 -0
- batFramework/gui/image.py +61 -23
- batFramework/gui/indicator.py +116 -40
- batFramework/gui/interactiveWidget.py +243 -22
- batFramework/gui/label.py +147 -110
- batFramework/gui/layout.py +442 -81
- batFramework/gui/meter.py +155 -0
- batFramework/gui/radioButton.py +43 -0
- batFramework/gui/root.py +228 -60
- batFramework/gui/scrollingContainer.py +282 -0
- batFramework/gui/selector.py +232 -0
- batFramework/gui/shape.py +286 -86
- batFramework/gui/slider.py +353 -0
- batFramework/gui/style.py +10 -0
- batFramework/gui/styleManager.py +49 -0
- batFramework/gui/syncedVar.py +43 -0
- batFramework/gui/textInput.py +331 -0
- batFramework/gui/textWidget.py +308 -0
- batFramework/gui/toggle.py +140 -62
- batFramework/gui/tooltip.py +35 -0
- batFramework/gui/widget.py +546 -307
- batFramework/manager.py +131 -50
- batFramework/particle.py +118 -0
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +34 -0
- batFramework/resourceManager.py +130 -0
- batFramework/scene.py +31 -226
- batFramework/sceneLayer.py +134 -0
- batFramework/sceneManager.py +200 -165
- batFramework/scrollingSprite.py +115 -0
- batFramework/sprite.py +46 -0
- batFramework/stateMachine.py +49 -51
- batFramework/templates/__init__.py +2 -0
- batFramework/templates/character.py +15 -0
- batFramework/templates/controller.py +158 -0
- batFramework/templates/stateMachine.py +39 -0
- batFramework/tileset.py +46 -0
- batFramework/timeManager.py +213 -0
- batFramework/transition.py +162 -157
- batFramework/triggerZone.py +22 -22
- batFramework/utils.py +306 -184
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/LICENSE +20 -20
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/METADATA +7 -2
- batframework-2.0.0a1.dist-info/RECORD +72 -0
- batFramework/cutsceneBlocks.py +0 -176
- batFramework/debugger.py +0 -48
- batFramework/easing.py +0 -71
- batFramework/gui/constraints.py +0 -204
- batFramework/gui/frame.py +0 -19
- batFramework/particles.py +0 -77
- batFramework/time.py +0 -75
- batFramework/transitionManager.py +0 -0
- batframework-1.1.0.dist-info/RECORD +0 -43
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/WHEEL +0 -0
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/top_level.txt +0 -0
batFramework/gui/layout.py
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
self.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
self.
|
27
|
-
self.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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()
|