]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/menu-div0test/item/listbox.c
-scmenu; make mapinfo default
[divverent/nexuiz.git] / data / qcsrc / menu-div0test / 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 = 0;
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                 me.setSelected(me, 0);
78         else if(key == K_END)
79                 me.setSelected(me, me.nItems - 1);
80         else
81                 return 0;
82         return 1;
83 }
84 float mouseDragListBox(entity me, vector pos)
85 {
86         float hit;
87         float i;
88         me.updateControlTopBottom(me);
89         me.dragScrollPos = pos;
90         if(me.pressed == 1)
91         {
92                 hit = 1;
93                 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
94                 if(pos_y < 0 - me.tolerance_x) hit = 0;
95                 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
96                 if(pos_y >= 1 + me.tolerance_x) hit = 0;
97                 if(hit)
98                 {
99                         // calculate new pos to v
100                         float delta;
101                         delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
102                         me.scrollPos = me.previousValue + delta;
103                 }
104                 else
105                         me.scrollPos = me.previousValue;
106                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
107                 me.scrollPos = max(me.scrollPos, 0);
108                 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
109                 i = max(i, ceil(me.scrollPos / me.itemHeight));
110                 me.setSelected(me, i);
111         }
112         else if(me.pressed == 2)
113         {
114                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
115         }
116         return 1;
117 }
118 float mousePressListBox(entity me, vector pos)
119 {
120         if(pos_x < 0) return 0;
121         if(pos_y < 0) return 0;
122         if(pos_x >= 1) return 0;
123         if(pos_y >= 1) return 0;
124         me.dragScrollPos = pos;
125         me.updateControlTopBottom(me);
126         me.dragScrollTimer = 0;
127         if(pos_x >= 1 - me.controlWidth)
128         {
129                 // if hit, set me.pressed, otherwise scroll by one page
130                 if(pos_y < me.controlTop)
131                 {
132                         // page up
133                         me.scrollPos = max(me.scrollPos - 1, 0);
134                         me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
135                 }
136                 else if(pos_y > me.controlBottom)
137                 {
138                         // page down
139                         me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
140                         me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
141                 }
142                 else
143                 {
144                         me.pressed = 1;
145                         me.pressOffset = pos_y;
146                         me.previousValue = me.scrollPos;
147                 }
148         }
149         else
150         {
151                 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
152                 me.pressed = 2;
153                 // an item has been clicked. Select it, ...
154                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
155         }
156         return 1;
157 }
158 float mouseReleaseListBox(entity me, vector pos)
159 {
160         vector absSize;
161         if(me.pressed == 1)
162         {
163                 // slider dragging mode
164                 // in that case, nothing happens on releasing
165         }
166         else if(me.pressed == 2)
167         {
168                 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
169                 // item dragging mode
170                 // select current one one last time...
171                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
172                 // and give it a nice click event
173                 if(me.nItems > 0)
174                 {
175                         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
176                         me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
177                 }
178         }
179         me.pressed = 0;
180         return 1;
181 }
182 void updateControlTopBottomListBox(entity me)
183 {
184         float f;
185         // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
186         if(me.nItems * me.itemHeight <= 1)
187         {
188                 // we don't need no stinkin' scrollbar, we don't need no view control...
189                 me.controlTop = 0;
190                 me.controlBottom = 1;
191                 me.scrollPos = 0;
192         }
193         else
194         {
195                 if(frametime) // only do this in draw frames
196                 {
197                         me.dragScrollTimer -= frametime;
198                         if(me.dragScrollTimer < 0)
199                         {
200                                 float save;
201                                 save = me.scrollPos;
202                                 // if selected item is below listbox, increase scrollpos so it is in
203                                 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
204                                 // if selected item is above listbox, decrease scrollpos so it is in
205                                 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
206                                 if(me.scrollPos != save)
207                                         me.dragScrollTimer = 0.2;
208                         }
209                 }
210                 // if scroll pos is below end of list, fix it
211                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
212                 // if scroll pos is above beginning of list, fix it
213                 me.scrollPos = max(me.scrollPos, 0);
214                 // now that we know where the list is scrolled to, find out where to draw the control
215                 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
216                 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
217
218                 float fmin;
219                 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
220                 f = me.controlBottom - me.controlTop;
221                 if(f < fmin) // FIXME good default?
222                 {
223                         // f * X + 1 * (1-X) = fmin
224                         // (f - 1) * X + 1 = fmin
225                         // (f - 1) * X = fmin - 1
226                         // X = (fmin - 1) / (f - 1)
227                         f = (fmin - 1) / (f - 1);
228                         me.controlTop = me.controlTop * f + 0 * (1 - f);
229                         me.controlBottom = me.controlBottom * f + 1 * (1 - f);
230                 }
231         }
232 }
233 void drawListBox(entity me)
234 {
235         float i;
236         vector absSize;
237         vector oldshift, oldscale;
238         if(me.pressed == 2)
239                 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
240         me.updateControlTopBottom(me);
241         draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
242         if(me.nItems * me.itemHeight > 1)
243         {
244                 vector o, s;
245                 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
246                 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
247                 if(me.pressed == 1)
248                         draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
249                 else if(me.focused)
250                         draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
251                 else
252                         draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
253         }
254         draw_SetClip();
255         oldshift = draw_shift;
256         oldscale = draw_scale;
257         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
258         for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
259         {
260                 float y;
261                 y = i * me.itemHeight - me.scrollPos;
262                 if(y >= 1)
263                         break;
264                 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
265                 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
266                 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
267         }
268         draw_ClearClip();
269 }
270
271 void clickListBoxItemListBox(entity me, float i, vector where)
272 {
273         // itemclick, itemclick, does whatever itemclick does
274 }
275
276 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
277 {
278         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);
279 }
280 #endif