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