From c312751c86755bdb592076aebd1bb048443ecb1e Mon Sep 17 00:00:00 2001 From: trav Date: Sat, 10 Oct 2026 02:24:17 -0700 Subject: [PATCH] v0.45.1: the laser's Afterimage is a number box, like Line width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type the seconds, or step by 0.05 with the arrows or ↑/↓; it reads "Off" at zero. Line width and Afterimage now share one number-box helper. --- lintunes/__init__.py | 2 +- lintunes/gui/preferences_dialog.py | 34 ++++++++++++++++++------------ tests/test_round78.py | 9 +++++--- tests/test_round79.py | 13 ++++++++++++ 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/lintunes/__init__.py b/lintunes/__init__.py index 3889316..772aab9 100644 --- a/lintunes/__init__.py +++ b/lintunes/__init__.py @@ -1,3 +1,3 @@ """LinTunes — iTunes-style music library manager and player for Linux.""" -__version__ = "0.45.0" +__version__ = "0.45.1" diff --git a/lintunes/gui/preferences_dialog.py b/lintunes/gui/preferences_dialog.py index 8ff71ff..08cb287 100644 --- a/lintunes/gui/preferences_dialog.py +++ b/lintunes/gui/preferences_dialog.py @@ -507,16 +507,18 @@ class PreferencesDialog(QDialog): laser = QWidget() form = self._viz_form(laser) self._preset_row(form, "laser") - self._laser_fade_slider, _ = self._viz_slider( + self._laser_fade_box = self._viz_spinbox( form, "Afterimage:", "visualizer_laser_afterimage", config, - scale=20, fmt=_fade_text, + step=0.05, decimals=2, suffix=" s", off_text="Off", tip="How long past sweeps of the beam take to fade — 0 is none") self._speaker = self._speaker_range(form, config) # The look controls on the left, and beside them a drawing of the # rig the settings describe — it labels the distance, the angles # and the dot, so those sliders carry no number of their own. looks = self._viz_form(None) - self._width_box = self._width_spinbox(looks, config) + self._width_box = self._viz_spinbox( + looks, "Line width:", "visualizer_laser_width", config, + step=0.5, decimals=1, suffix=" px") self._glow = self._viz_check( looks, "Glow", "visualizer_laser_glow", config, tip="The haze around the beam") @@ -583,27 +585,31 @@ class PreferencesDialog(QDialog): QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) return form - def _width_spinbox(self, form, config) -> QDoubleSpinBox: - """Line width as a number: type it, or step it by half a pixel with - the arrows (buttons or keys).""" - lo, hi = visualizer_styles.LIMITS["visualizer_laser_width"] + def _viz_spinbox(self, form, label, key, config, *, step, decimals, + suffix, off_text="", tip="") -> QDoubleSpinBox: + """A setting as a number: type it, or step it with the arrows + (buttons or ↑/↓). ``off_text`` is shown in place of the minimum.""" + lo, hi = visualizer_styles.LIMITS[key] box = QDoubleSpinBox() box.setRange(lo, hi) - box.setSingleStep(0.5) - box.setDecimals(1) - box.setSuffix(" px") - box.setValue(config["visualizer_laser_width"]) # before connect + box.setSingleStep(step) + box.setDecimals(decimals) + box.setSuffix(suffix) + box.setToolTip(tip) + if off_text: + box.setSpecialValueText(off_text) + box.setValue(config[key]) # before connect def changed(value): - self._prefs.set_live("visualizer_laser_width", value) + self._prefs.set_live(key, value) self._color_save_timer.start() box.valueChanged.connect(changed) row = QHBoxLayout() row.addWidget(box) row.addStretch() - form.addRow("Line width:", row) - self._viz_setters["visualizer_laser_width"] = box.setValue + form.addRow(label, row) + self._viz_setters[key] = box.setValue return box # ---- Visualizer presets ---- diff --git a/tests/test_round78.py b/tests/test_round78.py index 93883ac..8cb22c3 100644 --- a/tests/test_round78.py +++ b/tests/test_round78.py @@ -614,12 +614,15 @@ class TestPreferencesPage: prefs = Preferences(tmp_path) dialog = self._dialog(prefs) for slider, key in ((dialog._bars_fade_slider, "visualizer_bars_fade"), - (dialog._fade_slider, "visualizer_ghost_fade"), - (dialog._laser_fade_slider, - "visualizer_laser_afterimage")): + (dialog._fade_slider, "visualizer_ghost_fade")): assert slider.minimum() == 0 # 0 = off slider.setValue(10) assert prefs.get(key) == 0.5 + # The laser's is a number box, like Line width. + box = dialog._laser_fade_box + assert box.minimum() == 0 + box.setValue(0.5) + assert prefs.get("visualizer_laser_afterimage") == 0.5 def test_laser_settings(self, qapp, tmp_path): prefs = Preferences(tmp_path) diff --git a/tests/test_round79.py b/tests/test_round79.py index fddd6bf..fd84c5b 100644 --- a/tests/test_round79.py +++ b/tests/test_round79.py @@ -282,3 +282,16 @@ class TestCopyAsText: from lintunes.gui.track_table import song_line from lintunes.models import Track assert song_line(Track(track_id=9, name="", artist="X")) == "X - Untitled" + + +class TestAfterimageBox: + def test_steps_by_a_twentieth_and_reads_off_at_zero(self, qapp, tmp_path): + prefs = Preferences(tmp_path) + box = _prefs_dialog(prefs)._laser_fade_box + assert box.value() == pytest.approx(0.1) # the default + box.stepUp() + assert prefs.get("visualizer_laser_afterimage") == pytest.approx(0.15) + box.setValue(0) + assert box.text() == "Off" + box.setValue(99) # past the top + assert box.value() == 3.0