Dup Goto 📝

CustomPaintWidgets1

To Pop
307 lines, 829 words, 7656 chars Monday 2023-07-31 13:46:47

Linear and Rotary controls

These are intended to show MIDI CC values, hence the default min,max of 0..127.

Linear

from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from rtmidi import MidiIn, MidiOut
import sys
import math
def radians(x):
  return x*math.pi/180
def degrees(x):
  return x*180/math.pi
app = QApplication([])

# Define Widgets
class Linear(QWidget):
  def __init__(self):
    super().__init__()
    self.value = 45
    self.maxval = 127
    self.minval = 0
    self.mx = 0
    self.my = 0
  def mousePressEvent(self,e):
    self.mval = self.value
    pos = e.pos()
    self.mx = pos.x()
    self.my = pos.y()
    print(f"press {pos.x()} {pos.y()}")
  def mouseMoveEvent(self,e):
    pos = e.pos()
    dy = - (pos.y() - self.my)
    self.value = max(min(self.mval + dy,self.maxval),self.minval)
    print(f"move {pos.x()} {pos.y()} -- {dy=}")
    self.update()

  def paintEvent(self, event: QPaintEvent):
    """Override method from QWidget
    Paint the Pixmap into the widget
    """
    rect = self.rect()
    w = rect.width()
    h = rect.height()
    w1 = w*0.9
    h1 = h*0.9
    cx = w/2
    cy = h/2
    m = min(w,h)
    r = (m/2) * 0.9
    y0 = cy - h1/2

    font = QFont("Arial",r/4)
    metrics = QFontMetrics(font)
    t = str(self.value)
    twidth = metrics.horizontalAdvance(t)
    theight = metrics.capHeight()
    tx = cx-(twidth/2)
    ty = cy+(theight/2)
    svalue = (self.value-self.minval)/(self.maxval-self.minval)
    with QPainter(self) as painter:
      painter.fillRect(rect,Qt.white)
      painter.setBrush(Qt.NoBrush)
      path = QPainterPath()
      path.moveTo(QPoint(cx,y0+h1))
      path.lineTo(QPoint(cx,y0))
      painter.setPen(QPen(Qt.black, 5))
      painter.drawPath(path)
      path.clear()
      path.moveTo(QPoint(cx,y0+h1))
      path.lineTo(QPoint(cx,y0+h1*(1-svalue)))
      painter.setPen(QPen(Qt.red,10))
      painter.drawPath(path)
      painter.setFont(font)
      painter.drawText(QPoint(tx,ty),t)

# Instantiate Widgets
linear = Linear()
linear.show()

# Loader
def main():
  exit(app.exec())

if __name__ == "__main__":
  main()

Rotary

from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from rtmidi import MidiIn, MidiOut
import sys
import math
def radians(x):
  return x*math.pi/180
def degrees(x):
  return x*180/math.pi
app = QApplication([])

# Define Widgets
class Rotary(QWidget):
  def __init__(self):
    super().__init__()
    self.value = 45
    self.maxval = 127
    self.minval = 0
    self.mx = 0
    self.my = 0
  def mousePressEvent(self,e):
    self.mval = self.value
    pos = e.pos()
    self.mx = pos.x()
    self.my = pos.y()
    print(f"press {pos.x()} {pos.y()}")
  def mouseMoveEvent(self,e):
    pos = e.pos()
    dy = - (pos.y() - self.my)
    self.value = max(min(self.mval + dy,self.maxval),self.minval)
    print(f"move {pos.x()} {pos.y()} -- {dy=}")
    self.update()

  def paintEvent(self, event: QPaintEvent):
    """Override method from QWidget
    Paint the Pixmap into the widget
    """
    rect = self.rect()
    w = rect.width()
    h = rect.height()
    cx = w/2
    cy = h/2
    m = min(w,h)
    r = (m/2) * 0.9
    x = cx - r 
    y = cy - r
    ww = 2*r
    wh = 2*r
    wr = QRect(QPoint(x,y),QSize(ww,wh))

    wtheta = 300
    itheta = 270-((360-wtheta)//2)
    dtheta = wtheta*(self.value/(self.maxval-self.minval))
    font = QFont("Arial",r/2)
    metrics = QFontMetrics(font)
    t = str(self.value)
    twidth = metrics.horizontalAdvance(t)
    theight = metrics.capHeight()
    tx = cx-(twidth/2)
    ty = cy+(theight/2)
    with QPainter(self) as painter:
      painter.fillRect(rect,Qt.white)
      path = QPainterPath()
      path.arcMoveTo(wr,itheta)
      path.arcTo(wr,itheta,-dtheta)
      painter.setBrush(Qt.NoBrush)
      painter.setPen(QPen(Qt.black, 5))
      painter.drawPath(path)
      painter.setFont(font)
      painter.drawText(QPoint(tx,ty),t)

# Instantiate Widgets
rotary = Rotary()
rotary.show()

# Loader
def main():
  exit(app.exec())

if __name__ == "__main__":
  main()

Linear2

Illustrates how to use modifiers like shift and control (note mods.value and Qt.ShiftModifier.value if you want to bitwise compare), and have shift do fine-grained movements, and control jumping straight to the mouse position.

from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from rtmidi import MidiIn, MidiOut
import sys
import math
def radians(x):
  return x*math.pi/180
def degrees(x):
  return x*180/math.pi
app = QApplication([])

# Define Widgets
class LinearSlider1(QWidget):
  def __init__(self):
    super().__init__()
    self.value = 45
    self.maxval = 127
    self.minval = 0
    self.mx = 0
    self.my = 0
    self.ly = 0
    self.lv = 0
    self.sens = 1 # scales so that dragging the height of the widget goes from min to max
  def mousePressEvent(self,e):
    self.mval = self.value
    pos = e.pos()
    self.mx = pos.x()
    self.my = pos.y()
    self.ly = self.my
    self.lv = self.value
    mods = e.modifiers()
    if mods == Qt.ControlModifier:
      return self.mouseMoveAbsolute(e)
    print(f"press {pos.x()} {pos.y()}")
  def mouseMoveAbsolute(self,e):
    rect = self.rect()
    h = rect.height()
    pos = e.pos()
    y = pos.y()
    x = 1 - y/h
    self.value = self.minval + x*(self.maxval - self.minval)
    self.value = min(self.maxval,max(self.minval,self.value))
    self.lv = self.value
    self.ly = y
    self.value = int(self.value)
    self.update()
  def mouseMoveEvent(self,e):
    mods = e.modifiers()
    sens = self.sens
    if mods == Qt.ControlModifier:
      return self.mouseMoveAbsolute(e)
    if mods.value & Qt.ShiftModifier.value:
      print("shift")
      sens /= 10
    rect = self.rect()
    h = rect.height()
    pos = e.pos()
    dy = - (pos.y() - self.ly)
    self.ly = pos.y()
    dy1 = sens*(dy / h)*(self.maxval - self.minval) 
    print("aa",self.lv,dy1)
    self.lv = max(min(dy1+self.lv,self.maxval),self.minval)

    self.value = int(self.lv) 
    self.update()
    return
    dy = - (pos.y() - self.my)
    rect = self.rect()
    h = rect.height()
    dy1 = sens*(dy / h)*(self.maxval-self.minval)
    print(f"{h=} {dy=} {dy1=}")
    self.value = int(max(min(self.mval + dy1,self.maxval),self.minval))
    print(f"move {pos.x()} {pos.y()} -- {dy=}")
    self.update()

  def paintEvent(self, event: QPaintEvent):
    """Override method from QWidget
    Paint the Pixmap into the widget
    """
    rect = self.rect()
    w = rect.width()
    h = rect.height()
    w1 = w
    h1 = h
    cx = w/2
    cy = h/2
    m = min(w,h)
    r = (m/2)
    y0 = cy - h1/2

    font = QFont("Arial",r/4)
    metrics = QFontMetrics(font)
    t = str(self.value)
    twidth = metrics.horizontalAdvance(t)
    theight = metrics.capHeight()
    tx = cx-(twidth/2)
    ty = cy+(theight/2)
    svalue = (self.value-self.minval)/(self.maxval-self.minval)
    with QPainter(self) as painter:
      painter.fillRect(rect,Qt.white)
      painter.setBrush(Qt.NoBrush)
      path = QPainterPath()
      path.moveTo(QPoint(cx,y0+h1))
      path.lineTo(QPoint(cx,y0))
      painter.setPen(QPen(Qt.black, 5))
      painter.drawPath(path)
      path.clear()
      path.moveTo(QPoint(cx,y0+h1))
      path.lineTo(QPoint(cx,y0+h1*(1-svalue)))
      painter.setPen(QPen(Qt.red,10))
      painter.drawPath(path)
      painter.setFont(font)
      painter.drawText(QPoint(tx,ty),t)

# Instantiate Widgets
slider = LinearSlider1()
slider.show()

# Loader
def main():
  exit(app.exec())

if __name__ == "__main__":
  main()