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)
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 // an item has been clicked. Select it, ...
152 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
153 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
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 // item dragging mode
169 // select current one one last time...
170 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
171 // and give it a nice click event
174 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
175 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
181 void updateControlTopBottomListBox(entity me)
184 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
185 if(me.nItems * me.itemHeight <= 1)
187 // we don't need no stinkin' scrollbar, we don't need no view control...
189 me.controlBottom = 1;
194 if(frametime) // only do this in draw frames
196 me.dragScrollTimer -= frametime;
197 if(me.dragScrollTimer < 0)
201 // if selected item is below listbox, increase scrollpos so it is in
202 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
203 // if selected item is above listbox, decrease scrollpos so it is in
204 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
205 if(me.scrollPos != save)
206 me.dragScrollTimer = 0.2;
209 // if scroll pos is below end of list, fix it
210 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
211 // if scroll pos is above beginning of list, fix it
212 me.scrollPos = max(me.scrollPos, 0);
213 // now that we know where the list is scrolled to, find out where to draw the control
214 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
215 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
218 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
219 f = me.controlBottom - me.controlTop;
220 if(f < fmin) // FIXME good default?
222 // f * X + 1 * (1-X) = fmin
223 // (f - 1) * X + 1 = fmin
224 // (f - 1) * X = fmin - 1
225 // X = (fmin - 1) / (f - 1)
226 f = (fmin - 1) / (f - 1);
227 me.controlTop = me.controlTop * f + 0 * (1 - f);
228 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
232 void drawListBox(entity me)
236 vector oldshift, oldscale;
238 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
239 me.updateControlTopBottom(me);
240 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
241 if(me.nItems * me.itemHeight > 1)
244 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
245 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
247 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
249 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
251 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
254 oldshift = draw_shift;
255 oldscale = draw_scale;
256 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
257 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
260 y = i * me.itemHeight - me.scrollPos;
263 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
264 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
265 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
270 void clickListBoxItemListBox(entity me, float i, vector where)
272 // itemclick, itemclick, does whatever itemclick does
275 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
277 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);