batframework 1.0.9a7__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 +8 -5
- 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.9a7.dist-info → batframework-1.0.9a8.dist-info}/METADATA +24 -3
- batframework-1.0.9a8.dist-info/RECORD +66 -0
- {batframework-1.0.9a7.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.9a7.dist-info/RECORD +0 -63
- /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a8.dist-info/LICENSE +0 -0
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a8.dist-info}/top_level.txt +0 -0
batFramework/gui/slider.py
CHANGED
@@ -16,12 +16,14 @@ class SliderHandle(Indicator, DraggableWidget):
|
|
16
16
|
def __str__(self) -> str:
|
17
17
|
return "SliderHandle"
|
18
18
|
|
19
|
-
def on_click_down(self, button: int) ->
|
19
|
+
def on_click_down(self, button: int) -> bool:
|
20
20
|
if not self.parent.is_enabled():
|
21
|
-
return
|
21
|
+
return False
|
22
22
|
super().on_click_down(button)
|
23
23
|
if button == 1:
|
24
24
|
self.parent.get_focus()
|
25
|
+
return True
|
26
|
+
return False
|
25
27
|
|
26
28
|
def on_exit(self) -> None:
|
27
29
|
self.is_hovered = False
|
@@ -34,13 +36,17 @@ class SliderHandle(Indicator, DraggableWidget):
|
|
34
36
|
return
|
35
37
|
super().do_on_drag(drag_start, drag_end)
|
36
38
|
m: Meter = self.parent.meter
|
37
|
-
r = m.
|
38
|
-
|
39
|
+
r = m.get_inner_rect()
|
40
|
+
|
41
|
+
position = self.rect.centerx if self.parent.axis == bf.axis.HORIZONTAL else self.rect.centery
|
39
42
|
self.rect.clamp_ip(r)
|
40
43
|
# Adjust handle position to value
|
41
44
|
new_value = self.parent.position_to_value(position)
|
42
45
|
self.parent.set_value(new_value)
|
43
|
-
self.
|
46
|
+
if self.parent.axis == bf.axis.HORIZONTAL:
|
47
|
+
self.rect.centerx = self.parent.value_to_position(new_value)
|
48
|
+
else:
|
49
|
+
self.rect.centery = self.parent.value_to_position(new_value)
|
44
50
|
|
45
51
|
def top_at(self, x, y):
|
46
52
|
return Widget.top_at(self, x, y)
|
@@ -50,20 +56,44 @@ class SliderMeter(Meter, InteractiveWidget):
|
|
50
56
|
def __str__(self) -> str:
|
51
57
|
return "SliderMeter"
|
52
58
|
|
53
|
-
def
|
59
|
+
def __init__(self, min_value = 0, max_value = 1, step = 0.1):
|
60
|
+
super().__init__(min_value, max_value, step)
|
61
|
+
self.set_padding(0)
|
62
|
+
|
63
|
+
def get_min_required_size(self):
|
64
|
+
size = list(super().get_min_required_size())
|
65
|
+
if self.parent.axis == bf.axis.HORIZONTAL:
|
66
|
+
size[0] = size[1]*3
|
67
|
+
else:
|
68
|
+
size[1] = size[0]*3
|
69
|
+
return self.resolve_size(size)
|
70
|
+
|
71
|
+
def on_click_down(self, button: int) -> True:
|
54
72
|
if not self.parent.is_enabled():
|
55
|
-
return
|
56
|
-
if button
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
73
|
+
return False
|
74
|
+
if button != 1:
|
75
|
+
return False
|
76
|
+
|
77
|
+
self.parent.get_focus()
|
78
|
+
r = self.get_root()
|
79
|
+
if r:
|
80
|
+
pos = r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
|
81
|
+
if self.parent.axis == bf.axis.HORIZONTAL:
|
82
|
+
pos = pos[0]
|
83
|
+
else:
|
84
|
+
pos = pos[1]
|
85
|
+
new_value = self.parent.position_to_value(pos)
|
86
|
+
self.parent.set_value(new_value)
|
62
87
|
self.do_on_click_down(button)
|
88
|
+
return True
|
89
|
+
|
90
|
+
def resolve_constraints(self, size_only = False, position_only = False):
|
91
|
+
return super().resolve_constraints(size_only, position_only)
|
63
92
|
|
64
93
|
class Slider(Button):
|
65
94
|
def __init__(self, text: str, default_value: float = 1.0) -> None:
|
66
95
|
super().__init__(text, None)
|
96
|
+
self.axis : bf.axis = bf.axis.HORIZONTAL
|
67
97
|
self.gap: float | int = 0
|
68
98
|
self.spacing: bf.spacing = bf.spacing.MANUAL
|
69
99
|
self.modified_callback : Callable[[float],Any] = None
|
@@ -73,6 +103,19 @@ class Slider(Button):
|
|
73
103
|
self.meter.set_debug_color(bf.color.RED)
|
74
104
|
self.set_value(default_value, True)
|
75
105
|
|
106
|
+
def set_tooltip_text(self, text):
|
107
|
+
return super().set_tooltip_text(text)
|
108
|
+
|
109
|
+
def set_fill_color(self,color)->Self:
|
110
|
+
self.meter.content.set_color(color)
|
111
|
+
return self
|
112
|
+
|
113
|
+
def set_axis(self,axis:bf.axis)->Self:
|
114
|
+
self.axis = axis
|
115
|
+
self.meter.set_axis(axis)
|
116
|
+
self.dirty_shape = True
|
117
|
+
return self
|
118
|
+
|
76
119
|
def set_visible(self, value: bool) -> Self:
|
77
120
|
self.handle.set_visible(value)
|
78
121
|
self.meter.set_visible(value)
|
@@ -119,21 +162,38 @@ class Slider(Button):
|
|
119
162
|
def set_value(self, value, no_callback: bool = False) -> Self:
|
120
163
|
if self.meter.value != value:
|
121
164
|
self.meter.set_value(value)
|
122
|
-
self.dirty_shape = True
|
123
165
|
if self.modified_callback and (not no_callback):
|
124
166
|
self.modified_callback(self.meter.value)
|
167
|
+
self.handle.set_tooltip_text(str(self.get_value()))
|
168
|
+
self.meter.set_tooltip_text(str(self.get_value()))
|
169
|
+
|
125
170
|
return self
|
126
171
|
|
127
172
|
def get_value(self) -> float:
|
128
173
|
return self.meter.get_value()
|
129
174
|
|
130
|
-
def
|
175
|
+
def on_key_down(self, key):
|
176
|
+
if super().on_key_down(key):
|
177
|
+
return True
|
131
178
|
if not self.is_enabled():
|
132
|
-
return
|
133
|
-
if
|
134
|
-
|
135
|
-
|
136
|
-
|
179
|
+
return False
|
180
|
+
if self.axis == bf.axis.HORIZONTAL:
|
181
|
+
if key == pygame.K_RIGHT:
|
182
|
+
self.set_value(self.meter.get_value() + self.meter.step)
|
183
|
+
elif key == pygame.K_LEFT:
|
184
|
+
self.set_value(self.meter.get_value() - self.meter.step)
|
185
|
+
else:
|
186
|
+
return False
|
187
|
+
return True
|
188
|
+
else:
|
189
|
+
if key == pygame.K_UP:
|
190
|
+
self.set_value(self.meter.get_value() + self.meter.step)
|
191
|
+
elif key == pygame.K_DOWN:
|
192
|
+
self.set_value(self.meter.get_value() - self.meter.step)
|
193
|
+
else:
|
194
|
+
return False
|
195
|
+
|
196
|
+
return True
|
137
197
|
|
138
198
|
def do_on_click_down(self, button) -> None:
|
139
199
|
if not self.is_enabled():
|
@@ -145,98 +205,205 @@ class Slider(Button):
|
|
145
205
|
"""
|
146
206
|
Converts a value to a position on the meter, considering the step size.
|
147
207
|
"""
|
148
|
-
rect = self.meter.
|
208
|
+
rect = self.meter.get_inner_rect()
|
149
209
|
value_range = self.meter.get_range()
|
150
210
|
value = round(value / self.meter.step) * self.meter.step
|
151
211
|
position_ratio = (value - self.meter.min_value) / value_range
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
212
|
+
if self.axis == bf.axis.HORIZONTAL:
|
213
|
+
return (
|
214
|
+
rect.left
|
215
|
+
+ (self.handle.rect.w / 2)
|
216
|
+
+ position_ratio * (rect.width - self.handle.rect.w)
|
217
|
+
)
|
218
|
+
else:
|
219
|
+
return (
|
220
|
+
rect.bottom
|
221
|
+
- (self.handle.rect.h / 2)
|
222
|
+
- position_ratio * (rect.height - self.handle.rect.h)
|
223
|
+
)
|
224
|
+
|
158
225
|
def position_to_value(self, position: float) -> float:
|
159
226
|
"""
|
160
227
|
Converts a position on the meter to a value, considering the step size.
|
161
228
|
"""
|
162
|
-
|
163
|
-
|
164
|
-
|
229
|
+
rect = self.meter.get_inner_rect()
|
230
|
+
if self.axis == bf.axis.HORIZONTAL:
|
231
|
+
if self.rect.w == self.handle.rect.w:
|
232
|
+
position_ratio = 0
|
233
|
+
else:
|
234
|
+
handle_half = self.handle.rect.w // 2
|
235
|
+
position = max(rect.left + handle_half, min(position, rect.right - handle_half))
|
236
|
+
position_ratio = (position - rect.left - handle_half) / (
|
237
|
+
rect.width - self.handle.rect.w
|
238
|
+
)
|
239
|
+
else:
|
240
|
+
if self.rect.h == self.handle.rect.h:
|
241
|
+
position_ratio = 0
|
242
|
+
else:
|
243
|
+
handle_half = self.handle.rect.h // 2
|
244
|
+
position = max(rect.top + handle_half, min(position, rect.bottom - handle_half))
|
245
|
+
# Flip ratio vertically: bottom is min, top is max
|
246
|
+
position_ratio = (rect.bottom - position - handle_half) / (
|
247
|
+
rect.height - self.handle.rect.h
|
248
|
+
)
|
165
249
|
|
166
|
-
position_ratio = (position - rect.left - handle_half) / (
|
167
|
-
rect.width - self.handle.rect.w
|
168
|
-
)
|
169
250
|
value_range = self.meter.get_range()
|
170
251
|
value = self.meter.min_value + position_ratio * value_range
|
171
252
|
return round(value / self.meter.step) * self.meter.step
|
172
253
|
|
173
|
-
def get_min_required_size(self) -> tuple[float, float]:
|
174
|
-
gap = self.gap if self.text else 0
|
175
|
-
if not self.text_rect:
|
176
|
-
self.text_rect.size = self._get_text_rect_required_size()
|
177
|
-
w, h = self.text_rect.size
|
178
|
-
h+=self.unpressed_relief
|
179
|
-
return self.inflate_rect_by_padding((0, 0, w + gap + self.meter.get_min_required_size()[1], h)).size
|
180
254
|
|
181
|
-
def
|
255
|
+
def get_min_required_size(self) -> tuple[float, float]:
|
256
|
+
"""
|
257
|
+
Calculates the minimum required size for the slider, considering the text, meter, and axis.
|
182
258
|
|
259
|
+
Returns:
|
260
|
+
tuple[float, float]: The width and height of the minimum required size.
|
261
|
+
"""
|
183
262
|
gap = self.gap if self.text else 0
|
184
|
-
|
185
|
-
|
186
|
-
#right part size
|
187
|
-
meter_width = self.text_rect.h * 10
|
188
|
-
if not self.autoresize_w:
|
189
|
-
meter_width = self.get_padded_width() - self.text_rect.w - gap
|
190
|
-
right_part_height = min(self.text_rect.h, self.font_object.point_size)
|
191
|
-
self.meter.set_size_if_autoresize((meter_width,right_part_height))
|
192
|
-
self.handle.set_size_if_autoresize((None,right_part_height))
|
193
|
-
|
194
|
-
|
195
|
-
#join left and right
|
196
|
-
joined_rect = pygame.FRect(
|
197
|
-
0, 0, self.text_rect.w + gap + meter_width, self.text_rect.h
|
198
|
-
)
|
199
|
-
|
200
|
-
if self.autoresize_h or self.autoresize_w:
|
201
|
-
target_rect = self.inflate_rect_by_padding(joined_rect)
|
202
|
-
target_rect.h += self.unpressed_relief
|
203
|
-
if not self.autoresize_w:
|
204
|
-
target_rect.w = self.rect.w
|
205
|
-
if not self.autoresize_h:
|
206
|
-
target_rect.h = self.rect.h
|
207
|
-
if self.rect.size != target_rect.size:
|
208
|
-
self.set_size(target_rect.size)
|
209
|
-
self.build()
|
210
|
-
return
|
211
|
-
|
212
|
-
# ------------------------------------ size is ok
|
213
|
-
|
263
|
+
text_width, text_height = self._get_text_rect_required_size() if self.text else (0, 0)
|
264
|
+
meter_width, meter_height = self.meter.resolve_size(self.meter.get_min_required_size())
|
214
265
|
|
215
|
-
|
216
|
-
|
217
|
-
|
266
|
+
if self.axis == bf.axis.HORIZONTAL:
|
267
|
+
width = text_width + gap + meter_width
|
268
|
+
height = max(text_height, meter_height)
|
269
|
+
else:
|
270
|
+
width = max(text_width, meter_width)
|
271
|
+
height = text_height + gap + meter_height
|
218
272
|
|
219
|
-
self.
|
220
|
-
|
273
|
+
return self.expand_rect_with_padding((0, 0, width, height)).size
|
274
|
+
|
221
275
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
case bf.spacing.MIN:
|
227
|
-
gap = 0
|
276
|
+
def _build_composed_layout(self, other: Shape) -> None:
|
277
|
+
"""
|
278
|
+
Builds the layout for the slider, ensuring that child elements (handle and meter)
|
279
|
+
are resized and positioned correctly based on the slider's size and axis.
|
228
280
|
|
229
|
-
|
281
|
+
Args:
|
282
|
+
other (Shape): The shape object to align with the slider.
|
283
|
+
"""
|
284
|
+
size_changed = False
|
285
|
+
gap = self.gap if self.text else 0
|
230
286
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
).
|
235
|
-
|
236
|
-
|
287
|
+
full_rect = self.text_rect.copy()
|
288
|
+
# Resolve the meter's size based on the axis and autoresize conditions
|
289
|
+
if self.axis == bf.axis.HORIZONTAL:
|
290
|
+
meter_width = self.meter.get_min_required_size()[0] if self.meter.autoresize_w else self.meter.rect.w
|
291
|
+
full_rect.w = max(self.get_inner_width(), meter_width + gap + self.text_rect.w)
|
292
|
+
self.meter.set_size((meter_width, None))
|
293
|
+
full_rect.h = max(self.meter.rect.h, self.text_rect.h if self.text else self.meter.rect.h)
|
294
|
+
else: # VERTICAL
|
295
|
+
meter_height = self.meter.get_min_required_size()[1] if self.meter.autoresize_h else self.meter.rect.h
|
296
|
+
full_rect.h = max(self.get_inner_height(), meter_height + (gap + self.text_rect.h if self.text else 0))
|
297
|
+
self.meter.set_size((None, meter_height))
|
298
|
+
full_rect.w = max(self.meter.rect.w, self.text_rect.w if self.text else self.meter.rect.w)
|
299
|
+
|
300
|
+
# Inflate the rect by padding and resolve the target size
|
301
|
+
inflated = self.expand_rect_with_padding((0, 0, *full_rect.size)).size
|
302
|
+
target_size = self.resolve_size(inflated)
|
303
|
+
|
304
|
+
# Update the slider's size if it doesn't match the target size
|
305
|
+
if self.rect.size != target_size:
|
306
|
+
self.set_size(target_size)
|
307
|
+
size_changed = True
|
308
|
+
|
309
|
+
# Adjust the handle size based on the meter's size
|
310
|
+
if self.axis == bf.axis.HORIZONTAL:
|
311
|
+
handle_size = self.meter.get_inner_height()
|
312
|
+
self.handle.set_size(self.handle.resolve_size((handle_size, handle_size)))
|
313
|
+
else:
|
314
|
+
handle_size = self.meter.get_inner_width()
|
315
|
+
self.handle.set_size(self.handle.resolve_size((handle_size, handle_size)))
|
316
|
+
|
317
|
+
# Align the composed elements (text and meter)
|
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))
|
325
|
+
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
|
+
full_rect = self.get_local_padded_rect()
|
332
|
+
left_rect = self.text_rect
|
333
|
+
right_rect = other.rect.copy()
|
334
|
+
|
335
|
+
|
336
|
+
|
337
|
+
if self.axis == bf.axis.HORIZONTAL:
|
338
|
+
gap = {
|
339
|
+
bf.spacing.MIN: 0,
|
340
|
+
bf.spacing.HALF: (full_rect.width - left_rect.width - right_rect.width) // 2,
|
341
|
+
bf.spacing.MAX: full_rect.width - left_rect.width - right_rect.width,
|
342
|
+
bf.spacing.MANUAL: self.gap
|
343
|
+
}.get(self.spacing, 0)
|
344
|
+
|
345
|
+
gap = max(0, gap)
|
346
|
+
combined_width = left_rect.width + right_rect.width + gap
|
347
|
+
|
348
|
+
group_x = {
|
349
|
+
bf.alignment.LEFT: full_rect.left,
|
350
|
+
bf.alignment.MIDLEFT: full_rect.left,
|
351
|
+
bf.alignment.RIGHT: full_rect.right - combined_width,
|
352
|
+
bf.alignment.MIDRIGHT: full_rect.right - combined_width,
|
353
|
+
bf.alignment.CENTER: full_rect.centerx - combined_width // 2
|
354
|
+
}.get(self.alignment, full_rect.left)
|
355
|
+
|
356
|
+
left_rect.x, right_rect.x = group_x, group_x + left_rect.width + gap
|
357
|
+
left_rect.centery = right_rect.centery = full_rect.centery
|
358
|
+
|
359
|
+
else: # VERTICAL
|
360
|
+
gap = {
|
361
|
+
bf.spacing.MIN: 0,
|
362
|
+
bf.spacing.HALF: (full_rect.height - left_rect.height - right_rect.height) // 2,
|
363
|
+
bf.spacing.MAX: full_rect.height - left_rect.height - right_rect.height,
|
364
|
+
bf.spacing.MANUAL: self.gap
|
365
|
+
}.get(self.spacing, 0)
|
366
|
+
|
367
|
+
gap = max(0, gap)
|
368
|
+
combined_height = left_rect.height + right_rect.height + gap
|
369
|
+
|
370
|
+
group_y = {
|
371
|
+
bf.alignment.TOP: full_rect.top,
|
372
|
+
bf.alignment.MIDTOP: full_rect.top,
|
373
|
+
bf.alignment.BOTTOM: full_rect.bottom - combined_height,
|
374
|
+
bf.alignment.MIDBOTTOM: full_rect.bottom - combined_height,
|
375
|
+
bf.alignment.CENTER: full_rect.centery - combined_height // 2
|
376
|
+
}.get(self.alignment, full_rect.top)
|
377
|
+
|
378
|
+
left_rect.y, right_rect.y = group_y, group_y + left_rect.height + gap
|
379
|
+
left_rect.centerx = right_rect.centerx = full_rect.centerx
|
380
|
+
|
381
|
+
# Push text to local, push shape to world
|
382
|
+
self.text_rect = left_rect
|
383
|
+
right_rect.move_ip(*self.rect.topleft)
|
384
|
+
other.set_position(*right_rect.topleft)
|
237
385
|
|
238
|
-
|
239
|
-
|
240
|
-
self.
|
386
|
+
def _build_layout(self) -> None:
|
387
|
+
self.text_rect.size = self._get_text_rect_required_size()
|
388
|
+
return self._build_composed_layout(self.meter)
|
389
|
+
|
241
390
|
|
242
|
-
|
391
|
+
def apply_pre_updates(self):
|
392
|
+
# Step 1: Constraints and shape/size
|
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)
|
batFramework/gui/textInput.py
CHANGED
@@ -58,7 +58,7 @@ class TextInput(Label, InteractiveWidget):
|
|
58
58
|
def __init__(self) -> None:
|
59
59
|
self.cursor_position = (0, 0)
|
60
60
|
self.old_key_repeat = (0, 0)
|
61
|
-
self.cursor_timer = bf.Timer(0.2, self._cursor_toggle, loop
|
61
|
+
self.cursor_timer = bf.Timer(0.2, self._cursor_toggle, loop=-1).start()
|
62
62
|
self.cursor_timer.pause()
|
63
63
|
self.show_cursor = False
|
64
64
|
self.on_modify: Callable[[str], str] = None
|
@@ -82,8 +82,9 @@ class TextInput(Label, InteractiveWidget):
|
|
82
82
|
|
83
83
|
def do_on_click_down(self, button):
|
84
84
|
if button != 1:
|
85
|
-
return
|
85
|
+
return False
|
86
86
|
self.get_focus()
|
87
|
+
return True
|
87
88
|
|
88
89
|
def do_on_enter(self):
|
89
90
|
pygame.mouse.set_cursor(pygame.SYSTEM_CURSOR_IBEAM)
|
@@ -111,11 +112,18 @@ class TextInput(Label, InteractiveWidget):
|
|
111
112
|
return lines[line]
|
112
113
|
|
113
114
|
def get_debug_outlines(self):
|
114
|
-
if self.visible:
|
115
|
-
offset = self._get_outline_offset() if self.show_text_outline else (0,0)
|
116
|
-
yield (self.text_rect.move(self.rect.x - offset[0] - self.scroll.x,self.rect.y - offset[1] - self.scroll.y), "purple")
|
117
115
|
yield from super().get_debug_outlines()
|
118
|
-
|
116
|
+
if self.visible:
|
117
|
+
# offset = self._get_outline_offset() if self.show_text_outline else (0,0)
|
118
|
+
# yield (self.text_rect.move(self.rect.x - offset[0] - self.scroll.x,self.rect.y - offset[1] - self.scroll.y), "purple")
|
119
|
+
yield (self.get_cursor_rect().move(-self.scroll+self.rect.topleft),"green")
|
120
|
+
|
121
|
+
|
122
|
+
def get_min_required_size(self) -> tuple[float, float]:
|
123
|
+
size = self._get_text_rect_required_size()
|
124
|
+
return self.expand_rect_with_padding(
|
125
|
+
(0, 0,size[0]+self.get_cursor_rect().w,size[1])
|
126
|
+
).size
|
119
127
|
|
120
128
|
|
121
129
|
def set_cursor_position(self, position: tuple[int, int]) -> Self:
|
@@ -127,9 +135,9 @@ class TextInput(Label, InteractiveWidget):
|
|
127
135
|
x = max(0, min(x, line_length))
|
128
136
|
self.show_cursor = True
|
129
137
|
self.cursor_position = (x,y)
|
130
|
-
self.
|
138
|
+
self.apply_post_updates(skip_draw=True)
|
131
139
|
offset = self._get_outline_offset() if self.show_text_outline else (0,0)
|
132
|
-
padded = self.
|
140
|
+
padded = self.get_inner_rect().move(-self.rect.x + offset[0], -self.rect.y + offset[1])
|
133
141
|
self.align_text(self.text_rect,padded,self.alignment)
|
134
142
|
return self
|
135
143
|
|
@@ -142,11 +150,13 @@ class TextInput(Label, InteractiveWidget):
|
|
142
150
|
|
143
151
|
height = self.font_object.get_linesize()
|
144
152
|
|
145
|
-
cursor_y = self.
|
153
|
+
cursor_y = self.get_inner_rect().__getattribute__(self.alignment.value)[1] - self.rect.top
|
146
154
|
cursor_y += line_y * height
|
147
155
|
cursor_x = self.text_rect.x
|
148
156
|
cursor_x += self.font_object.size(lines[line_y][:line_x])[0] if line_x > 0 else 0
|
149
|
-
cursor_rect = pygame.Rect(cursor_x, cursor_y, 1, height)
|
157
|
+
cursor_rect = pygame.Rect(cursor_x, cursor_y, 1, height-1)
|
158
|
+
offset = self._get_outline_offset()
|
159
|
+
cursor_rect.move_ip(offset[0] if self.cursor_position[0] >0 else 0,offset[1] if self.cursor_position[1] > 0 else 0)
|
150
160
|
return cursor_rect
|
151
161
|
|
152
162
|
def cursor_to_absolute(self, position: tuple[int, int]) -> int:
|
@@ -183,7 +193,6 @@ class TextInput(Label, InteractiveWidget):
|
|
183
193
|
# Insert text at the current cursor position
|
184
194
|
self.set_text(f"{text[:current_pos]}{event.text}{text[current_pos:]}")
|
185
195
|
self.set_cursor_position(self.absolute_to_cursor(current_pos + len(event.text)))
|
186
|
-
|
187
196
|
elif event.type == pygame.KEYDOWN:
|
188
197
|
match event.key:
|
189
198
|
case pygame.K_ESCAPE:
|
@@ -191,34 +200,48 @@ class TextInput(Label, InteractiveWidget):
|
|
191
200
|
|
192
201
|
case pygame.K_BACKSPACE if current_pos > 0:
|
193
202
|
# Remove the character before the cursor
|
194
|
-
|
195
|
-
|
203
|
+
delta = current_pos-1
|
204
|
+
if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
|
205
|
+
delta = find_prev_word(self.text,current_pos-1)
|
206
|
+
if delta <0: delta = 0
|
207
|
+
|
208
|
+
self.set_text(f"{text[:delta]}{text[current_pos:]}")
|
209
|
+
self.set_cursor_position(self.absolute_to_cursor(delta))
|
210
|
+
self._cursor_toggle(True)
|
196
211
|
|
197
212
|
case pygame.K_DELETE if current_pos < len(text):
|
198
213
|
# Remove the character at the cursor
|
199
214
|
self.set_text(f"{text[:current_pos]}{text[current_pos + 1:]}")
|
215
|
+
self._cursor_toggle(True)
|
200
216
|
|
201
217
|
case pygame.K_RIGHT:
|
202
218
|
if current_pos < len(text):
|
203
219
|
self.handle_cursor_movement(pressed, current_pos, direction="right")
|
204
|
-
|
220
|
+
self._cursor_toggle(True)
|
221
|
+
|
205
222
|
case pygame.K_LEFT:
|
206
223
|
if current_pos > 0:
|
207
224
|
self.handle_cursor_movement(pressed, current_pos, direction="left")
|
225
|
+
self._cursor_toggle(True)
|
208
226
|
|
209
227
|
case pygame.K_UP:
|
210
228
|
# Move cursor up one line
|
211
229
|
self.set_cursor_position((self.cursor_position[0], self.cursor_position[1] - 1))
|
230
|
+
self._cursor_toggle(True)
|
212
231
|
|
213
232
|
case pygame.K_DOWN:
|
214
233
|
# Move cursor down one line
|
215
234
|
self.set_cursor_position((self.cursor_position[0], self.cursor_position[1] + 1))
|
235
|
+
self._cursor_toggle(True)
|
216
236
|
|
217
237
|
case pygame.K_RETURN:
|
218
238
|
# Insert a newline at the current cursor position
|
219
239
|
self.set_text(f"{text[:current_pos]}\n{text[current_pos:]}")
|
220
240
|
self.set_cursor_position(self.absolute_to_cursor(current_pos + 1))
|
241
|
+
self._cursor_toggle(True)
|
221
242
|
case _ :
|
243
|
+
if event.unicode:
|
244
|
+
event.consumed = True
|
222
245
|
return
|
223
246
|
|
224
247
|
event.consumed = True
|
@@ -249,7 +272,8 @@ class TextInput(Label, InteractiveWidget):
|
|
249
272
|
cursor_rect = self.get_cursor_rect()
|
250
273
|
cursor_rect.move_ip(-self.scroll)
|
251
274
|
|
252
|
-
|
275
|
+
if self.show_text_outline:
|
276
|
+
pygame.draw.rect(self.surface, self.text_outline_color, cursor_rect.inflate(2,2))
|
253
277
|
pygame.draw.rect(self.surface, self.text_color, cursor_rect)
|
254
278
|
|
255
279
|
def paint(self) -> None:
|
@@ -269,13 +293,13 @@ class TextInput(Label, InteractiveWidget):
|
|
269
293
|
text_rect.__setattr__(alignment.value, pos)
|
270
294
|
|
271
295
|
|
272
|
-
if cursor_rect.right > area.right+self.scroll.x:
|
273
|
-
|
274
|
-
elif cursor_rect.x < self.scroll.x+area.left:
|
275
|
-
|
276
|
-
self.scroll.x = max(self.scroll.x,0)
|
296
|
+
# if cursor_rect.right > area.right+self.scroll.x:
|
297
|
+
# self.scroll.x=cursor_rect.right - area.right
|
298
|
+
# elif cursor_rect.x < self.scroll.x+area.left:
|
299
|
+
# self.scroll.x= cursor_rect.left - area.left
|
300
|
+
# self.scroll.x = max(self.scroll.x,0)
|
277
301
|
|
278
|
-
if cursor_rect.bottom >
|
302
|
+
if cursor_rect.bottom > self.scroll.y + area.bottom:
|
279
303
|
self.scroll.y = cursor_rect.bottom - area.bottom
|
280
304
|
elif cursor_rect.y < self.scroll.y + area.top:
|
281
305
|
self.scroll.y = cursor_rect.top - area.top
|