]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/menu/item/listbox.c
gotoXY -> gotoRC (misnaming)
[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 = 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         {
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 = 0;
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                         me.dragScrollTimer -= frametime;
204                         if(me.dragScrollTimer < 0)
205                         {
206                                 float save;
207                                 save = me.scrollPos;
208                                 // if selected item is below listbox, increase scrollpos so it is in
209                                 me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
210                                 // if selected item is above listbox, decrease scrollpos so it is in
211                                 me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
212                                 if(me.scrollPos != save)
213                                         me.dragScrollTimer = 0.2;
214                         }
215                 }
216                 // if scroll pos is below end of list, fix it
217                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
218                 // if scroll pos is above beginning of list, fix it
219                 me.scrollPos = max(me.scrollPos, 0);
220                 // now that we know where the list is scrolled to, find out where to draw the control
221                 me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
222                 me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
223
224                 float fmin;
225                 fmin = 1 * me.controlWidth / me.size_y * me.size_x;
226                 f = me.controlBottom - me.controlTop;
227                 if(f < fmin) // FIXME good default?
228                 {
229                         // f * X + 1 * (1-X) = fmin
230                         // (f - 1) * X + 1 = fmin
231                         // (f - 1) * X = fmin - 1
232                         // X = (fmin - 1) / (f - 1)
233                         f = (fmin - 1) / (f - 1);
234                         me.controlTop = me.controlTop * f + 0 * (1 - f);
235                         me.controlBottom = me.controlBottom * f + 1 * (1 - f);
236                 }
237         }
238 }
239 void drawListBox(entity me)
240 {
241         float i;
242         vector absSize;
243         vector oldshift, oldscale;
244         if(me.pressed == 2)
245                 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
246         me.updateControlTopBottom(me);
247         draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
248         if(me.nItems * me.itemHeight > 1)
249         {
250                 vector o, s;
251                 o = eX * (1 - me.controlWidth) + eY * me.controlTop;
252                 s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
253                 if(me.pressed == 1)
254                         draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
255                 else if(me.focused)
256                         draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
257                 else
258                         draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
259         }
260         draw_SetClip();
261         oldshift = draw_shift;
262         oldscale = draw_scale;
263         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
264         for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
265         {
266                 float y;
267                 y = i * me.itemHeight - me.scrollPos;
268                 if(y >= 1)
269                         break;
270                 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
271                 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
272                 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
273         }
274         draw_ClearClip();
275 }
276
277 void clickListBoxItemListBox(entity me, float i, vector where)
278 {
279         // itemclick, itemclick, does whatever itemclick does
280 }
281
282 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
283 {
284         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);
285 }
286 #endif