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 - 1 / (me.nItems * me.itemHeight)) * (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);
175 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eX * (me.selectedItem * me.itemHeight - me.scrollPos), absSize));
181 void updateControlTopBottomListBox(entity me)
183 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
184 if(me.nItems * me.itemHeight <= 1)
186 // we don't need no stinkin' scrollbar, we don't need no view control...
188 me.controlBottom = 1;
193 if(frametime) // only do this in draw frames
195 me.dragScrollTimer -= frametime;
196 if(me.dragScrollTimer < 0)
200 // if selected item is below listbox, increase scrollpos so it is in
201 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
202 // if selected item is above listbox, decrease scrollpos so it is in
203 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
204 if(me.scrollPos != save)
205 me.dragScrollTimer = 0.2;
208 // if scroll pos is below end of list, fix it
209 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
210 // if scroll pos is above beginning of list, fix it
211 me.scrollPos = max(me.scrollPos, 0);
212 // now that we know where the list is scrolled to, find out where to draw the control
213 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
214 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
217 void drawListBox(entity me)
221 vector oldshift, oldscale;
223 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
224 me.updateControlTopBottom(me);
225 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
226 if(me.nItems * me.itemHeight > 1)
229 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
230 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
232 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
234 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
236 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
239 oldshift = draw_shift;
240 oldscale = draw_scale;
241 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
242 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
245 y = i * me.itemHeight - me.scrollPos;
248 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
249 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
250 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
255 void clickListBoxItemListBox(entity me, float i, vector where)
257 // itemclick, itemclick, does whatever itemclick does
260 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
262 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);