@@ -32,35 +32,32 @@ def precompute(self, banner) -> None:
3232 self ._total_chars = len (self ._full_text )
3333
3434 def apply (self , lines : list [str ], t : float ) -> list [str ]:
35- total_chars = self ._total_chars
36- if total_chars == 0 :
35+ if self ._total_chars == 0 :
3736 return lines
3837
39- typing_time = total_chars / (
40- self .chars_per_second * max (self .config .speed , 0.1 )
41- )
38+ speed = max (self .config .speed , 0.1 )
39+ t = t * speed
40+
41+ typing_time = self ._total_chars / self .chars_per_second
4242 cycle_time = typing_time + self .pause
4343
4444 if self .loop :
45- t = t % max ( cycle_time , 0.001 )
45+ t = t % cycle_time
4646 else :
4747 t = min (t , typing_time )
4848
49- if t >= typing_time :
50- revealed = total_chars
51- else :
52- revealed = int ((t * self .chars_per_second * self .config .speed ) + 0.5 )
49+ revealed = min (self ._total_chars , int (t * self .chars_per_second ))
5350
54- visible = self ._full_text [:revealed ]
55- split = visible .split ("\n " )
51+ visible = self ._full_text [:revealed ].split ("\n " )
5652
5753 result : list [str ] = []
5854 for row , line in enumerate (lines ):
59- if row < len (split ):
60- current = split [row ]
61- result .append (current + ( " " * max (0 , len (line ) - len (current ) )))
55+ if row < len (visible ):
56+ current = visible [row ]
57+ result .append (current + " " * max (0 , len (line ) - len (current )))
6258 else :
6359 result .append (" " * len (line ))
60+
6461 return result
6562
6663
@@ -71,32 +68,25 @@ class FadeInEffect(Effect):
7168 def name (self ) -> str :
7269 return "fade_in"
7370
71+ def _alpha (self , t : float ) -> float :
72+ speed = max (self .config .speed , 0.1 )
73+ progress = (t * speed ) % 2.0
74+ if progress > 1.0 :
75+ progress = 2.0 - progress
76+ return progress
77+
7478 def apply (self , lines : list [str ], t : float ) -> list [str ]:
7579 return lines
7680
7781 def brightness (
78- self ,
79- t : float ,
80- * ,
81- row : int ,
82- col : int ,
83- char : str ,
84- lines : list [str ],
82+ self , t : float , * , row : int , col : int , char : str , lines : list [str ]
8583 ) -> float :
86- del row , col , char , lines
87- return clamp (t * self .config .speed * 0.9 , 0.0 , 1.0 )
84+ return 1.0
8885
8986 def opacity (
90- self ,
91- t : float ,
92- * ,
93- row : int ,
94- col : int ,
95- char : str ,
96- lines : list [str ],
87+ self , t : float , * , row : int , col : int , char : str , lines : list [str ]
9788 ) -> float :
98- del row , col , char , lines
99- return clamp (t * self .config .speed * 0.9 , 0.0 , 1.0 )
89+ return self ._alpha (t )
10090
10191
10292class WipeEffect (Effect ):
@@ -106,7 +96,7 @@ def __init__(
10696 self ,
10797 config = None ,
10898 direction : str = "horizontal" ,
109- loop : bool = False ,
99+ loop : bool = True ,
110100 ** _ : object ,
111101 ) -> None :
112102 super ().__init__ (config )
@@ -119,69 +109,42 @@ def name(self) -> str:
119109
120110 def precompute (self , banner ) -> None :
121111 super ().precompute (banner )
122- # Compute the bounding box of visible (non-space) characters so the wipe
123- # doesn't spend most of its time "revealing" indentation.
124- min_row = None
125- max_row = None
126- min_col = None
127- max_col = None
128- for row , line in enumerate (self ._base_lines ):
129- if not line :
130- continue
131- for col , ch in enumerate (line ):
132- if ch == " " :
133- continue
134- if min_row is None or row < min_row :
135- min_row = row
136- if max_row is None or row > max_row :
137- max_row = row
138- if min_col is None or col < min_col :
139- min_col = col
140- if max_col is None or col > max_col :
141- max_col = col
142-
143- self ._min_row = min_row if min_row is not None else 0
144- self ._max_row = (
145- max_row if max_row is not None else max (0 , len (self ._base_lines ) - 1 )
146- )
147- self ._min_col = min_col if min_col is not None else 0
148- self ._max_col = max_col if max_col is not None else max (0 , self ._base_width - 1 )
112+
113+ rows = len (self ._base_lines )
114+ cols = self ._base_width
115+
116+ self ._min_row = 0
117+ self ._max_row = max (0 , rows - 1 )
118+ self ._min_col = 0
119+ self ._max_col = max (0 , cols - 1 )
149120
150121 def apply (self , lines : list [str ], t : float ) -> list [str ]:
151- progress = t * self .config .speed
122+ speed = max (self .config .speed , 0.1 )
123+ t = t * speed
124+
152125 if self .loop :
153- progress = progress % 1.0
126+ progress = t % 1.0
154127 else :
155- progress = clamp (progress )
128+ progress = clamp (t , 0.0 , 1.0 )
156129
157130 if self .direction == "vertical" :
158- start = self ._min_row
159- height = max (1 , (self ._max_row - self ._min_row ) + 1 )
160- cutoff = start + round (height * progress )
161- result : list [str ] = []
162- for row , line in enumerate (lines ):
163- if row < start or row > self ._max_row :
164- result .append (line )
165- elif row < cutoff :
166- result .append (line )
167- else :
168- result .append (" " * len (line ))
169- return result
170-
171- horizontal_result : list [str ] = []
172- start = self ._min_col
173- width = max (1 , (self ._max_col - self ._min_col ) + 1 )
174- cutoff_col = start + round (width * progress )
131+ height = self ._max_row - self ._min_row + 1
132+ cutoff = self ._min_row + int (height * progress )
133+
134+ return [
135+ line if row <= cutoff else " " * len (line )
136+ for row , line in enumerate (lines )
137+ ]
138+
139+ width = self ._max_col - self ._min_col + 1
140+ cutoff_col = self ._min_col + int (width * progress )
141+
142+ result = []
175143 for line in lines :
176- if not line :
177- horizontal_result .append (line )
178- continue
179144 padded = line .ljust (self ._base_width )
180- cutoff = max (0 , min (len (padded ), cutoff_col ))
181- horizontal_result .append (
182- padded [:cutoff ] + (" " * max (0 , len (padded ) - cutoff ))
183- )
184- return horizontal_result
145+ result .append (padded [:cutoff_col ] + " " * max (0 , len (padded ) - cutoff_col ))
146+
147+ return result
185148
186149
187150class StaggerEffect (Effect ):
@@ -192,23 +155,36 @@ def __init__(
192155 config = None ,
193156 line_delay : float = 0.16 ,
194157 chars_per_second : float = 120.0 ,
158+ loop : bool = True ,
195159 ** _ : object ,
196160 ) -> None :
197161 super ().__init__ (config )
198162 self .line_delay = line_delay
199163 self .chars_per_second = chars_per_second
164+ self .loop = loop
200165
201166 @property
202167 def name (self ) -> str :
203168 return "stagger"
204169
205170 def apply (self , lines : list [str ], t : float ) -> list [str ]:
171+ speed = max (self .config .speed , 0.1 )
172+ t = t * speed
173+
174+ if self .loop :
175+ # Calculate cycle time
176+ num_lines = len (lines )
177+ max_line_length = max (len (line ) for line in lines ) if lines else 0
178+ reveal_time = max_line_length / self .chars_per_second
179+ total_delay = (num_lines - 1 ) * self .line_delay
180+ cycle_time = total_delay + reveal_time
181+ t = t % cycle_time
182+
206183 result : list [str ] = []
207- cps = self .chars_per_second * max (self .config .speed , 0.1 )
208184
209185 for row , line in enumerate (lines ):
210- local_t = max (0.0 , t - ( row * self .line_delay ) )
211- visible = min (len (line ), int (local_t * cps ))
212- result .append (line [:visible ] + ( " " * max ( 0 , len (line ) - visible ) ))
186+ local_t = max (0.0 , t - row * self .line_delay )
187+ visible = min (len (line ), int (local_t * self . chars_per_second ))
188+ result .append (line [:visible ] + " " * ( len (line ) - visible ))
213189
214190 return result
0 commit comments