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)
49 me.origin = absOrigin;
50 me.controlWidth = me.scrollbarWidth / absSize_x;
52 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
54 me.scrollbarWidth = theScrollbarWidth;
55 me.itemHeight = theItemHeight;
57 float keyDownListBox(entity me, float key, float ascii, float shift)
59 me.dragScrollTimer = time;
62 me.scrollPos = max(me.scrollPos - 0.5, 0);
63 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
65 else if(key == K_MWHEELDOWN)
67 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
68 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
70 else if(key == K_PGUP)
71 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
72 else if(key == K_PGDN)
73 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
74 else if(key == K_UPARROW)
75 me.setSelected(me, me.selectedItem - 1);
76 else if(key == K_DOWNARROW)
77 me.setSelected(me, me.selectedItem + 1);
78 else if(key == K_HOME)
81 me.setSelected(me, 0);
85 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
86 me.setSelected(me, me.nItems - 1);
92 float mouseDragListBox(entity me, vector pos)
96 me.updateControlTopBottom(me);
97 me.dragScrollPos = pos;
101 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
102 if(pos_y < 0 - me.tolerance_x) hit = 0;
103 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
104 if(pos_y >= 1 + me.tolerance_x) hit = 0;
107 // calculate new pos to v
109 delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
110 me.scrollPos = me.previousValue + delta;
113 me.scrollPos = me.previousValue;
114 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
115 me.scrollPos = max(me.scrollPos, 0);
116 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
117 i = max(i, ceil(me.scrollPos / me.itemHeight));
118 me.setSelected(me, i);
120 else if(me.pressed == 2)
122 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
126 float mousePressListBox(entity me, vector pos)
128 if(pos_x < 0) return 0;
129 if(pos_y < 0) return 0;
130 if(pos_x >= 1) return 0;
131 if(pos_y >= 1) return 0;
132 me.dragScrollPos = pos;
133 me.updateControlTopBottom(me);
134 me.dragScrollTimer = time;
135 if(pos_x >= 1 - me.controlWidth)
137 // if hit, set me.pressed, otherwise scroll by one page
138 if(pos_y < me.controlTop)
141 me.scrollPos = max(me.scrollPos - 1, 0);
142 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
144 else if(pos_y > me.controlBottom)
147 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
148 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
153 me.pressOffset = pos_y;
154 me.previousValue = me.scrollPos;
159 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
161 // an item has been clicked. Select it, ...
162 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
166 float mouseReleaseListBox(entity me, vector pos)
171 // slider dragging mode
172 // in that case, nothing happens on releasing
174 else if(me.pressed == 2)
176 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
177 // item dragging mode
178 // select current one one last time...
179 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
180 // and give it a nice click event
183 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
184 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
190 void updateControlTopBottomListBox(entity me)
193 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
194 if(me.nItems * me.itemHeight <= 1)
196 // we don't need no stinkin' scrollbar, we don't need no view control...
198 me.controlBottom = 1;
203 if(frametime) // only do this in draw frames
205 if(me.dragScrollTimer < time)
209 // if selected item is below listbox, increase scrollpos so it is in
210 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
211 // if selected item is above listbox, decrease scrollpos so it is in
212 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
213 if(me.scrollPos != save)
214 me.dragScrollTimer = time + 0.2;
217 // if scroll pos is below end of list, fix it
218 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
219 // if scroll pos is above beginning of list, fix it
220 me.scrollPos = max(me.scrollPos, 0);
221 // now that we know where the list is scrolled to, find out where to draw the control
222 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
223 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
226 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
227 f = me.controlBottom - me.controlTop;
228 if(f < fmin) // FIXME good default?
230 // f * X + 1 * (1-X) = fmin
231 // (f - 1) * X + 1 = fmin
232 // (f - 1) * X = fmin - 1
233 // X = (fmin - 1) / (f - 1)
234 f = (fmin - 1) / (f - 1);
235 me.controlTop = me.controlTop * f + 0 * (1 - f);
236 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
240 void drawListBox(entity me)
244 vector oldshift, oldscale;
246 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
247 me.updateControlTopBottom(me);
250 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
251 if(me.nItems * me.itemHeight > 1)
254 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
255 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
257 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
259 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
261 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
265 oldshift = draw_shift;
266 oldscale = draw_scale;
267 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
268 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
271 y = i * me.itemHeight - me.scrollPos;
274 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
275 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
276 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
281 void clickListBoxItemListBox(entity me, float i, vector where)
283 // itemclick, itemclick, does whatever itemclick does
286 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
288 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);