"""
Digital Display
Usage Example:
==============
>>> digital = DigitalDisplay()
>>> digital.setPresetText("Grand Piano")
>>> digital.setPresetNumber(12)
>>> digital.setProgramText("User Program 1")
>>> digital.setProgramNumber(5)
>>> digital.setOctave(1)
"""
import platform
from decologr import Decologr as log
from PySide6.QtGui import QColor, QFont, QLinearGradient, QPainter, QPaintEvent, QPen
from PySide6.QtWidgets import QSizePolicy, QWidget
from jdxi_editor.ui.widgets.digital.base import DigitalDisplayBase
from jdxi_editor.ui.widgets.digital.state import JDXiDisplayState
[docs]
class DigitalDisplay(DigitalDisplayBase):
"""Digital LCD-style digital widget."""
def __init__(
self,
current_octave: int = 0,
digital_font_family: str = "JD LCD Rounded",
active_synth: str = "D1",
tone_name: str = "Init Tone",
tone_number: int = 1,
program_name: str = "Init Program",
program_bank_letter: str = "A",
program_number: int = 1,
parent: QWidget = None,
):
super().__init__(parent)
[docs]
self.active_synth = active_synth
[docs]
self.digital_font_family = digital_font_family
[docs]
self.current_octave = current_octave
[docs]
self.tone_name = tone_name
[docs]
self.tone_number = tone_number
[docs]
self.program_name = program_name or "Untitled Program"
[docs]
self.program_number = program_number
[docs]
self.program_bank_letter = program_bank_letter
[docs]
self.program_id = self.program_bank_letter + str(self.program_number)
[docs]
self.margin = 10 # Default margin for digital elements
[docs]
self._state: JDXiDisplayState = JDXiDisplayState(
synth="D1",
program_name="Init Program",
program_id="A1",
tone_name="Init Tone",
tone_number=1,
octave=0,
)
# Lazy import to avoid circular dependency
from jdxi_editor.core.jdxi import JDXi
self.setMinimumSize(
JDXi.UI.Dimensions.LED.WIDTH, JDXi.UI.Dimensions.LED.HEIGHT
) # Set size matching digital
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
[docs]
def paintEvent(self, event: QPaintEvent) -> None:
"""Handles the rendering of the digital digital.
:param event: QPaintEvent
"""
painter = QPainter(self)
if not painter.isActive():
return # Prevents drawing if painter failed to initialize
painter.setRenderHint(QPainter.Antialiasing, False)
self.draw_display(painter)
[docs]
def draw_display(self, painter: QPainter):
"""Draws the JD-Xi style digital digital with a gradient glow effect."""
display_x, display_y = 0, 0
display_width, display_height = self.width(), self.height()
# 1. Create an orange glow gradient background
gradient = QLinearGradient(0, 0, display_width, display_height)
gradient.setColorAt(0.0, QColor("#321212")) # Darker edges
gradient.setColorAt(0.3, QColor("#331111")) # Gray transition
gradient.setColorAt(0.5, QColor("#551100")) # Orange glow center
gradient.setColorAt(0.7, QColor("#331111")) # Gray transition
gradient.setColorAt(1.0, QColor("#111111")) # Darker edges
painter.setBrush(gradient)
painter.setRenderHint(QPainter.Antialiasing, False)
painter.setPen(QPen(QColor("#000000"), 2)) # black border
painter.drawRect(display_x, display_y, display_width, display_height)
# 2. Set font for digital digital
if platform.system() == "Windows":
font_size = 15
else:
font_size = 19
display_font = QFont(self.digital_font_family, font_size, QFont.Bold)
painter.setFont(display_font)
painter.setPen(QPen(QColor("#FFBB33"))) # Lighter orange for text
# 3. Draw text with glowing effect
tone_name_text = f" {self.active_synth}:{self.tone_name}"
tone_name_text = (
tone_name_text[:21] + "…" if len(tone_name_text) > 22 else tone_name_text
)
program_text = f"{self.program_id}:{self.program_name}"
program_text = (
program_text[:21] + "…" if len(program_text) > 22 else program_text
)
oct_text = f"Oct {self.current_octave:+}" if self.current_octave else "Oct 0"
# Glow effect simulation (by drawing text multiple times with slight offsets)
offsets = [(-2, -2), (1, -1), (-1, 1), (1, 1)]
glow_color = QColor("#FF00") # Darker orange for glow effect
for dx, dy in offsets:
painter.setPen(QPen(glow_color))
painter.drawText(display_x + 7 + dx, display_y + 20 + dy, program_text)
painter.drawText(display_x + 3 + dx, display_y + 50 + dy, tone_name_text)
painter.drawText(
display_x + display_width - 20 + dx, display_y + 30 + dy, oct_text
)
# Draw the main text on top
painter.setPen(QPen(QColor("#FFAA33"))) # Bright orange text
painter.drawText(display_x + 7, display_y + 50, tone_name_text)
painter.drawText(display_x + 7, display_y + 20, program_text)
painter.drawText(display_x + display_width - 66, display_y + 50, oct_text)
# --- Property Setters ---
[docs]
def setPresetText(self, text: str) -> None:
"""Set preset name and trigger repaint.
:param text: str
"""
self.tone_name = text
self.update()
[docs]
def setPresetNumber(self, number: int) -> None:
"""Set preset number and trigger repaint.
:param number: int
"""
self.tone_number = number
self.update()
[docs]
def setProgramText(self, text: str) -> None:
"""Set program name and trigger repaint.
:param text: str
"""
self.program_name = text
self.update()
[docs]
def setProgramNumber(self, number: int) -> None:
"""Set program number and trigger repaint.
:param number: int
"""
self.program_number = number
self.update()
[docs]
def setOctave(self, octave: int) -> None:
"""Set current octave and trigger repaint.
:param octave: int
"""
self.current_octave = octave
self.update()
[docs]
def repaint_display(
self,
current_octave: int,
tone_number: int,
tone_name: str,
program_name: str,
active_synth: str = "D1",
) -> None:
# Lazy import to avoid circular dependency
from jdxi_editor.ui.editors.helpers.program import get_program_id_by_name
self.current_octave = current_octave
self.tone_number = tone_number
self.tone_name = tone_name
self.program_name = program_name or "Untitled Program"
self.program_id = get_program_id_by_name(self.program_name)
self.active_synth = active_synth
self.update()
[docs]
def _update_display(
self,
synth_type,
digital1_tone_name,
digital2_tone_name,
drums_tone_name,
analog_tone_name,
tone_number,
tone_name,
program_name,
program_number,
program_bank_letter="A", # Default bank
):
"""Update the JD-Xi digital image.
:param synth_type: str
:param digital1_tone_name: str
:param digital2_tone_name: str
:param drums_tone_name: str
:param analog_tone_name: str
"""
# Lazy import to avoid circular dependency
from jdxi_editor.core.jdxi import JDXi
from jdxi_editor.ui.editors.helpers.preset import get_preset_list_number_by_name
if synth_type == JDXi.Synth.DIGITAL_SYNTH_1:
tone_name = digital1_tone_name
tone_number = get_preset_list_number_by_name(
tone_name, JDXi.UI.Preset.Digital.PROGRAM_CHANGE
)
active_synth = "D1"
elif synth_type == JDXi.Synth.DIGITAL_SYNTH_2:
tone_name = digital2_tone_name
active_synth = "D2"
tone_number = get_preset_list_number_by_name(
tone_name, JDXi.UI.Preset.Digital.PROGRAM_CHANGE
)
elif synth_type == JDXi.Synth.DRUM_KIT:
tone_name = drums_tone_name
active_synth = "DR"
tone_number = get_preset_list_number_by_name(
tone_name, JDXi.UI.Preset.Drum.PROGRAM_CHANGE
)
elif synth_type == JDXi.Synth.ANALOG_SYNTH:
tone_name = analog_tone_name
active_synth = "AN"
tone_number = get_preset_list_number_by_name(
tone_name, JDXi.UI.Preset.Analog.PROGRAM_CHANGE
)
else:
active_synth = "D1"
log.message(f"current tone number: {tone_number}")
log.message(f"current tone name: {tone_name}")
self.repaint_display(
current_octave=self.current_octave,
tone_number=tone_number,
tone_name=tone_name,
program_name=program_name,
active_synth=active_synth,
)