dynstring: use unified storage for both metadata and data
[iv.d.git] / protractor_test / gestures.d
blob4d9b014a26b28225b9ecfdd4c56431022b2b2762
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.gengpro1;
26 import iv.pxclock;
27 import iv.vfs.io;
30 // ////////////////////////////////////////////////////////////////////////// //
31 SimpleWindow sdwin;
34 // ////////////////////////////////////////////////////////////////////////// //
35 ulong msgHideTime = 0;
36 int msgAlpha = -1;
37 string msgText;
40 void updateMsg () {
41 if (msgAlpha >= 0) {
42 if (msgAlpha > 0) {
43 if ((msgAlpha += 10) > 255) {
44 msgAlpha = -1;
45 msgText = null;
47 } else {
48 if (clockMilli() >= msgHideTime) {
49 msgAlpha = 1;
52 frameChanged();
57 void showMessage (string msg) {
58 if (msg.length == 0) return;
60 ovlMsg = new VLOverlay(msg.length*6+6, 8+6);
61 ovlMsg.fillRect(0, 0, ovlMsg.width, ovlMsg.height, rgb2col(25, 69, 247));
62 ovlMsg.rect(0, 0, ovlMsg.width, ovlMsg.height, rgb2col(255, 255, 255));
63 ovlMsg.rect(1, 1, ovlMsg.width-2, ovlMsg.height-2, rgb2col(0, 0, 0));
64 Color fg = rgb2col(255, 255, 255);
65 int x = 3;
66 foreach (/+auto+/ ch; msg) {
68 switch (ch) {
69 case 1: fg = rgb2col(255, 255, 255); break;
70 case 2: fg = rgb2col(0, 255, 0); break;
71 case 3: fg = rgb2col(255, 255, 0); break;
72 case 4: fg = rgb2col(255, 127, 0); break;
73 default: break;
75 if (ch < 32) continue;
77 ovlMsg.drawChar(x, 3, ch, fg);
78 x += 6;
81 msgText = msg;
82 msgHideTime = clockMilli()+5000;
83 msgAlpha = 0;
84 frameChanged();
88 // ////////////////////////////////////////////////////////////////////////// //
89 PTGlyph[] glib;
90 int nameMaxLen = 0;
91 int curPattern = -1;
92 PTGlyph drawnGlyph;
93 PTGlyph detectedGlyph;
94 bool helpVisible;
97 void fixNameMaxLen () {
98 nameMaxLen = 0;
99 foreach (/*auto*/ g; glib) if (g.name.length > nameMaxLen) nameMaxLen = cast(int)g.name.length;
103 // ////////////////////////////////////////////////////////////////////////// //
104 int curGlyph = -1;
105 string curGlyphName;
106 bool editingName;
107 string yesNoMessage;
110 void registerGlyph () {
111 if (drawnGlyph !is null && drawnGlyph.valid && curGlyphName.length > 0) {
112 auto gg = drawnGlyph.clone;
113 gg.normalize(true); // drop original points
114 gg.name = curGlyphName;
115 usize gpos = usize.max;
116 foreach (/*auto*/ idx, /*auto*/ g; glib) if (g.name == curGlyphName) { gpos = idx; break; }
117 if (gpos != usize.max) {
118 glib[gpos] = gg;
119 } else {
120 gpos = glib.length;
121 glib ~= gg;
123 fixNameMaxLen();
124 curPattern = cast(int)gpos;
129 // ////////////////////////////////////////////////////////////////////////// //
130 enum CharWidth = 10;
131 enum CharHeight = 10;
133 void frameChanged () {
134 if (sdwin is null || sdwin.closed) return;
137 auto painter = sdwin.draw();
139 void drawText (int x, int y, const(char)[] str...) {
140 foreach (immutable char ch; str) {
141 foreach (immutable int dy; 0..CharHeight) {
142 ushort v = glConFont10.ptr[cast(ubyte)ch*CharHeight+dy];
143 foreach (immutable int dx; 0..CharWidth) {
144 if (v&0x8000) {
145 painter.drawPixel(Point(x+dx, y+dy));
146 //painter.drawLine(Point(x+dx, y+dy), Point(x+dx+1, y+dy+1));
148 v <<= 1;
151 x += CharWidth;
155 void helpOverlay () {
156 static immutable string[] helpText = [
157 "\x1fDemo actions",
158 "\x1f------------",
159 "\3keyboard:\1",
160 " \2F1\1: toggle help",
161 " \2F2\1: save library to '\4strokes.dat\1'",
162 " \2F3\1: replace library with '\4strokes.dat\1'",
163 " \2ESC\1: quit",
164 " \2DEL\1: delete selected stroke",
166 "\3mouse:\1",
167 " \2LMB\1: select name or start drawing",
168 " \2RMB\1: register current stroke as template",
171 static int stlen (string s) {
172 int res = 0;
173 foreach (immutable char ch; s) if (ch >= 32) ++res;
174 return res;
177 int maxlen = 0;
178 foreach (/*auto*/ s; helpText) {
179 auto ln = stlen(s);
180 if (ln > maxlen) maxlen = ln;
183 int wdt = (maxlen*CharWidth+6);
184 int hgt = (CharHeight*cast(int)helpText.length+6);
185 int x0 = (sdwin.width-wdt)/2;
186 int y0 = (sdwin.height-hgt)/2;
188 painter.outlineColor = Color(25, 69, 247);
189 painter.fillColor = Color(25, 69, 247);
190 painter.drawRectangle(Point(x0, y0), wdt, hgt);
191 painter.fillColor = Color.transparent;
192 painter.outlineColor = Color(255, 255, 255);
193 painter.drawRectangle(Point(x0, y0), wdt, hgt);
194 painter.outlineColor = Color.black;
195 painter.drawRectangle(Point(x0+1, y0+1), wdt-2, hgt-2);
197 foreach (/*auto*/ idx, /*auto*/ s; helpText) {
198 if (s.length == 0) continue;
199 auto ln = stlen(s)*CharWidth;
200 auto x = (wdt-ln)/2;
201 auto y = idx*CharHeight+3;
202 string st = s;
203 if (s[0] == '\x1f') {
204 st = s[1..$];
205 } else {
206 x = 3;
208 Color fg = Color(255, 255, 255);
209 foreach (/*auto*/ ch; st) {
210 switch (ch) {
211 case 1: fg = Color(255, 255, 255); break;
212 case 2: fg = Color(0, 255, 0); break;
213 case 3: fg = Color(255, 255, 0); break;
214 case 4: fg = Color(255, 127, 0); break;
215 default: break;
217 if (ch < 32) continue;
218 painter.outlineColor = fg;
219 drawText(x0+x, y0+y, ch);
220 x += CharWidth;
225 void drawStroke (const(PTGlyph) stk) {
226 painter.outlineColor = Color(255, 255, 0);
227 foreach (uint idx; 1..stk.length) {
228 immutable p0 = stk[idx-1], p1 = stk[idx];
229 double x0 = p0.x, y0 = p0.y;
230 double x1 = p1.x, y1 = p1.y;
231 painter.drawLine(Point(cast(int)x0, cast(int)y0), Point(cast(int)x1, cast(int)y1));
235 void drawTemplate (const(PTGlyph) stk) {
236 if (!stk.valid) return;
237 foreach (uint idx; 1..stk.normLength) {
238 auto g = cast(ubyte)(255*idx/(stk.normLength-1));
239 auto b = cast(ubyte)(255-(255*idx/(stk.normLength-1)));
240 immutable p0 = stk.normPoint(idx-1), p1 = stk.normPoint(idx);
241 double x0 = p0.x, y0 = p0.y;
242 double x1 = p1.x, y1 = p1.y;
243 x0 = x0*200+400;
244 y0 = y0*200+300;
245 x1 = x1*200+400;
246 y1 = y1*200+300;
247 painter.outlineColor = Color(0, g, b);
248 painter.drawLine(Point(cast(int)x0, cast(int)y0), Point(cast(int)x1, cast(int)y1));
252 void drawStrokeList (int curptr) {
253 int wdt = nameMaxLen*CharWidth+4;
254 int hgt = cast(int)(glib.length*CharHeight+4);
255 painter.outlineColor = Color.white;
256 painter.fillColor = Color.transparent;
257 painter.drawRectangle(Point(0, 0), wdt, hgt);
258 painter.outlineColor = Color.black;
259 painter.drawRectangle(Point(1, 1), wdt-2, hgt-2);
260 painter.fillColor = Color.white;
261 painter.drawRectangle(Point(2, 2), wdt-4, hgt-4);
262 painter.fillColor = Color.transparent;
263 foreach (/*auto*/ idx, /*auto*/ g; glib) {
264 Color col, bkcol;
265 if (g is detectedGlyph) {
266 // highlighted
267 col = Color(255, 255, 255);
268 //bkcol = rgb2col(0, 0, 255);
269 bkcol = Color(0, 100, 0);
270 } else {
271 col = Color(255, 127, 0);
272 bkcol = Color(0, 0, 127);
274 if (curptr == idx) bkcol = Color(0, 127, 0);
275 if (idx == curPattern) col = Color(255, 255, 0);
276 painter.outlineColor = bkcol;
277 painter.fillColor = bkcol;
278 painter.drawRectangle(Point(2, idx*CharHeight+2), wdt-4, CharHeight);
279 painter.outlineColor = col;
280 painter.fillColor = Color.transparent;
281 drawText(2, idx*CharHeight+2, g.name);
285 painter.outlineColor = Color.black;
286 painter.fillColor = Color.black;
287 painter.drawRectangle(Point(0, 0), sdwin.width, sdwin.height);
289 if (curPattern >= 0 && curPattern < cast(int)glib.length) drawTemplate(glib[curPattern]);
290 drawStrokeList(curGlyph);
291 if (drawnGlyph !is null && drawnGlyph.valid) drawStroke(drawnGlyph);
292 if (yesNoMessage.length > 0) {
293 painter.outlineColor = Color(128, 0, 0);
294 painter.fillColor = Color(128, 0, 0);
295 painter.drawRectangle(Point(0, sdwin.height-CharHeight), sdwin.width, CharHeight);
296 painter.outlineColor = Color(255, 255, 0);
297 painter.fillColor = Color.transparent;
298 drawText(0, sdwin.height-CharHeight, yesNoMessage);
299 } else if (editingName) {
300 painter.outlineColor = Color(0, 0, 190);
301 painter.fillColor = Color(0, 0, 190);
302 painter.drawRectangle(Point(0, sdwin.height-CharHeight), sdwin.width, CharHeight);
303 painter.outlineColor = Color(255, 127, 0);
304 painter.fillColor = Color.transparent;
305 drawText(0, sdwin.height-CharHeight, curGlyphName);
306 painter.outlineColor = Color(255, 255, 0);
307 painter.fillColor = Color(255, 255, 0);
308 painter.drawRectangle(Point(CharWidth*cast(int)curGlyphName.length, sdwin.height-CharHeight), CharWidth, CharHeight);
309 painter.outlineColor = Color(255, 127, 0);
310 painter.fillColor = Color.transparent;
312 if (msgAlpha >= 0 && msgText.length) {
313 int y = sdwin.height-CharHeight;
314 painter.outlineColor = Color(60, 60, 90);
315 painter.fillColor = Color(60, 60, 90);
316 painter.drawRectangle(Point(0, y), sdwin.width, CharHeight);
317 painter.outlineColor = Color(255, 255, 255);
318 painter.fillColor = Color.transparent;
319 drawText((sdwin.width-CharWidth*cast(int)msgText.length)/2, y, msgText);
320 painter.fillColor = Color.transparent;
322 if (helpVisible) {
323 helpOverlay();
326 flushGui();
330 // ////////////////////////////////////////////////////////////////////////// //
331 int mdown = 0;
334 int getSelectedGlyph (int x, int y) {
335 int wdt = nameMaxLen*CharWidth+4;
336 int hgt = cast(int)(glib.length*CharHeight+4);
337 if (x >= 2 && y >= 2 && x < wdt-4 && y < hgt-4) {
338 return cast(int)((y-2)/CharHeight);
339 } else {
340 return -1;
345 // ////////////////////////////////////////////////////////////////////////// //
346 void main (string[] args) {
347 glib = gstLibLoad(VFile("strokes.dat"));
348 fixNameMaxLen();
349 writefln("%s strokes loaded", glib.length);
350 //gstLibSave(File("strokes_new.dat", "w"), glib[]);
351 sdwin = new SimpleWindow(800, 600, "Protractor Gesture Recognizer test");
352 frameChanged();
353 sdwin.eventLoop(100,
354 // pulse timer
355 delegate () {
356 updateMsg();
358 // mouse events
359 delegate (MouseEvent event) {
360 switch (event.type) {
361 case MouseEventType.buttonPressed:
362 if (yesNoMessage.length > 0 || editingName) break;
363 if (mdown == 0) {
364 if (event.button == MouseButton.left) {
365 auto ng = getSelectedGlyph(event.x, event.y);
366 if (ng >= 0) {
367 curPattern = ng;
368 frameChanged();
369 return;
372 if (event.button == MouseButton.left || event.button == MouseButton.right) {
373 mdown = (event.button == MouseButton.left ? 1 : 2);
374 detectedGlyph = null;
375 drawnGlyph = new PTGlyph();
376 drawnGlyph.appendPoint(event.x, event.y);
377 frameChanged();
380 break;
381 case MouseEventType.buttonReleased:
382 if (yesNoMessage.length > 0 || editingName) return;
383 if (mdown != 0) {
384 if (drawnGlyph.valid) {
385 if (mdown == 1) {
386 GengFloat score;
387 detectedGlyph = cast(PTGlyph)drawnGlyph.findMatch(glib[], &score); // sorry
388 if (detectedGlyph !is null && detectedGlyph.name.length > 0) {
389 showMessage("glyph: '"~detectedGlyph.name~"'");
390 writeln("glyph: '", detectedGlyph.name, "'; score: ", score);
392 } else {
393 curGlyphName = (curPattern >= 0 ? glib[curPattern].name : "");
394 editingName = true;
396 } else {
397 drawnGlyph = null;
399 frameChanged();
401 mdown = 0;
402 break;
403 case MouseEventType.motion:
404 if (yesNoMessage.length > 0 || editingName) break;
405 if (mdown == 0) {
406 auto ng = getSelectedGlyph(event.x, event.y);
407 if (ng != curGlyph) {
408 curGlyph = ng;
409 frameChanged();
411 } else if (mdown != 0) {
412 drawnGlyph.appendPoint(event.x, event.y);
413 frameChanged();
415 break;
416 default:
419 // keyboard events
420 delegate (KeyEvent event) {
421 if (!event.pressed) return;
422 if (helpVisible) {
423 if (event == "Escape" || event == "F1") {
424 helpVisible = false;
425 frameChanged();
427 return;
429 if (yesNoMessage.length > 0) {
430 if (event == "Escape") {
431 yesNoMessage = null;
432 } else if (event == "Enter") {
433 glib = glib[0..curPattern]~glib[curPattern+1..$];
434 detectedGlyph = null;
435 curPattern = -1;
436 yesNoMessage = null;
438 frameChanged();
439 return;
441 if (event == "C-Q") { sdwin.close(); return; }
442 if (event == "F1") {
443 helpVisible = true;
444 frameChanged();
445 return;
447 if (event == "F2") {
448 gstLibSave(VFile("strokes.dat", "w"), glib[]);
449 writefln("%s strokes saved", glib.length);
450 frameChanged();
451 return;
453 if (event == "F3") {
454 glib = gstLibLoad(VFile("strokes.dat"));
455 fixNameMaxLen();
456 writefln("%s strokes loaded", glib.length);
457 detectedGlyph = null;
458 curPattern = -1;
459 drawnGlyph = null;
460 frameChanged();
461 return;
463 if (event == "Delete") {
464 if (curPattern >= 0) {
465 yesNoMessage = "Remove '"~glib[curPattern].name~"'?";
466 frameChanged();
467 return;
471 // characters
472 delegate (dchar ch) {
473 if (!editingName) return;
474 if (ch == 27) { editingName = false; frameChanged(); return; }
475 if (ch == 8) {
476 if (curGlyphName.length > 0) curGlyphName = curGlyphName[0..$-1];
477 frameChanged();
478 return;
480 if (ch == 25) {
481 // C-Y
482 curGlyphName = null;
483 frameChanged();
484 return;
486 if (ch == 10 || ch == 13) {
487 registerGlyph();
488 editingName = false;
489 return;
491 if (ch >= ' ' && ch < 127) {
492 curGlyphName ~= cast(char)ch;
493 frameChanged();
494 return;