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