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 ATTRIB(ListBox, focusable, float, 1)
11 ATTRIB(ListBox, selectedItem, float, 0)
12 ATTRIB(ListBox, size, vector, '0 0 0')
13 ATTRIB(ListBox, origin, vector, '0 0 0')
14 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
15 ATTRIB(ListBox, previousValue, float, 0)
16 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
17 ATTRIB(ListBox, pressOffset, float, 0)
19 METHOD(ListBox, updateControlTopBottom, void(entity))
20 ATTRIB(ListBox, controlTop, float, 0)
21 ATTRIB(ListBox, controlBottom, float, 0)
22 ATTRIB(ListBox, controlWidth, float, 0)
23 ATTRIB(ListBox, dragScrollTimer, float, 0)
24 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
26 ATTRIB(ListBox, src, string, string_null) // scrollbar
27 ATTRIB(ListBox, color, vector, '1 1 1')
28 ATTRIB(ListBox, color2, vector, '1 1 1')
29 ATTRIB(ListBox, colorC, vector, '1 1 1')
30 ATTRIB(ListBox, colorF, vector, '1 1 1')
31 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
32 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
33 ATTRIB(ListBox, nItems, float, 42)
34 ATTRIB(ListBox, itemHeight, float, 0)
35 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
36 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
37 METHOD(ListBox, setSelected, void(entity, float))
42 void setSelectedListBox(entity me, float i)
44 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
46 void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
48 resizeNotifyItem(me, relOrigin, relSize, absOrigin, absSize);
49 me.controlWidth = me.scrollbarWidth / absSize_x;
51 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
53 me.scrollbarWidth = theScrollbarWidth;
54 me.itemHeight = theItemHeight;
56 float keyDownListBox(entity me, float key, float ascii, float shift)
58 me.dragScrollTimer = time;
61 me.scrollPos = max(me.scrollPos - 0.5, 0);
62 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
64 else if(key == K_MWHEELDOWN)
66 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
67 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
69 else if(key == K_PGUP)
70 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
71 else if(key == K_PGDN)
72 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
73 else if(key == K_UPARROW)
74 me.setSelected(me, me.selectedItem - 1);
75 else if(key == K_DOWNARROW)
76 me.setSelected(me, me.selectedItem + 1);
77 else if(key == K_HOME)
80 me.setSelected(me, 0);
84 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
85 me.setSelected(me, me.nItems - 1);
91 float mouseDragListBox(entity me, vector pos)
95 me.updateControlTopBottom(me);
96 me.dragScrollPos = pos;
100 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
101 if(pos_y < 0 - me.tolerance_x) hit = 0;
102 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
103 if(pos_y >= 1 + me.tolerance_x) hit = 0;
106 // calculate new pos to v
108 delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
109 me.scrollPos = me.previousValue + delta;
112 me.scrollPos = me.previousValue;
113 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
114 me.scrollPos = max(me.scrollPos, 0);
115 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
116 i = max(i, ceil(me.scrollPos / me.itemHeight));
117 me.setSelected(me, i);
119 else if(me.pressed == 2)
121 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
125 float mousePressListBox(entity me, vector pos)
127 if(pos_x < 0) return 0;
128 if(pos_y < 0) return 0;
129 if(pos_x >= 1) return 0;
130 if(pos_y >= 1) return 0;
131 me.dragScrollPos = pos;
132 me.updateControlTopBottom(me);
133 me.dragScrollTimer = time;
134 if(pos_x >= 1 - me.controlWidth)
136 // if hit, set me.pressed, otherwise scroll by one page
137 if(pos_y < me.controlTop)
140 me.scrollPos = max(me.scrollPos - 1, 0);
141 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
143 else if(pos_y > me.controlBottom)
146 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
147 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
152 me.pressOffset = pos_y;
153 me.previousValue = me.scrollPos;
158 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
160 // an item has been clicked. Select it, ...
161 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
165 float mouseReleaseListBox(entity me, vector pos)
170 // slider dragging mode
171 // in that case, nothing happens on releasing
173 else if(me.pressed == 2)
175 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
176 // item dragging mode
177 // select current one one last time...
178 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
179 // and give it a nice click event
182 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
183 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
189 void updateControlTopBottomListBox(entity me)
192 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
193 if(me.nItems * me.itemHeight <= 1)
195 // we don't need no stinkin' scrollbar, we don't need no view control...
197 me.controlBottom = 1;
202 if(frametime) // only do this in draw frames
204 if(me.dragScrollTimer < time)
208 // if selected item is below listbox, increase scrollpos so it is in
209 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
210 // if selected item is above listbox, decrease scrollpos so it is in
211 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
212 if(me.scrollPos != save)
213 me.dragScrollTimer = time + 0.2;
216 // if scroll pos is below end of list, fix it
217 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
218 // if scroll pos is above beginning of list, fix it
219 me.scrollPos = max(me.scrollPos, 0);
220 // now that we know where the list is scrolled to, find out where to draw the control
221 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
222 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
225 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
226 f = me.controlBottom - me.controlTop;
227 if(f < fmin) // FIXME good default?
229 // f * X + 1 * (1-X) = fmin
230 // (f - 1) * X + 1 = fmin
231 // (f - 1) * X = fmin - 1
232 // X = (fmin - 1) / (f - 1)
233 f = (fmin - 1) / (f - 1);
234 me.controlTop = me.controlTop * f + 0 * (1 - f);
235 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
239 void drawListBox(entity me)
243 vector oldshift, oldscale;
245 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
246 me.updateControlTopBottom(me);
249 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
250 if(me.nItems * me.itemHeight > 1)
253 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
254 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
256 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
258 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
260 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
264 oldshift = draw_shift;
265 oldscale = draw_scale;
266 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
267 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
270 y = i * me.itemHeight - me.scrollPos;
273 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
274 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
275 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
280 void clickListBoxItemListBox(entity me, float i, vector where)
282 // itemclick, itemclick, does whatever itemclick does
285 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
287 draw_Text('0 0 0', strcat("Item ", ftos(i)), eX * (8 / absSize_x) + eY * (8 / absSize_y), (selected ? '0 1 0' : '1 1 1'), 1, 0);