Version 3 (modified by rme, 7 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 (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)
     (#/unlockFocus ,view)))

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

(setf *v* (show-red-view))
(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)))