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, previousValue, float, 0)
25 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
26 ATTRIB(ListBox, pressOffset, float, 0)
28 METHOD(ListBox, updateControlTopBottom, void(entity))
29 ATTRIB(ListBox, controlTop, float, 0)
30 ATTRIB(ListBox, controlBottom, float, 0)
31 ATTRIB(ListBox, controlWidth, float, 0)
32 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
34 ATTRIB(ListBox, src, string, string_null) // scrollbar
35 ATTRIB(ListBox, color, vector, '1 1 1')
36 ATTRIB(ListBox, color2, vector, '1 1 1')
37 ATTRIB(ListBox, colorC, vector, '1 1 1')
38 ATTRIB(ListBox, colorF, vector, '1 1 1')
39 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
40 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
41 ATTRIB(ListBox, nItems, float, 42)
42 ATTRIB(ListBox, itemHeight, float, 0)
43 ATTRIB(ListBox, colorBG, vector, '0 0 0')
44 ATTRIB(ListBox, alphaBG, float, 0)
46 ATTRIB(ListBox, lastClickedItem, float, -1)
47 ATTRIB(ListBox, lastClickedTime, float, 0)
49 METHOD(ListBox, drawListBoxItem, void(entity, int, vector, bool, bool)) // item number, width/height, isSelected, isFocused
50 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
51 METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
52 METHOD(ListBox, setSelected, void(entity, float))
54 METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
55 METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
57 // NOTE: override these four methods if you want variable sized list items
58 METHOD(ListBox, getTotalHeight, float(entity))
59 METHOD(ListBox, getItemAtPos, float(entity, float))
60 METHOD(ListBox, getItemStart, float(entity, float))
61 METHOD(ListBox, getItemHeight, float(entity, float))
62 // NOTE: if getItemAt* are overridden, it may make sense to cache the
63 // start and height of the last item returned by getItemAtPos and fast
64 // track returning their properties for getItemStart and getItemHeight.
65 // The "hot" code path calls getItemAtPos first, then will query
66 // getItemStart and getItemHeight on it soon.
67 // When overriding, the following consistency rules must hold:
68 // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
69 // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
70 // for 0 <= i < me.nItems-1
71 // getItemStart(0) == 0
72 // getItemStart(getItemAtPos(p)) <= p
74 // getItemAtPos(p) == 0
76 // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
77 // if p < getTotalHeigt()
78 // getItemAtPos(p) == me.nItems - 1
79 // if p >= getTotalHeight()
84 void ListBox_setSelected(entity me, float i)
86 i = bound(0, i, me.nItems - 1);
88 // scroll the list to make sure the selected item is visible
89 // (even if the selected item doesn't change).
90 if(i < me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos))
93 me.scrollPosTarget = me.getItemStart(me, i);
95 else if(i > me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos))
98 if(i == me.nItems - 1)
99 me.scrollPosTarget = me.getTotalHeight(me) - 1;
102 me.scrollPosTarget = me.getItemStart(me, i + 1) - 1;
107 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
109 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
110 me.controlWidth = me.scrollbarWidth / absSize.x;
112 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
114 me.scrollbarWidth = theScrollbarWidth;
115 me.itemHeight = theItemHeight;
118 float ListBox_getTotalHeight(entity me)
120 return me.nItems * me.itemHeight;
122 float ListBox_getItemAtPos(entity me, float pos)
124 return floor(pos / me.itemHeight);
126 float ListBox_getItemStart(entity me, float i)
128 return me.itemHeight * i;
130 float ListBox_getItemHeight(entity me, float i)
132 return me.itemHeight;
135 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
137 return me.getItemAtPos(me, pos + 0.999) - 1;
139 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
141 return me.getItemAtPos(me, pos + 0.001) + 1;
143 float ListBox_keyDown(entity me, float key, float ascii, float shift)
145 if(key == K_MWHEELUP)
147 me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0);
149 else if(key == K_MWHEELDOWN)
151 me.scrollPosTarget = min(me.scrollPosTarget + 0.5, me.getTotalHeight(me) - 1);
153 else if(key == K_PGUP || key == K_KP_PGUP)
155 float i = me.selectedItem;
156 float a = me.getItemHeight(me, i);
162 a += me.getItemHeight(me, i);
166 me.setSelected(me, i + 1);
168 else if(key == K_PGDN || key == K_KP_PGDN)
170 float i = me.selectedItem;
171 float a = me.getItemHeight(me, i);
177 a += me.getItemHeight(me, i);
181 me.setSelected(me, i - 1);
183 else if(key == K_UPARROW || key == K_KP_UPARROW)
184 me.setSelected(me, me.selectedItem - 1);
185 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
186 me.setSelected(me, me.selectedItem + 1);
187 else if(key == K_HOME || key == K_KP_HOME)
188 me.setSelected(me, 0);
189 else if(key == K_END || key == K_KP_END)
190 me.setSelected(me, me.nItems - 1);
195 float ListBox_mouseMove(entity me, vector pos)
197 float focusedItem_save = me.focusedItem;
199 if(pos_x < 0) return 0;
200 if(pos_y < 0) return 0;
201 if(pos_x >= 1) return 0;
202 if(pos_y >= 1) return 0;
203 if(pos_x < 1 - me.controlWidth)
205 me.focusedItem = me.getItemAtPos(me, me.scrollPos + pos.y);
206 me.focusedItemPos = eY * pos.y;
207 if(focusedItem_save != me.focusedItem)
208 me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED;
212 float ListBox_mouseDrag(entity me, vector pos)
215 me.updateControlTopBottom(me);
216 me.dragScrollPos = pos;
220 if(pos.x < 1 - me.controlWidth - me.tolerance.y * me.controlWidth) hit = 0;
221 if(pos.y < 0 - me.tolerance.x) hit = 0;
222 if(pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
223 if(pos.y >= 1 + me.tolerance.x) hit = 0;
226 // calculate new pos to v
228 d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
229 me.scrollPosTarget = me.previousValue + d;
232 me.scrollPosTarget = me.previousValue;
233 me.scrollPosTarget = min(me.scrollPosTarget, me.getTotalHeight(me) - 1);
234 me.scrollPosTarget = max(me.scrollPosTarget, 0);
236 else if(me.pressed == 2)
239 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
243 float ListBox_mousePress(entity me, vector pos)
245 if(pos.x < 0) return 0;
246 if(pos.y < 0) return 0;
247 if(pos.x >= 1) return 0;
248 if(pos.y >= 1) return 0;
249 me.dragScrollPos = pos;
250 me.updateControlTopBottom(me);
251 if(pos.x >= 1 - me.controlWidth)
253 // if hit, set me.pressed, otherwise scroll by one page
254 if(pos.y < me.controlTop)
257 me.scrollPosTarget = max(me.scrollPosTarget - 1, 0);
259 else if(pos.y > me.controlBottom)
262 me.scrollPosTarget = min(me.scrollPosTarget + 1, me.getTotalHeight(me) - 1);
267 me.pressOffset = pos.y;
268 me.previousValue = me.scrollPos;
273 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
275 // an item has been clicked. Select it, ...
276 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
280 float ListBox_mouseRelease(entity me, vector pos)
284 // slider dragging mode
285 // in that case, nothing happens on releasing
287 else if(me.pressed == 2)
289 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
290 // item dragging mode
291 // select current one one last time...
292 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
293 // and give it a nice click event
296 vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
298 if((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3))
299 me.doubleClickListBoxItem(me, me.selectedItem, where);
301 me.clickListBoxItem(me, me.selectedItem, where);
303 me.lastClickedItem = me.selectedItem;
304 me.lastClickedTime = time;
310 void ListBox_focusLeave(entity me)
312 // Reset the var pressed in case listbox loses focus
313 // by a mouse click on an item of the list
314 // for example showing a dialog on right click
318 void ListBox_updateControlTopBottom(entity me)
321 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
322 if(me.getTotalHeight(me) <= 1)
324 // we don't need no stinkin' scrollbar, we don't need no view control...
326 me.controlBottom = 1;
331 // if scroll pos is below end of list, fix it
332 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
333 // if scroll pos is above beginning of list, fix it
334 me.scrollPos = max(me.scrollPos, 0);
335 // now that we know where the list is scrolled to, find out where to draw the control
336 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
337 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
340 minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
341 f = me.controlBottom - me.controlTop;
342 if(f < minfactor) // FIXME good default?
344 // f * X + 1 * (1-X) = minfactor
345 // (f - 1) * X + 1 = minfactor
346 // (f - 1) * X = minfactor - 1
347 // X = (minfactor - 1) / (f - 1)
348 f = (minfactor - 1) / (f - 1);
349 me.controlTop = me.controlTop * f + 0 * (1 - f);
350 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
354 void ListBox_draw(entity me)
357 vector absSize, fillSize = '0 0 0';
358 vector oldshift, oldscale;
360 if(me.scrollPos != me.scrollPosTarget)
362 float PI = 3.1415926535897932384626433832795028841971693993751058209749445923;
363 // this formula is guaranted to work with whatever framerate
364 float f = sin(PI / 2 * pow(frametime, 0.65));
365 me.scrollPos = me.scrollPos * (1 - f) + me.scrollPosTarget * f;
367 // update focusedItem while scrolling
368 if(me.focusedItem >= 0)
369 me.mouseMove(me, me.focusedItemPos);
373 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
374 me.updateControlTopBottom(me);
375 fillSize.x = (1 - me.controlWidth);
377 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
380 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
381 if(me.getTotalHeight(me) > 1)
384 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
385 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
387 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
389 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
391 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
395 oldshift = draw_shift;
396 oldscale = draw_scale;
399 i = me.getItemAtPos(me, me.scrollPos);
400 y = me.getItemStart(me, i) - me.scrollPos;
401 for (; i < me.nItems && y < 1; ++i)
403 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
404 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
405 absSize = boxToGlobalSize(relSize, me.size);
406 draw_scale = boxToGlobalSize(relSize, oldscale);
407 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i));
412 draw_shift = oldshift;
413 draw_scale = oldscale;
414 SUPER(ListBox).draw(me);
417 void ListBox_clickListBoxItem(entity me, float i, vector where)
422 void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
427 void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
429 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);