Skip to content

Instantly share code, notes, and snippets.

@cellularmitosis cellularmitosis/README.md

Last active Oct 17, 2019
Embed
What would you like to do?
Baby steps towards structured editing. Progress as of 2019/2/24.

Blog 2019/2/24

<- previous | index | next ->

Towards structured editing of code (part 1)

part 2 ->

Rationale

It is 2019 and the dominant programming workflow is still hand-editing text files! Let's start working towards a better future.

Initial plan:

  • Start small: a JSON or EDN visualizer
  • Then add basic editing operations to it
  • At no time is the programmer directly editing the raw text representation of the structure -- all edits are operations on the AST.

Let's look at an EDN fragment (a snippet of Clojure code):

screen shot 2019-02-24 at 5 38 22 pm

Text is merely one possible way to represent this structure. Many alternate representations are possible, for example:

  • symbols in white boxes
  • lists as blue boxes
  • vectors as green boxes

Mockup:

(made in google docs draw.io):

screen shot 2019-02-24 at 5 38 17 pm

Prototype

Let's try to write a program which can make something similar.

Attached is a rough pyqt5 script which renders a list of words in colored boxes:

screen shot 2019-02-25 at 12 30 48 am

That's my progress so far.

Next steps:

  • Render a group of words in a containing box (representing a list or vector)
  • Interactivity (a "selected" box based on keyboard input)
  • Editing individual nodes
#!/usr/bin/env python
# a pyqt script which draws words inside of rounded rectangles.
# see https://doc.qt.io/qt-5/qpainter.html
# see http://pyqt.sourceforge.net/Docs/PyQt5/api/qpen.html
import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
words = ["hello", "world", "these", "are", "words", "in", "boxes"]
# this function will be called on every paint event.
def on_paint(event, painter):
# fill the canvas with white
painter.fillRect(event.rect(), QBrush(Qt.white))
# paint the words
(x, y) = (20, 20)
paint_words(words, painter, x, y)
# paint the words in boxes, all in a single row.
def paint_words(words, painter, x, y):
pad = 5
# draw the words in boxes, all in a row.
for word in words:
(width, height) = paint_word(word, painter, x, y)
x += (width + pad)
# paint a word inside of a box, starting at (x, y)
# returns the (width, height) used to paint the word.
def paint_word(text, painter, x, y):
text_pad = 5
text_top_fudge = -2 # qt text seems to have extra padding on top.
corner_radius = 4
# calculate the size of the text
text_bounds = QFontMetrics(painter.font()).boundingRect(text)
# configure the box stroke
pen = QPen()
pen.setColor(Qt.red)
pen.setWidth(1)
painter.setPen(pen)
# configure the box fill
painter.setBrush(QBrush(QColor(255,192,192,255))) # light red
# draw the box
# note: the rounded corners come out a bit odd.
# see https://stackoverflow.com/questions/6507511/qt-round-rectangle-why-corners-are-different
painter.drawRoundedRect(
x,
y,
text_bounds.width() + (text_pad * 2),
text_bounds.height() + (text_pad * 2),
corner_radius,
corner_radius
)
# configure the text stroke
pen = QPen() # defaults to black
painter.setPen(pen)
# draw the text
painter.drawText(
x + text_pad,
y + text_bounds.height() + text_pad + text_top_fudge,
text
)
return (
text_bounds.width() + (text_pad * 2),
text_bounds.height() + (text_pad * 2)
)
# a widget which paints words inside of boxes
class WordPainter(QWidget):
def __init__(self, on_paint_fn):
QWidget.__init__(self)
self.on_paint_fn = on_paint_fn
# this gets called every time the widget needs to repaint (e.g. window resize)
def paintEvent(self, event):
then = time.time()
painter = QPainter()
painter.begin(self)
self.on_paint_fn(event, painter)
painter.end()
now = time.time()
elapsed = now - then
print "elapsed: %s" % elapsed
print "fps: %s" % (1.0/elapsed)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = WordPainter(on_paint)
window.show()
sys.exit(app.exec_())
@cellularmitosis

This comment has been minimized.

Copy link
Owner Author

cellularmitosis commented Feb 25, 2019

Initially I wanted to do this with using an OpenGL back-end, to get hardware acceleration, and things like zooming / panning for free. I started working with Dear ImGUI, but soon found that uses a lot of power (poor battery life for laptops). So I stepped away from "immediate mode" GUI and tried a "retained mode" GUI, via pyqt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.