]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/item/listbox.c
Merge branch 'master' into divVerent/force_colors_teamplay
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / item / listbox.c
1 #ifdef INTERFACE
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)
19
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')
26
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))
41 ENDCLASS(ListBox)
42 #endif
43
44 #ifdef IMPLEMENTATION
45 void ListBox_setSelected(entity me, float i)
46 {
47         me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
48 }
49 void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
50 {
51         SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
52         me.controlWidth = me.scrollbarWidth / absSize_x;
53 }
54 void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
55 {
56         me.scrollbarWidth = theScrollbarWidth;
57         me.itemHeight = theItemHeight;
58 }
59 float ListBox_keyDown(entity me, float key, float ascii, float shift)
60 {
61         me.dragScrollTimer = time;
62         if(key == K_MWHEELUP)
63         {
64                 me.scrollPos = max(me.scrollPos - 0.5, 0);
65                 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
66         }
67         else if(key == K_MWHEELDOWN)
68         {
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)));
71         }
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)
81         {
82                 me.scrollPos = 0;
83                 me.setSelected(me, 0);
84         }
85         else if(key == K_END || key == K_KP_END)
86         {
87                 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
88                 me.setSelected(me, me.nItems - 1);
89         }
90         else
91                 return 0;
92         return 1;
93 }
94 float ListBox_mouseDrag(entity me, vector pos)
95 {
96         float hit;
97         float i;
98         me.updateControlTopBottom(me);
99         me.dragScrollPos = pos;
100         if(me.pressed == 1)
101         {
102                 hit = 1;
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;
107                 if(hit)
108                 {
109                         // calculate new pos to v
110                         float d;
111                         d = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
112                         me.scrollPos = me.previousValue + d;
113                 }
114                 else
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);
121         }
122         else if(me.pressed == 2)
123         {
124                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
125         }
126         return 1;
127 }
128 float ListBox_mousePress(entity me, vector pos)
129 {
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)
138         {
139                 // if hit, set me.pressed, otherwise scroll by one page
140                 if(pos_y < me.controlTop)
141                 {
142                         // page up
143                         me.scrollPos = max(me.scrollPos - 1, 0);
144                         me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
145                 }
146                 else if(pos_y > me.controlBottom)
147                 {
148                         // page down
149                         me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
150                         me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
151                 }
152                 else
153                 {
154                         me.pressed = 1;
155                         me.pressOffset = pos_y;
156                         me.previousValue = me.scrollPos;
157                 }
158         }
159         else
160         {
161                 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
162                 me.pressed = 2;
163                 // an item has been clicked. Select it, ...
164                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
165         }
166         return 1;
167 }
168 float ListBox_mouseRelease(entity me, vector pos)
169 {
170         vector absSize;
171         if(me.pressed == 1)
172         {
173                 // slider dragging mode
174                 // in that case, nothing happens on releasing
175         }
176         else if(me.pressed == 2)
177         {
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
183                 if(me.nItems > 0)
184                 {
185                         me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
186                 }
187         }
188         me.pressed = 0;
189         return 1;
190 }
191 void ListBox_focusLeave(entity me)
192 {
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
196         me.pressed = 0;
197 }
198 void ListBox_updateControlTopBottom(entity me)
199 {
200         float f;
201         // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
202         if(me.nItems * me.itemHeight <= 1)
203         {
204                 // we don't need no stinkin' scrollbar, we don't need no view control...
205                 me.controlTop = 0;
206                 me.controlBottom = 1;
207                 me.scrollPos = 0;
208         }
209         else
210         {
211                 if(frametime) // only do this in draw frames
212                 {
213                         if(me.dragScrollTimer < time)
214                         {
215                                 float save;
216                                 save = me.scrollPos;
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;
223                         }
224                 }
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);
232
233                 float minfactor;
234                 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
235                 f = me.controlBottom - me.controlTop;
236                 if(f < minfactor) // FIXME good default?
237                 {
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);
245                 }
246         }
247 }
248 void ListBox_draw(entity me)
249 {
250         float i;
251         vector absSize, fillSize;
252         vector oldshift, oldscale;
253         if(me.pressed == 2)
254                 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
255         me.updateControlTopBottom(me);
256         fillSize_x = (1 - me.controlWidth);
257         if(me.alphaBG)
258                 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
259         if(me.controlWidth)
260         {
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)
263                 {
264                         vector o, s;
265                         o = eX * (1 - me.controlWidth) + eY * me.controlTop;
266                         s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
267                         if(me.pressed == 1)
268                                 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
269                         else if(me.focused)
270                                 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
271                         else
272                                 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
273                 }
274         }
275         draw_SetClip();
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)
281         {
282                 float y;
283                 y = i * me.itemHeight - me.scrollPos;
284                 if(y >= 1)
285                         break;
286                 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
287                 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
288         }
289         draw_ClearClip();
290
291         draw_shift = oldshift;
292         draw_scale = oldscale;
293         SUPER(ListBox).draw(me);
294 }
295
296 void ListBox_clickListBoxItem(entity me, float i, vector where)
297 {
298         // itemclick, itemclick, does whatever itemclick does
299 }
300
301 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
302 {
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);
304 }
305 #endif