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 = time;
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 = time;
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 if(me.dragScrollTimer < time)
207 // if selected item is below listbox, increase scrollpos so it is in
208 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
209 // if selected item is above listbox, decrease scrollpos so it is in
210 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
211 if(me.scrollPos != save)
212 me.dragScrollTimer = time + 0.2;
215 // if scroll pos is below end of list, fix it
216 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
217 // if scroll pos is above beginning of list, fix it
218 me.scrollPos = max(me.scrollPos, 0);
219 // now that we know where the list is scrolled to, find out where to draw the control
220 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
221 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
224 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
225 f = me.controlBottom - me.controlTop;
226 if(f < fmin) // FIXME good default?
228 // f * X + 1 * (1-X) = fmin
229 // (f - 1) * X + 1 = fmin
230 // (f - 1) * X = fmin - 1
231 // X = (fmin - 1) / (f - 1)
232 f = (fmin - 1) / (f - 1);
233 me.controlTop = me.controlTop * f + 0 * (1 - f);
234 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
238 void drawListBox(entity me)
242 vector oldshift, oldscale;
244 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
245 me.updateControlTopBottom(me);
246 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
247 if(me.nItems * me.itemHeight > 1)
250 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
251 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
253 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
255 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
257 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
260 oldshift = draw_shift;
261 oldscale = draw_scale;
262 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
263 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
266 y = i * me.itemHeight - me.scrollPos;
269 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
270 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
271 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
276 void clickListBoxItemListBox(entity me, float i, vector where)
278 // itemclick, itemclick, does whatever itemclick does
281 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
283 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);