]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/item/listbox.qc
Merge branch 'master' into terencehill/dynamic_hud
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / item / listbox.qc
1 #include "listbox.qh"
2
3         bool ListBox_isScrolling(entity me)
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
52         float ListBox_getTotalHeight(entity me)
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
69         float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
70         {
71                 return me.getItemAtPos(me, pos + 0.999) - 1;
72         }
73         float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
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         }
159         float ListBox_mouseMove(entity me, vector pos)
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         }
177         float ListBox_mouseDrag(entity me, vector pos)
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.y * me.controlWidth) hit = 0;
186                         if (pos.y < 0 - me.tolerance.x) hit = 0;
187                         if (pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
188                         if (pos.y >= 1 + me.tolerance.x) 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         float ListBox_mousePress(entity me, vector pos)
212         {
213                 if (pos.x < 0) return 0;
214                 if (pos.y < 0) return 0;
215                 if (pos.x >= 1) return 0;
216                 if (pos.y >= 1) return 0;
217                 me.dragScrollPos = pos;
218                 me.updateControlTopBottom(me);
219                 if (pos.x >= 1 - me.controlWidth)
220                 {
221                         // if hit, set me.pressed, otherwise scroll by one page
222                         if (pos.y < me.controlTop)
223                         {
224                                 // page up
225                                 me.scrollPosTarget = max(me.scrollPosTarget - 1, 0);
226                         }
227                         else if (pos.y > me.controlBottom)
228                         {
229                                 // page down
230                                 me.scrollPosTarget = min(me.scrollPosTarget + 1, me.getTotalHeight(me) - 1);
231                         }
232                         else
233                         {
234                                 me.pressed = 1;
235                                 me.pressOffset = pos.y;
236                                 me.previousValue = me.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                         me.pressed = 2;
243                         // an item has been clicked. Select it, ...
244                         me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
245                         me.setFocusedItem(me, me.selectedItem);
246                 }
247                 return 1;
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         }
259         float ListBox_mouseRelease(entity me, vector pos)
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         }
288         void ListBox_focusLeave(entity me)
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         }
297         void ListBox_updateControlTopBottom(entity me)
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");
336         void ListBox_draw(entity me)
337         {
338                 float i;
339                 vector absSize, fillSize = '0 0 0';
340                 vector oldshift, oldscale;
341
342                 // we can't do this in mouseMove as the list can scroll without moving the cursor
343                 if (me.mouseMoveOffset != -1) me.setFocusedItem(me, me.getItemAtPos(me, me.scrollPos + me.mouseMoveOffset));
344
345                 if (me.needScrollToItem >= 0)
346                 {
347                         me.scrollToItem(me, me.needScrollToItem);
348                         me.needScrollToItem = -1;
349                 }
350                 if (me.scrollPos != me.scrollPosTarget)
351                 {
352                         float averaging_time = (me.pressed == 1)
353                             ? autocvar_menu_scroll_averaging_time_pressed
354                                 : autocvar_menu_scroll_averaging_time;
355                         // this formula works with whatever framerate
356                         float f = averaging_time ? exp(-frametime / averaging_time) : 0;
357                         me.scrollPos = me.scrollPos * f + me.scrollPosTarget * (1 - f);
358                         if (fabs(me.scrollPos - me.scrollPosTarget) < 0.001) me.scrollPos = me.scrollPosTarget;
359                 }
360
361                 if (me.pressed == 2) me.mouseDrag(me, me.dragScrollPos);  // simulate mouseDrag event
362                 me.updateControlTopBottom(me);
363                 fillSize.x = (1 - me.controlWidth);
364                 if (me.alphaBG) draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
365                 if (me.controlWidth)
366                 {
367                         draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
368                         if (me.getTotalHeight(me) > 1)
369                         {
370                                 vector o, s;
371                                 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
372                                 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
373                                 if (me.pressed == 1) draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
374                                 else if (me.focused) draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
375                                 else draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
376                         }
377                 }
378                 draw_SetClip();
379                 oldshift = draw_shift;
380                 oldscale = draw_scale;
381
382                 float y;
383                 i = me.getItemAtPos(me, me.scrollPos);
384                 y = me.getItemStart(me, i) - me.scrollPos;
385                 for ( ; i < me.nItems && y < 1; ++i)
386                 {
387                         draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
388                         vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
389                         absSize = boxToGlobalSize(relSize, me.size);
390                         draw_scale = boxToGlobalSize(relSize, oldscale);
391                         me.drawListBoxItem(me, i, absSize, (me.selectedItem == i), (me.focusedItem == i));
392                         y += relSize.y;
393                 }
394                 draw_ClearClip();
395
396                 draw_shift = oldshift;
397                 draw_scale = oldscale;
398                 SUPER(ListBox).draw(me);
399         }
400
401         void ListBox_focusedItemChangeNotify(entity me)
402         {}
403
404         void ListBox_clickListBoxItem(entity me, float i, vector where)
405         {
406                 // template method
407         }
408
409         void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
410         {
411                 // template method
412         }
413
414         void ListBox_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
415         {
416                 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);
417         }