batframework 1.1.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batFramework/__init__.py +84 -51
- batFramework/action.py +280 -252
- batFramework/actionContainer.py +105 -38
- batFramework/animatedSprite.py +81 -117
- batFramework/animation.py +91 -0
- batFramework/audioManager.py +156 -85
- batFramework/baseScene.py +249 -0
- batFramework/camera.py +245 -123
- batFramework/constants.py +57 -75
- batFramework/cutscene.py +239 -119
- batFramework/cutsceneManager.py +34 -0
- batFramework/drawable.py +107 -0
- batFramework/dynamicEntity.py +30 -23
- batFramework/easingController.py +58 -0
- batFramework/entity.py +130 -123
- batFramework/enums.py +171 -0
- batFramework/fontManager.py +65 -0
- batFramework/gui/__init__.py +28 -14
- batFramework/gui/animatedLabel.py +90 -0
- batFramework/gui/button.py +18 -84
- batFramework/gui/clickableWidget.py +244 -0
- batFramework/gui/collapseContainer.py +98 -0
- batFramework/gui/constraints/__init__.py +1 -0
- batFramework/gui/constraints/constraints.py +1066 -0
- batFramework/gui/container.py +220 -49
- batFramework/gui/debugger.py +140 -47
- batFramework/gui/draggableWidget.py +63 -0
- batFramework/gui/image.py +61 -23
- batFramework/gui/indicator.py +116 -40
- batFramework/gui/interactiveWidget.py +243 -22
- batFramework/gui/label.py +147 -110
- batFramework/gui/layout.py +442 -81
- batFramework/gui/meter.py +155 -0
- batFramework/gui/radioButton.py +43 -0
- batFramework/gui/root.py +228 -60
- batFramework/gui/scrollingContainer.py +282 -0
- batFramework/gui/selector.py +232 -0
- batFramework/gui/shape.py +286 -86
- batFramework/gui/slider.py +353 -0
- batFramework/gui/style.py +10 -0
- batFramework/gui/styleManager.py +49 -0
- batFramework/gui/syncedVar.py +43 -0
- batFramework/gui/textInput.py +331 -0
- batFramework/gui/textWidget.py +308 -0
- batFramework/gui/toggle.py +140 -62
- batFramework/gui/tooltip.py +35 -0
- batFramework/gui/widget.py +546 -307
- batFramework/manager.py +131 -50
- batFramework/particle.py +118 -0
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +34 -0
- batFramework/resourceManager.py +130 -0
- batFramework/scene.py +31 -226
- batFramework/sceneLayer.py +134 -0
- batFramework/sceneManager.py +200 -165
- batFramework/scrollingSprite.py +115 -0
- batFramework/sprite.py +46 -0
- batFramework/stateMachine.py +49 -51
- batFramework/templates/__init__.py +2 -0
- batFramework/templates/character.py +15 -0
- batFramework/templates/controller.py +158 -0
- batFramework/templates/stateMachine.py +39 -0
- batFramework/tileset.py +46 -0
- batFramework/timeManager.py +213 -0
- batFramework/transition.py +162 -157
- batFramework/triggerZone.py +22 -22
- batFramework/utils.py +306 -184
- {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/LICENSE +1 -1
- {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/METADATA +8 -4
- batframework-2.0.0.dist-info/RECORD +72 -0
- batFramework/cutsceneBlocks.py +0 -176
- batFramework/debugger.py +0 -48
- batFramework/easing.py +0 -71
- batFramework/gui/constraints.py +0 -204
- batFramework/gui/frame.py +0 -19
- batFramework/particles.py +0 -77
- batFramework/time.py +0 -75
- batFramework/transitionManager.py +0 -0
- batframework-1.1.0.dist-info/RECORD +0 -43
- {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/WHEEL +0 -0
- {batframework-1.1.0.dist-info → batframework-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,331 @@
|
|
1
|
+
import batFramework as bf
|
2
|
+
from typing import Self, Callable
|
3
|
+
from .label import Label
|
4
|
+
from .interactiveWidget import InteractiveWidget
|
5
|
+
import pygame
|
6
|
+
|
7
|
+
def find_next_word(s: str, start_index: int) -> int:
|
8
|
+
length = len(s)
|
9
|
+
# Ensure the starting index is within the bounds of the string
|
10
|
+
if start_index < 0 or start_index >= length:
|
11
|
+
raise ValueError("Starting index is out of bounds")
|
12
|
+
index = start_index
|
13
|
+
# If the start_index is at a space, skip leading spaces
|
14
|
+
if s[index] in [' ','\n']:
|
15
|
+
while index < length and s[index] in [' ','\n']:
|
16
|
+
index += 1
|
17
|
+
# If we've reached the end of the string
|
18
|
+
if index >= length:
|
19
|
+
return -1
|
20
|
+
else:
|
21
|
+
# If the start_index is within a word, move to the end of that word
|
22
|
+
while index < length and s[index] not in [' ','\n']:
|
23
|
+
index += 1
|
24
|
+
if index == length:
|
25
|
+
return index
|
26
|
+
# Return the index of the start of the next word or -1 if no more words are found
|
27
|
+
return index if index < length else -1
|
28
|
+
|
29
|
+
def find_prev_word(s: str, start_index: int) -> int:
|
30
|
+
if start_index <= 0 : return 0
|
31
|
+
length = len(s)
|
32
|
+
|
33
|
+
# Ensure the starting index is within the bounds of the string
|
34
|
+
if start_index < 0 or start_index >= length:
|
35
|
+
raise ValueError("Starting index is out of bounds")
|
36
|
+
|
37
|
+
index = start_index
|
38
|
+
|
39
|
+
# If the start_index is at a space, skip trailing spaces
|
40
|
+
if s[index] in [' ', '\n']:
|
41
|
+
while index > 0 and s[index-1] in [' ', '\n']:
|
42
|
+
index -= 1
|
43
|
+
# If we've reached the beginning of the string
|
44
|
+
if index <= 0:
|
45
|
+
return 0 if s[0] not in [' ', '\n'] else -1
|
46
|
+
else:
|
47
|
+
# If the start_index is within a word, move to the start of that word
|
48
|
+
while index > 0 and s[index-1] not in [' ', '\n']:
|
49
|
+
index -= 1
|
50
|
+
if index == 0 and s[index] not in [' ', '\n']:
|
51
|
+
return 0
|
52
|
+
|
53
|
+
# Return the index of the start of the previous word or -1 if no more words are found
|
54
|
+
return index if index > 0 or (index == 0 and s[0] not in [' ', '\n']) else -1
|
55
|
+
|
56
|
+
|
57
|
+
class TextInput(Label, InteractiveWidget):
|
58
|
+
def __init__(self) -> None:
|
59
|
+
self.cursor_position = (0, 0)
|
60
|
+
self.old_key_repeat = (0, 0)
|
61
|
+
self.placeholder_text = ""
|
62
|
+
self.cursor_timer = bf.Timer(0.2, self._cursor_toggle, loop=-1).start()
|
63
|
+
self.cursor_timer.pause()
|
64
|
+
self.show_cursor = False
|
65
|
+
self._cursor_blink_show : bool = True
|
66
|
+
self.on_modify: Callable[[str], str]| None = None
|
67
|
+
self.set_click_pass_through(False)
|
68
|
+
super().__init__("")
|
69
|
+
self.set_outline_color("black")
|
70
|
+
self.alignment = bf.alignment.TOPLEFT
|
71
|
+
|
72
|
+
def set_placeholder_text(self, text: str) -> Self:
|
73
|
+
self.placeholder_text = text
|
74
|
+
self.dirty_surface = True
|
75
|
+
return self
|
76
|
+
|
77
|
+
|
78
|
+
def set_modify_callback(self, callback: Callable[[str], str]) -> Self:
|
79
|
+
self.on_modify = callback
|
80
|
+
return self
|
81
|
+
|
82
|
+
def __str__(self) -> str:
|
83
|
+
return f"TextInput"
|
84
|
+
|
85
|
+
def _cursor_toggle(self, value: bool | None = None):
|
86
|
+
if value is None:
|
87
|
+
value = not self._cursor_blink_show
|
88
|
+
self._cursor_blink_show = value
|
89
|
+
self.dirty_surface = True
|
90
|
+
|
91
|
+
def on_click_down(self, button,event):
|
92
|
+
if button == 1:
|
93
|
+
self.get_focus()
|
94
|
+
event.consumed = True
|
95
|
+
super().on_click_down(button,event)
|
96
|
+
|
97
|
+
def do_on_enter(self):
|
98
|
+
pygame.mouse.set_cursor(pygame.SYSTEM_CURSOR_IBEAM)
|
99
|
+
|
100
|
+
def do_on_exit(self):
|
101
|
+
pygame.mouse.set_cursor(bf.const.DEFAULT_CURSOR)
|
102
|
+
|
103
|
+
def do_on_get_focus(self):
|
104
|
+
self.cursor_timer.resume()
|
105
|
+
# self._cursor_toggle(True)
|
106
|
+
self.show_cursor = True
|
107
|
+
self.old_key_repeat = pygame.key.get_repeat()
|
108
|
+
pygame.key.set_repeat(200, 50)
|
109
|
+
|
110
|
+
def do_on_lose_focus(self):
|
111
|
+
self.cursor_timer.pause()
|
112
|
+
self.show_cursor = False
|
113
|
+
pygame.key.set_repeat(*self.old_key_repeat)
|
114
|
+
|
115
|
+
def get_line(self, line: int) -> str | None:
|
116
|
+
if line < 0:
|
117
|
+
return None
|
118
|
+
lines = self.text_widget.text.split('\n')
|
119
|
+
if line >= len(lines):
|
120
|
+
return None
|
121
|
+
return lines[line]
|
122
|
+
|
123
|
+
def get_debug_outlines(self):
|
124
|
+
yield from super().get_debug_outlines()
|
125
|
+
if self.visible:
|
126
|
+
# offset = self._get_outline_offset() if self.show_text_outline else (0,0)
|
127
|
+
# yield (self.text_widget.rect.move(self.rect.x - offset[0] - self.scroll.x,self.rect.y - offset[1] - self.scroll.y), "purple")
|
128
|
+
yield (self.get_cursor_rect().move(*self.text_widget.rect.topleft),"green")
|
129
|
+
|
130
|
+
|
131
|
+
def get_min_required_size(self) -> tuple[float, float]:
|
132
|
+
size = self.text_widget.get_min_required_size()
|
133
|
+
return self.expand_rect_with_padding(
|
134
|
+
(0, 0,size[0]+self.get_cursor_rect().w,size[1])
|
135
|
+
).size
|
136
|
+
|
137
|
+
|
138
|
+
def set_cursor_position(self, position: tuple[int, int]) -> Self:
|
139
|
+
x, y = position
|
140
|
+
|
141
|
+
lines = self.text_widget.text.split('\n')
|
142
|
+
y = max(0, min(y, len(lines) - 1))
|
143
|
+
line_length = len(lines[y])
|
144
|
+
x = max(0, min(x, line_length))
|
145
|
+
self.cursor_position = (x,y)
|
146
|
+
return self
|
147
|
+
|
148
|
+
def get_cursor_rect(self) -> pygame.FRect:
|
149
|
+
if not self.text_widget.font_object:
|
150
|
+
return pygame.FRect(0, 0, 0, 0)
|
151
|
+
font = self.text_widget.font_object
|
152
|
+
|
153
|
+
lines = self.text_widget.text.split('\n')
|
154
|
+
line_x, line_y = self.cursor_position
|
155
|
+
line = lines[line_y]
|
156
|
+
|
157
|
+
# # Clamp line_y and line_x to valid ranges
|
158
|
+
# line_y = max(0, min(line_y, len(lines) - 1))
|
159
|
+
# line_x = max(0, min(line_x, len(line)))
|
160
|
+
|
161
|
+
line_height = font.get_linesize()
|
162
|
+
|
163
|
+
# Calculate the pixel x position of the cursor in the current line
|
164
|
+
x = font.size(line[:line_x])[0]
|
165
|
+
y = line_height * line_y
|
166
|
+
|
167
|
+
if self.text_widget.show_text_outline:
|
168
|
+
offset = self.text_widget._get_outline_offset()
|
169
|
+
x+=offset[0]
|
170
|
+
y+=offset[1]
|
171
|
+
|
172
|
+
res = pygame.FRect(x,y,1,line_height)
|
173
|
+
return res
|
174
|
+
|
175
|
+
def ensure_cursor_visible(self):
|
176
|
+
pass
|
177
|
+
|
178
|
+
def cursor_to_absolute(self, position: tuple[int, int]) -> int:
|
179
|
+
x, y = position
|
180
|
+
|
181
|
+
y = max(0, min(y, len(self.text_widget.text.split('\n')) - 1))
|
182
|
+
lines = self.text_widget.text.split('\n')
|
183
|
+
x = max(0, min(x, len(lines[y])))
|
184
|
+
|
185
|
+
absolute_position = sum(len(line) + 1 for line in lines[:y]) + x
|
186
|
+
return absolute_position
|
187
|
+
|
188
|
+
def absolute_to_cursor(self, absolute: int) -> tuple[int, int]:
|
189
|
+
text = self.text_widget.text
|
190
|
+
lines = text.split('\n')
|
191
|
+
current_pos = 0
|
192
|
+
|
193
|
+
for line_no, line in enumerate(lines):
|
194
|
+
if absolute <= current_pos + len(line):
|
195
|
+
return (absolute - current_pos, line_no)
|
196
|
+
current_pos += len(line) + 1
|
197
|
+
|
198
|
+
return (len(lines[-1]), len(lines) - 1)
|
199
|
+
|
200
|
+
def handle_event(self, event):
|
201
|
+
# TODO fix tab_focus not working when textInput in focus
|
202
|
+
super().handle_event(event)
|
203
|
+
if event.consumed or(not self.is_focused or event.type not in [pygame.TEXTINPUT, pygame.KEYDOWN]):
|
204
|
+
return
|
205
|
+
|
206
|
+
text = self.get_text()
|
207
|
+
current_pos = self.cursor_to_absolute(self.cursor_position)
|
208
|
+
pressed = pygame.key.get_pressed()
|
209
|
+
|
210
|
+
if event.type == pygame.TEXTINPUT:
|
211
|
+
# Insert text at the current cursor position
|
212
|
+
self.set_text(f"{text[:current_pos]}{event.text}{text[current_pos:]}")
|
213
|
+
self.set_cursor_position(self.absolute_to_cursor(current_pos + len(event.text)))
|
214
|
+
elif event.type == pygame.KEYDOWN:
|
215
|
+
match event.key:
|
216
|
+
case pygame.K_ESCAPE:
|
217
|
+
self.lose_focus()
|
218
|
+
|
219
|
+
case pygame.K_BACKSPACE if current_pos > 0:
|
220
|
+
# Remove the character before the cursor
|
221
|
+
delta = current_pos-1
|
222
|
+
if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
|
223
|
+
delta = find_prev_word(self.text_widget.text,current_pos-1)
|
224
|
+
if delta <0: delta = 0
|
225
|
+
|
226
|
+
self.set_text(f"{text[:delta]}{text[current_pos:]}")
|
227
|
+
self.set_cursor_position(self.absolute_to_cursor(delta))
|
228
|
+
self._cursor_toggle(True)
|
229
|
+
|
230
|
+
case pygame.K_DELETE if current_pos < len(text):
|
231
|
+
# Remove the character at the cursor
|
232
|
+
self.set_text(f"{text[:current_pos]}{text[current_pos + 1:]}")
|
233
|
+
self._cursor_toggle(True)
|
234
|
+
|
235
|
+
case pygame.K_RIGHT:
|
236
|
+
if current_pos < len(text):
|
237
|
+
self.handle_cursor_movement(pressed, current_pos, direction="right")
|
238
|
+
self._cursor_toggle(True)
|
239
|
+
|
240
|
+
case pygame.K_LEFT:
|
241
|
+
if current_pos > 0:
|
242
|
+
self.handle_cursor_movement(pressed, current_pos, direction="left")
|
243
|
+
self._cursor_toggle(True)
|
244
|
+
|
245
|
+
case pygame.K_UP:
|
246
|
+
# Move cursor up one line
|
247
|
+
self.set_cursor_position((self.cursor_position[0], self.cursor_position[1] - 1))
|
248
|
+
self._cursor_toggle(True)
|
249
|
+
|
250
|
+
case pygame.K_DOWN:
|
251
|
+
# Move cursor down one line
|
252
|
+
self.set_cursor_position((self.cursor_position[0], self.cursor_position[1] + 1))
|
253
|
+
self._cursor_toggle(True)
|
254
|
+
|
255
|
+
case pygame.K_RETURN:
|
256
|
+
# Insert a newline at the current cursor position
|
257
|
+
self.set_text(f"{text[:current_pos]}\n{text[current_pos:]}")
|
258
|
+
self.set_cursor_position(self.absolute_to_cursor(current_pos + 1))
|
259
|
+
self._cursor_toggle(True)
|
260
|
+
case _ :
|
261
|
+
if event.unicode:
|
262
|
+
event.consumed = True
|
263
|
+
return
|
264
|
+
|
265
|
+
event.consumed = True
|
266
|
+
|
267
|
+
def handle_cursor_movement(self, pressed, current_pos, direction):
|
268
|
+
if direction == "right":
|
269
|
+
if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
|
270
|
+
next_word_pos = find_next_word(self.text_widget.text, current_pos)
|
271
|
+
self.set_cursor_position(self.absolute_to_cursor(next_word_pos if next_word_pos != -1 else current_pos + 1))
|
272
|
+
else:
|
273
|
+
self.set_cursor_position(self.absolute_to_cursor(current_pos + 1))
|
274
|
+
elif direction == "left":
|
275
|
+
if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
|
276
|
+
prev_word_pos = find_prev_word(self.text_widget.text, current_pos - 1)
|
277
|
+
self.set_cursor_position(self.absolute_to_cursor(prev_word_pos if prev_word_pos != -1 else current_pos - 1))
|
278
|
+
else:
|
279
|
+
self.set_cursor_position(self.absolute_to_cursor(current_pos - 1))
|
280
|
+
|
281
|
+
|
282
|
+
def set_text(self, text: str) -> Self:
|
283
|
+
if self.on_modify:
|
284
|
+
text = self.on_modify(text)
|
285
|
+
if text == "" and self.placeholder_text:
|
286
|
+
text = self.placeholder_text
|
287
|
+
if text != "" and text == self.placeholder_text:
|
288
|
+
self.text_widget.set_text("")
|
289
|
+
self.text_widget.set_text(text)
|
290
|
+
|
291
|
+
return self
|
292
|
+
def _draw_cursor(self,camera:bf.Camera) -> None:
|
293
|
+
if not self.show_cursor or not self._cursor_blink_show:
|
294
|
+
return
|
295
|
+
|
296
|
+
cursor_rect = self.get_cursor_rect()
|
297
|
+
cursor_rect.move_ip(*self.text_widget.rect.topleft)
|
298
|
+
cursor_rect = camera.world_to_screen(cursor_rect)
|
299
|
+
if self.text_widget.show_text_outline:
|
300
|
+
pygame.draw.rect(camera.surface, self.text_widget.text_outline_color, cursor_rect.inflate(2,2))
|
301
|
+
pygame.draw.rect(camera.surface, self.text_widget.text_color, cursor_rect)
|
302
|
+
|
303
|
+
def draw(self,camera:bf.Camera) -> None:
|
304
|
+
super().draw(camera)
|
305
|
+
self._draw_cursor(camera)
|
306
|
+
|
307
|
+
def align_text(
|
308
|
+
self, text_rect: pygame.FRect, area: pygame.FRect, alignment: bf.alignment
|
309
|
+
):
|
310
|
+
cursor_rect = self.get_cursor_rect()
|
311
|
+
|
312
|
+
if alignment == bf.alignment.LEFT:
|
313
|
+
alignment = bf.alignment.MIDLEFT
|
314
|
+
elif alignment == bf.alignment.MIDRIGHT:
|
315
|
+
alignment = bf.alignment.MIDRIGHT
|
316
|
+
pos = area.__getattribute__(alignment.value)
|
317
|
+
text_rect.__setattr__(alignment.value, pos)
|
318
|
+
scroll = self.text_widget.scroll
|
319
|
+
|
320
|
+
if cursor_rect.right > area.right+scroll.x:
|
321
|
+
scroll.x=cursor_rect.right - area.right
|
322
|
+
elif cursor_rect.x < scroll.x+area.left:
|
323
|
+
scroll.x= cursor_rect.left - area.left
|
324
|
+
# self.scroll.x = 0
|
325
|
+
scroll.x = max(scroll.x,0)
|
326
|
+
|
327
|
+
if cursor_rect.bottom > scroll.y + area.bottom:
|
328
|
+
scroll.y = cursor_rect.bottom - area.bottom
|
329
|
+
elif cursor_rect.y < scroll.y + area.top:
|
330
|
+
scroll.y = cursor_rect.top - area.top
|
331
|
+
scroll.y = max(scroll.y, 0)
|
@@ -0,0 +1,308 @@
|
|
1
|
+
from math import ceil
|
2
|
+
import pygame
|
3
|
+
from .widget import Widget
|
4
|
+
import batFramework as bf
|
5
|
+
from typing import Literal, Self,Union
|
6
|
+
|
7
|
+
class TextWidget(Widget):
|
8
|
+
def __init__(self, text:str):
|
9
|
+
super().__init__()
|
10
|
+
self.text = text
|
11
|
+
|
12
|
+
# Allows scrolling the text
|
13
|
+
self.allow_scroll : bool = True
|
14
|
+
|
15
|
+
# Scroll variable
|
16
|
+
# TODO make scroll work
|
17
|
+
self.scroll :pygame.Vector2 = pygame.Vector2(0,0)
|
18
|
+
|
19
|
+
# Enable/Disable antialiasing
|
20
|
+
self.antialias: bool = bf.FontManager().DEFAULT_ANTIALIAS
|
21
|
+
|
22
|
+
self.text_size = bf.FontManager().DEFAULT_FONT_SIZE
|
23
|
+
|
24
|
+
self.auto_wraplength: bool = False
|
25
|
+
|
26
|
+
self.text_color: tuple[int, int, int] | str = "black"
|
27
|
+
|
28
|
+
self.text_bg_color : tuple[int,int,int]| str | None = None
|
29
|
+
|
30
|
+
|
31
|
+
self.text_outline_color: tuple[int, int, int] | str = "gray50"
|
32
|
+
|
33
|
+
self._text_outline_mask = pygame.Mask((3, 3), fill=True)
|
34
|
+
|
35
|
+
self.line_alignment = pygame.FONT_LEFT
|
36
|
+
# font name (given when loaded by utils) to use for the text
|
37
|
+
|
38
|
+
self.font_name = None
|
39
|
+
# reference to the font object
|
40
|
+
self.font_object = None
|
41
|
+
# Rect containing the text of the label
|
42
|
+
self.show_text_outline: bool = False
|
43
|
+
|
44
|
+
self.is_italic: bool = False
|
45
|
+
|
46
|
+
self.is_bold: bool = False
|
47
|
+
|
48
|
+
self.is_underlined: bool = False
|
49
|
+
|
50
|
+
super().__init__()
|
51
|
+
self.set_debug_color("purple")
|
52
|
+
self.set_autoresize(True)
|
53
|
+
self.set_font(force=True)
|
54
|
+
self.set_convert_alpha(True)
|
55
|
+
|
56
|
+
|
57
|
+
def set_padding(self, value): # can't set padding
|
58
|
+
return self
|
59
|
+
|
60
|
+
def __str__(self) -> str:
|
61
|
+
return f"TextWidget({repr(self.text)})"
|
62
|
+
|
63
|
+
def set_allow_scroll(self, value:bool)->Self:
|
64
|
+
if self.allow_scroll == value: return self
|
65
|
+
self.allow_scroll = value
|
66
|
+
self.dirty_surface = True
|
67
|
+
return self
|
68
|
+
|
69
|
+
def set_scroll(self,x=None,y=None)->Self:
|
70
|
+
x = x if x is not None else self.scroll.x
|
71
|
+
y = y if y is not None else self.scroll.y
|
72
|
+
|
73
|
+
self.scroll.update(x,y)
|
74
|
+
self.dirty_surface = True
|
75
|
+
return self
|
76
|
+
|
77
|
+
def scroll_by(self,x=0,y=0)->Self:
|
78
|
+
self.scroll += x,y
|
79
|
+
self.dirty_surface = True
|
80
|
+
return self
|
81
|
+
|
82
|
+
def set_text_color(self, color) -> Self:
|
83
|
+
self.text_color = color
|
84
|
+
self.dirty_surface = True
|
85
|
+
return self
|
86
|
+
|
87
|
+
def set_text_bg_color(self, color) -> Self:
|
88
|
+
self.text_bg_color = color
|
89
|
+
self.set_convert_alpha(color is None)
|
90
|
+
self.dirty_surface = True
|
91
|
+
return self
|
92
|
+
|
93
|
+
|
94
|
+
def top_at(self, x, y):
|
95
|
+
return None
|
96
|
+
|
97
|
+
def set_line_alignment(self, alignment: Union[Literal["left"], Literal["right"], Literal["center"]]) -> Self:
|
98
|
+
self.line_alignment = alignment
|
99
|
+
self.dirty_surface = True
|
100
|
+
return self
|
101
|
+
|
102
|
+
def set_italic(self, value: bool) -> Self:
|
103
|
+
if value == self.is_italic:
|
104
|
+
return self
|
105
|
+
self.is_italic = value
|
106
|
+
if self.autoresize_h or self.autoresize_w:
|
107
|
+
self.dirty_shape = True
|
108
|
+
else:
|
109
|
+
self.dirty_surface = True
|
110
|
+
return self
|
111
|
+
|
112
|
+
def set_bold(self, value: bool) -> Self:
|
113
|
+
if value == self.is_bold:
|
114
|
+
return self
|
115
|
+
self.is_bold = value
|
116
|
+
if self.autoresize_h or self.autoresize_w:
|
117
|
+
self.dirty_shape = True
|
118
|
+
else:
|
119
|
+
self.dirty_surface = True
|
120
|
+
return self
|
121
|
+
|
122
|
+
def set_underlined(self, value: bool) -> Self:
|
123
|
+
if value == self.is_underlined:
|
124
|
+
return self
|
125
|
+
self.is_underlined = value
|
126
|
+
self.dirty_surface = True
|
127
|
+
return self
|
128
|
+
|
129
|
+
def set_text_outline_mask_size(self,size:tuple[int,int])->Self:
|
130
|
+
old_size = self._text_outline_mask.get_size()
|
131
|
+
min_w, min_h = min(old_size[0], size[0]), min(old_size[1], size[1])
|
132
|
+
m = [
|
133
|
+
[self._text_outline_mask.get_at((x, y)) for x in range(min_w)]
|
134
|
+
for y in range(min_h)
|
135
|
+
]
|
136
|
+
self._text_outline_mask = pygame.Mask(size, fill=True)
|
137
|
+
self.set_text_outline_matrix(m)
|
138
|
+
return self
|
139
|
+
|
140
|
+
def set_text_outline_matrix(self, matrix: list[list[0 | 1]]) -> Self:
|
141
|
+
if matrix is None:
|
142
|
+
matrix = [[0 for _ in range(3)] for _ in range(3)]
|
143
|
+
for y in range(3):
|
144
|
+
for x in range(3):
|
145
|
+
self._text_outline_mask.set_at((x, y), matrix[2 - y][2 - x])
|
146
|
+
self.dirty_shape = True
|
147
|
+
return self
|
148
|
+
|
149
|
+
def set_text_outline_color(self, color) -> Self:
|
150
|
+
self.text_outline_color = color
|
151
|
+
self.dirty_surface = True
|
152
|
+
return self
|
153
|
+
|
154
|
+
def set_show_text_outline(self,value:bool) -> Self:
|
155
|
+
self.show_text_outline = value
|
156
|
+
self.dirty_shape = True
|
157
|
+
return self
|
158
|
+
|
159
|
+
def set_auto_wraplength(self, val: bool) -> Self:
|
160
|
+
self.auto_wraplength = val
|
161
|
+
if self.autoresize_h or self.autoresize_w:
|
162
|
+
self.dirty_shape = True
|
163
|
+
else:
|
164
|
+
self.dirty_surface = True
|
165
|
+
return self
|
166
|
+
|
167
|
+
def get_debug_outlines(self):
|
168
|
+
if self.visible:
|
169
|
+
yield from super().get_debug_outlines()
|
170
|
+
|
171
|
+
def set_font(self, font_name: str = None, force: bool = False) -> Self:
|
172
|
+
if font_name == self.font_name and not force:
|
173
|
+
return self
|
174
|
+
self.font_name = font_name
|
175
|
+
self.font_object = bf.FontManager().get_font(self.font_name, self.text_size)
|
176
|
+
if self.autoresize_h or self.autoresize_w:
|
177
|
+
self.dirty_shape = True
|
178
|
+
else:
|
179
|
+
self.dirty_surface = True
|
180
|
+
return self
|
181
|
+
|
182
|
+
def set_text_size(self, text_size: int) -> Self:
|
183
|
+
text_size = (text_size // 2) * 2
|
184
|
+
if text_size == self.text_size:
|
185
|
+
return self
|
186
|
+
self.text_size = text_size
|
187
|
+
self.font_object = bf.FontManager().get_font(self.font_name, self.text_size)
|
188
|
+
self.dirty_shape = True
|
189
|
+
return self
|
190
|
+
|
191
|
+
def get_text_size(self) -> int:
|
192
|
+
return self.text_size
|
193
|
+
|
194
|
+
|
195
|
+
def is_antialias(self) -> bool:
|
196
|
+
return self.antialias
|
197
|
+
|
198
|
+
def set_antialias(self, value: bool) -> Self:
|
199
|
+
self.antialias = value
|
200
|
+
self.dirty_surface = True
|
201
|
+
return self
|
202
|
+
|
203
|
+
def set_text(self, text: str) -> Self:
|
204
|
+
if text == self.text:
|
205
|
+
return self
|
206
|
+
self.text = text
|
207
|
+
self.dirty_shape = True
|
208
|
+
return self
|
209
|
+
|
210
|
+
def get_min_required_size(self) -> tuple[float, float]:
|
211
|
+
if not self.font_object : return 0,0
|
212
|
+
|
213
|
+
tmp_text = self.text
|
214
|
+
if self.text.endswith('\n'):
|
215
|
+
tmp_text+=" " # hack to have correct size if ends with newline
|
216
|
+
params = {
|
217
|
+
"font_name": self.font_object.name,
|
218
|
+
"text": tmp_text,
|
219
|
+
"antialias": self.antialias,
|
220
|
+
"color": self.text_color,
|
221
|
+
"bgcolor": self.text_bg_color,
|
222
|
+
"wraplength": int(self.get_inner_width()) if self.auto_wraplength and not self.autoresize_w else 0,
|
223
|
+
}
|
224
|
+
|
225
|
+
size = list(self._render_font(params).get_size())
|
226
|
+
size[1]= max(size[1],self.font_object.get_ascent() - self.font_object.get_descent())
|
227
|
+
if not self.show_text_outline:
|
228
|
+
return size
|
229
|
+
s = self._get_outline_offset()
|
230
|
+
return size[0] + s[0]*2, size[1] + s[1]*2
|
231
|
+
|
232
|
+
|
233
|
+
def get_text(self) -> str:
|
234
|
+
return self.text
|
235
|
+
|
236
|
+
def _render_font(self, params: dict) -> pygame.Surface:
|
237
|
+
params.pop("font_name")
|
238
|
+
# save old settings
|
239
|
+
old_italic = self.font_object.get_italic()
|
240
|
+
old_bold = self.font_object.get_bold()
|
241
|
+
old_underline = self.font_object.get_underline()
|
242
|
+
old_align = self.font_object.align
|
243
|
+
# setup font
|
244
|
+
self.font_object.set_italic(self.is_italic)
|
245
|
+
self.font_object.set_bold(self.is_bold)
|
246
|
+
self.font_object.set_underline(self.is_underlined)
|
247
|
+
self.font_object.align = self.line_alignment
|
248
|
+
surf = self.font_object.render(**params)
|
249
|
+
# reset font
|
250
|
+
self.font_object.set_italic(old_italic)
|
251
|
+
self.font_object.set_bold(old_bold)
|
252
|
+
self.font_object.set_underline(old_underline)
|
253
|
+
self.font_object.align = old_align
|
254
|
+
return surf
|
255
|
+
|
256
|
+
def _get_outline_offset(self)->tuple[int,int]:
|
257
|
+
mask_size = self._text_outline_mask.get_size()
|
258
|
+
return mask_size[0]//2,mask_size[1]//2
|
259
|
+
|
260
|
+
|
261
|
+
def build(self) -> bool:
|
262
|
+
"""
|
263
|
+
return True if size changed
|
264
|
+
"""
|
265
|
+
target_size = self.resolve_size(self.get_min_required_size())
|
266
|
+
if self.rect.size != target_size:
|
267
|
+
self.set_size(target_size)
|
268
|
+
return True
|
269
|
+
return False
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
def paint(self) -> None:
|
274
|
+
self._resize_surface()
|
275
|
+
if self.font_object is None:
|
276
|
+
print(f"No font for widget with text : '{self}' :(")
|
277
|
+
return
|
278
|
+
|
279
|
+
|
280
|
+
wrap = int(self.get_inner_width()) if self.auto_wraplength and not self.autoresize_w else 0
|
281
|
+
params = {
|
282
|
+
"font_name": self.font_object.name,
|
283
|
+
"text": self.text,
|
284
|
+
"antialias": self.antialias,
|
285
|
+
"color": self.text_color,
|
286
|
+
"bgcolor": self.text_bg_color if not self.show_text_outline else None,
|
287
|
+
"wraplength": wrap,
|
288
|
+
}
|
289
|
+
|
290
|
+
if self.text_bg_color is None :
|
291
|
+
self.surface = self.surface.convert_alpha()
|
292
|
+
|
293
|
+
bg_fill_color = (0, 0, 0, 0) if self.text_bg_color is None else self.text_bg_color
|
294
|
+
self.surface.fill(bg_fill_color)
|
295
|
+
|
296
|
+
text_surf = self._render_font(params)
|
297
|
+
|
298
|
+
if self.show_text_outline:
|
299
|
+
mask = pygame.mask.from_surface(text_surf).convolve(self._text_outline_mask)
|
300
|
+
outline_surf = mask.to_surface(
|
301
|
+
setcolor=self.text_outline_color,
|
302
|
+
unsetcolor=bg_fill_color
|
303
|
+
)
|
304
|
+
|
305
|
+
outline_surf.blit(text_surf,self._get_outline_offset())
|
306
|
+
text_surf = outline_surf
|
307
|
+
|
308
|
+
self.surface.blit(text_surf, -self.scroll)
|