;;; gadgets.lisp (in-package :climi) (defmethod handle-repaint ((sb scroll-bar-pane) region) (declare (ignore region)) (with-special-choices (sb) (let ((tr (scroll-bar-transformation sb))) (with-bounding-rectangle* (minx miny maxx maxy) (transform-region tr (sheet-region sb)) (with-drawing-options (sb :transformation tr) (draw-rectangle* sb minx miny maxx maxy :filled t :ink *3d-inner-color*) ;; draw up arrow (with-bounding-rectangle* (x1 y1 x2 y2) (scroll-bar-up-region sb) (let ((pg (list (make-point (/ (+ x1 x2) 2) y1) (make-point x1 y2) (make-point x2 y2)))) (case (slot-value sb 'event-state) (:up-armed (draw-polygon sb pg :ink *3d-inner-color*) (draw-bordered-polygon sb pg :style :inset :border-width 2)) (otherwise (draw-polygon sb pg :ink *3d-normal-color*) (draw-bordered-polygon sb pg :style :outset :border-width 2) )))) ;; draw down arrow (with-bounding-rectangle* (x1 y1 x2 y2) (scroll-bar-down-region sb) (let ((pg (list (make-point (/ (+ x1 x2) 2) y2) (make-point x1 y1) (make-point x2 y1)))) (case (slot-value sb 'event-state) (:dn-armed (draw-polygon sb pg :ink *3d-inner-color*) (draw-bordered-polygon sb pg :style :inset :border-width 2)) (otherwise (draw-polygon sb pg :ink *3d-normal-color*) (draw-bordered-polygon sb pg :style :outset :border-width 2))))) ;; draw thumb (with-bounding-rectangle* (x1 y1 x2 y2) (scroll-bar-thumb-region sb) (draw-rectangle* sb x1 y1 x2 y2 :ink *3d-normal-color*) (draw-bordered-polygon sb (polygon-points (make-rectangle* x1 y1 x2 y2)) :style :outset :border-width 2) (let ((y (/ (+ y1 y2) 2))) (draw-bordered-polygon sb (polygon-points (make-rectangle* (+ x1 3) (- y 1) (- x2 3) (+ y 1))) :style :inset :border-width 1) (draw-bordered-polygon sb (polygon-points (make-rectangle* (+ x1 3) (- y 4) (- x2 3) (- y 2))) :style :inset :border-width 1) (draw-bordered-polygon sb (polygon-points (make-rectangle* (+ x1 3) (+ y 4) (- x2 3) (+ y 2))) :style :inset :border-width 1))) ))))) ;;;; (labels ((line-hnf (x1 y1 x2 y2) (values (- y2 y1) (- x1 x2) (- (* x1 y2) (* y1 x2)))) (line-line-intersection (x1 y1 x2 y2 x3 y3 x4 y4) (multiple-value-bind (a1 b1 c1) (line-hnf x1 y1 x2 y2) (multiple-value-bind (a2 b2 c2) (line-hnf x3 y3 x4 y4) (let ((d (- (* a1 b2) (* b1 a2)))) (cond ((< (abs d) 1e-6) nil) (t (values (/ (- (* b2 c1) (* b1 c2)) d) (/ (- (* a1 c2) (* a2 c1)) d)))))))) (polygon-orientation (point-seq) "Determines the polygon's orientation. Returns: +1 = counter-clock-wise -1 = clock-wise The polygon should be clean from duplicate points or co-linear points. If the polygon self intersects, the orientation may not be defined, this function does not try to detect this situation and happily returns some value." ;; (let ((n (length point-seq))) (let* ((min-i 0) (min-val (point-x (elt point-seq min-i)))) ;; (loop for i from 1 below n do (when (< (point-x (elt point-seq i)) min-val) (setf min-val (point-x (elt point-seq i)) min-i i))) ;; (let ((p0 (elt point-seq (mod (+ min-i -1) n))) (p1 (elt point-seq (mod (+ min-i 0) n))) (p2 (elt point-seq (mod (+ min-i +1) n)))) (signum (- (* (- (point-x p2) (point-x p0)) (- (point-y p1) (point-y p0))) (* (- (point-x p1) (point-x p0)) (- (point-y p2) (point-y p0))))))))) (clean-polygon (point-seq) "Cleans a polygon from duplicate points and co-linear points. Furthermore tries to bring it into counter-clock-wise orientation." ;; first step: remove duplicates (setf point-seq (let ((n (length point-seq))) (loop for i from 0 below n for p0 = (elt point-seq (mod (+ i -1) n)) for p1 = (elt point-seq (mod (+ i 0) n)) unless (and (< (abs (- (point-x p0) (point-x p1))) 10e-8) (< (abs (- (point-y p0) (point-y p1))) 10e-8)) collect p1))) ;; second step: remove colinear points (setf point-seq (let ((n (length point-seq))) (loop for i from 0 below n for p0 = (elt point-seq (mod (+ i -1) n)) for p1 = (elt point-seq (mod (+ i 0) n)) for p2 = (elt point-seq (mod (+ i +1) n)) unless (< (abs (- (* (- (point-x p1) (point-x p0)) (- (point-y p2) (point-y p0))) (* (- (point-x p2) (point-x p0)) (- (point-y p1) (point-y p0))))) 10e-8) collect p1))) ;; third step: care for the orientation (if (and (not (null point-seq)) (minusp (polygon-orientation point-seq))) (reverse point-seq) point-seq) )) (defun shrink-polygon (point-seq width) (let ((point-seq (clean-polygon point-seq))) (let ((n (length point-seq))) (values point-seq (loop for i from 0 below n for p0 = (elt point-seq (mod (+ i -1) n)) for p1 = (elt point-seq (mod (+ i 0) n)) for p2 = (elt point-seq (mod (+ i +1) n)) collect (let* ((dx1 (- (point-x p1) (point-x p0))) (dy1 (- (point-y p1) (point-y p0))) (dx2 (- (point-x p2) (point-x p1))) (dy2 (- (point-y p2) (point-y p1))) ;; (m1 (/ width (sqrt (+ (* dx1 dx1) (* dy1 dy1))))) (m2 (/ width (sqrt (+ (* dx2 dx2) (* dy2 dy2))))) ;; (q0 (make-point (+ (point-x p0) (* m1 dy1)) (- (point-y p0) (* m1 dx1)))) (q1 (make-point (+ (point-x p1) (* m1 dy1)) (- (point-y p1) (* m1 dx1)))) (q2 (make-point (+ (point-x p1) (* m2 dy2)) (- (point-y p1) (* m2 dx2)))) (q3 (make-point (+ (point-x p2) (* m2 dy2)) (- (point-y p2) (* m2 dx2)))) ) ;; (multiple-value-bind (x y) (multiple-value-call #'line-line-intersection (point-position q0) (point-position q1) (point-position q2) (point-position q3)) (if x (make-point x y) (make-point 0 0))))))))) (defun draw-bordered-polygon (medium point-seq &key (border-width 2) (style :inset)) (labels ((draw-pieces (outer-points inner-points dark light) (let ((n (length outer-points))) (dotimes (i n) (let* ((p1 (elt outer-points (mod (+ i 0) n))) (p2 (elt outer-points (mod (+ i +1) n))) (q1 (elt inner-points (mod (+ i 0) n))) (q2 (elt inner-points (mod (+ i +1) n))) (p1* (transform-region +identity-transformation+ p1)) (p2* (transform-region +identity-transformation+ p2)) (a (mod (atan (- (point-y p2*) (point-y p1*)) (- (point-x p2*) (point-x p1*))) (* 2 pi)))) (draw-polygon medium (list p1 q1 q2 p2) :ink (if (<= (* 1/4 pi) a (* 5/4 pi)) dark light))))))) (let ((light *3d-light-color*) (dark *3d-dark-color*)) ;; (ecase style (:solid (multiple-value-call #'draw-pieces (shrink-polygon point-seq border-width) +black+ +black+)) #+NIL (:inset (multiple-value-call #'draw-pieces (shrink-polygon point-seq border-width) dark light)) #+NIL (:outset (multiple-value-call #'draw-pieces (shrink-polygon point-seq border-width) light dark)) ;; ;; Mickey Mouse is the trademark of the Walt Disney Company. ;; (:outset (multiple-value-bind (outer-points inner-points) (shrink-polygon point-seq border-width) (declare (ignore outer-points)) (multiple-value-bind (outer-points middle-points) (shrink-polygon point-seq (/ border-width 2)) (draw-pieces outer-points middle-points +white+ +black+) (draw-pieces middle-points inner-points light dark)))) (:inset (multiple-value-bind (outer-points inner-points) (shrink-polygon point-seq border-width) (declare (ignore outer-points)) (multiple-value-bind (outer-points middle-points) (shrink-polygon point-seq (/ border-width 2)) (draw-pieces outer-points middle-points dark light) (draw-pieces middle-points inner-points +black+ +white+)))) ;; (:ridge (multiple-value-bind (outer-points inner-points) (shrink-polygon point-seq border-width) (declare (ignore outer-points)) (multiple-value-bind (outer-points middle-points) (shrink-polygon point-seq (/ border-width 2)) (draw-pieces outer-points middle-points light dark) (draw-pieces middle-points inner-points dark light)))) (:groove (multiple-value-bind (outer-points inner-points) (shrink-polygon point-seq border-width) (declare (ignore outer-points)) (multiple-value-bind (outer-points middle-points) (shrink-polygon point-seq (/ border-width 2)) (draw-pieces outer-points middle-points dark light) (draw-pieces middle-points inner-points light dark)))) (:double (multiple-value-bind (outer-points inner-points) (shrink-polygon point-seq border-width) (declare (ignore outer-points)) (multiple-value-bind (outer-points imiddle-points) (shrink-polygon point-seq (* 2/3 border-width)) (declare (ignore outer-points)) (multiple-value-bind (outer-points omiddle-points) (shrink-polygon point-seq (* 1/3 border-width)) (draw-pieces outer-points omiddle-points +black+ +black+) (draw-pieces imiddle-points inner-points +black+ +black+))))))))) )