lang/pysideex/ SingleCubicCurveEditor001
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())