batframework 1.0.9a8__py3-none-any.whl → 1.0.9a10__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/constants.py +0 -1
- batFramework/gui/__init__.py +3 -2
- batFramework/gui/clickableWidget.py +32 -31
- batFramework/gui/container.py +9 -14
- batFramework/gui/draggableWidget.py +19 -12
- batFramework/gui/indicator.py +8 -25
- batFramework/gui/interactiveWidget.py +27 -25
- batFramework/gui/label.py +2 -3
- batFramework/gui/layout.py +108 -36
- batFramework/gui/meter.py +37 -29
- batFramework/gui/radioButton.py +20 -68
- batFramework/gui/root.py +5 -5
- batFramework/gui/selector.py +17 -24
- batFramework/gui/shape.py +1 -1
- batFramework/gui/slider.py +76 -88
- batFramework/gui/syncedVar.py +49 -0
- batFramework/gui/textInput.py +1 -1
- batFramework/gui/toggle.py +7 -7
- batFramework/gui/widget.py +25 -16
- batFramework/sceneLayer.py +1 -1
- batFramework/utils.py +6 -65
- {batframework-1.0.9a8.dist-info → batframework-1.0.9a10.dist-info}/METADATA +1 -1
- {batframework-1.0.9a8.dist-info → batframework-1.0.9a10.dist-info}/RECORD +26 -25
- {batframework-1.0.9a8.dist-info → batframework-1.0.9a10.dist-info}/LICENSE +0 -0
- {batframework-1.0.9a8.dist-info → batframework-1.0.9a10.dist-info}/WHEEL +0 -0
- {batframework-1.0.9a8.dist-info → batframework-1.0.9a10.dist-info}/top_level.txt +0 -0
batFramework/gui/shape.py
CHANGED
@@ -27,7 +27,7 @@ class Shape(Widget):
|
|
27
27
|
def get_inner_top(self) -> float:
|
28
28
|
return self.rect.y + self.padding[1]
|
29
29
|
|
30
|
-
def
|
30
|
+
def get_local_inner_rect(self)->pygame.FRect:
|
31
31
|
return pygame.FRect(
|
32
32
|
self.padding[0],
|
33
33
|
self.padding[1],
|
batFramework/gui/slider.py
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
import batFramework as bf
|
2
|
-
from .meter import
|
2
|
+
from .meter import BarMeter
|
3
3
|
from .button import Button
|
4
4
|
from .indicator import *
|
5
|
-
from .meter import
|
5
|
+
from .meter import BarMeter
|
6
6
|
from .shape import Shape
|
7
7
|
from .interactiveWidget import InteractiveWidget
|
8
8
|
|
9
|
-
|
10
9
|
class SliderHandle(Indicator, DraggableWidget):
|
11
10
|
def __init__(self):
|
12
11
|
super().__init__()
|
13
12
|
self.set_color(bf.color.CLOUD_SHADE)
|
14
13
|
self.old_key_repeat: tuple = (0, 0)
|
15
14
|
self.parent : bf.ClickableWidget = self.parent
|
15
|
+
self.set_click_mask(1)
|
16
16
|
def __str__(self) -> str:
|
17
17
|
return "SliderHandle"
|
18
18
|
|
19
19
|
def on_click_down(self, button: int) -> bool:
|
20
|
-
if not self.parent.is_enabled
|
21
|
-
return False
|
22
|
-
super().on_click_down(button)
|
23
|
-
if button == 1:
|
24
|
-
self.parent.get_focus()
|
20
|
+
if not self.parent.is_enabled:
|
25
21
|
return True
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
22
|
+
res = super().on_click_down(button)
|
23
|
+
if res :
|
24
|
+
self.parent.get_focus()
|
25
|
+
return res
|
26
|
+
|
27
|
+
def on_exit(self):
|
28
|
+
before = self.is_clicked_down
|
29
|
+
super().on_exit()
|
30
|
+
self.is_clicked_down = before
|
31
|
+
|
32
32
|
def do_on_drag(
|
33
33
|
self, drag_start: tuple[float, float], drag_end: tuple[float, float]
|
34
34
|
) -> None:
|
35
|
-
if not self.parent.is_enabled
|
35
|
+
if not self.parent.is_enabled:
|
36
36
|
return
|
37
37
|
super().do_on_drag(drag_start, drag_end)
|
38
|
-
m:
|
38
|
+
m: BarMeter = self.parent.meter
|
39
39
|
r = m.get_inner_rect()
|
40
40
|
|
41
41
|
position = self.rect.centerx if self.parent.axis == bf.axis.HORIZONTAL else self.rect.centery
|
@@ -51,8 +51,7 @@ class SliderHandle(Indicator, DraggableWidget):
|
|
51
51
|
def top_at(self, x, y):
|
52
52
|
return Widget.top_at(self, x, y)
|
53
53
|
|
54
|
-
|
55
|
-
class SliderMeter(Meter, InteractiveWidget):
|
54
|
+
class SliderMeter(BarMeter, InteractiveWidget):
|
56
55
|
def __str__(self) -> str:
|
57
56
|
return "SliderMeter"
|
58
57
|
|
@@ -68,29 +67,25 @@ class SliderMeter(Meter, InteractiveWidget):
|
|
68
67
|
size[1] = size[0]*3
|
69
68
|
return self.resolve_size(size)
|
70
69
|
|
71
|
-
def on_click_down(self, button: int) ->
|
72
|
-
if not self.parent.is_enabled
|
70
|
+
def on_click_down(self, button: int) -> bool:
|
71
|
+
if not self.parent.is_enabled:
|
73
72
|
return False
|
74
73
|
if button != 1:
|
75
74
|
return False
|
76
75
|
|
77
76
|
self.parent.get_focus()
|
78
|
-
|
79
|
-
if
|
80
|
-
pos =
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
new_value = self.parent.position_to_value(pos)
|
86
|
-
self.parent.set_value(new_value)
|
77
|
+
pos = self.parent_layer.camera.screen_to_world(pygame.mouse.get_pos())
|
78
|
+
if self.parent.axis == bf.axis.HORIZONTAL:
|
79
|
+
pos = pos[0]
|
80
|
+
else:
|
81
|
+
pos = pos[1]
|
82
|
+
new_value = self.parent.position_to_value(pos)
|
83
|
+
self.parent.set_value(new_value)
|
87
84
|
self.do_on_click_down(button)
|
88
85
|
return True
|
89
86
|
|
90
|
-
def resolve_constraints(self, size_only = False, position_only = False):
|
91
|
-
return super().resolve_constraints(size_only, position_only)
|
92
|
-
|
93
87
|
class Slider(Button):
|
88
|
+
|
94
89
|
def __init__(self, text: str, default_value: float = 1.0) -> None:
|
95
90
|
super().__init__(text, None)
|
96
91
|
self.axis : bf.axis = bf.axis.HORIZONTAL
|
@@ -162,6 +157,7 @@ class Slider(Button):
|
|
162
157
|
def set_value(self, value, no_callback: bool = False) -> Self:
|
163
158
|
if self.meter.value != value:
|
164
159
|
self.meter.set_value(value)
|
160
|
+
self.dirty_shape = True
|
165
161
|
if self.modified_callback and (not no_callback):
|
166
162
|
self.modified_callback(self.meter.value)
|
167
163
|
self.handle.set_tooltip_text(str(self.get_value()))
|
@@ -175,7 +171,7 @@ class Slider(Button):
|
|
175
171
|
def on_key_down(self, key):
|
176
172
|
if super().on_key_down(key):
|
177
173
|
return True
|
178
|
-
if not self.is_enabled
|
174
|
+
if not self.is_enabled:
|
179
175
|
return False
|
180
176
|
if self.axis == bf.axis.HORIZONTAL:
|
181
177
|
if key == pygame.K_RIGHT:
|
@@ -196,7 +192,7 @@ class Slider(Button):
|
|
196
192
|
return True
|
197
193
|
|
198
194
|
def do_on_click_down(self, button) -> None:
|
199
|
-
if not self.is_enabled
|
195
|
+
if not self.is_enabled:
|
200
196
|
return
|
201
197
|
if button == 1:
|
202
198
|
self.get_focus()
|
@@ -251,7 +247,6 @@ class Slider(Button):
|
|
251
247
|
value = self.meter.min_value + position_ratio * value_range
|
252
248
|
return round(value / self.meter.step) * self.meter.step
|
253
249
|
|
254
|
-
|
255
250
|
def get_min_required_size(self) -> tuple[float, float]:
|
256
251
|
"""
|
257
252
|
Calculates the minimum required size for the slider, considering the text, meter, and axis.
|
@@ -264,43 +259,59 @@ class Slider(Button):
|
|
264
259
|
meter_width, meter_height = self.meter.resolve_size(self.meter.get_min_required_size())
|
265
260
|
|
266
261
|
if self.axis == bf.axis.HORIZONTAL:
|
267
|
-
width = text_width + gap + meter_width
|
262
|
+
width = text_width + gap + meter_width
|
268
263
|
height = max(text_height, meter_height)
|
269
264
|
else:
|
270
265
|
width = max(text_width, meter_width)
|
271
266
|
height = text_height + gap + meter_height
|
272
267
|
|
268
|
+
height += self.unpressed_relief
|
273
269
|
return self.expand_rect_with_padding((0, 0, width, height)).size
|
274
|
-
|
275
270
|
|
276
|
-
def _build_composed_layout(self
|
271
|
+
def _build_composed_layout(self) -> None:
|
277
272
|
"""
|
278
|
-
Builds the layout for the slider,
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
other (Shape): The shape object to align with the slider.
|
273
|
+
Builds the composed layout for the slider, including the meter and handle.
|
274
|
+
This method adjusts the sizes and positions of the meter and handle based on the slider's axis,
|
275
|
+
autoresize conditions, and spacing settings. It ensures the slider's components are properly aligned
|
276
|
+
and sized within the slider's padded rectangle.
|
283
277
|
"""
|
278
|
+
self.text_rect.size = self._get_text_rect_required_size()
|
279
|
+
|
284
280
|
size_changed = False
|
285
281
|
gap = self.gap if self.text else 0
|
286
282
|
|
287
283
|
full_rect = self.text_rect.copy()
|
284
|
+
|
285
|
+
|
288
286
|
# Resolve the meter's size based on the axis and autoresize conditions
|
289
287
|
if self.axis == bf.axis.HORIZONTAL:
|
290
|
-
meter_width = self.meter.get_min_required_size()
|
291
|
-
|
292
|
-
|
293
|
-
|
288
|
+
meter_width,meter_height = self.meter.get_min_required_size()
|
289
|
+
if not self.autoresize_w:
|
290
|
+
meter_width = self.get_inner_width() - gap - self.text_rect.w
|
291
|
+
meter_width,meter_height = self.meter.resolve_size((meter_width,meter_height))
|
292
|
+
|
293
|
+
full_rect.w = max(self.get_inner_width(), meter_width + (gap + self.text_rect.w if self.text else 0))
|
294
|
+
self.meter.set_size((meter_width, meter_height))
|
295
|
+
full_rect.h = max(meter_height, self.text_rect.h if self.text else meter_height)
|
296
|
+
|
294
297
|
else: # VERTICAL
|
295
|
-
meter_height = self.meter.get_min_required_size()
|
296
|
-
|
297
|
-
|
298
|
-
|
298
|
+
meter_width, meter_height = self.meter.get_min_required_size()
|
299
|
+
if not self.autoresize_h:
|
300
|
+
meter_height = self.get_inner_height() - gap - self.text_rect.h
|
301
|
+
meter_width, meter_height = self.meter.resolve_size((meter_width, meter_height))
|
302
|
+
|
303
|
+
full_rect.h = meter_height + (gap + self.text_rect.h if self.text else 0)
|
304
|
+
self.meter.set_size((meter_width, meter_height))
|
305
|
+
full_rect.w = max(meter_width, self.text_rect.w if self.text else meter_width)
|
306
|
+
|
299
307
|
|
300
308
|
# Inflate the rect by padding and resolve the target size
|
309
|
+
full_rect.h += self.unpressed_relief
|
301
310
|
inflated = self.expand_rect_with_padding((0, 0, *full_rect.size)).size
|
311
|
+
|
302
312
|
target_size = self.resolve_size(inflated)
|
303
313
|
|
314
|
+
|
304
315
|
# Update the slider's size if it doesn't match the target size
|
305
316
|
if self.rect.size != target_size:
|
306
317
|
self.set_size(target_size)
|
@@ -314,23 +325,16 @@ class Slider(Button):
|
|
314
325
|
handle_size = self.meter.get_inner_width()
|
315
326
|
self.handle.set_size(self.handle.resolve_size((handle_size, handle_size)))
|
316
327
|
|
317
|
-
|
318
|
-
self._align_composed(other)
|
319
|
-
|
320
|
-
# Position the handle based on the current value
|
321
|
-
if self.axis == bf.axis.HORIZONTAL:
|
322
|
-
self.handle.set_center(self.value_to_position(self.meter.value), self.meter.rect.centery)
|
323
|
-
else:
|
324
|
-
self.handle.set_center(self.meter.rect.centerx, self.value_to_position(self.meter.value))
|
328
|
+
self._align_composed()
|
325
329
|
return size_changed
|
326
|
-
def _align_composed(self, other: Shape):
|
327
|
-
if not self.text:
|
328
|
-
self.text_rect.size = (0,0)
|
329
330
|
|
331
|
+
def _align_composed(self):
|
330
332
|
|
331
|
-
|
333
|
+
if not self.text:
|
334
|
+
self.text_rect.size = (0,0)
|
335
|
+
full_rect = self.get_local_inner_rect()
|
332
336
|
left_rect = self.text_rect
|
333
|
-
right_rect =
|
337
|
+
right_rect = self.meter.rect.copy()
|
334
338
|
|
335
339
|
|
336
340
|
|
@@ -381,29 +385,13 @@ class Slider(Button):
|
|
381
385
|
# Push text to local, push shape to world
|
382
386
|
self.text_rect = left_rect
|
383
387
|
right_rect.move_ip(*self.rect.topleft)
|
384
|
-
|
388
|
+
self.meter.set_position(*right_rect.topleft)
|
385
389
|
|
386
|
-
|
387
|
-
self.
|
388
|
-
|
389
|
-
|
390
|
+
# Position the handle based on the current value
|
391
|
+
if self.axis == bf.axis.HORIZONTAL:
|
392
|
+
self.handle.set_center(self.value_to_position(self.meter.value), self.meter.rect.centery)
|
393
|
+
else:
|
394
|
+
self.handle.set_center(self.meter.rect.centerx, self.value_to_position(self.meter.value))
|
390
395
|
|
391
|
-
def
|
392
|
-
|
393
|
-
super().apply_pre_updates()
|
394
|
-
# Build text rect size
|
395
|
-
self.text_rect.size = self._get_text_rect_required_size()
|
396
|
-
# Compose layout for meter (but not handle position yet)
|
397
|
-
self._build_composed_layout(self.meter)
|
398
|
-
# Meter and handle may need to update their own pre-updates
|
399
|
-
self.meter.apply_pre_updates()
|
400
|
-
self.handle.apply_pre_updates()
|
401
|
-
|
402
|
-
|
403
|
-
def apply_post_updates(self, skip_draw: bool = False):
|
404
|
-
# Step 2: Final alignment and painting
|
405
|
-
super().apply_post_updates(skip_draw=skip_draw)
|
406
|
-
# Align handle to value (now that sizes are final)
|
407
|
-
self._align_composed(self.meter)
|
408
|
-
self.handle.apply_post_updates(skip_draw=skip_draw)
|
409
|
-
self.meter.apply_post_updates(skip_draw=skip_draw)
|
396
|
+
def _build_layout(self) -> None:
|
397
|
+
return self._build_composed_layout()
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from .widget import Widget
|
2
|
+
from typing import Callable, Any, Self
|
3
|
+
|
4
|
+
class SyncedVar:
|
5
|
+
def __init__(self, value=None):
|
6
|
+
self._value: Any = value
|
7
|
+
self.modify_callback: Callable[[Any], Any] | None = None
|
8
|
+
self._bound_widgets: set[tuple[Widget, Callable[[Any], Any]]] = set()
|
9
|
+
|
10
|
+
def set_modify_callback(self, callback : Callable[[Any], Any]) -> Self:
|
11
|
+
self.modify_callback = callback
|
12
|
+
return self
|
13
|
+
|
14
|
+
def bind_widget(self, widget: Widget, update_callback: Callable[[Any], Any]) -> Self:
|
15
|
+
"""
|
16
|
+
Binds a widget to the SyncedVar. The widget must provide an update_callback
|
17
|
+
function that will be called whenever the value changes.
|
18
|
+
"""
|
19
|
+
self._bound_widgets.add((widget, update_callback))
|
20
|
+
self._update_widgets()
|
21
|
+
return self
|
22
|
+
|
23
|
+
def unbind_widget(self, widget: Widget)->Self:
|
24
|
+
"""
|
25
|
+
Unbinds a widget from the SyncedVar.
|
26
|
+
"""
|
27
|
+
self._bound_widgets = {
|
28
|
+
(w, cb) for w, cb in self._bound_widgets if w != widget
|
29
|
+
}
|
30
|
+
return self
|
31
|
+
|
32
|
+
@property
|
33
|
+
def value(self):
|
34
|
+
return self._value
|
35
|
+
|
36
|
+
@value.setter
|
37
|
+
def value(self, new_value):
|
38
|
+
if self._value != new_value:
|
39
|
+
self._value = new_value
|
40
|
+
self._update_widgets()
|
41
|
+
if self.modify_callback is not None:
|
42
|
+
self.modify_callback(new_value)
|
43
|
+
|
44
|
+
def _update_widgets(self):
|
45
|
+
"""
|
46
|
+
Calls the update callback for all bound widgets.
|
47
|
+
"""
|
48
|
+
for _, update_callback in self._bound_widgets:
|
49
|
+
update_callback(self._value)
|
batFramework/gui/textInput.py
CHANGED
batFramework/gui/toggle.py
CHANGED
@@ -63,8 +63,9 @@ class Toggle(Button):
|
|
63
63
|
|
64
64
|
return self.expand_rect_with_padding((0, 0, total_width, total_height)).size
|
65
65
|
|
66
|
-
|
67
66
|
def _build_composed_layout(self,other:Shape):
|
67
|
+
size_changed = False
|
68
|
+
self.text_rect.size = self._get_text_rect_required_size()
|
68
69
|
|
69
70
|
gap = self.gap if self.text else 0
|
70
71
|
full_rect = self.text_rect.copy()
|
@@ -81,13 +82,14 @@ class Toggle(Button):
|
|
81
82
|
target_size = self.resolve_size(inflated)
|
82
83
|
if self.rect.size != target_size:
|
83
84
|
self.set_size(target_size)
|
85
|
+
size_changed = True
|
84
86
|
|
85
87
|
self._align_composed(other)
|
86
|
-
|
88
|
+
return size_changed
|
87
89
|
|
88
90
|
def _align_composed(self,other:Shape):
|
89
91
|
|
90
|
-
full_rect = self.
|
92
|
+
full_rect = self.get_local_inner_rect()
|
91
93
|
left_rect = self.text_rect
|
92
94
|
right_rect = other.rect
|
93
95
|
gap = {
|
@@ -119,10 +121,8 @@ class Toggle(Button):
|
|
119
121
|
|
120
122
|
right_rect.move_ip(*self.rect.topleft)
|
121
123
|
|
122
|
-
|
123
|
-
|
124
124
|
def _build_layout(self) -> None:
|
125
|
-
self.
|
126
|
-
|
125
|
+
return self._build_composed_layout(self.indicator)
|
126
|
+
|
127
127
|
|
128
128
|
|
batFramework/gui/widget.py
CHANGED
@@ -203,20 +203,21 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
|
|
203
203
|
yield from child.get_debug_outlines()
|
204
204
|
|
205
205
|
def add_constraints(self, *constraints: "Constraint") -> Self:
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
seen.add(c.name)
|
213
|
-
self.constraints = result
|
206
|
+
# Add constraints without duplicates
|
207
|
+
existing_names = {c.name for c in self.constraints}
|
208
|
+
new_constraints = [c for c in constraints if c.name not in existing_names]
|
209
|
+
self.constraints.extend(new_constraints)
|
210
|
+
|
211
|
+
# Sort constraints by priority
|
214
212
|
self.constraints.sort(key=lambda c: c.priority)
|
215
|
-
|
216
|
-
|
217
|
-
if
|
218
|
-
|
219
|
-
|
213
|
+
|
214
|
+
# Update dirty flags based on the new constraints
|
215
|
+
if any(c.affects_size for c in new_constraints):
|
216
|
+
self.dirty_size_constraints = True
|
217
|
+
if any(c.affects_position for c in new_constraints):
|
218
|
+
self.dirty_position_constraints = True
|
219
|
+
|
220
|
+
# Clear ignored constraints
|
220
221
|
self._constraints_to_ignore = []
|
221
222
|
|
222
223
|
return self
|
@@ -226,8 +227,11 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
|
|
226
227
|
for c in self.constraints:
|
227
228
|
if c.name in names:
|
228
229
|
c.on_removal(self)
|
229
|
-
self.constraints = [c for c in self.constraints if c.name not in names]
|
230
|
+
self.constraints = [c for c in self.constraints if c.name not in names]
|
230
231
|
self._constraints_to_ignore = []
|
232
|
+
self.dirty_size_constraints = True
|
233
|
+
self.dirty_position_constraints= True
|
234
|
+
|
231
235
|
return self
|
232
236
|
|
233
237
|
|
@@ -319,6 +323,12 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
|
|
319
323
|
return self.parent.get_root()
|
320
324
|
return None
|
321
325
|
|
326
|
+
def get_by_tags(self,*tags: str)->list["Widget"]:
|
327
|
+
#use self.has_tags(*tags) for check
|
328
|
+
result = []
|
329
|
+
self.visit(lambda w: result.append(w) if w.has_tags(*tags) else None)
|
330
|
+
return result
|
331
|
+
|
322
332
|
def top_at(self, x: float | int, y: float | int) -> "None|Widget":
|
323
333
|
if self.children:
|
324
334
|
for child in reversed(self.children):
|
@@ -388,7 +398,7 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
|
|
388
398
|
return True if size changed
|
389
399
|
"""
|
390
400
|
new_size = tuple(map(int, self.rect.size))
|
391
|
-
if self.surface.
|
401
|
+
if self.surface.get_size() == new_size:
|
392
402
|
return False
|
393
403
|
|
394
404
|
old_alpha = self.surface.get_alpha()
|
@@ -485,7 +495,6 @@ class Widget(bf.Drawable, metaclass=WidgetMeta):
|
|
485
495
|
self.dirty_shape = False
|
486
496
|
self.dirty_surface = True
|
487
497
|
|
488
|
-
|
489
498
|
if self.dirty_position_constraints:
|
490
499
|
self.resolve_constraints(position_only=True)
|
491
500
|
|
batFramework/sceneLayer.py
CHANGED
@@ -47,7 +47,7 @@ class SceneLayer:
|
|
47
47
|
self.entities_to_remove.add(e)
|
48
48
|
|
49
49
|
def process_event(self,event:pygame.Event):
|
50
|
-
if event.type == pygame.VIDEORESIZE:
|
50
|
+
if event.type == pygame.VIDEORESIZE and not pygame.SCALED & bf.const.FLAGS:
|
51
51
|
self.camera.set_size(bf.const.RESOLUTION)
|
52
52
|
|
53
53
|
for e in self.entities.values():
|
batFramework/utils.py
CHANGED
@@ -151,69 +151,6 @@ class Utils:
|
|
151
151
|
else:
|
152
152
|
pygame.draw.circle(dest_surf, inside_color, center, radius)
|
153
153
|
|
154
|
-
@staticmethod
|
155
|
-
def animate_move(entity:"Entity", start_pos : tuple[float,float], end_pos:tuple[float,float])->Callable[[float],None]:
|
156
|
-
"""
|
157
|
-
Creates a function to animate the movement of an entity from start_pos to end_pos.
|
158
|
-
|
159
|
-
Args:
|
160
|
-
entity (Entity): The entity to move.
|
161
|
-
start_pos (tuple[float, float]): The starting position of the entity.
|
162
|
-
end_pos (tuple[float, float]): The ending position of the entity.
|
163
|
-
|
164
|
-
Returns:
|
165
|
-
Callable[[float], None]: A function that updates the entity's position based on a progression value (0 to 1).
|
166
|
-
"""
|
167
|
-
def func(x):
|
168
|
-
entity.set_center(start_pos[0]+(end_pos[0]-start_pos[0])*x,start_pos[1]+(end_pos[1]-start_pos[1])*x)
|
169
|
-
return func
|
170
|
-
|
171
|
-
def animate_move_to(entity: "Entity", end_pos: tuple[float, float]) -> Callable[[float], None]:
|
172
|
-
"""
|
173
|
-
Creates a function to animate the movement of an entity to a specified end position, capturing the start position at the start of the animation.
|
174
|
-
|
175
|
-
Args:
|
176
|
-
entity (Entity): The entity to move.
|
177
|
-
end_pos (tuple[float, float]): The target position of the entity.
|
178
|
-
|
179
|
-
Returns:
|
180
|
-
Callable[[float], None]: A function that updates the entity's position based on a progression value (0 to 1).
|
181
|
-
"""
|
182
|
-
|
183
|
-
# Start position will be captured once when the animation starts
|
184
|
-
start_pos = [None]
|
185
|
-
|
186
|
-
def update_position(progression: float):
|
187
|
-
if start_pos[0] is None:
|
188
|
-
start_pos[0] = entity.rect.center # Capture the start position at the start of the animation
|
189
|
-
|
190
|
-
# Calculate new position based on progression
|
191
|
-
new_x = start_pos[0][0] + (end_pos[0] - start_pos[0][0]) * progression
|
192
|
-
new_y = start_pos[0][1] + (end_pos[1] - start_pos[0][1]) * progression
|
193
|
-
|
194
|
-
# Set the entity's new position
|
195
|
-
entity.set_center(new_x, new_y)
|
196
|
-
|
197
|
-
return update_position
|
198
|
-
|
199
|
-
@staticmethod
|
200
|
-
def animate_alpha(entity:"Drawable", start : int, end:int)->Callable[[float],None]:
|
201
|
-
"""
|
202
|
-
Creates a function to animate the alpha (transparency) of a drawable entity between a start and end value.
|
203
|
-
|
204
|
-
Args:
|
205
|
-
entity (Drawable): The entity to animate.
|
206
|
-
start (int): The starting alpha value (0 to 255).
|
207
|
-
end (int): The ending alpha value (0 to 255).
|
208
|
-
|
209
|
-
Returns:
|
210
|
-
Callable[[float], None]: A function that updates the entity's alpha based on a progression value (0 to 1).
|
211
|
-
"""
|
212
|
-
def func(x):
|
213
|
-
entity.set_alpha(int(pygame.math.clamp(start+(end-start)*x,0,255)))
|
214
|
-
return func
|
215
|
-
|
216
|
-
|
217
154
|
|
218
155
|
@staticmethod
|
219
156
|
def random_color(min_value: int = 0, max_value: int = 255) -> tuple[int, int, int]:
|
@@ -306,7 +243,7 @@ class Utils:
|
|
306
243
|
# Draw the filled triangle
|
307
244
|
pygame.draw.polygon(surface, color, points,width=width)
|
308
245
|
|
309
|
-
def draw_arc_by_points(surface, color, start_pos, end_pos, tightness=0.5, width=1, resolution=0.5):
|
246
|
+
def draw_arc_by_points(surface, color, start_pos, end_pos, tightness=0.5, width=1, resolution=0.5,antialias:bool=False):
|
310
247
|
"""
|
311
248
|
Draw a smooth circular arc connecting start_pos and end_pos.
|
312
249
|
`tightness` controls curvature: 0 is straight line, 1 is semicircle, higher = more bulge.
|
@@ -327,6 +264,8 @@ class Utils:
|
|
327
264
|
p1 = pygame.Vector2(end_pos)
|
328
265
|
chord = p1 - p0
|
329
266
|
if chord.length_squared() == 0:
|
267
|
+
if antialias:
|
268
|
+
return pygame.draw.aacircle(surface, color, p0, width // 2)
|
330
269
|
return pygame.draw.circle(surface, color, p0, width // 2)
|
331
270
|
|
332
271
|
# Midpoint and perpendicular
|
@@ -361,5 +300,7 @@ class Utils:
|
|
361
300
|
center.x + math.cos(a) * r,
|
362
301
|
center.y + math.sin(a) * r
|
363
302
|
))
|
364
|
-
|
303
|
+
if antialias:
|
304
|
+
return pygame.draw.aalines(surface, color, False, points)
|
305
|
+
|
365
306
|
return pygame.draw.lines(surface, color, False, points, width)
|