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, "") // 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 if(key == K_DOWNARROW)
55 else if(key == K_UPARROW)
57 else if(key == K_MWHEELUP)
59 me.scrollPos = max(me.scrollPos - 0.5, 0);
60 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
62 else if(key == K_MWHEELDOWN)
64 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
65 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
67 else if(key == K_PGUP)
68 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
69 else if(key == K_PGDN)
70 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
71 else if(key == K_HOME)
72 me.setSelected(me, 0);
74 me.setSelected(me, me.nItems - 1);
79 float mouseDragListBox(entity me, vector pos)
83 me.updateControlTopBottom(me);
84 me.dragScrollPos = pos;
88 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
89 if(pos_y < 0 - me.tolerance_x) hit = 0;
90 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
91 if(pos_y >= 1 + me.tolerance_x) hit = 0;
94 // calculate new pos to v
96 delta = (pos_y - me.pressOffset) / (1 - 1 / (me.nItems * me.itemHeight)) * (me.nItems * me.itemHeight - 1);
97 me.scrollPos = me.previousValue + delta;
100 me.scrollPos = me.previousValue;
101 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
102 me.scrollPos = max(me.scrollPos, 0);
103 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
104 i = max(i, ceil(me.scrollPos / me.itemHeight));
105 me.setSelected(me, i);
107 else if(me.pressed == 2)
109 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
113 float mousePressListBox(entity me, vector pos)
115 if(pos_x < 0) return 0;
116 if(pos_y < 0) return 0;
117 if(pos_x >= 1) return 0;
118 if(pos_y >= 1) return 0;
119 me.dragScrollPos = pos;
120 me.updateControlTopBottom(me);
121 if(pos_x >= 1 - me.controlWidth)
123 // if hit, set me.pressed, otherwise scroll by one page
124 if(pos_y < me.controlTop)
127 me.scrollPos = max(me.scrollPos - 1, 0);
128 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
130 else if(pos_y > me.controlBottom)
133 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
134 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
139 me.pressOffset = pos_y;
140 me.previousValue = me.scrollPos;
145 // an item has been clicked. Select it, ...
146 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
147 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
152 float mouseReleaseListBox(entity me, vector pos)
157 // slider dragging mode
158 // in that case, nothing happens on releasing
160 else if(me.pressed == 2)
162 // item dragging mode
163 // select current one one last time...
164 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
165 // and give it a nice click event
168 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY);
169 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eX * (me.selectedItem * me.itemHeight - me.scrollPos), absSize));
175 void updateControlTopBottomListBox(entity me)
177 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
178 if(me.nItems * me.itemHeight <= 1)
180 // we don't need no stinkin' scrollbar, we don't need no view control...
182 me.controlBottom = 1;
187 if(frametime) // only do this in draw frames
189 me.dragScrollTimer -= frametime;
190 if(me.dragScrollTimer < 0)
194 // if selected item is below listbox, increase scrollpos so it is in
195 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
196 // if selected item is above listbox, decrease scrollpos so it is in
197 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
198 if(me.scrollPos != save)
199 me.dragScrollTimer = 0.2;
202 // if scroll pos is below end of list, fix it
203 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
204 // if scroll pos is above beginning of list, fix it
205 me.scrollPos = max(me.scrollPos, 0);
206 // now that we know where the list is scrolled to, find out where to draw the control
207 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
208 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
211 void drawListBox(entity me)
215 vector oldshift, oldscale;
217 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
218 me.updateControlTopBottom(me);
219 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, '1 1 1', 1);
220 if(me.nItems * me.itemHeight > 1)
223 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
224 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
226 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, '1 1 1', 1);
228 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, '1 1 1', 1);
230 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, '1 1 1', 1);
233 oldshift = draw_shift;
234 oldscale = draw_scale;
235 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
236 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
239 y = i * me.itemHeight - me.scrollPos;
242 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
243 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
244 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
249 void clickListBoxItemListBox(entity me, float i, vector where)
251 // itemclick, itemclick, does whatever itemclick does
254 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
256 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);