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