@@ -47,23 +47,11 @@ def spawn(self, lane_index, timestamp):
4747 if self .inactive_bars :
4848 bar = self .inactive_bars .pop ()
4949 else :
50- # Pool exhausted.
51- # Strategy: If below max_size, create new.
52- # If at max_size, recycle the oldest active bar (soft limit).
53- total_count = len (self .active_bars ) + len (self .inactive_bars ) # logic check
54- # Actually total known count is just what we have tracked.
55- # Let's track total instantiated count if strictly needed,
56- # but deque length is good enough.
57-
58- # Simple count check:
5950 if (len (self .active_bars ) + len (self .inactive_bars )) < self .max_size :
6051 bar = Bar ()
6152 elif self .active_bars :
62- # Soft Limit hit: Recycle oldest
63- bar = self .active_bars .popleft () # Oldest is usually at the left/start
64- # Technically we are taking it out of active to re-add it as new active
53+ bar = self .active_bars .popleft ()
6554 else :
66- # Should effectively never happen unless max_size=0
6755 bar = Bar ()
6856
6957 bar .active = True
@@ -79,16 +67,21 @@ def recycle(self, bar):
7967 self .inactive_bars .append (bar )
8068
8169class RainingKeysOverlay (QWidget ):
82- def __init__ (self ):
70+ def __init__ (self , settings_manager ):
8371 super ().__init__ ()
84- self .init_ui ()
72+ self .settings = settings_manager
73+ # Connect to settings changed signal
74+ self .settings .settings_changed .connect (self .on_settings_changed )
75+
8576 self .pool = BarPool (Config .MAX_BARS )
8677 self .active_holds = {} # {lane_index: Bar} tracking currently held notes
8778
79+ self .init_ui ()
80+
8881 # High-res timer for rendering
8982 self .timer = QTimer (self )
9083 self .timer .timeout .connect (self .update_canvas )
91- self .timer .start (16 ) # ~60 FPS target trigger, but delta used for physics
84+ self .timer .start (16 ) # ~60 FPS target trigger
9285
9386 # Debug stats
9487 self .last_fps_time = time .perf_counter ()
@@ -100,26 +93,42 @@ def init_ui(self):
10093 self .setWindowFlags (
10194 Qt .FramelessWindowHint |
10295 Qt .WindowStaysOnTopHint |
103- Qt .Tool | # Tool prevents showing in alt-tab usually
104- Qt .WindowTransparentForInput # Qt 5.10+ helper, acts as partial clickthrough
96+ Qt .Tool |
97+ Qt .WindowTransparentForInput
10598 )
10699 self .setAttribute (Qt .WA_TranslucentBackground )
107100 self .setAttribute (Qt .WA_TransparentForMouseEvents )
108101
109- # Full screen
110- screen_geo = QApplication .primaryScreen ().geometry ()
111- self .setGeometry (screen_geo )
102+ # Initial calculation for window size
103+ # We need enough width for all lanes
104+ max_lane = 0
105+ if Config .LANE_MAP :
106+ max_lane = max (Config .LANE_MAP .values ())
107+
108+ # Width: Start Offset + (Max Lane Index + 1) * Lane Width + Extra Padding
109+ width = Config .LANE_START_X + ((max_lane + 1 ) * Config .LANE_WIDTH ) + 50
110+ height = QApplication .primaryScreen ().size ().height ()
111+
112+ self .resize (width , height )
113+ # Move to configured position
114+ self .move (self .settings .overlay_x , self .settings .overlay_y )
112115
113- # Win32 Click-through + Always on Top enforcement
116+ # Win32 Click-through
114117 if HAS_WIN32 :
115118 hwnd = int (self .winId ())
116- styles = win32gui .GetWindowLong (hwnd , win32con .GWL_EXSTYLE )
117- win32gui .SetWindowLong (hwnd , win32con .GWL_EXSTYLE , styles | win32con .WS_EX_LAYERED | win32con .WS_EX_TRANSPARENT )
119+ try :
120+ styles = win32gui .GetWindowLong (hwnd , win32con .GWL_EXSTYLE )
121+ win32gui .SetWindowLong (hwnd , win32con .GWL_EXSTYLE , styles | win32con .WS_EX_LAYERED | win32con .WS_EX_TRANSPARENT )
122+ except Exception as e :
123+ print (f"Win32 Error: { e } " )
124+
125+ def on_settings_changed (self ):
126+ # Move window live when settings change
127+ self .move (self .settings .overlay_x , self .settings .overlay_y )
118128
119129 def handle_input (self , lane_index , timestamp ):
120130 """Slot called when input monitor detects a key press."""
121131 if lane_index in self .active_holds :
122- # Key already held? (Should be filtered by input_mon, but safety check)
123132 pass
124133 else :
125134 bar = self .pool .spawn (lane_index , timestamp )
@@ -138,66 +147,88 @@ def paintEvent(self, event):
138147 painter = QPainter ()
139148 try :
140149 if not painter .begin (self ):
141- # Failed to start painting (device occupied?)
142150 return
143151
144152 painter .setRenderHint (QPainter .Antialiasing )
145153
146154 current_time = time .perf_counter ()
147-
148- # Calculate Logic
149155 screen_h = self .height ()
150156 to_recycle = []
151157
158+ # Get current settings
159+ speed = self .settings .scroll_speed
160+ falling_down = (self .settings .fall_direction == 'down' )
161+
152162 # Bar Drawing Loop
153163 for bar in self .pool .active_bars :
154- # Calculate Y positions
155- # Bottom of the note (Leading Edge)
164+ # 1. Physics: Distance from Origin (Press Time)
156165 delta_press = current_time - bar .press_time + Config .INPUT_LATENCY_OFFSET
157- y_bottom = delta_press * Config . SCROLL_SPEED
166+ dist_head = delta_press * speed
158167
159- # Top of the note (Trailing Edge)
160168 if bar .release_time is None :
161- # Still held: Top is at current time (0 offset from 'now')
162- delta_release = Config .INPUT_LATENCY_OFFSET # effectively 0 time passed since 'now'
163- y_top = delta_release * Config .SCROLL_SPEED
169+ # Held
170+ dist_tail = Config .INPUT_LATENCY_OFFSET * speed
164171 else :
172+ # Released
165173 delta_release = current_time - bar .release_time + Config .INPUT_LATENCY_OFFSET
166- y_top = delta_release * Config . SCROLL_SPEED
174+ dist_tail = delta_release * speed
167175
168- # Check bounds (Recycle if Top is off-screen)
169- if y_top > screen_h :
170- to_recycle .append (bar )
171- continue
176+ # 2. Geometry
177+ height_bar = dist_head - dist_tail
178+ # Clamp min height so short taps are visible
179+ if height_bar < Config .BAR_HEIGHT :
180+ height_bar = Config .BAR_HEIGHT
181+ dist_tail = dist_head - height_bar
172182
173- # Render only if VISIBLE
174- if y_bottom < 0 :
175- continue
176-
177- # Fade Logic (based on leading edge / y_bottom)
183+ # Calculate Screen Y
184+ if falling_down :
185+ # Spawn at 0 (Top)
186+ # Tail is "above" Head (smaller Y value)
187+ rect_y = dist_tail
188+ else :
189+ # Spawn at ScreenHeight (Bottom)
190+ # Head moves UP (smaller Y value)
191+ # Tail moves UP (larger Y value than Head)
192+ # Y = H - dist.
193+ # Head Y = H - dist_head.
194+ # Tail Y = H - dist_tail.
195+ # Rect Top = Head Y (Smallest Y)
196+ rect_y = screen_h - dist_head
197+
198+ # 3. Recycle Check
199+ if falling_down :
200+ if rect_y > screen_h :
201+ to_recycle .append (bar )
202+ continue
203+ else :
204+ # Moving Up. If the bottom of that rect (rect_y + height) is < 0, it is gone.
205+ if (rect_y + height_bar ) < 0 :
206+ to_recycle .append (bar )
207+ continue
208+
209+ # 4. Fade Logic (Distance Traveled based)
210+ # Use dist_head (leading edge travel distance)
178211 alpha = 1.0
179- if y_bottom > Config .FADE_START_Y :
180- dist_into_fade = y_bottom - Config .FADE_START_Y
212+ if dist_head > Config .FADE_START_Y :
213+ dist_into_fade = dist_head - Config .FADE_START_Y
181214 factor = 1.0 - (dist_into_fade / Config .FADE_RANGE )
182215 alpha = max (0.0 , min (1.0 , factor ))
183-
184- # Draw
216+
217+ # 5. Draw
218+ # Optimization: Don't draw if transparent
219+ if alpha <= 0.0 :
220+ continue
221+
185222 x = Config .LANE_START_X + (bar .lane_index * Config .LANE_WIDTH )
186223
187- # Height
188- h = max (Config .BAR_HEIGHT , y_bottom - y_top )
189-
190- draw_y = y_bottom - h
191-
192- # Apply Color
193224 c = QColor (Config .COLOR_BAR )
194225 c .setAlphaF (alpha * (Config .COLOR_BAR .alphaF ()))
195226
196227 painter .setBrush (QBrush (c ))
197228 painter .setPen (Qt .NoPen )
198- painter .drawRect (QRectF (x , draw_y , Config .BAR_WIDTH , h ))
229+ painter .drawRect (QRectF (x , rect_y , Config .BAR_WIDTH , height_bar ))
199230
200- # Recycle off-screen bars
231+ # Recycle
201232 for bar in to_recycle :
202233 try :
203234 self .pool .active_bars .remove (bar )
@@ -230,9 +261,11 @@ def draw_debug(self, painter, current_time):
230261
231262 info = [
232263 f"FPS: { self .current_fps :.1f} " ,
233- f"Active Bars: { active_count } / { Config .MAX_BARS } " ,
234- f"Pool Size: { pool_size } " ,
235- f"Speed: { Config .SCROLL_SPEED } px/s"
264+ f"Active: { active_count } " ,
265+ f"Pool: { pool_size } " ,
266+ f"Speed: { self .settings .scroll_speed } " ,
267+ f"Dir: { self .settings .fall_direction } " ,
268+ f"Pos: { self .x ()} ,{ self .y ()} "
236269 ]
237270
238271 for i , line in enumerate (info ):
0 commit comments