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, mousePress, float(entity, vector))
8 METHOD(ListBox, mouseDrag, float(entity, vector))
9 METHOD(ListBox, mouseRelease, float(entity, vector))
10 METHOD(ListBox, focusLeave, void(entity))
11 ATTRIB(ListBox, focusable, float, 1)
12 ATTRIB(ListBox, selectedItem, float, 0)
13 ATTRIB(ListBox, size, vector, '0 0 0')
14 ATTRIB(ListBox, origin, vector, '0 0 0')
15 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
16 ATTRIB(ListBox, previousValue, float, 0)
17 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
18 ATTRIB(ListBox, pressOffset, float, 0)
20 METHOD(ListBox, updateControlTopBottom, void(entity))
21 ATTRIB(ListBox, controlTop, float, 0)
22 ATTRIB(ListBox, controlBottom, float, 0)
23 ATTRIB(ListBox, controlWidth, float, 0)
24 ATTRIB(ListBox, dragScrollTimer, float, 0)
25 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
27 ATTRIB(ListBox, src, string, string_null) // scrollbar
28 ATTRIB(ListBox, color, vector, '1 1 1')
29 ATTRIB(ListBox, color2, vector, '1 1 1')
30 ATTRIB(ListBox, colorC, vector, '1 1 1')
31 ATTRIB(ListBox, colorF, vector, '1 1 1')
32 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
33 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
34 ATTRIB(ListBox, nItems, float, 42)
35 ATTRIB(ListBox, itemHeight, float, 0)
36 ATTRIB(ListBox, colorBG, vector, '0 0 0')
37 ATTRIB(ListBox, alphaBG, float, 0)
38 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
39 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
40 METHOD(ListBox, setSelected, void(entity, float))
42 METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
43 METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
45 // NOTE: override these four methods if you want variable sized list items
46 METHOD(ListBox, getTotalHeight, float(entity))
47 METHOD(ListBox, getItemAtPos, float(entity, float))
48 METHOD(ListBox, getItemStart, float(entity, float))
49 METHOD(ListBox, getItemHeight, float(entity, float))
50 // NOTE: if getItemAt* are overridden, it may make sense to cache the
51 // start and height of the last item returned by getItemAtPos and fast
52 // track returning their properties for getItemStart and getItemHeight.
53 // The "hot" code path calls getItemAtPos first, then will query
54 // getItemStart and getItemHeight on it soon.
55 // When overriding, the following consistency rules must hold:
56 // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
57 // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
58 // for 0 <= i < me.nItems-1
59 // getItemStart(0) == 0
60 // getItemStart(getItemAtPos(p)) <= p
62 // getItemAtPos(p) == 0
64 // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
65 // if p < getTotalHeigt()
66 // getItemAtPos(p) == me.nItems - 1
67 // if p >= getTotalHeight()
72 void ListBox_setSelected(entity me, float i)
74 me.selectedItem = bound(0, i, me.nItems - 1);
76 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
78 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
79 me.controlWidth = me.scrollbarWidth / absSize_x;
81 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
83 me.scrollbarWidth = theScrollbarWidth;
84 me.itemHeight = theItemHeight;
87 float ListBox_getTotalHeight(entity me)
89 return me.nItems * me.itemHeight;
91 float ListBox_getItemAtPos(entity me, float pos)
93 return floor(pos / me.itemHeight);
95 float ListBox_getItemStart(entity me, float i)
97 return me.itemHeight * i;
99 float ListBox_getItemHeight(entity me, float i)
101 return me.itemHeight;
104 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
106 return me.getItemAtPos(me, pos + 1.001) - 1;
108 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
110 return me.getItemAtPos(me, pos - 0.001) + 1;
112 float ListBox_keyDown(entity me, float key, float ascii, float shift)
114 me.dragScrollTimer = time;
115 if(key == K_MWHEELUP)
117 me.scrollPos = max(me.scrollPos - 0.5, 0);
118 me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
120 else if(key == K_MWHEELDOWN)
122 me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
123 me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
125 else if(key == K_PGUP || key == K_KP_PGUP)
127 float i = me.selectedItem;
128 float a = me.getItemHeight(me, i);
134 a += me.getItemHeight(me, i);
138 me.setSelected(me, i + 1);
140 else if(key == K_PGDN || key == K_KP_PGDN)
142 float i = me.selectedItem;
143 float a = me.getItemHeight(me, i);
149 a += me.getItemHeight(me, i);
153 me.setSelected(me, i - 1);
155 else if(key == K_UPARROW || key == K_KP_UPARROW)
156 me.setSelected(me, me.selectedItem - 1);
157 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
158 me.setSelected(me, me.selectedItem + 1);
159 else if(key == K_HOME || key == K_KP_HOME)
162 me.setSelected(me, 0);
164 else if(key == K_END || key == K_KP_END)
166 me.scrollPos = max(0, me.getTotalHeight(me) - 1);
167 me.setSelected(me, me.nItems - 1);
173 float ListBox_mouseDrag(entity me, vector pos)
177 me.updateControlTopBottom(me);
178 me.dragScrollPos = pos;
182 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
183 if(pos_y < 0 - me.tolerance_x) hit = 0;
184 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
185 if(pos_y >= 1 + me.tolerance_x) hit = 0;
188 // calculate new pos to v
190 d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
191 me.scrollPos = me.previousValue + d;
194 me.scrollPos = me.previousValue;
195 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
196 me.scrollPos = max(me.scrollPos, 0);
197 i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
198 i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
199 me.setSelected(me, i);
201 else if(me.pressed == 2)
203 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
207 float ListBox_mousePress(entity me, vector pos)
209 if(pos_x < 0) return 0;
210 if(pos_y < 0) return 0;
211 if(pos_x >= 1) return 0;
212 if(pos_y >= 1) return 0;
213 me.dragScrollPos = pos;
214 me.updateControlTopBottom(me);
215 me.dragScrollTimer = time;
216 if(pos_x >= 1 - me.controlWidth)
218 // if hit, set me.pressed, otherwise scroll by one page
219 if(pos_y < me.controlTop)
222 me.scrollPos = max(me.scrollPos - 1, 0);
223 me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
225 else if(pos_y > me.controlBottom)
228 me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
229 me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
234 me.pressOffset = pos_y;
235 me.previousValue = me.scrollPos;
240 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
242 // an item has been clicked. Select it, ...
243 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
247 float ListBox_mouseRelease(entity me, vector pos)
251 // slider dragging mode
252 // in that case, nothing happens on releasing
254 else if(me.pressed == 2)
256 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
257 // item dragging mode
258 // select current one one last time...
259 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
260 // and give it a nice click event
263 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem)));
269 void ListBox_focusLeave(entity me)
271 // Reset the var pressed in case listbox loses focus
272 // by a mouse click on an item of the list
273 // for example showing a dialog on right click
276 void ListBox_updateControlTopBottom(entity me)
279 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
280 if(me.getTotalHeight(me) <= 1)
282 // we don't need no stinkin' scrollbar, we don't need no view control...
284 me.controlBottom = 1;
289 if(frametime) // only do this in draw frames
291 if(me.dragScrollTimer < time)
295 // if selected item is below listbox, increase scrollpos so it is in
296 me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
297 // if selected item is above listbox, decrease scrollpos so it is in
298 me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
299 if(me.scrollPos != save)
300 me.dragScrollTimer = time + 0.2;
303 // if scroll pos is below end of list, fix it
304 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
305 // if scroll pos is above beginning of list, fix it
306 me.scrollPos = max(me.scrollPos, 0);
307 // now that we know where the list is scrolled to, find out where to draw the control
308 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
309 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
312 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
313 f = me.controlBottom - me.controlTop;
314 if(f < minfactor) // FIXME good default?
316 // f * X + 1 * (1-X) = minfactor
317 // (f - 1) * X + 1 = minfactor
318 // (f - 1) * X = minfactor - 1
319 // X = (minfactor - 1) / (f - 1)
320 f = (minfactor - 1) / (f - 1);
321 me.controlTop = me.controlTop * f + 0 * (1 - f);
322 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
326 void ListBox_draw(entity me)
329 vector absSize, fillSize = '0 0 0';
330 vector oldshift, oldscale;
332 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
333 me.updateControlTopBottom(me);
334 fillSize_x = (1 - me.controlWidth);
336 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
339 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
340 if(me.getTotalHeight(me) > 1)
343 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
344 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
346 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
348 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
350 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
354 oldshift = draw_shift;
355 oldscale = draw_scale;
357 i = me.getItemAtPos(me, me.scrollPos);
358 y = me.getItemStart(me, i) - me.scrollPos;
359 for(; i < me.nItems && y < 1; ++i)
361 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
362 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
363 absSize = boxToGlobalSize(relSize, me.size);
364 draw_scale = boxToGlobalSize(relSize, oldscale);
365 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
370 draw_shift = oldshift;
371 draw_scale = oldscale;
372 SUPER(ListBox).draw(me);
375 void ListBox_clickListBoxItem(entity me, float i, vector where)
377 // itemclick, itemclick, does whatever itemclick does
380 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
382 draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize_x) + eY * (8 / absSize_y), (selected ? '0 1 0' : '1 1 1'), 1, 0);