1 /*
2 * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
3 *
4 * This is free software: you can redistribute it and/or modify
5 * it under the terms of the Artistic License 2.0 as published by
6 * The Perl Foundation.
7 *
8 * This source is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * Artistic License 2.0 for more details.
12 *
13 * You should have received a copy of the Artistic License 2.0
14 * along the source as a COPYING file. If not, obtain it from
15 * http://www.perlfoundation.org/artistic_license_2_0.
16 */
17
18 #include <stdio.h>
19 #include <math.h>
20 #include <string.h>
21 #include <inttypes.h>
22 #if !defined(_WIN32)
23 # include <poll.h>
24 #endif
25
26 #include "base_internal.h"
27
28 static inline d2tk_id_t
_d2tk_flip_get_cur(d2tk_flip_t * flip)29 _d2tk_flip_get_cur(d2tk_flip_t *flip)
30 {
31 return flip->cur;
32 }
33
34 static inline d2tk_id_t
_d2tk_flip_get_old(d2tk_flip_t * flip)35 _d2tk_flip_get_old(d2tk_flip_t *flip)
36 {
37 return flip->old;
38 }
39
40 static inline bool
_d2tk_flip_equal_cur(d2tk_flip_t * flip,d2tk_id_t id)41 _d2tk_flip_equal_cur(d2tk_flip_t *flip, d2tk_id_t id)
42 {
43 return _d2tk_flip_get_cur(flip) == id;
44 }
45
46 static inline bool
_d2tk_flip_equal_old(d2tk_flip_t * flip,d2tk_id_t id)47 _d2tk_flip_equal_old(d2tk_flip_t *flip, d2tk_id_t id)
48 {
49 return _d2tk_flip_get_old(flip) == id;
50 }
51
52 static inline bool
_d2tk_flip_invalid(d2tk_flip_t * flip)53 _d2tk_flip_invalid(d2tk_flip_t *flip)
54 {
55 return _d2tk_flip_equal_cur(flip, 0);
56 }
57
58 static inline bool
_d2tk_flip_invalid_old(d2tk_flip_t * flip)59 _d2tk_flip_invalid_old(d2tk_flip_t *flip)
60 {
61 return _d2tk_flip_equal_old(flip, 0);
62 }
63
64 static inline void
_d2tk_flip_set_old(d2tk_flip_t * flip,d2tk_id_t old)65 _d2tk_flip_set_old(d2tk_flip_t *flip, d2tk_id_t old)
66 {
67 flip->old = old;
68 }
69
70 static inline void
_d2tk_flip_set(d2tk_flip_t * flip,d2tk_id_t new)71 _d2tk_flip_set(d2tk_flip_t *flip, d2tk_id_t new)
72 {
73 if(_d2tk_flip_invalid_old(flip))
74 {
75 _d2tk_flip_set_old(flip, flip->cur);
76 }
77
78 flip->cur = new;
79 }
80
81 static inline void
_d2tk_flip_clear(d2tk_flip_t * flip)82 _d2tk_flip_clear(d2tk_flip_t *flip)
83 {
84 _d2tk_flip_set(flip, 0);
85 }
86
87 static inline void
_d2tk_flip_clear_old(d2tk_flip_t * flip)88 _d2tk_flip_clear_old(d2tk_flip_t *flip)
89 {
90 _d2tk_flip_set_old(flip, 0);
91 }
92
93 void *
_d2tk_base_get_atom(d2tk_base_t * base,d2tk_id_t id,d2tk_atom_type_t type,d2tk_atom_event_t event)94 _d2tk_base_get_atom(d2tk_base_t *base, d2tk_id_t id, d2tk_atom_type_t type,
95 d2tk_atom_event_t event)
96 {
97 for(unsigned i = 0, idx = (id + i*i) & _D2TK_MASK_ATOMS;
98 i < _D2TK_MAX_ATOM;
99 i++, idx = (id + i*i) & _D2TK_MASK_ATOMS)
100 {
101 d2tk_atom_t *atom = &base->atoms[idx];
102
103 if( (atom->id != 0) && (atom->id != id) )
104 {
105 continue;
106 }
107
108 if( (atom->id == 0) || (atom->type != type) || !atom->body) // new atom or changed type
109 {
110 atom->id = id;
111 atom->type = type;
112 atom->event = event;
113
114 size_t len;
115 switch(atom->type)
116 {
117 case D2TK_ATOM_SCROLL:
118 {
119 len = d2tk_atom_body_scroll_sz;
120 } break;
121 case D2TK_ATOM_PANE:
122 {
123 len = d2tk_atom_body_pane_sz;
124 } break;
125 case D2TK_ATOM_FLOW:
126 {
127 len = d2tk_atom_body_flow_sz;
128 } break;
129 #if D2TK_PTY
130 case D2TK_ATOM_PTY:
131 {
132 len = d2tk_atom_body_pty_sz;
133 } break;
134 #endif
135 #if D2TK_EVDEV
136 case D2TK_ATOM_VKB:
137 {
138 len = d2tk_atom_body_vkb_sz;
139 } break;
140 #endif
141 case D2TK_ATOM_FLOW_NODE:
142 // fall-through
143 case D2TK_ATOM_FLOW_ARC:
144 // fall-through
145 default:
146 {
147 len = 0;
148 } break;
149 }
150
151 if(len == 0)
152 {
153 if(atom->event)
154 {
155 atom->event(D2TK_ATOM_EVENT_DEINIT, atom->body);
156 atom->event = NULL;
157 }
158 free(atom->body);
159 atom->body = 0;
160 }
161 else
162 {
163 void *body = realloc(atom->body, len);
164 if(!body)
165 {
166 return NULL;
167 }
168
169 memset(body, 0x0, len);
170 atom->body = body;
171 }
172 }
173
174 return atom->body;
175 }
176
177 return NULL; // no space left
178 }
179
180 D2TK_API void
d2tk_clip_int32(int32_t min,int32_t * value,int32_t max)181 d2tk_clip_int32(int32_t min, int32_t *value, int32_t max)
182 {
183 if(*value < min)
184 {
185 *value = min;
186 }
187 else if(*value > max)
188 {
189 *value = max;
190 }
191 }
192
193 D2TK_API void
d2tk_clip_int64(int64_t min,int64_t * value,int64_t max)194 d2tk_clip_int64(int64_t min, int64_t *value, int64_t max)
195 {
196 if(*value < min)
197 {
198 *value = min;
199 }
200 else if(*value > max)
201 {
202 *value = max;
203 }
204 }
205
206 D2TK_API void
d2tk_clip_float(float min,float * value,float max)207 d2tk_clip_float(float min, float *value, float max)
208 {
209 if(*value < min)
210 {
211 *value = min;
212 }
213 else if(*value > max)
214 {
215 *value = max;
216 }
217 }
218
219 D2TK_API void
d2tk_clip_double(double min,double * value,double max)220 d2tk_clip_double(double min, double *value, double max)
221 {
222 if(*value < min)
223 {
224 *value = min;
225 }
226 else if(*value > max)
227 {
228 *value = max;
229 }
230 }
231
232 D2TK_API bool
d2tk_base_get_mod(d2tk_base_t * base)233 d2tk_base_get_mod(d2tk_base_t *base)
234 {
235 return base->keys.mod != D2TK_MODMASK_NONE;
236 }
237
238 D2TK_API const char *
d2tk_state_dump(d2tk_state_t state)239 d2tk_state_dump(d2tk_state_t state)
240 {
241 #define LEN 16
242 static char buf [LEN + 1];
243
244 for(unsigned i = 0; i < LEN; i++)
245 {
246 buf[LEN - 1 - i] = (1 << i) & state
247 ? '1'
248 : '.';
249 }
250
251 buf[LEN] = '\0';
252
253 return buf;
254 #undef LEN
255 }
256
257 D2TK_API bool
d2tk_state_is_down(d2tk_state_t state)258 d2tk_state_is_down(d2tk_state_t state)
259 {
260 return (state & D2TK_STATE_DOWN);
261 }
262
263 D2TK_API bool
d2tk_state_is_up(d2tk_state_t state)264 d2tk_state_is_up(d2tk_state_t state)
265 {
266 return (state & D2TK_STATE_UP);
267 }
268
269 D2TK_API bool
d2tk_state_is_scroll_up(d2tk_state_t state)270 d2tk_state_is_scroll_up(d2tk_state_t state)
271 {
272 return (state & D2TK_STATE_SCROLL_UP);
273 }
274
275 D2TK_API bool
d2tk_state_is_scroll_down(d2tk_state_t state)276 d2tk_state_is_scroll_down(d2tk_state_t state)
277 {
278 return (state & D2TK_STATE_SCROLL_DOWN);
279 }
280
281 D2TK_API bool
d2tk_state_is_motion(d2tk_state_t state)282 d2tk_state_is_motion(d2tk_state_t state)
283 {
284 return (state & D2TK_STATE_MOTION);
285 }
286
287 D2TK_API bool
d2tk_state_is_scroll_left(d2tk_state_t state)288 d2tk_state_is_scroll_left(d2tk_state_t state)
289 {
290 return (state & D2TK_STATE_SCROLL_LEFT);
291 }
292
293 D2TK_API bool
d2tk_state_is_scroll_right(d2tk_state_t state)294 d2tk_state_is_scroll_right(d2tk_state_t state)
295 {
296 return (state & D2TK_STATE_SCROLL_RIGHT);
297 }
298
299 D2TK_API bool
d2tk_state_is_active(d2tk_state_t state)300 d2tk_state_is_active(d2tk_state_t state)
301 {
302 return (state & D2TK_STATE_ACTIVE);
303 }
304
305 D2TK_API bool
d2tk_state_is_hot(d2tk_state_t state)306 d2tk_state_is_hot(d2tk_state_t state)
307 {
308 return (state & D2TK_STATE_HOT);
309 }
310
311 D2TK_API bool
d2tk_state_is_focused(d2tk_state_t state)312 d2tk_state_is_focused(d2tk_state_t state)
313 {
314 return (state & D2TK_STATE_FOCUS);
315 }
316
317 D2TK_API bool
d2tk_state_is_focus_in(d2tk_state_t state)318 d2tk_state_is_focus_in(d2tk_state_t state)
319 {
320 return (state & D2TK_STATE_FOCUS_IN);
321 }
322
323 D2TK_API bool
d2tk_state_is_focus_out(d2tk_state_t state)324 d2tk_state_is_focus_out(d2tk_state_t state)
325 {
326 return (state & D2TK_STATE_FOCUS_OUT);
327 }
328
329 D2TK_API bool
d2tk_state_is_changed(d2tk_state_t state)330 d2tk_state_is_changed(d2tk_state_t state)
331 {
332 return (state & D2TK_STATE_CHANGED);
333 }
334
335 D2TK_API bool
d2tk_state_is_enter(d2tk_state_t state)336 d2tk_state_is_enter(d2tk_state_t state)
337 {
338 return (state & D2TK_STATE_ENTER);
339 }
340
341 D2TK_API bool
d2tk_state_is_over(d2tk_state_t state)342 d2tk_state_is_over(d2tk_state_t state)
343 {
344 return (state & D2TK_STATE_OVER);
345 }
346
347 D2TK_API bool
d2tk_state_is_close(d2tk_state_t state)348 d2tk_state_is_close(d2tk_state_t state)
349 {
350 return (state & D2TK_STATE_CLOSE);
351 }
352
353 D2TK_API bool
d2tk_state_is_bell(d2tk_state_t state)354 d2tk_state_is_bell(d2tk_state_t state)
355 {
356 return (state & D2TK_STATE_BELL);
357 }
358
359 D2TK_API bool
d2tk_base_is_hit(d2tk_base_t * base,const d2tk_rect_t * rect)360 d2tk_base_is_hit(d2tk_base_t *base, const d2tk_rect_t *rect)
361 {
362 if( (base->mouse.x < rect->x)
363 || (base->mouse.y < rect->y)
364 || (base->mouse.x >= rect->x + rect->w)
365 || (base->mouse.y >= rect->y + rect->h) )
366 {
367 return false;
368 }
369
370 return true;
371 }
372
373 static inline bool
_d2tk_base_set_focus(d2tk_base_t * base,d2tk_id_t id)374 _d2tk_base_set_focus(d2tk_base_t *base, d2tk_id_t id)
375 {
376 _d2tk_flip_set(&base->focusitem, id);
377
378 return true;
379 }
380
381 static inline void
_d2tk_base_change_focus(d2tk_base_t * base)382 _d2tk_base_change_focus(d2tk_base_t *base)
383 {
384 // copy edit.text_in to edit.text_out
385 strncpy(base->edit.text_out, base->edit.text_in, sizeof(base->edit.text_out));
386 }
387
388 void
_d2tk_base_clear_chars(d2tk_base_t * base)389 _d2tk_base_clear_chars(d2tk_base_t *base)
390 {
391 base->keys.nchars = 0;
392 }
393
394 static void
_d2tk_base_append_utf8(d2tk_base_t * base,utf8_int32_t utf8)395 _d2tk_base_append_utf8(d2tk_base_t *base, utf8_int32_t utf8)
396 {
397 if(base->keys.nchars < sizeof(base->keys.nchars))
398 {
399 base->keys.chars[base->keys.nchars++] = utf8;
400 }
401 }
402
403 D2TK_API void
d2tk_base_append_utf8(d2tk_base_t * base,utf8_int32_t utf8)404 d2tk_base_append_utf8(d2tk_base_t *base, utf8_int32_t utf8)
405 {
406 if( !base->unicode_mode
407 && d2tk_base_get_modmask(base, D2TK_MODMASK_CTRL, false)
408 && d2tk_base_get_modmask(base, D2TK_MODMASK_SHIFT, false)
409 && (utf8 == 'u' - 0x60) ) // FIXME where the hello does the offset come from?
410 {
411 base->unicode_acc = 0;
412 base->unicode_mode = true;
413 }
414 else if(base->unicode_mode)
415 {
416 if(utf8 == ' ')
417 {
418 _d2tk_base_append_utf8(base, base->unicode_acc);
419
420 base->unicode_mode = false;
421 }
422 else
423 {
424 char str [2] = { utf8, 0x0 };
425 const uint32_t fig = strtol(str, NULL, 16);
426
427 base->unicode_acc <<= 4;
428 base->unicode_acc |= fig;
429 }
430 }
431 else
432 {
433 _d2tk_base_append_utf8(base, utf8);
434 }
435 }
436
437 D2TK_API void
d2tk_base_get_utf8(d2tk_base_t * base,ssize_t * len,const utf8_int32_t ** utf8)438 d2tk_base_get_utf8(d2tk_base_t *base, ssize_t *len, const utf8_int32_t **utf8)
439 {
440 if(len)
441 {
442 *len = base->keys.nchars;
443 }
444
445 if(utf8)
446 {
447 *utf8 = base->keys.chars;
448 }
449
450 _d2tk_base_clear_chars(base);
451 }
452
453 d2tk_state_t
_d2tk_base_is_active_hot_vertical_scroll(d2tk_base_t * base)454 _d2tk_base_is_active_hot_vertical_scroll(d2tk_base_t *base)
455 {
456 d2tk_state_t state = D2TK_STATE_NONE;
457
458 // test for vertical scrolling
459 if(base->scroll.dy != 0.f)
460 {
461 if(base->scroll.dy > 0.f)
462 {
463 state |= D2TK_STATE_SCROLL_UP;
464 }
465 else
466 {
467 state |= D2TK_STATE_SCROLL_DOWN;
468 }
469
470 base->scroll.ody = base->scroll.dy;
471 base->scroll.dy = 0; // eat scrolling
472 }
473
474 return state;
475 }
476
477 d2tk_state_t
_d2tk_base_is_active_hot_horizontal_scroll(d2tk_base_t * base)478 _d2tk_base_is_active_hot_horizontal_scroll(d2tk_base_t *base)
479 {
480 d2tk_state_t state = D2TK_STATE_NONE;
481
482 // test for horizontal scrolling
483 if(base->scroll.dx != 0.f)
484 {
485 if(base->scroll.dx > 0.f)
486 {
487 state |= D2TK_STATE_SCROLL_RIGHT;
488 }
489 else
490 {
491 state |= D2TK_STATE_SCROLL_LEFT;
492 }
493
494 base->scroll.odx = base->scroll.dx;
495 base->scroll.dx = 0; // eat scrolling
496 }
497
498 return state;
499 }
500
501 D2TK_API d2tk_state_t
d2tk_base_is_active_hot(d2tk_base_t * base,d2tk_id_t id,const d2tk_rect_t * rect,d2tk_flag_t flags)502 d2tk_base_is_active_hot(d2tk_base_t *base, d2tk_id_t id,
503 const d2tk_rect_t *rect, d2tk_flag_t flags)
504 {
505 d2tk_state_t state = D2TK_STATE_NONE;
506 bool is_active = _d2tk_flip_equal_cur(&base->activeitem, id);
507 bool is_hot = false;
508 bool is_over = false;
509 bool curfocus = _d2tk_flip_equal_cur(&base->focusitem, id);
510 bool newfocus = curfocus;
511 const bool lastfocus = _d2tk_flip_equal_old(&base->focusitem, id);
512
513 // test for mouse up
514 if( is_active
515 && !d2tk_base_get_butmask(base, D2TK_BUTMASK_LEFT, false) )
516 {
517 _d2tk_flip_clear(&base->activeitem);
518 is_active = false;
519 state |= D2TK_STATE_UP;
520 }
521
522 // handle forward focus
523 if(curfocus)
524 {
525 if(d2tk_base_get_modmask(base, D2TK_MODMASK_CTRL, false))
526 {
527 if(d2tk_base_get_keymask(base, D2TK_KEYMASK_RIGHT, false))
528 {
529 newfocus = false; // do NOT change curfocus
530 base->focused = false; // clear focused flag
531 }
532 }
533 else
534 {
535 if(d2tk_base_get_keymask(base, D2TK_KEYMASK_LEFT, false))
536 {
537 state |= D2TK_STATE_SCROLL_LEFT;
538 base->scroll.odx = -1;
539 }
540
541 if(d2tk_base_get_keymask(base, D2TK_KEYMASK_RIGHT, false))
542 {
543 state |= D2TK_STATE_SCROLL_RIGHT;
544 base->scroll.odx = 1;
545 }
546
547 if(d2tk_base_get_keymask(base, D2TK_KEYMASK_UP, false))
548 {
549 state |= D2TK_STATE_SCROLL_UP;
550 base->scroll.ody = 1;
551 }
552
553 if(d2tk_base_get_keymask(base, D2TK_KEYMASK_DOWN, false))
554 {
555 state |= D2TK_STATE_SCROLL_DOWN;
556 base->scroll.ody = -1;
557 }
558 }
559
560 if(d2tk_base_get_keymask_up(base, D2TK_KEYMASK_ENTER))
561 {
562 is_active = false;
563 }
564 else if(d2tk_base_get_keymask_down(base, D2TK_KEYMASK_ENTER))
565 {
566 is_active = true;
567 state |= D2TK_STATE_ENTER;
568 }
569 else if(d2tk_base_get_keymask(base, D2TK_KEYMASK_ENTER, false))
570 {
571 is_active = true;
572 }
573 }
574 else if(!base->focused)
575 {
576 curfocus = _d2tk_base_set_focus(base, id);
577 newfocus = curfocus;
578 base->focused = true; // set focused flag
579 }
580
581 // test for mouse over
582 if(d2tk_base_is_hit(base, rect))
583 {
584 // test for mouse down
585 if( _d2tk_flip_invalid(&base->activeitem)
586 && d2tk_base_get_butmask(base, D2TK_BUTMASK_LEFT, false) )
587 {
588 _d2tk_flip_set(&base->activeitem, id);
589 is_active = true;
590 curfocus = _d2tk_base_set_focus(base, id);
591 newfocus = curfocus;
592 state |= D2TK_STATE_DOWN;
593 }
594
595 if(d2tk_base_get_butmask(base, D2TK_BUTMASK_LEFT, false) && !is_active)
596 {
597 // another widget is active with mouse down, so don't be hot
598 }
599 else
600 {
601 _d2tk_flip_set(&base->hotitem, id);
602 is_hot = true;
603 }
604
605 is_over = true;
606
607 // test whether to handle scrolling
608 if(flags & D2TK_FLAG_SCROLL_Y)
609 {
610 state |= _d2tk_base_is_active_hot_vertical_scroll(base);
611 }
612
613 if(flags & D2TK_FLAG_SCROLL_X)
614 {
615 state |= _d2tk_base_is_active_hot_horizontal_scroll(base);
616 }
617 }
618
619 if(is_active)
620 {
621 if( (base->mouse.dx != 0) || (base->mouse.dy != 0) )
622 {
623 state |= D2TK_STATE_MOTION;
624 }
625
626 state |= D2TK_STATE_ACTIVE;
627 }
628
629 if(is_hot)
630 {
631 state |= D2TK_STATE_HOT;
632 }
633
634 if(is_over)
635 {
636 state |= D2TK_STATE_OVER;
637 }
638
639 if(newfocus)
640 {
641 state |= D2TK_STATE_FOCUS;
642 }
643
644 {
645 if(lastfocus && !curfocus)
646 {
647 state |= D2TK_STATE_FOCUS_OUT;
648 _d2tk_flip_clear_old(&base->focusitem); // clear previous focus
649 #if D2TK_DEBUG
650 fprintf(stderr, "\tfocus out 0x%016"PRIx64"\n", id);
651 #endif
652 }
653 else if(!lastfocus && curfocus)
654 {
655 if(_d2tk_flip_invalid_old(&base->focusitem) && base->not_first_time)
656 {
657 _d2tk_flip_set(&base->focusitem, _d2tk_flip_get_cur(&base->focusitem));
658 }
659 else
660 {
661 state |= D2TK_STATE_FOCUS_IN;
662 #if D2TK_DEBUG
663 fprintf(stderr, "\tfocus in 0x%016"PRIx64"\n", id);
664 #endif
665 _d2tk_base_change_focus(base);
666 }
667 }
668 }
669
670 // handle backwards focus
671 if(newfocus)
672 {
673 if(d2tk_base_get_modmask(base, D2TK_MODMASK_CTRL, false))
674 {
675 if(d2tk_base_get_keymask(base, D2TK_KEYMASK_LEFT, false))
676 {
677 _d2tk_base_set_focus(base, base->lastitem);
678 }
679 }
680 }
681
682 base->lastitem = id;
683
684 base->not_first_time = true;
685
686 return state;
687 }
688
689 #define nocol 0x0
690 #define light_grey 0x7f7f7fff
691 #define dark_grey 0x3f3f3fff
692 #define darker_grey 0x222222ff
693 #define black 0x000000ff
694 #define white 0xffffffff
695 #define light_orange 0xffcf00ff
696 #define dark_orange 0xcf9f00ff
697
698 #define FONT_SANS_BOLD "FiraSans:bold"
699
700 D2TK_API const d2tk_style_t *
d2tk_base_get_default_style()701 d2tk_base_get_default_style()
702 {
703 static const d2tk_style_t style = {
704 .font_face = FONT_SANS_BOLD,
705 .border_width = 1,
706 .padding = 1,
707 .rounding = 4,
708 .bg_color = darker_grey,
709 .fill_color = {
710 [D2TK_TRIPLE_NONE] = dark_grey,
711 [D2TK_TRIPLE_HOT] = light_grey,
712 [D2TK_TRIPLE_ACTIVE] = dark_orange,
713 [D2TK_TRIPLE_ACTIVE_HOT] = light_orange,
714 [D2TK_TRIPLE_FOCUS] = dark_grey,
715 [D2TK_TRIPLE_HOT_FOCUS] = light_grey,
716 [D2TK_TRIPLE_ACTIVE_FOCUS] = dark_orange,
717 [D2TK_TRIPLE_ACTIVE_HOT_FOCUS] = light_orange,
718 },
719 .stroke_color = {
720 [D2TK_TRIPLE_NONE] = black,
721 [D2TK_TRIPLE_HOT] = black,
722 [D2TK_TRIPLE_ACTIVE] = black,
723 [D2TK_TRIPLE_ACTIVE_HOT] = black,
724 [D2TK_TRIPLE_FOCUS] = white,
725 [D2TK_TRIPLE_HOT_FOCUS] = white,
726 [D2TK_TRIPLE_ACTIVE_FOCUS] = white,
727 [D2TK_TRIPLE_ACTIVE_HOT_FOCUS] = white,
728 },
729 .text_stroke_color = {
730 [D2TK_TRIPLE_NONE] = white,
731 [D2TK_TRIPLE_HOT] = light_orange,
732 [D2TK_TRIPLE_ACTIVE] = white,
733 [D2TK_TRIPLE_ACTIVE_HOT] = dark_grey,
734 [D2TK_TRIPLE_FOCUS] = white,
735 [D2TK_TRIPLE_HOT_FOCUS] = light_orange,
736 [D2TK_TRIPLE_ACTIVE_FOCUS] = white,
737 [D2TK_TRIPLE_ACTIVE_HOT_FOCUS] = dark_grey
738 },
739 .text_fill_color = {
740 [D2TK_TRIPLE_NONE] = nocol,
741 [D2TK_TRIPLE_HOT] = nocol,
742 [D2TK_TRIPLE_ACTIVE] = nocol,
743 [D2TK_TRIPLE_ACTIVE_HOT] = nocol,
744 [D2TK_TRIPLE_FOCUS] = nocol,
745 [D2TK_TRIPLE_HOT_FOCUS] = nocol,
746 [D2TK_TRIPLE_ACTIVE_FOCUS] = nocol,
747 [D2TK_TRIPLE_ACTIVE_HOT_FOCUS] = nocol
748 }
749 };
750
751 return &style;
752 }
753
754 D2TK_API const d2tk_style_t *
d2tk_base_get_style(d2tk_base_t * base)755 d2tk_base_get_style(d2tk_base_t *base)
756 {
757 return base->style ? base->style : d2tk_base_get_default_style();
758 }
759
760 D2TK_API void
d2tk_base_set_style(d2tk_base_t * base,const d2tk_style_t * style)761 d2tk_base_set_style(d2tk_base_t *base, const d2tk_style_t *style)
762 {
763 base->style = style;
764 }
765
766 D2TK_API void
d2tk_base_set_default_style(d2tk_base_t * base)767 d2tk_base_set_default_style(d2tk_base_t *base)
768 {
769 d2tk_base_set_style(base, NULL);
770 }
771
772 D2TK_API d2tk_base_t *
d2tk_base_new(const d2tk_core_driver_t * driver,void * data)773 d2tk_base_new(const d2tk_core_driver_t *driver, void *data)
774 {
775 d2tk_base_t *base = calloc(1, sizeof(d2tk_base_t));
776 if(!base)
777 {
778 return NULL;
779 }
780
781 atomic_init(&base->again, false);
782
783 base->core = d2tk_core_new(driver, data);
784
785 return base;
786 }
787
788 D2TK_API void
d2tk_base_set_ttls(d2tk_base_t * base,uint32_t sprites,uint32_t memcaches)789 d2tk_base_set_ttls(d2tk_base_t *base, uint32_t sprites, uint32_t memcaches)
790 {
791 d2tk_core_set_ttls(base->core, sprites, memcaches);
792 }
793
794 D2TK_API void
d2tk_base_free(d2tk_base_t * base)795 d2tk_base_free(d2tk_base_t *base)
796 {
797 for(unsigned i = 0; i < _D2TK_MAX_ATOM; i++)
798 {
799 d2tk_atom_t *atom = &base->atoms[i];
800
801 atom->id = 0;
802 atom->type = 0;
803 if(atom->event)
804 {
805 atom->event(D2TK_ATOM_EVENT_DEINIT, atom->body);
806 atom->event = NULL;
807 }
808 free(atom->body);
809 atom->body = NULL;
810 }
811
812 d2tk_core_free(base->core);
813 free(base);
814 }
815
816 D2TK_API int
d2tk_base_pre(d2tk_base_t * base,void * pctx)817 d2tk_base_pre(d2tk_base_t *base, void *pctx)
818 {
819 // reset hot item
820 _d2tk_flip_clear(&base->hotitem);
821
822 // calculate mouse motion
823 base->mouse.dx = (int32_t)base->mouse.x - base->mouse.ox;
824 base->mouse.dy = (int32_t)base->mouse.y - base->mouse.oy;
825
826 // reset clear-focus flag
827 base->clear_focus = false;
828
829 // reset tooltip
830 d2tk_base_clear_tooltip(base);
831
832 const d2tk_style_t *style = d2tk_base_get_style(base);
833 d2tk_core_set_bg_color(base->core, style->bg_color);
834
835 return d2tk_core_pre(base->core, pctx);
836 }
837
838 D2TK_API void
d2tk_base_post(d2tk_base_t * base)839 d2tk_base_post(d2tk_base_t *base)
840 {
841 // draw tooltip
842 if(base->tooltip.len > 0)
843 {
844 _d2tk_base_tooltip_draw(base, base->tooltip.len, base->tooltip.buf,
845 base->tooltip.h);
846 }
847
848 // clear scroll
849 base->scroll.dx = 0.f;
850 base->scroll.dy = 0.f;
851
852 // store old mouse position
853 base->mouse.ox = base->mouse.x;
854 base->mouse.oy = base->mouse.y;
855
856 if(base->clear_focus)
857 {
858 _d2tk_flip_clear(&base->activeitem);
859
860 base->focused = false;
861 }
862
863 base->mouse.mask_prev = base->mouse.mask;
864 base->keys.mask_prev = base->keys.mask;
865
866 _d2tk_base_clear_chars(base);
867
868 d2tk_core_post(base->core);
869 }
870
871 static int
_d2tk_base_probe(int fd)872 _d2tk_base_probe(int fd)
873 {
874 if(fd <= 0)
875 {
876 return 0;
877 }
878
879 #if !defined(_WIN32)
880 struct pollfd fds = {
881 .fd = fd,
882 .events = POLLIN,
883 .revents = 0
884 };
885
886 switch(poll(&fds, 1, 0))
887 {
888 case -1:
889 {
890 //printf("[%s] error: %s\n", __func__, strerror(errno));
891 } return 0;
892 case 0:
893 {
894 //printf("[%s] timeout\n", __func__);
895 } return 0;
896
897 default:
898 {
899 //printf("[%s] ready\n", __func__);
900 } return 1;
901 }
902 #else
903 return 0;
904 #endif
905 }
906
907 D2TK_API void
d2tk_base_probe(d2tk_base_t * base)908 d2tk_base_probe(d2tk_base_t *base)
909 {
910 for(unsigned i = 0; i < _D2TK_MAX_ATOM; i++)
911 {
912 d2tk_atom_t *atom = &base->atoms[i];
913
914 if(atom->id && atom->type && atom->event)
915 {
916 const int fd = atom->event(D2TK_ATOM_EVENT_FD, atom->body);
917
918 if(_d2tk_base_probe(fd))
919 {
920 d2tk_base_set_again(base);
921 break;
922 }
923 }
924 }
925 }
926
927 D2TK_API int
d2tk_base_get_file_descriptors(d2tk_base_t * base,int * fds,int numfds)928 d2tk_base_get_file_descriptors(d2tk_base_t *base, int *fds, int numfds)
929 {
930 int idx = 0;
931
932 for(unsigned i = 0; i < _D2TK_MAX_ATOM; i++)
933 {
934 d2tk_atom_t *atom = &base->atoms[i];
935
936 if(atom->id && atom->type && atom->event)
937 {
938 const int fd = atom->event(D2TK_ATOM_EVENT_FD, atom->body);
939
940 if( (fd > 0) && (idx < numfds) )
941 {
942 fds[idx++] = fd;
943 }
944 }
945 }
946
947 return idx;
948 }
949
950 D2TK_API void
d2tk_base_clear_focus(d2tk_base_t * base)951 d2tk_base_clear_focus(d2tk_base_t *base)
952 {
953 base->clear_focus = true;
954 }
955
956 D2TK_API bool
d2tk_base_set_again(d2tk_base_t * base)957 d2tk_base_set_again(d2tk_base_t *base)
958 {
959 return atomic_exchange(&base->again, true);
960 }
961
962 D2TK_API bool
d2tk_base_get_again(d2tk_base_t * base)963 d2tk_base_get_again(d2tk_base_t *base)
964 {
965 return atomic_exchange(&base->again, false);
966 }
967
968 D2TK_API void
d2tk_base_set_mouse_pos(d2tk_base_t * base,d2tk_coord_t x,d2tk_coord_t y)969 d2tk_base_set_mouse_pos(d2tk_base_t *base, d2tk_coord_t x, d2tk_coord_t y)
970 {
971 base->mouse.x = x;
972 base->mouse.y = y;
973 }
974
975 D2TK_API void
d2tk_base_get_mouse_pos(d2tk_base_t * base,d2tk_coord_t * x,d2tk_coord_t * y)976 d2tk_base_get_mouse_pos(d2tk_base_t *base, d2tk_coord_t *x, d2tk_coord_t *y)
977 {
978 if(x)
979 {
980 *x = base->mouse.x;
981 }
982
983 if(y)
984 {
985 *y = base->mouse.y;
986 }
987 }
988
989 D2TK_API void
d2tk_base_add_mouse_scroll(d2tk_base_t * base,int32_t dx,int32_t dy)990 d2tk_base_add_mouse_scroll(d2tk_base_t *base, int32_t dx, int32_t dy)
991 {
992 base->scroll.dx += dx;
993 base->scroll.dy += dy;
994 }
995
996 D2TK_API void
d2tk_base_get_mouse_scroll(d2tk_base_t * base,int32_t * dx,int32_t * dy,bool clear)997 d2tk_base_get_mouse_scroll(d2tk_base_t *base, int32_t *dx, int32_t *dy,
998 bool clear)
999 {
1000 if(dx)
1001 {
1002 *dx = base->scroll.dx;
1003 }
1004
1005 if(dy)
1006 {
1007 *dy = base->scroll.dy;
1008 }
1009
1010 if(clear)
1011 {
1012 base->scroll.dx = 0;
1013 base->scroll.dy = 0;
1014 }
1015 }
1016
1017 D2TK_API bool
d2tk_base_set_butmask(d2tk_base_t * base,d2tk_butmask_t mask,bool down)1018 d2tk_base_set_butmask(d2tk_base_t *base, d2tk_butmask_t mask, bool down)
1019 {
1020 const bool old_state = (base->mouse.mask & mask) == mask;
1021
1022 if(down)
1023 {
1024 base->mouse.mask |= mask;
1025 }
1026 else
1027 {
1028 base->mouse.mask &= ~mask;
1029 }
1030
1031 return old_state;
1032 }
1033
1034 D2TK_API bool
d2tk_base_get_butmask(d2tk_base_t * base,d2tk_butmask_t mask,bool clear)1035 d2tk_base_get_butmask(d2tk_base_t *base, d2tk_butmask_t mask, bool clear)
1036 {
1037 const bool old_state = (base->mouse.mask & mask) == mask;
1038
1039 if(clear)
1040 {
1041 base->mouse.mask &= ~mask;
1042 }
1043
1044 return old_state;
1045
1046 }
1047
1048 D2TK_API bool
d2tk_base_get_butmask_prev(d2tk_base_t * base,d2tk_butmask_t mask)1049 d2tk_base_get_butmask_prev(d2tk_base_t *base, d2tk_butmask_t mask)
1050 {
1051 const bool old_state = (base->mouse.mask_prev & mask) == mask;
1052
1053 return old_state;
1054
1055 }
1056
1057 D2TK_API bool
d2tk_base_get_butmask_down(d2tk_base_t * base,d2tk_butmask_t mask)1058 d2tk_base_get_butmask_down(d2tk_base_t *base, d2tk_butmask_t mask)
1059 {
1060 return !d2tk_base_get_butmask_prev(base, mask)
1061 && d2tk_base_get_butmask(base, mask, false);
1062 }
1063
1064 D2TK_API bool
d2tk_base_get_butmask_up(d2tk_base_t * base,d2tk_butmask_t mask)1065 d2tk_base_get_butmask_up(d2tk_base_t *base, d2tk_butmask_t mask)
1066 {
1067 return d2tk_base_get_butmask_prev(base, mask)
1068 && !d2tk_base_get_butmask(base, mask, false);
1069 }
1070
1071 D2TK_API bool
d2tk_base_set_modmask(d2tk_base_t * base,d2tk_modmask_t mask,bool down)1072 d2tk_base_set_modmask(d2tk_base_t *base, d2tk_modmask_t mask, bool down)
1073 {
1074 const bool old_state = (base->keys.mod & mask) == mask;
1075
1076 if(down)
1077 {
1078 base->keys.mod |= mask;
1079 }
1080 else
1081 {
1082 base->keys.mod &= ~mask;
1083 }
1084
1085 return old_state;
1086 }
1087
1088 D2TK_API bool
d2tk_base_get_modmask(d2tk_base_t * base,d2tk_modmask_t mask,bool clear)1089 d2tk_base_get_modmask(d2tk_base_t *base, d2tk_modmask_t mask, bool clear)
1090 {
1091 const bool old_state = (base->keys.mod & mask) == mask;
1092
1093 if(clear)
1094 {
1095 base->keys.mod &= ~mask;
1096 }
1097
1098 return old_state;
1099
1100 }
1101
1102 D2TK_API bool
d2tk_base_set_keymask(d2tk_base_t * base,d2tk_keymask_t mask,bool down)1103 d2tk_base_set_keymask(d2tk_base_t *base, d2tk_keymask_t mask, bool down)
1104 {
1105 const bool old_state = (base->keys.mask & mask) == mask;
1106
1107 if(down)
1108 {
1109 base->keys.mask |= mask;
1110 }
1111 else
1112 {
1113 base->keys.mask &= ~mask;
1114 }
1115
1116 return old_state;
1117 }
1118
1119 D2TK_API bool
d2tk_base_get_keymask(d2tk_base_t * base,d2tk_keymask_t mask,bool clear)1120 d2tk_base_get_keymask(d2tk_base_t *base, d2tk_keymask_t mask, bool clear)
1121 {
1122 const bool old_state = (base->keys.mask & mask) == mask;
1123
1124 if(clear)
1125 {
1126 base->keys.mask &= ~mask;
1127 }
1128
1129 return old_state;
1130
1131 }
1132
1133 D2TK_API bool
d2tk_base_get_keymask_prev(d2tk_base_t * base,d2tk_keymask_t mask)1134 d2tk_base_get_keymask_prev(d2tk_base_t *base, d2tk_keymask_t mask)
1135 {
1136 const bool old_state = (base->keys.mask_prev & mask) == mask;
1137
1138 return old_state;
1139 }
1140
1141 D2TK_API bool
d2tk_base_get_keymask_down(d2tk_base_t * base,d2tk_keymask_t mask)1142 d2tk_base_get_keymask_down(d2tk_base_t *base, d2tk_keymask_t mask)
1143 {
1144 return !d2tk_base_get_keymask_prev(base, mask)
1145 && d2tk_base_get_keymask(base, mask, false);
1146 }
1147
1148 D2TK_API bool
d2tk_base_get_keymask_up(d2tk_base_t * base,d2tk_keymask_t mask)1149 d2tk_base_get_keymask_up(d2tk_base_t *base, d2tk_keymask_t mask)
1150 {
1151 return d2tk_base_get_keymask_prev(base, mask)
1152 && !d2tk_base_get_keymask(base, mask, false);
1153 }
1154
1155 D2TK_API void
d2tk_base_set_dimensions(d2tk_base_t * base,d2tk_coord_t w,d2tk_coord_t h)1156 d2tk_base_set_dimensions(d2tk_base_t *base, d2tk_coord_t w, d2tk_coord_t h)
1157 {
1158 d2tk_core_set_dimensions(base->core, w, h);
1159 }
1160
1161 D2TK_API void
d2tk_base_get_dimensions(d2tk_base_t * base,d2tk_coord_t * w,d2tk_coord_t * h)1162 d2tk_base_get_dimensions(d2tk_base_t *base, d2tk_coord_t *w, d2tk_coord_t *h)
1163 {
1164 d2tk_core_get_dimensions(base->core, w, h);
1165 }
1166
1167 D2TK_API void
d2tk_base_set_full_refresh(d2tk_base_t * base)1168 d2tk_base_set_full_refresh(d2tk_base_t *base)
1169 {
1170 d2tk_core_set_full_refresh(base->core);
1171 }
1172