batframework 1.0.8a9__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.
Files changed (70) hide show
  1. batFramework/__init__.py +68 -51
  2. batFramework/action.py +126 -99
  3. batFramework/actionContainer.py +53 -9
  4. batFramework/animatedSprite.py +141 -82
  5. batFramework/audioManager.py +69 -26
  6. batFramework/camera.py +259 -69
  7. batFramework/character.py +27 -0
  8. batFramework/constants.py +16 -54
  9. batFramework/cutscene.py +39 -29
  10. batFramework/cutsceneBlocks.py +36 -43
  11. batFramework/dynamicEntity.py +18 -9
  12. batFramework/easingController.py +58 -0
  13. batFramework/entity.py +48 -97
  14. batFramework/enums.py +113 -0
  15. batFramework/fontManager.py +65 -0
  16. batFramework/gui/__init__.py +10 -2
  17. batFramework/gui/button.py +9 -78
  18. batFramework/gui/clickableWidget.py +220 -0
  19. batFramework/gui/constraints/__init__.py +1 -0
  20. batFramework/gui/constraints/constraints.py +815 -0
  21. batFramework/gui/container.py +174 -32
  22. batFramework/gui/debugger.py +131 -43
  23. batFramework/gui/dialogueBox.py +99 -0
  24. batFramework/gui/draggableWidget.py +40 -0
  25. batFramework/gui/image.py +56 -20
  26. batFramework/gui/indicator.py +38 -21
  27. batFramework/gui/interactiveWidget.py +192 -13
  28. batFramework/gui/label.py +309 -74
  29. batFramework/gui/layout.py +231 -63
  30. batFramework/gui/meter.py +74 -0
  31. batFramework/gui/radioButton.py +84 -0
  32. batFramework/gui/root.py +134 -38
  33. batFramework/gui/shape.py +237 -57
  34. batFramework/gui/slider.py +240 -0
  35. batFramework/gui/style.py +10 -0
  36. batFramework/gui/styleManager.py +48 -0
  37. batFramework/gui/textInput.py +247 -0
  38. batFramework/gui/toggle.py +101 -51
  39. batFramework/gui/widget.py +358 -250
  40. batFramework/manager.py +52 -19
  41. batFramework/object.py +123 -0
  42. batFramework/particle.py +115 -0
  43. batFramework/renderGroup.py +67 -0
  44. batFramework/resourceManager.py +100 -0
  45. batFramework/scene.py +281 -123
  46. batFramework/sceneManager.py +178 -116
  47. batFramework/scrollingSprite.py +114 -0
  48. batFramework/sprite.py +51 -0
  49. batFramework/stateMachine.py +11 -8
  50. batFramework/templates/__init__.py +2 -0
  51. batFramework/templates/character.py +44 -0
  52. batFramework/templates/states.py +166 -0
  53. batFramework/tileset.py +46 -0
  54. batFramework/time.py +145 -58
  55. batFramework/transition.py +195 -124
  56. batFramework/triggerZone.py +1 -1
  57. batFramework/utils.py +112 -147
  58. batframework-1.0.8a10.dist-info/LICENCE +21 -0
  59. batframework-1.0.8a10.dist-info/METADATA +43 -0
  60. batframework-1.0.8a10.dist-info/RECORD +62 -0
  61. batFramework/debugger.py +0 -48
  62. batFramework/easing.py +0 -71
  63. batFramework/gui/constraints.py +0 -204
  64. batFramework/gui/frame.py +0 -19
  65. batFramework/particles.py +0 -77
  66. batFramework/transitionManager.py +0 -0
  67. batframework-1.0.8a9.dist-info/METADATA +0 -53
  68. batframework-1.0.8a9.dist-info/RECORD +0 -42
  69. {batframework-1.0.8a9.dist-info → batframework-1.0.8a10.dist-info}/WHEEL +0 -0
  70. {batframework-1.0.8a9.dist-info → batframework-1.0.8a10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,247 @@
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.cursor_timer = bf.Timer(0.3, self._cursor_toggle, loop=True).start()
62
+ self.cursor_timer.pause()
63
+ self.show_cursor = False
64
+ self.on_modify: Callable[[str], str] = None
65
+ self.set_focusable(True)
66
+ self.set_outline_color("black")
67
+ super().__init__("")
68
+ self.alignment = bf.alignment.TOPLEFT
69
+
70
+ def set_modify_callback(self, callback: Callable[[str], str]) -> Self:
71
+ self.on_modify = callback
72
+ return self
73
+
74
+ def __str__(self) -> str:
75
+ return f"TextInput({repr(self.text)})"
76
+
77
+ def _cursor_toggle(self, value: bool | None = None):
78
+ if value is None:
79
+ value = not self.show_cursor
80
+ self.show_cursor = value
81
+ self.dirty_surface = True
82
+
83
+ def do_on_click_down(self, button):
84
+ if button != 1:
85
+ return
86
+ self.get_focus()
87
+
88
+ def do_on_enter(self):
89
+ pygame.mouse.set_cursor(pygame.SYSTEM_CURSOR_IBEAM)
90
+
91
+ def do_on_exit(self):
92
+ pygame.mouse.set_cursor(bf.const.DEFAULT_CURSOR)
93
+
94
+ def do_on_get_focus(self):
95
+ self.cursor_timer.resume()
96
+ self._cursor_toggle(True)
97
+ self.old_key_repeat = pygame.key.get_repeat()
98
+ pygame.key.set_repeat(200, 50)
99
+
100
+ def do_on_lose_focus(self):
101
+ self.cursor_timer.pause()
102
+ self._cursor_toggle(False)
103
+ pygame.key.set_repeat(*self.old_key_repeat)
104
+
105
+ def get_line(self, line: int) -> str | None:
106
+ if line < 0:
107
+ return None
108
+ lines = self.text.split('\n')
109
+ if line >= len(lines):
110
+ return None
111
+ return lines[line]
112
+
113
+ def set_cursor_position(self, position: tuple[int, int]) -> Self:
114
+ x, y = position
115
+
116
+ lines = self.text.split('\n')
117
+ y = max(0, min(y, len(lines) - 1))
118
+ line_length = len(lines[y])
119
+ x = max(0, min(x, line_length))
120
+
121
+ self.cursor_position = (x, y)
122
+ self.show_cursor = True
123
+ self.dirty_shape = True
124
+ return self
125
+
126
+ def cursor_to_absolute(self, position: tuple[int, int]) -> int:
127
+ x, y = position
128
+
129
+ y = max(0, min(y, len(self.text.split('\n')) - 1))
130
+ lines = self.text.split('\n')
131
+ x = max(0, min(x, len(lines[y])))
132
+
133
+ absolute_position = sum(len(line) + 1 for line in lines[:y]) + x
134
+ return absolute_position
135
+
136
+ def absolute_to_cursor(self, absolute: int) -> tuple[int, int]:
137
+ text = self.text
138
+ lines = text.split('\n')
139
+ current_pos = 0
140
+
141
+ for line_no, line in enumerate(lines):
142
+ if absolute <= current_pos + len(line):
143
+ return (absolute - current_pos, line_no)
144
+ current_pos += len(line) + 1
145
+
146
+ return (len(lines[-1]), len(lines) - 1)
147
+
148
+ def do_handle_event(self, event):
149
+ if not self.is_focused:
150
+ return
151
+
152
+ if event.type not in [pygame.TEXTINPUT, pygame.KEYDOWN]:
153
+ return
154
+
155
+ text = self.get_text()
156
+ current = self.cursor_to_absolute(self.cursor_position)
157
+ if event.type == pygame.TEXTINPUT:
158
+ self.set_text(text[:current] + event.text + text[current:])
159
+ self.set_cursor_position(self.absolute_to_cursor(current + len(event.text)))
160
+ elif event.type == pygame.KEYDOWN:
161
+ pressed = pygame.key.get_pressed()
162
+ if event.key == pygame.K_ESCAPE:
163
+ self.lose_focus()
164
+ elif event.key == pygame.K_BACKSPACE:
165
+ if current > 0:
166
+ self.set_text(text[:current - 1] + text[current:])
167
+ self.set_cursor_position(self.absolute_to_cursor(current - 1))
168
+ elif event.key == pygame.K_DELETE:
169
+ if current < len(text):
170
+ self.set_text(text[:current] + text[current + 1:])
171
+ elif event.key == pygame.K_RIGHT:
172
+ if self.cursor_to_absolute(self.cursor_position)>=len(self.text):
173
+ return
174
+ if self.cursor_position[0] == len(self.get_line(self.cursor_position[1])):
175
+ self.set_cursor_position((0, self.cursor_position[1] + 1))
176
+ else:
177
+ if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
178
+ index = find_next_word(self.text,current)
179
+ if index ==-1 : index = current+1
180
+ self.set_cursor_position(self.absolute_to_cursor(index))
181
+ else:
182
+ self.set_cursor_position(self.absolute_to_cursor(current + 1))
183
+ elif event.key == pygame.K_LEFT:
184
+
185
+
186
+ if self.cursor_position[0] == 0 and self.cursor_position[1] > 0:
187
+ self.set_cursor_position((len(self.get_line(self.cursor_position[1] - 1)), self.cursor_position[1] - 1))
188
+ else:
189
+ if pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL]:
190
+ index = find_prev_word(self.text,current-1)
191
+ if index ==-1 : index = current-1
192
+ self.set_cursor_position(self.absolute_to_cursor(index))
193
+ else:
194
+ self.set_cursor_position(self.absolute_to_cursor(current - 1))
195
+ elif event.key == pygame.K_UP:
196
+ x, y = self.cursor_position
197
+ self.set_cursor_position((x, y - 1))
198
+ elif event.key == pygame.K_DOWN:
199
+ x, y = self.cursor_position
200
+ self.set_cursor_position((x, y + 1))
201
+ elif event.key == pygame.K_RETURN:
202
+ self.set_text(text[:current] + '\n' + text[current:])
203
+ self.set_cursor_position(self.absolute_to_cursor(current + 1))
204
+ else:
205
+ return
206
+ else:
207
+ return
208
+
209
+ event.consumed = True
210
+
211
+ def set_text(self, text: str) -> Self:
212
+ if self.on_modify:
213
+ text = self.on_modify(text)
214
+ return super().set_text(text)
215
+
216
+ def _paint_cursor(self) -> None:
217
+ if not self.font_object or not self.show_cursor:
218
+ return
219
+
220
+ lines = self.text.split('\n')
221
+ line_x, line_y = self.cursor_position
222
+
223
+ cursor_y = self.padding[1]
224
+ cursor_y += line_y * self.font_object.get_linesize()
225
+ cursor_x = self.padding[0]
226
+ cursor_x += self.font_object.size(lines[line_y][:line_x])[0] if line_x > 0 else 0
227
+
228
+ cursor_rect = pygame.Rect(cursor_x, cursor_y, 2, self.font_object.get_height())
229
+ pygame.draw.rect(self.surface, self.text_color, cursor_rect)
230
+
231
+ def paint(self) -> None:
232
+ super().paint()
233
+ self._paint_cursor()
234
+
235
+ # def set_alignment(self, alignment: bf.alignment) -> Self:
236
+ # return self
237
+
238
+ # def align_text(
239
+ # self, text_rect: pygame.FRect, area: pygame.FRect, alignment: bf.alignment
240
+ # ):
241
+ # if alignment == bf.alignment.LEFT:
242
+ # alignment = bf.alignment.MIDLEFT
243
+ # elif alignment == bf.alignment.MIDRIGHT:
244
+ # alignment = bf.alignment.MIDRIGHT
245
+ # pos = area.__getattribute__(alignment.value)
246
+ # text_rect.__setattr__(alignment.value, pos)
247
+ # return
@@ -1,62 +1,112 @@
1
1
  from .button import Button
2
- from .indicator import Indicator,ToggleIndicator
3
- import pygame
2
+ from .indicator import Indicator, ToggleIndicator
4
3
  import batFramework as bf
5
4
  from typing import Self
5
+ import pygame
6
+
6
7
 
7
8
  class Toggle(Button):
8
- def __init__(self,text:str,default_value : bool = False)->None:
9
- self.value :bool= default_value
10
- self.on_toggle = None
11
- self.indicator : Indicator=ToggleIndicator(default_value)
12
- self.gap :float|int = 0
13
- super().__init__(text,self.toggle)
14
- self.add_child(self.indicator)
15
- self.set_gap(int(max(4,self.get_content_width()/3)))
16
- # self.set_gap()
17
- # self.set_gap(20)
18
-
19
- def set_gap(self,value:int|float)->Self:
20
- if value < 0 : return self
9
+ def __init__(self, text: str = "", callback=None, default_value: bool = False) -> None:
10
+ self.value: bool = default_value
11
+ self.indicator: ToggleIndicator = ToggleIndicator(default_value)
12
+ self.gap: float | int = 0
13
+ self.spacing: bf.spacing = bf.spacing.MANUAL
14
+ super().__init__(text, callback)
15
+ self.add(self.indicator)
16
+ self.set_clip_children(False)
17
+ # self.set_gap(int(max(4, self.get_padded_width() / 3)))
18
+
19
+ def set_visible(self, value: bool) -> Self:
20
+ self.indicator.set_visible(value)
21
+ return super().set_visible(value)
22
+
23
+ def set_value(self, value: bool, do_callback=False) -> Self:
24
+ self.value = value
25
+ self.indicator.set_value(value)
26
+ self.dirty_surface = True
27
+ if do_callback and self.callback:
28
+ self.callback(self.value)
29
+ return self
30
+
31
+ def set_spacing(self, spacing: bf.spacing) -> Self:
32
+ if spacing == self.spacing:
33
+ return self
34
+ self.spacing = spacing
35
+ self.dirty_shape = True
36
+ return self
37
+
38
+ def click(self) -> None:
39
+ self.set_value(not self.value, True)
40
+
41
+ def set_gap(self, value: int | float) -> Self:
42
+ value = max(0, value)
43
+ if value == self.gap:
44
+ return self
21
45
  self.gap = value
22
- self.build()
23
- if self.parent : self.parent.children_modified()
46
+ self.dirty_shape = True
24
47
  return self
25
- def to_string_id(self)->str:
48
+
49
+ def __str__(self) -> str:
26
50
  return f"Toggle({self.value})"
27
-
28
- def toggle(self)->None:
29
- self.value = not self.value
30
- self.build()
31
- if self.on_toggle : self.on_toggle(self.value)
32
-
33
- def set_toggle_callback(self,callback)->Self:
34
- self.on_toggle = callback
35
- return self
36
51
 
37
- def _build_layout(self)->None:
38
- self.indicator.set_value(self.value)
39
-
52
+ def toggle(self) -> None:
53
+ self.set_value(not self.value, do_callback=True)
54
+
55
+ def get_min_required_size(self) -> tuple[float, float]:
56
+ if not self.text_rect:
57
+ self.text_rect.size = self._get_text_rect_required_size()
40
58
  size = (
41
- 0,
42
- 0,
43
- self._text_rect.w + self.indicator.rect.w + self.gap,
44
- max(self._text_rect.h, self.indicator.rect.h)
59
+ max(
60
+ self.indicator.get_min_required_size()[0],
61
+ self.text_rect.w + self.font_object.point_size + (self.gap if self.text else 0),
62
+ ),
63
+ self.text_rect.h,
64
+ )
65
+ return self.inflate_rect_by_padding((0, 0, *size)).size
66
+
67
+ def _build_layout(self) -> None:
68
+ gap = self.gap if self.text else 0
69
+ self.text_rect.size = self._get_text_rect_required_size()
70
+
71
+ #right part size
72
+ right_part_height = min(self.text_rect.h, self.font_object.point_size)
73
+ self.indicator.set_size_if_autoresize((right_part_height,right_part_height))
74
+
75
+ #join left and right
76
+ joined_rect = pygame.FRect(
77
+ 0, 0, self.text_rect.w + gap + self.indicator.rect.w, self.text_rect.h
45
78
  )
46
-
47
- required_rect = self.inflate_rect_by_padding(size)
48
-
49
- if self.autoresize and (self.rect.size != required_rect.size) :
50
- self.set_size(*required_rect.size)
51
- return
52
-
53
- required_rect = self.get_content_rect()
54
- required_rect_rel = self.get_content_rect_rel()
55
-
56
- self._text_rect.midleft = required_rect_rel.midleft
57
- r = self.indicator.rect.copy()
58
- r.midleft = required_rect.move(self._text_rect.w+self.gap,0).midleft
59
- self.indicator.set_position(*r.topleft)
60
-
61
- self.surface.blit(self._text_surface,self._text_rect)
62
-
79
+
80
+
81
+ if self.autoresize_h or self.autoresize_w:
82
+ target_rect = self.inflate_rect_by_padding(joined_rect)
83
+ if not self.autoresize_w:
84
+ target_rect.w = self.rect.w
85
+ if not self.autoresize_h:
86
+ target_rect.h = self.rect.h
87
+ if self.rect.size != target_rect.size:
88
+ self.set_size(target_rect.size)
89
+ self.build()
90
+ return
91
+
92
+ # ------------------------------------ size is ok
93
+
94
+ offset = self._get_outline_offset() if self.show_text_outline else (0,0)
95
+ padded_rect = self.get_padded_rect()
96
+ padded_relative = padded_rect.move(-self.rect.x, -self.rect.y)
97
+
98
+ self.align_text(joined_rect, padded_relative.move( offset), self.alignment)
99
+ self.text_rect.midleft = joined_rect.midleft
100
+
101
+ if self.text:
102
+ match self.spacing:
103
+ case bf.spacing.MAX:
104
+ gap = padded_relative.right - self.text_rect.right - self.indicator.rect.w
105
+ case bf.spacing.MIN:
106
+ gap = 0
107
+
108
+ pos = self.text_rect.move(
109
+ self.rect.x + gap -offset[0],
110
+ self.rect.y + (self.text_rect.h / 2) - (right_part_height/ 2) -offset[1],
111
+ ).topright
112
+ self.indicator.rect.topleft = pos