batframework 1.0.8a2__py3-none-any.whl → 1.0.8a4__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 (65) hide show
  1. batFramework/__init__.py +53 -50
  2. batFramework/action.py +126 -99
  3. batFramework/actionContainer.py +53 -9
  4. batFramework/animatedSprite.py +117 -73
  5. batFramework/audioManager.py +69 -26
  6. batFramework/camera.py +259 -69
  7. batFramework/constants.py +16 -54
  8. batFramework/cutscene.py +39 -29
  9. batFramework/cutsceneBlocks.py +36 -43
  10. batFramework/dynamicEntity.py +17 -9
  11. batFramework/easingController.py +58 -0
  12. batFramework/entity.py +48 -97
  13. batFramework/enums.py +113 -0
  14. batFramework/fontManager.py +65 -0
  15. batFramework/gui/__init__.py +10 -2
  16. batFramework/gui/button.py +9 -78
  17. batFramework/gui/clickableWidget.py +221 -0
  18. batFramework/gui/constraints/__init__.py +1 -0
  19. batFramework/gui/constraints/constraints.py +730 -0
  20. batFramework/gui/container.py +174 -32
  21. batFramework/gui/debugger.py +131 -43
  22. batFramework/gui/dialogueBox.py +99 -0
  23. batFramework/gui/draggableWidget.py +40 -0
  24. batFramework/gui/image.py +54 -18
  25. batFramework/gui/indicator.py +38 -21
  26. batFramework/gui/interactiveWidget.py +177 -13
  27. batFramework/gui/label.py +292 -74
  28. batFramework/gui/layout.py +219 -60
  29. batFramework/gui/meter.py +71 -0
  30. batFramework/gui/radioButton.py +84 -0
  31. batFramework/gui/root.py +134 -38
  32. batFramework/gui/shape.py +259 -57
  33. batFramework/gui/slider.py +230 -0
  34. batFramework/gui/style.py +10 -0
  35. batFramework/gui/styleManager.py +48 -0
  36. batFramework/gui/textInput.py +137 -0
  37. batFramework/gui/toggle.py +103 -51
  38. batFramework/gui/widget.py +329 -254
  39. batFramework/manager.py +40 -19
  40. batFramework/object.py +114 -0
  41. batFramework/particle.py +101 -0
  42. batFramework/renderGroup.py +67 -0
  43. batFramework/resourceManager.py +100 -0
  44. batFramework/scene.py +281 -123
  45. batFramework/sceneManager.py +141 -108
  46. batFramework/scrollingSprite.py +114 -0
  47. batFramework/sprite.py +51 -0
  48. batFramework/stateMachine.py +2 -2
  49. batFramework/tileset.py +46 -0
  50. batFramework/time.py +123 -58
  51. batFramework/transition.py +195 -124
  52. batFramework/utils.py +87 -151
  53. batframework-1.0.8a4.dist-info/LICENCE +21 -0
  54. batframework-1.0.8a4.dist-info/METADATA +55 -0
  55. batframework-1.0.8a4.dist-info/RECORD +58 -0
  56. batFramework/debugger.py +0 -48
  57. batFramework/easing.py +0 -71
  58. batFramework/gui/constraints.py +0 -204
  59. batFramework/gui/frame.py +0 -19
  60. batFramework/particles.py +0 -77
  61. batFramework/transitionManager.py +0 -0
  62. batframework-1.0.8a2.dist-info/METADATA +0 -58
  63. batframework-1.0.8a2.dist-info/RECORD +0 -42
  64. {batframework-1.0.8a2.dist-info → batframework-1.0.8a4.dist-info}/WHEEL +0 -0
  65. {batframework-1.0.8a2.dist-info → batframework-1.0.8a4.dist-info}/top_level.txt +0 -0
@@ -1,40 +1,57 @@
1
1
  from .shape import Shape
2
- from typing import Any
2
+ from typing import Any, Self
3
3
  import pygame
4
- # from .constraints import ConstraintAspectRatio
4
+ from .widget import Widget
5
+ from .interactiveWidget import InteractiveWidget
6
+ from .draggableWidget import DraggableWidget
7
+ import batFramework as bf
8
+
5
9
 
6
10
  class Indicator(Shape):
7
- def __init__(self,width:int|float= 10,height:int|float=10)->None:
8
- super().__init__(width,height)
11
+ def __init__(self, size: tuple[int | float] = (10, 10)) -> None:
12
+ super().__init__(size)
13
+ self.debug_color = "magenta"
14
+ self.set_outline_width(1)
15
+ self.set_outline_color("black")
9
16
 
10
- def to_string_id(self)->str:
17
+ def to_string_id(self) -> str:
11
18
  return "Indicator"
12
19
 
13
- def set_value(self,value:Any)->None:
20
+ def set_value(self, value: Any) -> None:
14
21
  pass
15
22
 
16
- def get_value(self)->Any:
23
+ def get_value(self) -> Any:
17
24
  pass
18
25
 
19
- def _build_indicator(self)->None:
20
- pass
21
-
22
- def build(self)->None:
23
- super().build()
24
- self._build_indicator()
26
+ def top_at(self, x, y):
27
+ return None
25
28
 
26
29
 
27
30
  class ToggleIndicator(Indicator):
28
- def __init__(self,default_value:bool)->None:
29
- self.value:bool = default_value
30
- super().__init__(20,20)
31
+ def __init__(self, default_value: bool) -> None:
32
+ self.value: bool = default_value
33
+ self.callback = lambda val: self.set_color("green" if val else "red")
34
+ super().__init__((20, 20))
35
+ self.set_value(default_value)
36
+
37
+ # TODO aspect ratio would be good right about here
31
38
  # self.add_constraint(ConstraintAspectRatio(1))
32
39
 
33
- def set_value(self,value)->None:
40
+ def set_callback(self, callback) -> Self:
41
+ self.callback = callback
42
+ return self
43
+
44
+ def set_value(self, value: bool) -> None:
34
45
  self.value = value
35
- self.set_color("green" if value else "red")
36
- self.build()
46
+ if self.callback:
47
+ self.callback(value)
48
+ self.dirty_surface = True
37
49
 
38
- def get_value(self)->bool:
50
+ def get_value(self) -> bool:
39
51
  return self.value
40
- #
52
+
53
+ def top_at(self, x: float, y: float) -> "None|Widget":
54
+ r = super().top_at(x, y)
55
+ if r is self:
56
+ return None
57
+ return r
@@ -1,22 +1,186 @@
1
1
  from .widget import Widget
2
+ from typing import Self
3
+ from typing import TYPE_CHECKING
4
+ import pygame
5
+ from math import cos
6
+
7
+ if TYPE_CHECKING:
8
+ from .container import Container
9
+ import batFramework as bf
10
+
2
11
 
3
12
  class InteractiveWidget(Widget):
4
- def __init__(self,*args,**kwargs):
5
- super().__init__(convert_alpha = True)
13
+ def __init__(self, *args, **kwargs) -> None:
14
+ self.is_focused: bool = False
15
+ self.is_hovered: bool = False
16
+ self.is_clicked_down: bool = False
17
+ self.focused_index = 0
6
18
  self.focusable = True
7
- self.is_focused : bool = False
19
+ super().__init__(*args, **kwargs)
20
+
21
+ def set_focusable(self, value: bool) -> Self:
22
+ self.focusable = value
23
+ return self
24
+
25
+ def allow_focus_to_self(self) -> bool:
26
+ return self.visible
27
+
28
+ def get_focus(self) -> bool:
29
+ if self.focusable and ((r := self.get_root()) is not None):
30
+ r.focus_on(self)
31
+ if self.parent and isinstance(self.parent, InteractiveWidget):
32
+ self.parent.set_focused_child(self)
33
+ return True
34
+ return False
8
35
 
9
- def get_focus(self)->bool:
10
- if self.parent is None or not self.focusable: return False
11
- self.get_root().focus_on(self)
36
+ def lose_focus(self) -> bool:
37
+ if self.is_focused and ((r := self.get_root()) is not None):
38
+ r.focus_on(None)
39
+ return True
40
+ return False
12
41
 
13
- def on_get_focus(self)->None:
42
+ def on_get_focus(self) -> None:
14
43
  self.is_focused = True
44
+ self.do_on_get_focus()
15
45
 
16
- def on_lose_focus(self)->None:
46
+ def on_lose_focus(self) -> None:
17
47
  self.is_focused = False
18
-
19
- def lose_focus(self)->bool:
20
- if self.is_focused and self.parent is not None:
21
- self.get_root().focus_on(None)
22
-
48
+ self.do_on_lose_focus()
49
+
50
+ def focus_next_tab(self, previous_widget):
51
+
52
+ if previous_widget != self and self.visible:
53
+ if (
54
+ isinstance(self, InteractiveWidget)
55
+ and not isinstance(self, bf.Container)
56
+ and self.allow_focus_to_self()
57
+ ):
58
+ self.focus_next_sibling()
59
+ return
60
+ i_children = [
61
+ c
62
+ for c in self.children
63
+ if isinstance(c, InteractiveWidget) and c.visible
64
+ ]
65
+ if i_children:
66
+ index = i_children.index(previous_widget)
67
+ if index < len(i_children) - 1:
68
+
69
+ i_children[index + 1].get_focus()
70
+ return
71
+
72
+ if self.parent:
73
+ self.parent.focus_next_tab(self)
74
+
75
+ def focus_prev_tab(self, previous_widget):
76
+ if previous_widget != self and self.visible:
77
+ if (
78
+ isinstance(self, InteractiveWidget)
79
+ and not isinstance(self, bf.Container)
80
+ and self.allow_focus_to_self()
81
+ ):
82
+ self.get_focus()
83
+ return
84
+ i_children = [
85
+ c
86
+ for c in self.children
87
+ if isinstance(c, InteractiveWidget) and c.visible
88
+ ]
89
+
90
+ if i_children:
91
+ index = i_children.index(previous_widget)
92
+ if index > 0:
93
+ i_children[index - 1].get_focus()
94
+ return
95
+
96
+ if self.parent:
97
+ self.parent.focus_prev_tab(self)
98
+
99
+ def focus_next_sibling(self) -> None:
100
+ if isinstance(self.parent, bf.Container):
101
+ self.parent.focus_next_child()
102
+
103
+ def focus_prev_sibling(self) -> None:
104
+ if isinstance(self.parent, bf.Container):
105
+ self.parent.focus_prev_child()
106
+
107
+ def on_key_down(self, key) -> bool:
108
+ if key == pygame.K_DOWN:
109
+ self.focus_next_sibling()
110
+ elif key == pygame.K_UP:
111
+ self.focus_prev_sibling()
112
+ elif key == pygame.K_TAB and self.parent:
113
+ keys = pygame.key.get_pressed()
114
+ if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
115
+
116
+ self.focus_prev_tab(self)
117
+ else:
118
+ self.focus_next_tab(self)
119
+
120
+ else:
121
+
122
+ return self.do_on_key_down(key)
123
+
124
+ return False
125
+
126
+ def on_key_up(self, key) -> bool:
127
+ return self.do_on_key_up(key)
128
+
129
+ def do_on_key_down(self, key) -> bool:
130
+ return False
131
+
132
+ def do_on_key_up(self, key) -> bool:
133
+ return False
134
+
135
+ def do_on_get_focus(self) -> None:
136
+ pass
137
+
138
+ def do_on_lose_focus(self) -> None:
139
+ pass
140
+
141
+ def on_click_down(self, button: int) -> bool:
142
+ self.is_clicked_down = True
143
+ return self.do_on_click_down(button)
144
+
145
+ def on_click_up(self, button: int) -> bool:
146
+ self.is_clicked_down = False
147
+ return self.do_on_click_up(button)
148
+
149
+ def do_on_click_down(self, button: int) -> bool:
150
+ return False
151
+
152
+ def do_on_click_up(self, button: int) -> bool:
153
+ return False
154
+
155
+ def on_enter(self) -> None:
156
+ self.is_hovered = True
157
+ self.do_on_enter()
158
+
159
+ def on_exit(self) -> None:
160
+ self.is_hovered = False
161
+ self.is_clicked_down = False
162
+ self.do_on_exit()
163
+
164
+ def do_on_enter(self) -> None:
165
+ pass
166
+
167
+ def do_on_exit(self) -> None:
168
+ pass
169
+
170
+ def on_mouse_motion(self, x, y) -> None:
171
+ self.do_on_mouse_motion(x, y)
172
+
173
+ def do_on_mouse_motion(self, x, y) -> None:
174
+ pass
175
+
176
+ def set_focused_child(self, child: "InteractiveWidget"):
177
+ pass
178
+
179
+ def draw_focused(self, camera: bf.Camera) -> None:
180
+ delta = 4 + ((2 * cos(pygame.time.get_ticks() / 100)) // 2) * 2
181
+ pygame.draw.rect(
182
+ camera.surface,
183
+ "white",
184
+ self.rect.move(-camera.rect.x, -camera.rect.y),#.inflate(delta, delta),
185
+ 1,
186
+ )
batFramework/gui/label.py CHANGED
@@ -3,108 +3,326 @@ import pygame
3
3
  from .shape import Shape
4
4
  from typing import Self
5
5
 
6
+
6
7
  class Label(Shape):
7
- def __init__(self,text:str) -> None:
8
- self._text = ""
8
+ _text_cache = {}
9
+
10
+ def __init__(self, text: str = "") -> None:
11
+ self.text = text
12
+
13
+ self.resized_flag: bool = False
14
+
9
15
  # Enable/Disable antialiasing
10
- self._antialias : bool = True
11
-
12
- self._text_size = bf.const.DEFAULT_TEXT_SIZE
13
-
14
- self._text_color : tuple[int,int,int]|str = "black"
16
+ self.antialias: bool = bf.FontManager().DEFAULT_ANTIALIAS
17
+
18
+ self.text_size = bf.FontManager().DEFAULT_TEXT_SIZE
19
+
20
+ self.auto_wraplength: bool = False
21
+
22
+ self.alignment: bf.alignment = bf.alignment.CENTER
23
+
24
+ self.text_color: tuple[int, int, int] | str = "black"
25
+
26
+ self.text_outline_color: tuple[int, int, int] | str = "gray50"
27
+
28
+ self.text_outline_surface: pygame.Surface = None
29
+
30
+ self._text_outline_mask = pygame.Mask((3, 3), fill=True)
31
+
15
32
  # font name (given when loaded by utils) to use for the text
16
- self._font_name = None
33
+ self.font_name = None
17
34
  # reference to the font object
18
- self._font_object = None
35
+ self.font_object = None
19
36
  # Rect containing the text of the label
20
- self._text_rect = None
37
+ self.text_rect = pygame.FRect(0, 0, 0, 0)
21
38
  # text surface (result of font.render)
22
- self._text_surface : pygame.Surface | None= None
23
- super().__init__(width=0,height=0)
24
- self.set_padding((10,4))
39
+ self.text_surface: pygame.Surface = pygame.Surface((0, 0))
40
+ self.do_caching: bool = False
41
+
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__((0, 0))
51
+ self.set_padding((10, 4))
25
52
  self.set_debug_color("blue")
26
- self.set_color("white")
53
+ self.set_color("gray50")
27
54
  self.set_autoresize(True)
28
55
  self.set_font(force=True)
29
- self.set_text(text)
30
56
 
31
- def set_text_color(self,color)->Self:
32
- self._text_color = color
33
- self.build()
57
+ @staticmethod
58
+ def clear_cache():
59
+ Label._text_cache = {}
60
+
61
+ def __str__(self) -> str:
62
+ return f"Label({self.text})"
63
+
64
+ def enable_caching(self) -> Self:
65
+ self.do_caching = True
34
66
  return self
35
67
 
36
- def to_string_id(self)->str:
37
- return f"Label({self._text})"
68
+ def disable_caching(self) -> Self:
69
+ self.do_caching = False
70
+ return self
38
71
 
72
+ def set_text_color(self, color) -> Self:
73
+ self.text_color = color
74
+ self.dirty_surface = True
75
+ return self
39
76
 
40
- def get_bounding_box(self):
41
- yield from super().get_bounding_box()
42
- if self._text_rect : yield self._text_rect.move(*self.rect.topleft)
77
+ def set_italic(self, value: bool) -> Self:
78
+ if value == self.is_italic:
79
+ return self
80
+ self.is_italic = value
81
+ if self.autoresize_h or self.autoresize_w:
82
+ self.dirty_shape = True
83
+ else:
84
+ self.dirty_surface = True
85
+ return self
43
86
 
44
- def set_font(self,font_name:str=None,force:bool = False)-> "Label":
45
- if font_name == self._font_name and not force: return self
46
- self._font_name = font_name
47
- self._font_object = bf.utils.get_font(self._font_name,self._text_size)
48
- self.build()
87
+ def set_bold(self, value: bool) -> Self:
88
+ if value == self.is_bold:
89
+ return self
90
+ self.is_bold = value
91
+ if self.autoresize_h or self.autoresize_w:
92
+ self.dirty_shape = True
93
+ else:
94
+ self.dirty_surface = True
49
95
  return self
50
96
 
51
- def set_text_size(self,text_size:int) -> "Label":
52
- text_size = round(text_size/2) * 2
53
- if text_size == self._text_size : return self
54
- self._text_size = text_size
55
- self._font_object = bf.utils.get_font(self._font_name,self._text_size)
56
- self.build()
97
+ def set_underlined(self, value: bool) -> Self:
98
+ if value == self.is_underlined:
99
+ return self
100
+ self.is_underlined = value
101
+ self.dirty_surface = True
57
102
  return self
58
103
 
59
- def get_text_size(self)-> int:
60
- return self._text_size
104
+ def set_text_outline_mask_size(self,size:tuple[int,int])->Self:
105
+ old_size = self._text_outline_mask.get_size()
106
+ m = [[self._text_outline_mask.get_at((x,y)) for x in range(min(old_size[0],size[0]))] for y in range(min(old_size[1],size[1]))]
107
+ self._text_outline_mask = pygame.Mask(size, fill=True)
108
+ self.set_text_outline_matrix(m)
61
109
 
62
- def is_antialias(self)->bool:
63
- return self._antialias
110
+ def set_text_outline_matrix(self, matrix: list[list[0 | 1]]) -> Self:
111
+ if matrix is None:
112
+ matrix = [[0 for _ in range(3)] for _ in range(3)]
113
+ for y in range(3):
114
+ for x in range(3):
115
+ self._text_outline_mask.set_at((x, y), matrix[2 - y][2 - x])
116
+ self.dirty_surface = True
117
+ return self
64
118
 
65
- def set_antialias(self,value:bool)->"Label":
66
- self._antialias = value
67
- self.build()
119
+ def set_text_outline_color(self, color) -> Self:
120
+ self.text_outline_color = color
121
+ self.dirty_surface = True
68
122
  return self
69
123
 
70
- def set_text(self,text:str) -> "Label":
71
- if text == self._text : return self
72
- self._text = text
73
- self.build()
124
+ def enable_text_outline(self) -> Self:
125
+ self.show_text_outline = True
126
+ self.dirty_surface = True
74
127
  return self
75
-
76
- def get_text(self)->str:
77
- return self._text
78
128
 
129
+ def disable_text_outline(self) -> Self:
130
+ self.show_text_outline = False
131
+ self.dirty_surface = True
132
+ return self
79
133
 
80
- def _build_text(self)-> None:
81
- if self._font_object is None:
82
- print("No font :(")
83
- return
84
- # render(text, antialias, color, bgcolor=None, wraplength=0) -> Surface
85
- self._text_surface = self._font_object.render(
86
- text = self._text,
87
- antialias = self._antialias,
88
- color = self._text_color,
89
- bgcolor = self._color,
90
- wraplength = 0
134
+ def set_alignment(self, alignment: bf.alignment) -> Self:
135
+ self.alignment = alignment
136
+ self.dirty_surface = True
137
+ return self
138
+
139
+ def set_auto_wraplength(self, val: bool) -> Self:
140
+ self.auto_wraplength = val
141
+ if self.autoresize_h or self.autoresize_w:
142
+ self.dirty_shape = True
143
+ else:
144
+ self.dirty_surface = True
145
+ return self
146
+
147
+ def get_debug_outlines(self):
148
+ if self.visible:
149
+ yield (self.text_rect.move(*self.rect.topleft), "purple")
150
+ yield from super().get_debug_outlines()
151
+
152
+ def set_font(self, font_name: str = None, force: bool = False) -> Self:
153
+ if font_name == self.font_name and not force:
154
+ return self
155
+ self.font_name = font_name
156
+ self.font_object = bf.FontManager().get_font(self.font_name, self.text_size)
157
+ if self.autoresize_h or self.autoresize_w:
158
+ self.dirty_shape = True
159
+ else:
160
+ self.dirty_surface = True
161
+ return self
162
+
163
+ def set_text_size(self, text_size: int) -> Self:
164
+ text_size = round(text_size / 2) * 2
165
+ if text_size == self.text_size:
166
+ return self
167
+ self.text_size = text_size
168
+ self.font_object = bf.FontManager().get_font(self.font_name, self.text_size)
169
+ if self.autoresize_h or self.autoresize_w:
170
+ self.dirty_shape = True
171
+ return self
172
+
173
+ def get_text_size(self) -> int:
174
+ return self.text_size
175
+
176
+ def is_antialias(self) -> bool:
177
+ return self.antialias
178
+
179
+ def set_antialias(self, value: bool) -> Self:
180
+ self.antialias = value
181
+ self.dirty_surface = True
182
+ return self
183
+
184
+ def set_text(self, text: str) -> Self:
185
+ if text == self.text:
186
+ return self
187
+ self.text = text
188
+ self.dirty_shape = True
189
+ return self
190
+
191
+ def get_min_required_size(self) -> tuple[float, float]:
192
+ if not (self.autoresize_w or self.autoresize_h):
193
+ return self.rect.size
194
+ if not self.text_rect:
195
+ self.text_rect.size = self._get_text_rect_required_size()
196
+ res = self.inflate_rect_by_padding((0, 0, *self.text_rect.size)).size
197
+ # return res
198
+ return res[0] if self.autoresize_w else self.rect.w, (
199
+ res[1] if self.autoresize_h else self.rect.h
91
200
  )
92
- self._text_rect = self._text_surface.get_frect()
93
-
94
- def _build_layout(self)->None:
95
- if self.autoresize:
96
- if self.rect.size != self.inflate_rect_by_padding(self._text_rect).size :
97
- self.set_size(
98
- self._text_rect.w + self.padding[0]+self.padding[2],
99
- self._text_rect.h + self.padding[1]+self.padding[3]
100
- )
201
+
202
+ def get_text(self) -> str:
203
+ return self.text
204
+
205
+ def _render_font(self, params: dict) -> pygame.Surface:
206
+ key = tuple(params.values())
207
+
208
+ cached_value = Label._text_cache.get(key, None)
209
+
210
+ if self.draw_mode == bf.drawMode.SOLID:
211
+ if cached_value is None:
212
+ params.pop("font_name")
213
+
214
+ # save old settings
215
+ old_italic = self.font_object.get_italic()
216
+ old_bold = self.font_object.get_bold()
217
+ old_underline = self.font_object.get_underline()
218
+
219
+ # setup font
220
+ self.font_object.set_italic(self.is_italic)
221
+ self.font_object.set_bold(self.is_bold)
222
+ self.font_object.set_underline(self.is_underlined)
223
+
224
+ surf = self.font_object.render(**params)
225
+
226
+ # reset font
227
+ self.font_object.set_italic(old_italic)
228
+ self.font_object.set_bold(old_bold)
229
+ self.font_object.set_underline(old_underline)
230
+
231
+ if self.do_caching:
232
+ Label._text_cache[key] = surf
233
+ else:
234
+ surf = cached_value
235
+ else:
236
+ params.pop("font_name")
237
+ surf = self.font_object.render(**params)
238
+
239
+ return surf
240
+ def _get_text_rect_required_size(self):
241
+ params = {
242
+ "font_name": self.font_object.name,
243
+ "text": self.text,
244
+ "antialias": False,
245
+ "color": "white",
246
+ "bgcolor": "black", # if (self.has_alpha_color() or self.draw_mode == bf.drawMode.TEXTURED) else self.color,
247
+ "wraplength": int(self.get_padded_width()) if self.auto_wraplength else 0,
248
+ }
249
+
250
+ size = self._render_font(params).get_size()
251
+ s = self._text_outline_mask.get_size()
252
+ return size[0] + s[0]//2, size[1] + s[1]//2
253
+ def _build_layout(self) -> None:
254
+
255
+ self.text_rect.size = self._get_text_rect_required_size()
256
+
257
+ if self.autoresize_h or self.autoresize_w:
258
+ target_rect = self.inflate_rect_by_padding((0, 0, *self.text_rect.size))
259
+ if not self.autoresize_w:
260
+ target_rect.w = self.rect.w
261
+ if not self.autoresize_h:
262
+ target_rect.h = self.rect.h
263
+ if self.rect.size != target_rect.size:
264
+ self.set_size(target_rect.size)
265
+ self.build()
101
266
  return
102
- self._text_rect.center = self.get_content_rect_rel().center
103
- self.surface.blit(self._text_surface,self._text_rect)
104
-
105
- def build(self)->None:
267
+ padded = self.get_padded_rect().move(-self.rect.x, -self.rect.y)
268
+ self.align_text(self.text_rect, padded, self.alignment)
269
+
270
+ def _get_outline_offset(self)->tuple[int,int]:
271
+ mask_size = self._text_outline_mask.get_size()
272
+ return mask_size[0]//2,mask_size[1]//2
273
+
274
+ def _paint_text(self) -> None:
275
+ if self.font_object is None:
276
+ print(f"No font for widget with text : '{self}' :(")
277
+ return
278
+
279
+ params = {
280
+ "font_name": self.font_object.name,
281
+ "text": self.text,
282
+ "antialias": self.antialias,
283
+ "color": self.text_color,
284
+ "bgcolor": None, # if (self.has_alpha_color() or self.draw_mode == bf.drawMode.TEXTURED) else self.color,
285
+ "wraplength": int(self.get_padded_width()) if self.auto_wraplength else 0,
286
+ }
287
+ self.text_surface = self._render_font(params)
288
+
289
+ if self.show_text_outline:
290
+ self.text_outline_surface = (
291
+ pygame.mask.from_surface(self.text_surface)
292
+ .convolve(self._text_outline_mask)
293
+ .to_surface(setcolor=self.text_outline_color, unsetcolor=(0, 0, 0, 0))
294
+ )
295
+
296
+ l = []
297
+ outline_offset = self._get_outline_offset()
298
+ if self.show_text_outline:
299
+ l.append(
300
+ (
301
+ self.text_outline_surface,
302
+ self.text_rect.move(0, self.relief - self.get_relief()),
303
+ )
304
+ )
305
+ l.append(
306
+ (self.text_surface, self.text_rect.move(outline_offset[0], self.relief - self.get_relief() + outline_offset[1]))
307
+ )
308
+ self.surface.fblits(l)
309
+
310
+ def align_text(
311
+ self, text_rect: pygame.FRect, area: pygame.FRect, alignment: bf.alignment
312
+ ):
313
+ if alignment == bf.alignment.LEFT:
314
+ alignment = bf.alignment.MIDLEFT
315
+ elif alignment == bf.alignment.MIDRIGHT:
316
+ alignment = bf.alignment.MIDRIGHT
317
+
318
+ pos = area.__getattribute__(alignment.value)
319
+ text_rect.__setattr__(alignment.value, pos)
320
+
321
+ def build(self) -> None:
106
322
  super().build()
107
- if not self._font_object:return
108
- self._build_text()
109
323
  self._build_layout()
110
324
 
325
+ def paint(self) -> None:
326
+ super().paint()
327
+ if self.font_object:
328
+ self._paint_text()