robot-ltk.lisp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. ;;;; robot-ltk.lisp
  2. (in-package #:robot-ltk)
  3. (defparameter *originx* 5)
  4. (defparameter *originy* 5)
  5. (defparameter *cell-size* 32)
  6. (defparameter *active-robot-colour* :green)
  7. (defparameter *inactive-robot-colour* :red)
  8. (defparameter *painted-colour* :yellow)
  9. (defparameter *grid-colour* :grey)
  10. (defparameter *dot-radius* 2)
  11. (defparameter *led-size* 32)
  12. (defparameter *yes-led-on-colour* :green)
  13. (defparameter *no-led-on-colour* :red)
  14. (defparameter *window-title* "Робот")
  15. (defclass robot-map (frame)
  16. ((cells :accessor robot-map-cells)
  17. (robot :accessor robot-map-robot)))
  18. (defun draw-cells (canvas app)
  19. (let* ((world (robot-app-world app))
  20. (columns (landscape-width (landscape world)))
  21. (rows (landscape-height (landscape world))))
  22. (let ((cells (make-array (list rows columns))))
  23. (dotimes (i rows)
  24. (dotimes (j columns)
  25. (let ((r (make-rectangle canvas
  26. (+ *originx* (* j *cell-size*))
  27. (+ *originy* (* i *cell-size*))
  28. (+ *originx* (* (1+ j) *cell-size*))
  29. (+ *originy* (* (1+ i) *cell-size*)))))
  30. (configure r :outline :gray)
  31. (setf (aref cells i j) r))))
  32. (update-cells cells app)
  33. cells)))
  34. (defun draw-dotted-cells (canvas app)
  35. (loop for (x y) in (dotted-cells (landscape (robot-app-world app)))
  36. for x0 = (- (+ (* (+ x 1/2) *cell-size*) *originx*) *dot-radius*)
  37. for y0 = (- (+ (* (+ y 1/2) *cell-size*) *originy*) *dot-radius*)
  38. for x1 = (+ x0 (* 2 *dot-radius*))
  39. for y1 = (+ y0 (* 2 *dot-radius*))
  40. for handle = (create-oval canvas x0 y0 x1 y1)
  41. do (itemconfigure canvas handle :fill :black)))
  42. (defun update-cells (cells app)
  43. (loop for (x y) in (painted-cells (robot-app-world app))
  44. do (configure (aref cells y x) :fill *painted-colour*))
  45. cells)
  46. (defun draw-walls (canvas app)
  47. (let* ((world (robot-app-world app))
  48. (hwall (landscape-hwalls (landscape world)))
  49. (vwall (landscape-vwalls (landscape world))))
  50. (loop for (nx ny) in hwall
  51. for y = (+ *originy* (* ny *cell-size*))
  52. for x1 = (+ *originx* (* nx *cell-size*))
  53. for x2 = (+ x1 *cell-size*)
  54. for line = (create-line canvas (list x1 y x2 y))
  55. do (itemconfigure canvas line :fill :red)
  56. (itemconfigure canvas line :width :3))
  57. (loop for (nx ny) in vwall
  58. for x = (+ *originx* (* nx *cell-size*))
  59. for y1 = (+ *originy* (* ny *cell-size*))
  60. for y2 = (+ y1 *cell-size*)
  61. for line = (create-line canvas (list x y1 x y2))
  62. do (itemconfigure canvas line :fill :red)
  63. (itemconfigure canvas line :width :3))))
  64. (defun robot-oval-coords (world-x world-y)
  65. (let* ((r (/ *cell-size* 4))
  66. (x0 (+ (+ *originx* (* (+ world-x 1/2) *cell-size*)) (- r)))
  67. (y0 (+ (+ *originy* (* (+ world-y 1/2) *cell-size*)) (- r))))
  68. (list x0 y0 (+ x0 (* 2 r)) (+ y0 (* 2 r)))))
  69. (defun create-robot (canvas)
  70. (let ((robot-sprite (apply #'make-oval canvas (robot-oval-coords 0 0))))
  71. (configure robot-sprite :state :hidden)
  72. robot-sprite))
  73. (defun update-robot (robot-sprite app)
  74. (let ((robot (robot (robot-app-world app))))
  75. (when robot
  76. (setf (coords robot-sprite) (apply #'robot-oval-coords (robot-position (robot-app-world app))))
  77. (configure robot-sprite
  78. :state :normal
  79. :fill (if (robot-active-p robot)
  80. *active-robot-colour*
  81. *inactive-robot-colour*))))
  82. robot-sprite)
  83. (defun draw-robot (canvas app)
  84. (update-robot (create-robot canvas) app))
  85. (defun create-robot-map (app master)
  86. (let* ((landscape (landscape (robot-app-world app)))
  87. (map (make-instance 'robot-map :master master))
  88. (canvas (make-instance 'canvas
  89. :master map
  90. :height (+ (* (landscape-height landscape) *cell-size*)
  91. (* 2 *originy*))
  92. :width (+ (* (landscape-width landscape) *cell-size*)
  93. (* 2 *originx*)))))
  94. (configure canvas :background :white)
  95. (setf (robot-map-cells map) (draw-cells canvas app))
  96. (draw-walls canvas app)
  97. (draw-dotted-cells canvas app)
  98. (setf (robot-map-robot map) (draw-robot canvas app))
  99. (pack canvas)
  100. map))
  101. (defun robot-app-update-map (app)
  102. (with-atomic
  103. (let ((map (robot-app-component :map app)))
  104. (update-cells (robot-map-cells map) app)
  105. (update-robot (robot-map-robot map) app))))
  106. (defun create-five-buttons (master caption labels-commands)
  107. (loop with main-frame = (make-instance 'frame :master master)
  108. with frame = (make-instance 'frame :master main-frame)
  109. with label = (make-instance 'label :master main-frame :text caption)
  110. for (text command) in labels-commands
  111. for position in '((0 1) (1 0) (1 1) (1 2) (2 1))
  112. for button = (make-instance 'button
  113. :master frame
  114. :text text
  115. :command command
  116. :style "Robot.TButton")
  117. do (apply #'grid button position)
  118. finally (grid label 0 0) (grid frame 1 0) (return main-frame)))
  119. (defclass robot-led (frame)
  120. ((oval :accessor led-oval)
  121. (on-colour :reader on-colour :initarg :on-colour)))
  122. (defun create-led (master caption on-colour)
  123. (let* ((led (make-instance 'robot-led
  124. :master master
  125. :height *led-size*
  126. :width *led-size*
  127. :on-colour on-colour))
  128. (label (make-instance 'label
  129. :master led
  130. :text caption))
  131. (canvas (make-canvas led :width (+ *led-size* 2) :height (+ *led-size* 2)))
  132. (oval (make-oval canvas 1 1 (1+ *led-size*) (1+ *led-size*))))
  133. (grid label 0 0)
  134. (grid canvas 1 0)
  135. (setf (led-oval led) oval)
  136. (set-robot-led-mode :off led)
  137. led))
  138. (defun set-robot-led-mode (mode led)
  139. (with-atomic
  140. (configure (led-oval led) :fill (if (eql mode :on)
  141. (on-colour led)
  142. :white))))
  143. (defclass robot-data (frame)
  144. ((data-label :reader data-label)))
  145. (defun create-robot-data (master caption callback)
  146. (let* ((frame (make-instance 'robot-data :master master))
  147. (button (make-instance 'button :master frame :text caption :command callback :style "Robot.TButton"))
  148. (label (make-instance 'label :master frame)))
  149. (grid button 0 0 :padx 10)
  150. (grid label 0 1)
  151. (setf (slot-value frame 'data-label) label)
  152. frame))
  153. (defun set-robot-data (data component)
  154. (with-atomic
  155. (setf (text (data-label component)) data)))
  156. (defparameter *buttons* '((motion ("↑" :up) ("←" :left) ("К" :paint) ("→" :right) ("↓" :down))
  157. (query ("↑" :wall-up-p) ("←" :wall-left-p) ("К?" :paintedp) ("→" :wall-right-p) ("↓" :wall-down-p))
  158. (alt-query ("↑" :clear-up-p) ("←" :clear-left-p) ("С?" :blankp) ("→" :clear-right-p) ("↓" :clear-down-p))
  159. (temperature ("℃" :temperature))
  160. (radiation ("☢" :radiation))))
  161. (defun all-callbacks (app)
  162. (loop for (category . buttons) in *buttons*
  163. collect (cons category (loop for (text callback-name) in buttons
  164. do (robot-app-callback callback-name app)
  165. collect (list text (robot-app-callback callback-name app))))))
  166. (defun create-robot-app (app)
  167. (send-wish "ttk::style configure Robot.TButton -width 5")
  168. (wm-title *tk* *window-title*)
  169. (let* ((main-frame (make-instance 'frame :master *tk*))
  170. (callbacks (all-callbacks app)))
  171. (grid (setf (robot-app-component :map app) (create-robot-map app main-frame)) 0 0 :columnspan 2)
  172. (grid (create-five-buttons main-frame "Идти" (cdr (assoc 'motion callbacks))) 1 0 :columnspan 2 :pady "10")
  173. (grid (create-five-buttons main-frame "Занято?" (cdr (assoc 'query callbacks))) 2 0 :padx 5)
  174. (grid (create-five-buttons main-frame "Свободно?" (cdr (assoc 'alt-query callbacks))) 2 1 :padx 5)
  175. (grid (setf (robot-app-component :yes-led app) (create-led main-frame "Да" *yes-led-on-colour*)) 3 0 :sticky :e :padx 5)
  176. (grid (setf (robot-app-component :no-led app) (create-led main-frame "Нет" *no-led-on-colour*)) 3 1 :sticky :w :padx 5)
  177. (grid (setf (robot-app-component :temperature app) (apply #'create-robot-data main-frame (cadr (assoc 'temperature callbacks)))) 4 0)
  178. (grid (setf (robot-app-component :radiation app) (apply #'create-robot-data main-frame (cadr (assoc 'radiation callbacks)))) 4 1)
  179. (grid main-frame 0 0 :padx 12 :pady 3)))
  180. ;; TODO Use mailboxes
  181. (defun ltk-background (&optional thunk)
  182. (let ((box (make-mailbox)))
  183. (make-thread (lambda ()
  184. (setf *robot-app-running-p* t)
  185. (unwind-protect
  186. (with-ltk ()
  187. (mailbox-send-message box *wish*)
  188. (when thunk
  189. (funcall thunk))))
  190. (setf *robot-app-running-p* nil))
  191. :name "LTK")
  192. (mailbox-receive-message box)))
  193. (defun show-robot-app (app &key (background t))
  194. (let ((do-stuff (lambda ()
  195. (create-robot-app app))))
  196. (setf (robot-app-wish app) (if background
  197. (ltk-background do-stuff)
  198. (prog1
  199. *wish*
  200. (with-ltk ()
  201. (funcall do-stuff)))))))