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