2 CLASS(ListBox) EXTENDS(Item)
3 METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector))
4 METHOD(ListBox, configureListBox, void(entity, float, float))
5 METHOD(ListBox, draw, void(entity))
6 METHOD(ListBox, keyDown, float(entity, float, float, float))
7 METHOD(ListBox, mouseMove, float(entity, vector))
8 METHOD(ListBox, mousePress, float(entity, vector))
9 METHOD(ListBox, mouseDrag, float(entity, vector))
10 METHOD(ListBox, mouseRelease, float(entity, vector))
11 METHOD(ListBox, focusLeave, void(entity))
12 ATTRIB(ListBox, focusable, float, 1)
13 ATTRIB(ListBox, focusedItem, int, -1)
14 ATTRIB(ListBox, focusedItemTime, float, 0)
15 ATTRIB(ListBox, allowFocusSound, float, 1)
16 ATTRIB(ListBox, selectedItem, int, 0)
17 ATTRIB(ListBox, size, vector, '0 0 0')
18 ATTRIB(ListBox, origin, vector, '0 0 0')
19 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
20 ATTRIB(ListBox, previousValue, float, 0)
21 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
22 ATTRIB(ListBox, pressOffset, float, 0)
24 METHOD(ListBox, updateControlTopBottom, void(entity))
25 ATTRIB(ListBox, controlTop, float, 0)
26 ATTRIB(ListBox, controlBottom, float, 0)
27 ATTRIB(ListBox, controlWidth, float, 0)
28 ATTRIB(ListBox, dragScrollTimer, float, 0)
29 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
31 ATTRIB(ListBox, src, string, string_null) // scrollbar
32 ATTRIB(ListBox, color, vector, '1 1 1')
33 ATTRIB(ListBox, color2, vector, '1 1 1')
34 ATTRIB(ListBox, colorC, vector, '1 1 1')
35 ATTRIB(ListBox, colorF, vector, '1 1 1')
36 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
37 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
38 ATTRIB(ListBox, nItems, float, 42)
39 ATTRIB(ListBox, itemHeight, float, 0)
40 ATTRIB(ListBox, colorBG, vector, '0 0 0')
41 ATTRIB(ListBox, alphaBG, float, 0)
43 ATTRIB(ListBox, lastClickedItem, float, -1)
44 ATTRIB(ListBox, lastClickedTime, float, 0)
46 METHOD(ListBox, drawListBoxItem, void(entity, int, vector, bool, float)) // item number, width/height, isSelected, highlightedTime
47 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
48 METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
49 METHOD(ListBox, setSelected, void(entity, float))
51 METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
52 METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
54 // NOTE: override these four methods if you want variable sized list items
55 METHOD(ListBox, getTotalHeight, float(entity))
56 METHOD(ListBox, getItemAtPos, float(entity, float))
57 METHOD(ListBox, getItemStart, float(entity, float))
58 METHOD(ListBox, getItemHeight, float(entity, float))
59 // NOTE: if getItemAt* are overridden, it may make sense to cache the
60 // start and height of the last item returned by getItemAtPos and fast
61 // track returning their properties for getItemStart and getItemHeight.
62 // The "hot" code path calls getItemAtPos first, then will query
63 // getItemStart and getItemHeight on it soon.
64 // When overriding, the following consistency rules must hold:
65 // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
66 // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
67 // for 0 <= i < me.nItems-1
68 // getItemStart(0) == 0
69 // getItemStart(getItemAtPos(p)) <= p
71 // getItemAtPos(p) == 0
73 // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
74 // if p < getTotalHeigt()
75 // getItemAtPos(p) == me.nItems - 1
76 // if p >= getTotalHeight()
81 void ListBox_setSelected(entity me, float i)
83 me.selectedItem = bound(0, i, me.nItems - 1);
85 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
87 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
88 me.controlWidth = me.scrollbarWidth / absSize.x;
90 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
92 me.scrollbarWidth = theScrollbarWidth;
93 me.itemHeight = theItemHeight;
96 float ListBox_getTotalHeight(entity me)
98 return me.nItems * me.itemHeight;
100 float ListBox_getItemAtPos(entity me, float pos)
102 return floor(pos / me.itemHeight);
104 float ListBox_getItemStart(entity me, float i)
106 return me.itemHeight * i;
108 float ListBox_getItemHeight(entity me, float i)
110 return me.itemHeight;
113 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
115 return me.getItemAtPos(me, pos + 1.001) - 1;
117 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
119 return me.getItemAtPos(me, pos - 0.001) + 1;
121 float ListBox_keyDown(entity me, float key, float ascii, float shift)
123 me.dragScrollTimer = time;
124 if(key == K_MWHEELUP)
126 me.scrollPos = max(me.scrollPos - 0.5, 0);
127 me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
129 else if(key == K_MWHEELDOWN)
131 me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
132 me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
134 else if(key == K_PGUP || key == K_KP_PGUP)
136 float i = me.selectedItem;
137 float a = me.getItemHeight(me, i);
143 a += me.getItemHeight(me, i);
147 me.setSelected(me, i + 1);
149 else if(key == K_PGDN || key == K_KP_PGDN)
151 float i = me.selectedItem;
152 float a = me.getItemHeight(me, i);
158 a += me.getItemHeight(me, i);
162 me.setSelected(me, i - 1);
164 else if(key == K_UPARROW || key == K_KP_UPARROW)
165 me.setSelected(me, me.selectedItem - 1);
166 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
167 me.setSelected(me, me.selectedItem + 1);
168 else if(key == K_HOME || key == K_KP_HOME)
171 me.setSelected(me, 0);
173 else if(key == K_END || key == K_KP_END)
175 me.scrollPos = max(0, me.getTotalHeight(me) - 1);
176 me.setSelected(me, me.nItems - 1);
182 float ListBox_mouseMove(entity me, vector pos)
184 if(pos_x < 0) return 0;
185 if(pos_y < 0) return 0;
186 if(pos_x >= 1) return 0;
187 if(pos_y >= 1) return 0;
188 if(pos_x < 1 - me.controlWidth)
192 me.focusedItem = me.getItemAtPos(me, me.scrollPos + pos.y);
193 if(x != me.focusedItem)
194 me.focusedItemTime = time;
198 float ListBox_mouseDrag(entity me, vector pos)
202 me.updateControlTopBottom(me);
203 me.dragScrollPos = pos;
207 if(pos.x < 1 - me.controlWidth - me.tolerance.y * me.controlWidth) hit = 0;
208 if(pos.y < 0 - me.tolerance.x) hit = 0;
209 if(pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
210 if(pos.y >= 1 + me.tolerance.x) hit = 0;
213 // calculate new pos to v
215 d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
216 me.scrollPos = me.previousValue + d;
219 me.scrollPos = me.previousValue;
220 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
221 me.scrollPos = max(me.scrollPos, 0);
222 i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
223 i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
224 me.setSelected(me, i);
226 else if(me.pressed == 2)
228 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
232 float ListBox_mousePress(entity me, vector pos)
234 if(pos.x < 0) return 0;
235 if(pos.y < 0) return 0;
236 if(pos.x >= 1) return 0;
237 if(pos.y >= 1) return 0;
238 me.dragScrollPos = pos;
239 me.updateControlTopBottom(me);
240 me.dragScrollTimer = time;
241 if(pos.x >= 1 - me.controlWidth)
243 // if hit, set me.pressed, otherwise scroll by one page
244 if(pos.y < me.controlTop)
247 me.scrollPos = max(me.scrollPos - 1, 0);
248 me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
250 else if(pos.y > me.controlBottom)
253 me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
254 me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
259 me.pressOffset = pos.y;
260 me.previousValue = me.scrollPos;
265 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
267 // an item has been clicked. Select it, ...
268 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
272 float ListBox_mouseRelease(entity me, vector pos)
276 // slider dragging mode
277 // in that case, nothing happens on releasing
279 else if(me.pressed == 2)
281 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
282 // item dragging mode
283 // select current one one last time...
284 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
285 // and give it a nice click event
288 vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
290 if((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3))
291 me.doubleClickListBoxItem(me, me.selectedItem, where);
293 me.clickListBoxItem(me, me.selectedItem, where);
295 me.lastClickedItem = me.selectedItem;
296 me.lastClickedTime = time;
302 void ListBox_focusLeave(entity me)
304 // Reset the var pressed in case listbox loses focus
305 // by a mouse click on an item of the list
306 // for example showing a dialog on right click
310 void ListBox_updateControlTopBottom(entity me)
313 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
314 if(me.getTotalHeight(me) <= 1)
316 // we don't need no stinkin' scrollbar, we don't need no view control...
318 me.controlBottom = 1;
323 if(frametime) // only do this in draw frames
325 if(me.dragScrollTimer < time)
329 // if selected item is below listbox, increase scrollpos so it is in
330 me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
331 // if selected item is above listbox, decrease scrollpos so it is in
332 me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
333 if(me.scrollPos != save)
334 me.dragScrollTimer = time + 0.2;
337 // if scroll pos is below end of list, fix it
338 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
339 // if scroll pos is above beginning of list, fix it
340 me.scrollPos = max(me.scrollPos, 0);
341 // now that we know where the list is scrolled to, find out where to draw the control
342 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
343 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
346 minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
347 f = me.controlBottom - me.controlTop;
348 if(f < minfactor) // FIXME good default?
350 // f * X + 1 * (1-X) = minfactor
351 // (f - 1) * X + 1 = minfactor
352 // (f - 1) * X = minfactor - 1
353 // X = (minfactor - 1) / (f - 1)
354 f = (minfactor - 1) / (f - 1);
355 me.controlTop = me.controlTop * f + 0 * (1 - f);
356 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
360 void ListBox_draw(entity me)
363 vector absSize, fillSize = '0 0 0';
364 vector oldshift, oldscale;
366 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
367 me.updateControlTopBottom(me);
368 fillSize.x = (1 - me.controlWidth);
370 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
373 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
374 if(me.getTotalHeight(me) > 1)
377 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
378 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
380 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
382 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
384 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
388 oldshift = draw_shift;
389 oldscale = draw_scale;
392 i = me.getItemAtPos(me, me.scrollPos);
393 y = me.getItemStart(me, i) - me.scrollPos;
394 for (; i < me.nItems && y < 1; ++i)
396 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
397 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
398 absSize = boxToGlobalSize(relSize, me.size);
399 draw_scale = boxToGlobalSize(relSize, oldscale);
400 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i) ? me.focusedItemTime : 0);
405 draw_shift = oldshift;
406 draw_scale = oldscale;
407 SUPER(ListBox).draw(me);
410 void ListBox_clickListBoxItem(entity me, float i, vector where)
415 void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
420 void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, float highlightedTime)
422 draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize.x) + eY * (8 / absSize.y), (isSelected ? '0 1 0' : '1 1 1'), 1, 0);