batframework 1.0.8a3__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 (44) hide show
  1. batFramework/__init__.py +16 -2
  2. batFramework/animatedSprite.py +94 -85
  3. batFramework/audioManager.py +2 -2
  4. batFramework/character.py +27 -0
  5. batFramework/cutscene.py +5 -2
  6. batFramework/cutsceneBlocks.py +3 -5
  7. batFramework/dynamicEntity.py +11 -4
  8. batFramework/enums.py +2 -2
  9. batFramework/fontManager.py +2 -2
  10. batFramework/gui/clickableWidget.py +10 -9
  11. batFramework/gui/constraints/constraints.py +282 -57
  12. batFramework/gui/image.py +14 -14
  13. batFramework/gui/interactiveWidget.py +16 -1
  14. batFramework/gui/label.py +58 -37
  15. batFramework/gui/layout.py +23 -14
  16. batFramework/gui/meter.py +10 -7
  17. batFramework/gui/radioButton.py +1 -1
  18. batFramework/gui/root.py +7 -1
  19. batFramework/gui/shape.py +21 -37
  20. batFramework/gui/slider.py +52 -58
  21. batFramework/gui/textInput.py +161 -51
  22. batFramework/gui/toggle.py +27 -41
  23. batFramework/gui/widget.py +68 -35
  24. batFramework/manager.py +17 -5
  25. batFramework/object.py +17 -8
  26. batFramework/particle.py +22 -8
  27. batFramework/resourceManager.py +18 -2
  28. batFramework/scene.py +50 -20
  29. batFramework/sceneManager.py +52 -28
  30. batFramework/scrollingSprite.py +7 -8
  31. batFramework/stateMachine.py +9 -6
  32. batFramework/templates/__init__.py +2 -0
  33. batFramework/templates/character.py +44 -0
  34. batFramework/templates/states.py +166 -0
  35. batFramework/time.py +54 -28
  36. batFramework/transition.py +25 -12
  37. batFramework/triggerZone.py +1 -1
  38. batFramework/utils.py +92 -2
  39. {batframework-1.0.8a3.dist-info → batframework-1.0.8a6.dist-info}/METADATA +3 -15
  40. batframework-1.0.8a6.dist-info/RECORD +62 -0
  41. {batframework-1.0.8a3.dist-info → batframework-1.0.8a6.dist-info}/WHEEL +1 -1
  42. batframework-1.0.8a3.dist-info/RECORD +0 -58
  43. {batframework-1.0.8a3.dist-info → batframework-1.0.8a6.dist-info}/LICENCE +0 -0
  44. {batframework-1.0.8a3.dist-info → batframework-1.0.8a6.dist-info}/top_level.txt +0 -0
@@ -11,11 +11,14 @@ class SliderHandle(Indicator, DraggableWidget):
11
11
  def __init__(self):
12
12
  super().__init__()
13
13
  self.set_color(bf.color.CLOUD_SHADE)
14
-
14
+ self.old_key_repeat: tuple = (0, 0)
15
+ self.parent : bf.ClickableWidget = self.parent
15
16
  def __str__(self) -> str:
16
17
  return "SliderHandle"
17
18
 
18
19
  def on_click_down(self, button: int) -> None:
20
+ if not self.parent.is_enabled():
21
+ return
19
22
  super().on_click_down(button)
20
23
  if button == 1:
21
24
  self.parent.get_focus()
@@ -27,6 +30,8 @@ class SliderHandle(Indicator, DraggableWidget):
27
30
  def do_on_drag(
28
31
  self, drag_start: tuple[float, float], drag_end: tuple[float, float]
29
32
  ) -> None:
33
+ if not self.parent.is_enabled():
34
+ return
30
35
  super().do_on_drag(drag_start, drag_end)
31
36
  m: Meter = self.parent.meter
32
37
  r = m.get_padded_rect()
@@ -42,21 +47,20 @@ class SliderHandle(Indicator, DraggableWidget):
42
47
 
43
48
 
44
49
  class SliderMeter(Meter, InteractiveWidget):
45
-
46
50
  def __str__(self) -> str:
47
51
  return "SliderMeter"
48
52
 
49
53
  def on_click_down(self, button: int) -> None:
54
+ if not self.parent.is_enabled():
55
+ return
50
56
  if button == 1:
51
57
  self.parent.get_focus()
52
58
  r = self.get_root()
53
59
  if r:
54
60
  pos = r.drawing_camera.screen_to_world(pygame.mouse.get_pos())[0]
55
61
  self.parent.set_value(self.parent.position_to_value(pos))
56
-
57
62
  self.do_on_click_down(button)
58
63
 
59
-
60
64
  class Slider(Button):
61
65
  def __init__(self, text: str, default_value: float = 1.0) -> None:
62
66
  super().__init__(text, None)
@@ -68,8 +72,6 @@ class Slider(Button):
68
72
  self.add(self.meter, self.handle)
69
73
  self.meter.set_debug_color(bf.color.RED)
70
74
  self.set_value(default_value, True)
71
- # print(self.handle.rect)
72
- # self.handle.set_visible(False)
73
75
 
74
76
  def set_visible(self, value: bool) -> Self:
75
77
  self.handle.set_visible(value)
@@ -84,22 +86,14 @@ class Slider(Button):
84
86
  self.gap = value
85
87
  return self
86
88
 
87
- def get_min_required_size(self) -> tuple[float, float]:
88
- gap = self.gap if self.text else 0
89
- if not self.text_rect:
90
- params = {
91
- "font_name": self.font_object.name,
92
- "text": self.text,
93
- "antialias": False,
94
- "color": "white",
95
- "bgcolor": "black", # if (self.has_alpha_color() or self.draw_mode == bf.drawMode.TEXTURED) else self.color,
96
- "wraplength": (
97
- int(self.get_padded_width()) if self.auto_wraplength else 0
98
- ),
99
- }
100
- self.text_rect.size = self._render_font(params).get_size()
101
- w, h = self.text_rect.size
102
- return self.inflate_rect_by_padding((0, 0, w + gap + self.meter.rect.w, h)).size
89
+ def do_on_get_focus(self) -> None:
90
+ super().do_on_get_focus()
91
+ self.old_key_repeat = pygame.key.get_repeat()
92
+ pygame.key.set_repeat(200, 50)
93
+
94
+ def do_on_lose_focus(self) -> None:
95
+ super().do_on_lose_focus()
96
+ pygame.key.set_repeat(*self.old_key_repeat)
103
97
 
104
98
  def set_spacing(self, spacing: bf.spacing) -> Self:
105
99
  if spacing == self.spacing:
@@ -114,10 +108,12 @@ class Slider(Button):
114
108
 
115
109
  def set_range(self, range_min: float, range_max: float) -> Self:
116
110
  self.meter.set_range(range_min, range_max)
111
+ self.dirty_shape = True
117
112
  return self
118
113
 
119
114
  def set_step(self, step: float) -> Self:
120
115
  self.meter.set_step(step)
116
+ self.dirty_shape = True
121
117
  return self
122
118
 
123
119
  def set_value(self, value, no_callback: bool = False) -> Self:
@@ -132,14 +128,16 @@ class Slider(Button):
132
128
  return self.meter.get_value()
133
129
 
134
130
  def do_on_key_down(self, key):
131
+ if not self.is_enabled():
132
+ return
135
133
  if key == pygame.K_RIGHT:
136
134
  self.set_value(self.meter.get_value() + self.meter.step)
137
- bf.AudioManager().play_sound(self.click_down_sound)
138
135
  elif key == pygame.K_LEFT:
139
136
  self.set_value(self.meter.get_value() - self.meter.step)
140
- bf.AudioManager().play_sound(self.click_down_sound)
141
137
 
142
138
  def do_on_click_down(self, button) -> None:
139
+ if not self.is_enabled():
140
+ return
143
141
  if button == 1:
144
142
  self.get_focus()
145
143
 
@@ -151,8 +149,6 @@ class Slider(Button):
151
149
  value_range = self.meter.get_range()
152
150
  value = round(value / self.meter.step) * self.meter.step
153
151
  position_ratio = (value - self.meter.min_value) / value_range
154
- # print(self.handle.rect)
155
- # print(rect.left + (self.handle.rect.w/2) + position_ratio * (rect.width - self.handle.rect.w),self.handle.rect.w,self.rect.right)
156
152
  return (
157
153
  rect.left
158
154
  + (self.handle.rect.w / 2)
@@ -174,31 +170,34 @@ class Slider(Button):
174
170
  value = self.meter.min_value + position_ratio * value_range
175
171
  return round(value / self.meter.step) * self.meter.step
176
172
 
177
- def _build_layout(self) -> None:
178
-
173
+ def get_min_required_size(self) -> tuple[float, float]:
179
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
180
179
 
181
- params = {
182
- "font_name": self.font_object.name,
183
- "text": self.text,
184
- "antialias": False,
185
- "color": "white",
186
- "bgcolor": "black", # if (self.has_alpha_color() or self.draw_mode == bf.drawMode.TEXTURED) else self.color,
187
- "wraplength": int(self.get_padded_width()) if self.auto_wraplength else 0,
188
- }
180
+ def _build_layout(self) -> None:
189
181
 
190
- self.text_rect.size = self._render_font(params).get_size()
182
+ gap = self.gap if self.text else 0
183
+ self.text_rect.size = self._get_text_rect_required_size()
191
184
 
192
- meter_size = [self.text_rect.h * 10, self.font_object.point_size]
185
+ #right part size
186
+ meter_width = self.text_rect.h * 10
193
187
  if not self.autoresize_w:
194
- 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))
192
+
195
193
 
196
- tmp_rect = pygame.FRect(
197
- 0, 0, self.text_rect.w + gap + meter_size[0], self.text_rect.h
194
+ #join left and right
195
+ joined_rect = pygame.FRect(
196
+ 0, 0, self.text_rect.w + gap + meter_width, self.text_rect.h
198
197
  )
199
198
 
200
199
  if self.autoresize_h or self.autoresize_w:
201
- target_rect = self.inflate_rect_by_padding(tmp_rect)
200
+ target_rect = self.inflate_rect_by_padding(joined_rect)
202
201
  if not self.autoresize_w:
203
202
  target_rect.w = self.rect.w
204
203
  if not self.autoresize_h:
@@ -209,36 +208,31 @@ class Slider(Button):
209
208
  return
210
209
 
211
210
  # ------------------------------------ size is ok
212
- padded = self.get_padded_rect().move(-self.rect.x, -self.rect.y)
213
-
214
- self.meter.set_size_if_autoresize(meter_size)
215
- handle_size = 2 * [self.meter.get_padded_height()]
211
+
216
212
 
217
- self.handle.set_size_if_autoresize(handle_size)
213
+ offset = self._get_outline_offset() if self.show_text_outline else (0,0)
214
+ padded_rect = self.get_padded_rect()
215
+ padded_relative = padded_rect.move(-self.rect.x, -self.rect.y)
218
216
 
219
- self.align_text(tmp_rect, padded, self.alignment)
220
- 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
221
219
 
222
220
  if self.text:
223
221
  match self.spacing:
224
222
  case bf.spacing.MAX:
225
- gap = padded.w - self.text_rect.w - self.meter.rect.w
223
+ gap = padded_relative.right - self.text_rect.right - self.meter.rect.w
226
224
  case bf.spacing.MIN:
227
225
  gap = 0
228
226
 
229
227
  # place meter
230
228
 
231
- self.meter.set_position(
232
- *self.text_rect.move(
233
- self.rect.x + gap,
234
- 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],
235
232
  ).topright
236
- )
233
+ self.meter.rect.topleft = pos
237
234
  # place handle
238
235
 
239
- # print(self.meter.rect.top - self.rect.top)
240
- # print(self.meter.rect.h)
241
-
242
236
  x = self.value_to_position(self.meter.value)
243
237
  r = self.meter.get_padded_rect()
244
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
@@ -42,10 +92,9 @@ class TextInput(Label, InteractiveWidget):
42
92
  pygame.mouse.set_cursor(bf.const.DEFAULT_CURSOR)
43
93
 
44
94
  def do_on_get_focus(self):
45
- self.old_key_repeat = pygame.key.get_repeat()
46
95
  self.cursor_timer.resume()
47
96
  self._cursor_toggle(True)
48
- self.set_cursor_position(len(self.get_text()))
97
+ self.old_key_repeat = pygame.key.get_repeat()
49
98
  pygame.key.set_repeat(200, 50)
50
99
 
51
100
  def do_on_lose_focus(self):
@@ -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
@@ -6,13 +6,14 @@ import pygame
6
6
 
7
7
 
8
8
  class Toggle(Button):
9
- def __init__(self, text: str, callback=None, default_value: bool = False) -> None:
9
+ def __init__(self, text: str = "", callback=None, default_value: bool = False) -> None:
10
10
  self.value: bool = default_value
11
11
  self.indicator: ToggleIndicator = ToggleIndicator(default_value)
12
12
  self.gap: float | int = 0
13
13
  self.spacing: bf.spacing = bf.spacing.MANUAL
14
14
  super().__init__(text, callback)
15
15
  self.add(self.indicator)
16
+ self.set_clip_children(False)
16
17
  # self.set_gap(int(max(4, self.get_padded_width() / 3)))
17
18
 
18
19
  def set_visible(self, value: bool) -> Self:
@@ -53,51 +54,32 @@ class Toggle(Button):
53
54
 
54
55
  def get_min_required_size(self) -> tuple[float, float]:
55
56
  if not self.text_rect:
56
- params = {
57
- "font_name": self.font_object.name,
58
- "text": self.text,
59
- "antialias": False,
60
- "color": "white",
61
- "bgcolor": "black", # if (self.has_alpha_color() or self.draw_mode == bf.drawMode.TEXTURED) else self.color,
62
- "wraplength": (
63
- int(self.get_padded_width()) if self.auto_wraplength else 0
64
- ),
65
- }
66
- self.text_rect.size = self._render_font(params).get_size()
67
- w, h = self.text_rect.size
57
+ self.text_rect.size = self._get_text_rect_required_size()
68
58
  size = (
69
59
  max(
70
- self.indicator.rect.w,
71
- w + self.font_object.point_size + (self.gap if self.text else 0),
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),
72
62
  ),
73
63
  self.text_rect.h,
74
64
  )
75
65
  return self.inflate_rect_by_padding((0, 0, *size)).size
76
66
 
77
67
  def _build_layout(self) -> None:
78
-
79
68
  gap = self.gap if self.text else 0
69
+ self.text_rect.size = self._get_text_rect_required_size()
80
70
 
81
- params = {
82
- "font_name": self.font_object.name,
83
- "text": self.text,
84
- "antialias": False,
85
- "color": "white",
86
- "bgcolor": "black", # if (self.has_alpha_color() or self.draw_mode == bf.drawMode.TEXTURED) else self.color,
87
- "wraplength": int(self.get_padded_width()) if self.auto_wraplength else 0,
88
- }
89
- self.text_rect.size = self._render_font(params).get_size()
90
-
91
- indicator_height = self.font_object.point_size
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))
92
74
 
93
- self.indicator.set_size((indicator_height, indicator_height))
94
-
95
- tmp_rect = pygame.FRect(
96
- 0, 0, self.text_rect.w + gap + indicator_height, self.text_rect.h
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
97
78
  )
98
79
 
80
+
99
81
  if self.autoresize_h or self.autoresize_w:
100
- target_rect = self.inflate_rect_by_padding(tmp_rect)
82
+ target_rect = self.inflate_rect_by_padding(joined_rect)
101
83
  if not self.autoresize_w:
102
84
  target_rect.w = self.rect.w
103
85
  if not self.autoresize_h:
@@ -107,20 +89,24 @@ class Toggle(Button):
107
89
  self.build()
108
90
  return
109
91
 
110
- padded = self.get_padded_rect().move(-self.rect.x, -self.rect.y)
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
111
100
 
112
- self.align_text(tmp_rect, padded, self.alignment)
113
- self.text_rect.midleft = tmp_rect.midleft
114
101
  if self.text:
115
102
  match self.spacing:
116
103
  case bf.spacing.MAX:
117
- gap = padded.w - self.text_rect.w - self.indicator.rect.w
104
+ gap = padded_relative.right - self.text_rect.right - self.indicator.rect.w
118
105
  case bf.spacing.MIN:
119
106
  gap = 0
120
107
 
121
- self.indicator.set_position(
122
- *self.text_rect.move(
123
- self.rect.x + gap,
124
- self.rect.y + (self.text_rect.h / 2) - self.indicator.rect.h / 2,
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],
125
111
  ).topright
126
- )
112
+ self.indicator.rect.topleft = pos