Adding graphics support to DandeGUI

DandeGUI now does graphics and this is what it looks like.

Some text and graphics output windows created with DandeGUI on Medley Interlisp.

In addition to the square root table text output demo, I created the other graphics windows with the newly implemented functionality. For example, this code draws the random circles of the top window:

(DEFUN RANDOM-CIRCLES (&KEY (N 200)
                            (MAX-R 50)
                            (WIDTH 640)
                            (HEIGHT 480))
       (LET ((RANGE-X (- WIDTH (* 2 MAX-R)))
             (RANGE-Y (- HEIGHT (* 2 MAX-R)))
             (SHADES (LIST IL:BLACKSHADE IL:GRAYSHADE (RANDOM 65536))))
            (DANDEGUI:WITH-GRAPHICS-WINDOW (STREAM :TITLE "Random Circles")
					   (DOTIMES (I N)
						    (DECLARE (IGNORE I))
						    (IL:FILLCIRCLE (+ MAX-R (RANDOM RANGE-X))
								   (+ MAX-R (RANDOM RANGE-Y))
								   (RANDOM MAX-R)
								   (ELT SHADES (RANDOM 3))
								   STREAM)))))

GUI:WITH-GRAPHICS-WINDOW, GUI:OPEN-GRAPHICS-STREAM, and GUI:WITH-GRAPHICS-STREAM are the main additions. These functions and macros are the equivalent for graphics of what GUI:WITH-OUTPUT-TO-WINDOW, GUI:OPEN-WINDOW-STREAM, and GUI:WITH-WINDOW-STREAM, respectively, do for text. The difference is the text facilities send output to TEXTSTREAM streams whereas the graphics facilities to IMAGESTREAM, a type of device-independent graphics streams.

Under the hood DandeGUI text windows are customized TEdit windows with an associated TEXTSTREAM. TEdit is the rich text editor of Medley Interlisp.

Similarly, the graphics windows of DandeGUI run the Sketch line drawing editor under the hood. Sketch windows have an IMAGESTREAM which Interlisp graphics primitives like IL:DRAWLINE and IL:DRAWPOINT accept as an output destination. DandeGUI creates and manages Sketch windows with the type of stream the graphics primitives require. In other words, IMAGESTREAM is to Sketch what TEXTSTREAM is to TEdit.

The benefits of programmatically using Sketch for graphics are the same as TEdit windows for text: automatic window repainting, scrolling, and resizing. The downside is overhead. Scrolling more than a few thousand graphics elements is slow and adding even more may crash the system. However, this is an acceptable tradeoff.

The new graphics functions and macros work similarly to the text ones, with a few differences. First, DandeGUI now depends on the SKETCH and SKETCH-STREAM library modules which it automatically loads.

Since Sketch has no notion of a read-only drawing area GUI:OPEN-GRAPHICS-STREAM achieves the same effect by other means:

(DEFUN OPEN-GRAPHICS-STREAM (&KEY (TITLE "Untitled"))
   "Open a new window and return the associated IMAGESTREAM to send graphics output to.
Sets the window title to TITLE if supplied."
   (LET* ((STREAM (IL:OPENIMAGESTREAM '|Untitled| 'IL:SKETCH '(IL:FONTS ,*DEFAULT-FONT*)))
          (WINDOW (IL:\\SKSTRM.WINDOW.FROM.STREAM STREAM)))
         (IL:WINDOWPROP WINDOW 'IL:TITLE TITLE)
         ;; Disable left and middle-click title bar menu
         (IL:WINDOWPROP WINDOW 'IL:BUTTONEVENTFN NIL)
         ;; Disable sketch editing via right-click actions
         (IL:WINDOWPROP WINDOW 'IL:RIGHTBUTTONFN NIL)
         ;; Disable querying the user whether to save changes
         (IL:WINDOWPROP WINDOW 'IL:DONTQUERYCHANGES T)
         STREAM))

Only the mouse gestures and commands of the middle-click title bar menu and the right-click menu change the drawing area interactively. To disable these actions GUI:OPEN-GRAPHICS-STREAM removes their menu handlers by setting to NIL the window properties IL:BUTTONEVENTFN and IL:RIGHTBUTTONFN. This way only programmatic output can change the drawing area.

The function also sets IL:DONTQUERYCHANGES to T to prevent querying whether to save the changes at window close. By design output to DandeGUI windows is not permanent, so saving isn't necessary.

GUI:WITH-GRAPHICS-STREAM and GUI:WITH-GRAPHICS-WINDOW are straightforward:

(DEFMACRO WITH-GRAPHICS-STREAM ((VAR STREAM)
                                &BODY BODY)
   "Perform the operations in BODY with VAR bound to the graphics window STREAM.
Evaluates the forms in BODY in a context in which VAR is bound to STREAM which must already exist, then returns the value of the last form of BODY."
   `(LET ((,VAR ,STREAM))
         ,@BODY))

(DEFMACRO WITH-GRAPHICS-WINDOW ((VAR &KEY TITLE)
                                &BODY BODY)
   "Perform the operations in BODY with VAR bound to a new graphics window stream.
Creates a new window titled TITLE if supplied, binds VAR to the IMAGESTREAM associated with the window, and executes BODY in this context. Returns the value of the last form of BODY."
   `(WITH-GRAPHICS-STREAM (,VAR (OPEN-GRAPHICS-STREAM :TITLE (OR ,TITLE "Untitled")))
           ,@BODY))

Unlike GUI:WITH-TEXT-STREAM and GUI:WITH-TEXT-WINDOW, which need to call GUI::WITH-WRITE-ENABLED to establish a read-only environment after every output operation, GUI:OPEN-GRAPHICS-STREAM can do this only once at window creation.

GUI:CLEAR-WINDOW, GUI:WINDOW-TITLE, and GUI:PRINT-MESSAGE now work with graphics streams in addition to text streams. For IMAGESTREAM arguments GUI:PRINT-MESSAGE prints to the system prompt window as Sketch stream windows have no prompt area.

The random circles and fractal triangles graphics demos round up the latest additions.

#DandeGUI #CommonLisp #Interlisp #Lisp

Discuss... Email | Reply @amoroso@oldbytes.space