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