pythonenaml

Is there a drawing/canvas widget in enaml?


I'd like to make UI that's basically an image that a user can trace out a path on with points connected by lines. I'd like it to be cross platform so I'm considering trying out enaml. I'm not sure how to accomplish this though.

I've looked through the documentation and examples and I can't find anything like a canvas widget. Is it possible to do arbitrary drawing (points, lines, etc) on a surface with enaml?


Solution

  • enaml doesn't have a canvas widget, but you can use the RawWidget along with a custom QWidget and a QPainter to draw whatever you need.

    from atom.api import Typed, Event
    from enaml.widgets.api import Window, Container, RawWidget
    from enaml.core.declarative import d_
    from enaml.qt.QtCore import Qt
    from enaml.qt.QtGui import QPainter, QPainterPath, QMouseEvent
    from enaml.qt.QtWidgets import QWidget
    
    
    class QtPaintWidget(QWidget):
        """ A widget that delegates drawing to enaml """
        def __init__(self, parent, widget):
            super(QtPaintWidget, self).__init__(parent)
            self.widget = widget
    
        def paintEvent(self, e):
            qp = QPainter()
            qp.begin(self)
            # Trigger the 'paint' event on th PaintWidget
            self.widget.paint(qp)
            qp.end()
    
        def mouseReleaseEvent(self, e):
            super(QtPaintWidget, self).mouseReleaseEvent(e)
            self.widget.mouse_event(e)
    
    
    class PaintWidget(RawWidget):
    
        #: Push the paint event up to enaml
        paint = d_(Event(QPainter))
    
        #: Handle mouse event in enaml
        mouse_event = d_(Event(QMouseEvent))
    
        def create_widget(self, parent):
            return QtPaintWidget(parent, self)
    
        def update(self):
            self.proxy.widget.update()
    
    
    enamldef Main(Window):
        Container:
            padding = 0
            PaintWidget: canvas:
                attr points = []
                minimum_size = (500, 500)
                paint ::
                    # See https://doc.qt.io/qt-5/qpainter.html
                    # for how to use the QPainter
                    qp = change['value']
                    qp.setBrush(Qt.white)
                    qp.drawRect(0, 0, 500,  500)
                    qp.setBrush(Qt.NoBrush)
                    qp.setPen(Qt.blue)
                    for p in points:
                        qp.drawPoint(p)
                    qp.setPen(Qt.red)
                    path = QPainterPath()
                    if len(points) > 1:
                        for i, p in enumerate(points):
                            if i==0:
                                path.moveTo(p)
                            else:
                                path.lineTo(p)
                    qp.drawPath(path)
                mouse_event ::
                    # Save the position
                    e = change['value']
                    print(e)
                    if e.button() == Qt.RightButton:
                        del points[:]
                    else:
                        points.append(e.pos())
                    canvas.update()
    

    And wala

    Drawing

    You don't have to push the paint event to enaml it just makes it easier to interact with other widgets. See QtPainter docs and http://zetcode.com/gui/pyqt5/painting/ for more on drawing in Qt.