def wavy_path(width, height, y_origin): half_height = height / 2 path = QPainterPath() pi2 = math.pi * 2 num = 100 num_waves = 4 wav_limit = num // num_waves sin = math.sin path.reserve(num) for i in range(num): x = width * i / num rads = pi2 * (i % wav_limit) / wav_limit factor = sin(rads) y = y_origin + factor * half_height path.lineTo(x, y) if i else path.moveTo(x, y) return path
class Pointer(QWidget): def __init__(self, gui): QWidget.__init__(self, gui) self.setObjectName('jobs_pointer') self.setVisible(False) self.resize(100, 80) self.animation = QPropertyAnimation(self, b"geometry", self) self.animation.setDuration(750) self.animation.setLoopCount(2) self.animation.setEasingCurve(QEasingCurve.Type.Linear) self.animation.finished.connect(self.hide) taily, heady = 0, 55 self.arrow_path = QPainterPath(QPointF(40, taily)) self.arrow_path.lineTo(40, heady) self.arrow_path.lineTo(20, heady) self.arrow_path.lineTo(50, self.height()) self.arrow_path.lineTo(80, heady) self.arrow_path.lineTo(60, heady) self.arrow_path.lineTo(60, taily) self.arrow_path.closeSubpath() c = self.palette().color(QPalette.ColorGroup.Active, QPalette.ColorRole.WindowText) self.color = QColor(c) self.color.setAlpha(100) self.brush = QBrush(self.color, Qt.BrushStyle.SolidPattern) # from qt.core import QTimer # QTimer.singleShot(1000, self.start) @property def gui(self): return self.parent() def point_at(self, frac): return (self.path.pointAtPercent(frac).toPoint() - QPoint(self.rect().center().x(), self.height())) def rect_at(self, frac): return QRect(self.point_at(frac), self.size()) def abspos(self, widget): pos = widget.pos() parent = widget.parent() while parent is not self.gui: pos += parent.pos() parent = parent.parent() return pos def start(self): if config['disable_animations']: return self.setVisible(True) self.raise_() end = self.abspos(self.gui.jobs_button) end = QPointF(end.x() + self.gui.jobs_button.width() / 3.0, end.y() + 20) start = QPointF(end.x(), end.y() - 0.5 * self.height()) self.path = QPainterPath(QPointF(start)) self.path.lineTo(end) self.path.closeSubpath() self.animation.setStartValue(self.rect_at(0.0)) self.animation.setEndValue(self.rect_at(1.0)) self.animation.setDirection(QAbstractAnimation.Direction.Backward) num_keys = 100 for i in range(1, num_keys): i /= num_keys self.animation.setKeyValueAt(i, self.rect_at(i)) self.animation.start() def paintEvent(self, ev): p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing) p.setBrush(self.brush) p.setPen(Qt.PenStyle.NoPen) p.drawPath(self.arrow_path) p.end()
def paintEvent(self, event): QSplitterHandle.paintEvent(self, event) left, right = self.parent().left, self.parent().right painter = QPainter(self) painter.setClipRect(event.rect()) w = self.width() h = self.height() painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) C = 16 # Curve factor. def create_line(ly, ry, right_to_left=False): ' Create path that represents upper or lower line of change marker ' line = QPainterPath() if not right_to_left: line.moveTo(0, ly) line.cubicTo(C, ly, w - C, ry, w, ry) else: line.moveTo(w, ry) line.cubicTo(w - C, ry, C, ly, 0, ly) return line ldoc, rdoc = left.document(), right.document() lorigin, rorigin = left.contentOffset(), right.contentOffset() lfv, rfv = left.firstVisibleBlock().blockNumber(), right.firstVisibleBlock().blockNumber() lines = [] for (ltop, lbot, kind), (rtop, rbot, kind) in zip(left.changes, right.changes): if lbot < lfv and rbot < rfv: continue ly_top = left.blockBoundingGeometry(ldoc.findBlockByNumber(ltop)).translated(lorigin).y() ly_bot = left.blockBoundingGeometry(ldoc.findBlockByNumber(lbot)).translated(lorigin).y() ry_top = right.blockBoundingGeometry(rdoc.findBlockByNumber(rtop)).translated(rorigin).y() ry_bot = right.blockBoundingGeometry(rdoc.findBlockByNumber(rbot)).translated(rorigin).y() if max(ly_top, ly_bot, ry_top, ry_bot) < 0: continue if min(ly_top, ly_bot, ry_top, ry_bot) > h: break upper_line = create_line(ly_top, ry_top) lower_line = create_line(ly_bot, ry_bot, True) region = QPainterPath() region.moveTo(0, ly_top) region.connectPath(upper_line) region.lineTo(w, ry_bot) region.connectPath(lower_line) region.closeSubpath() painter.fillPath(region, left.diff_backgrounds[kind]) for path, aa in zip((upper_line, lower_line), (ly_top != ry_top, ly_bot != ry_bot)): lines.append((kind, path, aa)) for kind, path, aa in sorted(lines, key=lambda x:{'replace':0}.get(x[0], 1)): painter.setPen(left.diff_foregrounds[kind]) painter.setRenderHints(QPainter.RenderHint.Antialiasing, aa) painter.drawPath(path) painter.setFont(left.heading_font) for (lnum, text), (rnum, text) in zip(left.headers, right.headers): ltop, lbot, rtop, rbot = lnum, lnum + 3, rnum, rnum + 3 if lbot < lfv and rbot < rfv: continue ly_top = left.blockBoundingGeometry(ldoc.findBlockByNumber(ltop)).translated(lorigin).y() ly_bot = left.blockBoundingGeometry(ldoc.findBlockByNumber(lbot)).translated(lorigin).y() ry_top = right.blockBoundingGeometry(rdoc.findBlockByNumber(rtop)).translated(rorigin).y() ry_bot = right.blockBoundingGeometry(rdoc.findBlockByNumber(rbot)).translated(rorigin).y() if max(ly_top, ly_bot, ry_top, ry_bot) < 0: continue if min(ly_top, ly_bot, ry_top, ry_bot) > h: break ly = painter.boundingRect(3, ly_top, left.width(), ly_bot - ly_top - 5, Qt.TextFlag.TextSingleLine, text).bottom() + 3 ry = painter.boundingRect(3, ry_top, right.width(), ry_bot - ry_top - 5, Qt.TextFlag.TextSingleLine, text).bottom() + 3 line = create_line(ly, ry) painter.setPen(QPen(left.palette().text(), 2)) painter.setRenderHints(QPainter.RenderHint.Antialiasing, ly != ry) painter.drawPath(line) painter.end() # Paint the splitter without the change lines if the mouse is over the # splitter if getattr(self, 'hover', False): QSplitterHandle.paintEvent(self, event)
def svg_path_to_painter_path(d): ''' Convert a tiny SVG 1.2 path into a QPainterPath. :param d: The value of the d attribute of an SVG <path> tag ''' from qt.core import QPainterPath cmd = last_cmd = b'' path = QPainterPath() moveto_abs, moveto_rel = b'M', b'm' closepath1, closepath2 = b'Z', b'z' lineto_abs, lineto_rel = b'L', b'l' hline_abs, hline_rel = b'H', b'h' vline_abs, vline_rel = b'V', b'v' curveto_abs, curveto_rel = b'C', b'c' smoothcurveto_abs, smoothcurveto_rel = b'S', b's' quadcurveto_abs, quadcurveto_rel = b'Q', b'q' smoothquadcurveto_abs, smoothquadcurveto_rel = b'T', b't' # Store the last parsed values # x/y = end position # x1/y1 and x2/y2 = bezier control points x = y = x1 = y1 = x2 = y2 = 0 if isinstance(d, unicode_type): d = d.encode('ascii') d = d.replace(b',', b' ').replace(b'\n', b' ') end = len(d) pos = [0] def read_byte(): p = pos[0] pos[0] += 1 return d[p:p+1] def parse_float(): chars = [] while pos[0] < end: c = read_byte() if c == b' ' and not chars: continue if c in b'-.0123456789': chars.append(c) else: break if not chars: raise ValueError('Premature end of input while expecting a number') return float(b''.join(chars)) def parse_floats(num, x_offset=0, y_offset=0): for i in range(num): val = parse_float() yield val + (x_offset if i % 2 == 0 else y_offset) repeated_command = None while pos[0] < end: last_cmd = cmd cmd = read_byte() if repeated_command is None else repeated_command repeated_command = None if cmd == b' ': continue if cmd == moveto_abs: x, y = parse_float(), parse_float() path.moveTo(x, y) elif cmd == moveto_rel: x += parse_float() y += parse_float() path.moveTo(x, y) elif cmd == closepath1 or cmd == closepath2: path.closeSubpath() elif cmd == lineto_abs: x, y = parse_floats(2) path.lineTo(x, y) elif cmd == lineto_rel: x += parse_float() y += parse_float() path.lineTo(x, y) elif cmd == hline_abs: x = parse_float() path.lineTo(x, y) elif cmd == hline_rel: x += parse_float() path.lineTo(x, y) elif cmd == vline_abs: y = parse_float() path.lineTo(x, y) elif cmd == vline_rel: y += parse_float() path.lineTo(x, y) elif cmd == curveto_abs: x1, y1, x2, y2, x, y = parse_floats(6) path.cubicTo(x1, y1, x2, y2, x, y) elif cmd == curveto_rel: x1, y1, x2, y2, x, y = parse_floats(6, x, y) path.cubicTo(x1, y1, x2, y2, x, y) elif cmd == smoothcurveto_abs: if last_cmd == curveto_abs or last_cmd == curveto_rel or last_cmd == smoothcurveto_abs or last_cmd == smoothcurveto_rel: x1 = 2 * x - x2 y1 = 2 * y - y2 else: x1, y1 = x, y x2, y2, x, y = parse_floats(4) path.cubicTo(x1, y1, x2, y2, x, y) elif cmd == smoothcurveto_rel: if last_cmd == curveto_abs or last_cmd == curveto_rel or last_cmd == smoothcurveto_abs or last_cmd == smoothcurveto_rel: x1 = 2 * x - x2 y1 = 2 * y - y2 else: x1, y1 = x, y x2, y2, x, y = parse_floats(4, x, y) path.cubicTo(x1, y1, x2, y2, x, y) elif cmd == quadcurveto_abs: x1, y1, x, y = parse_floats(4) path.quadTo(x1, y1, x, y) elif cmd == quadcurveto_rel: x1, y1, x, y = parse_floats(4, x, y) path.quadTo(x1, y1, x, y) elif cmd == smoothquadcurveto_abs: if last_cmd in (quadcurveto_abs, quadcurveto_rel, smoothquadcurveto_abs, smoothquadcurveto_rel): x1 = 2 * x - x1 y1 = 2 * y - y1 else: x1, y1 = x, y x, y = parse_floats(2) path.quadTo(x1, y1, x, y) elif cmd == smoothquadcurveto_rel: if last_cmd in (quadcurveto_abs, quadcurveto_rel, smoothquadcurveto_abs, smoothquadcurveto_rel): x1 = 2 * x - x1 y1 = 2 * y - y1 else: x1, y1 = x, y x, y = parse_floats(2, x, y) path.quadTo(x1, y1, x, y) elif cmd in b'-.0123456789': # A new number begins # In this case, multiple parameters tuples are specified for the last command # We rewind to reparse data correctly pos[0] -= 1 # Handle extra parameters if last_cmd == moveto_abs: repeated_command = cmd = lineto_abs elif last_cmd == moveto_rel: repeated_command = cmd = lineto_rel elif last_cmd in (closepath1, closepath2): raise ValueError('Extra parameters after close path command') elif last_cmd in ( lineto_abs, lineto_rel, hline_abs, hline_rel, vline_abs, vline_rel, curveto_abs, curveto_rel,smoothcurveto_abs, smoothcurveto_rel, quadcurveto_abs, quadcurveto_rel, smoothquadcurveto_abs, smoothquadcurveto_rel ): repeated_command = cmd = last_cmd else: raise ValueError('Unknown path command: %s' % cmd) return path