Dup Ver Goto 📝

SingleCubicCurveEditor001

PT2/aw/lang/pysideex does not exist
To
130 lines, 346 words, 3685 chars Page 'SingleCubicCurveEditor001' does not exist.

It's not perfect as if you drag an endpoint, the control point nearest to it doesn't move. But it illustrates the basic idea.

#
# Aim: single cubic segment with draggable handles
#
import sys
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtNetwork import *

class Handle:
  def __init__(self,x=0,y=0,r=10,shape="square",color=None):
    self.x = x
    self.y = y
    self.r = r
    self.shape = shape
    if color is not None:
      self.color = color
    else:
      self.color = QColor.fromRgb(0,0,0)
  def hit(self,x,y):
    if self.shape == "square":
      if (x < self.x - self.r) or (x > self.x + self.r) or (y < self.y - self.r ) or (y > self.y + self.r):
        return False
      return True
    elif self.shape == "circle":
      dx = x - self.x
      dy = y - self.y
      r2 = self.r * self.r
      dist2 = dx*dx + dy*dy
      return dist2 <= r2
    else:
      print(f"Invalid shape {self.shape}")
      return false
  def mouseMove(self,x,y):
    self.x = x
    self.y = y
  def paint(self,painter):
    x = self.x
    y = self.y
    r = self.r
    rect = QRect(x-r,y-r,2*r,2*r)
    path = QPainterPath()
    if self.shape == "square":
      path.addRect(rect)
    elif self.shape == "circle":
      path.addEllipse(rect)
    else:
      print(f"Invalid shape {self.shape}")
      return
    painter.save()
    painter.setBrush(self.color)
    painter.setPen(Qt.NoPen)
    painter.drawPath(path)
    painter.restore()

class Cubic:
  def __init__(self,x0,y0,xa,ya,xb,yb,x1,y1,color=None,width=5):
    self.p0 = Handle(x0,y0,10,"square",QColorConstants.Red)
    self.p1 = Handle(x1,y1,10,"square",QColorConstants.Green)
    self.pa = Handle(xa,ya,10,"circle",QColorConstants.Blue)
    self.pb = Handle(xb,yb,10,"circle",QColorConstants.Magenta)
    self.handles = [self.pa,self.pb,self.p0,self.p1]
    if color is not None:
      self.color = color
    else:
      self.color = QColor.fromRgb(0,0,0)
    self.width = width
  def hit(self,x,y):
    for handle in self.handles:
      if handle.hit(x,y):
        return handle
    return None
  def paint(self,painter):
    pa,pb,p0,p1 = self.handles
    path = QPainterPath()
    path.moveTo(p0.x,p0.y)
    path.cubicTo(pa.x,pa.y,pb.x,pb.y,p1.x,p1.y)
    painter.save()
    painter.setPen(QPen(self.color,self.width))
    painter.setBrush(Qt.NoBrush)
    painter.drawPath(path)
    painter.setPen(QPen(QColorConstants.Black,1))
    path = QPainterPath()
    path.moveTo(p0.x,p0.y)
    path.lineTo(pa.x,pa.y)
    painter.drawPath(path)
    path = QPainterPath()
    path.moveTo(p1.x,p1.y)
    path.lineTo(pb.x,pb.y)
    painter.drawPath(path)
    for handle in self.handles:
      handle.paint(painter)
    painter.restore()

class MyWidget(QWidget):
  def __init__(self,*xs,**kw):
    super().__init__(*xs,**kw)
    self.cubic = Cubic(50,50,150,50,150,150,250,150)
    self.sel = None
  def paintEvent(self,event):
    with QPainter(self) as painter:
      self.cubic.paint(painter)
  def mousePressEvent(self,event):
    pos = event.position().toPoint()
    x,y = pos.x(),pos.y()
    if handle := self.cubic.hit(x,y):
      self.sel = handle
    return super().mousePressEvent(event)
  def mouseReleaseEvent(self,event):
    self.sel = None
    return super().mouseReleaseEvent(event)
  def mouseMoveEvent(self,event):
    if self.sel is not None:
      pos = event.position().toPoint()
      x,y = pos.x(),pos.y()
      self.sel.mouseMove(x,y)
      self.update()
    return super().mouseMoveEvent(event)

app = QApplication(sys.argv)
win = QMainWindow()
w = MyWidget()
win.setCentralWidget(w)
win.setFixedSize(640,512)
win.show()
exit(app.exec())