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, scrollPos, float, 0) // measured in window heights, fixed when needed
14 ATTRIB(ListBox, previousValue, float, 0)
15 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
16 ATTRIB(ListBox, pressOffset, float, 0)
18 METHOD(ListBox, updateControlTopBottom, void(entity))
19 ATTRIB(ListBox, controlTop, float, 0)
20 ATTRIB(ListBox, controlBottom, float, 0)
21 ATTRIB(ListBox, controlWidth, float, 0)
22 ATTRIB(ListBox, dragScrollTimer, float, 0)
23 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
25 ATTRIB(ListBox, src, string, string_null) // scrollbar
26 ATTRIB(ListBox, color, vector, '1 1 1')
27 ATTRIB(ListBox, color2, vector, '1 1 1')
28 ATTRIB(ListBox, colorC, vector, '1 1 1')
29 ATTRIB(ListBox, colorF, vector, '1 1 1')
30 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
31 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
32 ATTRIB(ListBox, nItems, float, 42)
33 ATTRIB(ListBox, itemHeight, float, 0)
34 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
35 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
36 METHOD(ListBox, setSelected, void(entity, float))
41 void setSelectedListBox(entity me, float i)
43 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
45 void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
48 me.controlWidth = me.scrollbarWidth / absSize_x;
50 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
52 me.scrollbarWidth = theScrollbarWidth;
53 me.itemHeight = theItemHeight;
55 float keyDownListBox(entity me, float key, float ascii, float shift)
57 me.dragScrollTimer = 0;
60 me.scrollPos = max(me.scrollPos - 0.5, 0);
61 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
63 else if(key == K_MWHEELDOWN)
65 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
66 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
68 else if(key == K_PGUP)
69 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
70 else if(key == K_PGDN)
71 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
72 else if(key == K_UPARROW)
73 me.setSelected(me, me.selectedItem - 1);
74 else if(key == K_DOWNARROW)
75 me.setSelected(me, me.selectedItem + 1);
76 else if(key == K_HOME)
79 me.setSelected(me, 0);
83 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
84 me.setSelected(me, me.nItems - 1);
90 float mouseDragListBox(entity me, vector pos)
94 me.updateControlTopBottom(me);
95 me.dragScrollPos = pos;
99 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
100 if(pos_y < 0 - me.tolerance_x) hit = 0;
101 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
102 if(pos_y >= 1 + me.tolerance_x) hit = 0;
105 // calculate new pos to v
107 delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
108 me.scrollPos = me.previousValue + delta;
111 me.scrollPos = me.previousValue;
112 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
113 me.scrollPos = max(me.scrollPos, 0);
114 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
115 i = max(i, ceil(me.scrollPos / me.itemHeight));
116 me.setSelected(me, i);
118 else if(me.pressed == 2)
120 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
124 float mousePressListBox(entity me, vector pos)
126 if(pos_x < 0) return 0;
127 if(pos_y < 0) return 0;
128 if(pos_x >= 1) return 0;
129 if(pos_y >= 1) return 0;
130 me.dragScrollPos = pos;
131 me.updateControlTopBottom(me);
132 me.dragScrollTimer = 0;
133 if(pos_x >= 1 - me.controlWidth)
135 // if hit, set me.pressed, otherwise scroll by one page
136 if(pos_y < me.controlTop)
139 me.scrollPos = max(me.scrollPos - 1, 0);
140 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
142 else if(pos_y > me.controlBottom)
145 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
146 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
151 me.pressOffset = pos_y;
152 me.previousValue = me.scrollPos;
157 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
159 // an item has been clicked. Select it, ...
160 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
164 float mouseReleaseListBox(entity me, vector pos)
169 // slider dragging mode
170 // in that case, nothing happens on releasing
172 else if(me.pressed == 2)
174 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
175 // item dragging mode
176 // select current one one last time...
177 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
178 // and give it a nice click event
181 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
182 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
188 void updateControlTopBottomListBox(entity me)
191 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
192 if(me.nItems * me.itemHeight <= 1)
194 // we don't need no stinkin' scrollbar, we don't need no view control...
196 me.controlBottom = 1;
201 if(frametime) // only do this in draw frames
203 me.dragScrollTimer -= frametime;
204 if(me.dragScrollTimer < 0)
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 = 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);
247 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
248 if(me.nItems * me.itemHeight > 1)
251 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
252 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
254 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
256 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
258 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
261 oldshift = draw_shift;
262 oldscale = draw_scale;
263 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
264 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
267 y = i * me.itemHeight - me.scrollPos;
270 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
271 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
272 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
277 void clickListBoxItemListBox(entity me, float i, vector where)
279 // itemclick, itemclick, does whatever itemclick does
282 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
284 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);