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)
172 // slider dragging mode
173 // in that case, nothing happens on releasing
175 else if(me.pressed == 2)
177 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
178 // item dragging mode
179 // select current one one last time...
180 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
181 // and give it a nice click event
184 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
190 void ListBox_focusLeave(entity me)
192 // Reset the var pressed in case listbox loses focus
193 // by a mouse click on an item of the list
194 // for example showing a dialog on right click
197 void ListBox_updateControlTopBottom(entity me)
200 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
201 if(me.nItems * me.itemHeight <= 1)
203 // we don't need no stinkin' scrollbar, we don't need no view control...
205 me.controlBottom = 1;
210 if(frametime) // only do this in draw frames
212 if(me.dragScrollTimer < time)
216 // if selected item is below listbox, increase scrollpos so it is in
217 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
218 // if selected item is above listbox, decrease scrollpos so it is in
219 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
220 if(me.scrollPos != save)
221 me.dragScrollTimer = time + 0.2;
224 // if scroll pos is below end of list, fix it
225 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
226 // if scroll pos is above beginning of list, fix it
227 me.scrollPos = max(me.scrollPos, 0);
228 // now that we know where the list is scrolled to, find out where to draw the control
229 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
230 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
233 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
234 f = me.controlBottom - me.controlTop;
235 if(f < minfactor) // FIXME good default?
237 // f * X + 1 * (1-X) = minfactor
238 // (f - 1) * X + 1 = minfactor
239 // (f - 1) * X = minfactor - 1
240 // X = (minfactor - 1) / (f - 1)
241 f = (minfactor - 1) / (f - 1);
242 me.controlTop = me.controlTop * f + 0 * (1 - f);
243 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
247 void ListBox_draw(entity me)
250 vector absSize, fillSize = '0 0 0';
251 vector oldshift, oldscale;
253 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
254 me.updateControlTopBottom(me);
255 fillSize_x = (1 - me.controlWidth);
257 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
260 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
261 if(me.nItems * me.itemHeight > 1)
264 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
265 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
267 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
269 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
271 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
275 oldshift = draw_shift;
276 oldscale = draw_scale;
277 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
278 draw_scale = boxToGlobalSize(eX * (1 - me.controlWidth) + eY * me.itemHeight, oldscale);
279 for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
282 y = i * me.itemHeight - me.scrollPos;
285 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
286 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
290 draw_shift = oldshift;
291 draw_scale = oldscale;
292 SUPER(ListBox).draw(me);
295 void ListBox_clickListBoxItem(entity me, float i, vector where)
297 // itemclick, itemclick, does whatever itemclick does
300 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
302 draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize_x) + eY * (8 / absSize_y), (selected ? '0 1 0' : '1 1 1'), 1, 0);