;; Mixin classes for zorphs ;; There's probably loads of things to be factored into here .. ;; A node that likes to know about what can see it; i.e., ;; something that cameras look at (define-class () (viewers)) (define-method (initialize (node ) args) (initialize-slots node '(viewers ())) (call-next-method)) ;; Don't know if these should be generic .. (define (viewer-add! node viewer) (slot-set-add! node 'viewers viewer)) (define (viewer-remove! node viewer) (slot-set-remove! node 'viewers)) (define (viewers node) (slot-ref node 'viewers)) ;; Hit testing ;; Cairo has some handy functions for testing insideness and giving extents. ;; We need to be able to draw the zorph first, since it works on the current ;; context. We could either: render to a copy of the context (rather than directly to the surface) ;; and hit test on that; or keep the information from the last time we rendered around and use that. ;; In the first case we have to reproduce the context in the same way as we would when we are rendering. ;; These approaches are much the same, with the difference being simply when we make the copy ;; and when we release it. It may be down to what Cairo supports (like taking a snapshot of the ;; context). ;; Bounds, clipping path ;; These are all tangled up. This is what I take them to mean: ;; *Bounds*: the rectangle that minimally covers the visible parts of something. This is ;; usually calculated. ;; *Clipping path*: The rectangle that something will clip its drawing to when rendering. ;; ;; The clipping path will probably be found implicitly when rendering. ; Something with a (2D) spatial extent. This might be a slightly pointless base ; if there aren't some default implementations to be had. (define-class () ()) (define-method (point2d-inside? (thing ) point) #f) ; %%% is here the right place to do a default implementation? ;; Something (rectalinearly) bounded -- a refinement of spatial-extent that caches and uses ;; rectangular bounds as an approximation for hit-testing, resorting to [code point2d-inside-outline?] ;; only when it needs to. (define-class () (cached-bounds)) (define-method (initialize (thing ) initargs) (initialize-slots thing (list 'cached-bounds #f)) ; rather it went wrong than give an incorrect result (call-next-method)) (define-method (invalidate-bounds (thing )) (slot-set! thing 'cached-bounds #f)) (define-method (bounds (zorph )) (let ((b (slot-ref zorph 'cached-bounds))) (or b (let ((cb (calculate-bounds zorph))) (slot-set! zorph 'cached-bounds cb) cb)))) (define-method (calculate-bounds (thing )) (bounds->rect 0 0 0 0)) ;; Assuming this is only called if the bounds test has succeeded, a reasonable ;; default is to just say 'yes'. Perhaps a better default, which doesn't make ;; that assumption, is to use point2d-in-rect (again). Then again, in cases where ;; it isn't overidden it doesn't make sense to repeat the test. I pronounce it part ;; of the contract of this method. (define-method (point2d-in-outline? (thing ) point) #t) ;; Here's our opportunity to shine (define-method (point2d-inside? (bounded ) point) (let ((b (bounds bounded))) (and (point2d-in-rect? point b) (point2d-in-outline? bounded point))))