batframework 1.0.6__py3-none-any.whl → 1.0.8a1__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 (59) hide show
  1. batFramework/__init__.py +23 -14
  2. batFramework/action.py +95 -106
  3. batFramework/actionContainer.py +11 -8
  4. batFramework/animatedSprite.py +60 -43
  5. batFramework/audioManager.py +52 -22
  6. batFramework/camera.py +87 -72
  7. batFramework/constants.py +19 -41
  8. batFramework/cutscene.py +12 -11
  9. batFramework/cutsceneBlocks.py +12 -14
  10. batFramework/dynamicEntity.py +5 -4
  11. batFramework/easingController.py +58 -0
  12. batFramework/entity.py +37 -130
  13. batFramework/enums.py +93 -3
  14. batFramework/fontManager.py +15 -7
  15. batFramework/gui/__init__.py +5 -1
  16. batFramework/gui/button.py +6 -144
  17. batFramework/gui/clickableWidget.py +206 -0
  18. batFramework/gui/constraints/__init__.py +1 -0
  19. batFramework/gui/constraints/constraints.py +378 -0
  20. batFramework/gui/container.py +147 -34
  21. batFramework/gui/debugger.py +39 -22
  22. batFramework/gui/dialogueBox.py +69 -43
  23. batFramework/gui/draggableWidget.py +38 -0
  24. batFramework/gui/image.py +33 -28
  25. batFramework/gui/indicator.py +30 -16
  26. batFramework/gui/interactiveWidget.py +72 -20
  27. batFramework/gui/label.py +240 -98
  28. batFramework/gui/layout.py +125 -53
  29. batFramework/gui/meter.py +76 -0
  30. batFramework/gui/radioButton.py +62 -0
  31. batFramework/gui/root.py +72 -48
  32. batFramework/gui/shape.py +257 -49
  33. batFramework/gui/slider.py +217 -2
  34. batFramework/gui/textInput.py +106 -60
  35. batFramework/gui/toggle.py +85 -42
  36. batFramework/gui/widget.py +259 -288
  37. batFramework/manager.py +30 -16
  38. batFramework/object.py +115 -0
  39. batFramework/{particles.py → particle.py} +37 -34
  40. batFramework/renderGroup.py +62 -0
  41. batFramework/resourceManager.py +36 -24
  42. batFramework/scene.py +94 -88
  43. batFramework/sceneManager.py +140 -57
  44. batFramework/scrollingSprite.py +113 -0
  45. batFramework/sprite.py +35 -23
  46. batFramework/tileset.py +34 -52
  47. batFramework/time.py +105 -56
  48. batFramework/transition.py +213 -1
  49. batFramework/utils.py +38 -18
  50. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/METADATA +1 -1
  51. batframework-1.0.8a1.dist-info/RECORD +56 -0
  52. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/WHEEL +1 -1
  53. batFramework/easing.py +0 -76
  54. batFramework/gui/constraints.py +0 -277
  55. batFramework/gui/frame.py +0 -25
  56. batFramework/transitionManager.py +0 -0
  57. batframework-1.0.6.dist-info/RECORD +0 -50
  58. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/LICENCE +0 -0
  59. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,113 @@
1
+ from typing import Self, Iterator
2
+ from pygame.math import Vector2
3
+ import batFramework as bf
4
+ import pygame
5
+
6
+
7
+ class ScrollingSprite(bf.Sprite):
8
+ def __init__(
9
+ self,
10
+ data: pygame.Surface | str,
11
+ size: None | tuple[int, int] = None,
12
+ convert_alpha: bool = True,
13
+ ):
14
+ self.scroll_value = Vector2(0, 0)
15
+ self.auto_scroll = Vector2(0, 0)
16
+
17
+ # Use integer values for the starting points, converted from floating point scroll values
18
+
19
+ super().__init__(data, size, convert_alpha)
20
+ self.original_width, self.original_height = self.original_surface.get_size()
21
+
22
+ def get_debug_outlines(self):
23
+ yield from super().get_debug_outlines()
24
+ for r in self._get_mosaic_rect_list():
25
+ yield r.move(*self.rect.topleft)
26
+
27
+ def set_image(
28
+ self, data: pygame.Surface | str, size: None | tuple[int, int] = None
29
+ ) -> Self:
30
+ super().set_image(data, size)
31
+ self.original_width, self.original_height = self.original_surface.get_size()
32
+ return self
33
+
34
+ def set_autoscroll(self, x: float, y: float) -> Self:
35
+ self.auto_scroll.update(x, y)
36
+ return self
37
+
38
+ def set_scroll(self, x: float = None, y: float = None) -> Self:
39
+ self.scroll_value.update(
40
+ x if x else self.scroll_value.x, y if y else self.scroll_value.y
41
+ )
42
+ return self
43
+
44
+ def scroll(self, x: float, y: float) -> Self:
45
+ self.scroll_value += x, y
46
+ return self
47
+
48
+ def update(self, dt: float) -> None:
49
+ if self.auto_scroll:
50
+ self.scroll(*self.auto_scroll * dt)
51
+ original_width, original_height = self.original_surface.get_size()
52
+
53
+ # Use integer values for the starting points, converted from floating point scroll values
54
+
55
+ if self.scroll_value.x > self.original_width:
56
+ self.scroll_value.x -= self.original_width
57
+ if self.scroll_value.y > self.original_height:
58
+ self.scroll_value.y -= self.original_height
59
+
60
+ super().update(dt)
61
+
62
+ def set_size(self, size: tuple[int | None, int | None]) -> Self:
63
+ size = list(size)
64
+ if size[0] is None: size[0] = self.rect.w
65
+ if size[1] is None: size[1] = self.rect.h
66
+
67
+ self.surface = pygame.Surface(size).convert_alpha()
68
+ self.rect = self.surface.get_frect(center=self.rect.center)
69
+ return self
70
+
71
+ def _get_mosaic_rect_list(self) -> Iterator[pygame.Rect]:
72
+ # Use integer values for the starting points, converted from floating point scroll values
73
+ start_x = int(self.scroll_value.x % self.original_width)
74
+ start_y = int(self.scroll_value.y % self.original_height)
75
+
76
+ # Adjust start_x and start_y to begin tiling off-screen to the top-left, covering all visible area
77
+ if start_x != 0:
78
+ start_x -= self.original_width
79
+ if start_y != 0:
80
+ start_y -= self.original_height
81
+
82
+ # Set the region in which to tile
83
+ end_x = self.rect.w
84
+ end_y = self.rect.h
85
+
86
+ # Starting y_position for the inner loop
87
+ y_position = start_y
88
+
89
+ # if self.rect.w-1 < self.scroll_value.x < self.rect.w+1 : print(self.scroll_value.x,int(self.scroll_value.x % self.rect.w),start_x,self.rect.w,original_width)
90
+ # Generate all necessary rectangles
91
+ x = start_x
92
+ while x < end_x:
93
+ y = y_position
94
+ while y < end_y:
95
+ yield pygame.Rect(x, y, self.original_width, self.original_height)
96
+ y += self.original_height
97
+ x += self.original_width
98
+ return self
99
+
100
+ def draw(self, camera: bf.Camera) -> int:
101
+ if not (
102
+ self.visible
103
+ and (self.surface is not None)
104
+ and camera.rect.colliderect(self.rect)
105
+ ):
106
+ return 0
107
+ self.surface.fill((0, 0, 0, 0))
108
+ self.surface.fblits(
109
+ [(self.original_surface, r) for r in self._get_mosaic_rect_list()]
110
+ )
111
+ # pygame.draw.rect(camera.surface,"green",next(self._get_mosaic_rect_list()).move(*self.rect.topleft),1)
112
+ camera.surface.blit(self.surface, camera.world_to_screen(self.rect))
113
+ return 1
batFramework/sprite.py CHANGED
@@ -2,32 +2,44 @@ import batFramework as bf
2
2
  import pygame
3
3
  from typing import Self
4
4
 
5
- class Sprite(bf.DynamicEntity):
5
+
6
+ class Sprite(bf.Entity):
6
7
  def __init__(
7
8
  self,
8
- data: pygame.Surface | str,
9
+ path=None,
9
10
  size: None | tuple[int, int] = None,
10
11
  convert_alpha: bool = True,
11
12
  ):
12
- self.original_surface = None
13
- super().__init__(convert_alpha=convert_alpha)
14
- if data:
15
- self.set_image(data, size)
16
-
17
- def set_image(
18
- self, data: pygame.Surface | str, size: None | tuple[int, int] = None
19
- ):
20
- if isinstance(data, str):
21
- tmp = bf.ResourceManager().get_image(data,self.convert_alpha)
22
- self.original_surface = tmp
23
- elif isinstance(data, pygame.Surface):
24
- self.original_surface = data
25
- else:
26
- raise ValueError("Image data can be either path or Surface")
27
- if self.convert_alpha:
28
- self.original_surface = self.original_surface.convert_alpha()
29
- if not size:
30
- size = self.original_surface.get_size()
31
- self.surface = pygame.transform.scale(self.original_surface, size)
32
- self.rect = self.surface.get_frect(center=self.rect.center)
13
+ self.original_surface: pygame.Surface = None
14
+
15
+ super().__init__(convert_alpha = convert_alpha)
16
+ if path is not None:
17
+ self.from_path(path)
18
+ if size is not None and self.original_surface:
19
+ self.set_size(self.original_surface.get_size())
20
+
21
+
22
+ def set_size(self,size:tuple[float,float]) -> Self:
23
+ if size == self.rect.size : return self
24
+ self.rect.size = size
25
+ self.surface = pygame.Surface((int(self.rect.w),int(self.rect.h)),self.surface_flags)
26
+ if self.convert_alpha : self.surface = self.surface.convert_alpha()
27
+ self.surface.fill((0,0,0,0 if self.convert_alpha else 255))
28
+ self.surface.blit(pygame.transform.scale(self.original_surface,self.rect.size),(0,0))
29
+ return self
30
+
31
+ def from_path(self,path:str)->Self:
32
+ tmp = bf.ResourceManager().get_image(path,self.convert_alpha)
33
+ if tmp is None:
34
+ return self
35
+ self.original_surface = tmp
36
+ size = self.original_surface.get_size()
37
+ self.set_size(size)
38
+ return self
33
39
 
40
+ def from_surface(self,surface: pygame.Surface)->Self:
41
+ if surface is None : return self
42
+ self.original_surface = surface
43
+ size = self.original_surface.get_size()
44
+ self.set_size(size)
45
+ return self
batFramework/tileset.py CHANGED
@@ -1,64 +1,46 @@
1
1
  import pygame
2
- from .utils import Utils
3
2
  from .resourceManager import ResourceManager
3
+ import batFramework as bf
4
4
 
5
- class Tileset:
6
- _tilesets = {}
7
- _flip_cache = {} # {"tileset":tileset,"index","flipX","flipY"}
8
5
 
9
- def __init__(self, source: pygame.Surface | str, tilesize) -> None:
10
- if isinstance(source, str):
11
- source = ResourceManager().get_image(source,convert_alpha=True)
12
6
 
13
- self.tile_dict = {}
7
+ class Tileset:
8
+ def __init__(self, source: pygame.Surface, tilesize: tuple[int, int]) -> None:
14
9
  self.surface = source
15
10
  self.tile_size = tilesize
16
- self.split_surface(self.surface)
11
+ self.tile_width = source.get_width() // tilesize[0]
12
+ self.tile_height = source.get_height() // tilesize[1]
13
+ # Create flipped versions of the tileset surface
14
+ self.flipped_surfaces = {
15
+ (False, False): self.surface,
16
+ (False, True): pygame.transform.flip(self.surface, False, True),
17
+ (True, False): pygame.transform.flip(self.surface, True, False),
18
+ (True, True): pygame.transform.flip(self.surface, True, True),
19
+ }
20
+
21
+ # Split each of the surfaces into tiles
22
+ self.tile_dict = {}
23
+ for flip_state, surf in self.flipped_surfaces.items():
24
+ tiles = bf.utils.split_surface(surf, self.tile_size)
25
+ for coord, tile in tiles.items():
26
+ if coord not in self.tile_dict:
27
+ self.tile_dict[coord] = {}
28
+ self.tile_dict[coord][flip_state] = tile
17
29
 
18
- def split_surface(self, surface: pygame.Surface):
19
- width, height = surface.get_size()
20
- num_tiles_x = width // self.tile_size
21
- num_tiles_y = height // self.tile_size
22
- # Iterate over the tiles vertically and horizontally
23
- for y in range(num_tiles_y):
24
- for x in range(num_tiles_x):
25
- # Calculate the coordinates of the current tile in the tileset
26
- tile_x = x * self.tile_size
27
- tile_y = y * self.tile_size
28
- # Create a subsurface for the current tile
29
- tile_surface = surface.subsurface(
30
- pygame.Rect(tile_x, tile_y, self.tile_size, self.tile_size)
31
- )
32
- # Calculate the unique key for the tile (e.g., based on its coordinates)
33
- tile_key = (x, y)
34
- # Store the subsurface in the dictionary with the corresponding key
35
- self.tile_dict[tile_key] = tile_surface
36
- # print(self.tile_dict)
37
30
 
38
- def get_tile(self, x, y, flipX=False, flipY=False) -> pygame.Surface | None:
39
- if (x, y) not in self.tile_dict:
40
- return None
41
- if flipX or flipY:
42
- key = f"{x}{y}:{flipX}{flipY}"
43
- if not key in self._flip_cache:
44
- self._flip_cache[key] = pygame.transform.flip(
45
- self.tile_dict[(x, y)], flipX, flipY
46
- )
47
- return self._flip_cache[key]
48
- return self.tile_dict[(x, y)]
31
+ def __str__(self)->str:
32
+ num_tiles = 0
33
+ if self.tile_dict:
34
+ num_tiles = len(self.tile_dict.values())
35
+ return f"{num_tiles} tiles | Tile size : {self.tile_size}"
49
36
 
50
- @staticmethod
51
- def load_tileset(path: str, name: str, tilesize) -> "Tileset":
52
- if name in Tileset._tilesets:
53
- return Tileset._tilesets[name]
54
- else:
55
- img = ResourceManager().get_image(path,convert_alpha=True)
56
- tileset = Tileset(img, tilesize)
57
- Tileset._tilesets[name] = tileset
58
- return tileset
37
+ def get_tile(self, x, y, flipX=False, flipY=False) -> pygame.Surface | None:
59
38
 
60
- @staticmethod
61
- def get_tileset(name: str) -> "Tileset":
62
- if name not in Tileset._tilesets:
39
+ if flipX:
40
+ x = self.tile_width - 1 - x
41
+ if flipY:
42
+ y = self.tile_height - 1 - y
43
+ tile_data = self.tile_dict.get((x, y), None)
44
+ if tile_data is None:
63
45
  return None
64
- return Tileset._tilesets[name]
46
+ return tile_data.get((flipX, flipY), None)
batFramework/time.py CHANGED
@@ -1,86 +1,135 @@
1
- import pygame
2
1
  import batFramework as bf
3
2
  from typing import Self
4
3
 
4
+
5
5
  class Timer:
6
- _count :int = 0
7
- def __init__(self,duration:float|int,end_callback,loop:bool=False)->None:
8
- self.name = Timer._count
9
- Timer._count+=1
6
+ _count: int = 0
7
+
8
+ def __init__(self, duration: float | int, end_callback, loop: bool = False) -> None:
9
+ self.name: int = Timer._count
10
+ Timer._count += 1
11
+
12
+ self.duration: int | float = duration
10
13
  self.end_callback = end_callback
11
- self.duration : int|float = duration
12
- self.paused : bool = False
13
- self.elapsed_time : int = -1
14
- self.over : bool = False
15
- self.do_delete:bool = False
16
- self.is_looping :bool = loop
17
-
18
- def stop(self)->Self:
19
- self.elapsed_time =-1
20
- self.over = False
21
- self.paused = False
14
+
15
+ self.elapsed_time: float = -1
16
+ self.is_over: bool = False
17
+ self.is_looping: bool = loop
18
+ self.is_paused: bool = False
19
+ self.do_delete: bool = False
20
+
21
+ def __repr__(self) -> str:
22
+ return f"Timer ({self.name}) {self.elapsed_time}/{self.duration} | {'loop ' if self.is_looping else ''} {'(D) ' if self.do_delete else ''}"
23
+
24
+ def stop(self) -> Self:
25
+ self.elapsed_time = -1
26
+ self.is_over = False
27
+ self.is_paused = False
22
28
  return self
23
-
24
- def start(self)->Self:
25
- if self.elapsed_time >= 0 : return self
29
+
30
+ def start(self, force: bool = False) -> Self:
31
+ if self.elapsed_time != -1 and not force:
32
+ return self
33
+ if not bf.TimeManager().add_timer(self):
34
+ return self
26
35
  self.elapsed_time = 0
27
- self.paused = False
28
- self.over = False
29
- bf.TimeManager().add_timer(self)
36
+ self.is_paused = False
37
+ self.is_over = False
30
38
  return self
31
-
32
- def pause(self)->Self:
33
- self.paused = True
39
+
40
+ def pause(self) -> Self:
41
+ self.is_paused = True
34
42
  return self
35
43
 
36
- def resume(self)->Self:
37
- self.paused = False
44
+ def resume(self) -> Self:
45
+ self.is_paused = False
38
46
  return self
39
- def delete(self)->Self:
47
+
48
+ def delete(self) -> Self:
40
49
  self.do_delete = True
41
50
  return self
42
- def get_progression(self)->float:
43
- if self.elapsed_time < 0 : return 0
44
- if self.elapsed_time >= self.duration: return 1
45
- return self.elapsed_time / self.duration
46
-
47
- def update(self,dt)->None:
48
- if self.elapsed_time < 0 or self.paused: return
51
+
52
+ def has_started(self) -> bool:
53
+ return self.elapsed_time != -1
54
+
55
+ def get_progression(self) -> float:
56
+ if self.elapsed_time < 0:
57
+ return 0
58
+ if self.elapsed_time >= self.duration:
59
+ return 1
60
+ return self.elapsed_time / self.duration
61
+
62
+ def update(self, dt) -> None:
63
+ if self.elapsed_time < 0 or self.is_paused or self.is_over:
64
+ return
49
65
  self.elapsed_time += dt
50
66
  # print("update :",self.elapsed_time,self.duration)
51
67
  if self.get_progression() == 1:
52
68
  self.end()
53
-
69
+
54
70
  def end(self):
55
- self.end_callback()
71
+ if self.end_callback:
72
+ self.end_callback()
73
+ self.elapsed_time = -1
74
+ self.is_over = True
56
75
  if self.is_looping:
57
- self.elapsed_time = -1
58
76
  self.start()
59
77
  return
60
-
61
- self.over = True
62
78
 
63
- def has_ended(self)->bool:
64
- return self.over or self.do_delete
79
+ def should_delete(self) -> bool:
80
+ return self.is_over or self.do_delete
81
+
82
+
83
+ class TimerRegister:
84
+ def __init__(self, active=True):
85
+ self.active = active
86
+ self.timers: dict[int | str, Timer] = {}
87
+
88
+ def __iter__(self):
89
+ return iter(self.timers.values())
90
+
91
+ def add_timer(self, timer: Timer):
92
+ self.timers[timer.name] = timer
93
+
94
+ def update(self, dt):
95
+ expired_timers = []
96
+ for timer in list(self.timers.values()):
97
+ if not timer.is_paused:
98
+ timer.update(dt)
99
+ if timer.should_delete():
100
+ expired_timers.append(timer.name)
101
+ for name in expired_timers:
102
+ del self.timers[name]
65
103
 
66
104
 
67
105
  class TimeManager(metaclass=bf.Singleton):
68
106
  def __init__(self):
69
- # Initialize the TimeManager class with a dictionary of timers
70
- self.timers = {}
107
+ self.registers = {"global": TimerRegister()}
71
108
 
72
- def add_timer(self, timer):
73
- # Add a timer to the dictionary
74
- self.timers[timer.name] = timer
109
+ def add_register(self, name, active=True):
110
+ if name not in self.registers:
111
+ self.registers[name] = TimerRegister(active)
112
+
113
+ def add_timer(self, timer, register="global") -> bool:
114
+ if register in self.registers:
115
+ self.registers[register].add_timer(timer)
116
+ return True
117
+ print(f"Register '{register}' does not exist.")
118
+ return False
119
+
120
+ def get_active_registers(self) -> list[TimerRegister]:
121
+ return [t for t in self.registers.values() if t.active]
75
122
 
76
- def update(self,dt):
77
- # Update all timers and remove completed ones
78
- timers = list(self.timers.values())
79
- for timer in [t for t in timers if not t.paused]:
80
- timer.update(dt)
123
+ def update(self, dt):
124
+ for register_name, register in self.registers.items():
125
+ if register.active:
126
+ register.update(dt)
81
127
 
82
- to_remove = [name for name, timer in self.timers.items() if timer.has_ended()]
128
+ def activate_register(self, name, active=True):
129
+ if name in self.registers:
130
+ self.registers[name].active = active
131
+ else:
132
+ print(f"Register '{name}' does not exist.")
83
133
 
84
- for name in to_remove:
85
- # print(self.timers.pop(name).name,"removed !")
86
- self.timers.pop(name)
134
+ def deactivate_register(self, name):
135
+ self.activate_register(name, active=False)
@@ -1,3 +1,215 @@
1
- import pygame
2
1
  import batFramework as bf
2
+ from typing import Self
3
+ import pygame
4
+
5
+ """
6
+ Both surfaces to transition need to be the same size
7
+
8
+ """
9
+
10
+
11
+ class Transition:
12
+ def __init__(
13
+ self, duration: float, easing_function: bf.easing = bf.easing.LINEAR
14
+ ) -> None:
15
+ """
16
+ duration : time in seconds
17
+ easing function : controls the progression rate
18
+ """
19
+ self.duration: float = duration
20
+ self.controller = bf.EasingController(
21
+ easing_function,
22
+ duration,
23
+ update_callback=self.update,
24
+ end_callback=self.end,
25
+ )
26
+ self.start_callback = None
27
+ self.update_callback = None
28
+ self.end_callback = None
29
+ self.source: pygame.Surface = None
30
+ self.dest: pygame.Surface = None
31
+
32
+ def __repr__(self) -> str:
33
+ return f"Transition {self.__class__},{self.duration}"
34
+
35
+ def set_start_callback(self, func) -> Self:
36
+ self.start_callback = func
37
+ return self
38
+
39
+ def set_update_callback(self, func) -> Self:
40
+ self.update_callback = func
41
+ return self
42
+
43
+ def set_end_callback(self, func) -> Self:
44
+ self.end_callback = func
45
+ return self
46
+
47
+ def set_source(self, surface: pygame.Surface) -> None:
48
+ self.source = surface
49
+
50
+ def set_dest(self, surface: pygame.Surface) -> None:
51
+ self.dest = surface
52
+
53
+ def start(self):
54
+ if self.controller.has_started():
55
+ return
56
+ if self.duration:
57
+ self.controller.start()
58
+ if self.start_callback:
59
+ self.start_callback()
60
+ return
61
+
62
+ self.controller.start()
63
+ if self.start_callback:
64
+ self.start_callback()
65
+ self.controller.end()
66
+ self.update(1)
67
+ self.end()
68
+
69
+ def update(self, progression: float) -> None:
70
+ if self.update_callback:
71
+ self.update_callback(progression)
72
+
73
+ def end(self):
74
+ self.controller.stop()
75
+ if self.end_callback:
76
+ self.end_callback()
77
+
78
+ def draw(self, surface: pygame.Surface) -> None:
79
+ pass
80
+
81
+ def skip(self, no_callback: bool = False):
82
+ self.controller.stop()
83
+ if self.end_callback and (no_callback == False):
84
+ self.end_callback()
85
+
86
+
87
+ class FadeColor(Transition):
88
+ def __init__(
89
+ self,
90
+ color: tuple | str,
91
+ middle_duration: float,
92
+ first_duration: float = None,
93
+ second_duration: float = None,
94
+ easing_function: bf.easing = bf.easing.LINEAR,
95
+ ) -> None:
96
+ super().__init__(0, easing_function)
97
+ if first_duration is None:
98
+ first_duration = middle_duration
99
+ if second_duration is None:
100
+ second_duration = middle_duration
101
+ self.index = 0
102
+ self.first = Fade(first_duration)
103
+ self.color = color
104
+ self.second = Fade(second_duration).set_end_callback(
105
+ lambda: self.next_step(self.end)
106
+ )
107
+ self.timer = bf.Timer(
108
+ middle_duration, lambda: self.next_step(self.second.start)
109
+ )
110
+ self.first.set_end_callback(lambda: self.next_step(self.timer.start))
111
+
112
+ def next_step(self, callback=None) -> None:
113
+ self.index += 1
114
+ if callback:
115
+ callback()
116
+
117
+ def set_source(self, surface) -> None:
118
+ super().set_source(surface)
119
+ self.first.set_source(surface)
120
+
121
+ def set_dest(self, surface) -> None:
122
+ super().set_dest(surface)
123
+ self.second.set_dest(surface)
124
+
125
+ def start(self):
126
+ if self.start_callback:
127
+ self.start_callback()
128
+ self.color_surf = pygame.Surface(self.source.get_size())
129
+ self.color_surf.fill(self.color)
130
+
131
+ self.first.set_dest(self.color_surf)
132
+ self.second.set_source(self.color_surf)
133
+ self.first.start()
134
+
135
+ def draw(self, surface):
136
+ if self.index == 0:
137
+ self.first.draw(surface)
138
+ elif self.index == 1:
139
+ surface.blit(self.color_surf, (0, 0))
140
+ else:
141
+ self.second.draw(surface)
142
+
143
+ def skip(self, no_callback: bool = False):
144
+ if (no_callback == False) and self.end_callback:
145
+ self.end_callback()
146
+
147
+ self.first.controller.stop()
148
+ self.timer.stop()
149
+ self.second.controller.stop()
150
+
151
+
152
+ class Fade(Transition):
153
+ def end(self):
154
+ self.dest.set_alpha(255)
155
+ return super().end()
156
+
157
+ def draw(self, surface):
158
+ dest_alpha = 255 * self.controller.get_value()
159
+ self.dest.set_alpha(dest_alpha)
160
+ surface.blit(self.source, (0, 0))
161
+ surface.blit(self.dest, (0, 0))
162
+
163
+
164
+ class GlideRight(Transition):
165
+ def draw(self, surface):
166
+ width = surface.get_width()
167
+ source_x = -self.controller.get_value() * width
168
+ surface.blit(self.source, (source_x, 0))
169
+ surface.blit(self.dest, (width + source_x, 0))
170
+
171
+
172
+ class GlideLeft(Transition):
173
+ def draw(self, surface):
174
+ width = surface.get_width()
175
+ source_x = self.controller.get_value() * width
176
+ surface.blit(self.source, (source_x, 0))
177
+ surface.blit(self.dest, (source_x - width, 0))
178
+
179
+
180
+ class CircleOut(Transition):
181
+ def start(self):
182
+ super().start()
183
+ self.circle_surf = self.source.copy()
184
+ self.circle_surf.set_colorkey((0, 0, 0))
185
+ self.circle_surf.fill((0, 0, 0))
186
+ self.surface_width = self.circle_surf.get_width()
187
+
188
+ def draw(self, surface):
189
+ v = self.controller.get_value()
190
+
191
+ radius = self.surface_width * v
192
+ pygame.draw.circle(
193
+ self.circle_surf, "white", self.circle_surf.get_rect().center, radius
194
+ )
195
+ mask = pygame.mask.from_surface(self.circle_surf)
196
+ mask.to_surface(surface=surface, setsurface=self.dest, unsetsurface=self.source)
197
+
198
+
199
+ class CircleIn(Transition):
200
+ def start(self):
201
+ super().start()
202
+ self.circle_surf = self.source.copy()
203
+ self.circle_surf.set_colorkey((0, 0, 0))
204
+ self.circle_surf.fill((0, 0, 0))
205
+ self.surface_width = self.circle_surf.get_width()
3
206
 
207
+ def draw(self, surface):
208
+ v = self.controller.get_value()
209
+ radius = self.surface_width - (self.surface_width * v)
210
+ self.circle_surf.fill((0, 0, 0))
211
+ pygame.draw.circle(
212
+ self.circle_surf, "white", self.circle_surf.get_rect().center, radius
213
+ )
214
+ mask = pygame.mask.from_surface(self.circle_surf)
215
+ mask.to_surface(surface=surface, setsurface=self.source, unsetsurface=self.dest)