Version 14 (modified by rme, 14 years ago) (diff)


Cocoa Bridge

As discussed in the section on the OpenMCL FFI, OpenMCL has a very powerful interface to the world of libraries and components that exist outside of the Lisp image. Foremost among these is the Cocoa Bridge, a binding layer to the Mac OS X user interface.

A good introductory Cocoa text is Aaron Hillegass's Cocoa Programming for Mac OS X.

Elementary Usage

Here's a very simple example of how to create a window and draw to it.

(in-package "CL-USER")

(require "COCOA")

(defclass red-view (ns:ns-view)
  (:metaclass ns:+ns-object))

(objc:defmethod (#/drawRect: :void) ((self red-view) (rect :<NSR>ect))
  (#/set (#/redColor ns:ns-color))
  (#_NSRectFill (#/bounds self)))

(defun show-red-window ()
   (let* ((rect (ns:make-ns-rect 0 0 300 300))
	  (w (make-instance 'ns:ns-window
			    :with-content-rect rect
			    :style-mask (logior #$NSTitledWindowMask
			    :backing #$NSBackingStoreBuffered
			    :defer t)))
     (#/setTitle: w "Red")
     (#/setContentView: w (#/autorelease (make-instance 'red-view)))
     (#/center w)
     (#/makeKeyAndOrderFront: w nil)
     (#/contentView w))))

Load the file containing these forms, evaluate (show-red-window), and you'll see a red window.

More drawing

The general assumption in Cocoa is that you will do your drawing in your custom view's drawRect: method. It's often nice, though, to be able to draw by evaluating forms at the lisp top-level.

One problem with that, at least in OpenMCL, is that parts of Cocoa are not thread-safe. Fortunately, creating windows on secondary threads is supported. So is drawing to a view, provided that the drawing is bracketed with calls to lockFocusIfCanDraw and unlockFocus.

So, to keep from forgetting to unlockFocus, define this simple macro. (If you forget to unlockFocus, you'll get a spinning beach ball and have to kill the lisp.)

(defmacro with-focused-view (view &body forms)
  `(when (#/lockFocusIfCanDraw ,view)
	  (progn ,@forms)
       (#/unlockFocus ,view)
       (#/flushGraphics (#/currentContext ns:ns-graphics-context))
       (#/flushWindow (#/window ,view)))))

The flushWindow makes sure that your immediate drawing will get displayed.

Now, you can draw. Notice that show-red-window returns a view instance.

(setf *v* (show-red-window))
(with-focused-view *v*
    (let* ((path (#/bezierPath ns:ns-bezier-path)))
      (#/moveToPoint: path (ns:make-ns-point 10 10))
      (#/lineToPoint: path (ns:make-ns-point 100 100))
      (#/stroke path)
      (#/drawAtPoint:withAttributes: #@"hello world"
		  		     (ns:make-ns-point 10 100)

Note that if the view is ever told to redisplay itself, your drawing will be wiped out. You need to arrange for your drawing routines to be called from the view's drawRect: method in order for it to be "permanent."

See also: EasyGuiCurrencyConverter.