Xonotic
listbox.qc
Go to the documentation of this file.
1 #include "listbox.qh"
2 
4  {
5  return me.scrollPos != me.scrollPosTarget;
6  }
7 
8  void ListBox_scrollToItem(entity me, int i)
9  {
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
13  {
14  me.needScrollToItem = i;
15  return;
16  }
17 
18  i = bound(0, i, me.nItems - 1);
19 
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))
23  {
24  // above visible area
25  me.scrollPosTarget = me.getItemStart(me, i);
26  }
27  else if (i > me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos))
28  {
29  // below visible area
30  if (i == me.nItems - 1) me.scrollPosTarget = me.getTotalHeight(me) - 1;
31  else me.scrollPosTarget = me.getItemStart(me, i + 1) - 1;
32  }
33  }
34 
35  void ListBox_setSelected(entity me, float i)
36  {
37  i = bound(0, i, me.nItems - 1);
38  me.scrollToItem(me, i);
39  me.selectedItem = i;
40  }
41  void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
42  {
43  SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
44  me.controlWidth = me.scrollbarWidth / absSize.x;
45  }
46  void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
47  {
48  me.scrollbarWidth = theScrollbarWidth;
49  me.itemHeight = theItemHeight;
50  }
51 
53  {
54  return me.nItems * me.itemHeight;
55  }
56  float ListBox_getItemAtPos(entity me, float pos)
57  {
58  return floor(pos / me.itemHeight);
59  }
60  float ListBox_getItemStart(entity me, float i)
61  {
62  return me.itemHeight * i;
63  }
64  float ListBox_getItemHeight(entity me, float i)
65  {
66  return me.itemHeight;
67  }
68 
70  {
71  return me.getItemAtPos(me, pos + 0.999) - 1;
72  }
74  {
75  return me.getItemAtPos(me, pos + 0.001) + 1;
76  }
77  float ListBox_keyDown(entity me, float key, float ascii, float shift)
78  {
79  if (key == K_MWHEELUP)
80  {
81  me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0);
82  }
83  else if (key == K_MWHEELDOWN)
84  {
85  me.scrollPosTarget = min(me.scrollPosTarget + 0.5, max(0, me.getTotalHeight(me) - 1));
86  }
87  else if (key == K_PGUP || key == K_KP_PGUP)
88  {
89  if (me.selectionDoesntMatter)
90  {
91  me.scrollPosTarget = max(me.scrollPosTarget - 0.5, 0);
92  return 1;
93  }
94 
95  float i = me.selectedItem;
96  float a = me.getItemHeight(me, i);
97  for ( ; ; )
98  {
99  --i;
100  if (i < 0) break;
101  a += me.getItemHeight(me, i);
102  if (a >= 1) break;
103  }
104  me.setSelected(me, i + 1);
105  }
106  else if (key == K_PGDN || key == K_KP_PGDN)
107  {
108  if (me.selectionDoesntMatter)
109  {
110  me.scrollPosTarget = min(me.scrollPosTarget + 0.5, me.nItems * me.itemHeight - 1);
111  return 1;
112  }
113 
114  float i = me.selectedItem;
115  float a = me.getItemHeight(me, i);
116  for ( ; ; )
117  {
118  ++i;
119  if (i >= me.nItems) break;
120  a += me.getItemHeight(me, i);
121  if (a >= 1) break;
122  }
123  me.setSelected(me, i - 1);
124  }
125  else if (key == K_UPARROW || key == K_KP_UPARROW)
126  {
127  if (me.selectionDoesntMatter)
128  {
129  me.scrollPosTarget = max(me.scrollPosTarget - me.itemHeight, 0);
130  return 1;
131  }
132 
133  me.setSelected(me, me.selectedItem - 1);
134  }
135  else if (key == K_DOWNARROW || key == K_KP_DOWNARROW)
136  {
137  if (me.selectionDoesntMatter)
138  {
139  me.scrollPosTarget = min(me.scrollPosTarget + me.itemHeight, me.nItems * me.itemHeight - 1);
140  return 1;
141  }
142 
143  me.setSelected(me, me.selectedItem + 1);
144  }
145  else if (key == K_HOME || key == K_KP_HOME)
146  {
147  me.setSelected(me, 0);
148  }
149  else if (key == K_END || key == K_KP_END)
150  {
151  me.setSelected(me, me.nItems - 1);
152  }
153  else
154  {
155  return 0;
156  }
157  return 1;
158  }
160  {
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)
167  {
168  me.mouseMoveOffset = pos.y;
169  }
170  else
171  {
172  me.setFocusedItem(me, -1);
173  me.mouseMoveOffset = -1;
174  }
175  return 1;
176  }
178  {
179  float hit;
180  me.updateControlTopBottom(me);
181  me.dragScrollPos = pos;
182  if (me.pressed == 1)
183  {
184  hit = 1;
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;
189  if (hit)
190  {
191  // calculate new pos to v
192  float d;
193  d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
194  me.scrollPosTarget = me.previousValue + d;
195  }
196  else
197  {
198  me.scrollPosTarget = me.previousValue;
199  }
200  me.scrollPosTarget = min(me.scrollPosTarget, me.getTotalHeight(me) - 1);
201  me.scrollPosTarget = max(me.scrollPosTarget, 0);
202  }
203  else if (me.pressed == 2)
204  {
205  me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
206  me.setFocusedItem(me, me.selectedItem);
207  me.mouseMoveOffset = -1;
208  }
209  return 1;
210  }
211  METHOD(ListBox, mousePress, bool(ListBox this, vector pos))
212  {
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)
220  {
221  // if hit, set this.pressed, otherwise scroll by one page
222  if (pos.y < this.controlTop)
223  {
224  // page up
225  this.scrollPosTarget = max(this.scrollPosTarget - 1, 0);
226  }
227  else if (pos.y > this.controlBottom)
228  {
229  // page down
230  this.scrollPosTarget = min(this.scrollPosTarget + 1, this.getTotalHeight(this) - 1);
231  }
232  else
233  {
234  this.pressed = 1;
235  this.pressOffset = pos.y;
236  this.previousValue = this.scrollPos;
237  }
238  }
239  else
240  {
241  // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
242  this.pressed = 2;
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);
246  }
247  return true;
248  }
249  void ListBox_setFocusedItem(entity me, int item)
250  {
251  float focusedItem_save = me.focusedItem;
252  me.focusedItem = (item < me.nItems) ? item : -1;
253  if (focusedItem_save != me.focusedItem)
254  {
255  me.focusedItemChangeNotify(me);
256  if (me.focusedItem >= 0) me.focusedItemAlpha = SKINALPHA_LISTBOX_FOCUSED;
257  }
258  }
260  {
261  if (me.pressed == 1)
262  {
263  // slider dragging mode
264  // in that case, nothing happens on releasing
265  }
266  else if (me.pressed == 2)
267  {
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
274  if (me.nItems > 0)
275  {
276  vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
277 
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);
280 
281  me.lastClickedItem = me.selectedItem;
282  me.lastClickedTime = time;
283  }
284  }
285  me.pressed = 0;
286  return 1;
287  }
289  {
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
293  me.pressed = 0;
294  me.setFocusedItem(me, -1);
295  me.mouseMoveOffset = -1;
296  }
298  {
299  float f;
300  // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
301  if (me.getTotalHeight(me) <= 1)
302  {
303  // we don't need no stinkin' scrollbar, we don't need no view control...
304  me.controlTop = 0;
305  me.controlBottom = 1;
306  me.scrollPos = 0;
307  }
308  else
309  {
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);
317 
318  float minfactor;
319  minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
320  f = me.controlBottom - me.controlTop;
321  if (f < minfactor) // FIXME good default?
322  {
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);
330  }
331  }
332  }
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");
337  {
338  vector fillSize = '0 0 0';
339 
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));
342 
343  if (me.needScrollToItem >= 0)
344  {
345  me.scrollToItem(me, me.needScrollToItem);
346  me.needScrollToItem = -1;
347  }
348  if (me.scrollPos != me.scrollPosTarget)
349  {
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;
357  }
358 
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);
363  if (me.controlWidth)
364  {
365  draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
366  if (me.getTotalHeight(me) > 1)
367  {
368  vector o, s;
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);
374  }
375  }
376  draw_SetClip();
377  vector oldshift = draw_shift;
378  vector oldscale = draw_scale;
379 
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)
383  {
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));
389  y += relSize.y;
390  }
391  draw_ClearClip();
392 
393  draw_shift = oldshift;
394  draw_scale = oldscale;
395  SUPER(ListBox).draw(me);
396  }
397 
399  {}
400 
401  void ListBox_clickListBoxItem(entity me, float i, vector where)
402  {
403  // template method
404  }
405 
407  {
408  // template method
409  }
410 
411  void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
412  {
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);
414  }
float K_UPARROW
Definition: keycodes.qc:15
void ListBox_clickListBoxItem(entity me, float i, vector where)
Definition: listbox.qc:401
float ListBox_keyDown(entity me, float key, float ascii, float shift)
Definition: listbox.qc:77
const vector eY
Definition: vector.qh:45
float K_DOWNARROW
Definition: keycodes.qc:16
float K_HOME
Definition: keycodes.qc:41
void ListBox_scrollToItem(entity me, int i)
Definition: listbox.qc:8
float K_KP_DOWNARROW
Definition: keycodes.qc:53
void ListBox_updateControlTopBottom(entity me)
Definition: listbox.qc:297
void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
Definition: listbox.qc:411
float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
Definition: listbox.qc:69
entity() spawn
float ListBox_mouseDrag(entity me, vector pos)
Definition: listbox.qc:177
bool ListBox_isScrolling(entity me)
Definition: listbox.qc:3
float K_KP_PGDN
Definition: keycodes.qc:55
#define METHOD(cname, name, prototype)
Definition: oo.qh:257
void ListBox_focusedItemChangeNotify(entity me)
Definition: listbox.qc:398
void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
Definition: listbox.qc:46
float ListBox_getItemAtPos(entity me, float pos)
Definition: listbox.qc:56
void ListBox_setFocusedItem(entity me, int item)
Definition: listbox.qc:249
void ListBox_draw(entity me)
Definition: listbox.qc:336
float exp(float e)
Definition: mathlib.qc:73
#define SUPER(cname)
Definition: oo.qh:219
float ListBox_getTotalHeight(entity me)
Definition: listbox.qc:52
float ListBox_getItemHeight(entity me, float i)
Definition: listbox.qc:64
float ListBox_mouseMove(entity me, vector pos)
Definition: listbox.qc:159
void ListBox_setSelected(entity me, float i)
Definition: listbox.qc:35
float ListBox_mouseRelease(entity me, vector pos)
Definition: listbox.qc:259
float K_PGDN
Definition: keycodes.qc:39
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
Definition: listbox.qc:41
float frametime
Definition: csprogsdefs.qc:17
void ListBox_focusLeave(entity me)
Definition: listbox.qc:288
void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
Definition: listbox.qc:406
float K_END
Definition: keycodes.qc:42
float K_MWHEELDOWN
Definition: keycodes.qc:133
vector(float skel, float bonenum) _skel_get_boneabs_hidden
const vector eX
Definition: vector.qh:44
float K_KP_HOME
Definition: keycodes.qc:62
bool pressed
Definition: picker.qc:3
float ListBox_getItemStart(entity me, float i)
Definition: listbox.qc:60
float K_KP_END
Definition: keycodes.qc:51
float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
Definition: listbox.qc:73
float time
Definition: csprogsdefs.qc:16
float K_KP_PGUP
Definition: keycodes.qc:66
float K_PGUP
Definition: keycodes.qc:40
float K_KP_UPARROW
Definition: keycodes.qc:64
float K_MWHEELUP
Definition: keycodes.qc:132
AUTOCVAR(menu_scroll_averaging_time, float, 0.16, "smooth scroll averaging time")