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)
77 me.setSelected(me, 0);
79 me.setSelected(me, me.nItems - 1);
84 float mouseDragListBox(entity me, vector pos)
88 me.updateControlTopBottom(me);
89 me.dragScrollPos = pos;
93 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
94 if(pos_y < 0 - me.tolerance_x) hit = 0;
95 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
96 if(pos_y >= 1 + me.tolerance_x) hit = 0;
99 // calculate new pos to v
101 delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
102 me.scrollPos = me.previousValue + delta;
105 me.scrollPos = me.previousValue;
106 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
107 me.scrollPos = max(me.scrollPos, 0);
108 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
109 i = max(i, ceil(me.scrollPos / me.itemHeight));
110 me.setSelected(me, i);
112 else if(me.pressed == 2)
114 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
118 float mousePressListBox(entity me, vector pos)
120 if(pos_x < 0) return 0;
121 if(pos_y < 0) return 0;
122 if(pos_x >= 1) return 0;
123 if(pos_y >= 1) return 0;
124 me.dragScrollPos = pos;
125 me.updateControlTopBottom(me);
126 me.dragScrollTimer = 0;
127 if(pos_x >= 1 - me.controlWidth)
129 // if hit, set me.pressed, otherwise scroll by one page
130 if(pos_y < me.controlTop)
133 me.scrollPos = max(me.scrollPos - 1, 0);
134 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
136 else if(pos_y > me.controlBottom)
139 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
140 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
145 me.pressOffset = pos_y;
146 me.previousValue = me.scrollPos;
151 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
153 // an item has been clicked. Select it, ...
154 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
158 float mouseReleaseListBox(entity me, vector pos)
163 // slider dragging mode
164 // in that case, nothing happens on releasing
166 else if(me.pressed == 2)
168 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
169 // item dragging mode
170 // select current one one last time...
171 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
172 // and give it a nice click event
175 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
176 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
182 void updateControlTopBottomListBox(entity me)
185 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
186 if(me.nItems * me.itemHeight <= 1)
188 // we don't need no stinkin' scrollbar, we don't need no view control...
190 me.controlBottom = 1;
195 if(frametime) // only do this in draw frames
197 me.dragScrollTimer -= frametime;
198 if(me.dragScrollTimer < 0)
202 // if selected item is below listbox, increase scrollpos so it is in
203 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
204 // if selected item is above listbox, decrease scrollpos so it is in
205 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
206 if(me.scrollPos != save)
207 me.dragScrollTimer = 0.2;
210 // if scroll pos is below end of list, fix it
211 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
212 // if scroll pos is above beginning of list, fix it
213 me.scrollPos = max(me.scrollPos, 0);
214 // now that we know where the list is scrolled to, find out where to draw the control
215 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
216 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
219 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
220 f = me.controlBottom - me.controlTop;
221 if(f < fmin) // FIXME good default?
223 // f * X + 1 * (1-X) = fmin
224 // (f - 1) * X + 1 = fmin
225 // (f - 1) * X = fmin - 1
226 // X = (fmin - 1) / (f - 1)
227 f = (fmin - 1) / (f - 1);
228 me.controlTop = me.controlTop * f + 0 * (1 - f);
229 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
233 void drawListBox(entity me)
237 vector oldshift, oldscale;
239 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
240 me.updateControlTopBottom(me);
241 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
242 if(me.nItems * me.itemHeight > 1)
245 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
246 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
248 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
250 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
252 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
255 oldshift = draw_shift;
256 oldscale = draw_scale;
257 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
258 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
261 y = i * me.itemHeight - me.scrollPos;
264 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
265 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
266 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
271 void clickListBoxItemListBox(entity me, float i, vector where)
273 // itemclick, itemclick, does whatever itemclick does
276 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
278 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);