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))
54 void ListBox_setSelected(entity me, float i)
56 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
58 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
60 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
61 me.controlWidth = me.scrollbarWidth / absSize_x;
63 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
65 me.scrollbarWidth = theScrollbarWidth;
66 me.itemHeight = theItemHeight;
69 float ListBox_getTotalHeight(entity me)
71 return me.nItems * me.itemHeight;
73 float ListBox_getItemAtPos(entity me, float pos)
75 return floor(pos / me.itemHeight);
77 float ListBox_getItemStart(entity me, float i)
79 return me.itemHeight * i;
81 float ListBox_getItemHeight(entity me, float i)
86 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
88 return me.getItemAtPos(me, pos + 1.001) - 1;
90 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
92 return me.getItemAtPos(me, pos - 0.001) + 1;
94 float ListBox_keyDown(entity me, float key, float ascii, float shift)
96 me.dragScrollTimer = time;
99 me.scrollPos = max(me.scrollPos - 0.5, 0);
100 me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
102 else if(key == K_MWHEELDOWN)
104 me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
105 me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
107 else if(key == K_PGUP || key == K_KP_PGUP)
109 float i = me.selectedItem;
110 float a = me.getItemHeight(me, i);
116 a += me.getItemHeight(me, i);
120 me.setSelected(me, i + 1);
122 else if(key == K_PGDN || key == K_KP_PGDN)
124 float i = me.selectedItem;
125 float a = me.getItemHeight(me, i);
131 a += me.getItemHeight(me, i);
135 me.setSelected(me, i - 1);
137 else if(key == K_UPARROW || key == K_KP_UPARROW)
138 me.setSelected(me, me.selectedItem - 1);
139 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
140 me.setSelected(me, me.selectedItem + 1);
141 else if(key == K_HOME || key == K_KP_HOME)
144 me.setSelected(me, 0);
146 else if(key == K_END || key == K_KP_END)
148 me.scrollPos = max(0, me.getTotalHeight(me) - 1);
149 me.setSelected(me, me.nItems - 1);
155 float ListBox_mouseDrag(entity me, vector pos)
159 me.updateControlTopBottom(me);
160 me.dragScrollPos = pos;
164 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
165 if(pos_y < 0 - me.tolerance_x) hit = 0;
166 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
167 if(pos_y >= 1 + me.tolerance_x) hit = 0;
170 // calculate new pos to v
172 d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
173 me.scrollPos = me.previousValue + d;
176 me.scrollPos = me.previousValue;
177 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
178 me.scrollPos = max(me.scrollPos, 0);
179 i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
180 i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
181 me.setSelected(me, i);
183 else if(me.pressed == 2)
185 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
189 float ListBox_mousePress(entity me, vector pos)
191 if(pos_x < 0) return 0;
192 if(pos_y < 0) return 0;
193 if(pos_x >= 1) return 0;
194 if(pos_y >= 1) return 0;
195 me.dragScrollPos = pos;
196 me.updateControlTopBottom(me);
197 me.dragScrollTimer = time;
198 if(pos_x >= 1 - me.controlWidth)
200 // if hit, set me.pressed, otherwise scroll by one page
201 if(pos_y < me.controlTop)
204 me.scrollPos = max(me.scrollPos - 1, 0);
205 me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
207 else if(pos_y > me.controlBottom)
210 me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
211 me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
216 me.pressOffset = pos_y;
217 me.previousValue = me.scrollPos;
222 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
224 // an item has been clicked. Select it, ...
225 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
229 float ListBox_mouseRelease(entity me, vector pos)
233 // slider dragging mode
234 // in that case, nothing happens on releasing
236 else if(me.pressed == 2)
238 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
239 // item dragging mode
240 // select current one one last time...
241 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
242 // and give it a nice click event
245 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)));
251 void ListBox_focusLeave(entity me)
253 // Reset the var pressed in case listbox loses focus
254 // by a mouse click on an item of the list
255 // for example showing a dialog on right click
258 void ListBox_updateControlTopBottom(entity me)
261 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
262 if(me.getTotalHeight(me) <= 1)
264 // we don't need no stinkin' scrollbar, we don't need no view control...
266 me.controlBottom = 1;
271 if(frametime) // only do this in draw frames
273 if(me.dragScrollTimer < time)
277 // if selected item is below listbox, increase scrollpos so it is in
278 me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
279 // if selected item is above listbox, decrease scrollpos so it is in
280 me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
281 if(me.scrollPos != save)
282 me.dragScrollTimer = time + 0.2;
285 // if scroll pos is below end of list, fix it
286 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
287 // if scroll pos is above beginning of list, fix it
288 me.scrollPos = max(me.scrollPos, 0);
289 // now that we know where the list is scrolled to, find out where to draw the control
290 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
291 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
294 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
295 f = me.controlBottom - me.controlTop;
296 if(f < minfactor) // FIXME good default?
298 // f * X + 1 * (1-X) = minfactor
299 // (f - 1) * X + 1 = minfactor
300 // (f - 1) * X = minfactor - 1
301 // X = (minfactor - 1) / (f - 1)
302 f = (minfactor - 1) / (f - 1);
303 me.controlTop = me.controlTop * f + 0 * (1 - f);
304 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
308 void ListBox_draw(entity me)
311 vector absSize, fillSize = '0 0 0';
312 vector oldshift, oldscale;
314 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
315 me.updateControlTopBottom(me);
316 fillSize_x = (1 - me.controlWidth);
318 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
321 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
322 if(me.getTotalHeight(me) > 1)
325 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
326 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
328 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
330 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
332 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
336 oldshift = draw_shift;
337 oldscale = draw_scale;
339 i = me.getItemAtPos(me, me.scrollPos);
340 y = me.getItemStart(me, i) - me.scrollPos;
341 for(; i < me.nItems && y < 1; ++i)
343 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
344 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
345 absSize = boxToGlobalSize(relSize, me.size);
346 draw_scale = boxToGlobalSize(relSize, oldscale);
347 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
352 draw_shift = oldshift;
353 draw_scale = oldscale;
354 SUPER(ListBox).draw(me);
357 void ListBox_clickListBoxItem(entity me, float i, vector where)
359 // itemclick, itemclick, does whatever itemclick does
362 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
364 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);