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, 0)
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
36 void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
39 me.controlWidth = me.scrollbarWidth / absSize_x;
41 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
43 me.scrollbarWidth = theScrollbarWidth;
44 me.itemHeight = theItemHeight;
46 float keyDownListBox(entity me, float key, float ascii, float shift)
48 if(key == K_DOWNARROW)
50 else if(key == K_UPARROW)
52 else if(key == K_MWHEELUP)
54 me.scrollPos = max(me.scrollPos - 0.5, 0);
55 me.selectedItem = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
57 else if(key == K_MWHEELDOWN)
59 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
60 me.selectedItem = max(me.selectedItem, ceil(me.scrollPos / me.itemHeight));
62 else if(key == K_PGUP)
63 me.selectedItem = me.selectedItem - 1 / me.itemHeight;
64 else if(key == K_PGDN)
65 me.selectedItem = me.selectedItem + 1 / me.itemHeight;
66 else if(key == K_HOME)
69 me.selectedItem = me.nItems - 1;
72 me.selectedItem = floor(0.5 + bound(0, me.selectedItem, me.nItems - 1));
75 float mouseDragListBox(entity me, vector pos)
78 me.updateControlTopBottom(me);
79 me.dragScrollPos = pos;
83 if(pos_x < 1 - me.controlWidth - me.tolerance_x) hit = 0;
84 if(pos_y < 0 - me.tolerance_y) hit = 0;
85 if(pos_x >= 1 + me.tolerance_x) hit = 0;
86 if(pos_y >= 1 + me.tolerance_y) hit = 0;
89 // calculate new pos to v
91 delta = (pos_y - me.pressOffset) / (1 - 1 / (me.nItems * me.itemHeight)) * (me.nItems * me.itemHeight - 1);
92 me.scrollPos = me.previousValue + delta;
95 me.scrollPos = me.previousValue;
96 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
97 me.scrollPos = max(me.scrollPos, 0);
98 me.selectedItem = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
99 me.selectedItem = max(me.selectedItem, ceil(me.scrollPos / me.itemHeight));
101 else if(me.pressed == 2)
103 me.selectedItem = (me.scrollPos + pos_y) / me.itemHeight;
104 me.selectedItem = floor(0.5 + bound(0, me.selectedItem, me.nItems - 1));
108 float mousePressListBox(entity me, vector pos)
110 if(pos_x < 0) return 0;
111 if(pos_y < 0) return 0;
112 if(pos_x >= 1) return 0;
113 if(pos_y >= 1) return 0;
114 me.updateControlTopBottom(me);
115 if(pos_x >= 1 - me.controlWidth)
117 // if hit, set me.pressed, otherwise scroll by one page
118 if(pos_y < me.controlTop)
121 me.scrollPos = max(me.scrollPos - 1, 0);
122 me.selectedItem = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
124 else if(pos_y > me.controlBottom)
127 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
128 me.selectedItem = max(me.selectedItem, ceil(me.scrollPos / me.itemHeight));
133 me.pressOffset = pos_y;
134 me.previousValue = me.scrollPos;
139 // an item has been clicked. Select it, ...
140 me.selectedItem = (me.scrollPos + pos_y) / me.itemHeight;
141 me.selectedItem = floor(0.5 + bound(0, me.selectedItem, me.nItems - 1));
142 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
147 float mouseReleaseListBox(entity me, vector pos)
152 // slider dragging mode
153 // in that case, nothing happens on releasing
155 else if(me.pressed == 2)
157 // item dragging mode
158 // select current one one last time...
159 me.selectedItem = (me.scrollPos + pos_y) / me.itemHeight;
160 me.selectedItem = floor(0.5 + bound(0, me.selectedItem, me.nItems - 1));
161 // and give it a nice click event
164 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY);
165 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eX * (me.selectedItem * me.itemHeight - me.scrollPos), absSize));
171 void updateControlTopBottomListBox(entity me)
173 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
174 if(me.nItems * me.itemHeight <= 1)
176 // we don't need no stinkin' scrollbar, we don't need no view control...
178 me.controlBottom = 1;
183 if(frametime) // only do this in draw frames
185 me.dragScrollTimer -= frametime;
186 if(me.dragScrollTimer < 0)
190 // if selected item is below listbox, increase scrollpos so it is in
191 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
192 // if selected item is above listbox, decrease scrollpos so it is in
193 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
194 if(me.scrollPos != save)
195 me.dragScrollTimer = 0.2;
198 // if scroll pos is below end of list, fix it
199 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
200 // if scroll pos is above beginning of list, fix it
201 me.scrollPos = max(me.scrollPos, 0);
202 // now that we know where the list is scrolled to, find out where to draw the control
203 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
204 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
207 void drawListBox(entity me)
211 vector oldshift, oldscale;
213 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
214 me.updateControlTopBottom(me);
215 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, '1 1 1', 1);
216 if(me.nItems * me.itemHeight > 1)
219 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
220 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
222 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, '1 1 1', 1);
224 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, '1 1 1', 1);
226 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, '1 1 1', 1);
229 oldshift = draw_shift;
230 oldscale = draw_scale;
231 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
232 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
235 y = i * me.itemHeight - me.scrollPos;
238 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
239 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
240 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
245 void clickListBoxItemListBox(entity me, float i, vector where)
247 // itemclick, itemclick, does whatever itemclick does
250 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
252 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);