From: Frank Buss
Subject: GUI eval
Date: 
Message-ID: <1l8mpv9517muz$.1n8lmvlbfns9s$.dlg@40tude.net>
See http://www.lispbuilder.org and the latest project
"Windows CFFI mapping and examples"

I've included an example, with which you can write interactive and animated
GUI applications with Lisp for Windows without need to know something about
the Windows API. I have tested it with CLISP 2.38 and LispWorks 4.3.7, but
it uses CFFI, so it should work with every Windows Lisp implementation
which supports CFFI.

If you just want to write Lisp code for drawing lines, text and evaluating
mouse actions (or creating a solution for the "Progressive Dash"
challenge), but if you don't want to do all the ASDF and Lisp program
installation work, try the binary release, where CLISP and all necessary
packages are included:

http://prdownloads.sourceforge.net/lispbuilder/win32-gui-eval-0.1.zip?download

The included CLISP version is based on my CLISP GUI patch from
http://www.frank-buss.de/lisp/clisp.html , so it looks like a standard
Windows application without a CLISP console.

All the source is in the subversion repository of SourceForge. If you like,
join the Common Lisp Application Builder project and enhance the Windows
bindings and the GUI Eval example, e.g. show a message box when an error
occured instead of silently ignoring it, like it is currently implemented.

-- 
Frank Buss, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Frank Buss
Subject: Re: GUI eval
Date: 
Message-ID: <kccnc6k3g4a0$.1gmje0la1526z.dlg@40tude.net>
Frank Buss wrote:

> I've included an example, with which you can write interactive and animated
> GUI applications with Lisp for Windows without need to know something about
> the Windows API.

The timer example is a bit boring and black/grey is not so interesting. But
we can access the internal framebuffer for setting every color we want. You
can define a set-pixel function like this:

(defun set-pixel (x y color)
  (setf (mem-aref *framebuffer* :int (+ (* *width* y) x)) color))


and then a Game of Life implementation:


(defparameter *life-width* 200)
(defparameter *life-height* 200)

(defun make-board ()
  (make-array (list *life-width* *life-height*)
              :element-type '(unsigned-byte 8)
              :initial-element 0))

(defparameter *board-1* (make-board))
(defparameter *board-2* (make-board))
(defparameter *life-foreground* #x80ffff)
(defparameter *life-background* #x101010)

(defun set-rabbit-pattern ()
  (loop for (x y) in '((-3 0) (1 0) (2 0) (3 0) (-3 1) (-2 1) (-1 1) (2 1)
(-2 2)) 
        with center-x = (floor *life-width* 2)
        with center-y = (floor *life-height* 2) do
        (setf (aref *board-1* (+ center-x x) (+ center-y y)) 1)))

(defun draw-and-step-board ()
  (clear)
  (loop for x from 1 below (1- *life-width*) do
        (loop for y from 1 below (1- *life-height*) do
              (set-pixel x y
                         (if (zerop (aref *board-1* x y))
                             *life-background*
                           *life-foreground*))
              (let ((result (aref *board-1* x y))
                    (sum (+ (aref *board-1* x (1- y))
                            (aref *board-1* x (1+ y))
                            (aref *board-1* (1+ x) y)
                            (aref *board-1* (1- x) y)
                            (aref *board-1* (1+ x) (1+ y))
                            (aref *board-1* (1- x) (1- y))
                            (aref *board-1* (1+ x) (1- y))
                            (aref *board-1* (1- x) (1+ y)))))
                (if (= sum 3)
                    (setf result 1)
                  (if (/= sum 2)
                      (setf result 0)))
                (setf (aref *board-2* x y) result))))
  (rotatef *board-1* *board-2*)
  (repaint))


When entering it in the GUI window, you should add a compile at the end,
because I have only a normal "eval" implemented:


(compile 'set-pixel)
(compile 'draw-and-step-board)


Finally you can start the animation with these lines and a click on "Eval":


(set-rabbit-pattern)
(install-timer 100 #'draw-and-step-board)


http://www.frank-buss.de/tmp/lispbuilder-life.png


The bad news is that it is terrible slow. The blitting itself is fast, but
accessing the board-array and the offscreen-array is and needs 100% CPU
usage on my computer. The same in Java is much faster:

http://www.frank-buss.de/automaton/golautomaton.html

But if you fill larger rects, the Windows API is very fast, e.g. you can
use this function:


(defun fill-rect (x0 y0 width height color)
  (with-foreign-object (rect 'RECT)
    (set-struct-members rect RECT
      (left x0)
      (top y0)
      (right width)
      (bottom height))
    (let ((brush (CreateSolidBrush color)))
      (FillRect *framebuffer-dc* rect brush)
      (DeleteObject brush))))


You can add not only FillRect, but all other Windows GDI functions, like
for drawing beziers curves and arcs:

http://msdn.microsoft.com/library/en-us/gdi/linecurv_8bn7.asp
(but you have to write or create the CFFI definition for it, first)

On my computer I can draw 100 rectangles per second with nearly no CPU
usage, so as long as you don't set every pixel yourself, Lisp is fast
enough:


(install-timer 10 #'(lambda ()
                       (let ((x (random *width*))
                             (y (random *height*))
                             (width (random *width*))
                             (height (random *height*))
                             (color (random #x1000000)))
                         (fill-rect x y width height color)
                         (repaint))))


Finally the usual Mandelbrot example :-)


(defun value-to-color (value)
  (logior (mod (* 13 value) 256)
          (ash (mod (* 7 value) 256) 8)
          (ash (mod (* 2 value) 256) 16)))

(defun mandelbrot (x0 y0 x1 y1)
  (loop for y from 0 below 300 do
        (loop for x from 0 below 300 do
              (loop with a = (complex
                              (float (+ (* (/ (- x1 x0) 300) x) x0))
                              (float (+ (* (/ (- y1 y0) 300) y) y0)))
                    for z = a then (+ (* z z) a)
                    while (< (abs z) 2)
                    for c from 60 above 0
                    finally (set-pixel x y (value-to-color c)))))
  (repaint))

(compile 'value-to-color)
(compile 'mandelbrot)
(mandelbrot 0.2 0.5 0.4 0.7)


It needs some seconds to calculate the image. The result looks like in my
old CL-Canvas implementation:

http://www.frank-buss.de/lisp/canvas.html

-- 
Frank Buss, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de