Updating the window in response to CLIM frame commands

250 views Asked by At

While trying to figure out CLIM, I ran into this example program. It's a simple maze game. The author claims to have tested it in LispWorks (and even has #+Genera in there, implying that this program would work on a real Lisp Machine), but I'm trying to get it working in SBCL with McCLIM.

Under SBCL/McCLIM, the window draws, but nothing visible happens when you press the movement keys. Non-movement keys cause text to be entered into the pane with the game instructions.

I figured out that the game command keys are changing the game's internal state, so the only problem is that the screen does not update.

Then I realized that you couldn't write code to redraw the maze from the scope of the code that implements the commands. All the methods that draw receive a stream argument from CLIM, which must be passed to the graphics primitives. For example:

(defun draw-stone (stream x y cell-width cell-height)
  (let ((half-cell-width (/ cell-width 2))
        (half-cell-height (/ cell-height 2)))
    (draw-ellipse* stream
                   (+ (* x cell-width) half-cell-width)
                   (+ (* y cell-height) half-cell-height)
                   half-cell-width 0
                   0 half-cell-height
                   :ink +red+)))

But the code that handles keystrokes receives no stream argument:

(defmacro define-stone-move-command (name keystroke dx dy)
  `(define-maze-frame-command (,name :keystroke ,keystroke) ()
     (let ((maze-array (maze-array *application-frame*)))
       (move-stone maze-array ,dx ,dy)
       (check-for-win maze-array))))

What I ended up having to do is to save the stream argument from the first (and only) call to draw-maze-array to a global variable, so that I could add update code to the define-stone-command macro as follows:

(defmacro define-stone-move-command (name keystroke dx dy)
  `(define-maze-frame-command (,name :keystroke ,keystroke) ()
     (let ((maze-array (maze-array *application-frame*)))
       (move-stone maze-array ,dx ,dy)
       (check-for-win maze-array)
       (draw-maze-array *application-frame* *maze-stream*))))

This slight alteration gives the desired behavior on SBCL with McCLIM, but this doesn't seem right, however. After all, the author claimed that the code worked fine on LispWorks. I have a few questions:

  1. Can somebody who has LispWorks confirm that this program works as-is on LispWorks?
  2. Does my alteration to the code make it fail on LispWorks?
  3. What is the accepted way to handle screen updating in CLIM applications?
1

There are 1 answers

1
Rainer Joswig On BEST ANSWER

Drawing the maze in the command is not the right approach. Putting a maze-stream into a global variable is also bad. ;-)

The display pane has a :display-function. The idea is that after a command the whole application frame gets updated automagically. For example for :display-time :command-loop, the display pane would be updated automagically, after a command runs. There are other ways to update panes, but in this case a keystroke runs a command and then the top-level-loop would just call the display-function for each applicable pane. The default toplevel-loop reads a command (via mouse, command lines, keystrokes, ...), executes it and updates the application frame - in a loop.

The whole redisplay thing is extremely tricky/powerful. It allows from fully automagical redisplay mechanisms to extremely fine-grained control.

You can read about it here: CLIM 2 Spec. Note: there might be quite a bit difference between the spec and what implementations provide...