2 CLASS(ListBox) EXTENDS(Item)
\r
3 METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector))
\r
4 METHOD(ListBox, configureListBox, void(entity, float, float))
\r
5 METHOD(ListBox, draw, void(entity))
\r
6 METHOD(ListBox, keyDown, float(entity, float, float, float))
\r
7 METHOD(ListBox, mousePress, float(entity, vector))
\r
8 METHOD(ListBox, mouseDrag, float(entity, vector))
\r
9 METHOD(ListBox, mouseRelease, float(entity, vector))
\r
10 ATTRIB(ListBox, focusable, float, 1)
\r
11 ATTRIB(ListBox, selectedItem, float, 0)
\r
12 ATTRIB(ListBox, size, vector, '0 0 0')
\r
13 ATTRIB(ListBox, origin, vector, '0 0 0')
\r
14 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
\r
15 ATTRIB(ListBox, previousValue, float, 0)
\r
16 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
\r
17 ATTRIB(ListBox, pressOffset, float, 0)
\r
19 METHOD(ListBox, updateControlTopBottom, void(entity))
\r
20 ATTRIB(ListBox, controlTop, float, 0)
\r
21 ATTRIB(ListBox, controlBottom, float, 0)
\r
22 ATTRIB(ListBox, controlWidth, float, 0)
\r
23 ATTRIB(ListBox, dragScrollTimer, float, 0)
\r
24 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
\r
26 ATTRIB(ListBox, src, string, string_null) // scrollbar
\r
27 ATTRIB(ListBox, color, vector, '1 1 1')
\r
28 ATTRIB(ListBox, color2, vector, '1 1 1')
\r
29 ATTRIB(ListBox, colorC, vector, '1 1 1')
\r
30 ATTRIB(ListBox, colorF, vector, '1 1 1')
\r
31 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
\r
32 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
\r
33 ATTRIB(ListBox, nItems, float, 42)
\r
34 ATTRIB(ListBox, itemHeight, float, 0)
\r
35 ATTRIB(ListBox, colorBG, vector, '0 0 0')
\r
36 ATTRIB(ListBox, alphaBG, float, 0)
\r
37 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
\r
38 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
\r
39 METHOD(ListBox, setSelected, void(entity, float))
\r
43 #ifdef IMPLEMENTATION
\r
44 void setSelectedListBox(entity me, float i)
\r
46 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
\r
48 void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
\r
50 resizeNotifyItem(me, relOrigin, relSize, absOrigin, absSize);
\r
51 me.controlWidth = me.scrollbarWidth / absSize_x;
\r
53 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
\r
55 me.scrollbarWidth = theScrollbarWidth;
\r
56 me.itemHeight = theItemHeight;
\r
58 float keyDownListBox(entity me, float key, float ascii, float shift)
\r
60 me.dragScrollTimer = time;
\r
61 if(key == K_MWHEELUP)
\r
63 me.scrollPos = max(me.scrollPos - 0.5, 0);
\r
64 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
\r
66 else if(key == K_MWHEELDOWN)
\r
68 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
\r
69 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
\r
71 else if(key == K_PGUP)
\r
72 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
\r
73 else if(key == K_PGDN)
\r
74 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
\r
75 else if(key == K_UPARROW)
\r
76 me.setSelected(me, me.selectedItem - 1);
\r
77 else if(key == K_DOWNARROW)
\r
78 me.setSelected(me, me.selectedItem + 1);
\r
79 else if(key == K_HOME)
\r
82 me.setSelected(me, 0);
\r
84 else if(key == K_END)
\r
86 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
\r
87 me.setSelected(me, me.nItems - 1);
\r
93 float mouseDragListBox(entity me, vector pos)
\r
97 me.updateControlTopBottom(me);
\r
98 me.dragScrollPos = pos;
\r
102 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
\r
103 if(pos_y < 0 - me.tolerance_x) hit = 0;
\r
104 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
\r
105 if(pos_y >= 1 + me.tolerance_x) hit = 0;
\r
108 // calculate new pos to v
\r
110 delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
\r
111 me.scrollPos = me.previousValue + delta;
\r
114 me.scrollPos = me.previousValue;
\r
115 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
\r
116 me.scrollPos = max(me.scrollPos, 0);
\r
117 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
\r
118 i = max(i, ceil(me.scrollPos / me.itemHeight));
\r
119 me.setSelected(me, i);
\r
121 else if(me.pressed == 2)
\r
123 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
\r
127 float mousePressListBox(entity me, vector pos)
\r
129 if(pos_x < 0) return 0;
\r
130 if(pos_y < 0) return 0;
\r
131 if(pos_x >= 1) return 0;
\r
132 if(pos_y >= 1) return 0;
\r
133 me.dragScrollPos = pos;
\r
134 me.updateControlTopBottom(me);
\r
135 me.dragScrollTimer = time;
\r
136 if(pos_x >= 1 - me.controlWidth)
\r
138 // if hit, set me.pressed, otherwise scroll by one page
\r
139 if(pos_y < me.controlTop)
\r
142 me.scrollPos = max(me.scrollPos - 1, 0);
\r
143 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
\r
145 else if(pos_y > me.controlBottom)
\r
148 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
\r
149 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
\r
154 me.pressOffset = pos_y;
\r
155 me.previousValue = me.scrollPos;
\r
160 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
\r
162 // an item has been clicked. Select it, ...
\r
163 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
\r
167 float mouseReleaseListBox(entity me, vector pos)
\r
170 if(me.pressed == 1)
\r
172 // slider dragging mode
\r
173 // in that case, nothing happens on releasing
\r
175 else if(me.pressed == 2)
\r
177 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
\r
178 // item dragging mode
\r
179 // select current one one last time...
\r
180 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
\r
181 // and give it a nice click event
\r
184 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
\r
185 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
\r
191 void updateControlTopBottomListBox(entity me)
\r
194 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
\r
195 if(me.nItems * me.itemHeight <= 1)
\r
197 // we don't need no stinkin' scrollbar, we don't need no view control...
\r
199 me.controlBottom = 1;
\r
204 if(frametime) // only do this in draw frames
\r
206 if(me.dragScrollTimer < time)
\r
209 save = me.scrollPos;
\r
210 // if selected item is below listbox, increase scrollpos so it is in
\r
211 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
\r
212 // if selected item is above listbox, decrease scrollpos so it is in
\r
213 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
\r
214 if(me.scrollPos != save)
\r
215 me.dragScrollTimer = time + 0.2;
\r
218 // if scroll pos is below end of list, fix it
\r
219 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
\r
220 // if scroll pos is above beginning of list, fix it
\r
221 me.scrollPos = max(me.scrollPos, 0);
\r
222 // now that we know where the list is scrolled to, find out where to draw the control
\r
223 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
\r
224 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
\r
227 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
\r
228 f = me.controlBottom - me.controlTop;
\r
229 if(f < minfactor) // FIXME good default?
\r
231 // f * X + 1 * (1-X) = minfactor
\r
232 // (f - 1) * X + 1 = minfactor
\r
233 // (f - 1) * X = minfactor - 1
\r
234 // X = (minfactor - 1) / (f - 1)
\r
235 f = (minfactor - 1) / (f - 1);
\r
236 me.controlTop = me.controlTop * f + 0 * (1 - f);
\r
237 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
\r
241 void drawListBox(entity me)
\r
244 vector absSize, fillSize;
\r
245 vector oldshift, oldscale;
\r
246 if(me.pressed == 2)
\r
247 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
\r
248 me.updateControlTopBottom(me);
\r
249 fillSize_x = (1 - me.controlWidth);
\r
251 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
\r
252 if(me.controlWidth)
\r
254 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
\r
255 if(me.nItems * me.itemHeight > 1)
\r
258 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
\r
259 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
\r
260 if(me.pressed == 1)
\r
261 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
\r
262 else if(me.focused)
\r
263 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
\r
265 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
\r
269 oldshift = draw_shift;
\r
270 oldscale = draw_scale;
\r
271 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
\r
272 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
\r
275 y = i * me.itemHeight - me.scrollPos;
\r
278 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
\r
279 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
\r
280 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
\r
285 void clickListBoxItemListBox(entity me, float i, vector where)
\r
287 // itemclick, itemclick, does whatever itemclick does
\r
290 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
\r
292 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);
\r