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, allowFocusSound, float, 1)
18 ATTRIB(ListBox, selectedItem, int, 0)
19 ATTRIB(ListBox, size, vector, '0 0 0')
20 ATTRIB(ListBox, origin, vector, '0 0 0')
21 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
22 ATTRIB(ListBox, previousValue, float, 0)
23 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
24 ATTRIB(ListBox, pressOffset, float, 0)
26 METHOD(ListBox, updateControlTopBottom, void(entity))
27 ATTRIB(ListBox, controlTop, float, 0)
28 ATTRIB(ListBox, controlBottom, float, 0)
29 ATTRIB(ListBox, controlWidth, float, 0)
30 ATTRIB(ListBox, dragScrollTimer, float, 0)
31 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
33 ATTRIB(ListBox, src, string, string_null) // scrollbar
34 ATTRIB(ListBox, color, vector, '1 1 1')
35 ATTRIB(ListBox, color2, vector, '1 1 1')
36 ATTRIB(ListBox, colorC, vector, '1 1 1')
37 ATTRIB(ListBox, colorF, vector, '1 1 1')
38 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
39 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
40 ATTRIB(ListBox, nItems, float, 42)
41 ATTRIB(ListBox, itemHeight, float, 0)
42 ATTRIB(ListBox, colorBG, vector, '0 0 0')
43 ATTRIB(ListBox, alphaBG, float, 0)
45 ATTRIB(ListBox, lastClickedItem, float, -1)
46 ATTRIB(ListBox, lastClickedTime, float, 0)
48 METHOD(ListBox, drawListBoxItem, void(entity, int, vector, bool, bool)) // item number, width/height, isSelected, isFocused
49 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
50 METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
51 METHOD(ListBox, setSelected, void(entity, float))
53 METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
54 METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
56 // NOTE: override these four methods if you want variable sized list items
57 METHOD(ListBox, getTotalHeight, float(entity))
58 METHOD(ListBox, getItemAtPos, float(entity, float))
59 METHOD(ListBox, getItemStart, float(entity, float))
60 METHOD(ListBox, getItemHeight, float(entity, float))
61 // NOTE: if getItemAt* are overridden, it may make sense to cache the
62 // start and height of the last item returned by getItemAtPos and fast
63 // track returning their properties for getItemStart and getItemHeight.
64 // The "hot" code path calls getItemAtPos first, then will query
65 // getItemStart and getItemHeight on it soon.
66 // When overriding, the following consistency rules must hold:
67 // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
68 // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
69 // for 0 <= i < me.nItems-1
70 // getItemStart(0) == 0
71 // getItemStart(getItemAtPos(p)) <= p
73 // getItemAtPos(p) == 0
75 // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
76 // if p < getTotalHeigt()
77 // getItemAtPos(p) == me.nItems - 1
78 // if p >= getTotalHeight()
83 void ListBox_setSelected(entity me, float i)
85 me.selectedItem = bound(0, i, me.nItems - 1);
87 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
89 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
90 me.controlWidth = me.scrollbarWidth / absSize.x;
92 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
94 me.scrollbarWidth = theScrollbarWidth;
95 me.itemHeight = theItemHeight;
98 float ListBox_getTotalHeight(entity me)
100 return me.nItems * me.itemHeight;
102 float ListBox_getItemAtPos(entity me, float pos)
104 return floor(pos / me.itemHeight);
106 float ListBox_getItemStart(entity me, float i)
108 return me.itemHeight * i;
110 float ListBox_getItemHeight(entity me, float i)
112 return me.itemHeight;
115 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
117 return me.getItemAtPos(me, pos + 1.001) - 1;
119 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
121 return me.getItemAtPos(me, pos - 0.001) + 1;
123 float ListBox_keyDown(entity me, float key, float ascii, float shift)
125 me.dragScrollTimer = time;
126 if(key == K_MWHEELUP)
128 me.scrollPos = max(me.scrollPos - 0.5, 0);
129 me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
131 else if(key == K_MWHEELDOWN)
133 me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
134 me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
136 else if(key == K_PGUP || key == K_KP_PGUP)
138 float i = me.selectedItem;
139 float a = me.getItemHeight(me, i);
145 a += me.getItemHeight(me, i);
149 me.setSelected(me, i + 1);
151 else if(key == K_PGDN || key == K_KP_PGDN)
153 float i = me.selectedItem;
154 float a = me.getItemHeight(me, i);
160 a += me.getItemHeight(me, i);
164 me.setSelected(me, i - 1);
166 else if(key == K_UPARROW || key == K_KP_UPARROW)
167 me.setSelected(me, me.selectedItem - 1);
168 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
169 me.setSelected(me, me.selectedItem + 1);
170 else if(key == K_HOME || key == K_KP_HOME)
173 me.setSelected(me, 0);
175 else if(key == K_END || key == K_KP_END)
177 me.scrollPos = max(0, me.getTotalHeight(me) - 1);
178 me.setSelected(me, me.nItems - 1);
184 float ListBox_mouseMove(entity me, vector pos)
186 if(pos_x < 0) return 0;
187 if(pos_y < 0) return 0;
188 if(pos_x >= 1) return 0;
189 if(pos_y >= 1) return 0;
190 if(pos_x < 1 - me.controlWidth)
194 me.focusedItem = me.getItemAtPos(me, me.scrollPos + pos.y);
195 if(x != me.focusedItem)
196 me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED;
200 float ListBox_mouseDrag(entity me, vector pos)
204 me.updateControlTopBottom(me);
205 me.dragScrollPos = pos;
209 if(pos.x < 1 - me.controlWidth - me.tolerance.y * me.controlWidth) hit = 0;
210 if(pos.y < 0 - me.tolerance.x) hit = 0;
211 if(pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
212 if(pos.y >= 1 + me.tolerance.x) hit = 0;
215 // calculate new pos to v
217 d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
218 me.scrollPos = me.previousValue + d;
221 me.scrollPos = me.previousValue;
222 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
223 me.scrollPos = max(me.scrollPos, 0);
224 i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
225 i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
226 me.setSelected(me, i);
228 else if(me.pressed == 2)
230 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
234 float ListBox_mousePress(entity me, vector pos)
236 if(pos.x < 0) return 0;
237 if(pos.y < 0) return 0;
238 if(pos.x >= 1) return 0;
239 if(pos.y >= 1) return 0;
240 me.dragScrollPos = pos;
241 me.updateControlTopBottom(me);
242 me.dragScrollTimer = time;
243 if(pos.x >= 1 - me.controlWidth)
245 // if hit, set me.pressed, otherwise scroll by one page
246 if(pos.y < me.controlTop)
249 me.scrollPos = max(me.scrollPos - 1, 0);
250 me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
252 else if(pos.y > me.controlBottom)
255 me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
256 me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
261 me.pressOffset = pos.y;
262 me.previousValue = me.scrollPos;
267 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
269 // an item has been clicked. Select it, ...
270 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
274 float ListBox_mouseRelease(entity me, vector pos)
278 // slider dragging mode
279 // in that case, nothing happens on releasing
281 else if(me.pressed == 2)
283 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
284 // item dragging mode
285 // select current one one last time...
286 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
287 // and give it a nice click event
290 vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
292 if((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3))
293 me.doubleClickListBoxItem(me, me.selectedItem, where);
295 me.clickListBoxItem(me, me.selectedItem, where);
297 me.lastClickedItem = me.selectedItem;
298 me.lastClickedTime = time;
304 void ListBox_focusLeave(entity me)
306 // Reset the var pressed in case listbox loses focus
307 // by a mouse click on an item of the list
308 // for example showing a dialog on right click
312 void ListBox_updateControlTopBottom(entity me)
315 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
316 if(me.getTotalHeight(me) <= 1)
318 // we don't need no stinkin' scrollbar, we don't need no view control...
320 me.controlBottom = 1;
325 if(frametime) // only do this in draw frames
327 if(me.dragScrollTimer < time)
331 // if selected item is below listbox, increase scrollpos so it is in
332 me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
333 // if selected item is above listbox, decrease scrollpos so it is in
334 me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
335 if(me.scrollPos != save)
336 me.dragScrollTimer = time + 0.2;
339 // if scroll pos is below end of list, fix it
340 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
341 // if scroll pos is above beginning of list, fix it
342 me.scrollPos = max(me.scrollPos, 0);
343 // now that we know where the list is scrolled to, find out where to draw the control
344 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
345 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
348 minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
349 f = me.controlBottom - me.controlTop;
350 if(f < minfactor) // FIXME good default?
352 // f * X + 1 * (1-X) = minfactor
353 // (f - 1) * X + 1 = minfactor
354 // (f - 1) * X = minfactor - 1
355 // X = (minfactor - 1) / (f - 1)
356 f = (minfactor - 1) / (f - 1);
357 me.controlTop = me.controlTop * f + 0 * (1 - f);
358 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
362 void ListBox_draw(entity me)
365 vector absSize, fillSize = '0 0 0';
366 vector oldshift, oldscale;
368 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
369 me.updateControlTopBottom(me);
370 fillSize.x = (1 - me.controlWidth);
372 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
375 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
376 if(me.getTotalHeight(me) > 1)
379 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
380 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
382 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
384 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
386 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
390 oldshift = draw_shift;
391 oldscale = draw_scale;
394 i = me.getItemAtPos(me, me.scrollPos);
395 y = me.getItemStart(me, i) - me.scrollPos;
396 for (; i < me.nItems && y < 1; ++i)
398 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
399 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
400 absSize = boxToGlobalSize(relSize, me.size);
401 draw_scale = boxToGlobalSize(relSize, oldscale);
402 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i));
407 draw_shift = oldshift;
408 draw_scale = oldscale;
409 SUPER(ListBox).draw(me);
412 void ListBox_clickListBoxItem(entity me, float i, vector where)
417 void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
422 void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
424 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);