|Version 16 (modified by rme, 9 years ago) (diff)|
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.
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 () (ccl::with-autorelease-pool (let* ((rect (ns:make-ns-rect 0 0 300 300)) (w (make-instance 'ns:ns-window :with-content-rect rect :style-mask (logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask) :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.
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) (unwind-protect (progn ,@forms) (#/unlockFocus ,view) (#/flushGraphics (#/currentContext ns:ns-graphics-context)) (#/flushWindow (#/window ,view)))))
The flushGraphics/flushWindow business makes sure that your immediate drawing will get displayed.
First, create a window. (Notice that show-red-window returns a view instance.)
(setf *v* (show-red-window))
Now, with a view in hand, you can draw.
(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) +null-ptr+)))
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.