3 bool ListBox_isScrolling(entity me)
5 return me.scrollPos != me.scrollPosTarget;
8 void ListBox_scrollToItem(entity me, int i)
10 // scroll doesn't work properly until itemHeight is set to the correct value
11 // at the first resizeNotify call
12 if (me.itemHeight == 1) // initial temporary value of itemHeight is 1
14 me.needScrollToItem = i;
18 i = bound(0, i, me.nItems - 1);
20 // scroll the list to make sure the selected item is visible
21 // (even if the selected item doesn't change).
22 if (i < me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos))
25 me.scrollPosTarget = me.getItemStart(me, i);
27 else if (i > me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos))
30 if (i == me.nItems - 1) me.scrollPosTarget = me.getTotalHeight(me) - 1;
31 else me.scrollPosTarget = me.getItemStart(me, i + 1) - 1;
35 void ListBox_setSelected(entity me, float i)
37 i = bound(0, i, me.nItems - 1);
38 me.scrollToItem(me, i);
41 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
43 SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
44 me.controlWidth = me.scrollbarWidth / absSize.x;
46 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
48 me.scrollbarWidth = theScrollbarWidth;
49 me.itemHeight = theItemHeight;
52 float ListBox_getTotalHeight(entity me)
54 return me.nItems * me.itemHeight;
56 float ListBox_getItemAtPos(entity me, float pos)
58 return floor(pos / me.itemHeight);
60 float ListBox_getItemStart(entity me, float i)
62 return me.itemHeight * i;
64 float ListBox_getItemHeight(entity me, float i)
69 float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
71 return me.getItemAtPos(me, pos + 0.999) - 1;
73 float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
75 return me.getItemAtPos(me, pos + 0.001) + 1;
77 float ListBox_keyDown(entity me, float key, float ascii, float shift)
79 if (key == K_MWHEELUP)
81 me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0);
83 else if (key == K_MWHEELDOWN)
85 me.scrollPosTarget = min(me.scrollPosTarget + 0.5, max(0, me.getTotalHeight(me) - 1));
87 else if (key == K_PGUP || key == K_KP_PGUP)
89 if (me.selectionDoesntMatter)
91 me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0);
95 float i = me.selectedItem;
96 float a = me.getItemHeight(me, i);
101 a += me.getItemHeight(me, i);
104 me.setSelected(me, i + 1);
106 else if (key == K_PGDN || key == K_KP_PGDN)
108 if (me.selectionDoesntMatter)
110 me.scrollPosTarget = min(me.scrollPosTarget + 0.5, me.nItems * me.itemHeight - 1);
114 float i = me.selectedItem;
115 float a = me.getItemHeight(me, i);
119 if (i >= me.nItems) break;
120 a += me.getItemHeight(me, i);
123 me.setSelected(me, i - 1);
125 else if (key == K_UPARROW || key == K_KP_UPARROW)
127 if (me.selectionDoesntMatter)
129 me.scrollPosTarget = max(me.scrollPosTarget - me.itemHeight, 0);
133 me.setSelected(me, me.selectedItem - 1);
135 else if (key == K_DOWNARROW || key == K_KP_DOWNARROW)
137 if (me.selectionDoesntMatter)
139 me.scrollPosTarget = min(me.scrollPosTarget + me.itemHeight, me.nItems * me.itemHeight - 1);
143 me.setSelected(me, me.selectedItem + 1);
145 else if (key == K_HOME || key == K_KP_HOME)
147 me.setSelected(me, 0);
149 else if (key == K_END || key == K_KP_END)
151 me.setSelected(me, me.nItems - 1);
159 float ListBox_mouseMove(entity me, vector pos)
161 me.mouseMoveOffset = -1;
162 if (pos_x < 0) return 0;
163 if (pos_y < 0) return 0;
164 if (pos_x >= 1) return 0;
165 if (pos_y >= 1) return 0;
166 if (pos_x < 1 - me.controlWidth)
168 me.mouseMoveOffset = pos.y;
172 me.setFocusedItem(me, -1);
173 me.mouseMoveOffset = -1;
177 float ListBox_mouseDrag(entity me, vector pos)
180 me.updateControlTopBottom(me);
181 me.dragScrollPos = pos;
185 if (pos.x < 1 - me.controlWidth - me.tolerance.x * me.controlWidth) hit = 0;
186 if (pos.y < 0 - me.tolerance.y) hit = 0;
187 if (pos.x >= 1 + me.tolerance.x * me.controlWidth) hit = 0;
188 if (pos.y >= 1 + me.tolerance.y) hit = 0;
191 // calculate new pos to v
193 d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
194 me.scrollPosTarget = me.previousValue + d;
198 me.scrollPosTarget = me.previousValue;
200 me.scrollPosTarget = min(me.scrollPosTarget, me.getTotalHeight(me) - 1);
201 me.scrollPosTarget = max(me.scrollPosTarget, 0);
203 else if (me.pressed == 2)
205 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
206 me.setFocusedItem(me, me.selectedItem);
207 me.mouseMoveOffset = -1;
211 METHOD(ListBox, mousePress, bool(ListBox this, vector pos))
213 if (pos.x < 0) return false;
214 if (pos.y < 0) return false;
215 if (pos.x >= 1) return false;
216 if (pos.y >= 1) return false;
217 this.dragScrollPos = pos;
218 this.updateControlTopBottom(this);
219 if (pos.x >= 1 - this.controlWidth)
221 // if hit, set this.pressed, otherwise scroll by one page
222 if (pos.y < this.controlTop)
225 this.scrollPosTarget = max(this.scrollPosTarget - 1, 0);
227 else if (pos.y > this.controlBottom)
230 this.scrollPosTarget = min(this.scrollPosTarget + 1, this.getTotalHeight(this) - 1);
235 this.pressOffset = pos.y;
236 this.previousValue = this.scrollPos;
241 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
243 // an item has been clicked. Select it, ...
244 this.setSelected(this, this.getItemAtPos(this, this.scrollPos + pos.y));
245 this.setFocusedItem(this, this.selectedItem);
249 void ListBox_setFocusedItem(entity me, int item)
251 float focusedItem_save = me.focusedItem;
252 me.focusedItem = (item < me.nItems) ? item : -1;
253 if (focusedItem_save != me.focusedItem)
255 me.focusedItemChangeNotify(me);
256 if (me.focusedItem >= 0) me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED;
259 float ListBox_mouseRelease(entity me, vector pos)
263 // slider dragging mode
264 // in that case, nothing happens on releasing
266 else if (me.pressed == 2)
268 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
269 // item dragging mode
270 // select current one one last time...
271 me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
272 me.setFocusedItem(me, me.selectedItem);
273 // and give it a nice click event
276 vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
278 if ((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3)) me.doubleClickListBoxItem(me, me.selectedItem, where);
279 else me.clickListBoxItem(me, me.selectedItem, where);
281 me.lastClickedItem = me.selectedItem;
282 me.lastClickedTime = time;
288 void ListBox_focusLeave(entity me)
290 // Reset the var pressed in case listbox loses focus
291 // by a mouse click on an item of the list
292 // for example showing a dialog on right click
294 me.setFocusedItem(me, -1);
295 me.mouseMoveOffset = -1;
297 void ListBox_updateControlTopBottom(entity me)
300 // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
301 if (me.getTotalHeight(me) <= 1)
303 // we don't need no stinkin' scrollbar, we don't need no view control...
305 me.controlBottom = 1;
310 // if scroll pos is below end of list, fix it
311 me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
312 // if scroll pos is above beginning of list, fix it
313 me.scrollPos = max(me.scrollPos, 0);
314 // now that we know where the list is scrolled to, find out where to draw the control
315 me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
316 me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
319 minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
320 f = me.controlBottom - me.controlTop;
321 if (f < minfactor) // FIXME good default?
323 // f * X + 1 * (1-X) = minfactor
324 // (f - 1) * X + 1 = minfactor
325 // (f - 1) * X = minfactor - 1
326 // X = (minfactor - 1) / (f - 1)
327 f = (minfactor - 1) / (f - 1);
328 me.controlTop = me.controlTop * f + 0 * (1 - f);
329 me.controlBottom = me.controlBottom * f + 1 * (1 - f);
333 AUTOCVAR(menu_scroll_averaging_time, float, 0.16, "smooth scroll averaging time");
334 // scroll faster while dragging the scrollbar
335 AUTOCVAR(menu_scroll_averaging_time_pressed, float, 0.06, "smooth scroll averaging time when dragging the scrollbar");
336 void ListBox_draw(entity me)
338 vector fillSize = '0 0 0';
340 // we can't do this in mouseMove as the list can scroll without moving the cursor
341 if (me.mouseMoveOffset != -1) me.setFocusedItem(me, me.getItemAtPos(me, me.scrollPos + me.mouseMoveOffset));
343 if (me.needScrollToItem >= 0)
345 me.scrollToItem(me, me.needScrollToItem);
346 me.needScrollToItem = -1;
348 if (me.scrollPos != me.scrollPosTarget)
350 float averaging_time = (me.pressed == 1)
351 ? autocvar_menu_scroll_averaging_time_pressed
352 : autocvar_menu_scroll_averaging_time;
353 // this formula works with whatever framerate
354 float f = averaging_time ? exp(-frametime / averaging_time) : 0;
355 me.scrollPos = me.scrollPos * f + me.scrollPosTarget * (1 - f);
356 if (fabs(me.scrollPos - me.scrollPosTarget) < 0.001) me.scrollPos = me.scrollPosTarget;
359 if (me.pressed == 2) me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
360 me.updateControlTopBottom(me);
361 fillSize.x = (1 - me.controlWidth);
362 if (me.alphaBG) draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
365 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
366 if (me.getTotalHeight(me) > 1)
369 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
370 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
371 if (me.pressed == 1) draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
372 else if (me.focused) draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
373 else draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
377 vector oldshift = draw_shift;
378 vector oldscale = draw_scale;
380 int i = me.getItemAtPos(me, me.scrollPos);
381 float y = me.getItemStart(me, i) - me.scrollPos;
382 for ( ; i < me.nItems && y < 1; ++i)
384 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
385 vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
386 vector absSize = boxToGlobalSize(relSize, me.size);
387 draw_scale = boxToGlobalSize(relSize, oldscale);
388 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i));
393 draw_shift = oldshift;
394 draw_scale = oldscale;
395 SUPER(ListBox).draw(me);
398 void ListBox_focusedItemChangeNotify(entity me)
401 void ListBox_clickListBoxItem(entity me, float i, vector where)
406 void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
411 void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
413 draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize.x) + eY * (8 / absSize.y), (isSelected ? '0 1 0' : '1 1 1'), 1, 0);