3ebbf6093e8672ba1b856e7aa84a5ed4de71770e
[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         resizeNotifyItem(me, relOrigin, relSize, absOrigin, absSize);
49         me.controlWidth = me.scrollbarWidth / absSize_x;
50 }
51 void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
52 {
53         me.scrollbarWidth = theScrollbarWidth;
54         me.itemHeight = theItemHeight;
55 }
56 float keyDownListBox(entity me, float key, float ascii, float shift)
57 {
58         me.dragScrollTimer = time;
59         if(key == K_MWHEELUP)
60         {
61                 me.scrollPos = max(me.scrollPos - 0.5, 0);
62                 me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
63         }
64         else if(key == K_MWHEELDOWN)
65         {
66                 me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
67                 me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
68         }
69         else if(key == K_PGUP)
70                 me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
71         else if(key == K_PGDN)
72                 me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
73         else if(key == K_UPARROW)
74                 me.setSelected(me, me.selectedItem - 1);
75         else if(key == K_DOWNARROW)
76                 me.setSelected(me, me.selectedItem + 1);
77         else if(key == K_HOME)
78         {
79                 me.scrollPos = 0;
80                 me.setSelected(me, 0);
81         }
82         else if(key == K_END)
83         {
84                 me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
85                 me.setSelected(me, me.nItems - 1);
86         }
87         else
88                 return 0;
89         return 1;
90 }
91 float mouseDragListBox(entity me, vector pos)
92 {
93         float hit;
94         float i;
95         me.updateControlTopBottom(me);
96         me.dragScrollPos = pos;
97         if(me.pressed == 1)
98         {
99                 hit = 1;
100                 if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
101                 if(pos_y < 0 - me.tolerance_x) hit = 0;
102                 if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
103                 if(pos_y >= 1 + me.tolerance_x) hit = 0;
104                 if(hit)
105                 {
106                         // calculate new pos to v
107                         float delta;
108                         delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
109                         me.scrollPos = me.previousValue + delta;
110                 }
111                 else
112                         me.scrollPos = me.previousValue;
113                 me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
114                 me.scrollPos = max(me.scrollPos, 0);
115                 i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
116                 i = max(i, ceil(me.scrollPos / me.itemHeight));
117                 me.setSelected(me, i);
118         }
119         else if(me.pressed == 2)
120         {
121                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
122         }
123         return 1;
124 }
125 float mousePressListBox(entity me, vector pos)
126 {
127         if(pos_x < 0) return 0;
128         if(pos_y < 0) return 0;
129         if(pos_x >= 1) return 0;
130         if(pos_y >= 1) return 0;
131         me.dragScrollPos = pos;
132         me.updateControlTopBottom(me);
133         me.dragScrollTimer = time;
134         if(pos_x >= 1 - me.controlWidth)
135         {
136                 // if hit, set me.pressed, otherwise scroll by one page
137                 if(pos_y < me.controlTop)
138                 {
139                         // page up
140                         me.scrollPos = max(me.scrollPos - 1, 0);
141                         me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
142                 }
143                 else if(pos_y > me.controlBottom)
144                 {
145                         // page down
146                         me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
147                         me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
148                 }
149                 else
150                 {
151                         me.pressed = 1;
152                         me.pressOffset = pos_y;
153                         me.previousValue = me.scrollPos;
154                 }
155         }
156         else
157         {
158                 // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
159                 me.pressed = 2;
160                 // an item has been clicked. Select it, ...
161                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
162         }
163         return 1;
164 }
165 float mouseReleaseListBox(entity me, vector pos)
166 {
167         vector absSize;
168         if(me.pressed == 1)
169         {
170                 // slider dragging mode
171                 // in that case, nothing happens on releasing
172         }
173         else if(me.pressed == 2)
174         {
175                 me.pressed = 3; // do that here, so setSelected can know the mouse has been released
176                 // item dragging mode
177                 // select current one one last time...
178                 me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
179                 // and give it a nice click event
180                 if(me.nItems > 0)
181                 {
182                         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
183                         me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
184                 }
185         }
186         me.pressed = 0;
187         return 1;
188 }
189 void updateControlTopBottomListBox(entity me)
190 {
191         float f;
192         // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
193         if(me.nItems * me.itemHeight <= 1)
194         {
195                 // we don't need no stinkin' scrollbar, we don't need no view control...
196                 me.controlTop = 0;
197                 me.controlBottom = 1;
198                 me.scrollPos = 0;
199         }
200         else
201         {
202                 if(frametime) // only do this in draw frames
203                 {
204                         if(me.dragScrollTimer < time)
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 = time + 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         if(me.controlWidth)
248         {
249                 draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
250                 if(me.nItems * me.itemHeight > 1)
251                 {
252                         vector o, s;
253                         o = eX * (1 - me.controlWidth) + eY * me.controlTop;
254                         s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
255                         if(me.pressed == 1)
256                                 draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
257                         else if(me.focused)
258                                 draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
259                         else
260                                 draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
261                 }
262         }
263         draw_SetClip();
264         oldshift = draw_shift;
265         oldscale = draw_scale;
266         absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
267         for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
268         {
269                 float y;
270                 y = i * me.itemHeight - me.scrollPos;
271                 if(y >= 1)
272                         break;
273                 draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
274                 draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
275                 me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
276         }
277         draw_ClearClip();
278 }
279
280 void clickListBoxItemListBox(entity me, float i, vector where)
281 {
282         // itemclick, itemclick, does whatever itemclick does
283 }
284
285 void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
286 {
287         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);
288 }
289 #endif