]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/menu/item/listbox.c
camping rifle: make reloading use an extra weapon frame (yes, the model does not...
[divverent/nexuiz.git] / data / 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         ATTRIB(ListBox, focusable, float, 1)
11         ATTRIB(ListBox, selectedItem, float, 0)
12         ATTRIB(ListBox, size, vector, '0 0 0')
13         ATTRIB(ListBox, origin, vector, '0 0 0')
14         ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
15         ATTRIB(ListBox, previousValue, float, 0)
16         ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
17         ATTRIB(ListBox, pressOffset, float, 0)
18
19         METHOD(ListBox, updateControlTopBottom, void(entity))
20         ATTRIB(ListBox, controlTop, float, 0)
21         ATTRIB(ListBox, controlBottom, float, 0)
22         ATTRIB(ListBox, controlWidth, float, 0)
23         ATTRIB(ListBox, dragScrollTimer, float, 0)
24         ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
25
26         ATTRIB(ListBox, src, string, string_null) // scrollbar
27         ATTRIB(ListBox, color, vector, '1 1 1')
28         ATTRIB(ListBox, color2, vector, '1 1 1')
29         ATTRIB(ListBox, colorC, vector, '1 1 1')
30         ATTRIB(ListBox, colorF, vector, '1 1 1')
31         ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
32         ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
33         ATTRIB(ListBox, nItems, float, 42)
34         ATTRIB(ListBox, itemHeight, float, 0)
35         METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
36         METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
37         METHOD(ListBox, setSelected, void(entity, float))
38 ENDCLASS(ListBox)
39 #endif
40
41 #ifdef IMPLEMENTATION
42 void setSelectedListBox(entity me, float i)
43 {
44         me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
45 }
46 void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
47 {
48         me.size = absSize;
49         me.origin = absOrigin;
50         me.controlWidth = me.scrollbarWidth / absSize_x;
51 }
52 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
53 {
54         me.scrollbarWidth = theScrollbarWidth;
55         me.itemHeight = theItemHeight;
56 }
57 float keyDownListBox(entity me, float key, float ascii, float shift)
58 {
59         me.dragScrollTimer = time;
60         if(key == K_MWHEELUP)
61         {
62                 me.scrollPos = max(me.scrollPos - 0.5, 0);
63                 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
64         }
65         else if(key == K_MWHEELDOWN)
66         {
67                 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
68                 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
69         }
70         else if(key == K_PGUP)
71                 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
72         else if(key == K_PGDN)
73                 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
74         else if(key == K_UPARROW)
75                 me.setSelected(me, me.selectedItem - 1);
76         else if(key == K_DOWNARROW)
77                 me.setSelected(me, me.selectedItem + 1);
78         else if(key == K_HOME)
79         {
80                 me.scrollPos = 0;
81                 me.setSelected(me, 0);
82         }
83         else if(key == K_END)
84         {
85                 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
86                 me.setSelected(me, me.nItems - 1);
87         }
88         else
89                 return 0;
90         return 1;
91 }
92 float mouseDragListBox(entity me, vector pos)
93 {
94         float hit;
95         float i;
96         me.updateControlTopBottom(me);
97         me.dragScrollPos = pos;
98         if(me.pressed == 1)
99         {
100                 hit = 1;
101                 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
102                 if(pos_y < 0 - me.tolerance_x) hit = 0;
103                 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
104                 if(pos_y >= 1 + me.tolerance_x) hit = 0;
105                 if(hit)
106                 {
107                         // calculate new pos to v
108                         float delta;
109                         delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
110                         me.scrollPos = me.previousValue + delta;
111                 }
112                 else
113                         me.scrollPos = me.previousValue;
114                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
115                 me.scrollPos = max(me.scrollPos, 0);
116                 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
117                 i = max(i, ceil(me.scrollPos / me.itemHeight));
118                 me.setSelected(me, i);
119         }
120         else if(me.pressed == 2)
121         {
122                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
123         }
124         return 1;
125 }
126 float mousePressListBox(entity me, vector pos)
127 {
128         if(pos_x < 0) return 0;
129         if(pos_y < 0) return 0;
130         if(pos_x >= 1) return 0;
131         if(pos_y >= 1) return 0;
132         me.dragScrollPos = pos;
133         me.updateControlTopBottom(me);
134         me.dragScrollTimer = time;
135         if(pos_x >= 1 - me.controlWidth)
136         {
137                 // if hit, set me.pressed, otherwise scroll by one page
138                 if(pos_y < me.controlTop)
139                 {
140                         // page up
141                         me.scrollPos = max(me.scrollPos - 1, 0);
142                         me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
143                 }
144                 else if(pos_y > me.controlBottom)
145                 {
146                         // page down
147                         me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
148                         me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
149                 }
150                 else
151                 {
152                         me.pressed = 1;
153                         me.pressOffset = pos_y;
154                         me.previousValue = me.scrollPos;
155                 }
156         }
157         else
158         {
159                 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
160                 me.pressed = 2;
161                 // an item has been clicked. Select it, ...
162                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
163         }
164         return 1;
165 }
166 float mouseReleaseListBox(entity me, vector pos)
167 {
168         vector absSize;
169         if(me.pressed == 1)
170         {
171                 // slider dragging mode
172                 // in that case, nothing happens on releasing
173         }
174         else if(me.pressed == 2)
175         {
176                 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
177                 // item dragging mode
178                 // select current one one last time...
179                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
180                 // and give it a nice click event
181                 if(me.nItems > 0)
182                 {
183                         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
184                         me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
185                 }
186         }
187         me.pressed = 0;
188         return 1;
189 }
190 void updateControlTopBottomListBox(entity me)
191 {
192         float f;
193         // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
194         if(me.nItems * me.itemHeight <= 1)
195         {
196                 // we don't need no stinkin' scrollbar, we don't need no view control...
197                 me.controlTop = 0;
198                 me.controlBottom = 1;
199                 me.scrollPos = 0;
200         }
201         else
202         {
203                 if(frametime) // only do this in draw frames
204                 {
205                         if(me.dragScrollTimer < time)
206                         {
207                                 float save;
208                                 save = me.scrollPos;
209                                 // if selected item is below listbox, increase scrollpos so it is in
210                                 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
211                                 // if selected item is above listbox, decrease scrollpos so it is in
212                                 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
213                                 if(me.scrollPos != save)
214                                         me.dragScrollTimer = time + 0.2;
215                         }
216                 }
217                 // if scroll pos is below end of list, fix it
218                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
219                 // if scroll pos is above beginning of list, fix it
220                 me.scrollPos = max(me.scrollPos, 0);
221                 // now that we know where the list is scrolled to, find out where to draw the control
222                 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
223                 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
224
225                 float fmin;
226                 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
227                 f = me.controlBottom - me.controlTop;
228                 if(f < fmin) // FIXME good default?
229                 {
230                         // f * X + 1 * (1-X) = fmin
231                         // (f - 1) * X + 1 = fmin
232                         // (f - 1) * X = fmin - 1
233                         // X = (fmin - 1) / (f - 1)
234                         f = (fmin - 1) / (f - 1);
235                         me.controlTop = me.controlTop * f + 0 * (1 - f);
236                         me.controlBottom = me.controlBottom * f + 1 * (1 - f);
237                 }
238         }
239 }
240 void drawListBox(entity me)
241 {
242         float i;
243         vector absSize;
244         vector oldshift, oldscale;
245         if(me.pressed == 2)
246                 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
247         me.updateControlTopBottom(me);
248         if(me.controlWidth)
249         {
250                 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
251                 if(me.nItems * me.itemHeight > 1)
252                 {
253                         vector o, s;
254                         o = eX * (1 - me.controlWidth) + eY * me.controlTop;
255                         s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
256                         if(me.pressed == 1)
257                                 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
258                         else if(me.focused)
259                                 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
260                         else
261                                 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
262                 }
263         }
264         draw_SetClip();
265         oldshift = draw_shift;
266         oldscale = draw_scale;
267         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
268         for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
269         {
270                 float y;
271                 y = i * me.itemHeight - me.scrollPos;
272                 if(y >= 1)
273                         break;
274                 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
275                 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
276                 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
277         }
278         draw_ClearClip();
279 }
280
281 void clickListBoxItemListBox(entity me, float i, vector where)
282 {
283         // itemclick, itemclick, does whatever itemclick does
284 }
285
286 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
287 {
288         draw_Text('0 0 0', strcat("Item ", ftos(i)), eX * (8 / absSize_x) + eY * (8 / absSize_y), (selected ? '0 1 0' : '1 1 1'), 1, 0);
289 }
290 #endif