batframework 1.0.8a1__py3-none-any.whl → 1.0.8a3__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 (46) hide show
  1. batFramework/action.py +30 -14
  2. batFramework/actionContainer.py +5 -3
  3. batFramework/audioManager.py +0 -1
  4. batFramework/camera.py +29 -24
  5. batFramework/cutscene.py +2 -4
  6. batFramework/entity.py +9 -2
  7. batFramework/enums.py +11 -2
  8. batFramework/fontManager.py +1 -1
  9. batFramework/gui/__init__.py +3 -1
  10. batFramework/gui/button.py +5 -2
  11. batFramework/gui/clickableWidget.py +42 -29
  12. batFramework/gui/constraints/constraints.py +269 -57
  13. batFramework/gui/container.py +39 -21
  14. batFramework/gui/debugger.py +18 -11
  15. batFramework/gui/dialogueBox.py +20 -17
  16. batFramework/gui/draggableWidget.py +7 -5
  17. batFramework/gui/image.py +27 -15
  18. batFramework/gui/indicator.py +1 -4
  19. batFramework/gui/interactiveWidget.py +91 -30
  20. batFramework/gui/label.py +44 -35
  21. batFramework/gui/layout.py +115 -43
  22. batFramework/gui/meter.py +3 -8
  23. batFramework/gui/radioButton.py +47 -25
  24. batFramework/gui/root.py +50 -25
  25. batFramework/gui/shape.py +14 -19
  26. batFramework/gui/slider.py +70 -44
  27. batFramework/gui/style.py +10 -0
  28. batFramework/gui/styleManager.py +48 -0
  29. batFramework/gui/textInput.py +25 -22
  30. batFramework/gui/toggle.py +42 -29
  31. batFramework/gui/widget.py +176 -115
  32. batFramework/object.py +9 -10
  33. batFramework/renderGroup.py +7 -2
  34. batFramework/scene.py +70 -49
  35. batFramework/sceneManager.py +15 -20
  36. batFramework/scrollingSprite.py +5 -3
  37. batFramework/sprite.py +20 -14
  38. batFramework/stateMachine.py +1 -2
  39. batFramework/tileset.py +5 -5
  40. batFramework/utils.py +12 -10
  41. {batframework-1.0.8a1.dist-info → batframework-1.0.8a3.dist-info}/METADATA +1 -1
  42. batframework-1.0.8a3.dist-info/RECORD +58 -0
  43. {batframework-1.0.8a1.dist-info → batframework-1.0.8a3.dist-info}/WHEEL +1 -1
  44. batframework-1.0.8a1.dist-info/RECORD +0 -56
  45. {batframework-1.0.8a1.dist-info → batframework-1.0.8a3.dist-info}/LICENCE +0 -0
  46. {batframework-1.0.8a1.dist-info → batframework-1.0.8a3.dist-info}/top_level.txt +0 -0
batFramework/action.py CHANGED
@@ -1,10 +1,9 @@
1
- from typing import Any,Self
1
+ from typing import Any, Self
2
2
  from enum import Enum
3
3
  import pygame
4
4
  from .enums import actionType
5
5
 
6
6
 
7
-
8
7
  class Action:
9
8
  def __init__(self, name: str) -> None:
10
9
  """
@@ -16,7 +15,7 @@ class Action:
16
15
  self.name: str = name
17
16
  self.active: bool = False
18
17
  self.data: dict = {}
19
- self.consume_event : bool = False
18
+ self.consume_event: bool = False
20
19
  self._type: actionType = actionType.INSTANTANEOUS
21
20
  self._key_control: set = set()
22
21
  self._mouse_control: set = set()
@@ -46,16 +45,14 @@ class Action:
46
45
  self.active = value
47
46
  # self._holding = set()
48
47
 
49
-
50
- def add_event_control(self,*events)->Self:
48
+ def add_event_control(self, *events) -> Self:
51
49
  self._event_control.update(events)
52
50
  return self
53
51
 
54
- def remove_event_control(self,*events)->Self:
52
+ def remove_event_control(self, *events) -> Self:
55
53
  self._event_control = self._event_control - events
56
54
  return self
57
55
 
58
-
59
56
  def add_key_control(self, *keys) -> Self:
60
57
  """
61
58
  Add key controls to the action.
@@ -89,7 +86,7 @@ class Action:
89
86
  self.add_key_control(new_key)
90
87
  return self
91
88
 
92
- def add_mouse_control(self, *mouse_buttons: int) -> Self:
89
+ def add_mouse_control(self, *mouse: int) -> Self:
93
90
  """
94
91
  Add mouse control to the action.
95
92
 
@@ -99,7 +96,18 @@ class Action:
99
96
  Returns:
100
97
  Action: The updated Action object for method chaining.
101
98
  """
102
- self._mouse_control.update(mouse_buttons)
99
+ self._mouse_control.update(mouse)
100
+ return self
101
+
102
+ def remove_mouse_control(self, *mouse: int) -> Self:
103
+ self._mouse_control = self._mouse_control - set(mouse)
104
+ return self
105
+
106
+ def replace_mouse_control(self, mouse, new_mouse) -> Self:
107
+ if not mouse in self._mouse_control:
108
+ return self
109
+ self.remove_mouse_control(mouse)
110
+ self.add_mouse_control(new_mouse)
103
111
  return self
104
112
 
105
113
  def set_continuous(self) -> Self:
@@ -183,7 +191,9 @@ class Action:
183
191
  if event.type == pygame.KEYDOWN and event.key in self._key_control:
184
192
  self._activate_action(event.key)
185
193
 
186
- elif event.type == pygame.MOUSEBUTTONDOWN and event.button in self._mouse_control:
194
+ elif (
195
+ event.type == pygame.MOUSEBUTTONDOWN and event.button in self._mouse_control
196
+ ):
187
197
  self._activate_action(event.button)
188
198
 
189
199
  elif event.type == pygame.MOUSEMOTION and event.type in self._mouse_control:
@@ -194,7 +204,8 @@ class Action:
194
204
  self.data = event.dict
195
205
  else:
196
206
  return
197
- if self.consume_event : event.consumed = True
207
+ if self.consume_event:
208
+ event.consumed = True
198
209
 
199
210
  def _activate_action(self, control):
200
211
  self.active = True
@@ -212,7 +223,10 @@ class Action:
212
223
  if self._type == actionType.HOLDING:
213
224
  if event.type == pygame.KEYUP and event.key in self._key_control:
214
225
  self._deactivate_action(event.key)
215
- elif event.type == pygame.MOUSEBUTTONUP and event.button in self._mouse_control:
226
+ elif (
227
+ event.type == pygame.MOUSEBUTTONUP
228
+ and event.button in self._mouse_control
229
+ ):
216
230
  self._deactivate_action(event.button)
217
231
  elif event.type == pygame.MOUSEMOTION and event.type in self._mouse_control:
218
232
  self._deactivate_action(event.type)
@@ -221,7 +235,8 @@ class Action:
221
235
  else:
222
236
  event.consumed = False
223
237
 
224
- if self.consume_event: event.consumed = True
238
+ if self.consume_event:
239
+ event.consumed = True
225
240
 
226
241
  def _deactivate_action(self, control) -> bool:
227
242
  if control in self._holding:
@@ -237,7 +252,8 @@ class Action:
237
252
  event (pygame.event.Event): The pygame event to process.
238
253
  """
239
254
 
240
- if event.consumed : return
255
+ if event.consumed:
256
+ return
241
257
  if not self.active:
242
258
  self.process_activate(event)
243
259
  else:
@@ -34,11 +34,13 @@ class ActionContainer:
34
34
  )
35
35
 
36
36
  def process_event(self, event):
37
- if event.consumed : return
37
+ if event.consumed:
38
+ return
38
39
  for action in self._actions.values():
39
40
  action.process_event(event)
40
- if event.consumed == True:break
41
-
41
+ if event.consumed == True:
42
+ break
43
+
42
44
  def reset(self):
43
45
  for action in self._actions.values():
44
46
  action.reset()
@@ -24,7 +24,6 @@ class AudioManager(metaclass=bf.Singleton):
24
24
  def free_music(self):
25
25
  if self.current_music:
26
26
  pygame.mixer.music.unload(self.current_music)
27
-
28
27
 
29
28
  def set_sound_volume(self, volume: float):
30
29
  self.sound_volume = volume
batFramework/camera.py CHANGED
@@ -2,6 +2,7 @@ import pygame
2
2
  from pygame.math import Vector2
3
3
  import batFramework as bf
4
4
  from typing import Self
5
+ import math
5
6
 
6
7
 
7
8
  class Camera:
@@ -32,7 +33,8 @@ class Camera:
32
33
  self._clear_color = pygame.Color(0, 0, 0)
33
34
 
34
35
  self.follow_point_func = None
35
- self.follow_friction: float = 0.5
36
+ self.damping = float("inf")
37
+ self.dead_zone_radius = 10
36
38
 
37
39
  self.zoom_factor = 1
38
40
  self.max_zoom = 2
@@ -145,23 +147,20 @@ class Camera:
145
147
  self.rect.center = (x, y)
146
148
  return self
147
149
 
148
- def set_follow_point(self, func, follow_speed: float = 0.1) -> Self:
149
- """
150
- Set the following function (returns tuple x y).
151
- Camera will center its position to the center of the given coordinates.
150
+ def set_follow_point_func(self, func) -> Self:
151
+ self.follow_point_func = func
152
+ return self
152
153
 
153
- Args:
154
- func: Function returning coordinates to follow.
154
+ def set_follow_speed(self, speed: float) -> Self:
155
+ self.follow_speed = speed
156
+ return self
155
157
 
156
- Returns:
157
- Camera: Returns the Camera object.
158
- """
159
- self.follow_point_func = func
160
- self.follow_speed = follow_speed if func else 0.1
158
+ def set_follow_damping(self, damping: float) -> Self:
159
+ self.damping = damping
161
160
  return self
162
161
 
163
- def set_follow_friction(self,friction:float)->Self:
164
- self.follow_friction = friction
162
+ def set_dead_zone_radius(self, radius: float) -> Self:
163
+ self.dead_zone_radius = radius
165
164
  return self
166
165
 
167
166
  def zoom_by(self, amount: float) -> Self:
@@ -247,7 +246,9 @@ class Camera:
247
246
  Returns:
248
247
  pygame.FRect: Transposed rectangle.
249
248
  """
250
- return pygame.FRect(rect[0] - self.rect.left, rect[1] - self.rect.top, rect[2],rect[3])
249
+ return pygame.FRect(
250
+ rect[0] - self.rect.left, rect[1] - self.rect.top, rect[2], rect[3]
251
+ )
251
252
 
252
253
  def world_to_screen_point(
253
254
  self, point: tuple[float, float] | tuple[int, int]
@@ -280,16 +281,20 @@ class Camera:
280
281
  )
281
282
 
282
283
  def update(self, dt):
283
- """
284
- Update the camera position based on the follow point function.
284
+ if not self.follow_point_func or not (math.isfinite(dt) and dt > 0):
285
+ return
285
286
 
286
- Args:
287
- dt: Time delta for updating the camera position.
288
- """
289
- if self.follow_point_func:
290
- self.vector_center.update(*self.rect.center)
291
- amount = pygame.math.clamp(self.follow_friction,0,1)
292
- self.set_center(*self.vector_center.lerp(self.follow_point_func(), amount))
287
+ target = Vector2(self.follow_point_func())
288
+ self.vector_center.update(self.rect.center)
289
+
290
+ if self.damping == float("inf"):
291
+ self.vector_center = target
292
+ elif math.isfinite(self.damping) and self.damping > 0:
293
+ damping_factor = 1 - math.exp(-self.damping * dt)
294
+ if not math.isnan(damping_factor):
295
+ self.vector_center += (target - self.vector_center) * damping_factor
296
+
297
+ self.rect.center = self.vector_center
293
298
 
294
299
  def draw(self, surface: pygame.Surface):
295
300
  """
batFramework/cutscene.py CHANGED
@@ -1,12 +1,10 @@
1
1
  import batFramework as bf
2
2
 
3
3
 
4
- class CutsceneBlock:
5
- ...
4
+ class CutsceneBlock: ...
6
5
 
7
6
 
8
- class Cutscene:
9
- ...
7
+ class Cutscene: ...
10
8
 
11
9
 
12
10
  class CutsceneManager(metaclass=bf.Singleton):
batFramework/entity.py CHANGED
@@ -3,10 +3,12 @@ import pygame
3
3
  import batFramework as bf
4
4
  from .object import Object
5
5
 
6
+
6
7
  class Entity(Object):
7
8
  """
8
9
  Basic entity class
9
10
  """
11
+
10
12
  def __init__(
11
13
  self,
12
14
  size: None | tuple[int, int] = None,
@@ -30,7 +32,7 @@ class Entity(Object):
30
32
  self.surface.set_alpha(min(max(0, alpha), 255))
31
33
  return self
32
34
 
33
- def get_alpha(self)->int:
35
+ def get_alpha(self) -> int:
34
36
  return self.surface.get_alpha()
35
37
 
36
38
  def set_surface_flags(self, surface_flags: int) -> Self:
@@ -45,6 +47,10 @@ class Entity(Object):
45
47
  self.blit_flags = blit_flags
46
48
  return self
47
49
 
50
+ def get_debug_outlines(self):
51
+ if self.visible:
52
+ yield (self.rect, self.debug_color)
53
+
48
54
  def set_render_order(self, render_order: int) -> Self:
49
55
  self.render_order = render_order
50
56
  if self.parent_scene:
@@ -59,7 +65,8 @@ class Entity(Object):
59
65
  """
60
66
  Draw the entity onto the camera surface
61
67
  """
62
- if not self.visible or not camera.rect.colliderect(self.rect): return
68
+ if not self.visible or not camera.rect.colliderect(self.rect):
69
+ return
63
70
  camera.surface.blit(
64
71
  self.surface,
65
72
  camera.world_to_screen(self.rect),
batFramework/enums.py CHANGED
@@ -45,6 +45,7 @@ class color:
45
45
  LIGHT_GB = (73, 107, 34)
46
46
  LIGHTER_GB = (154, 158, 63)
47
47
 
48
+
48
49
  class easing(Enum):
49
50
  LINEAR = (0, 0, 1, 1)
50
51
  EASE_IN = (0.95, 0, 1, 0.55)
@@ -55,15 +56,19 @@ class easing(Enum):
55
56
  def __init__(self, *control_points):
56
57
  self.control_points = control_points
57
58
 
59
+
58
60
  class axis(Enum):
59
61
  HORIZONTAL = "horizontal"
60
62
  VERTICAL = "vertical"
61
63
 
64
+
62
65
  class spacing(Enum):
63
66
  MIN = "min"
64
67
  HALF = "half"
65
68
  MAX = "max"
66
69
  MANUAL = "manual"
70
+
71
+
67
72
  class alignment(Enum):
68
73
  LEFT = "left"
69
74
  RIGHT = "right"
@@ -77,21 +82,25 @@ class alignment(Enum):
77
82
  BOTTOMLEFT = "bottomleft"
78
83
  BOTTOMRIGHT = "bottomright"
79
84
 
85
+
80
86
  class direction(Enum):
81
87
  LEFT = 0
82
88
  UP = 1
83
89
  RIGHT = 2
84
- DOWN = 3
90
+ DOWN = 3
91
+
85
92
 
86
93
  class drawMode(Enum):
87
94
  SOLID = 0
88
95
  TEXTURED = 1
89
96
 
97
+
90
98
  class debugMode(Enum):
91
99
  HIDDEN = 0
92
100
  DEBUGGER = 1
93
101
  OUTLINES = 2
94
102
 
103
+
95
104
  class actionType(Enum):
96
105
  INSTANTANEOUS = 0
97
106
  CONTINUOUS = 1
@@ -101,4 +110,4 @@ class actionType(Enum):
101
110
  class textMode(Enum):
102
111
  ALPHABETICAL = 0
103
112
  NUMERICAL = 1
104
- ALPHANUMERIC = 3
113
+ ALPHANUMERIC = 3
@@ -15,7 +15,7 @@ class FontManager(metaclass=Singleton):
15
15
  self.DEFAULT_ANTIALIAS = False
16
16
  self.FONTS = {}
17
17
 
18
- def set_antialias(self,value:bool):
18
+ def set_antialias(self, value: bool):
19
19
  self.DEFAULT_ANTIALIAS = value
20
20
 
21
21
  def set_default_text_size(self, size: int):
@@ -1,5 +1,7 @@
1
1
  from .constraints import *
2
2
  from .widget import Widget
3
+ from .styleManager import StyleManager
4
+ from .style import Style
3
5
  from .image import Image
4
6
  from .interactiveWidget import InteractiveWidget
5
7
  from .draggableWidget import DraggableWidget
@@ -16,5 +18,5 @@ from .layout import *
16
18
  from .container import Container
17
19
  from .indicator import *
18
20
  from .toggle import Toggle
19
- from .radioButton import RadioButton,RadioVariable
21
+ from .radioButton import RadioButton, RadioVariable
20
22
  from .slider import Slider
@@ -7,6 +7,9 @@ from math import ceil
7
7
 
8
8
 
9
9
  class Button(Label, ClickableWidget):
10
- def __init__(self, text: str, callback: None | Callable = None) -> None:
10
+ def __init__(self, text: str = "", callback: None | Callable = None) -> None:
11
11
  super().__init__(text=text)
12
- self.set_callback(callback)
12
+ self.set_callback(callback)
13
+
14
+ def __str__(self) -> str:
15
+ return f"Button({self.text})"
@@ -7,10 +7,10 @@ import pygame
7
7
  from math import ceil
8
8
 
9
9
 
10
- class ClickableWidget(Shape,InteractiveWidget):
10
+ class ClickableWidget(Shape, InteractiveWidget):
11
11
  _cache: dict = {}
12
12
 
13
- def __init__(self,callback: None | Callable = None,*args,**kwargs) -> None:
13
+ def __init__(self, callback: None | Callable = None, *args, **kwargs) -> None:
14
14
  super().__init__(*args, **kwargs)
15
15
  self.callback = callback
16
16
  self.is_pressed: bool = False
@@ -21,29 +21,32 @@ class ClickableWidget(Shape,InteractiveWidget):
21
21
  self.click_up_sound = None
22
22
  self.get_focus_sound = None
23
23
  self.lose_focus_sound = None
24
- self.pressed_relief : int = 1
25
- self.unpressed_relief : int = 2
26
- self.silent_focus : bool = False
24
+ self.pressed_relief: int = 1
25
+ self.unpressed_relief: int = 2
26
+ self.silent_focus: bool = False
27
27
  self.set_debug_color("cyan")
28
28
  self.set_relief(self.unpressed_relief)
29
29
 
30
- def set_unpressed_relief(self,relief:int=0)->Self:
31
- if relief == self.unpressed_relief : return self
32
- self.unpressed_relief = relief
30
+ def set_unpressed_relief(self, relief: int = 0) -> Self:
31
+ if relief == self.unpressed_relief:
32
+ return self
33
+ self.unpressed_relief = relief
33
34
  self.dirty_shape = True
34
- if not self.is_pressed : self.set_relief(relief)
35
+ if not self.is_pressed:
36
+ self.set_relief(relief)
35
37
  return self
36
38
 
37
- def set_silent_focus(self,value:bool)->Self:
39
+ def set_silent_focus(self, value: bool) -> Self:
38
40
  self.silent_focus = value
39
41
  return self
40
42
 
41
-
42
- def set_pressed_relief(self,relief:int=0)->Self:
43
- if relief == self.pressed_relief : return self
44
- self.pressed_relief = relief
43
+ def set_pressed_relief(self, relief: int = 0) -> Self:
44
+ if relief == self.pressed_relief:
45
+ return self
46
+ self.pressed_relief = relief
45
47
  self.dirty_shape = True
46
- if self.is_pressed : self.set_relief(relief)
48
+ if self.is_pressed:
49
+ self.set_relief(relief)
47
50
 
48
51
  return self
49
52
 
@@ -72,13 +75,15 @@ class ClickableWidget(Shape,InteractiveWidget):
72
75
  return self
73
76
 
74
77
  def get_surface_filter(self) -> pygame.Surface | None:
75
- size = int(self.rect.w),int(self.rect.h)
78
+ size = int(self.rect.w), int(self.rect.h)
76
79
  surface_filter = ClickableWidget._cache.get((size, *self.border_radius), None)
77
80
  if surface_filter is None:
78
81
  # Create a mask from the original surface
79
82
  mask = pygame.mask.from_surface(self.surface, threshold=0)
80
-
81
- silhouette_surface = mask.to_surface(setcolor=(30, 30, 30), unsetcolor=(0,0,0))
83
+
84
+ silhouette_surface = mask.to_surface(
85
+ setcolor=(30, 30, 30), unsetcolor=(0, 0, 0)
86
+ )
82
87
 
83
88
  ClickableWidget._cache[(size, *self.border_radius)] = silhouette_surface
84
89
 
@@ -86,7 +91,7 @@ class ClickableWidget(Shape,InteractiveWidget):
86
91
 
87
92
  return surface_filter
88
93
 
89
- def allow_focus_to_self(self)->bool:
94
+ def allow_focus_to_self(self) -> bool:
90
95
  return True
91
96
 
92
97
  def enable(self) -> Self:
@@ -111,7 +116,7 @@ class ClickableWidget(Shape,InteractiveWidget):
111
116
  if self.get_focus_sound and not self.silent_focus:
112
117
  if self.parent_scene and self.parent_scene.visible:
113
118
  bf.AudioManager().play_sound(self.get_focus_sound)
114
- if self.silent_focus :
119
+ if self.silent_focus:
115
120
  self.silent_focus = False
116
121
 
117
122
  def on_lose_focus(self):
@@ -119,7 +124,7 @@ class ClickableWidget(Shape,InteractiveWidget):
119
124
  if self.lose_focus_sound and not self.silent_focus:
120
125
  if self.parent_scene and self.parent_scene.visible:
121
126
  bf.AudioManager().play_sound(self.lose_focus_sound)
122
- if self.silent_focus :
127
+ if self.silent_focus:
123
128
  self.silent_focus = False
124
129
 
125
130
  def __str__(self) -> str:
@@ -132,7 +137,7 @@ class ClickableWidget(Shape,InteractiveWidget):
132
137
  self.callback()
133
138
 
134
139
  def do_on_click_down(self, button) -> None:
135
- if self.enabled and button == 1 :
140
+ if self.enabled and button == 1:
136
141
  if not self.get_focus():
137
142
  return
138
143
  self.is_pressed = True
@@ -152,15 +157,15 @@ class ClickableWidget(Shape,InteractiveWidget):
152
157
  if not self.enabled:
153
158
  return
154
159
  super().on_enter()
155
- self.dirty_surface = True
160
+ self.dirty_surface = True
156
161
  pygame.mouse.set_cursor(self.hover_cursor)
157
162
 
158
163
  def on_exit(self) -> None:
159
164
  super().on_exit()
160
165
  if self.is_pressed:
161
166
  self.set_relief(self.unpressed_relief)
162
- self.is_pressed = False
163
- self.dirty_surface = True
167
+ self.is_pressed = False
168
+ self.dirty_surface = True
164
169
 
165
170
  pygame.mouse.set_cursor(bf.const.DEFAULT_CURSOR)
166
171
 
@@ -188,15 +193,23 @@ class ClickableWidget(Shape,InteractiveWidget):
188
193
  self.get_surface_filter(), (0, 0), special_flags=pygame.BLEND_RGB_ADD
189
194
  )
190
195
 
191
- def get_padded_rect(self)->pygame.FRect:
196
+ def get_padded_rect(self) -> pygame.FRect:
192
197
  return pygame.FRect(
193
- self.rect.x + self.padding[0], self.rect.y + self.padding[1] + (self.unpressed_relief - self.pressed_relief if self.is_pressed else 0),
198
+ self.rect.x + self.padding[0],
199
+ self.rect.y
200
+ + self.padding[1]
201
+ + (self.unpressed_relief - self.pressed_relief if self.is_pressed else 0),
194
202
  self.rect.w - self.padding[2] - self.padding[0],
195
- self.rect.h - self.unpressed_relief - self.padding[1] - self.padding[3] #
203
+ self.rect.h - self.unpressed_relief - self.padding[1] - self.padding[3], #
196
204
  )
197
205
 
198
206
  def _get_elevated_rect(self) -> pygame.FRect:
199
- return pygame.FRect(0, self.unpressed_relief - self.pressed_relief if self.is_pressed else 0 , self.rect.w, self.rect.h - self.unpressed_relief)
207
+ return pygame.FRect(
208
+ 0,
209
+ self.unpressed_relief - self.pressed_relief if self.is_pressed else 0,
210
+ self.rect.w,
211
+ self.rect.h - self.unpressed_relief,
212
+ )
200
213
 
201
214
  def paint(self) -> None:
202
215
  super().paint()