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 me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
191 void ListBox_focusLeave(entity me)
193 // Reset the var pressed in case listbox loses focus
194 // by a mouse click on an item of the list
195 // for example showing a dialog on right click
198 void ListBox_updateControlTopBottom(entity me)
201 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
202 if(me.nItems * me.itemHeight <= 1)
204 // we don't need no stinkin' scrollbar, we don't need no view control...
206 me.controlBottom = 1;
211 if(frametime) // only do this in draw frames
213 if(me.dragScrollTimer < time)
217 // if selected item is below listbox, increase scrollpos so it is in
218 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
219 // if selected item is above listbox, decrease scrollpos so it is in
220 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
221 if(me.scrollPos != save)
222 me.dragScrollTimer = time + 0.2;
225 // if scroll pos is below end of list, fix it
226 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
227 // if scroll pos is above beginning of list, fix it
228 me.scrollPos = max(me.scrollPos, 0);
229 // now that we know where the list is scrolled to, find out where to draw the control
230 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
231 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
234 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
235 f = me.controlBottom - me.controlTop;
236 if(f < minfactor) // FIXME good default?
238 // f * X + 1 * (1-X) = minfactor
239 // (f - 1) * X + 1 = minfactor
240 // (f - 1) * X = minfactor - 1
241 // X = (minfactor - 1) / (f - 1)
242 f = (minfactor - 1) / (f - 1);
243 me.controlTop = me.controlTop * f + 0 * (1 - f);
244 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
248 void ListBox_draw(entity me)
251 vector absSize, fillSize;
252 vector oldshift, oldscale;
254 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
255 me.updateControlTopBottom(me);
256 fillSize_x = (1 - me.controlWidth);
258 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
261 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
262 if(me.nItems * me.itemHeight > 1)
265 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
266 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
268 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
270 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
272 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
276 oldshift = draw_shift;
277 oldscale = draw_scale;
278 absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
279 draw_scale = boxToGlobalSize(eX * (1 - me.controlWidth) + eY * me.itemHeight, oldscale);
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 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
291 draw_shift = oldshift;
292 draw_scale = oldscale;
293 SUPER(ListBox).draw(me);
296 void ListBox_clickListBoxItem(entity me, float i, vector where)
298 // itemclick, itemclick, does whatever itemclick does
301 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
303 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);