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 METHOD(ListBox, focusLeave, void(entity))
11 ATTRIB(ListBox, focusable, float, 1)
12 ATTRIB(ListBox, selectedItem, float, 0)
13 ATTRIB(ListBox, size, vector, '0 0 0')
14 ATTRIB(ListBox, origin, vector, '0 0 0')
15 ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
16 ATTRIB(ListBox, previousValue, float, 0)
17 ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
18 ATTRIB(ListBox, pressOffset, float, 0)
20 METHOD(ListBox, updateControlTopBottom, void(entity))
21 ATTRIB(ListBox, controlTop, float, 0)
22 ATTRIB(ListBox, controlBottom, float, 0)
23 ATTRIB(ListBox, controlWidth, float, 0)
24 ATTRIB(ListBox, dragScrollTimer, float, 0)
25 ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
27 ATTRIB(ListBox, src, string, string_null) // scrollbar
28 ATTRIB(ListBox, color, vector, '1 1 1')
29 ATTRIB(ListBox, color2, vector, '1 1 1')
30 ATTRIB(ListBox, colorC, vector, '1 1 1')
31 ATTRIB(ListBox, colorF, vector, '1 1 1')
32 ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
33 ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
34 ATTRIB(ListBox, nItems, float, 42)
35 ATTRIB(ListBox, itemHeight, float, 0)
36 ATTRIB(ListBox, colorBG, vector, '0 0 0')
37 ATTRIB(ListBox, alphaBG, float, 0)
38 METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
39 METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
40 METHOD(ListBox, setSelected, void(entity, float))
45 void ListBox_setSelected(entity me, float i)
47 me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
49 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
51 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
52 me.controlWidth = me.scrollbarWidth / absSize_x;
54 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
56 me.scrollbarWidth = theScrollbarWidth;
57 me.itemHeight = theItemHeight;
59 float ListBox_keyDown(entity me, float key, float ascii, float shift)
61 me.dragScrollTimer = time;
64 me.scrollPos = max(me.scrollPos - 0.5, 0);
65 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
67 else if(key == K_MWHEELDOWN)
69 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
70 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
72 else if(key == K_PGUP || key == K_KP_PGUP)
73 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
74 else if(key == K_PGDN || key == K_KP_PGDN)
75 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
76 else if(key == K_UPARROW || key == K_KP_UPARROW)
77 me.setSelected(me, me.selectedItem - 1);
78 else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
79 me.setSelected(me, me.selectedItem + 1);
80 else if(key == K_HOME || key == K_KP_HOME)
83 me.setSelected(me, 0);
85 else if(key == K_END || key == K_KP_END)
87 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
88 me.setSelected(me, me.nItems - 1);
94 float ListBox_mouseDrag(entity me, vector pos)
98 me.updateControlTopBottom(me);
99 me.dragScrollPos = pos;
103 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
104 if(pos_y < 0 - me.tolerance_x) hit = 0;
105 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
106 if(pos_y >= 1 + me.tolerance_x) hit = 0;
109 // calculate new pos to v
111 d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
112 me.scrollPos = me.previousValue + d;
115 me.scrollPos = me.previousValue;
116 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
117 me.scrollPos = max(me.scrollPos, 0);
118 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
119 i = max(i, ceil(me.scrollPos / me.itemHeight));
120 me.setSelected(me, i);
122 else if(me.pressed == 2)
124 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
128 float ListBox_mousePress(entity me, vector pos)
130 if(pos_x < 0) return 0;
131 if(pos_y < 0) return 0;
132 if(pos_x >= 1) return 0;
133 if(pos_y >= 1) return 0;
134 me.dragScrollPos = pos;
135 me.updateControlTopBottom(me);
136 me.dragScrollTimer = time;
137 if(pos_x >= 1 - me.controlWidth)
139 // if hit, set me.pressed, otherwise scroll by one page
140 if(pos_y < me.controlTop)
143 me.scrollPos = max(me.scrollPos - 1, 0);
144 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
146 else if(pos_y > me.controlBottom)
149 me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
150 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
155 me.pressOffset = pos_y;
156 me.previousValue = me.scrollPos;
161 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
163 // an item has been clicked. Select it, ...
164 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
168 float ListBox_mouseRelease(entity me, vector pos)
173 // slider dragging mode
174 // in that case, nothing happens on releasing
176 else if(me.pressed == 2)
178 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
179 // item dragging mode
180 // select current one one last time...
181 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
182 // and give it a nice click event
185 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
186 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
192 void ListBox_focusLeave(entity me)
194 // Reset the var pressed in case listbox loses focus
195 // by a mouse click on an item of the list
196 // for example showing a dialog on right click
199 void ListBox_updateControlTopBottom(entity me)
202 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
203 if(me.nItems * me.itemHeight <= 1)
205 // we don't need no stinkin' scrollbar, we don't need no view control...
207 me.controlBottom = 1;
212 if(frametime) // only do this in draw frames
214 if(me.dragScrollTimer < time)
218 // if selected item is below listbox, increase scrollpos so it is in
219 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
220 // if selected item is above listbox, decrease scrollpos so it is in
221 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
222 if(me.scrollPos != save)
223 me.dragScrollTimer = time + 0.2;
226 // if scroll pos is below end of list, fix it
227 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
228 // if scroll pos is above beginning of list, fix it
229 me.scrollPos = max(me.scrollPos, 0);
230 // now that we know where the list is scrolled to, find out where to draw the control
231 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
232 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
235 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
236 f = me.controlBottom - me.controlTop;
237 if(f < minfactor) // FIXME good default?
239 // f * X + 1 * (1-X) = minfactor
240 // (f - 1) * X + 1 = minfactor
241 // (f - 1) * X = minfactor - 1
242 // X = (minfactor - 1) / (f - 1)
243 f = (minfactor - 1) / (f - 1);
244 me.controlTop = me.controlTop * f + 0 * (1 - f);
245 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
249 void ListBox_draw(entity me)
252 vector absSize, fillSize;
253 vector oldshift, oldscale;
255 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
256 me.updateControlTopBottom(me);
257 fillSize_x = (1 - me.controlWidth);
259 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
262 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
263 if(me.nItems * me.itemHeight > 1)
266 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
267 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
269 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
271 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
273 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
277 oldshift = draw_shift;
278 oldscale = draw_scale;
279 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
280 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
283 y = i * me.itemHeight - me.scrollPos;
286 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
287 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
288 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
293 void ListBox_clickListBoxItem(entity me, float i, vector where)
295 // itemclick, itemclick, does whatever itemclick does
298 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
300 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);