egra: don't use ENTER/LEAVE (because intel sux, and they are slower than the correspo...
[iv.d.git] / pdollar_test / gestures.d
blob31078127c4790a4824205877f970d3ad1e85e73c
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module gesturespro_test /*is aliced*/;
18 private:
20 import arsd.color;
21 import arsd.simpledisplay;
23 import iv.alice;
24 import iv.cmdcongl;
25 import iv.pdollar0;
26 import iv.pxclock;
27 import iv.vfs.io;
29 import testgestures;
32 // ////////////////////////////////////////////////////////////////////////// //
33 SimpleWindow sdwin;
36 // ////////////////////////////////////////////////////////////////////////// //
37 ulong msgHideTime = 0;
38 int msgAlpha = -1;
39 string msgText;
42 void updateMsg () {
43 if (msgAlpha >= 0) {
44 if (msgAlpha > 0) {
45 if ((msgAlpha += 10) > 255) {
46 msgAlpha = -1;
47 msgText = null;
49 } else {
50 if (clockMilli() >= msgHideTime) {
51 msgAlpha = 1;
54 frameChanged();
59 void showMessage (string msg) {
60 if (msg.length == 0) return;
62 ovlMsg = new VLOverlay(msg.length*6+6, 8+6);
63 ovlMsg.fillRect(0, 0, ovlMsg.width, ovlMsg.height, rgb2col(25, 69, 247));
64 ovlMsg.rect(0, 0, ovlMsg.width, ovlMsg.height, rgb2col(255, 255, 255));
65 ovlMsg.rect(1, 1, ovlMsg.width-2, ovlMsg.height-2, rgb2col(0, 0, 0));
66 Color fg = rgb2col(255, 255, 255);
67 int x = 3;
68 foreach (/+auto+/ ch; msg) {
70 switch (ch) {
71 case 1: fg = rgb2col(255, 255, 255); break;
72 case 2: fg = rgb2col(0, 255, 0); break;
73 case 3: fg = rgb2col(255, 255, 0); break;
74 case 4: fg = rgb2col(255, 127, 0); break;
75 default: break;
77 if (ch < 32) continue;
79 ovlMsg.drawChar(x, 3, ch, fg);
80 x += 6;
83 msgText = msg;
84 msgHideTime = clockMilli()+5000;
85 msgAlpha = 0;
86 frameChanged();
90 // ////////////////////////////////////////////////////////////////////////// //
91 DPGestureList glib;
92 int nameMaxLen = 0;
93 DPPointCloud curPattern;
94 DPPoint[] drawnGlyph;
95 string detectedGlyph;
96 bool helpVisible;
99 void fixNameMaxLen () {
100 nameMaxLen = 0;
101 foreach (string name; glib.knownGestureNames) if (name.length > nameMaxLen) nameMaxLen = cast(int)name.length;
105 // ////////////////////////////////////////////////////////////////////////// //
106 int curGlyph = -1;
107 string curGlyphName;
108 bool editingName;
109 string yesNoMessage;
112 void registerGlyph () {
113 if (drawnGlyph.length > 5 && curGlyphName.length > 0) {
114 auto pc = new DPPointCloud(curGlyphName, drawnGlyph);
115 glib.appendGesture(pc);
116 fixNameMaxLen();
117 curPattern = pc;
122 // ////////////////////////////////////////////////////////////////////////// //
123 enum CharWidth = 10;
124 enum CharHeight = 10;
126 void frameChanged () {
127 if (sdwin is null || sdwin.closed) return;
130 auto painter = sdwin.draw();
132 void drawText (int x, int y, const(char)[] str...) {
133 foreach (immutable char ch; str) {
134 foreach (immutable int dy; 0..CharHeight) {
135 ushort v = glConFont10.ptr[cast(ubyte)ch*CharHeight+dy];
136 foreach (immutable int dx; 0..CharWidth) {
137 if (v&0x8000) {
138 painter.drawPixel(Point(x+dx, y+dy));
139 //painter.drawLine(Point(x+dx, y+dy), Point(x+dx+1, y+dy+1));
141 v <<= 1;
144 x += CharWidth;
148 void helpOverlay () {
149 static immutable string[] helpText = [
150 "\x1fDemo actions",
151 "\x1f------------",
152 "\3keyboard:\1",
153 " \2F1\1: toggle help",
154 " \2F2\1: save library to '\4strokes.dat\1'",
155 " \2F3\1: replace library with '\4strokes.dat\1'",
156 " \2ESC\1: quit",
157 " \2DEL\1: delete selected stroke",
159 "\3mouse:\1",
160 " \2LMB\1: select name or start drawing",
161 " \2RMB\1: register current stroke as template",
162 " \2Ctl\1: hold it to draw next segment",
165 static int stlen (string s) {
166 int res = 0;
167 foreach (immutable char ch; s) if (ch >= 32) ++res;
168 return res;
171 int maxlen = 0;
172 foreach (/*auto*/ s; helpText) {
173 auto ln = stlen(s);
174 if (ln > maxlen) maxlen = ln;
177 int wdt = (maxlen*CharWidth+6);
178 int hgt = (CharHeight*cast(int)helpText.length+6);
179 int x0 = (sdwin.width-wdt)/2;
180 int y0 = (sdwin.height-hgt)/2;
182 painter.outlineColor = Color(25, 69, 247);
183 painter.fillColor = Color(25, 69, 247);
184 painter.drawRectangle(Point(x0, y0), wdt, hgt);
185 painter.fillColor = Color.transparent;
186 painter.outlineColor = Color(255, 255, 255);
187 painter.drawRectangle(Point(x0, y0), wdt, hgt);
188 painter.outlineColor = Color.black;
189 painter.drawRectangle(Point(x0+1, y0+1), wdt-2, hgt-2);
191 foreach (/*auto*/ idx, /*auto*/ s; helpText) {
192 if (s.length == 0) continue;
193 auto ln = stlen(s)*CharWidth;
194 auto x = (wdt-ln)/2;
195 auto y = idx*CharHeight+3;
196 string st = s;
197 if (s[0] == '\x1f') {
198 st = s[1..$];
199 } else {
200 x = 3;
202 Color fg = Color(255, 255, 255);
203 foreach (/*auto*/ ch; st) {
204 switch (ch) {
205 case 1: fg = Color(255, 255, 255); break;
206 case 2: fg = Color(0, 255, 0); break;
207 case 3: fg = Color(255, 255, 0); break;
208 case 4: fg = Color(255, 127, 0); break;
209 default: break;
211 if (ch < 32) continue;
212 painter.outlineColor = fg;
213 drawText(x0+x, y0+y, ch);
214 x += CharWidth;
219 void drawStroke (const(DPPoint)[] stk) {
220 painter.outlineColor = Color(255, 255, 0);
221 bool first = true;
222 uint lastid = uint.max;
223 foreach (immutable pidx, const ref DPPoint p; stk) {
224 if (first) {
225 lastid = p.id;
226 first = false;
227 } else if (p.id == lastid) {
228 painter.drawLine(Point(cast(int)stk[pidx-1].x, cast(int)stk[pidx-1].y), Point(cast(int)stk[pidx].x, cast(int)stk[pidx].y));
229 } else {
230 painter.drawLine(Point(cast(int)stk[pidx-1].x, cast(int)stk[pidx-1].y), Point(cast(int)stk[pidx-1].x+1, cast(int)stk[pidx-1].y+1));
231 first = true;
236 void drawTemplate (DPPointCloud stk) {
237 if (stk is null) return;
238 auto pts = stk.points;
239 if (pts.length < 2) return;
240 bool first = true;
241 uint lastid = uint.max;
242 double x0, y0, x1, y1;
243 foreach (uint idx, const ref DPPoint p; pts) {
244 auto g = cast(ubyte)(255*idx/(pts.length-1));
245 auto b = cast(ubyte)(255-(255*idx/(pts.length-1)));
247 if (first) {
248 lastid = p.id;
249 first = false;
250 continue;
252 if (p.id == lastid) {
253 x0 = pts[idx-1].x;
254 y0 = pts[idx-1].y;
255 x1 = pts[idx].x;
256 y1 = pts[idx].y;
257 } else {
258 x0 = x1 = pts[idx-1].x;
259 y0 = y1 = pts[idx-1].y;
260 first = true;
263 //immutable p0 = stk.normPoint(idx-1), p1 = stk.normPoint(idx);
264 x0 = x0*200+400;
265 y0 = y0*200+300;
266 x1 = x1*200+400;
267 y1 = y1*200+300;
268 painter.outlineColor = Color(0, g, b);
269 painter.drawLine(Point(cast(int)x0, cast(int)y0), Point(cast(int)x1, cast(int)y1));
273 void drawStrokeList (int curptr) {
274 auto names = glib.knownGestureNames; //WARNING: this allocates like crazy!
275 int wdt = nameMaxLen*CharWidth+4;
276 int hgt = cast(int)(names.length*CharHeight+4);
277 painter.outlineColor = Color.white;
278 painter.fillColor = Color.transparent;
279 painter.drawRectangle(Point(0, 0), wdt, hgt);
280 painter.outlineColor = Color.black;
281 painter.drawRectangle(Point(1, 1), wdt-2, hgt-2);
282 painter.fillColor = Color.white;
283 painter.drawRectangle(Point(2, 2), wdt-4, hgt-4);
284 painter.fillColor = Color.transparent;
285 foreach (immutable idx, string name; names) {
286 Color col, bkcol;
287 if (name == detectedGlyph) {
288 // highlighted
289 col = Color(255, 255, 255);
290 //bkcol = rgb2col(0, 0, 255);
291 bkcol = Color(0, 100, 0);
292 } else {
293 col = Color(255, 127, 0);
294 bkcol = Color(0, 0, 127);
296 if (curptr == idx) bkcol = Color(0, 127, 0);
297 foreach (uint gidx; 0..uint.max) {
298 auto fg = glib.findGesture(name, gidx);
299 if (fg is null) break;
300 if (fg is curPattern) { col = Color(255, 255, 0); break; }
302 painter.outlineColor = bkcol;
303 painter.fillColor = bkcol;
304 painter.drawRectangle(Point(2, idx*CharHeight+2), wdt-4, CharHeight);
305 painter.outlineColor = col;
306 painter.fillColor = Color.transparent;
307 drawText(2, idx*CharHeight+2, name);
311 painter.outlineColor = Color.black;
312 painter.fillColor = Color.black;
313 painter.drawRectangle(Point(0, 0), sdwin.width, sdwin.height);
315 if (curPattern !is null) drawTemplate(curPattern);
316 drawStrokeList(curGlyph);
317 if (drawnGlyph.length) drawStroke(drawnGlyph);
318 if (yesNoMessage.length > 0) {
319 painter.outlineColor = Color(128, 0, 0);
320 painter.fillColor = Color(128, 0, 0);
321 painter.drawRectangle(Point(0, sdwin.height-CharHeight), sdwin.width, CharHeight);
322 painter.outlineColor = Color(255, 255, 0);
323 painter.fillColor = Color.transparent;
324 drawText(0, sdwin.height-CharHeight, yesNoMessage);
325 } else if (editingName) {
326 painter.outlineColor = Color(0, 0, 190);
327 painter.fillColor = Color(0, 0, 190);
328 painter.drawRectangle(Point(0, sdwin.height-CharHeight), sdwin.width, CharHeight);
329 painter.outlineColor = Color(255, 127, 0);
330 painter.fillColor = Color.transparent;
331 drawText(0, sdwin.height-CharHeight, curGlyphName);
332 painter.outlineColor = Color(255, 255, 0);
333 painter.fillColor = Color(255, 255, 0);
334 painter.drawRectangle(Point(CharWidth*cast(int)curGlyphName.length, sdwin.height-CharHeight), CharWidth, CharHeight);
335 painter.outlineColor = Color(255, 127, 0);
336 painter.fillColor = Color.transparent;
338 if (msgAlpha >= 0 && msgText.length) {
339 int y = sdwin.height-CharHeight;
340 painter.outlineColor = Color(60, 60, 90);
341 painter.fillColor = Color(60, 60, 90);
342 painter.drawRectangle(Point(0, y), sdwin.width, CharHeight);
343 painter.outlineColor = Color(255, 255, 255);
344 painter.fillColor = Color.transparent;
345 drawText((sdwin.width-CharWidth*cast(int)msgText.length)/2, y, msgText);
346 painter.fillColor = Color.transparent;
348 if (helpVisible) {
349 helpOverlay();
352 flushGui();
356 // ////////////////////////////////////////////////////////////////////////// //
357 int mdown = 0;
360 int getSelectedGlyph (int x, int y) {
361 int wdt = nameMaxLen*CharWidth+4;
362 int hgt = cast(int)(glib.knownGestureNames.length*CharHeight+4);
363 if (x >= 2 && y >= 2 && x < wdt-4 && y < hgt-4) {
364 return cast(int)((y-2)/CharHeight);
365 } else {
366 return -1;
371 // ////////////////////////////////////////////////////////////////////////// //
372 void main (string[] args) {
373 glib = new DPGestureList();
374 glib.load(VFile("zgest.gsl"));
375 fixNameMaxLen();
376 writefln("%s strokes loaded", glib.knownGestureNames.length);
377 //gstLibSave(File("strokes_new.dat", "w"), glib[]);
378 sdwin = new SimpleWindow(800, 600, "Protractor Gesture Recognizer test");
379 frameChanged();
380 sdwin.eventLoop(100,
381 // pulse timer
382 delegate () {
383 updateMsg();
385 // mouse events
386 delegate (MouseEvent event) {
387 switch (event.type) {
388 case MouseEventType.buttonPressed:
389 if (yesNoMessage.length > 0 || editingName) break;
390 if (mdown == 0) {
391 if (event.button == MouseButton.left) {
392 auto ng = getSelectedGlyph(event.x, event.y);
393 if (ng >= 0) {
394 auto names = glib.knownGestureNames;
395 if (ng < names.length) {
396 curPattern = glib.findGesture(names[ng]);
397 frameChanged();
399 return;
402 if (event.button == MouseButton.left || event.button == MouseButton.right) {
403 mdown = (event.button == MouseButton.left ? 1 : 2);
404 if (detectedGlyph.length == 0 && (event.modifierState&ModifierState.ctrl)) {
405 // new segment
406 if (drawnGlyph.length > 0) {
407 drawnGlyph ~= DPPoint(event.x, event.y, drawnGlyph[$-1].id+1);
408 } else {
409 drawnGlyph ~= DPPoint(event.x, event.y, 1);
411 } else {
412 detectedGlyph = null;
413 drawnGlyph.length = 0;
414 drawnGlyph ~= DPPoint(event.x, event.y, 1);
416 frameChanged();
419 break;
420 case MouseEventType.buttonReleased:
421 if (yesNoMessage.length > 0 || editingName) return;
422 if (mdown != 0) {
423 if (drawnGlyph.length > 0) {
424 if (mdown == 1) {
425 if (event.modifierState&ModifierState.ctrl) {
426 // segment end
427 } else {
428 auto res = glib.recognize(drawnGlyph);
429 if (res.valid) {
430 detectedGlyph = res.name;
431 showMessage("glyph: '"~detectedGlyph~"'");
432 writeln("glyph: '", detectedGlyph, "'; score: ", res.score);
433 } else {
434 curGlyphName = (curPattern !is null ? curPattern.name : "");
435 editingName = true;
439 } else {
440 drawnGlyph = null;
442 frameChanged();
444 mdown = 0;
445 break;
446 case MouseEventType.motion:
447 if (yesNoMessage.length > 0 || editingName) break;
448 if (mdown == 0) {
449 auto ng = getSelectedGlyph(event.x, event.y);
450 if (ng != curGlyph) {
451 curGlyph = ng;
452 frameChanged();
454 } else if (mdown != 0) {
455 if (drawnGlyph.length > 0) {
456 drawnGlyph ~= DPPoint(event.x, event.y, drawnGlyph[$-1].id);
458 frameChanged();
460 break;
461 default:
464 // keyboard events
465 delegate (KeyEvent event) {
466 if (!event.pressed && event.key == Key.Ctrl) {
467 if (drawnGlyph.length) {
468 auto res = glib.recognize(drawnGlyph);
469 if (res.valid) {
470 detectedGlyph = res.name;
471 showMessage("glyph: '"~detectedGlyph~"'");
472 writeln("glyph: '", detectedGlyph, "'; score: ", res.score);
473 } else {
474 curGlyphName = (curPattern !is null ? curPattern.name : "");
475 editingName = true;
478 return;
480 if (!event.pressed) return;
481 if (helpVisible) {
482 if (event == "Escape" || event == "F1") {
483 helpVisible = false;
484 frameChanged();
486 return;
488 if (yesNoMessage.length > 0) {
489 if (event == "Escape") {
490 yesNoMessage = null;
491 } else if (event == "Enter") {
492 if (curPattern !is null) glib.removeGesture(curPattern.name);
493 detectedGlyph = null;
494 curPattern = null;
495 yesNoMessage = null;
497 frameChanged();
498 return;
500 if (event == "C-Q") { sdwin.close(); return; }
501 if (event == "C-T") { addTestGestures(glib); fixNameMaxLen(); frameChanged(); return; }
502 if (event == "F1") {
503 helpVisible = true;
504 frameChanged();
505 return;
507 if (event == "F2") {
508 glib.save(VFile("strokes.dat", "w"));
509 //writefln("%s strokes saved", glib.length);
510 frameChanged();
511 return;
513 if (event == "F3") {
514 glib.load(VFile("strokes.dat"));
515 fixNameMaxLen();
516 //writefln("%s strokes loaded", glib.length);
517 detectedGlyph = null;
518 curPattern = null;
519 drawnGlyph = null;
520 frameChanged();
521 return;
523 if (event == "Delete") {
524 if (curPattern !is null) {
525 yesNoMessage = "Remove '"~curPattern.name~"'?";
526 frameChanged();
527 return;
531 // characters
532 delegate (dchar ch) {
533 if (!editingName) return;
534 if (ch == 27) { editingName = false; frameChanged(); return; }
535 if (ch == 8) {
536 if (curGlyphName.length > 0) curGlyphName = curGlyphName[0..$-1];
537 frameChanged();
538 return;
540 if (ch == 25) {
541 // C-Y
542 curGlyphName = null;
543 frameChanged();
544 return;
546 if (ch == 10 || ch == 13) {
547 registerGlyph();
548 editingName = false;
549 return;
551 if (ch >= ' ' && ch < 127) {
552 curGlyphName ~= cast(char)ch;
553 frameChanged();
554 return;