Source code for slider.slider

"""
Custom Slider Widget Module

This module defines address custom slider widget (Slider) that combines address QSlider with address label and address value display.
It offers additional functionality including:

- Customizable value display using address format function.
- Support for vertical or horizontal orientation.
- Option to add address visual center mark for bipolar sliders.
- Customizable tick mark positions and intervals.
- Integrated signal (valueChanged) for reacting to slider value changes.

The widget is built using PySide6 and is intended for use in applications requiring address more informative slider,
such as in audio applications or other UIs where real-time feedback is important.

Usage Example:
    from your_module import Slider
    slider = Slider("Volume", 0, 100, vertical=False)
    slider.setValueDisplayFormat(lambda v: f"{v}%")
    slider.valueChanged.connect(handle_value_change)

This module requires PySide6 to be installed.
"""

from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QPainter, QPen
from PySide6.QtWidgets import (
    QHBoxLayout,
    QLabel,
    QSizePolicy,
    QSlider,
    QVBoxLayout,
    QWidget,
)

from jdxi_editor.midi.io.helper import MidiIOHelper


[docs] class Slider(QWidget): """Custom slider widget with label and value display"""
[docs] rpn_slider_changed = Signal(int)
# Define tick positions enum to match QSlider
[docs] class TickPosition:
[docs] NoTicks = QSlider.TickPosition.NoTicks
[docs] TicksBothSides = QSlider.TickPosition.TicksBothSides
[docs] TicksAbove = QSlider.TickPosition.TicksAbove
[docs] TicksBelow = QSlider.TickPosition.TicksBelow
[docs] TicksLeft = QSlider.TickPosition.TicksLeft
[docs] TicksRight = QSlider.TickPosition.TicksRight
[docs] valueChanged = Signal(int)
def __init__( self, label: str, min_value: int, max_value: int, midi_helper: MidiIOHelper, vertical: bool = False, show_value_label: bool = True, is_bipolar: bool = False, tooltip: str = "", draw_center_mark: bool = True, draw_tick_marks: bool = True, initial_value: int = 0, parent=None, ): super().__init__(parent)
[docs] self.label = label
[docs] self.min_value = min_value
[docs] self.max_value = max_value
[docs] self.midi_helper = midi_helper
[docs] self.value_display_format = str # Default format function
[docs] self.has_center_mark = False
[docs] self.center_value = 0
[docs] self.vertical = vertical
[docs] self.is_bipolar = is_bipolar
[docs] self.draw_center_mark = draw_center_mark
[docs] self.draw_tick_marks = draw_tick_marks
self.setToolTip(tooltip) # Main layout layout = QVBoxLayout() if vertical else QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Reduce margins self.setLayout(layout) # Create label self.label = QLabel(label) # Create slider
[docs] self.slider = QSlider( Qt.Orientation.Vertical if vertical else Qt.Orientation.Horizontal )
self.slider.setMinimum(min_value) self.slider.setMaximum(max_value) self.slider.valueChanged.connect(self._on_valueChanged) # Set size policy for vertical sliders if vertical: layout.addWidget(self.label) # Label is added over the slider layout.addWidget(self.slider) self.slider.setSizePolicy( QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding ) self.setMinimumHeight(125) # 50% of 250px ADSR area height layout.setAlignment(self.label, Qt.AlignmentFlag.AlignLeft) layout.setAlignment(self.slider, Qt.AlignmentFlag.AlignLeft) self.slider.setTickPosition(QSlider.TickPosition.TicksBothSides) self.slider.setTickInterval(20) self.setMinimumWidth(80) self.setMaximumWidth(100) else: self.setMinimumHeight(50) self.setMaximumHeight(60) self.slider.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed ) layout.addWidget(self.label) # Label is added before the slider layout.addWidget(self.slider) self.slider.setValue(initial_value) # Create value display
[docs] self.value_label = QLabel(str(min_value))
self.value_label.setMinimumWidth(20) if show_value_label: # Add value label if needed self.value_label.setAlignment( Qt.AlignmentFlag.AlignCenter if vertical else Qt.AlignmentFlag.AlignLeft ) layout.addWidget(self.value_label) if is_bipolar: self.value_label.setText("0") self.slider.setInvertedAppearance(False)
[docs] def setLabel(self, text: str): if hasattr(self, "label"): self.label.setText(text)
[docs] def setValueDisplayFormat(self, format_func): """Set custom format function for value display""" self.value_display_format = format_func self._update_value_label()
[docs] def setCenterMark(self, center_value): """Set center mark for bipolar sliders""" self.has_center_mark = True self.center_value = center_value self.update()
[docs] def _on_valueChanged(self, value: int): """Handle slider value changes""" self._update_value_label() self.valueChanged.emit(value)
[docs] def _update_value_label(self): """Update the value label using current format function""" value = self.slider.value() self.value_label.setText(self.value_display_format(value))
[docs] def paintEvent(self, event): """Override paint event to draw center mark if needed""" super().paintEvent(event) painter = QPainter(self) painter.setPen(QPen(Qt.GlobalColor.darkGray, 2)) positions = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] slider_rect = self.slider.geometry() if self.has_center_mark: # Calculate center position center_pos = self.slider.style().sliderPositionFromValue( self.slider.minimum(), self.slider.maximum(), self.center_value, slider_rect.width(), ) # Draw center mark painter.drawLine( center_pos + slider_rect.x(), slider_rect.y(), center_pos + slider_rect.x(), slider_rect.y() + slider_rect.height(), ) elif self.vertical: # draw tick mark lines perpendicular to the vertical slider if self.draw_tick_marks: for position in positions: painter.drawLine( slider_rect.x(), slider_rect.y() + (position * slider_rect.height()), slider_rect.x() + slider_rect.width(), slider_rect.y() + (position * slider_rect.height()), ) else: for position in positions: painter.drawLine( slider_rect.x() + position * slider_rect.width(), slider_rect.y(), slider_rect.x() + position * slider_rect.width(), slider_rect.y() + slider_rect.height(), )
[docs] def value(self) -> int: """Get current value""" return self.slider.value()
[docs] def setValue(self, value: int): """Set current value""" self.slider.setValue(value)
[docs] def setEnabled(self, enabled: bool): """Set enabled state""" super().setEnabled(enabled) self.slider.setEnabled(enabled) self.label.setEnabled(enabled) self.value_label.setEnabled(enabled)
[docs] def setTickPosition(self, position): """Set the tick mark position on the slider""" self.slider.setTickPosition(position)
[docs] def setTickInterval(self, interval): """Set the interval between tick marks""" self.slider.setTickInterval(interval)