batframework 1.0.8a8__py3-none-any.whl → 1.0.8a10__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 +68 -51
- batFramework/action.py +126 -99
- batFramework/actionContainer.py +53 -9
- batFramework/animatedSprite.py +141 -82
- batFramework/audioManager.py +69 -26
- batFramework/camera.py +259 -69
- batFramework/character.py +27 -0
- batFramework/constants.py +16 -54
- batFramework/cutscene.py +39 -29
- batFramework/cutsceneBlocks.py +36 -43
- batFramework/dynamicEntity.py +18 -9
- batFramework/easingController.py +58 -0
- batFramework/entity.py +48 -97
- batFramework/enums.py +113 -0
- batFramework/fontManager.py +65 -0
- batFramework/gui/__init__.py +10 -2
- batFramework/gui/button.py +9 -78
- batFramework/gui/clickableWidget.py +220 -0
- batFramework/gui/constraints/__init__.py +1 -0
- batFramework/gui/constraints/constraints.py +815 -0
- batFramework/gui/container.py +174 -32
- batFramework/gui/debugger.py +131 -43
- batFramework/gui/dialogueBox.py +99 -0
- batFramework/gui/draggableWidget.py +40 -0
- batFramework/gui/image.py +56 -20
- batFramework/gui/indicator.py +38 -21
- batFramework/gui/interactiveWidget.py +192 -13
- batFramework/gui/label.py +309 -74
- batFramework/gui/layout.py +231 -63
- batFramework/gui/meter.py +74 -0
- batFramework/gui/radioButton.py +84 -0
- batFramework/gui/root.py +134 -38
- batFramework/gui/shape.py +237 -57
- batFramework/gui/slider.py +240 -0
- batFramework/gui/style.py +10 -0
- batFramework/gui/styleManager.py +48 -0
- batFramework/gui/textInput.py +247 -0
- batFramework/gui/toggle.py +101 -51
- batFramework/gui/widget.py +358 -250
- batFramework/manager.py +52 -19
- batFramework/object.py +123 -0
- batFramework/particle.py +115 -0
- batFramework/renderGroup.py +67 -0
- batFramework/resourceManager.py +100 -0
- batFramework/scene.py +281 -123
- batFramework/sceneManager.py +178 -116
- batFramework/scrollingSprite.py +114 -0
- batFramework/sprite.py +51 -0
- batFramework/stateMachine.py +11 -8
- batFramework/templates/__init__.py +2 -0
- batFramework/templates/character.py +44 -0
- batFramework/templates/states.py +166 -0
- batFramework/tileset.py +46 -0
- batFramework/time.py +145 -58
- batFramework/transition.py +195 -124
- batFramework/triggerZone.py +1 -1
- batFramework/utils.py +112 -147
- batframework-1.0.8a10.dist-info/LICENCE +21 -0
- batframework-1.0.8a10.dist-info/METADATA +43 -0
- batframework-1.0.8a10.dist-info/RECORD +62 -0
- 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/transitionManager.py +0 -0
- batframework-1.0.8a8.dist-info/METADATA +0 -53
- batframework-1.0.8a8.dist-info/RECORD +0 -42
- {batframework-1.0.8a8.dist-info → batframework-1.0.8a10.dist-info}/WHEEL +0 -0
- {batframework-1.0.8a8.dist-info → batframework-1.0.8a10.dist-info}/top_level.txt +0 -0
batFramework/gui/layout.py
CHANGED
@@ -1,81 +1,249 @@
|
|
1
1
|
import batFramework as bf
|
2
2
|
from .widget import Widget
|
3
|
-
from .constraints import *
|
4
|
-
from typing import Self
|
3
|
+
from .constraints.constraints import *
|
4
|
+
from typing import Self, TYPE_CHECKING,override
|
5
|
+
from abc import ABC,abstractmethod
|
6
|
+
import pygame
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from .container import Container
|
10
|
+
|
11
|
+
|
12
|
+
class Layout(ABC):
|
13
|
+
def __init__(self, parent: "Container" = None):
|
8
14
|
self.parent = parent
|
9
|
-
self.child_constraints
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
self.
|
15
|
+
self.child_constraints: list[Constraint] = []
|
16
|
+
self.children_rect = pygame.FRect(0, 0, 0, 0)
|
17
|
+
|
18
|
+
def set_child_constraints(self, *constraints) -> Self:
|
19
|
+
self.child_constraints = list(constraints)
|
20
|
+
self.notify_parent()
|
14
21
|
return self
|
15
|
-
|
16
|
-
def set_parent(self,parent:Widget):
|
22
|
+
|
23
|
+
def set_parent(self, parent: Widget):
|
17
24
|
self.parent = parent
|
18
|
-
self.
|
25
|
+
self.notify_parent()
|
26
|
+
|
27
|
+
def notify_parent(self) -> None:
|
28
|
+
if self.parent:
|
29
|
+
self.parent.dirty_children = True
|
30
|
+
|
31
|
+
def arrange(self) -> None:
|
32
|
+
return
|
33
|
+
|
34
|
+
def get_raw_size(self) -> tuple[float, float]:
|
35
|
+
"""
|
36
|
+
Returns the supposed size the container should have to encapsulate perfectly all of its widgets
|
37
|
+
"""
|
38
|
+
return self.parent.rect.size if self.parent else (0, 0)
|
39
|
+
|
40
|
+
def get_auto_size(self) -> tuple[float, float]:
|
41
|
+
"""
|
42
|
+
Returns the final size the container should have (while keeping the the width and height if they are non-resizable)
|
43
|
+
"""
|
44
|
+
target_size = list(self.get_raw_size())
|
45
|
+
if not self.parent.autoresize_w:
|
46
|
+
target_size[0] = self.parent.rect.w
|
47
|
+
if not self.parent.autoresize_h:
|
48
|
+
target_size[1] = self.parent.rect.h
|
49
|
+
return target_size
|
50
|
+
|
51
|
+
def focus_next_child(self) -> None:
|
52
|
+
pass
|
53
|
+
|
54
|
+
def focus_prev_child(self) -> None:
|
55
|
+
pass
|
56
|
+
|
57
|
+
def scroll_to_widget(self, widget: Widget) -> None:
|
58
|
+
padded = self.parent.get_padded_rect()
|
59
|
+
r = widget.rect
|
60
|
+
if padded.contains(r):
|
61
|
+
return
|
62
|
+
clamped = r.clamp(padded)
|
63
|
+
dx, dy = clamped.move(-r.x, -r.y).topleft
|
64
|
+
self.parent.scroll_by((-dx, -dy))
|
65
|
+
|
66
|
+
def handle_event(self, event):
|
67
|
+
pass
|
68
|
+
|
19
69
|
|
20
|
-
|
21
|
-
|
70
|
+
class SingleAxisLayout(Layout):
|
71
|
+
def focus_next_child(self) -> None:
|
72
|
+
l = self.parent.get_interactive_children()
|
73
|
+
self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
|
74
|
+
focused = l[self.parent.focused_index]
|
75
|
+
focused.get_focus()
|
76
|
+
self.scroll_to_widget(focused)
|
22
77
|
|
23
|
-
|
24
|
-
|
78
|
+
def focus_prev_child(self) -> None:
|
79
|
+
l = self.parent.get_interactive_children()
|
80
|
+
self.parent.focused_index = max(self.parent.focused_index - 1, 0)
|
81
|
+
focused = l[self.parent.focused_index]
|
82
|
+
focused.get_focus()
|
83
|
+
self.scroll_to_widget(focused)
|
84
|
+
|
85
|
+
|
86
|
+
class Column(SingleAxisLayout):
|
87
|
+
def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
|
25
88
|
super().__init__()
|
26
89
|
self.gap = gap
|
27
|
-
self.
|
28
|
-
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
90
|
+
self.spacing = spacing
|
91
|
+
|
92
|
+
def set_gap(self, value: float) -> Self:
|
93
|
+
self.gap = value
|
94
|
+
self.notify_parent()
|
95
|
+
return self
|
96
|
+
|
97
|
+
def set_spacing(self, spacing: bf.spacing) -> Self:
|
98
|
+
self.spacing = spacing
|
99
|
+
self.notify_parent()
|
100
|
+
return self
|
44
101
|
|
102
|
+
def get_raw_size(self) -> tuple[float, float]:
|
103
|
+
len_children = len(self.parent.children)
|
104
|
+
if not len_children:
|
105
|
+
return self.parent.rect.size
|
106
|
+
parent_height = sum(c.get_min_required_size()[1] for c in self.parent.children)
|
107
|
+
parent_width = max(c.get_min_required_size()[0] for c in self.parent.children)
|
108
|
+
if self.gap:
|
109
|
+
parent_height += (len_children - 1) * self.gap
|
110
|
+
target_rect = self.parent.inflate_rect_by_padding(
|
111
|
+
(0, 0, parent_width, parent_height)
|
112
|
+
)
|
113
|
+
return target_rect.size
|
114
|
+
|
115
|
+
def get_auto_size(self) -> tuple[float, float]:
|
116
|
+
target_size = list(self.get_raw_size())
|
117
|
+
if not self.parent.autoresize_w:
|
118
|
+
target_size[0] = self.parent.rect.w
|
119
|
+
if not self.parent.autoresize_h:
|
120
|
+
target_size[1] = self.parent.rect.h
|
121
|
+
return target_size
|
122
|
+
|
123
|
+
def arrange(self) -> None:
|
124
|
+
if not self.parent or not self.parent.children:
|
125
|
+
return
|
126
|
+
if self.child_constraints:
|
127
|
+
for child in self.parent.children:
|
128
|
+
child.add_constraints(*self.child_constraints)
|
129
|
+
self.child_rect = self.parent.get_padded_rect()
|
130
|
+
|
131
|
+
if self.parent.autoresize_w or self.parent.autoresize_h:
|
132
|
+
width, height = self.get_auto_size()
|
133
|
+
if self.parent.rect.size != (width, height):
|
134
|
+
self.parent.set_size((width, height))
|
135
|
+
self.parent.build()
|
136
|
+
self.arrange()
|
137
|
+
return
|
138
|
+
self.child_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
|
139
|
+
y = self.child_rect.top
|
45
140
|
for child in self.parent.children:
|
46
|
-
child.set_position(self.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
141
|
+
child.set_position(self.child_rect.x, y)
|
142
|
+
y += child.get_min_required_size()[1] + self.gap
|
143
|
+
|
144
|
+
def handle_event(self, event):
|
145
|
+
if self.parent.autoresize_h or not self.parent.visible:
|
146
|
+
return
|
147
|
+
|
148
|
+
if not self.parent.children:
|
149
|
+
return
|
150
|
+
|
151
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
152
|
+
r = self.parent.get_root()
|
153
|
+
if not r:
|
154
|
+
return
|
155
|
+
|
156
|
+
if self.parent.rect.collidepoint(
|
157
|
+
r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
|
158
|
+
):
|
159
|
+
if event.button == 4:
|
160
|
+
self.parent.scroll_by((0, -10))
|
161
|
+
elif event.button == 5:
|
162
|
+
self.parent.scroll_by((0, 10))
|
163
|
+
else:
|
164
|
+
return
|
165
|
+
event.consumed = True
|
166
|
+
self.parent.clamp_scroll()
|
167
|
+
|
168
|
+
|
169
|
+
class Row(SingleAxisLayout):
|
170
|
+
def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
|
54
171
|
super().__init__()
|
55
172
|
self.gap = gap
|
56
|
-
self.
|
173
|
+
self.spacing = spacing
|
174
|
+
|
175
|
+
def set_gap(self, value: float) -> Self:
|
176
|
+
self.gap = value
|
177
|
+
self.notify_parent()
|
178
|
+
return self
|
179
|
+
|
180
|
+
def set_spacing(self, spacing: bf.spacing) -> Self:
|
181
|
+
self.spacing = spacing
|
182
|
+
self.notify_parent()
|
183
|
+
return self
|
184
|
+
|
185
|
+
def get_raw_size(self) -> tuple[float, float]:
|
186
|
+
len_children = len(self.parent.children)
|
187
|
+
if not len_children:
|
188
|
+
return self.parent.rect.size
|
189
|
+
parent_width = sum(c.get_min_required_size()[0] for c in self.parent.children)
|
190
|
+
parent_height = max(c.get_min_required_size()[1] for c in self.parent.children)
|
191
|
+
if self.gap:
|
192
|
+
parent_width += (len_children - 1) * self.gap
|
193
|
+
target_rect = self.parent.inflate_rect_by_padding(
|
194
|
+
(0, 0, parent_width, parent_height)
|
195
|
+
)
|
196
|
+
|
197
|
+
return target_rect.size
|
198
|
+
|
199
|
+
def get_auto_size(self) -> tuple[float, float]:
|
200
|
+
target_size = list(self.get_raw_size())
|
201
|
+
if not self.parent.autoresize_w:
|
202
|
+
target_size[0] = self.parent.rect.w
|
203
|
+
if not self.parent.autoresize_h:
|
204
|
+
target_size[1] = self.parent.rect.h
|
205
|
+
return target_size
|
57
206
|
|
58
207
|
def arrange(self) -> None:
|
59
|
-
if not self.parent:
|
208
|
+
if not self.parent or not self.parent.children:
|
60
209
|
return
|
61
|
-
if self.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
self.parent.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
current_x = self.parent.rect.left
|
210
|
+
if self.child_constraints:
|
211
|
+
for child in self.parent.children:
|
212
|
+
child.add_constraints(*self.child_constraints)
|
213
|
+
self.child_rect = self.parent.get_padded_rect()
|
214
|
+
|
215
|
+
if self.parent.autoresize_w or self.parent.autoresize_h:
|
216
|
+
width, height = self.get_auto_size()
|
217
|
+
if self.parent.rect.size != (width, height):
|
218
|
+
self.parent.set_size((width, height))
|
219
|
+
self.parent.build()
|
220
|
+
self.arrange()
|
221
|
+
return
|
222
|
+
self.child_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
|
223
|
+
x = self.child_rect.left
|
76
224
|
for child in self.parent.children:
|
77
|
-
child.set_position(
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
225
|
+
child.set_position(x, self.child_rect.y)
|
226
|
+
x += child.get_min_required_size()[0] + self.gap
|
227
|
+
|
228
|
+
def handle_event(self, event):
|
229
|
+
if self.parent.autoresize_w or not self.parent.visible:
|
230
|
+
return
|
231
|
+
|
232
|
+
if not self.parent.children:
|
233
|
+
return
|
234
|
+
|
235
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
236
|
+
r = self.parent.get_root()
|
237
|
+
if not r:
|
238
|
+
return
|
239
|
+
if self.parent.rect.collidepoint(
|
240
|
+
r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
|
241
|
+
):
|
242
|
+
if event.button == 4:
|
243
|
+
self.parent.scroll_by((-10, 0))
|
244
|
+
elif event.button == 5:
|
245
|
+
self.parent.scroll_by((10, 0))
|
246
|
+
else:
|
247
|
+
return
|
248
|
+
event.consumed = True
|
249
|
+
self.parent.clamp_scroll()
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import batFramework as bf
|
2
|
+
from .shape import Shape
|
3
|
+
from typing import Self
|
4
|
+
|
5
|
+
|
6
|
+
def custom_top_at(self, x, y):
|
7
|
+
if Shape.top_at(self, x, y) == self:
|
8
|
+
return self.parent
|
9
|
+
return None
|
10
|
+
|
11
|
+
|
12
|
+
class Meter(Shape):
|
13
|
+
def __init__(self, min_value: float = 0, max_value: float = 1, step: float = 0.1):
|
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.value = self.max_value
|
19
|
+
self.content = Shape((0, 0)).set_color(bf.color.BLUE)
|
20
|
+
self.content.set_debug_color("cyan")
|
21
|
+
self.content.top_at = lambda x, y: custom_top_at(self.content, x, y)
|
22
|
+
self.add(self.content)
|
23
|
+
self.set_padding(4)
|
24
|
+
self.set_color("gray20")
|
25
|
+
self.set_outline_width(1)
|
26
|
+
self.set_outline_color(bf.color.BLACK)
|
27
|
+
self.set_debug_color("pink")
|
28
|
+
|
29
|
+
def __str__(self) -> str:
|
30
|
+
return "Meter"
|
31
|
+
|
32
|
+
def set_step(self, step: float) -> Self:
|
33
|
+
self.step = step
|
34
|
+
self.set_value(self.value)
|
35
|
+
return self
|
36
|
+
|
37
|
+
def set_range(self, range_min: float, range_max: float) -> Self:
|
38
|
+
if range_min >= range_max:
|
39
|
+
print(
|
40
|
+
f"[Warning] : minimum value {range_min} is greater than or equal to maximum value {range_max}"
|
41
|
+
)
|
42
|
+
return self
|
43
|
+
self.min_value = range_min
|
44
|
+
self.max_value = range_max
|
45
|
+
self.dirty_shape = True
|
46
|
+
|
47
|
+
def get_debug_outlines(self):
|
48
|
+
yield from super().get_debug_outlines()
|
49
|
+
# yield from self.content.get_debug_outlines()
|
50
|
+
|
51
|
+
def set_value(self, value: float) -> Self:
|
52
|
+
value = max(self.min_value, min(self.max_value, value))
|
53
|
+
value = round(value / self.step) * self.step
|
54
|
+
self.value = round(value,10)
|
55
|
+
self.dirty_shape = True
|
56
|
+
return self
|
57
|
+
|
58
|
+
def get_value(self) -> float:
|
59
|
+
return self.value
|
60
|
+
|
61
|
+
def get_range(self) -> float:
|
62
|
+
return self.max_value - self.min_value
|
63
|
+
|
64
|
+
def get_ratio(self) -> float:
|
65
|
+
return (self.value-self.min_value) / (self.max_value - self.min_value)
|
66
|
+
|
67
|
+
def _build_content(self) -> None:
|
68
|
+
width = self.get_padded_width() * self.get_ratio()
|
69
|
+
self.content.set_size((width, self.get_padded_height()))
|
70
|
+
self.content.rect.topleft = self.get_padded_rect().topleft
|
71
|
+
|
72
|
+
def build(self) -> None:
|
73
|
+
super().build()
|
74
|
+
self._build_content()
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import batFramework as bf
|
2
|
+
from typing import Self, Any, Callable
|
3
|
+
from .toggle import Toggle
|
4
|
+
|
5
|
+
|
6
|
+
class RadioVariable: ...
|
7
|
+
|
8
|
+
|
9
|
+
class RadioButton(Toggle):
|
10
|
+
def __init__(self, text: str = "", radio_value: Any = None) -> None:
|
11
|
+
super().__init__(text, None, False)
|
12
|
+
self.radio_value: Any = (
|
13
|
+
radio_value if radio_value is not None else text if text else None
|
14
|
+
)
|
15
|
+
self.radio_variable: RadioVariable = None
|
16
|
+
|
17
|
+
def __str__(self) -> str:
|
18
|
+
return (
|
19
|
+
f"RadioButton({self.radio_value}|{'Active' if self.value else 'Inactive'})"
|
20
|
+
)
|
21
|
+
|
22
|
+
def set_radio_value(self, value: Any) -> Self:
|
23
|
+
self.radio_value = value
|
24
|
+
|
25
|
+
if self.radio_variable:
|
26
|
+
self.radio_variable.update_buttons()
|
27
|
+
return self
|
28
|
+
|
29
|
+
def set_text(self, text: str) -> Self:
|
30
|
+
flag = False
|
31
|
+
if self.value == self.text or self.value is None:
|
32
|
+
flag = True
|
33
|
+
super().set_text(text)
|
34
|
+
if flag:
|
35
|
+
self.set_radio_value(self.text)
|
36
|
+
return self
|
37
|
+
|
38
|
+
def click(self) -> None:
|
39
|
+
if self.radio_variable is None:
|
40
|
+
return
|
41
|
+
self.radio_variable.set_value(self.radio_value)
|
42
|
+
|
43
|
+
|
44
|
+
class RadioVariable:
|
45
|
+
def __init__(self) -> None:
|
46
|
+
self.buttons: list[RadioButton] = []
|
47
|
+
self.value = None
|
48
|
+
self.modify_callback: Callable[[Any],] = None
|
49
|
+
|
50
|
+
def set_modify_callback(self, callback) -> Self:
|
51
|
+
self.modify_callback = callback
|
52
|
+
return self
|
53
|
+
|
54
|
+
def link(self, *buttons: RadioButton) -> Self:
|
55
|
+
if not buttons:
|
56
|
+
return self
|
57
|
+
for b in buttons:
|
58
|
+
b.radio_variable = self
|
59
|
+
self.buttons.extend(buttons)
|
60
|
+
|
61
|
+
if self.value is None:
|
62
|
+
self.set_value(buttons[0].radio_value)
|
63
|
+
else:
|
64
|
+
self.update_buttons()
|
65
|
+
return self
|
66
|
+
|
67
|
+
def unlink(self, *buttons) -> Self:
|
68
|
+
for b in self.buttons:
|
69
|
+
if b in buttons:
|
70
|
+
b.radio_variable = None
|
71
|
+
self.buttons = [b for b in self.buttons if b not in buttons]
|
72
|
+
return self
|
73
|
+
|
74
|
+
def set_value(self, value: Any) -> Self:
|
75
|
+
if value == self.value:
|
76
|
+
return
|
77
|
+
self.value = value
|
78
|
+
if self.modify_callback:
|
79
|
+
self.modify_callback(value)
|
80
|
+
self.update_buttons()
|
81
|
+
return self
|
82
|
+
|
83
|
+
def update_buttons(self):
|
84
|
+
_ = [b.set_value(b.radio_value == self.value, False) for b in self.buttons]
|
batFramework/gui/root.py
CHANGED
@@ -2,59 +2,155 @@ import batFramework as bf
|
|
2
2
|
from .interactiveWidget import InteractiveWidget
|
3
3
|
from .widget import Widget
|
4
4
|
import pygame
|
5
|
+
from typing import Self
|
6
|
+
import sys
|
7
|
+
|
5
8
|
|
6
9
|
class Root(InteractiveWidget):
|
7
|
-
def __init__(self):
|
10
|
+
def __init__(self, camera) -> None:
|
8
11
|
super().__init__()
|
9
|
-
self.
|
10
|
-
self.
|
12
|
+
self.is_root = True
|
13
|
+
self.drawing_camera: bf.Camera = camera
|
14
|
+
self.visible = False
|
11
15
|
self.rect.size = pygame.display.get_surface().get_size()
|
12
|
-
self.focused
|
13
|
-
self.hovered
|
14
|
-
self.set_debug_color("
|
16
|
+
self.focused: InteractiveWidget | None = self
|
17
|
+
self.hovered: Widget | None = self
|
18
|
+
self.set_debug_color("yellow")
|
19
|
+
self.set_render_order(sys.maxsize)
|
20
|
+
self.clip_children = False
|
21
|
+
|
22
|
+
def __str__(self) -> str:
|
23
|
+
return "Root"
|
15
24
|
|
16
|
-
def
|
17
|
-
|
25
|
+
def set_parent_scene(self, parent_scene: bf.Scene) -> Self:
|
26
|
+
bf.StyleManager().register_widget(self)
|
27
|
+
return super().set_parent_scene(parent_scene)
|
18
28
|
|
19
|
-
def get_focused(self)->Widget|None:
|
29
|
+
def get_focused(self) -> Widget | None:
|
20
30
|
return self.focused
|
21
31
|
|
22
|
-
def get_hovered(self)->Widget|None:
|
32
|
+
def get_hovered(self) -> Widget | None:
|
23
33
|
return self.hovered
|
24
|
-
|
25
|
-
def to_string_id(self)->str:
|
26
|
-
return "ROOT"
|
27
34
|
|
28
|
-
def
|
29
|
-
|
35
|
+
def clear_focused(self) -> None:
|
36
|
+
self.focus_on(None)
|
37
|
+
|
38
|
+
def clear_hovered(self) -> None:
|
39
|
+
if isinstance(self.hovered, InteractiveWidget):
|
40
|
+
self.hovered.on_exit()
|
41
|
+
self.hovered = None
|
42
|
+
|
43
|
+
def get_debug_outlines(self):
|
44
|
+
yield (self.rect, self.debug_color)
|
45
|
+
for child in self.children:
|
46
|
+
yield from child.get_debug_outlines()
|
47
|
+
|
48
|
+
def focus_on(self, widget: InteractiveWidget | None) -> None:
|
49
|
+
if widget == self.focused:
|
50
|
+
return
|
51
|
+
if widget and not widget.allow_focus_to_self():
|
52
|
+
return
|
53
|
+
if self.focused is not None:
|
30
54
|
self.focused.on_lose_focus()
|
31
|
-
if widget is None
|
55
|
+
if widget is None:
|
32
56
|
self.focused = self
|
33
57
|
return
|
34
|
-
self.focused= widget
|
58
|
+
self.focused = widget
|
35
59
|
self.focused.on_get_focus()
|
36
60
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
61
|
+
def get_by_tags(self, *tags) -> list[Widget]:
|
62
|
+
res = []
|
63
|
+
|
64
|
+
def getter(w: Widget):
|
65
|
+
nonlocal res
|
66
|
+
if any(t in w.tags for t in tags):
|
67
|
+
res.append(w)
|
68
|
+
|
69
|
+
self.visit(getter)
|
70
|
+
return res
|
71
|
+
|
72
|
+
def focus_next_tab(self, widget):
|
73
|
+
return True
|
74
|
+
|
75
|
+
def focus_prev_tab(self, widget):
|
76
|
+
return True
|
77
|
+
|
78
|
+
def get_by_uid(self, uid: int) -> "Widget":
|
79
|
+
def helper(w: "Widget", uid: int) -> "Widget":
|
80
|
+
if w.uid == uid:
|
81
|
+
return w
|
82
|
+
for child in w.children:
|
83
|
+
res = helper(child, uid)
|
84
|
+
if res is not None:
|
85
|
+
return res
|
86
|
+
return None
|
87
|
+
|
88
|
+
return helper(self, uid)
|
89
|
+
|
90
|
+
def set_size(self, size: tuple[float, float], force: bool = False) -> "Root":
|
91
|
+
if not force:
|
92
|
+
return self
|
93
|
+
self.rect.size = size
|
94
|
+
# self.build(resolve_constraints=True)
|
41
95
|
return self
|
42
|
-
|
43
|
-
def build(self,apply_constraints:bool=False)->None:
|
44
|
-
if apply_constraints : self.apply_all_constraints()
|
45
|
-
for child in self.children :
|
46
|
-
child.build()
|
47
|
-
|
48
|
-
def do_handle_event(self,event):
|
49
|
-
if event.type == pygame.VIDEORESIZE:
|
50
|
-
self.set_size(event.w,event.h,force=True)
|
51
|
-
return True
|
52
|
-
return False
|
53
|
-
|
54
|
-
def update(self,dt:float)->None:
|
55
|
-
super().update(dt)
|
56
|
-
self.hovered = self.top_at(*pygame.mouse.get_pos()) if self.top_at(*pygame.mouse.get_pos()) else None
|
57
|
-
|
58
96
|
|
97
|
+
def do_handle_event(self, event):
|
98
|
+
if not self.parent_scene.get_sharedVar("player_has_control"):
|
99
|
+
return
|
100
|
+
if self.focused:
|
101
|
+
if event.type == pygame.KEYDOWN:
|
102
|
+
if self.focused.on_key_down(event.key):
|
103
|
+
event.consumed = True
|
104
|
+
elif event.type == pygame.KEYUP:
|
105
|
+
if self.focused.on_key_up(event.key):
|
106
|
+
event.consumed = True
|
107
|
+
|
108
|
+
if not self.hovered or (not isinstance(self.hovered, InteractiveWidget)):
|
109
|
+
return
|
110
|
+
|
111
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
112
|
+
if self.hovered.on_click_down(event.button):
|
113
|
+
event.consumed = True
|
114
|
+
|
115
|
+
elif event.type == pygame.MOUSEBUTTONUP:
|
116
|
+
if self.hovered.on_click_up(event.button):
|
117
|
+
event.consumed = True
|
118
|
+
|
119
|
+
def do_on_click_down(self, button: int) -> None:
|
120
|
+
if button == 1:
|
121
|
+
self.clear_focused()
|
59
122
|
|
60
|
-
|
123
|
+
def top_at(self, x: float | int, y: float | int) -> "None|Widget":
|
124
|
+
if self.children:
|
125
|
+
for child in reversed(self.children):
|
126
|
+
r = child.top_at(x, y)
|
127
|
+
if r is not None:
|
128
|
+
return r
|
129
|
+
return self if self.rect.collidepoint(x, y) else None
|
130
|
+
|
131
|
+
def update(self, dt: float) -> None:
|
132
|
+
super().update(dt)
|
133
|
+
if not self.parent_scene.get_sharedVar("player_has_control",True):
|
134
|
+
return
|
135
|
+
old = self.hovered
|
136
|
+
transposed = self.drawing_camera.screen_to_world(pygame.mouse.get_pos())
|
137
|
+
self.hovered = self.top_at(*transposed) if self.top_at(*transposed) else None
|
138
|
+
if old == self.hovered and isinstance(self.hovered, InteractiveWidget):
|
139
|
+
self.hovered.on_mouse_motion(*transposed)
|
140
|
+
return
|
141
|
+
if old and isinstance(old, InteractiveWidget):
|
142
|
+
old.on_exit()
|
143
|
+
if self.hovered and isinstance(self.hovered, InteractiveWidget):
|
144
|
+
self.hovered.on_enter()
|
145
|
+
|
146
|
+
def draw(self, camera: bf.Camera) -> None:
|
147
|
+
super().draw(camera)
|
148
|
+
if not self.parent_scene.get_sharedVar("player_has_control"):
|
149
|
+
return
|
150
|
+
if (
|
151
|
+
self.parent_scene
|
152
|
+
and self.parent_scene.active
|
153
|
+
and self.focused
|
154
|
+
and self.focused != self
|
155
|
+
):
|
156
|
+
self.focused.draw_focused(camera)
|