+(defvar *dimensions* nil) ; This is a useful placeholder for other view properties
+(defvar *window-width* 1024)
+(defvar *window-height* 768)
+
+(defvar *default-shapes* nil)
+
+(defvar *textbox* nil)
+(defvar *inputbox* nil)
+
+(defvar *current-globals* nil)
+
+(defvar *widgets* nil) ; this is going to be pairs to save the original definition
+
+; This is the thread that does the work of querying the currently running system to update
+; the interface state. We keep it seperate from the main system display thread for two reasons:
+; 1) It should run even if there's no active display
+; 2) We want it to run slower than the current display thread
+(defvar *interface-thread* nil)
+
+(defgeneric update-widget (type def widget extra-info))
+(defmethod update-widget (type def widget extra-info))
+
+(defmethod update-widget ((type (eql :button)) button-def button idx)
+ (when (getf button-def :forever)
+ (clnl-gltk:toggle button (clnl-model:forever-button-on (getf button-def :display) idx))))
+
+(defmethod update-widget ((type (eql :switch)) switch-def switch nothing)
+ (let
+ ((global (find (getf switch-def :var) *current-globals* :key (lambda (def) (getf def :name)))))
+ (clnl-gltk:toggle switch (getf global :value))))
+
+(defun update-interface ()
+ (mapcar
+ (lambda (widget) (apply #'update-widget widget))
+ *widgets*))
+
+(defun boot-interface-thread ()
+ (when (not *interface-thread*)
+ (setf *interface-thread*
+ (sb-thread:make-thread (lambda () (loop (update-interface) (sleep .1))) :name "Interface Thread"))))
+
+; For now, shapes can live in here
+; header is
+; * name <like default>
+; * rotatable (equal to "true" if yes)
+;
+; then after, the elements are like so:
+;
+; filled == filled in (always for now, ha)
+; marked == use the turtle color instead of a color
+; polygon -> Polygon <color> <filled> <marked> <alternating x y coords>
+; circle -> Circle <color> <filled> <marked> <left> <top> <diameter> ; here, the left and top are NOT the center
+; rectangle -> Rectangle <color> <filled> <marked> <left> <top> <right> <bottom>
+;
+; then ends with an empty string
+
+(defun parse-circle (sections)
+ (list :circle
+ :color (parse-integer (car sections))
+ :filled (string= (nth 1 sections) "true")
+ :marked (string= (nth 2 sections) "true")
+ :left (parse-integer (nth 3 sections))
+ :top (parse-integer (nth 4 sections))
+ :diameter (parse-integer (nth 5 sections))))
+
+(defun parse-rectangle (sections)
+ (list
+ :rectangle
+ :color (parse-integer (car sections))
+ :filled (string= (nth 1 sections) "true")
+ :marked (string= (nth 2 sections) "true")
+ :left (parse-integer (nth 3 sections))
+ :top (parse-integer (nth 4 sections))
+ :right (parse-integer (nth 5 sections))
+ :bottom (parse-integer (nth 6 sections))))
+
+(defun parse-polygon (sections)
+ (labels
+ ((parse-points (sections)
+ (when sections
+ (cons
+ (list (- 300 (parse-integer (car sections))) (parse-integer (cadr sections)))
+ (parse-points (cddr sections))))))
+ (list
+ :polygon
+ :color (parse-integer (car sections))
+ :filled (string= (nth 1 sections) "true")
+ :marked (string= (nth 2 sections) "true")
+ :coords (parse-points (nthcdr 3 sections)))))
+
+(defun parse-shape (str)
+ (labels
+ ((parse-element (line)
+ (let
+ ((sections (cl-ppcre:split " " line)))
+ (cond
+ ((string= (car sections) "Circle") (parse-circle (cdr sections)))
+ ((string= (car sections) "Rectangle") (parse-rectangle (cdr sections)))
+ ((string= (car sections) "Polygon") (parse-polygon (cdr sections))))))
+ (parse-elements ()
+ (let
+ ((line (read-line str nil)))
+ (when (and line (string/= line ""))
+ (cons
+ (parse-element line)
+ (parse-elements))))))
+ (let
+ ((next-line (read-line str nil)))
+ (when next-line
+ (list
+ :name next-line
+ :rotatable (string= "true" (read-line str))
+ :rgb (read-line str) ; this is ignored for now, I think
+ :elements (parse-elements))))))
+
+; Clipping ears algorithm. This can be slow due to the fact that it will only be run once.
+(defun triangulate (points &optional (ccw :unknown))
+ (labels
+ ((tri-is-ccw (x y z)
+ (< 0 (- (* (- (car y) (car x)) (- (cadr z) (cadr x))) (* (- (car z) (car x)) (- (cadr y) (cadr x))))))
+ (tri-is-concave (x y z) (if (tri-is-ccw x y z) (not ccw) ccw))
+ (poly-is-ccw (points &optional cur-tri)
+ (cond
+ ((not cur-tri)
+ (poly-is-ccw (append points (list (car points))) (list (car (last points)) (car points) (cadr points))))
+ ((eql (length points) 2)
+ (apply #'tri-is-ccw cur-tri))
+ ((or
+ (< (car (cadr points)) (car (cadr cur-tri)))
+ (and
+ (= (car (cadr points)) (car (cadr cur-tri)))
+ (< (cadr (cadr points)) (cadr (cadr cur-tri)))))
+ (poly-is-ccw (cdr points) (subseq points 0 3)))
+ (t (poly-is-ccw (cdr points) cur-tri))))
+ (point-in-tri (x y z p)
+ ; Barycentric system test
+ (let*
+ ((denom (+ (* (- (cadr y) (cadr z)) (- (car x) (car z))) (* (- (car z) (car y)) (- (cadr x) (cadr z)))))
+ (a (/ (+ (* (- (cadr y) (cadr z)) (- (car p) (car z))) (* (- (car z) (car y)) (- (cadr p) (cadr z)))) denom))
+ (b (/ (+ (* (- (cadr z) (cadr x)) (- (car p) (car z))) (* (- (car x) (car z)) (- (cadr p) (cadr z)))) denom))
+ (c (- 1 a b)))
+ (and (<= 0 a 1) (<= 0 b 1) (<= 0 c 1))))
+ (no-points-in-tri (tri points)
+ (every (lambda (point) (not (point-in-tri (car tri) (cadr tri) (caddr tri) point))) points))
+ (tri-is-actually-line (x y z)
+ (zerop (+ (* (- (cadr y) (cadr z)) (- (car x) (car z))) (* (- (car z) (car y)) (- (cadr x) (cadr z)))))))
+ (cond
+ ((not (find :end points)) (triangulate (append points (list :end)) ccw))
+ ((< (length points) 4) (error "Must have at least 3 points..."))
+ ((= (length points) 4) (list (remove :end points)))
+ ((eql ccw :unknown) (triangulate points (poly-is-ccw (remove :end points))))
+ ((eql :end (car points)) (error "This polygon may not be triangulateable"))
+ (t
+ (let*
+ ((endless (remove :end points))
+ (tri (subseq endless 0 3)))
+ (cond
+ ((apply #'tri-is-actually-line tri)
+ (triangulate (cons (car endless) (cddr endless)) ccw))
+ ((apply #'tri-is-concave tri)
+ (triangulate (append (cdr points) (list (car points))) ccw))
+ ((no-points-in-tri tri (nthcdr 3 endless))
+ (cons tri (triangulate (cons (car endless) (cddr endless)) ccw)))
+ (t (triangulate (append (cdr points) (list (car points))) ccw))))))))
+
+(defun element->gl-list (shape)
+ (progn
+ (when (not (getf (cdr shape) :marked))
+ (gl:push-attrib :all-attrib-bits)
+ (gl:color
+ (/ (ash (ldb (byte 24 0) (getf (cdr shape) :color)) -16) 255)
+ (/ (ash (ldb (byte 16 0) (getf (cdr shape) :color)) -8) 255)
+ (/ (ldb (byte 8 0) (getf (cdr shape) :color)) 255)))
+ (gl:begin :triangles)
+ (case (car shape)
+ (:polygon
+ (mapcar
+ (lambda (point) (gl:vertex (car point) (cadr point) 0))
+ (apply #'append (triangulate (getf (cdr shape) :coords)))))
+ (:rectangle
+ (mapcar
+ (lambda (point) (gl:vertex (car point) (cadr point) 0))
+ (apply #'append
+ (triangulate
+ (list
+ (list (- 300 (getf (cdr shape) :left)) (getf (cdr shape) :top))
+ (list (- 300 (getf (cdr shape) :right)) (getf (cdr shape) :top))
+ (list (- 300 (getf (cdr shape) :right)) (getf (cdr shape) :bottom))
+ (list (- 300 (getf (cdr shape) :left)) (getf (cdr shape) :bottom)))))))
+ (:circle
+ (mapcar
+ (lambda (point) (gl:vertex (car point) (cadr point) 0))
+ (apply #'append
+ (triangulate
+ (loop
+ :repeat 360
+ :with c := (strictmath:cos (strictmath:to-radians 1))
+ :with s := (strictmath:sin (strictmath:to-radians 1))
+ :with r := (/ (getf (cdr shape) :diameter) 2)
+ :with left := (getf (cdr shape) :left)
+ :with top := (getf (cdr shape) :top)
+ :for n := 0 :then x
+ :for x := r :then (- (* c x) (* s y))
+ :for y := 0 :then (+ (* s n) (* c y))
+ :collect (list (- 300 (+ (+ x left) r)) (+ (+ y top) r))))))))
+ (gl:end)
+ (when (not (getf (cdr shape) :marked))
+ (gl:pop-attrib))))
+
+(defun parse-shapes (str)
+ (let
+ ((shape (parse-shape str)))
+ (when shape (cons shape (parse-shapes str)))))
+
+(defun default-shapes ()
+ (with-open-file (str "resources/defaultshapes") (parse-shapes str)))
+
+(eval-when (:load-toplevel)
+ (when (probe-file "resources/defaultshapes")
+ (setf *default-shapes* (default-shapes))))