Merge remote-tracking branch 'origin/divVerent/allow-override-item-model'
[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                         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
186                         me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
187                 }
188         }
189         me.pressed = 0;
190         return 1;
191 }
192 void ListBox_focusLeave(entity me)
193 {
194         // Reset the var pressed in case listbox loses focus
195         // by a mouse click on an item of the list
196         // for example showing a dialog on right click
197         me.pressed = 0;
198 }
199 void ListBox_updateControlTopBottom(entity me)
200 {
201         float f;
202         // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
203         if(me.nItems * me.itemHeight <= 1)
204         {
205                 // we don't need no stinkin' scrollbar, we don't need no view control...
206                 me.controlTop = 0;
207                 me.controlBottom = 1;
208                 me.scrollPos = 0;
209         }
210         else
211         {
212                 if(frametime) // only do this in draw frames
213                 {
214                         if(me.dragScrollTimer < time)
215                         {
216                                 float save;
217                                 save = me.scrollPos;
218                                 // if selected item is below listbox, increase scrollpos so it is in
219                                 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
220                                 // if selected item is above listbox, decrease scrollpos so it is in
221                                 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
222                                 if(me.scrollPos != save)
223                                         me.dragScrollTimer = time + 0.2;
224                         }
225                 }
226                 // if scroll pos is below end of list, fix it
227                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
228                 // if scroll pos is above beginning of list, fix it
229                 me.scrollPos = max(me.scrollPos, 0);
230                 // now that we know where the list is scrolled to, find out where to draw the control
231                 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
232                 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
233
234                 float minfactor;
235                 minfactor = 1 * me.controlWidth / me.size_y * me.size_x;
236                 f = me.controlBottom - me.controlTop;
237                 if(f < minfactor) // FIXME good default?
238                 {
239                         // f * X + 1 * (1-X) = minfactor
240                         // (f - 1) * X + 1 = minfactor
241                         // (f - 1) * X = minfactor - 1
242                         // X = (minfactor - 1) / (f - 1)
243                         f = (minfactor - 1) / (f - 1);
244                         me.controlTop = me.controlTop * f + 0 * (1 - f);
245                         me.controlBottom = me.controlBottom * f + 1 * (1 - f);
246                 }
247         }
248 }
249 void ListBox_draw(entity me)
250 {
251         float i;
252         vector absSize, fillSize;
253         vector oldshift, oldscale;
254         if(me.pressed == 2)
255                 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
256         me.updateControlTopBottom(me);
257         fillSize_x = (1 - me.controlWidth);
258         if(me.alphaBG)
259                 draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
260         if(me.controlWidth)
261         {
262                 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
263                 if(me.nItems * me.itemHeight > 1)
264                 {
265                         vector o, s;
266                         o = eX * (1 - me.controlWidth) + eY * me.controlTop;
267                         s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
268                         if(me.pressed == 1)
269                                 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
270                         else if(me.focused)
271                                 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
272                         else
273                                 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
274                 }
275         }
276         draw_SetClip();
277         oldshift = draw_shift;
278         oldscale = draw_scale;
279         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
280         draw_scale = boxToGlobalSize(eX * (1 - me.controlWidth) + eY * me.itemHeight, oldscale);
281         for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
282         {
283                 float y;
284                 y = i * me.itemHeight - me.scrollPos;
285                 if(y >= 1)
286                         break;
287                 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
288                 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
289         }
290         draw_ClearClip();
291 }
292
293 void ListBox_clickListBoxItem(entity me, float i, vector where)
294 {
295         // itemclick, itemclick, does whatever itemclick does
296 }
297
298 void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
299 {
300         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);
301 }
302 #endif