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, tolerance, vector, '0 0 0') // drag tolerance
27 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
28 ATTRIB(ListBox, nItems, float, 42)
29 ATTRIB(ListBox, itemHeight, float, 0)
30 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
31 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
32 METHOD(ListBox, setSelected, void(entity, float))
37 void setSelectedListBox(entity me, float i)
39 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
41 void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
44 me.controlWidth = me.scrollbarWidth / absSize_x;
46 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
48 me.scrollbarWidth = theScrollbarWidth;
49 me.itemHeight = theItemHeight;
51 float keyDownListBox(entity me, float key, float ascii, float shift)
53 me.dragScrollTimer = 0;
56 me.scrollPos = max(me.scrollPos - 0.5, 0);
57 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
59 else if(key == K_MWHEELDOWN)
61 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
62 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
64 else if(key == K_PGUP)
65 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
66 else if(key == K_PGDN)
67 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
68 else if(key == K_UPARROW)
69 me.setSelected(me, me.selectedItem - 1);
70 else if(key == K_DOWNARROW)
71 me.setSelected(me, me.selectedItem + 1);
72 else if(key == K_HOME)
73 me.setSelected(me, 0);
75 me.setSelected(me, me.nItems - 1);
80 float mouseDragListBox(entity me, vector pos)
84 me.updateControlTopBottom(me);
85 me.dragScrollPos = pos;
89 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
90 if(pos_y < 0 - me.tolerance_x) hit = 0;
91 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
92 if(pos_y >= 1 + me.tolerance_x) hit = 0;
95 // calculate new pos to v
97 delta = (pos_y - me.pressOffset) / (1 - 1 / (me.nItems * me.itemHeight)) * (me.nItems * me.itemHeight - 1);
98 me.scrollPos = me.previousValue + delta;
101 me.scrollPos = me.previousValue;
102 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
103 me.scrollPos = max(me.scrollPos, 0);
104 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
105 i = max(i, ceil(me.scrollPos / me.itemHeight));
106 me.setSelected(me, i);
108 else if(me.pressed == 2)
110 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
114 float mousePressListBox(entity me, vector pos)
116 if(pos_x < 0) return 0;
117 if(pos_y < 0) return 0;
118 if(pos_x >= 1) return 0;
119 if(pos_y >= 1) return 0;
120 me.dragScrollPos = pos;
121 me.updateControlTopBottom(me);
122 me.dragScrollTimer = 0;
123 if(pos_x >= 1 - me.controlWidth)
125 // if hit, set me.pressed, otherwise scroll by one page
126 if(pos_y < me.controlTop)
129 me.scrollPos = max(me.scrollPos - 1, 0);
130 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
132 else if(pos_y > me.controlBottom)
135 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
136 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
141 me.pressOffset = pos_y;
142 me.previousValue = me.scrollPos;
147 // an item has been clicked. Select it, ...
148 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
149 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
154 float mouseReleaseListBox(entity me, vector pos)
159 // slider dragging mode
160 // in that case, nothing happens on releasing
162 else if(me.pressed == 2)
164 // item dragging mode
165 // select current one one last time...
166 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
167 // and give it a nice click event
170 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY);
171 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eX * (me.selectedItem * me.itemHeight - me.scrollPos), absSize));
177 void updateControlTopBottomListBox(entity me)
179 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
180 if(me.nItems * me.itemHeight <= 1)
182 // we don't need no stinkin' scrollbar, we don't need no view control...
184 me.controlBottom = 1;
189 if(frametime) // only do this in draw frames
191 me.dragScrollTimer -= frametime;
192 if(me.dragScrollTimer < 0)
196 // if selected item is below listbox, increase scrollpos so it is in
197 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
198 // if selected item is above listbox, decrease scrollpos so it is in
199 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
200 if(me.scrollPos != save)
201 me.dragScrollTimer = 0.2;
204 // if scroll pos is below end of list, fix it
205 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
206 // if scroll pos is above beginning of list, fix it
207 me.scrollPos = max(me.scrollPos, 0);
208 // now that we know where the list is scrolled to, find out where to draw the control
209 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
210 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
213 void drawListBox(entity me)
217 vector oldshift, oldscale;
219 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
220 me.updateControlTopBottom(me);
221 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, '1 1 1', 1);
222 if(me.nItems * me.itemHeight > 1)
225 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
226 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
228 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, '1 1 1', 1);
230 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, '1 1 1', 1);
232 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, '1 1 1', 1);
235 oldshift = draw_shift;
236 oldscale = draw_scale;
237 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
238 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
241 y = i * me.itemHeight - me.scrollPos;
244 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
245 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
246 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
251 void clickListBoxItemListBox(entity me, float i, vector where)
253 // itemclick, itemclick, does whatever itemclick does
256 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
258 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);