Listbox: highlight item under the cursor
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / item / inputbox.qc
1 #ifdef INTERFACE
2 CLASS(InputBox) EXTENDS(Label)
3         METHOD(InputBox, configureInputBox, void(entity, string, float, float, string))
4         METHOD(InputBox, draw, void(entity))
5         METHOD(InputBox, setText, void(entity, string))
6         METHOD(InputBox, enterText, void(entity, string))
7         METHOD(InputBox, keyDown, float(entity, float, float, float))
8         METHOD(InputBox, mouseMove, float(entity, vector))
9         METHOD(InputBox, mouseRelease, float(entity, vector))
10         METHOD(InputBox, mousePress, float(entity, vector))
11         METHOD(InputBox, mouseDrag, float(entity, vector))
12         METHOD(InputBox, showNotify, void(entity))
13         METHOD(InputBox, resizeNotify, void(entity, vector, vector, vector, vector))
14
15         ATTRIB(InputBox, src, string, string_null)
16
17         ATTRIB(InputBox, cursorPos, float, 0) // characters
18         ATTRIB(InputBox, scrollPos, float, 0) // widths
19
20         ATTRIB(InputBox, focusable, float, 1)
21         ATTRIB(InputBox, allowFocusSound, float, 1)
22         ATTRIB(InputBox, disabled, float, 0)
23         ATTRIB(InputBox, lastChangeTime, float, 0)
24         ATTRIB(InputBox, dragScrollTimer, float, 0)
25         ATTRIB(InputBox, dragScrollPos, vector, '0 0 0')
26         ATTRIB(InputBox, pressed, float, 0)
27         ATTRIB(InputBox, editColorCodes, float, 1)
28         ATTRIB(InputBox, forbiddenCharacters, string, "")
29         ATTRIB(InputBox, color, vector, '1 1 1')
30         ATTRIB(InputBox, colorF, vector, '1 1 1')
31         ATTRIB(InputBox, maxLength, float, 255) // if negative, it counts bytes, not chars
32
33         ATTRIB(InputBox, enableClearButton, float, 1)
34         ATTRIB(InputBox, clearButton, entity, NULL)
35         ATTRIB(InputBox, cb_width, float, 0)
36         ATTRIB(InputBox, cb_pressed, float, 0)
37         ATTRIB(InputBox, cb_focused, float, 0)
38         ATTRIB(InputBox, cb_color, vector, '1 1 1')
39         ATTRIB(InputBox, cb_colorF, vector, '1 1 1')
40         ATTRIB(InputBox, cb_colorC, vector, '1 1 1')
41 ENDCLASS(InputBox)
42 #endif
43
44 #ifdef IMPLEMENTATION
45 void InputBox_configureInputBox(entity me, string theText, float theCursorPos, float theFontSize, string gfx)
46 {
47         SUPER(InputBox).configureLabel(me, theText, theFontSize, 0.0);
48         me.src = gfx;
49         me.cursorPos = theCursorPos;
50 }
51 void InputBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
52 {
53         SUPER(InputBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
54         if (me.enableClearButton)
55         {
56                 me.cb_width = absSize.y / absSize.x;
57                 me.cb_offset = bound(-1, me.cb_offset, 0) * me.cb_width; // bound to range -1, 0
58                 me.keepspaceRight = me.keepspaceRight - me.cb_offset + me.cb_width;
59         }
60 }
61
62 void InputBox_setText(entity me, string txt)
63 {
64         if(me.text)
65                 strunzone(me.text);
66         SUPER(InputBox).setText(me, strzone(txt));
67 }
68
69 float over_ClearButton(entity me, vector pos)
70 {
71         if (pos.x >= 1 + me.cb_offset - me.cb_width)
72         if (pos.x < 1 + me.cb_offset)
73         if (pos.y >= 0)
74         if (pos.y < 1)
75                 return 1;
76         return 0;
77 }
78
79 float InputBox_mouseMove(entity me, vector pos)
80 {
81         if (me.enableClearButton)
82         {
83                 if (over_ClearButton(me, pos))
84                 {
85                         me.cb_focused = 1;
86                         return 1;
87                 }
88                 me.cb_focused = 0;
89         }
90         return 1;
91 }
92
93 float InputBox_mouseDrag(entity me, vector pos)
94 {
95         float p;
96         if(me.pressed)
97         {
98                 me.dragScrollPos = pos;
99                 p = me.scrollPos + pos.x - me.keepspaceLeft;
100                 me.cursorPos = draw_TextLengthUpToWidth(me.text, p, 0, me.realFontSize);
101                 me.lastChangeTime = time;
102         }
103         else if (me.enableClearButton)
104         {
105                 if (over_ClearButton(me, pos))
106                 {
107                         me.cb_pressed = 1;
108                         return 1;
109                 }
110         }
111         me.cb_pressed = 0;
112         return 1;
113 }
114
115 float InputBox_mousePress(entity me, vector pos)
116 {
117         if (me.enableClearButton)
118         if (over_ClearButton(me, pos))
119         {
120                 me.cb_pressed = 1;
121                 return 1;
122         }
123         me.dragScrollTimer = time;
124         me.pressed = 1;
125         return InputBox_mouseDrag(me, pos);
126 }
127
128 float InputBox_mouseRelease(entity me, vector pos)
129 {
130         if(me.cb_pressed)
131         if (over_ClearButton(me, pos))
132         {
133                 m_play_click_sound(MENU_SOUND_CLEAR);
134                 me.setText(me, "");
135                 me.cb_pressed = 0;
136                 return 1;
137         }
138         float r = InputBox_mouseDrag(me, pos);
139         //reset cb_pressed after mouseDrag, mouseDrag could set cb_pressed in this case:
140         //mouse press out of the clear button, drag and then mouse release over the clear button
141         me.cb_pressed = 0;
142         me.pressed = 0;
143         return r;
144 }
145
146 void InputBox_enterText(entity me, string ch)
147 {
148         float i;
149         for(i = 0; i < strlen(ch); ++i)
150                 if(strstrofs(me.forbiddenCharacters, substring(ch, i, 1), 0) > -1)
151                         return;
152         if(me.maxLength > 0)
153         {
154                 if(strlen(ch) + strlen(me.text) > me.maxLength)
155                         return;
156         }
157         else if(me.maxLength < 0)
158         {
159                 if(u8_strsize(ch) + u8_strsize(me.text) > -me.maxLength)
160                         return;
161         }
162         me.setText(me, strcat(substring(me.text, 0, me.cursorPos), ch, substring(me.text, me.cursorPos, strlen(me.text) - me.cursorPos)));
163         me.cursorPos += strlen(ch);
164 }
165
166 float InputBox_keyDown(entity me, float key, float ascii, float shift)
167 {
168         me.lastChangeTime = time;
169         me.dragScrollTimer = time;
170         if(ascii >= 32 && ascii != 127)
171         {
172                 me.enterText(me, chr(ascii));
173                 return 1;
174         }
175         switch(key)
176         {
177                 case K_KP_LEFTARROW:
178                 case K_LEFTARROW:
179                         me.cursorPos -= 1;
180                         return 1;
181                 case K_KP_RIGHTARROW:
182                 case K_RIGHTARROW:
183                         me.cursorPos += 1;
184                         return 1;
185                 case K_KP_HOME:
186                 case K_HOME:
187                         me.cursorPos = 0;
188                         return 1;
189                 case K_KP_END:
190                 case K_END:
191                         me.cursorPos = strlen(me.text);
192                         return 1;
193                 case K_BACKSPACE:
194                         if(me.cursorPos > 0)
195                         {
196                                 me.cursorPos -= 1;
197                                 me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
198                         }
199                         return 1;
200                 case K_KP_DEL:
201                 case K_DEL:
202                         if(shift & S_CTRL)
203                         {
204                                 m_play_click_sound(MENU_SOUND_CLEAR);
205                                 me.setText(me, "");
206                         }
207                         else
208                                 me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
209                         return 1;
210         }
211         return 0;
212 }
213
214 void InputBox_draw(entity me)
215 {
216         string CURSOR = "_";
217         float cursorPosInWidths, totalSizeInWidths;
218
219         if(me.pressed)
220                 me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
221
222         if(me.recalcPos)
223                 me.recalcPositionWithText(me, me.text);
224
225         me.focusable = !me.disabled;
226         if(me.disabled)
227                 draw_alpha *= me.disabledAlpha;
228
229         if(me.src)
230         {
231                 if(me.focused && !me.disabled)
232                         draw_ButtonPicture('0 0 0', strcat(me.src, "_f"), '1 1 0', me.colorF, 1);
233                 else
234                         draw_ButtonPicture('0 0 0', strcat(me.src, "_n"), '1 1 0', me.color, 1);
235         }
236
237         me.cursorPos = bound(0, me.cursorPos, strlen(me.text));
238         cursorPosInWidths = draw_TextWidth(substring(me.text, 0, me.cursorPos), 0, me.realFontSize);
239         totalSizeInWidths = draw_TextWidth(strcat(me.text, CURSOR), 0, me.realFontSize);
240
241         if(me.dragScrollTimer < time)
242         {
243                 float save;
244                 save = me.scrollPos;
245                 me.scrollPos = bound(cursorPosInWidths - (0.875 - me.keepspaceLeft - me.keepspaceRight), me.scrollPos, cursorPosInWidths - 0.125);
246                 if(me.scrollPos != save)
247                         me.dragScrollTimer = time + 0.2;
248         }
249         me.scrollPos = min(me.scrollPos, totalSizeInWidths - (1 - me.keepspaceRight - me.keepspaceLeft));
250         me.scrollPos = max(0, me.scrollPos);
251
252         draw_SetClipRect(eX * me.keepspaceLeft, eX * (1 - me.keepspaceLeft - me.keepspaceRight) + eY);
253         if(me.editColorCodes)
254         {
255                 string ch, ch2;
256                 float i, n;
257                 vector theColor;
258                 float theAlpha;    //float theVariableAlpha;
259                 vector p;
260                 vector theTempColor;
261                 float component;
262
263                 p = me.realOrigin - eX * me.scrollPos;
264                 theColor = '1 1 1';
265                 theAlpha = 1;    //theVariableAlpha = 1; // changes when ^ax found
266
267                 n = strlen(me.text);
268                 for(i = 0; i < n; ++i)
269                 {
270                         ch = substring(me.text, i, 1);
271                         if(ch == "^")
272                         {
273                                 float w;
274                                 ch2 = substring(me.text, i+1, 1);
275                                 w = draw_TextWidth(strcat(ch, ch2), 0, me.realFontSize);
276                                 if(ch2 == "^")
277                                 {
278                                         draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
279                                         draw_Text(p + eX * 0.25 * w, "^", me.realFontSize, theColor, theAlpha, 0);
280                                 }
281                                 else if(ch2 == "0" || stof(ch2)) // digit?
282                                 {
283                                         switch(stof(ch2))
284                                         {
285                                                 case 0: theColor = '0 0 0'; theAlpha = 1; break;
286                                                 case 1: theColor = '1 0 0'; theAlpha = 1; break;
287                                                 case 2: theColor = '0 1 0'; theAlpha = 1; break;
288                                                 case 3: theColor = '1 1 0'; theAlpha = 1; break;
289                                                 case 4: theColor = '0 0 1'; theAlpha = 1; break;
290                                                 case 5: theColor = '0 1 1'; theAlpha = 1; break;
291                                                 case 6: theColor = '1 0 1'; theAlpha = 1; break;
292                                                 case 7: theColor = '1 1 1'; theAlpha = 1; break;
293                                                 case 8: theColor = '1 1 1'; theAlpha = 0.5; break;
294                                                 case 9: theColor = '0.5 0.5 0.5'; theAlpha = 1; break;
295                                         }
296                                         draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
297                                         draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
298                                 }
299                                 else if(ch2 == "x") // ^x found
300                                 {
301                                         theColor = '1 1 1';
302
303                                         component = HEXDIGIT_TO_DEC(substring(me.text, i+2, 1));
304                                         if (component >= 0) // ^xr found
305                                         {
306                                                 theTempColor.x = component/15;
307
308                                                 component = HEXDIGIT_TO_DEC(substring(me.text, i+3, 1));
309                                                 if (component >= 0) // ^xrg found
310                                                 {
311                                                         theTempColor.y = component/15;
312
313                                                         component = HEXDIGIT_TO_DEC(substring(me.text, i+4, 1));
314                                                         if (component >= 0) // ^xrgb found
315                                                         {
316                                                                 theTempColor.z = component/15;
317                                                                 theColor = theTempColor;
318                                                                 w = draw_TextWidth(substring(me.text, i, 5), 0, me.realFontSize);
319
320                                                                 draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
321                                                                 draw_Text(p, substring(me.text, i, 5), me.realFontSize, theColor, 1, 0);    // theVariableAlpha instead of 1 using alpha tags ^ax
322                                                                 i += 3;
323                                                         }
324                                                         else
325                                                         {
326                                                                 // blue missing
327                                                                 w = draw_TextWidth(substring(me.text, i, 4), 0, me.realFontSize);
328                                                                 draw_Fill(p, eX * w + eY * me.realFontSize.y, eZ, 0.5);
329                                                                 draw_Text(p, substring(me.text, i, 4), me.realFontSize, '1 1 1', theAlpha, 0);
330                                                                 i += 2;
331                                                         }
332                                                 }
333                                                 else
334                                                 {
335                                                         // green missing
336                                                         w = draw_TextWidth(substring(me.text, i, 3), 0, me.realFontSize);
337                                                         draw_Fill(p, eX * w + eY * me.realFontSize.y, eY, 0.5);
338                                                         draw_Text(p, substring(me.text, i, 3), me.realFontSize, '1 1 1', theAlpha, 0);
339                                                         i += 1;
340                                                 }
341                                         }
342                                         else
343                                         {
344                                                 // red missing
345                                                 //w = draw_TextWidth(substring(me.text, i, 2), 0) * me.realFontSize_x;
346                                                 draw_Fill(p, eX * w + eY * me.realFontSize.y, eX, 0.5);
347                                                 draw_Text(p, substring(me.text, i, 2), me.realFontSize, '1 1 1', theAlpha, 0);
348                                         }
349                                 }
350                                 else
351                                 {
352                                         draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
353                                         draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
354                                 }
355                                 p += w * eX;
356                                 ++i;
357                                 continue;
358                         }
359                         draw_Text(p, ch, me.realFontSize, theColor, theAlpha, 0); // TODO theVariableAlpha
360                         p += eX * draw_TextWidth(ch, 0, me.realFontSize);
361                 }
362         }
363         else
364                 draw_Text(me.realOrigin - eX * me.scrollPos, me.text, me.realFontSize, '1 1 1', 1, 0);
365
366         if(!me.focused || (time - me.lastChangeTime) < floor(time - me.lastChangeTime) + 0.5)
367                 draw_Text(me.realOrigin + eX * (cursorPosInWidths - me.scrollPos), CURSOR, me.realFontSize, '1 1 1', 1, 0);
368
369         draw_ClearClip();
370
371         if (me.enableClearButton)
372         if (me.text != "")
373         {
374                 if(me.focused && me.cb_pressed)
375                         draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_c"), eX * me.cb_width + eY, me.cb_colorC, 1);
376                 else if(me.focused && me.cb_focused)
377                         draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_f"), eX * me.cb_width + eY, me.cb_colorF, 1);
378                 else
379                         draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_n"), eX * me.cb_width + eY, me.cb_color, 1);
380         }
381
382         // skipping SUPER(InputBox).draw(me);
383         Item_draw(me);
384 }
385
386 void InputBox_showNotify(entity me)
387 {
388         me.focusable = !me.disabled;
389 }
390 #endif