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 me.scrollPos = max(me.scrollPos - 1, 0);
110 me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
112 else if(key == K_PGDN || key == K_KP_PGDN)
114 me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
115 me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
117 else if(key == K_UPARROW || key == K_KP_UPARROW)
118 me.setSelected(me, me.selectedItem - 1);
119 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
120 me.setSelected(me, me.selectedItem + 1);
121 else if(key == K_HOME || key == K_KP_HOME)
124 me.setSelected(me, 0);
126 else if(key == K_END || key == K_KP_END)
128 me.scrollPos = max(0, me.getTotalHeight(me) - 1);
129 me.setSelected(me, me.nItems - 1);
135 float ListBox_mouseDrag(entity me, vector pos)
139 me.updateControlTopBottom(me);
140 me.dragScrollPos = pos;
144 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
145 if(pos_y < 0 - me.tolerance_x) hit = 0;
146 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
147 if(pos_y >= 1 + me.tolerance_x) hit = 0;
150 // calculate new pos to v
152 d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
153 me.scrollPos = me.previousValue + d;
156 me.scrollPos = me.previousValue;
157 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
158 me.scrollPos = max(me.scrollPos, 0);
159 i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
160 i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
161 me.setSelected(me, i);
163 else if(me.pressed == 2)
165 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
169 float ListBox_mousePress(entity me, vector pos)
171 if(pos_x < 0) return 0;
172 if(pos_y < 0) return 0;
173 if(pos_x >= 1) return 0;
174 if(pos_y >= 1) return 0;
175 me.dragScrollPos = pos;
176 me.updateControlTopBottom(me);
177 me.dragScrollTimer = time;
178 if(pos_x >= 1 - me.controlWidth)
180 // if hit, set me.pressed, otherwise scroll by one page
181 if(pos_y < me.controlTop)
184 me.scrollPos = max(me.scrollPos - 1, 0);
185 me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
187 else if(pos_y > me.controlBottom)
190 me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
191 me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
196 me.pressOffset = pos_y;
197 me.previousValue = me.scrollPos;
202 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
204 // an item has been clicked. Select it, ...
205 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
209 float ListBox_mouseRelease(entity me, vector pos)
213 // slider dragging mode
214 // in that case, nothing happens on releasing
216 else if(me.pressed == 2)
218 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
219 // item dragging mode
220 // select current one one last time...
221 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos_y));
222 // and give it a nice click event
225 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)));
231 void ListBox_focusLeave(entity me)
233 // Reset the var pressed in case listbox loses focus
234 // by a mouse click on an item of the list
235 // for example showing a dialog on right click
238 void ListBox_updateControlTopBottom(entity me)
241 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
242 if(me.getTotalHeight(me) <= 1)
244 // we don't need no stinkin' scrollbar, we don't need no view control...
246 me.controlBottom = 1;
251 if(frametime) // only do this in draw frames
253 if(me.dragScrollTimer < time)
257 // if selected item is below listbox, increase scrollpos so it is in
258 me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
259 // if selected item is above listbox, decrease scrollpos so it is in
260 me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
261 if(me.scrollPos != save)
262 me.dragScrollTimer = time + 0.2;
265 // if scroll pos is below end of list, fix it
266 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
267 // if scroll pos is above beginning of list, fix it
268 me.scrollPos = max(me.scrollPos, 0);
269 // now that we know where the list is scrolled to, find out where to draw the control
270 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
271 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
274 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
275 f = me.controlBottom - me.controlTop;
276 if(f < minfactor) // FIXME good default?
278 // f * X + 1 * (1-X) = minfactor
279 // (f - 1) * X + 1 = minfactor
280 // (f - 1) * X = minfactor - 1
281 // X = (minfactor - 1) / (f - 1)
282 f = (minfactor - 1) / (f - 1);
283 me.controlTop = me.controlTop * f + 0 * (1 - f);
284 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
288 void ListBox_draw(entity me)
291 vector absSize, fillSize = '0 0 0';
292 vector oldshift, oldscale;
294 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
295 me.updateControlTopBottom(me);
296 fillSize_x = (1 - me.controlWidth);
298 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
301 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
302 if(me.getTotalHeight(me) > 1)
305 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
306 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
308 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
310 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
312 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
316 oldshift = draw_shift;
317 oldscale = draw_scale;
319 i = me.getItemAtPos(me, me.scrollPos);
320 y = me.getItemStart(me, i) - me.scrollPos;
321 for(; i < me.nItems && y < 1; ++i)
323 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
324 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
325 absSize = boxToGlobalSize(relSize, me.size);
326 draw_scale = boxToGlobalSize(relSize, oldscale);
327 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
332 draw_shift = oldshift;
333 draw_scale = oldscale;
334 SUPER(ListBox).draw(me);
337 void ListBox_clickListBoxItem(entity me, float i, vector where)
339 // itemclick, itemclick, does whatever itemclick does
342 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
344 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);