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