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 ATTRIB(ListBox, colorBG, vector, '0 0 0')
36 ATTRIB(ListBox, alphaBG, float, 0)
37 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
38 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
39 METHOD(ListBox, setSelected, void(entity, float))
44 void ListBox_setSelected(entity me, float i)
46 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
48 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
50 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
51 me.controlWidth = me.scrollbarWidth / absSize_x;
53 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
55 me.scrollbarWidth = theScrollbarWidth;
56 me.itemHeight = theItemHeight;
58 float ListBox_keyDown(entity me, float key, float ascii, float shift)
60 me.dragScrollTimer = time;
63 me.scrollPos = max(me.scrollPos - 0.5, 0);
64 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
66 else if(key == K_MWHEELDOWN)
68 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
69 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
71 else if(key == K_PGUP || key == K_KP_PGUP)
72 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
73 else if(key == K_PGDN || key == K_KP_PGDN)
74 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
75 else if(key == K_UPARROW || key == K_KP_UPARROW)
76 me.setSelected(me, me.selectedItem - 1);
77 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
78 me.setSelected(me, me.selectedItem + 1);
79 else if(key == K_HOME || key == K_KP_HOME)
82 me.setSelected(me, 0);
84 else if(key == K_END || key == K_KP_END)
86 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
87 me.setSelected(me, me.nItems - 1);
93 float ListBox_mouseDrag(entity me, vector pos)
97 me.updateControlTopBottom(me);
98 me.dragScrollPos = pos;
102 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
103 if(pos_y < 0 - me.tolerance_x) hit = 0;
104 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
105 if(pos_y >= 1 + me.tolerance_x) hit = 0;
108 // calculate new pos to v
110 d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
111 me.scrollPos = me.previousValue + d;
114 me.scrollPos = me.previousValue;
115 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
116 me.scrollPos = max(me.scrollPos, 0);
117 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
118 i = max(i, ceil(me.scrollPos / me.itemHeight));
119 me.setSelected(me, i);
121 else if(me.pressed == 2)
123 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
127 float ListBox_mousePress(entity me, vector pos)
129 if(pos_x < 0) return 0;
130 if(pos_y < 0) return 0;
131 if(pos_x >= 1) return 0;
132 if(pos_y >= 1) return 0;
133 me.dragScrollPos = pos;
134 me.updateControlTopBottom(me);
135 me.dragScrollTimer = time;
136 if(pos_x >= 1 - me.controlWidth)
138 // if hit, set me.pressed, otherwise scroll by one page
139 if(pos_y < me.controlTop)
142 me.scrollPos = max(me.scrollPos - 1, 0);
143 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
145 else if(pos_y > me.controlBottom)
148 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
149 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
154 me.pressOffset = pos_y;
155 me.previousValue = me.scrollPos;
160 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
162 // an item has been clicked. Select it, ...
163 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
167 float ListBox_mouseRelease(entity me, vector pos)
172 // slider dragging mode
173 // in that case, nothing happens on releasing
175 else if(me.pressed == 2)
177 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
178 // item dragging mode
179 // select current one one last time...
180 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
181 // and give it a nice click event
184 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
185 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
191 void ListBox_updateControlTopBottom(entity me)
194 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
195 if(me.nItems * me.itemHeight <= 1)
197 // we don't need no stinkin' scrollbar, we don't need no view control...
199 me.controlBottom = 1;
204 if(frametime) // only do this in draw frames
206 if(me.dragScrollTimer < time)
210 // if selected item is below listbox, increase scrollpos so it is in
211 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
212 // if selected item is above listbox, decrease scrollpos so it is in
213 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
214 if(me.scrollPos != save)
215 me.dragScrollTimer = time + 0.2;
218 // if scroll pos is below end of list, fix it
219 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
220 // if scroll pos is above beginning of list, fix it
221 me.scrollPos = max(me.scrollPos, 0);
222 // now that we know where the list is scrolled to, find out where to draw the control
223 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
224 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
227 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
228 f = me.controlBottom - me.controlTop;
229 if(f < minfactor) // FIXME good default?
231 // f * X + 1 * (1-X) = minfactor
232 // (f - 1) * X + 1 = minfactor
233 // (f - 1) * X = minfactor - 1
234 // X = (minfactor - 1) / (f - 1)
235 f = (minfactor - 1) / (f - 1);
236 me.controlTop = me.controlTop * f + 0 * (1 - f);
237 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
241 void ListBox_draw(entity me)
244 vector absSize, fillSize;
245 vector oldshift, oldscale;
247 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
248 me.updateControlTopBottom(me);
249 fillSize_x = (1 - me.controlWidth);
251 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
254 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
255 if(me.nItems * me.itemHeight > 1)
258 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
259 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
261 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
263 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
265 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
269 oldshift = draw_shift;
270 oldscale = draw_scale;
271 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
272 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
275 y = i * me.itemHeight - me.scrollPos;
278 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
279 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
280 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
285 void ListBox_clickListBoxItem(entity me, float i, vector where)
287 // itemclick, itemclick, does whatever itemclick does
290 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
292 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);