Skip to content

Commit 0d6d7d3

Browse files
committed
refactor: replace global config with AppConfig dataclass and dependency injection
1 parent 1f64a57 commit 0d6d7d3

8 files changed

Lines changed: 289 additions & 328 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ RainingKeys is a purely external overlay application that visualizes keyboard in
4242
```text
4343
RainingKeysPython/
4444
├── core/
45-
│ ├── config.py # Default configuration constants
45+
│ ├── configuration.py # Dataclasses for application configuration
4646
│ ├── gui.py # Configuration Window GUI
4747
│ ├── input_mon.py # Global input listener
4848
│ ├── overlay.py # Main rendering loop and window logic

core/config.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

core/configuration.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from dataclasses import dataclass, field
2+
from typing import Dict, List
3+
from PySide6.QtGui import QColor
4+
5+
@dataclass
6+
class VisualSettings:
7+
scroll_speed: int = 800
8+
lane_width: int = 70
9+
bar_width: int = 70
10+
bar_height: int = 20
11+
bar_color_str: str = "0,255,255,200"
12+
13+
@property
14+
def bar_color(self) -> QColor:
15+
try:
16+
r, g, b, a = map(int, self.bar_color_str.split(','))
17+
return QColor(r, g, b, a)
18+
except ValueError:
19+
return QColor(0, 255, 255, 200)
20+
21+
@dataclass
22+
class PositionSettings:
23+
x: int = 0
24+
y: int = 0
25+
26+
@dataclass
27+
class KeyViewerSettings:
28+
enabled: bool = False
29+
layout: str = "horizontal"
30+
panel_position: str = "below"
31+
panel_offset_x: int = 0
32+
panel_offset_y: int = 0
33+
show_counts: bool = True
34+
height: int = 60
35+
opacity: float = 0.2
36+
37+
@dataclass
38+
class AppConfig:
39+
visual: VisualSettings = field(default_factory=VisualSettings)
40+
position: PositionSettings = field(default_factory=PositionSettings)
41+
key_viewer: KeyViewerSettings = field(default_factory=KeyViewerSettings)
42+
lane_map: Dict[str, int] = field(default_factory=dict)
43+
44+
# Constants
45+
MAX_BARS: int = 300
46+
INPUT_LATENCY_OFFSET: float = 0.0
47+
FADE_START_Y: int = 800
48+
FADE_RANGE: int = 200
49+
DEBUG_MODE: bool = False
50+
VERSION: str = "1.2.0"
51+
52+
# Derived properties could go here, but kept simple for now
53+
54+
def set_lane_keys(self, keys: List[str]):
55+
new_map = {}
56+
for idx, key in enumerate(keys):
57+
new_map[key] = idx
58+
self.lane_map = new_map

core/gui.py

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
from PySide6.QtCore import Qt, Slot
33
from PySide6.QtGui import QColor, QPalette
44
from .settings_manager import SettingsManager
5-
from .config import Config
65

76
class SettingsWindow(QWidget):
87
def __init__(self, settings_manager: SettingsManager):
98
super().__init__()
109
self.settings = settings_manager
11-
self.setWindowTitle(f"RainingKeys Config v{Config.VERSION}")
10+
self.config = self.settings.app_config
11+
self.setWindowTitle(f"RainingKeys Config v{self.config.VERSION}")
1212
self.resize(300, 350)
1313

1414
# Recording State
@@ -27,13 +27,13 @@ def init_ui(self):
2727
self.spin_x = QSpinBox()
2828
self.spin_x.setRange(-10000, 10000)
2929
self.spin_x.setPrefix("X: ")
30-
self.spin_x.setValue(self.settings.overlay_x)
30+
self.spin_x.setValue(self.config.position.x)
3131
self.spin_x.valueChanged.connect(self.on_pos_changed)
3232

3333
self.spin_y = QSpinBox()
3434
self.spin_y.setRange(-10000, 10000)
3535
self.spin_y.setPrefix("Y: ")
36-
self.spin_y.setValue(self.settings.overlay_y)
36+
self.spin_y.setValue(self.config.position.y)
3737
self.spin_y.valueChanged.connect(self.on_pos_changed)
3838

3939
pos_layout.addWidget(self.spin_x)
@@ -45,23 +45,13 @@ def init_ui(self):
4545
vis_group = QGroupBox("Visual Settings")
4646
vis_layout = QVBoxLayout()
4747

48-
# Direction (REMOVED: Bound to Position)
49-
# dir_layout = QHBoxLayout()
50-
# dir_layout.addWidget(QLabel("Fall Direction:"))
51-
# self.combo_dir = QComboBox()
52-
# self.combo_dir.addItems(["down", "up"])
53-
# self.combo_dir.setCurrentText(self.settings.fall_direction)
54-
# self.combo_dir.currentTextChanged.connect(self.on_visual_changed)
55-
# dir_layout.addWidget(self.combo_dir)
56-
# vis_layout.addLayout(dir_layout)
57-
5848
# Speed
5949
speed_layout = QHBoxLayout()
6050
speed_layout.addWidget(QLabel("Scroll Speed (px/s):"))
6151
self.spin_speed = QSpinBox()
6252
self.spin_speed.setRange(100, 5000)
6353
self.spin_speed.setSingleStep(50)
64-
self.spin_speed.setValue(self.settings.scroll_speed)
54+
self.spin_speed.setValue(self.config.visual.scroll_speed)
6555
self.spin_speed.valueChanged.connect(self.on_visual_changed)
6656
speed_layout.addWidget(self.spin_speed)
6757
vis_layout.addLayout(speed_layout)
@@ -82,7 +72,7 @@ def init_ui(self):
8272
lane_group = QGroupBox("Lane Configuration")
8373
lane_layout = QVBoxLayout()
8474

85-
self.lbl_lane_status = QLabel("Current Keys: " + str(len(Config.LANE_MAP)))
75+
self.lbl_lane_status = QLabel("Current Keys: " + str(len(self.config.lane_map)))
8676
self.lbl_lane_status.setWordWrap(True)
8777
lane_layout.addWidget(self.lbl_lane_status)
8878

@@ -102,7 +92,7 @@ def init_ui(self):
10292
kv_layout = QVBoxLayout()
10393

10494
self.chk_kv_enabled = QCheckBox("Enable KeyViewer")
105-
self.chk_kv_enabled.setChecked(self.settings.kv_enabled)
95+
self.chk_kv_enabled.setChecked(self.config.key_viewer.enabled)
10696
self.chk_kv_enabled.stateChanged.connect(self.on_kv_changed)
10797
kv_layout.addWidget(self.chk_kv_enabled)
10898

@@ -111,15 +101,14 @@ def init_ui(self):
111101
kv_grid.addWidget(QLabel("Height:"))
112102
self.spin_kv_height = QSpinBox()
113103
self.spin_kv_height.setRange(10, 500)
114-
self.spin_kv_height.setValue(self.settings.kv_height)
104+
self.spin_kv_height.setValue(self.config.key_viewer.height)
115105
self.spin_kv_height.valueChanged.connect(self.on_kv_changed)
116106
kv_grid.addWidget(self.spin_kv_height)
117107

118108
kv_grid.addWidget(QLabel("Pos:"))
119109
self.combo_kv_pos = QComboBox()
120110
self.combo_kv_pos.addItems(["below", "above"])
121-
# Handle removed 'auto' gracefully if config still has it
122-
current = self.settings.kv_position
111+
current = self.config.key_viewer.panel_position
123112
if current not in ["below", "above"]:
124113
current = "below"
125114
self.combo_kv_pos.setCurrentText(current)
@@ -132,13 +121,13 @@ def init_ui(self):
132121
off_layout.addWidget(QLabel("Offset X:"))
133122
self.spin_kv_off_x = QSpinBox()
134123
self.spin_kv_off_x.setRange(-1000, 1000)
135-
self.spin_kv_off_x.setValue(self.settings.kv_offset_x)
124+
self.spin_kv_off_x.setValue(self.config.key_viewer.panel_offset_x)
136125
self.spin_kv_off_x.valueChanged.connect(self.on_kv_changed)
137126
off_layout.addWidget(self.spin_kv_off_x)
138127
off_layout.addWidget(QLabel("Y:"))
139128
self.spin_kv_off_y = QSpinBox()
140129
self.spin_kv_off_y.setRange(-1000, 1000)
141-
self.spin_kv_off_y.setValue(self.settings.kv_offset_y)
130+
self.spin_kv_off_y.setValue(self.config.key_viewer.panel_offset_y)
142131
self.spin_kv_off_y.valueChanged.connect(self.on_kv_changed)
143132
off_layout.addWidget(self.spin_kv_off_y)
144133
kv_layout.addLayout(off_layout)
@@ -149,13 +138,13 @@ def init_ui(self):
149138
self.spin_kv_opacity = QSpinBox()
150139
self.spin_kv_opacity.setRange(0, 100)
151140
self.spin_kv_opacity.setSuffix("%")
152-
self.spin_kv_opacity.setValue(int(self.settings.kv_opacity * 100))
141+
self.spin_kv_opacity.setValue(int(self.config.key_viewer.opacity * 100))
153142
self.spin_kv_opacity.valueChanged.connect(self.on_kv_changed)
154143
trans_layout.addWidget(self.spin_kv_opacity)
155144
kv_layout.addLayout(trans_layout)
156145

157146
self.chk_kv_counts = QCheckBox("Show Key Counts")
158-
self.chk_kv_counts.setChecked(self.settings.kv_show_counts)
147+
self.chk_kv_counts.setChecked(self.config.key_viewer.show_counts)
159148
self.chk_kv_counts.stateChanged.connect(self.on_kv_changed)
160149
kv_layout.addWidget(self.chk_kv_counts)
161150

@@ -166,40 +155,39 @@ def init_ui(self):
166155
self.setLayout(layout)
167156

168157
def on_pos_changed(self):
169-
self.settings.set('Position', 'x', self.spin_x.value())
170-
self.settings.set('Position', 'y', self.spin_y.value())
171-
self.settings.save()
158+
self.config.position.x = self.spin_x.value()
159+
self.config.position.y = self.spin_y.value()
160+
self.settings.save()
172161

173162
def choose_color(self):
174-
current = self.settings.bar_color
163+
current = self.config.visual.bar_color
175164
color = QColorDialog.getColor(current, self, "Select Bar Color", QColorDialog.ShowAlphaChannel)
176165
if color.isValid():
177166
rgba = f"{color.red()},{color.green()},{color.blue()},{color.alpha()}"
178-
self.settings.set('Visual', 'bar_color', rgba)
167+
self.config.visual.bar_color_str = rgba
179168
self.settings.save()
180169
self.update_color_btn_style()
181170

182171
def update_color_btn_style(self):
183-
c = self.settings.bar_color
172+
c = self.config.visual.bar_color
184173
# Text color contrasting
185174
text_col = "black" if c.lightness() > 128 else "white"
186175
style = f"background-color: rgba({c.red()},{c.green()},{c.blue()},{c.alpha()}); color: {text_col};"
187176
self.btn_color.setStyleSheet(style)
188177
self.btn_color.setText(f"RGBA({c.red()},{c.green()},{c.blue()},{c.alpha()})")
189178

190179
def on_visual_changed(self):
191-
# self.settings.set('Visual', 'fall_direction', self.combo_dir.currentText())
192-
self.settings.set('Visual', 'scroll_speed', self.spin_speed.value())
180+
self.config.visual.scroll_speed = self.spin_speed.value()
193181
self.settings.save()
194182

195183
def on_kv_changed(self):
196-
self.settings.set('keyviewer', 'enabled', self.chk_kv_enabled.isChecked())
197-
self.settings.set('keyviewer', 'height', self.spin_kv_height.value())
198-
self.settings.set('keyviewer', 'panel_position', self.combo_kv_pos.currentText())
199-
self.settings.set('keyviewer', 'panel_offset_x', self.spin_kv_off_x.value())
200-
self.settings.set('keyviewer', 'panel_offset_y', self.spin_kv_off_y.value())
201-
self.settings.set('keyviewer', 'show_counts', self.chk_kv_counts.isChecked())
202-
self.settings.set('keyviewer', 'opacity', self.spin_kv_opacity.value() / 100.0)
184+
self.config.key_viewer.enabled = self.chk_kv_enabled.isChecked()
185+
self.config.key_viewer.height = self.spin_kv_height.value()
186+
self.config.key_viewer.panel_position = self.combo_kv_pos.currentText()
187+
self.config.key_viewer.panel_offset_x = self.spin_kv_off_x.value()
188+
self.config.key_viewer.panel_offset_y = self.spin_kv_off_y.value()
189+
self.config.key_viewer.show_counts = self.chk_kv_counts.isChecked()
190+
self.config.key_viewer.opacity = self.spin_kv_opacity.value() / 100.0
203191
self.settings.save()
204192

205193
def toggle_recording(self):
@@ -218,10 +206,8 @@ def toggle_recording(self):
218206

219207
if self.recorded_keys:
220208
# Save
221-
self.settings.save_lanes(self.recorded_keys)
209+
self.settings.update_lanes(self.recorded_keys)
222210
self.lbl_lane_status.setText(f"Saved {len(self.recorded_keys)} lane keys.")
223-
224-
# Update overlay if needed? save_lanes emits settings_changed which overlay listens to.
225211
else:
226212
self.lbl_lane_status.setText("No keys recorded. Canceled.")
227213

core/input_mon.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import time
22
from PySide6.QtCore import QObject, Signal, QThread
33
from pynput import keyboard
4-
from .config import Config
4+
from .configuration import AppConfig
55

66
class InputWorker(QObject):
77
"""
@@ -12,8 +12,9 @@ class InputWorker(QObject):
1212
key_released = Signal(int, float) # lane_index, timestamp
1313
raw_key_pressed = Signal(str) # raw_key_string
1414

15-
def __init__(self):
15+
def __init__(self, app_config: AppConfig):
1616
super().__init__()
17+
self.config = app_config
1718
self.listener = None
1819
self.running = False
1920
self.active_keys = set() # Track pressed keys to filter autorepeats
@@ -50,10 +51,10 @@ def on_press(self, key):
5051

5152
self.raw_key_pressed.emit(k_str)
5253

53-
if k_str in Config.LANE_MAP:
54+
if k_str in self.config.lane_map:
5455
self.active_keys.add(k_str)
5556
timestamp = time.perf_counter()
56-
lane_idx = Config.LANE_MAP[k_str]
57+
lane_idx = self.config.lane_map[k_str]
5758
self.key_pressed.emit(lane_idx, timestamp)
5859

5960
def on_release(self, key):
@@ -65,9 +66,9 @@ def on_release(self, key):
6566
if k_str in self.active_keys:
6667
self.active_keys.remove(k_str)
6768

68-
if k_str in Config.LANE_MAP:
69+
if k_str in self.config.lane_map:
6970
timestamp = time.perf_counter()
70-
lane_idx = Config.LANE_MAP[k_str]
71+
lane_idx = self.config.lane_map[k_str]
7172
self.key_released.emit(lane_idx, timestamp)
7273

7374
class InputMonitor(QThread):
@@ -78,9 +79,9 @@ class InputMonitor(QThread):
7879
key_released = Signal(int, float)
7980
raw_key_pressed = Signal(str)
8081

81-
def __init__(self):
82+
def __init__(self, app_config: AppConfig):
8283
super().__init__()
83-
self.worker = InputWorker()
84+
self.worker = InputWorker(app_config)
8485
self.worker.key_pressed.connect(self.key_pressed.emit)
8586
self.worker.key_released.connect(self.key_released.emit)
8687
self.worker.raw_key_pressed.connect(self.raw_key_pressed.emit)

0 commit comments

Comments
 (0)