1 #include "tickit.h"
2 #include "bindings.h"
3
4 #include <stdio.h>
5 #include <string.h>
6
7 #define streq(a,b) (!strcmp(a,b))
8
9 #define ROOT_AS_WINDOW(root) ((TickitWindow*)root)
10 #define WINDOW_AS_ROOT(win) ((TickitRootWindow*)win)
11
12 #define DEBUG_LOGF if(tickit_debug_enabled) tickit_debug_logf
13
14 typedef enum {
15 TICKIT_HIERARCHY_INSERT_FIRST,
16 TICKIT_HIERARCHY_INSERT_LAST,
17 TICKIT_HIERARCHY_REMOVE,
18 TICKIT_HIERARCHY_RAISE,
19 TICKIT_HIERARCHY_RAISE_FRONT,
20 TICKIT_HIERARCHY_LOWER,
21 TICKIT_HIERARCHY_LOWER_BACK
22 } HierarchyChangeType;
23
24 struct TickitWindow {
25 TickitWindow *parent;
26 TickitWindow *first_child;
27 TickitWindow *next;
28 TickitWindow *focused_child;
29 TickitPen *pen;
30 TickitRect rect;
31 struct {
32 int line;
33 int col;
34 TickitCursorShape shape;
35 unsigned int visible : 1;
36 int blink : 2; /* -1, 0, 1 */
37 } cursor;
38
39 unsigned int is_root : 1;
40 unsigned int is_visible : 1;
41 unsigned int is_focused : 1;
42 unsigned int is_closed : 1;
43 unsigned int steal_input : 1;
44 unsigned int focus_child_notify : 1;
45
46 int refcount;
47 struct TickitBindings bindings;
48 };
49
50 #define WINDOW_PRINTF_FMT "[%dx%d abs@%d,%d]"
51 #define WINDOW_PRINTF_ARGS(w) (w)->rect.cols, (w)->rect.lines, tickit_window_get_abs_geometry(w).left, tickit_window_get_abs_geometry(w).top
52
53 #define RECT_PRINTF_FMT "[(%d,%d)..(%d,%d)]"
54 #define RECT_PRINTF_ARGS(r) (r).left, (r).top, tickit_rect_right(&(r)), tickit_rect_bottom(&(r))
55
56 DEFINE_BINDINGS_FUNCS(window,TickitWindow,TickitWindowEventFn)
57
58 typedef struct HierarchyChange HierarchyChange;
59 struct HierarchyChange {
60 HierarchyChangeType change;
61 TickitWindow *parent;
62 TickitWindow *win;
63 HierarchyChange *next;
64 };
65
66 typedef struct TickitRootWindow TickitRootWindow;
67 struct TickitRootWindow {
68 TickitWindow win;
69
70 TickitTerm *term;
71 TickitRectSet *damage;
72 HierarchyChange *hierarchy_changes;
73 bool needs_expose;
74 bool needs_restore;
75 bool needs_later_processing;
76
77 Tickit *tickit; /* uncounted */
78
79 int event_ids[3];
80
81 // Drag/drop context handling
82 bool mouse_dragging;
83 int mouse_last_button;
84 int mouse_last_line;
85 int mouse_last_col;
86 TickitWindow *drag_source_window;
87 };
88
89 static void _request_restore(TickitRootWindow *root);
90 static void _request_later_processing(TickitRootWindow *root);
91 static void _request_hierarchy_change(HierarchyChangeType, TickitWindow *);
92 static void _do_hierarchy_change(HierarchyChangeType change, TickitWindow *parent, TickitWindow *win);
93 static void _purge_hierarchy_changes(TickitWindow *win);
94 static int _handle_key(TickitWindow *win, TickitKeyEventInfo *args);
95 static TickitWindow *_handle_mouse(TickitWindow *win, TickitMouseEventInfo *args);
96
on_term_resize(TickitTerm * term,TickitEventFlags flags,void * _info,void * user)97 static int on_term_resize(TickitTerm *term, TickitEventFlags flags, void *_info, void *user)
98 {
99 TickitRootWindow *root = user;
100 TickitWindow *win = ROOT_AS_WINDOW(root);
101
102 TickitResizeEventInfo *info = _info;
103 int oldlines = win->rect.lines;
104 int oldcols = win->rect.cols;
105
106 tickit_window_resize(win, info->lines, info->cols);
107 DEBUG_LOGF("Ir", "Resize to %dx%d",
108 info->cols, info->lines);
109
110 if(info->lines > oldlines) {
111 TickitRect damage = {
112 .top = oldlines,
113 .left = 0,
114 .lines = info->lines - oldlines,
115 .cols = info->cols,
116 };
117 tickit_window_expose(win, &damage);
118 }
119
120 if(info->cols > oldcols) {
121 TickitRect damage = {
122 .top = 0,
123 .left = oldcols,
124 .lines = oldlines,
125 .cols = info->cols - oldcols,
126 };
127 tickit_window_expose(win, &damage);
128 }
129
130 return 1;
131 }
132
on_term_key(TickitTerm * term,TickitEventFlags flags,void * _info,void * user)133 static int on_term_key(TickitTerm *term, TickitEventFlags flags, void *_info, void *user)
134 {
135 TickitRootWindow *root = user;
136 TickitWindow *win = ROOT_AS_WINDOW(root);
137
138 TickitKeyEventInfo *info = _info;
139 static const char * const evnames[] = { NULL, "KEY", "TEXT" };
140
141 DEBUG_LOGF("Ik", "Key event %s %s (mod=%02x)",
142 evnames[info->type], info->str, info->mod);
143
144 return _handle_key(win, info);
145 }
146
on_term_mouse(TickitTerm * term,TickitEventFlags flags,void * _info,void * user)147 static int on_term_mouse(TickitTerm *term, TickitEventFlags flags, void *_info, void *user)
148 {
149 TickitRootWindow *root = user;
150 TickitWindow *win = ROOT_AS_WINDOW(root);
151
152 TickitMouseEventInfo *info = _info;
153 static const char * const evnames[] = { NULL, "PRESS", "DRAG", "RELEASE", "WHEEL" };
154
155 DEBUG_LOGF("Im", "Mouse event %s %d @%d,%d (mod=%02x)",
156 evnames[info->type], info->button, info->col, info->line, info->mod);
157
158 if(info->type == TICKIT_MOUSEEV_PRESS) {
159 /* Save the last press location in case of drag */
160 root->mouse_last_button = info->button;
161 root->mouse_last_line = info->line;
162 root->mouse_last_col = info->col;
163 }
164 else if(info->type == TICKIT_MOUSEEV_DRAG && !root->mouse_dragging) {
165 TickitMouseEventInfo draginfo = {
166 .type = TICKIT_MOUSEEV_DRAG_START,
167 .button = root->mouse_last_button,
168 .line = root->mouse_last_line,
169 .col = root->mouse_last_col,
170 };
171
172 root->drag_source_window = _handle_mouse(win, &draginfo);
173 root->mouse_dragging = true;
174 }
175 else if(info->type == TICKIT_MOUSEEV_RELEASE && root->mouse_dragging) {
176 TickitMouseEventInfo draginfo = {
177 .type = TICKIT_MOUSEEV_DRAG_DROP,
178 .button = info->button,
179 .line = info->line,
180 .col = info->col,
181 };
182
183 _handle_mouse(win, &draginfo);
184
185 if(root->drag_source_window) {
186 TickitRect geom = tickit_window_get_abs_geometry(root->drag_source_window);
187 TickitMouseEventInfo draginfo = {
188 .type = TICKIT_MOUSEEV_DRAG_STOP,
189 .button = info->button,
190 .line = info->line - geom.top,
191 .col = info->col - geom.left,
192 };
193
194 _handle_mouse(root->drag_source_window, &draginfo);
195 }
196
197 root->mouse_dragging = false;
198 }
199
200 TickitWindow *handled = _handle_mouse(win, info);
201
202 if(info->type == TICKIT_MOUSEEV_DRAG &&
203 root->drag_source_window &&
204 (!handled || handled != root->drag_source_window)) {
205 TickitRect geom = tickit_window_get_abs_geometry(root->drag_source_window);
206 TickitMouseEventInfo draginfo = {
207 .type = TICKIT_MOUSEEV_DRAG_OUTSIDE,
208 .button = info->button,
209 .line = info->line - geom.top,
210 .col = info->col - geom.left,
211 };
212
213 _handle_mouse(root->drag_source_window, &draginfo);
214 }
215
216 return !!handled;
217 }
218
init_window(TickitWindow * win,TickitWindow * parent,TickitRect rect)219 static void init_window(TickitWindow *win, TickitWindow *parent, TickitRect rect)
220 {
221 win->parent = parent;
222 win->first_child = NULL;
223 win->next = NULL;
224 win->focused_child = NULL;
225 win->pen = tickit_pen_new();
226 win->rect = rect;
227 win->cursor.line = 0;
228 win->cursor.col = 0;
229 win->cursor.shape = TICKIT_CURSORSHAPE_BLOCK;
230 win->cursor.visible = true;
231 win->cursor.blink = -1;
232 win->is_root = false;
233 win->is_visible = true;
234 win->is_focused = false;
235 win->is_closed = false;
236 win->steal_input = false;
237 win->focus_child_notify = false;
238
239 win->refcount = 1;
240 win->bindings = (struct TickitBindings){ NULL };
241 }
242
243 /* INTERNAL */
tickit_window_new_root2(Tickit * t,TickitTerm * term)244 TickitWindow* tickit_window_new_root2(Tickit *t, TickitTerm *term)
245 {
246 int lines, cols;
247 tickit_term_get_size(term, &lines, &cols);
248
249 TickitRootWindow *root = malloc(sizeof(TickitRootWindow));
250 if(!root)
251 return NULL;
252
253 init_window(ROOT_AS_WINDOW(root), NULL, (TickitRect) { .top = 0, .left = 0, .lines = lines, .cols = cols });
254 ROOT_AS_WINDOW(root)->is_root = true;
255
256 root->term = tickit_term_ref(term);
257 root->hierarchy_changes = NULL;
258 root->needs_expose = false;
259 root->needs_restore = false;
260 root->needs_later_processing = false;
261 root->tickit = t; /* uncounted */
262
263 root->damage = tickit_rectset_new();
264 if(!root->damage) {
265 tickit_window_destroy(ROOT_AS_WINDOW(root));
266 return NULL;
267 }
268
269 root->event_ids[0] = tickit_term_bind_event(term, TICKIT_TERM_ON_RESIZE, 0,
270 &on_term_resize, root);
271 root->event_ids[1] = tickit_term_bind_event(term, TICKIT_TERM_ON_KEY, 0,
272 &on_term_key, root);
273 root->event_ids[2] = tickit_term_bind_event(term, TICKIT_TERM_ON_MOUSE, 0,
274 &on_term_mouse, root);
275
276 root->mouse_dragging = false;
277
278 tickit_window_expose(ROOT_AS_WINDOW(root), NULL);
279
280 return ROOT_AS_WINDOW(root);
281 }
282
tickit_window_new_root(TickitTerm * tt)283 TickitWindow *tickit_window_new_root(TickitTerm *tt)
284 {
285 return tickit_window_new_root2(NULL, tt);
286 }
287
_get_root(const TickitWindow * win)288 static TickitRootWindow *_get_root(const TickitWindow *win)
289 {
290 while(!win->is_root) {
291 if(!win->parent) {
292 fprintf(stderr, "tickit_window:_get_root: orphaned window win=%p\n", win);
293 abort();
294 }
295
296 win = win->parent;
297 }
298 return WINDOW_AS_ROOT(win);
299 }
300
tickit_window_new(TickitWindow * parent,TickitRect rect,TickitWindowFlags flags)301 TickitWindow *tickit_window_new(TickitWindow *parent, TickitRect rect, TickitWindowFlags flags)
302 {
303 if(flags & TICKIT_WINDOW_ROOT_PARENT)
304 while(parent->parent) {
305 rect.top += parent->rect.top;
306 rect.left += parent->rect.left;
307 parent = parent->parent;
308 }
309
310 TickitWindow *win = malloc(sizeof(TickitWindow));
311 if(!win)
312 return NULL;
313
314 init_window(win, parent, rect);
315
316 if(flags & TICKIT_WINDOW_HIDDEN)
317 win->is_visible = false;
318 if(flags & TICKIT_WINDOW_STEAL_INPUT)
319 win->steal_input = true;
320
321 _do_hierarchy_change(
322 (flags & TICKIT_WINDOW_LOWEST) ? TICKIT_HIERARCHY_INSERT_LAST : TICKIT_HIERARCHY_INSERT_FIRST,
323 parent, win
324 );
325
326 return win;
327 }
328
tickit_window_parent(const TickitWindow * win)329 TickitWindow *tickit_window_parent(const TickitWindow *win)
330 {
331 return win->parent;
332 }
333
tickit_window_root(const TickitWindow * win)334 TickitWindow *tickit_window_root(const TickitWindow *win)
335 {
336 return ROOT_AS_WINDOW(_get_root(win));
337 }
338
tickit_window_children(const TickitWindow * win)339 size_t tickit_window_children(const TickitWindow *win)
340 {
341 size_t ret = 0;
342
343 for(TickitWindow *child = win->first_child; child; child = child->next)
344 ret++;
345
346 return ret;
347 }
348
tickit_window_get_children(const TickitWindow * win,TickitWindow * children[],size_t n)349 size_t tickit_window_get_children(const TickitWindow *win, TickitWindow *children[], size_t n)
350 {
351 size_t ret = 0;
352
353 for(TickitWindow *child = win->first_child; ret < n && child; child = child->next)
354 children[ret++] = child;
355
356 return ret;
357 }
358
tickit_window_get_term(const TickitWindow * win)359 TickitTerm *tickit_window_get_term(const TickitWindow *win)
360 {
361 return _get_root(win)->term;
362 }
363
tickit_window_close(TickitWindow * win)364 void tickit_window_close(TickitWindow *win)
365 {
366 if(win->parent)
367 _do_hierarchy_change(TICKIT_HIERARCHY_REMOVE, win->parent, win);
368
369 win->is_closed = true;
370 }
371
tickit_window_destroy(TickitWindow * win)372 void tickit_window_destroy(TickitWindow *win)
373 {
374 tickit_bindings_unbind_and_destroy(&win->bindings, win);
375
376 if(win->pen)
377 tickit_pen_unref(win->pen);
378
379 for(TickitWindow *child = win->first_child; child; /**/) {
380 TickitWindow *next = child->next;
381
382 tickit_window_unref(child);
383 child->parent = NULL;
384 child = next;
385 }
386
387 if(win->parent)
388 _purge_hierarchy_changes(win);
389
390 if(!win->is_closed)
391 tickit_window_close(win);
392
393 /* Root cleanup */
394 if(win->is_root) {
395 TickitRootWindow *root = WINDOW_AS_ROOT(win);
396 if(root->damage) {
397 tickit_rectset_destroy(root->damage);
398 }
399
400 tickit_term_unbind_event_id(root->term, root->event_ids[0]);
401 tickit_term_unbind_event_id(root->term, root->event_ids[1]);
402 tickit_term_unbind_event_id(root->term, root->event_ids[2]);
403
404 tickit_term_unref(root->term);
405 }
406
407 DEBUG_LOGF("W*", "Window destroyed " WINDOW_PRINTF_FMT,
408 WINDOW_PRINTF_ARGS(win));
409
410 free(win);
411 }
412
tickit_window_ref(TickitWindow * win)413 TickitWindow *tickit_window_ref(TickitWindow *win)
414 {
415 win->refcount++;
416 return win;
417 }
418
tickit_window_unref(TickitWindow * win)419 void tickit_window_unref(TickitWindow *win)
420 {
421 if(win->refcount < 1) {
422 fprintf(stderr, "tickit_window_unref: invalid refcount %d on win=%p\n", win->refcount, win);
423 abort();
424 }
425 win->refcount--;
426 if(!win->refcount)
427 tickit_window_destroy(win);
428 }
429
tickit_window_raise(TickitWindow * win)430 void tickit_window_raise(TickitWindow *win)
431 {
432 _request_hierarchy_change(TICKIT_HIERARCHY_RAISE, win);
433 }
434
tickit_window_raise_to_front(TickitWindow * win)435 void tickit_window_raise_to_front(TickitWindow *win)
436 {
437 _request_hierarchy_change(TICKIT_HIERARCHY_RAISE_FRONT, win);
438 }
439
tickit_window_lower(TickitWindow * win)440 void tickit_window_lower(TickitWindow *win)
441 {
442 _request_hierarchy_change(TICKIT_HIERARCHY_LOWER, win);
443 }
444
tickit_window_lower_to_back(TickitWindow * win)445 void tickit_window_lower_to_back(TickitWindow *win)
446 {
447 _request_hierarchy_change(TICKIT_HIERARCHY_LOWER_BACK, win);
448 }
449
tickit_window_show(TickitWindow * win)450 void tickit_window_show(TickitWindow *win)
451 {
452 win->is_visible = true;
453 if(win->parent) {
454 if(!win->parent->focused_child &&
455 (win->focused_child || win->is_focused)) {
456 win->parent->focused_child = win;
457 }
458 }
459 tickit_window_expose(win, NULL);
460 }
461
tickit_window_hide(TickitWindow * win)462 void tickit_window_hide(TickitWindow *win)
463 {
464 win->is_visible = false;
465
466 if(win->parent) {
467 TickitWindow *parent = win->parent;
468 if(parent->focused_child && (parent->focused_child == win)) {
469 parent->focused_child = NULL;
470 }
471 tickit_window_expose(parent, &win->rect);
472 }
473 }
474
tickit_window_is_visible(TickitWindow * win)475 bool tickit_window_is_visible(TickitWindow *win)
476 {
477 return win->is_visible;
478 }
479
tickit_window_get_geometry(const TickitWindow * win)480 TickitRect tickit_window_get_geometry(const TickitWindow *win)
481 {
482 return win->rect;
483 }
484
tickit_window_get_abs_geometry(const TickitWindow * win)485 TickitRect tickit_window_get_abs_geometry(const TickitWindow *win)
486 {
487 TickitRect geom = win->rect;
488
489 for(win = win->parent; win; win = win->parent)
490 tickit_rect_translate(&geom, win->rect.top, win->rect.left);
491
492 return geom;
493 }
494
tickit_window_bottom(const TickitWindow * win)495 int tickit_window_bottom(const TickitWindow *win)
496 {
497 return tickit_rect_bottom(&win->rect);
498 }
499
tickit_window_right(const TickitWindow * win)500 int tickit_window_right(const TickitWindow *win)
501 {
502 return win->rect.left + win->rect.cols;
503 }
504
tickit_window_resize(TickitWindow * win,int lines,int cols)505 void tickit_window_resize(TickitWindow *win, int lines, int cols)
506 {
507 tickit_window_set_geometry(win, (TickitRect){
508 .top = win->rect.top,
509 .left = win->rect.left,
510 .lines = lines,
511 .cols = cols}
512 );
513 }
514
tickit_window_reposition(TickitWindow * win,int top,int left)515 void tickit_window_reposition(TickitWindow *win, int top, int left)
516 {
517 tickit_window_set_geometry(win, (TickitRect){
518 .top = top,
519 .left = left,
520 .lines = win->rect.lines,
521 .cols = win->rect.cols}
522 );
523
524 if(win->is_focused)
525 _request_restore(_get_root(win));
526 }
527
tickit_window_set_geometry(TickitWindow * win,TickitRect geom)528 void tickit_window_set_geometry(TickitWindow *win, TickitRect geom)
529 {
530 if((win->rect.top != geom.top) ||
531 (win->rect.left != geom.left) ||
532 (win->rect.lines != geom.lines) ||
533 (win->rect.cols != geom.cols))
534 {
535 TickitGeomchangeEventInfo info = {
536 .rect = geom,
537 .oldrect = win->rect,
538 };
539
540 win->rect = geom;
541
542 run_events(win, TICKIT_WINDOW_ON_GEOMCHANGE, &info);
543 }
544 }
545
tickit_window_get_pen(const TickitWindow * win)546 TickitPen *tickit_window_get_pen(const TickitWindow *win)
547 {
548 return win->pen;
549 }
550
tickit_window_set_pen(TickitWindow * win,TickitPen * pen)551 void tickit_window_set_pen(TickitWindow *win, TickitPen *pen)
552 {
553 if(win->pen)
554 tickit_pen_unref(win->pen);
555
556 if(pen)
557 win->pen = tickit_pen_ref(pen);
558 else
559 win->pen = NULL;
560 }
561
tickit_window_expose(TickitWindow * win,const TickitRect * exposed)562 void tickit_window_expose(TickitWindow *win, const TickitRect *exposed)
563 {
564 TickitRect selfrect = { .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols };
565 TickitRect damaged;
566
567 if(exposed) {
568 if(!tickit_rect_intersect(&damaged, &selfrect, exposed))
569 return;
570 }
571 else
572 damaged = selfrect;
573
574 if(!win->is_visible)
575 return;
576
577 if(!win->is_root) {
578 if(!win->parent)
579 /* During cleanup at shutdown this might be empty already */
580 return;
581
582 tickit_rect_translate(&damaged, win->rect.top, win->rect.left);
583 tickit_window_expose(win->parent, &damaged);
584 return;
585 }
586
587 DEBUG_LOGF("Wd", "Damage root " RECT_PRINTF_FMT,
588 RECT_PRINTF_ARGS(damaged));
589
590 /* If we're here, then we're a root win. */
591 TickitRootWindow *root = WINDOW_AS_ROOT(win);
592 if(tickit_rectset_contains(root->damage, &damaged))
593 return;
594
595 tickit_rectset_add(root->damage, &damaged);
596
597 root->needs_expose = true;
598 _request_later_processing(root);
599 }
600
_gen_indent(TickitWindow * win)601 static char *_gen_indent(TickitWindow *win)
602 {
603 int depth = 0;
604 while(win->parent) {
605 depth++;
606 win = win->parent;
607 }
608
609 static size_t buflen = 0;
610 static char *buf = NULL;
611
612 if(depth * 2 >= buflen) {
613 free(buf);
614 buf = malloc(buflen = (depth * 2 + 1));
615 buf[0] = 0;
616 }
617
618 buf[depth*2] = 0;
619 for(char *s = buf; depth; depth--, s += 2)
620 s[0] = '|', s[1] = ' ';
621
622 return buf;
623 }
624
_do_expose(TickitWindow * win,const TickitRect * rect,TickitRenderBuffer * rb)625 static void _do_expose(TickitWindow *win, const TickitRect *rect, TickitRenderBuffer *rb)
626 {
627 DEBUG_LOGF("Wx", "%sExpose " WINDOW_PRINTF_FMT " " RECT_PRINTF_FMT,
628 _gen_indent(win), WINDOW_PRINTF_ARGS(win), RECT_PRINTF_ARGS(*rect));
629
630 if(win->pen)
631 tickit_renderbuffer_setpen(rb, win->pen);
632
633 for(TickitWindow* child = win->first_child; child; child = child->next) {
634 if(!child->is_visible)
635 continue;
636
637 TickitRect exposed;
638 if(tickit_rect_intersect(&exposed, rect, &child->rect)) {
639 tickit_renderbuffer_save(rb);
640
641 tickit_renderbuffer_clip(rb, &exposed);
642 tickit_renderbuffer_translate(rb, child->rect.top, child->rect.left);
643 tickit_rect_translate(&exposed, -child->rect.top, -child->rect.left);
644 _do_expose(child, &exposed, rb);
645
646 tickit_renderbuffer_restore(rb);
647 }
648
649 tickit_renderbuffer_mask(rb, &child->rect);
650 }
651
652 TickitExposeEventInfo info = {
653 .rect = *rect,
654 .rb = rb,
655 };
656 run_events(win, TICKIT_WINDOW_ON_EXPOSE, &info);
657 }
658
_request_restore(TickitRootWindow * root)659 static void _request_restore(TickitRootWindow *root)
660 {
661 root->needs_restore = true;
662 _request_later_processing(root);
663 }
664
_flush_fn(Tickit * t,TickitEventFlags flags,void * info,void * user)665 static int _flush_fn(Tickit *t, TickitEventFlags flags, void *info, void *user)
666 {
667 TickitWindow *win = user;
668 tickit_window_flush(win);
669 return 1;
670 }
671
_request_later_processing(TickitRootWindow * root)672 static void _request_later_processing(TickitRootWindow *root)
673 {
674 root->needs_later_processing = true;
675 if(root->tickit)
676 tickit_watch_later(root->tickit, 0, _flush_fn, ROOT_AS_WINDOW(root));
677 }
678
_cell_visible(TickitWindow * win,int line,int col)679 static bool _cell_visible(TickitWindow *win, int line, int col)
680 {
681 TickitWindow *prev = NULL;
682 while(win) {
683 if(line < 0 || line >= win->rect.lines ||
684 col < 0 || col >= win->rect.cols)
685 return false;
686
687 for(TickitWindow *child = win->first_child; child; child = child->next) {
688 if(prev && child == prev)
689 break;
690 if(!child->is_visible)
691 continue;
692
693 if(line < child->rect.top || line >= tickit_rect_bottom(&child->rect))
694 continue;
695 if(col < child->rect.left || col >= tickit_rect_right(&child->rect))
696 continue;
697
698 return false;
699 }
700
701 line += win->rect.top;
702 col += win->rect.left;
703
704 prev = win;
705 win = win->parent;
706 }
707
708 return true;
709 }
710
_do_restore(TickitRootWindow * root)711 static void _do_restore(TickitRootWindow *root)
712 {
713 TickitWindow *win = ROOT_AS_WINDOW(root);
714
715 while(win) {
716 if(!win->is_visible)
717 break;
718
719 if(!win->focused_child)
720 break;
721
722 win = win->focused_child;
723 }
724
725 if(win && win->is_focused && win->cursor.visible &&
726 _cell_visible(win, win->cursor.line, win->cursor.col)) {
727 TickitRect abs_geom = tickit_window_get_abs_geometry(win);
728 int cursor_line = win->cursor.line + abs_geom.top;
729 int cursor_col = win->cursor.col + abs_geom.left;
730 tickit_term_goto(root->term, cursor_line, cursor_col);
731 tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORSHAPE, win->cursor.shape);
732 if(win->cursor.blink != -1)
733 tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORBLINK, win->cursor.blink);
734 tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORVIS, 1);
735 }
736 else
737 tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORVIS, 0);
738
739 tickit_term_flush(root->term);
740 }
741
tickit_window_flush(TickitWindow * win)742 void tickit_window_flush(TickitWindow *win)
743 {
744 if(win->parent)
745 // Can't flush non-root.
746 return;
747
748 TickitRootWindow *root = WINDOW_AS_ROOT(win);
749 if(!root->needs_later_processing)
750 return;
751
752 root->needs_later_processing = false;
753
754 if(root->hierarchy_changes) {
755 HierarchyChange *req = root->hierarchy_changes;
756 while(req) {
757 _do_hierarchy_change(req->change, req->parent, req->win);
758 HierarchyChange *next = req->next;
759 free(req);
760 req = next;
761 }
762 root->hierarchy_changes = NULL;
763 }
764
765 if(root->needs_expose) {
766 root->needs_expose = false;
767
768 TickitWindow *root_window = ROOT_AS_WINDOW(root);
769 TickitRenderBuffer *rb = tickit_renderbuffer_new(root_window->rect.lines, root_window->rect.cols);
770
771 int damage_count = tickit_rectset_rects(root->damage);
772 TickitRect *rects = malloc(damage_count * sizeof(TickitRect));
773 tickit_rectset_get_rects(root->damage, rects, damage_count);
774
775 tickit_rectset_clear(root->damage);
776
777 for(int i = 0; i < damage_count; i++) {
778 TickitRect *rect = &rects[i];
779 tickit_renderbuffer_save(rb);
780 tickit_renderbuffer_clip(rb, rect);
781 _do_expose(root_window, rect, rb);
782 tickit_renderbuffer_restore(rb);
783 }
784
785 free(rects);
786
787 /* Hide terminal cursor during redraw. _do_restore will show it again if
788 * required
789 */
790 tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORVIS, 0);
791
792 tickit_renderbuffer_flush_to_term(rb, root->term);
793 tickit_renderbuffer_unref(rb);
794
795 root->needs_restore = true;
796 }
797
798 if(root->needs_restore) {
799 root->needs_restore = false;
800 _do_restore(root);
801 }
802 }
803
_find_child(TickitWindow * parent,TickitWindow * win)804 static TickitWindow **_find_child(TickitWindow *parent, TickitWindow *win)
805 {
806 TickitWindow **winp = &parent->first_child;
807 while(*winp && *winp != win)
808 winp = &(*winp)->next;
809
810 return winp;
811 }
812
_do_hierarchy_insert_first(TickitWindow * parent,TickitWindow * win)813 static void _do_hierarchy_insert_first(TickitWindow *parent, TickitWindow *win)
814 {
815 win->next = parent->first_child;
816 parent->first_child = win;
817 }
818
_do_hierarchy_insert_last(TickitWindow * parent,TickitWindow * win)819 static void _do_hierarchy_insert_last(TickitWindow *parent, TickitWindow *win)
820 {
821 TickitWindow **lastp = &parent->first_child;
822 while(*lastp)
823 lastp = &(*lastp)->next;
824
825 *lastp = win;
826 win->next = NULL;
827 }
828
_do_hierarchy_remove(TickitWindow * parent,TickitWindow * win)829 static void _do_hierarchy_remove(TickitWindow *parent, TickitWindow *win)
830 {
831 TickitWindow **winp = _find_child(parent, win);
832 if(!winp)
833 return;
834
835 *winp = (*winp)->next;
836 win->next = NULL;
837 }
838
_do_hierarchy_raise(TickitWindow * parent,TickitWindow * win)839 static void _do_hierarchy_raise(TickitWindow *parent, TickitWindow *win)
840 {
841 TickitWindow **prevp = &parent->first_child;
842 if(*prevp == win) // already first
843 return;
844
845 while(*prevp && (*prevp)->next != win)
846 prevp = &(*prevp)->next;
847
848 if(!prevp) // not found
849 return;
850
851 TickitWindow *after = win->next;
852
853 win->next = *prevp;
854 (*prevp)->next = after;
855 *prevp = win;
856 }
857
_do_hierarchy_lower(TickitWindow * parent,TickitWindow * win)858 static void _do_hierarchy_lower(TickitWindow *parent, TickitWindow *win)
859 {
860 TickitWindow **winp = _find_child(parent, win);
861 if(!winp) // not found
862 return;
863
864 TickitWindow *after = win->next;
865 if(!after) // already last
866 return;
867
868 win->next = after->next;
869 *winp = after;
870 after->next = win;
871 }
872
_do_hierarchy_change(HierarchyChangeType change,TickitWindow * parent,TickitWindow * win)873 static void _do_hierarchy_change(HierarchyChangeType change, TickitWindow *parent, TickitWindow *win)
874 {
875 const char *fmt = NULL;
876
877 switch(change) {
878 case TICKIT_HIERARCHY_INSERT_FIRST:
879 fmt = "Window " WINDOW_PRINTF_FMT " adds " WINDOW_PRINTF_FMT;
880 _do_hierarchy_insert_first(parent, win);
881 break;
882 case TICKIT_HIERARCHY_INSERT_LAST:
883 fmt = "Window " WINDOW_PRINTF_FMT " adds " WINDOW_PRINTF_FMT;
884 _do_hierarchy_insert_last(parent, win);
885 break;
886 case TICKIT_HIERARCHY_REMOVE:
887 fmt = "Window " WINDOW_PRINTF_FMT " removes " WINDOW_PRINTF_FMT;
888 _do_hierarchy_remove(parent, win);
889 win->parent = NULL;
890 if(parent->focused_child && parent->focused_child == win)
891 parent->focused_child = NULL;
892 break;
893 case TICKIT_HIERARCHY_RAISE:
894 fmt = "Window " WINDOW_PRINTF_FMT " raises " WINDOW_PRINTF_FMT;
895 _do_hierarchy_raise(parent, win);
896 break;
897 case TICKIT_HIERARCHY_RAISE_FRONT:
898 fmt = "Window " WINDOW_PRINTF_FMT " raises " WINDOW_PRINTF_FMT " to front";
899 _do_hierarchy_remove(parent, win);
900 _do_hierarchy_insert_first(parent, win);
901 break;
902 case TICKIT_HIERARCHY_LOWER:
903 fmt = "Window " WINDOW_PRINTF_FMT " lowers " WINDOW_PRINTF_FMT;
904 _do_hierarchy_lower(parent, win);
905 break;
906 case TICKIT_HIERARCHY_LOWER_BACK:
907 fmt = "Window " WINDOW_PRINTF_FMT " lowers " WINDOW_PRINTF_FMT " to back";
908 _do_hierarchy_remove(parent, win);
909 _do_hierarchy_insert_last(parent, win);
910 break;
911 }
912
913 if(fmt)
914 DEBUG_LOGF("Wh", fmt,
915 WINDOW_PRINTF_ARGS(parent), WINDOW_PRINTF_ARGS(win));
916
917 if(win->is_visible)
918 tickit_window_expose(parent, &win->rect);
919 }
920
_request_hierarchy_change(HierarchyChangeType change,TickitWindow * win)921 static void _request_hierarchy_change(HierarchyChangeType change, TickitWindow *win)
922 {
923 if(!win->parent)
924 /* Can't do anything to the root win */
925 return;
926
927 HierarchyChange *req = malloc(sizeof(HierarchyChange));
928 req->change = change;
929 req->parent = win->parent;
930 req->win = win;
931 req->next = NULL;
932 TickitRootWindow *root = _get_root(win);
933 if(!root->hierarchy_changes) {
934 root->hierarchy_changes = req;
935 _request_later_processing(root);
936 }
937 else {
938 HierarchyChange *chain = root->hierarchy_changes;
939 while(chain->next) {
940 chain = chain->next;
941 }
942 chain->next = req;
943 }
944 }
945
_purge_hierarchy_changes(TickitWindow * win)946 static void _purge_hierarchy_changes(TickitWindow *win)
947 {
948 TickitRootWindow *root = _get_root(win);
949 HierarchyChange **changep = &root->hierarchy_changes;
950 while(*changep) {
951 HierarchyChange *req = *changep;
952 if(req->parent == win || req->win == win) {
953 *changep = req->next;
954 free(req);
955 }
956 else
957 changep = &req->next;
958 }
959 }
960
_scrollrectset(TickitWindow * win,TickitRectSet * visible,int downward,int rightward,TickitPen * pen)961 static bool _scrollrectset(TickitWindow *win, TickitRectSet *visible, int downward, int rightward, TickitPen *pen)
962 {
963 TickitWindow *origwin = win;
964
965 int abs_top = 0, abs_left = 0;
966
967 while(win) {
968 if(!win->is_visible)
969 return false;
970
971 if(win->pen)
972 tickit_pen_copy(pen, win->pen, false);
973
974 TickitWindow *parent = win->parent;
975 if(!parent)
976 break;
977
978 abs_top += win->rect.top;
979 abs_left += win->rect.left;
980 tickit_rectset_translate(visible, win->rect.top, win->rect.left);
981
982 for(TickitWindow *sib = parent->first_child; sib; sib = sib->next) {
983 if(sib == win)
984 break;
985 if(!sib->is_visible)
986 continue;
987
988 tickit_rectset_subtract(visible, &sib->rect);
989 }
990
991 win = parent;
992 }
993
994 TickitTerm *term = WINDOW_AS_ROOT(win)->term;
995
996 int n = tickit_rectset_rects(visible);
997 TickitRect rects[n];
998 tickit_rectset_get_rects(visible, rects, n);
999
1000 bool ret = true;
1001 bool done_pen = false;
1002
1003 for(int i = 0; i < n; i++) {
1004 TickitRect rect = rects[i];
1005
1006 TickitRect origrect = rect;
1007 tickit_rect_translate(&origrect, -abs_top, -abs_left);
1008
1009 if(abs(downward) >= rect.lines || abs(rightward) >= rect.cols) {
1010 tickit_window_expose(origwin, &origrect);
1011 continue;
1012 }
1013
1014 // TODO: This may be more efficiently done with some rectset operations
1015 // instead of completely resetting and rebuilding the set
1016 TickitRectSet *damageset = WINDOW_AS_ROOT(win)->damage;
1017 int n_damage = tickit_rectset_rects(damageset);
1018 TickitRect damage[n_damage];
1019 tickit_rectset_get_rects(damageset, damage, n_damage);
1020 tickit_rectset_clear(damageset);
1021
1022 for(int j = 0; j < n_damage; j++) {
1023 TickitRect r = damage[j];
1024
1025 if(tickit_rect_bottom(&r) < rect.top || r.top > tickit_rect_bottom(&rect) ||
1026 tickit_rect_right(&r) < rect.left || r.left > tickit_rect_right(&rect)) {
1027 tickit_rectset_add(damageset, &r);
1028 continue;
1029 }
1030
1031 TickitRect outside[4];
1032 int n_outside;
1033 if((n_outside = tickit_rect_subtract(outside, &r, &rect))) {
1034 for(int k = 0; k < n_outside; k++)
1035 tickit_rectset_add(damageset, outside+k);
1036 }
1037
1038 TickitRect inside;
1039 if(tickit_rect_intersect(&inside, &r, &rect)) {
1040 tickit_rect_translate(&inside, -downward, -rightward);
1041 if(tickit_rect_intersect(&inside, &inside, &rect))
1042 tickit_rectset_add(damageset, &inside);
1043 }
1044 }
1045
1046 DEBUG_LOGF("Wsr", "Term scrollrect " RECT_PRINTF_FMT " by %+d,%+d",
1047 RECT_PRINTF_ARGS(rect), rightward, downward);
1048
1049 if(!done_pen) {
1050 // TODO: only bg matters
1051 tickit_term_setctl_int(term, TICKIT_TERMCTL_CURSORVIS, 0);
1052 tickit_term_setpen(term, pen);
1053 done_pen = true;
1054 }
1055
1056 if(tickit_term_scrollrect(term, rect, downward, rightward)) {
1057 if(downward > 0) {
1058 // "scroll down" means lines moved upward, so the bottom needs redrawing
1059 tickit_window_expose(origwin, &(TickitRect){
1060 .top = tickit_rect_bottom(&origrect) - downward, .lines = downward,
1061 .left = origrect.left, .cols = rect.cols
1062 });
1063 }
1064 else if(downward < 0) {
1065 // "scroll up" means lines moved downward, so top needs redrawing
1066 tickit_window_expose(origwin, &(TickitRect){
1067 .top = origrect.top, .lines = -downward,
1068 .left = origrect.left, .cols = rect.cols,
1069 });
1070 }
1071
1072 if(rightward > 0) {
1073 // "scroll right" means columns moved leftward, so the right edge needs redrawing
1074 tickit_window_expose(origwin, &(TickitRect){
1075 .top = origrect.top, .lines = rect.lines,
1076 .left = tickit_rect_right(&origrect) - rightward, .cols = rightward,
1077 });
1078 }
1079 else if(rightward < 0) {
1080 tickit_window_expose(origwin, &(TickitRect){
1081 .top = origrect.top, .lines = rect.lines,
1082 .left = origrect.left, .cols = -rightward,
1083 });
1084 }
1085 }
1086 else {
1087 tickit_window_expose(origwin, &origrect);
1088 ret = false;
1089 }
1090 }
1091
1092 if(done_pen) {
1093 _request_restore(WINDOW_AS_ROOT(win));
1094 }
1095
1096 return ret;
1097 }
1098
_scroll(TickitWindow * win,const TickitRect * origrect,int downward,int rightward,TickitPen * pen,bool mask_children)1099 static bool _scroll(TickitWindow *win, const TickitRect *origrect, int downward, int rightward, TickitPen *pen, bool mask_children)
1100 {
1101 TickitRect rect;
1102 TickitRect selfrect = { .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols };
1103
1104 if(!tickit_rect_intersect(&rect, &selfrect, origrect))
1105 return false;
1106
1107 DEBUG_LOGF("Ws", "Scroll " RECT_PRINTF_FMT " by %+d,%+d",
1108 RECT_PRINTF_ARGS(rect), rightward, downward);
1109
1110 if(pen)
1111 pen = tickit_pen_ref(pen);
1112 else
1113 pen = tickit_pen_new();
1114
1115 TickitRectSet *visible = tickit_rectset_new();
1116
1117 tickit_rectset_add(visible, &rect);
1118
1119 if(mask_children)
1120 for(TickitWindow *child = win->first_child; child; child = child->next) {
1121 if(!child->is_visible)
1122 continue;
1123 tickit_rectset_subtract(visible, &child->rect);
1124 }
1125
1126 bool ret = _scrollrectset(win, visible, downward, rightward, pen);
1127
1128 tickit_rectset_destroy(visible);
1129 tickit_pen_unref(pen);
1130
1131 return ret;
1132 }
1133
tickit_window_scrollrect(TickitWindow * win,const TickitRect * rect,int downward,int rightward,TickitPen * pen)1134 bool tickit_window_scrollrect(TickitWindow *win, const TickitRect *rect, int downward, int rightward, TickitPen *pen)
1135 {
1136 return _scroll(win, rect, downward, rightward, pen, true);
1137 }
1138
tickit_window_scroll(TickitWindow * win,int downward,int rightward)1139 bool tickit_window_scroll(TickitWindow *win, int downward, int rightward)
1140 {
1141 return _scroll(win,
1142 &((TickitRect){ .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols }),
1143 downward, rightward, NULL, true);
1144 }
1145
tickit_window_scroll_with_children(TickitWindow * win,int downward,int rightward)1146 bool tickit_window_scroll_with_children(TickitWindow *win, int downward, int rightward)
1147 {
1148 return _scroll(win,
1149 &((TickitRect){ .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols }),
1150 downward, rightward, NULL, false);
1151 }
1152
tickit_window_set_cursor_position(TickitWindow * win,int line,int col)1153 void tickit_window_set_cursor_position(TickitWindow *win, int line, int col)
1154 {
1155 win->cursor.line = line;
1156 win->cursor.col = col;
1157
1158 if(win->is_focused)
1159 _request_restore(_get_root(win));
1160 }
1161
tickit_window_set_cursor_visible(TickitWindow * win,bool visible)1162 void tickit_window_set_cursor_visible(TickitWindow *win, bool visible)
1163 {
1164 tickit_window_setctl_int(win, TICKIT_WINCTL_CURSORVIS, visible);
1165 }
1166
tickit_window_set_cursor_shape(TickitWindow * win,TickitCursorShape shape)1167 void tickit_window_set_cursor_shape(TickitWindow *win, TickitCursorShape shape)
1168 {
1169 tickit_window_setctl_int(win, TICKIT_WINCTL_CURSORSHAPE, shape);
1170 }
1171
1172 static void _focus_gained(TickitWindow *win, TickitWindow *child);
1173 static void _focus_lost(TickitWindow *win);
1174
tickit_window_take_focus(TickitWindow * win)1175 void tickit_window_take_focus(TickitWindow *win)
1176 {
1177 _focus_gained(win, NULL);
1178 }
1179
_focus_gained(TickitWindow * win,TickitWindow * child)1180 static void _focus_gained(TickitWindow *win, TickitWindow *child)
1181 {
1182 if(win->focused_child && child && win->focused_child != child)
1183 _focus_lost(win->focused_child);
1184
1185 if(win->parent) {
1186 if(win->is_visible)
1187 _focus_gained(win->parent, win);
1188 }
1189 else
1190 _request_restore(_get_root(win));
1191
1192 if(!child) {
1193 win->is_focused = true;
1194
1195 TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_IN, .win = win };
1196 run_events(win, TICKIT_WINDOW_ON_FOCUS, &info);
1197 }
1198 else if(win->focus_child_notify) {
1199 TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_IN, .win = child };
1200 run_events(win, TICKIT_WINDOW_ON_FOCUS, &info);
1201 }
1202
1203 win->focused_child = child;
1204 }
1205
_focus_lost(TickitWindow * win)1206 static void _focus_lost(TickitWindow *win)
1207 {
1208 if(win->focused_child) {
1209 _focus_lost(win->focused_child);
1210
1211 if(win->focus_child_notify) {
1212 TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_OUT, .win = win->focused_child };
1213 run_events(win, TICKIT_WINDOW_ON_FOCUS, &info);
1214 }
1215 }
1216
1217 if(win->is_focused) {
1218 win->is_focused = false;
1219 TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_OUT, .win = win };
1220 run_events(win, TICKIT_WINDOW_ON_FOCUS, &info);
1221 }
1222 }
1223
tickit_window_is_focused(const TickitWindow * win)1224 bool tickit_window_is_focused(const TickitWindow *win)
1225 {
1226 return win->is_focused;
1227 }
1228
tickit_window_set_focus_child_notify(TickitWindow * win,bool notify)1229 void tickit_window_set_focus_child_notify(TickitWindow *win, bool notify)
1230 {
1231 tickit_window_setctl_int(win, TICKIT_WINCTL_FOCUS_CHILD_NOTIFY, notify);
1232 }
1233
tickit_window_is_steal_input(const TickitWindow * win)1234 bool tickit_window_is_steal_input(const TickitWindow *win)
1235 {
1236 return win->steal_input;
1237 }
1238
tickit_window_set_steal_input(TickitWindow * win,bool steal)1239 void tickit_window_set_steal_input(TickitWindow *win, bool steal)
1240 {
1241 tickit_window_setctl_int(win, TICKIT_WINCTL_STEAL_INPUT, steal);
1242 }
1243
tickit_window_getctl_int(TickitWindow * win,TickitWindowCtl ctl,int * value)1244 bool tickit_window_getctl_int(TickitWindow *win, TickitWindowCtl ctl, int *value)
1245 {
1246 switch(ctl) {
1247 case TICKIT_WINCTL_STEAL_INPUT:
1248 *value = win->steal_input;
1249 return true;
1250
1251 case TICKIT_WINCTL_FOCUS_CHILD_NOTIFY:
1252 *value = win->focus_child_notify;
1253 return true;
1254
1255 case TICKIT_WINCTL_CURSORVIS:
1256 *value = win->cursor.visible;
1257 return true;
1258
1259 case TICKIT_WINCTL_CURSORBLINK:
1260 *value = win->cursor.blink;
1261 return true;
1262
1263 case TICKIT_WINCTL_CURSORSHAPE:
1264 *value = win->cursor.shape;
1265 return true;
1266
1267 case TICKIT_N_WINCTLS:
1268 ;
1269 }
1270 return false;
1271 }
1272
tickit_window_setctl_int(TickitWindow * win,TickitWindowCtl ctl,int value)1273 bool tickit_window_setctl_int(TickitWindow *win, TickitWindowCtl ctl, int value)
1274 {
1275 switch(ctl) {
1276 case TICKIT_WINCTL_STEAL_INPUT:
1277 win->steal_input = value;
1278 return true;
1279
1280 case TICKIT_WINCTL_FOCUS_CHILD_NOTIFY:
1281 win->focus_child_notify = value;
1282 return true;
1283
1284 case TICKIT_WINCTL_CURSORVIS:
1285 win->cursor.visible = value;
1286 goto restore;
1287
1288 case TICKIT_WINCTL_CURSORBLINK:
1289 win->cursor.blink = value ? 1 : 0;
1290 goto restore;
1291
1292 case TICKIT_WINCTL_CURSORSHAPE:
1293 win->cursor.shape = value;
1294 goto restore;
1295
1296 case TICKIT_N_WINCTLS:
1297 ;
1298 }
1299 return false;
1300
1301 restore:
1302 if(win->is_focused)
1303 _request_restore(_get_root(win));
1304
1305 return true;
1306 }
1307
_handle_key(TickitWindow * win,TickitKeyEventInfo * info)1308 static int _handle_key(TickitWindow *win, TickitKeyEventInfo *info)
1309 {
1310 if(!win->is_visible)
1311 return 0;
1312
1313 int ret = 1;
1314 tickit_window_ref(win);
1315
1316 if(win->first_child && win->first_child->steal_input)
1317 if(_handle_key(win->first_child, info))
1318 goto done;
1319
1320 if(win->focused_child)
1321 if(_handle_key(win->focused_child, info))
1322 goto done;
1323
1324 if(run_events_whilefalse(win, TICKIT_WINDOW_ON_KEY, info))
1325 goto done;
1326
1327 // Last-ditch attempt to spread it around other children
1328 TickitWindow *next;
1329 for(TickitWindow *child = win->first_child; child; child = next) {
1330 next = child->next;
1331
1332 if(child == win->focused_child)
1333 continue;
1334
1335 if(_handle_key(child, info))
1336 goto done;
1337 }
1338
1339 ret = 0;
1340 /* fallthough */
1341 done:
1342 tickit_window_unref(win);
1343
1344 return ret;
1345 }
1346
_handle_mouse(TickitWindow * win,TickitMouseEventInfo * info)1347 static TickitWindow *_handle_mouse(TickitWindow *win, TickitMouseEventInfo *info)
1348 {
1349 if(!win->is_visible)
1350 return NULL;
1351
1352 TickitWindow *ret;
1353 tickit_window_ref(win);
1354
1355 TickitWindow *next;
1356 for(TickitWindow *child = win->first_child; child; child = next) {
1357 next = child->next;
1358
1359 int child_line = info->line - child->rect.top;
1360 int child_col = info->col - child->rect.left;
1361
1362 if(!child->steal_input) {
1363 if(child_line < 0 || child_line >= child->rect.lines)
1364 continue;
1365 if(child_col < 0 || child_col >= child->rect.cols)
1366 continue;
1367 }
1368
1369 TickitMouseEventInfo childinfo = *info;
1370 childinfo.line = child_line;
1371 childinfo.col = child_col;
1372
1373 if((ret = _handle_mouse(child, &childinfo)))
1374 goto done;
1375 }
1376
1377 ret = win;
1378 if(run_events_whilefalse(win, TICKIT_WINDOW_ON_MOUSE, info))
1379 goto done;
1380
1381 ret = NULL;
1382 /* fallthrough */
1383 done:
1384 tickit_window_unref(win);
1385
1386 return ret;
1387 }
1388
tickit_window_ctlname(TickitWindowCtl ctl)1389 const char *tickit_window_ctlname(TickitWindowCtl ctl)
1390 {
1391 switch(ctl) {
1392 case TICKIT_WINCTL_STEAL_INPUT: return "steal-input";
1393 case TICKIT_WINCTL_FOCUS_CHILD_NOTIFY: return "focus-child-notify";
1394 case TICKIT_WINCTL_CURSORVIS: return "cursor-visible";
1395 case TICKIT_WINCTL_CURSORBLINK: return "cursor-blink";
1396 case TICKIT_WINCTL_CURSORSHAPE: return "cursor-shape";
1397
1398 case TICKIT_N_WINCTLS: ;
1399 }
1400 return NULL;
1401 }
1402
tickit_window_lookup_ctl(const char * name)1403 TickitWindowCtl tickit_window_lookup_ctl(const char *name)
1404 {
1405 const char *s;
1406
1407 for(TickitWindowCtl ctl = 1; ctl < TICKIT_N_WINCTLS; ctl++)
1408 if((s = tickit_window_ctlname(ctl)) && streq(name, s))
1409 return ctl;
1410
1411 return -1;
1412 }
1413
tickit_window_ctltype(TickitWindowCtl ctl)1414 TickitType tickit_window_ctltype(TickitWindowCtl ctl)
1415 {
1416 switch(ctl) {
1417 case TICKIT_WINCTL_STEAL_INPUT:
1418 case TICKIT_WINCTL_FOCUS_CHILD_NOTIFY:
1419 case TICKIT_WINCTL_CURSORVIS:
1420 case TICKIT_WINCTL_CURSORBLINK:
1421 return TICKIT_TYPE_BOOL;
1422
1423 case TICKIT_WINCTL_CURSORSHAPE:
1424 return TICKIT_TYPE_INT;
1425
1426 case TICKIT_N_WINCTLS:
1427 ;
1428 }
1429 return TICKIT_TYPE_NONE;
1430 }
1431