4 (defclass container
(widget)
5 ((contents :type
(simple-array (or null widget
) 2) :reader contents-of
6 :documentation
"A matrix of contained widgets")
7 (row-heights :type list
:accessor row-heights
:initarg
:row-heights
:accessor row-heights-of
8 :documentation
"Heights of the rows, list of either a stretch-weight (:weight . number),
9 or a fixed value (:fixed . number).")
10 (column-widths :type list
:accessor column-widths
:initarg
:column-widths
:accessor column-widths-of
11 :documentation
"Widths of the columns, list of either a stretch-weight (:weight . number),
12 or a fixed value (:fixed . number).")
13 (computed-row-heights :type list
:accessor computed-row-heights-of
)
14 (computed-column-widths :type list
:accessor computed-column-widths-of
))
15 (:documentation
"A grid-layout container for other widgets."))
17 (defmethod (setf contents-of
) (ncontents (container container
))
18 (with-slots (contents) container
21 (unless (listp (first ncontents
)) (error "contents must be a list of lists"))
22 ;; (format t "Making a ~ax~a grid~%" (length contents) (length (first contents)))
24 (make-array (list (length ncontents
) (length (first ncontents
)))
25 :element-type
'(or null widget
)
26 :initial-contents ncontents
)))
28 (setf contents ncontents
))))
29 (with-accessors ((contents contents-of
)) container
30 (loop for row below
(array-dimension contents
0) do
31 (loop for column below
(array-dimension contents
1) do
32 (let ((widget (aref contents row column
)))
34 (setf (parent-of widget
) container
)))))
35 (unless (slot-boundp container
'row-heights
)
36 (setf (slot-value container
'row-heights
)
37 (make-list (array-dimension contents
0)
38 :initial-element
(cons :expand
(if (default-font)
39 (ftgl:get-font-line-height
(default-font))
41 ;; (format t "row heights unintialized, set to ~a~%" (slot-value container 'row-heights))
43 (unless (slot-boundp container
'column-widths
)
44 (setf (slot-value container
'column-widths
)
45 (make-list (array-dimension contents
1)
46 :initial-element
(cons :expand
(if (default-font)
47 (ftgl:get-font-line-height
(default-font))
49 ;; (format t "column widths unintialized, set to ~a~%" (slot-value container 'column-widths))
53 (defmethod initialize-instance :after
((container container
) &key rows columns contents
)
56 (setf (contents-of container
) contents
))
57 ((and (realp columns
) (realp rows
))
58 (setf (contents-of container
)
59 (make-array (list rows columns
)
60 :element-type
'(or null widget
)
61 :initial-element nil
)))))
63 (defmethod set-cell ((container container
) row column widget
)
64 (when (aref (contents-of container
) row column
)
65 (detach (aref (contents-of container
) row column
)))
66 (setf (aref (contents-of container
) row column
)
69 (setf (parent-of widget
) container
)))
71 (defun row-widget-heights-of (2d-array)
72 (loop for row below
(array-dimension 2d-array
0) collecting
73 (loop for column below
(array-dimension 2d-array
1) collecting
74 (let ((widget (aref 2d-array row column
)))
76 (total-ideal-height-of widget
)
79 (defun column-widget-widths-of (2d-array)
80 (loop for column below
(array-dimension 2d-array
1) collecting
81 (loop for row below
(array-dimension 2d-array
0) collecting
82 (let ((widget (aref 2d-array row column
)))
84 (total-ideal-width-of widget
)
87 (defmethod contents-list ((container container
))
88 "Just collect up all of the contents into one list, ignoring empty cells."
89 (loop for column below
(array-dimension (contents-of container
) 1) nconcing
90 (loop for row below
(array-dimension (contents-of container
) 0)
91 when
(aref (contents-of container
) row column
)
93 (aref (contents-of container
) row column
))))
95 (defun compute-ideal-sizes (sizes set-ideal-sizes
)
96 (loop for size in sizes
97 for ideal-sizes in set-ideal-sizes
100 ((:expand
:weight
) (reduce #'max
(cons (cdr size
) ideal-sizes
)))
101 (:fixed
(cdr size
)))))
104 (defmethod ideal-width-of ((container container
))
105 (if (slot-boundp (x container
) 'ideal-size
)
106 (slot-value (x container
) 'ideal-size
)
108 (compute-ideal-sizes (column-widths-of container
)
109 (column-widget-widths-of (contents-of container
))))))
111 (defmethod ideal-height-of ((container container
))
112 (if (slot-boundp (y container
) 'ideal-size
)
113 (slot-value (y container
) 'ideal-size
)
115 (compute-ideal-sizes (row-heights-of container
)
116 (row-widget-heights-of (contents-of container
))))))
119 (defun compute-set-sizes (available-size sizes set-ideal-sizes
)
120 (let* ((available-weighted-size available-size
)
123 (loop for size in sizes
124 for ideal-sizes in set-ideal-sizes
128 (incf total-weight
(cdr size
))
131 (let ((size (min available-weighted-size
133 (:expand
(reduce #'max
(cons (cdr size
) ideal-sizes
)))
134 (:fixed
(cdr size
))))))
135 (decf available-weighted-size size
)
140 (* (cdr size
) (/ available-weighted-size total-weight
))
144 (defmethod detach ((container container
))
145 "Callback for when an widget is detached from the current view (ie. it goes off the screen)."
146 (mapcar #'detach
(contents-list container
)))
148 (defmethod layout ((container container
))
149 (setf (computed-row-heights-of container
)
150 (compute-set-sizes (current-size-of (y container
))
151 (row-heights-of container
)
152 (row-widget-heights-of (contents-of container
)))
153 (computed-column-widths-of container
)
154 (compute-set-sizes (current-size-of (x container
))
155 (column-widths-of container
)
156 (column-widget-widths-of (contents-of container
))))
157 ;; (format t "computed ~a~% widths~a~% heights~a~%"
159 ;; (computed-column-widths-of container)
160 ;; (computed-row-heights-of container))
161 (let ((ix 0.0) (iy 0.0))
162 (loop for row below
(array-dimension (contents-of container
) 0)
163 for height in
(computed-row-heights-of container
) do
164 (loop for column below
(array-dimension (contents-of container
) 1)
165 for width in
(computed-column-widths-of container
) do
166 (let ((widget (aref (contents-of container
) row column
)))
168 (setf (total-current-size-of (x widget
)) (min width
(total-ideal-width-of widget
))
169 (total-current-size-of (y widget
)) (min height
(total-ideal-height-of widget
))
170 (current-offset-of (x widget
)) ix
171 (current-offset-of (y widget
)) iy
)
177 (defmethod draw ((container container
))
179 (with-slots (x y
) container
180 (mapcar #'(lambda (widget)
182 (gl:translate-f
(current-offset-of (x widget
))
183 (current-offset-of (y widget
))
186 (contents-list container
))))
188 (defmethod update-text ((container container
))
189 (mapc #'update-text
(contents-list container
)))
192 (defmacro define-propogating-container-method
(name (&rest lambda-list
) &body forms
)
193 "Defines a method named name, with lambda-list prepended by (container container),
194 that propogates to child widgets, x and y must appear in the lambda list.
195 For the course of forms, widget is also bound to the widget located."
196 `(defmethod ,name
,(list* '(container container
) lambda-list
)
197 (declare (optimize (debug 3)))
198 (when (and (slot-boundp container
'computed-row-heights
)
199 (slot-boundp container
'computed-column-widths
))
201 (loop for row-height in
(computed-row-heights-of container
)
204 (loop for column-width in
(computed-column-widths-of container
)
206 (when (and (>= x x-column
)
207 (<= x
(+ column-width x-column
))
209 (<= y
(+ row-height y-row
)))
210 (let ((widget (aref (contents-of container
) iy ix
)))
213 (incf x-column column-width
)))
214 (incf y-row row-height
))))))
216 (define-propogating-container-method handle-mouse-button
(button press x y
)
217 (handle-mouse-button widget
219 (- x
(current-offset-of (x widget
)))
220 (- y
(current-offset-of (y widget
)))))
222 (define-propogating-container-method handle-mouse-wheel
(zrel x y
)
223 (handle-mouse-wheel widget
225 (- x
(current-offset-of (x widget
)))
226 (- y
(current-offset-of (y widget
)))))
231 (defmacro contents-iterate-forwards
(contents widget-name
&body forms
)
232 (let ((x (gensym "X"))
234 (c (gensym "CONTENTS")))
235 `(let ((,c
,contents
))
236 (loop for
,y below
(array-dimension ,c
0) do
237 (loop for
,x below
(array-dimension ,c
1) do
238 (let ((,widget-name
(aref ,c
,y
,x
)))
241 (defmacro contents-iterate-backwards
(contents widget-name
&body forms
)
242 (let ((x (gensym "X"))
244 (c (gensym "CONTENTS")))
245 `(let ((,c
,contents
))
246 (loop for
,y from
(1- (array-dimension ,c
0)) downto
0 do
247 (loop for
,x from
(1- (array-dimension ,c
1)) downto
0 do
248 (let ((,widget-name
(aref ,c
,y
,x
)))
252 (defun find-first-focusable (widget)
253 "Find the first focusable widget that is widget, or is in widget.
255 (declare (type widget widget
))
257 ((accept-focus-p widget
)
259 ((typep widget
'container
)
260 (contents-iterate-forwards (contents-of widget
) w
261 (when (and w
(setf w
(find-first-focusable w
)))
262 (return-from find-first-focusable w
))))))
264 (defun find-last-focusable (widget)
265 "Find the last focusable widget that is widget, or is in widget.
267 (declare (type widget widget
))
269 ((accept-focus-p widget
)
271 ((typep widget
'container
)
272 (contents-iterate-backwards (contents-of widget
) w
273 (when (and w
(setf w
(find-last-focusable w
)))
274 (return-from find-last-focusable w
))))))
277 (defun next-widget (widget)
278 (declare (type widget widget
))
279 (let ((parent (when (slot-boundp widget
'parent
)
280 (parent-of widget
))))
282 (when (typep parent
'container
)
284 (contents-iterate-forwards (contents-of parent
) w
286 (when (and w
(setf w
(find-first-focusable w
)))
287 (return-from next-widget w
))
288 ;;Found our own widget in the container, from now on, search for something focusable
291 ;;Nothing was found - go up a level
292 (next-widget parent
))))
294 (defun prev-widget (widget)
295 (declare (type widget widget
))
296 (let ((parent (when (slot-boundp widget
'parent
)
297 (parent-of widget
))))
299 (when (typep parent
'container
)
301 (contents-iterate-backwards (contents-of parent
) w
303 (when (and w
(setf w
(find-last-focusable w
)))
304 (return-from prev-widget w
))
305 ;;Found our own widget in the container, from now on, search for something focusable
308 ;;Nothing was found - go up a level
309 (prev-widget parent
))))