batframework 1.0.8a3__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.
batFramework/__init__.py CHANGED
@@ -8,7 +8,7 @@ from .resourceManager import ResourceManager
8
8
  from .fontManager import FontManager
9
9
  from .utils import Utils as utils
10
10
  from .tileset import Tileset
11
- from .time import TimeManager, Timer
11
+ from .time import *
12
12
  from .easingController import EasingController
13
13
  from .cutscene import Cutscene, CutsceneManager
14
14
  from .cutsceneBlocks import *
@@ -1,8 +1,10 @@
1
1
  import batFramework as bf
2
2
  import pygame
3
+ from typing import List, Dict, Tuple, Union, Optional
4
+ from enum import Enum
3
5
 
4
6
 
5
- def search_index(target, lst):
7
+ def search_index(target: int, lst: List[int]) -> int:
6
8
  cumulative_sum = 0
7
9
  for index, value in enumerate(lst):
8
10
  cumulative_sum += value
@@ -16,37 +18,34 @@ class AnimState:
16
18
  self,
17
19
  name: str,
18
20
  surface: pygame.Surface,
19
- width,
20
- height,
21
- duration_list: list | int,
21
+ size: Tuple[int,int],
22
+ duration_list: Union[List[int], int],
22
23
  ) -> None:
23
- self.frames: list[pygame.Surface] = list(
24
+ self.frames: List[pygame.Surface] = list(
24
25
  bf.utils.split_surface(
25
- surface, width, height, False, convert_alpha
26
+ surface, size
26
27
  ).values()
27
28
  )
28
- self.frames_flipX: list[pygame.Surface] = list(
29
- bf.utils.split_surface(surface, width, height, True, convert_alpha).values()
29
+ self.frames_flipX: List[pygame.Surface] = list(
30
+ bf.utils.split_surface(surface, size,).values()
30
31
  )
31
32
 
32
33
  self.name = name
33
- self.duration_list: list[int] = []
34
- self.duration_list_length = 0
35
34
  self.set_duration_list(duration_list)
36
35
 
37
36
  def __repr__(self):
38
37
  return f"AnimState({self.name})"
39
38
 
40
- def counter_to_frame(self, counter: float | int) -> int:
39
+ def counter_to_frame(self, counter: Union[float, int]) -> int:
41
40
  return search_index(
42
41
  int(counter % self.duration_list_length), self.duration_list
43
42
  )
44
43
 
45
- def get_frame(self, counter, flip):
44
+ def get_frame(self, counter: Union[float, int], flip: bool) -> pygame.Surface:
46
45
  i = self.counter_to_frame(counter)
47
46
  return self.frames_flipX[i] if flip else self.frames[i]
48
47
 
49
- def set_duration_list(self, duration_list: list[int] | int):
48
+ def set_duration_list(self, duration_list: Union[List[int], int]):
50
49
  if isinstance(duration_list, int):
51
50
  duration_list = [duration_list] * len(self.frames)
52
51
  if len(duration_list) != len(self.frames):
@@ -55,21 +54,23 @@ class AnimState:
55
54
  self.duration_list_length = sum(self.duration_list)
56
55
 
57
56
 
58
- class AnimatedSprite(bf.DynamicEntity):
59
- def __init__(self, size=None) -> None:
57
+ class AnimatedSprite(bf.Entity):
58
+ def __init__(self, size: Optional[Tuple[int, int]] = None) -> None:
60
59
  super().__init__(size, no_surface=True)
61
60
  self.float_counter: float = 0
62
- self.animStates: dict[str, AnimState] = {}
63
- self.current_state: AnimState | None = None
64
- self.flipX = False
65
- self._locked = False
66
- self.paused: bool = False
61
+ self.animStates: Dict[str, AnimState] = {}
62
+ self.current_state: Optional[AnimState] = None
63
+ self.flipX: bool = False
64
+ self._locked: bool = False
65
+ self._paused: bool = False
67
66
 
68
- def pause(self) -> None:
69
- self.paused = True
67
+ @property
68
+ def paused(self) -> bool:
69
+ return self._paused
70
70
 
71
- def resume(self) -> None:
72
- self.paused = False
71
+ @paused.setter
72
+ def paused(self, value: bool) -> None:
73
+ self._paused = value
73
74
 
74
75
  def toggle_pause(self) -> None:
75
76
  self.paused = not self.paused
@@ -80,16 +81,7 @@ class AnimatedSprite(bf.DynamicEntity):
80
81
  def set_frame(self, frame_index: int) -> None:
81
82
  if not self.current_state:
82
83
  return
83
- total = sum(self.current_state.duration_list)
84
- frame_index = max(0, min(total, frame_index))
85
- new_counter = 0
86
- i = 0
87
- while frame_index < total:
88
- if self.current_state.counter_to_frame(new_counter) >= frame_index:
89
- break
90
- new_counter += self.current_state.duration_list[i]
91
- i += 1
92
- self.set_counter(new_counter)
84
+ self.set_counter(sum(self.current_state.duration_list[:frame_index]))
93
85
 
94
86
  def lock(self) -> None:
95
87
  self._locked = True
@@ -97,16 +89,16 @@ class AnimatedSprite(bf.DynamicEntity):
97
89
  def unlock(self) -> None:
98
90
  self._locked = False
99
91
 
100
- def set_flipX(self, value) -> None:
92
+ def set_flipX(self, value: bool) -> None:
101
93
  self.flipX = value
102
94
 
103
95
  def remove_animState(self, name: str) -> bool:
104
- if not name in self.animStates:
96
+ if name not in self.animStates:
105
97
  return False
106
98
  self.animStates.pop(name)
107
99
  if self.current_state and self.current_state.name == name:
108
- self.current_animState = (
109
- list(self.animStates.keys())[0] if self.animStates else ""
100
+ self.current_state = (
101
+ list(self.animStates.values())[0] if self.animStates else None
110
102
  )
111
103
  return True
112
104
 
@@ -114,54 +106,56 @@ class AnimatedSprite(bf.DynamicEntity):
114
106
  self,
115
107
  name: str,
116
108
  surface: pygame.Surface,
117
- size: tuple[int, int],
118
- duration_list: list[int],
119
- convert_alpha: bool = True,
109
+ size: Tuple[int, int],
110
+ duration_list: Union[List[int], int],
120
111
  ) -> bool:
121
112
  if name in self.animStates:
122
113
  return False
123
- self.animStates[name] = AnimState(name, surface, *size, duration_list)
114
+ self.animStates[name] = AnimState(
115
+ name, surface, size, duration_list
116
+ )
124
117
  if len(self.animStates) == 1:
125
118
  self.set_animState(name)
126
119
  return True
127
120
 
128
- def set_animState(self, state: str, reset_counter=True, lock=False) -> bool:
121
+ def set_animState(
122
+ self, state: str, reset_counter: bool = True, lock: bool = False
123
+ ) -> bool:
129
124
  if state not in self.animStates or self._locked:
130
125
  return False
131
126
 
132
127
  animState = self.animStates[state]
133
128
  self.current_state = animState
134
129
 
135
- self.rect = self.current_state.frames[0].get_frect(center=self.rect.center)
130
+ self.rect = self.current_state.frames[0].get_rect(center=self.rect.center)
136
131
 
137
- if reset_counter or self.float_counter > sum(animState.duration_list):
132
+ if reset_counter or self.float_counter > animState.duration_list_length:
138
133
  self.float_counter = 0
139
134
  if lock:
140
135
  self.lock()
141
136
  return True
142
137
 
143
- def get_animState(self) -> AnimState | None:
138
+ def get_animState(self) -> Optional[AnimState]:
144
139
  return self.current_state
145
140
 
146
141
  def update(self, dt: float) -> None:
147
142
  s = self.get_animState()
148
- if not self.animStates or s is None:
149
- return
150
- if not self.paused:
151
- self.float_counter += 60 * dt
152
- if self.float_counter > s.duration_list_length:
153
- self.float_counter = 0
143
+ if self.animStates and s is not None:
144
+ if not self.paused:
145
+ self.float_counter += 60 * dt
146
+ if self.float_counter > s.duration_list_length:
147
+ self.float_counter = 0
154
148
  self.do_update(dt)
155
149
 
156
- def draw(self, camera: bf.Camera) -> int:
150
+ def draw(self, camera: bf.Camera) -> None:
157
151
  if (
158
152
  not self.visible
159
153
  or not camera.rect.colliderect(self.rect)
160
154
  or not self.current_state
161
155
  ):
162
- return 0
156
+ return
163
157
  camera.surface.blit(
164
158
  self.current_state.get_frame(self.float_counter, self.flipX),
165
159
  camera.world_to_screen(self.rect),
166
160
  )
167
- return 1
161
+ return
@@ -68,7 +68,7 @@ class AudioManager(metaclass=bf.Singleton):
68
68
  self.sounds[name]["sound"].play()
69
69
  return True
70
70
  except KeyError:
71
- # print(f"Sound '{name}' not loaded in AudioManager.")
71
+ print(f"Sound '{name}' not loaded in AudioManager.")
72
72
  return False
73
73
 
74
74
  def stop_sound(self, name) -> bool:
@@ -77,7 +77,7 @@ class AudioManager(metaclass=bf.Singleton):
77
77
  return True
78
78
  except KeyError:
79
79
  return False
80
- # print(f"Sound '{name}' not loaded in AudioManager.")
80
+ print(f"Sound '{name}' not loaded in AudioManager.")
81
81
 
82
82
  def load_music(self, name, path):
83
83
  self.musics[name] = bf.ResourceManager().get_path(path)
batFramework/cutscene.py CHANGED
@@ -1,8 +1,11 @@
1
1
  import batFramework as bf
2
-
2
+ from typing import TYPE_CHECKING
3
3
 
4
4
  class CutsceneBlock: ...
5
5
 
6
+ if TYPE_CHECKING:
7
+ from .cutsceneBlocks import CutsceneBlock
8
+
6
9
 
7
10
  class Cutscene: ...
8
11
 
@@ -58,7 +61,7 @@ class CutsceneManager(metaclass=bf.Singleton):
58
61
 
59
62
  class Cutscene:
60
63
  def __init__(self) -> None:
61
- self.cutscene_blocks = []
64
+ self.cutscene_blocks : list[CutsceneBlock] = []
62
65
  self.block_index = 0
63
66
  self.end_blocks: list[CutsceneBlock] = []
64
67
  self.ended = False
@@ -1,7 +1,7 @@
1
1
  import batFramework as bf
2
2
  from .cutscene import Cutscene, CutsceneManager
3
3
  from .transition import *
4
-
4
+ from typing import Optional,Callable
5
5
 
6
6
  # Define the base CutsceneBlock class
7
7
  class CutsceneBlock:
@@ -150,7 +150,6 @@ class SceneTransitionBlock(CutsceneBlock):
150
150
  # Start the timer to handle the end of the transition
151
151
  self.timer.start()
152
152
 
153
-
154
153
  class DelayBlock(CutsceneBlock):
155
154
  def __init__(self, duration) -> None:
156
155
  super().__init__()
@@ -160,12 +159,11 @@ class DelayBlock(CutsceneBlock):
160
159
  super().start()
161
160
  self.timer.start()
162
161
 
163
-
164
162
  class FunctionBlock(CutsceneBlock):
165
- def __init__(self, func) -> None:
163
+ def __init__(self, func : Optional[Callable]) -> None:
166
164
  self.function = func
167
165
 
168
166
  def start(self):
169
167
  super().start()
170
- self.function()
168
+ if self.function : self.function()
171
169
  self.end()
@@ -8,15 +8,21 @@ class DynamicEntity(bf.Entity):
8
8
  self,
9
9
  size: None | tuple[int, int] = None,
10
10
  surface_flags: int = 0,
11
- convert_alpha: bool = False,
11
+ convert_alpha: bool = False,*args,**kwargs
12
12
  ) -> None:
13
- super().__init__(size, surface_flags, convert_alpha)
13
+ super().__init__(size, surface_flags, convert_alpha,*args,**kwargs)
14
14
  self.velocity = pygame.math.Vector2(0, 0)
15
15
 
16
- def on_collideX(self, collider: Self):
16
+ def on_collideX(self, collider: "DynamicEntity"):
17
+ """
18
+ Return true if collision
19
+ """
17
20
  return False
18
21
 
19
- def on_collideY(self, collider: Self):
22
+ def on_collideY(self, collider: "DynamicEntity"):
23
+ """
24
+ Return true if collision
25
+ """
20
26
  return False
21
27
 
22
28
  def move_by_velocity(self, dt) -> None:
@@ -141,7 +141,8 @@ class ClickableWidget(Shape, InteractiveWidget):
141
141
  if not self.get_focus():
142
142
  return
143
143
  self.is_pressed = True
144
- bf.AudioManager().play_sound(self.click_down_sound)
144
+ if self.click_down_sound:
145
+ bf.AudioManager().play_sound(self.click_down_sound)
145
146
 
146
147
  pygame.mouse.set_cursor(self.click_cursor)
147
148
  self.set_relief(self.pressed_relief)
@@ -149,7 +150,8 @@ class ClickableWidget(Shape, InteractiveWidget):
149
150
  def do_on_click_up(self, button) -> None:
150
151
  if self.enabled and button == 1 and self.is_pressed:
151
152
  self.is_pressed = False
152
- bf.AudioManager().play_sound(self.click_up_sound)
153
+ if self.click_up_sound:
154
+ bf.AudioManager().play_sound(self.click_up_sound)
153
155
  self.set_relief(self.unpressed_relief)
154
156
  self.click()
155
157
 
@@ -26,7 +26,11 @@ class Constraint:
26
26
 
27
27
  def apply_constraint(self, parent_widget: Widget, child_widget: Widget):
28
28
  raise NotImplementedError("Subclasses must implement apply_constraint method")
29
-
29
+
30
+ def __eq__(self,other:"Constraint")->bool:
31
+ if not isinstance(other,self.__class__):
32
+ return False
33
+ return other.name == self.name
30
34
 
31
35
  class MinWidth(Constraint):
32
36
  def __init__(self, width: float):
@@ -39,6 +43,13 @@ class MinWidth(Constraint):
39
43
  def apply_constraint(self, parent_widget, child_widget):
40
44
  child_widget.set_size((self.min_width, None))
41
45
 
46
+ def __eq__(self,other:"Constraint")->bool:
47
+ if not isinstance(other,self.__class__):
48
+ return False
49
+ return (
50
+ other.name == self.name and
51
+ other.min_width == self.min_width
52
+ )
42
53
 
43
54
  class MinHeight(Constraint):
44
55
  def __init__(self, height: float):
@@ -51,6 +62,13 @@ class MinHeight(Constraint):
51
62
  def apply_constraint(self, parent_widget, child_widget):
52
63
  child_widget.set_size((None, self.min_height))
53
64
 
65
+ def __eq__(self,other:"Constraint")->bool:
66
+ if not isinstance(other,self.__class__):
67
+ return False
68
+ return (
69
+ other.name == self.name and
70
+ other.min_height == self.min_height
71
+ )
54
72
 
55
73
  class CenterX(Constraint):
56
74
  def __init__(self):
@@ -124,6 +142,14 @@ class PercentageWidth(Constraint):
124
142
  (round(parent_widget.get_padded_width() * self.percentage), None)
125
143
  )
126
144
 
145
+ def __eq__(self,other:"Constraint")->bool:
146
+ if not isinstance(other,self.__class__):
147
+ return False
148
+ return (
149
+ other.name == self.name and
150
+ other.percentage == self.percentage
151
+ )
152
+
127
153
 
128
154
  class PercentageHeight(Constraint):
129
155
  def __init__(self, percentage: float, keep_autoresize: bool = False):
@@ -151,18 +177,30 @@ class PercentageHeight(Constraint):
151
177
  (None, round(parent_widget.get_padded_height() * self.percentage))
152
178
  )
153
179
 
180
+ def __eq__(self,other:"Constraint")->bool:
181
+ if not isinstance(other,self.__class__):
182
+ return False
183
+ return (
184
+ other.name == self.name and
185
+ other.percentage == self.percentage
186
+ )
154
187
 
155
188
  class FillX(PercentageWidth):
156
189
  def __init__(self, keep_autoresize: bool = False):
157
190
  super().__init__(1, keep_autoresize)
158
191
  self.name = "fill_x"
159
192
 
193
+ def __eq__(self, other: Constraint) -> bool:
194
+ return Constraint.__eq__(self,other)
160
195
 
161
196
  class FillY(PercentageHeight):
162
197
  def __init__(self, keep_autoresize: bool = False):
163
198
  super().__init__(1, keep_autoresize)
164
199
  self.name = "fill_y"
165
200
 
201
+ def __eq__(self, other: Constraint) -> bool:
202
+ return Constraint.__eq__(self,other)
203
+
166
204
 
167
205
  class PercentageRectHeight(Constraint):
168
206
  def __init__(self, percentage: float, keep_autoresize: bool = False):
@@ -190,6 +228,13 @@ class PercentageRectHeight(Constraint):
190
228
  (None, round(parent_widget.rect.height * self.percentage))
191
229
  )
192
230
 
231
+ def __eq__(self,other:"Constraint")->bool:
232
+ if not isinstance(other,self.__class__):
233
+ return False
234
+ return (
235
+ other.name == self.name and
236
+ other.percentage == self.percentage
237
+ )
193
238
 
194
239
  class PercentageRectWidth(Constraint):
195
240
  def __init__(self, percentage: float, keep_autoresize: bool = False):
@@ -215,6 +260,13 @@ class PercentageRectWidth(Constraint):
215
260
  child_widget.set_autoresize_w(False)
216
261
  child_widget.set_size((round(parent_widget.rect.width * self.percentage), None))
217
262
 
263
+ def __eq__(self,other:"Constraint")->bool:
264
+ if not isinstance(other,self.__class__):
265
+ return False
266
+ return (
267
+ other.name == self.name and
268
+ other.percentage == self.percentage
269
+ )
218
270
 
219
271
  class FillRectX(PercentageRectWidth):
220
272
  def __init__(self, keep_autoresize: bool = False):
@@ -252,9 +304,10 @@ class AspectRatio(Constraint):
252
304
 
253
305
  def evaluate(self, parent_widget, child_widget):
254
306
  if self.ref_axis == bf.axis.HORIZONTAL:
255
- return self.ratio == child_widget.rect.w / child_widget.rect.h
256
- if self.ref_axis == bf.axis.VERTICAL:
257
307
  return self.ratio == child_widget.rect.h / child_widget.rect.w
308
+ if self.ref_axis == bf.axis.VERTICAL:
309
+ return self.ratio == child_widget.rect.w / child_widget.rect.h
310
+
258
311
 
259
312
  def apply_constraint(self, parent_widget, child_widget):
260
313
 
@@ -262,7 +315,7 @@ class AspectRatio(Constraint):
262
315
  if child_widget.autoresize_w:
263
316
  if self.keep_autoresize:
264
317
  print(
265
- f"WARNING: Constraint on {child_widget.__str__()} can't resize, autoresize set to True"
318
+ f"WARNING: Constraint on {str(child_widget)} can't resize, autoresize set to True"
266
319
  )
267
320
  return
268
321
  child_widget.set_autoresize_w(False)
@@ -272,13 +325,21 @@ class AspectRatio(Constraint):
272
325
  if child_widget.autoresize_h:
273
326
  if self.keep_autoresize:
274
327
  print(
275
- f"WARNING: Constraint on {child_widget.__str__()} can't resize, autoresize set to True"
328
+ f"WARNING: Constraint on {str(child_widget)} can't resize, autoresize set to True"
276
329
  )
277
330
  return
278
331
  child_widget.set_autoresize_h(False)
279
- print("HERE")
332
+
280
333
  child_widget.set_size((None, child_widget.rect.w * self.ratio))
281
334
 
335
+ def __eq__(self,other:"Constraint")->bool:
336
+ if not isinstance(other,self.__class__):
337
+ return False
338
+ return (
339
+ other.name == self.name and
340
+ other.ratio == self.ratio and
341
+ other.ref_axis == self.ref_axis
342
+ )
282
343
 
283
344
  class AnchorBottom(Constraint):
284
345
  def __init__(self):
@@ -286,8 +347,8 @@ class AnchorBottom(Constraint):
286
347
 
287
348
  def evaluate(self, parent_widget, child_widget):
288
349
  return (
289
- child_widget.rect.top
290
- == parent_widget.get_padded_bottom() - child_widget.rect.h
350
+ child_widget.rect.bottom
351
+ == parent_widget.get_padded_bottom()
291
352
  )
292
353
 
293
354
  def apply_constraint(self, parent_widget, child_widget):
@@ -295,21 +356,15 @@ class AnchorBottom(Constraint):
295
356
  child_widget.rect.x, parent_widget.get_padded_bottom() - child_widget.rect.h
296
357
  )
297
358
 
298
-
299
- class AnchorBottom(Constraint):
359
+ class AnchorTop(Constraint):
300
360
  def __init__(self):
301
- super().__init__(name="anchor_bottom")
361
+ super().__init__(name="anchor_top")
302
362
 
303
363
  def evaluate(self, parent_widget, child_widget):
304
- return (
305
- child_widget.rect.top
306
- == parent_widget.get_padded_bottom() - child_widget.rect.h
307
- )
364
+ return (child_widget.rect.top == parent_widget.get_padded_top())
308
365
 
309
366
  def apply_constraint(self, parent_widget, child_widget):
310
- child_widget.set_position(
311
- child_widget.rect.x, parent_widget.get_padded_bottom() - child_widget.rect.h
312
- )
367
+ child_widget.set_position(child_widget.rect.x, parent_widget.get_padded_top())
313
368
 
314
369
 
315
370
  class AnchorTopRight(Constraint):
@@ -385,6 +440,13 @@ class MarginBottom(Constraint):
385
440
  parent_widget.get_padded_bottom() - child_widget.rect.h - self.margin,
386
441
  )
387
442
 
443
+ def __eq__(self,other:"Constraint")->bool:
444
+ if not isinstance(other,self.__class__):
445
+ return False
446
+ return (
447
+ other.name == self.name and
448
+ other.margin == self.margin
449
+ )
388
450
 
389
451
  class MarginTop(Constraint):
390
452
  def __init__(self, margin: float):
@@ -399,6 +461,13 @@ class MarginTop(Constraint):
399
461
  child_widget.rect.x, parent_widget.get_padded_top() + self.margin
400
462
  )
401
463
 
464
+ def __eq__(self,other:"Constraint")->bool:
465
+ if not isinstance(other,self.__class__):
466
+ return False
467
+ return (
468
+ other.name == self.name and
469
+ other.margin == self.margin
470
+ )
402
471
 
403
472
  class MarginLeft(Constraint):
404
473
  def __init__(self, margin: float):
@@ -414,6 +483,13 @@ class MarginLeft(Constraint):
414
483
  parent_widget.get_padded_left() + self.margin, child_widget.rect.y
415
484
  )
416
485
 
486
+ def __eq__(self,other:"Constraint")->bool:
487
+ if not isinstance(other,self.__class__):
488
+ return False
489
+ return (
490
+ other.name == self.name and
491
+ other.margin == self.margin
492
+ )
417
493
 
418
494
  class MarginRight(Constraint):
419
495
  def __init__(self, margin: float):
@@ -429,6 +505,13 @@ class MarginRight(Constraint):
429
505
  child_widget.rect.y,
430
506
  )
431
507
 
508
+ def __eq__(self,other:"Constraint")->bool:
509
+ if not isinstance(other,self.__class__):
510
+ return False
511
+ return (
512
+ other.name == self.name and
513
+ other.margin == self.margin
514
+ )
432
515
 
433
516
  class PercentageMarginBottom(Constraint):
434
517
  def __init__(self, margin: float):
@@ -450,6 +533,13 @@ class PercentageMarginBottom(Constraint):
450
533
  - parent_widget.get_padded_height() * self.margin,
451
534
  )
452
535
 
536
+ def __eq__(self,other:"Constraint")->bool:
537
+ if not isinstance(other,self.__class__):
538
+ return False
539
+ return (
540
+ other.name == self.name and
541
+ other.margin == self.margin
542
+ )
453
543
 
454
544
  class PercentageMarginTop(Constraint):
455
545
  def __init__(self, margin: float):
@@ -470,6 +560,13 @@ class PercentageMarginTop(Constraint):
470
560
  + parent_widget.get_padded_height() * self.margin,
471
561
  )
472
562
 
563
+ def __eq__(self,other:"Constraint")->bool:
564
+ if not isinstance(other,self.__class__):
565
+ return False
566
+ return (
567
+ other.name == self.name and
568
+ other.margin == self.margin
569
+ )
473
570
 
474
571
  class PercentageMarginLeft(Constraint):
475
572
  def __init__(self, margin: float):
@@ -491,6 +588,13 @@ class PercentageMarginLeft(Constraint):
491
588
  child_widget.rect.y,
492
589
  )
493
590
 
591
+ def __eq__(self,other:"Constraint")->bool:
592
+ if not isinstance(other,self.__class__):
593
+ return False
594
+ return (
595
+ other.name == self.name and
596
+ other.margin == self.margin
597
+ )
494
598
 
495
599
  class PercentageMarginRight(Constraint):
496
600
  def __init__(self, margin: float):
@@ -512,6 +616,13 @@ class PercentageMarginRight(Constraint):
512
616
  child_widget.rect.y,
513
617
  )
514
618
 
619
+ def __eq__(self,other:"Constraint")->bool:
620
+ if not isinstance(other,self.__class__):
621
+ return False
622
+ return (
623
+ other.name == self.name and
624
+ other.margin == self.margin
625
+ )
515
626
 
516
627
  class PercentageRectMarginBottom(Constraint):
517
628
  def __init__(self, margin: float):
@@ -532,6 +643,13 @@ class PercentageRectMarginBottom(Constraint):
532
643
  - parent_widget.rect.height * self.margin,
533
644
  )
534
645
 
646
+ def __eq__(self,other:"Constraint")->bool:
647
+ if not isinstance(other,self.__class__):
648
+ return False
649
+ return (
650
+ other.name == self.name and
651
+ other.margin == self.margin
652
+ )
535
653
 
536
654
  class PercentageRectMarginTop(Constraint):
537
655
  def __init__(self, margin: float):
@@ -550,6 +668,13 @@ class PercentageRectMarginTop(Constraint):
550
668
  parent_widget.rect.top + parent_widget.rect.height * self.margin,
551
669
  )
552
670
 
671
+ def __eq__(self,other:"Constraint")->bool:
672
+ if not isinstance(other,self.__class__):
673
+ return False
674
+ return (
675
+ other.name == self.name and
676
+ other.margin == self.margin
677
+ )
553
678
 
554
679
  class PercentageRectMarginLeft(Constraint):
555
680
  def __init__(self, margin: float):
@@ -569,6 +694,13 @@ class PercentageRectMarginLeft(Constraint):
569
694
  child_widget.rect.y,
570
695
  )
571
696
 
697
+ def __eq__(self,other:"Constraint")->bool:
698
+ if not isinstance(other,self.__class__):
699
+ return False
700
+ return (
701
+ other.name == self.name and
702
+ other.margin == self.margin
703
+ )
572
704
 
573
705
  class PercentageRectMarginRight(Constraint):
574
706
  def __init__(self, margin: float):
@@ -588,3 +720,11 @@ class PercentageRectMarginRight(Constraint):
588
720
  - parent_widget.rect.width * self.margin,
589
721
  child_widget.rect.y,
590
722
  )
723
+
724
+ def __eq__(self,other:"Constraint")->bool:
725
+ if not isinstance(other,self.__class__):
726
+ return False
727
+ return (
728
+ other.name == self.name and
729
+ other.margin == self.margin
730
+ )