5 METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector))
6 METHOD(ListBox, configureListBox, void(entity, float, float))
7 METHOD(ListBox, draw, void(entity))
8 METHOD(ListBox, keyDown, float(entity, float, float, float))
9 METHOD(ListBox, mouseMove, float(entity, vector))
10 METHOD(ListBox, mousePress, float(entity, vector))
11 METHOD(ListBox, mouseDrag, float(entity, vector))
12 METHOD(ListBox, mouseRelease, float(entity, vector))
13 METHOD(ListBox, focusLeave, void(entity))
14 ATTRIB(ListBox, focusable, float, 1)
15 ATTRIB(ListBox, focusedItem, int, -1)
16 ATTRIB(ListBox, focusedItemAlpha, float, 0.3)
17 ATTRIB(ListBox, focusedItemPos, vector, '0 0 0')
18 ATTRIB(ListBox, allowFocusSound, float, 1)
19 ATTRIB(ListBox, selectedItem, int, 0)
20 ATTRIB(ListBox, size, vector, '0 0 0')
21 ATTRIB(ListBox, origin, vector, '0 0 0')
22 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
23 ATTRIB(ListBox, scrollPosTarget, float, 0)
24 ATTRIB(ListBox, needScrollToItem, float, -1)
25 METHOD(ListBox, scrollToItem, void(entity, int))
26 ATTRIB(ListBox, previousValue, float, 0)
27 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
28 ATTRIB(ListBox, pressOffset, float, 0)
30 METHOD(ListBox, updateControlTopBottom, void(entity))
31 ATTRIB(ListBox, controlTop, float, 0)
32 ATTRIB(ListBox, controlBottom, float, 0)
33 ATTRIB(ListBox, controlWidth, float, 0)
34 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
36 ATTRIB(ListBox, src, string, string_null) // scrollbar
37 ATTRIB(ListBox, color, vector, '1 1 1')
38 ATTRIB(ListBox, color2, vector, '1 1 1')
39 ATTRIB(ListBox, colorC, vector, '1 1 1')
40 ATTRIB(ListBox, colorF, vector, '1 1 1')
41 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
42 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
43 ATTRIB(ListBox, nItems, float, 42)
44 ATTRIB(ListBox, itemHeight, float, 0)
45 ATTRIB(ListBox, colorBG, vector, '0 0 0')
46 ATTRIB(ListBox, alphaBG, float, 0)
48 ATTRIB(ListBox, lastClickedItem, float, -1)
49 ATTRIB(ListBox, lastClickedTime, float, 0)
51 METHOD(ListBox, drawListBoxItem, void(entity, int, vector, bool, bool)) // item number, width/height, isSelected, isFocused
52 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
53 METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
54 METHOD(ListBox, setSelected, void(entity, float))
56 METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
57 METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
59 // NOTE: override these four methods if you want variable sized list items
60 METHOD(ListBox, getTotalHeight, float(entity))
61 METHOD(ListBox, getItemAtPos, float(entity, float))
62 METHOD(ListBox, getItemStart, float(entity, float))
63 METHOD(ListBox, getItemHeight, float(entity, float))
64 // NOTE: if getItemAt* are overridden, it may make sense to cache the
65 // start and height of the last item returned by getItemAtPos and fast
66 // track returning their properties for getItemStart and getItemHeight.
67 // The "hot" code path calls getItemAtPos first, then will query
68 // getItemStart and getItemHeight on it soon.
69 // When overriding, the following consistency rules must hold:
70 // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
71 // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
72 // for 0 <= i < me.nItems-1
73 // getItemStart(0) == 0
74 // getItemStart(getItemAtPos(p)) <= p
76 // getItemAtPos(p) == 0
78 // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
79 // if p < getTotalHeigt()
80 // getItemAtPos(p) == me.nItems - 1
81 // if p >= getTotalHeight()
86 void ListBox_scrollToItem(entity me, int i)
88 // scroll doesn't work properly until itemHeight is set to the correct value
89 // at the first resizeNotify call
90 if(me.itemHeight == 1) // initial temporary value of itemHeight is 1
92 me.needScrollToItem = i;
96 i = bound(0, i, me.nItems - 1);
98 // scroll the list to make sure the selected item is visible
99 // (even if the selected item doesn't change).
100 if(i < me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos))
102 // above visible area
103 me.scrollPosTarget = me.getItemStart(me, i);
105 else if(i > me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos))
107 // below visible area
108 if(i == me.nItems - 1)
109 me.scrollPosTarget = me.getTotalHeight(me) - 1;
111 me.scrollPosTarget = me.getItemStart(me, i + 1) - 1;
115 void ListBox_setSelected(entity me, float i)
117 i = bound(0, i, me.nItems - 1);
118 me.scrollToItem(me, i);
121 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
123 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
124 me.controlWidth = me.scrollbarWidth / absSize.x;
126 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
128 me.scrollbarWidth = theScrollbarWidth;
129 me.itemHeight = theItemHeight;
132 float ListBox_getTotalHeight(entity me)
134 return me.nItems * me.itemHeight;
136 float ListBox_getItemAtPos(entity me, float pos)
138 return floor(pos / me.itemHeight);
140 float ListBox_getItemStart(entity me, float i)
142 return me.itemHeight * i;
144 float ListBox_getItemHeight(entity me, float i)
146 return me.itemHeight;
149 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
151 return me.getItemAtPos(me, pos + 0.999) - 1;
153 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
155 return me.getItemAtPos(me, pos + 0.001) + 1;
157 float ListBox_keyDown(entity me, float key, float ascii, float shift)
159 if(key == K_MWHEELUP)
161 me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0);
163 else if(key == K_MWHEELDOWN)
165 me.scrollPosTarget = min(me.scrollPosTarget + 0.5, me.getTotalHeight(me) - 1);
167 else if(key == K_PGUP || key == K_KP_PGUP)
169 float i = me.selectedItem;
170 float a = me.getItemHeight(me, i);
176 a += me.getItemHeight(me, i);
180 me.setSelected(me, i + 1);
182 else if(key == K_PGDN || key == K_KP_PGDN)
184 float i = me.selectedItem;
185 float a = me.getItemHeight(me, i);
191 a += me.getItemHeight(me, i);
195 me.setSelected(me, i - 1);
197 else if(key == K_UPARROW || key == K_KP_UPARROW)
198 me.setSelected(me, me.selectedItem - 1);
199 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
200 me.setSelected(me, me.selectedItem + 1);
201 else if(key == K_HOME || key == K_KP_HOME)
202 me.setSelected(me, 0);
203 else if(key == K_END || key == K_KP_END)
204 me.setSelected(me, me.nItems - 1);
209 float ListBox_mouseMove(entity me, vector pos)
211 float focusedItem_save = me.focusedItem;
213 if(pos_x < 0) return 0;
214 if(pos_y < 0) return 0;
215 if(pos_x >= 1) return 0;
216 if(pos_y >= 1) return 0;
217 if(pos_x < 1 - me.controlWidth)
219 me.focusedItem = me.getItemAtPos(me, me.scrollPos + pos.y);
220 me.focusedItemPos = eY * pos.y;
221 if(focusedItem_save != me.focusedItem)
222 me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED;
226 float ListBox_mouseDrag(entity me, vector pos)
229 me.updateControlTopBottom(me);
230 me.dragScrollPos = pos;
234 if(pos.x < 1 - me.controlWidth - me.tolerance.y * me.controlWidth) hit = 0;
235 if(pos.y < 0 - me.tolerance.x) hit = 0;
236 if(pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
237 if(pos.y >= 1 + me.tolerance.x) hit = 0;
240 // calculate new pos to v
242 d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
243 me.scrollPosTarget = me.previousValue + d;
246 me.scrollPosTarget = me.previousValue;
247 me.scrollPosTarget = min(me.scrollPosTarget, me.getTotalHeight(me) - 1);
248 me.scrollPosTarget = max(me.scrollPosTarget, 0);
250 else if(me.pressed == 2)
253 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
257 float ListBox_mousePress(entity me, vector pos)
259 if(pos.x < 0) return 0;
260 if(pos.y < 0) return 0;
261 if(pos.x >= 1) return 0;
262 if(pos.y >= 1) return 0;
263 me.dragScrollPos = pos;
264 me.updateControlTopBottom(me);
265 if(pos.x >= 1 - me.controlWidth)
267 // if hit, set me.pressed, otherwise scroll by one page
268 if(pos.y < me.controlTop)
271 me.scrollPosTarget = max(me.scrollPosTarget - 1, 0);
273 else if(pos.y > me.controlBottom)
276 me.scrollPosTarget = min(me.scrollPosTarget + 1, me.getTotalHeight(me) - 1);
281 me.pressOffset = pos.y;
282 me.previousValue = me.scrollPos;
287 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
289 // an item has been clicked. Select it, ...
290 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
294 float ListBox_mouseRelease(entity me, vector pos)
298 // slider dragging mode
299 // in that case, nothing happens on releasing
301 else if(me.pressed == 2)
303 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
304 // item dragging mode
305 // select current one one last time...
306 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
307 // and give it a nice click event
310 vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
312 if((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3))
313 me.doubleClickListBoxItem(me, me.selectedItem, where);
315 me.clickListBoxItem(me, me.selectedItem, where);
317 me.lastClickedItem = me.selectedItem;
318 me.lastClickedTime = time;
324 void ListBox_focusLeave(entity me)
326 // Reset the var pressed in case listbox loses focus
327 // by a mouse click on an item of the list
328 // for example showing a dialog on right click
332 void ListBox_updateControlTopBottom(entity me)
335 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
336 if(me.getTotalHeight(me) <= 1)
338 // we don't need no stinkin' scrollbar, we don't need no view control...
340 me.controlBottom = 1;
345 // if scroll pos is below end of list, fix it
346 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
347 // if scroll pos is above beginning of list, fix it
348 me.scrollPos = max(me.scrollPos, 0);
349 // now that we know where the list is scrolled to, find out where to draw the control
350 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
351 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
354 minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
355 f = me.controlBottom - me.controlTop;
356 if(f < minfactor) // FIXME good default?
358 // f * X + 1 * (1-X) = minfactor
359 // (f - 1) * X + 1 = minfactor
360 // (f - 1) * X = minfactor - 1
361 // X = (minfactor - 1) / (f - 1)
362 f = (minfactor - 1) / (f - 1);
363 me.controlTop = me.controlTop * f + 0 * (1 - f);
364 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
368 void ListBox_draw(entity me)
371 vector absSize, fillSize = '0 0 0';
372 vector oldshift, oldscale;
373 if(me.needScrollToItem >= 0)
375 me.scrollToItem(me, me.needScrollToItem);
376 me.needScrollToItem = -1;
378 if(me.scrollPos != me.scrollPosTarget)
380 float PI = 3.1415926535897932384626433832795028841971693993751058209749445923;
381 // this formula is guaranted to work with whatever framerate
382 float f = sin(PI / 2 * pow(frametime, 0.65));
383 me.scrollPos = me.scrollPos * (1 - f) + me.scrollPosTarget * f;
384 if(fabs(me.scrollPos - me.scrollPosTarget) < 0.001)
385 me.scrollPos = me.scrollPosTarget;
387 // update focusedItem while scrolling
388 if(me.focusedItem >= 0)
389 me.mouseMove(me, me.focusedItemPos);
393 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
394 me.updateControlTopBottom(me);
395 fillSize.x = (1 - me.controlWidth);
397 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
400 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
401 if(me.getTotalHeight(me) > 1)
404 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
405 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
407 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
409 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
411 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
415 oldshift = draw_shift;
416 oldscale = draw_scale;
419 i = me.getItemAtPos(me, me.scrollPos);
420 y = me.getItemStart(me, i) - me.scrollPos;
421 for (; i < me.nItems && y < 1; ++i)
423 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
424 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
425 absSize = boxToGlobalSize(relSize, me.size);
426 draw_scale = boxToGlobalSize(relSize, oldscale);
427 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i));
432 draw_shift = oldshift;
433 draw_scale = oldscale;
434 SUPER(ListBox).draw(me);
437 void ListBox_clickListBoxItem(entity me, float i, vector where)
442 void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
447 void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
449 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);