batframework 1.0.8a4__py3-none-any.whl → 1.0.8a6__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 (38) hide show
  1. batFramework/__init__.py +15 -1
  2. batFramework/animatedSprite.py +65 -50
  3. batFramework/character.py +27 -0
  4. batFramework/dynamicEntity.py +1 -0
  5. batFramework/enums.py +2 -2
  6. batFramework/fontManager.py +2 -2
  7. batFramework/gui/clickableWidget.py +6 -7
  8. batFramework/gui/constraints/constraints.py +125 -40
  9. batFramework/gui/image.py +14 -14
  10. batFramework/gui/interactiveWidget.py +15 -0
  11. batFramework/gui/label.py +44 -27
  12. batFramework/gui/layout.py +23 -14
  13. batFramework/gui/meter.py +10 -7
  14. batFramework/gui/radioButton.py +1 -1
  15. batFramework/gui/shape.py +3 -25
  16. batFramework/gui/slider.py +40 -30
  17. batFramework/gui/textInput.py +160 -50
  18. batFramework/gui/toggle.py +20 -22
  19. batFramework/gui/widget.py +65 -32
  20. batFramework/manager.py +17 -5
  21. batFramework/object.py +17 -8
  22. batFramework/particle.py +18 -4
  23. batFramework/scene.py +1 -1
  24. batFramework/sceneManager.py +42 -13
  25. batFramework/stateMachine.py +9 -6
  26. batFramework/templates/__init__.py +2 -0
  27. batFramework/templates/character.py +44 -0
  28. batFramework/templates/states.py +166 -0
  29. batFramework/time.py +30 -9
  30. batFramework/transition.py +2 -2
  31. batFramework/triggerZone.py +1 -1
  32. batFramework/utils.py +35 -6
  33. {batframework-1.0.8a4.dist-info → batframework-1.0.8a6.dist-info}/METADATA +3 -15
  34. batframework-1.0.8a6.dist-info/RECORD +62 -0
  35. {batframework-1.0.8a4.dist-info → batframework-1.0.8a6.dist-info}/WHEEL +1 -1
  36. batframework-1.0.8a4.dist-info/RECORD +0 -58
  37. {batframework-1.0.8a4.dist-info → batframework-1.0.8a6.dist-info}/LICENCE +0 -0
  38. {batframework-1.0.8a4.dist-info → batframework-1.0.8a6.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,15 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
3
  from .constraints.constraints import *
4
- from typing import Self, TYPE_CHECKING
4
+ from typing import Self, TYPE_CHECKING,override
5
+ from abc import ABC,abstractmethod
5
6
  import pygame
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from .container import Container
9
10
 
10
11
 
11
- class Layout:
12
+ class Layout(ABC):
12
13
  def __init__(self, parent: "Container" = None):
13
14
  self.parent = parent
14
15
  self.child_constraints: list[Constraint] = []
@@ -48,18 +49,10 @@ class Layout:
48
49
  return target_size
49
50
 
50
51
  def focus_next_child(self) -> None:
51
- l = self.parent.get_interactive_children()
52
- self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
53
- focused = l[self.parent.focused_index]
54
- focused.get_focus()
55
- self.scroll_to_widget(focused)
52
+ pass
56
53
 
57
54
  def focus_prev_child(self) -> None:
58
- l = self.parent.get_interactive_children()
59
- self.parent.focused_index = max(self.parent.focused_index - 1, 0)
60
- focused = l[self.parent.focused_index]
61
- focused.get_focus()
62
- self.scroll_to_widget(focused)
55
+ pass
63
56
 
64
57
  def scroll_to_widget(self, widget: Widget) -> None:
65
58
  padded = self.parent.get_padded_rect()
@@ -74,7 +67,23 @@ class Layout:
74
67
  pass
75
68
 
76
69
 
77
- class Column(Layout):
70
+ class SingleAxisLayout(Layout):
71
+ def focus_next_child(self) -> None:
72
+ l = self.parent.get_interactive_children()
73
+ self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
74
+ focused = l[self.parent.focused_index]
75
+ focused.get_focus()
76
+ self.scroll_to_widget(focused)
77
+
78
+ def focus_prev_child(self) -> None:
79
+ l = self.parent.get_interactive_children()
80
+ self.parent.focused_index = max(self.parent.focused_index - 1, 0)
81
+ focused = l[self.parent.focused_index]
82
+ focused.get_focus()
83
+ self.scroll_to_widget(focused)
84
+
85
+
86
+ class Column(SingleAxisLayout):
78
87
  def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
79
88
  super().__init__()
80
89
  self.gap = gap
@@ -157,7 +166,7 @@ class Column(Layout):
157
166
  self.parent.clamp_scroll()
158
167
 
159
168
 
160
- class Row(Layout):
169
+ class Row(SingleAxisLayout):
161
170
  def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
162
171
  super().__init__()
163
172
  self.gap = gap
batFramework/gui/meter.py CHANGED
@@ -17,9 +17,11 @@ class Meter(Shape):
17
17
  self.snap: bool = False
18
18
  self.value = self.max_value
19
19
  self.content = Shape((0, 0)).set_color(bf.color.BLUE)
20
+ self.content.set_debug_color("cyan")
20
21
  self.content.top_at = lambda x, y: custom_top_at(self.content, x, y)
21
22
  self.add(self.content)
22
- self.set_padding(1)
23
+ self.set_padding(4)
24
+ self.set_color("gray20")
23
25
  self.set_outline_width(1)
24
26
  self.set_outline_color(bf.color.BLACK)
25
27
  self.set_debug_color("pink")
@@ -38,18 +40,19 @@ class Meter(Shape):
38
40
  f"[Warning] : minimum value {range_min} is greater than or equal to maximum value {range_max}"
39
41
  )
40
42
  return self
41
- self.min_value, self.max_value = range_min, range_max
43
+ self.min_value = range_min
44
+ self.max_value = range_max
42
45
  self.dirty_shape = True
43
46
 
44
47
  def get_debug_outlines(self):
45
48
  yield from super().get_debug_outlines()
46
- yield from self.content.get_debug_outlines()
49
+ # yield from self.content.get_debug_outlines()
47
50
 
48
51
  def set_value(self, value: float) -> Self:
49
52
  value = max(self.min_value, min(self.max_value, value))
50
53
  value = round(value / self.step) * self.step
51
- self.value = value
52
- self.dirty_surface = True
54
+ self.value = round(value,10)
55
+ self.dirty_shape = True
53
56
  return self
54
57
 
55
58
  def get_value(self) -> float:
@@ -59,12 +62,12 @@ class Meter(Shape):
59
62
  return self.max_value - self.min_value
60
63
 
61
64
  def get_ratio(self) -> float:
62
- return self.value / (self.max_value - self.min_value)
65
+ return (self.value-self.min_value) / (self.max_value - self.min_value)
63
66
 
64
67
  def _build_content(self) -> None:
65
68
  width = self.get_padded_width() * self.get_ratio()
66
69
  self.content.set_size((width, self.get_padded_height()))
67
- self.content.set_position(*self.get_padded_rect().topleft)
70
+ self.content.rect.topleft = self.get_padded_rect().topleft
68
71
 
69
72
  def build(self) -> None:
70
73
  super().build()
@@ -7,7 +7,7 @@ class RadioVariable: ...
7
7
 
8
8
 
9
9
  class RadioButton(Toggle):
10
- def __init__(self, text: str, radio_value: Any = None) -> None:
10
+ def __init__(self, text: str = "", radio_value: Any = None) -> None:
11
11
  super().__init__(text, None, False)
12
12
  self.radio_value: Any = (
13
13
  radio_value if radio_value is not None else text if text else None
batFramework/gui/shape.py CHANGED
@@ -43,15 +43,10 @@ class Shape(Widget):
43
43
  def set_relief(self, relief: int) -> Self:
44
44
  if relief < 0:
45
45
  return self
46
- if self.relief == relief:
47
- return self
46
+ self.dirty_shape = self.relief != relief
48
47
  self.relief = relief
49
- self.dirty_shape = True
50
48
  return self
51
49
 
52
- def get_relief(self) -> int:
53
- return self.relief
54
-
55
50
  def set_texture(
56
51
  self, surface: pygame.SurfaceType, subsize: tuple[int, int] | None = None
57
52
  ) -> Self:
@@ -223,8 +218,8 @@ class Shape(Widget):
223
218
  return pygame.FRect(0, self.rect.h - self.relief, self.rect.w, self.relief)
224
219
 
225
220
  def _paint_shape(self) -> None:
221
+ self.surface.fill((0, 0, 0, 0))
226
222
  if self.relief!=0:
227
- self.surface.fill((0, 0, 0, 0))
228
223
  self.surface.fill(self.shadow_color, self._get_base_rect())
229
224
  self.surface.fill(self.color, self._get_elevated_rect())
230
225
  else:
@@ -242,27 +237,10 @@ class Shape(Widget):
242
237
  pygame.draw.rect(self.surface, self.color, e, 0, *self.border_radius)
243
238
 
244
239
  def _paint_outline(self) -> None:
245
- if self.relief:
246
- pygame.draw.rect(
247
- self.surface,
248
- self.outline_color,
249
- (
250
- 0,
251
- self.relief - self.get_relief(),
252
- self.rect.w,
253
- self.rect.h - self.relief,
254
- ),
255
- self.outline_width,
256
- )
257
240
  pygame.draw.rect(
258
241
  self.surface,
259
242
  self.outline_color,
260
- (
261
- 0,
262
- self.relief - self.get_relief(),
263
- self.rect.w,
264
- self.rect.h - (self.relief - self.get_relief()),
265
- ),
243
+ self._get_elevated_rect(),
266
244
  self.outline_width,
267
245
  )
268
246
 
@@ -12,11 +12,13 @@ class SliderHandle(Indicator, DraggableWidget):
12
12
  super().__init__()
13
13
  self.set_color(bf.color.CLOUD_SHADE)
14
14
  self.old_key_repeat: tuple = (0, 0)
15
-
15
+ self.parent : bf.ClickableWidget = self.parent
16
16
  def __str__(self) -> str:
17
17
  return "SliderHandle"
18
18
 
19
19
  def on_click_down(self, button: int) -> None:
20
+ if not self.parent.is_enabled():
21
+ return
20
22
  super().on_click_down(button)
21
23
  if button == 1:
22
24
  self.parent.get_focus()
@@ -28,6 +30,8 @@ class SliderHandle(Indicator, DraggableWidget):
28
30
  def do_on_drag(
29
31
  self, drag_start: tuple[float, float], drag_end: tuple[float, float]
30
32
  ) -> None:
33
+ if not self.parent.is_enabled():
34
+ return
31
35
  super().do_on_drag(drag_start, drag_end)
32
36
  m: Meter = self.parent.meter
33
37
  r = m.get_padded_rect()
@@ -43,21 +47,20 @@ class SliderHandle(Indicator, DraggableWidget):
43
47
 
44
48
 
45
49
  class SliderMeter(Meter, InteractiveWidget):
46
-
47
50
  def __str__(self) -> str:
48
51
  return "SliderMeter"
49
52
 
50
53
  def on_click_down(self, button: int) -> None:
54
+ if not self.parent.is_enabled():
55
+ return
51
56
  if button == 1:
52
57
  self.parent.get_focus()
53
58
  r = self.get_root()
54
59
  if r:
55
60
  pos = r.drawing_camera.screen_to_world(pygame.mouse.get_pos())[0]
56
61
  self.parent.set_value(self.parent.position_to_value(pos))
57
-
58
62
  self.do_on_click_down(button)
59
63
 
60
-
61
64
  class Slider(Button):
62
65
  def __init__(self, text: str, default_value: float = 1.0) -> None:
63
66
  super().__init__(text, None)
@@ -92,13 +95,6 @@ class Slider(Button):
92
95
  super().do_on_lose_focus()
93
96
  pygame.key.set_repeat(*self.old_key_repeat)
94
97
 
95
- def get_min_required_size(self) -> tuple[float, float]:
96
- gap = self.gap if self.text else 0
97
- if not self.text_rect:
98
- self.text_rect.size = self._get_text_rect_required_size()
99
- w, h = self.text_rect.size
100
- return self.inflate_rect_by_padding((0, 0, w + gap + self.meter.rect.w, h)).size
101
-
102
98
  def set_spacing(self, spacing: bf.spacing) -> Self:
103
99
  if spacing == self.spacing:
104
100
  return self
@@ -112,10 +108,12 @@ class Slider(Button):
112
108
 
113
109
  def set_range(self, range_min: float, range_max: float) -> Self:
114
110
  self.meter.set_range(range_min, range_max)
111
+ self.dirty_shape = True
115
112
  return self
116
113
 
117
114
  def set_step(self, step: float) -> Self:
118
115
  self.meter.set_step(step)
116
+ self.dirty_shape = True
119
117
  return self
120
118
 
121
119
  def set_value(self, value, no_callback: bool = False) -> Self:
@@ -130,12 +128,16 @@ class Slider(Button):
130
128
  return self.meter.get_value()
131
129
 
132
130
  def do_on_key_down(self, key):
131
+ if not self.is_enabled():
132
+ return
133
133
  if key == pygame.K_RIGHT:
134
134
  self.set_value(self.meter.get_value() + self.meter.step)
135
135
  elif key == pygame.K_LEFT:
136
136
  self.set_value(self.meter.get_value() - self.meter.step)
137
137
 
138
138
  def do_on_click_down(self, button) -> None:
139
+ if not self.is_enabled():
140
+ return
139
141
  if button == 1:
140
142
  self.get_focus()
141
143
 
@@ -168,22 +170,34 @@ class Slider(Button):
168
170
  value = self.meter.min_value + position_ratio * value_range
169
171
  return round(value / self.meter.step) * self.meter.step
170
172
 
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
+ return self.inflate_rect_by_padding((0, 0, w + gap + self.meter.get_min_required_size()[1], h)).size
179
+
171
180
  def _build_layout(self) -> None:
172
181
 
173
182
  gap = self.gap if self.text else 0
174
-
175
183
  self.text_rect.size = self._get_text_rect_required_size()
176
184
 
177
- meter_size = [self.text_rect.h * 10, self.font_object.point_size]
185
+ #right part size
186
+ meter_width = self.text_rect.h * 10
178
187
  if not self.autoresize_w:
179
- meter_size[0] = self.get_padded_width() - self.text_rect.w - gap
188
+ meter_width = self.get_padded_width() - self.text_rect.w - gap
189
+ right_part_height = min(self.text_rect.h, self.font_object.point_size)
190
+ self.meter.set_size_if_autoresize((meter_width,right_part_height))
191
+ self.handle.set_size_if_autoresize((None,right_part_height))
180
192
 
181
- tmp_rect = pygame.FRect(
182
- 0, 0, self.text_rect.w + gap + meter_size[0], self.text_rect.h
193
+
194
+ #join left and right
195
+ joined_rect = pygame.FRect(
196
+ 0, 0, self.text_rect.w + gap + meter_width, self.text_rect.h
183
197
  )
184
198
 
185
199
  if self.autoresize_h or self.autoresize_w:
186
- target_rect = self.inflate_rect_by_padding(tmp_rect)
200
+ target_rect = self.inflate_rect_by_padding(joined_rect)
187
201
  if not self.autoresize_w:
188
202
  target_rect.w = self.rect.w
189
203
  if not self.autoresize_h:
@@ -194,14 +208,14 @@ class Slider(Button):
194
208
  return
195
209
 
196
210
  # ------------------------------------ size is ok
211
+
212
+
213
+ offset = self._get_outline_offset() if self.show_text_outline else (0,0)
197
214
  padded_rect = self.get_padded_rect()
198
215
  padded_relative = padded_rect.move(-self.rect.x, -self.rect.y)
199
216
 
200
-
201
- self.meter.set_size_if_autoresize(meter_size)
202
-
203
- self.align_text(tmp_rect, padded_relative, self.alignment)
204
- self.text_rect.midleft = tmp_rect.midleft
217
+ self.align_text(joined_rect, padded_relative.move( offset), self.alignment)
218
+ self.text_rect.midleft = joined_rect.midleft
205
219
 
206
220
  if self.text:
207
221
  match self.spacing:
@@ -212,17 +226,13 @@ class Slider(Button):
212
226
 
213
227
  # place meter
214
228
 
215
- self.meter.set_position(
216
- *self.text_rect.move(
217
- self.rect.x + gap,
218
- self.rect.y + (self.text_rect.h / 2) - meter_size[1] / 2,
229
+ pos = self.text_rect.move(
230
+ self.rect.x + gap -offset[0],
231
+ self.rect.y + (self.text_rect.h / 2) - (right_part_height / 2) -offset[1],
219
232
  ).topright
220
- )
233
+ self.meter.rect.topleft = pos
221
234
  # place handle
222
235
 
223
- # print(self.meter.rect.top - self.rect.top)
224
- # print(self.meter.rect.h)
225
-
226
236
  x = self.value_to_position(self.meter.value)
227
237
  r = self.meter.get_padded_rect()
228
238
  self.handle.set_center(x, r.centery)
@@ -4,27 +4,77 @@ from .label import Label
4
4
  from .interactiveWidget import InteractiveWidget
5
5
  import pygame
6
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
+
7
56
 
8
57
  class TextInput(Label, InteractiveWidget):
9
58
  def __init__(self) -> None:
10
- self.cursor_position = 0
11
- self.old_key_repeat: tuple = (0, 0)
59
+ self.cursor_position = (0, 0)
60
+ self.old_key_repeat = (0, 0)
12
61
  self.cursor_timer = bf.Timer(0.3, self._cursor_toggle, loop=True).start()
13
62
  self.cursor_timer.pause()
14
- self.show_cursor: bool = False
63
+ self.show_cursor = False
15
64
  self.on_modify: Callable[[str], str] = None
16
65
  self.set_focusable(True)
17
66
  self.set_outline_color("black")
18
67
  super().__init__("")
68
+ self.alignment = bf.alignment.TOPLEFT
19
69
 
20
70
  def set_modify_callback(self, callback: Callable[[str], str]) -> Self:
21
71
  self.on_modify = callback
22
72
  return self
23
73
 
24
74
  def __str__(self) -> str:
25
- return f"TextInput({self.text})"
75
+ return f"TextInput({repr(self.text)})"
26
76
 
27
- def _cursor_toggle(self, value: bool = None):
77
+ def _cursor_toggle(self, value: bool | None = None):
28
78
  if value is None:
29
79
  value = not self.show_cursor
30
80
  self.show_cursor = value
@@ -44,7 +94,6 @@ class TextInput(Label, InteractiveWidget):
44
94
  def do_on_get_focus(self):
45
95
  self.cursor_timer.resume()
46
96
  self._cursor_toggle(True)
47
- self.set_cursor_position(len(self.get_text()))
48
97
  self.old_key_repeat = pygame.key.get_repeat()
49
98
  pygame.key.set_repeat(200, 50)
50
99
 
@@ -53,42 +102,105 @@ class TextInput(Label, InteractiveWidget):
53
102
  self._cursor_toggle(False)
54
103
  pygame.key.set_repeat(*self.old_key_repeat)
55
104
 
56
- def set_cursor_position(self, position: int) -> Self:
57
- if position < 0:
58
- position = 0
59
- elif position > len(self.get_text()):
60
- position = len(self.get_text())
61
- self.cursor_position = position
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)
62
122
  self.show_cursor = True
63
- self.dirty_surface = True
64
- if self.text_rect.w > self.get_padded_width():
65
- self.dirty_shape = True
123
+ self.dirty_shape = True
66
124
  return self
67
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
+
68
148
  def do_handle_event(self, event):
69
149
  if not self.is_focused:
70
150
  return
71
- text = self.get_text()
72
- cursor_position = self.cursor_position
73
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)
74
157
  if event.type == pygame.TEXTINPUT:
75
- self.set_text(text[:cursor_position] + event.text + text[cursor_position:])
76
- self.set_cursor_position(cursor_position + 1)
158
+ self.set_text(text[:current] + event.text + text[current:])
159
+ self.set_cursor_position(self.absolute_to_cursor(current + len(event.text)))
77
160
  elif event.type == pygame.KEYDOWN:
161
+ pressed = pygame.key.get_pressed()
78
162
  if event.key == pygame.K_ESCAPE:
79
163
  self.lose_focus()
80
-
81
164
  elif event.key == pygame.K_BACKSPACE:
82
- if cursor_position > 0:
83
- self.set_text(text[: cursor_position - 1] + text[cursor_position:])
84
- self.set_cursor_position(cursor_position - 1)
85
-
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:])
86
171
  elif event.key == pygame.K_RIGHT:
87
- self.set_cursor_position(cursor_position + 1)
88
-
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))
89
183
  elif event.key == pygame.K_LEFT:
90
- self.set_cursor_position(cursor_position - 1)
91
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))
92
204
  else:
93
205
  return
94
206
  else:
@@ -104,34 +216,32 @@ class TextInput(Label, InteractiveWidget):
104
216
  def _paint_cursor(self) -> None:
105
217
  if not self.font_object or not self.show_cursor:
106
218
  return
107
- partial_text_size = self.font_object.size(
108
- self.get_text()[: self.cursor_position]
109
- )
110
219
 
111
- cursor_rect = pygame.Rect(0, 0, 1, self.font_object.point_size)
112
- if self.cursor_position != 0: # align left properly
113
- cursor_rect.midleft = self.text_rect.move(partial_text_size[0], 0).midleft
114
- else:
115
- cursor_rect.midright = self.text_rect.midleft
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
116
227
 
228
+ cursor_rect = pygame.Rect(cursor_x, cursor_y, 2, self.font_object.get_height())
117
229
  pygame.draw.rect(self.surface, self.text_color, cursor_rect)
118
230
 
119
231
  def paint(self) -> None:
120
232
  super().paint()
121
233
  self._paint_cursor()
122
234
 
123
- def align_text(
124
- self, text_rect: pygame.FRect, area: pygame.FRect, alignment: bf.alignment
125
- ):
126
- if alignment == bf.alignment.LEFT:
127
- alignment = bf.alignment.MIDLEFT
128
- elif alignment == bf.alignment.MIDRIGHT:
129
- alignment = bf.alignment.MIDRIGHT
130
-
131
- pos = area.__getattribute__(alignment.value)
132
- text_rect.__setattr__(alignment.value, pos)
133
- w = self.font_object.size(self.get_text()[: self.cursor_position])[0]
134
- if self.text_rect.x + w > area.right:
135
- self.text_rect.right = area.right
136
- elif self.text_rect.x + w < area.left:
137
- self.text_rect.left = area.left - w
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