adding a bunch of code (vesa, keyb, mouse, etc) to the menu
[cdmenu] / menu / src / rtk.c
diff --git a/menu/src/rtk.c b/menu/src/rtk.c
new file mode 100644 (file)
index 0000000..5c3704b
--- /dev/null
@@ -0,0 +1,1169 @@
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include "app.h"
+#include "rtk.h"
+#include "rtk_impl.h"
+#include "logger.h"
+
+static void on_any_nop();
+static void on_window_drag(rtk_widget *w, int dx, int dy, int total_dx, int total_dy);
+static void on_button_click(rtk_widget *w);
+static void on_textbox_key(rtk_widget *w, int key, int press);
+
+void inval_vport(void);        /* scr_mod.c */
+
+
+void rtk_setup(rtk_draw_ops *drawop)
+{
+       rtk_gfx = *drawop;
+
+       rtk_init_drawing();
+}
+
+static int wsize(int type)
+{
+       switch(type) {
+       case RTK_LABEL:
+       case RTK_CHECKBOX:
+       case RTK_SEP:
+       case RTK_DRAWBOX:
+               return sizeof(rtk_widget);
+       case RTK_WIN:
+               return sizeof(rtk_window);
+       case RTK_BUTTON:
+               return sizeof(rtk_button);
+       case RTK_TEXTBOX:
+               return sizeof(rtk_textbox);
+       case RTK_SLIDER:
+               return sizeof(rtk_slider);
+       default:
+               break;
+       }
+       return 0;
+}
+
+rtk_widget *rtk_create_widget(int type)
+{
+       rtk_widget *w;
+
+       if(!(w = calloc(1, wsize(type)))) {
+               return 0;
+       }
+       w->type = type;
+       w->flags = VISIBLE | ENABLED | GEOMCHG | DIRTY;
+
+       w->on_key = on_any_nop;
+       w->on_mbutton = on_any_nop;
+       w->on_click = on_any_nop;
+       w->on_drag = on_any_nop;
+       w->on_drop = on_any_nop;
+
+       w->pad = 2;
+       return w;
+}
+
+void rtk_free_widget(rtk_widget *w)
+{
+       if(!w) return;
+
+       if(w->type == RTK_WIN) {
+               rtk_window *win = (rtk_window*)w;
+               while(win->clist) {
+                       rtk_widget *c = win->clist;
+                       win->clist = win->clist->next;
+                       rtk_free_widget(c);
+               }
+       }
+
+       free(w->text);
+       free(w);
+}
+
+int rtk_type(rtk_widget *w)
+{
+       return w->type;
+}
+
+void rtk_padding(rtk_widget *w, int pad)
+{
+       w->pad = pad;
+}
+
+void rtk_move(rtk_widget *w, int x, int y)
+{
+       if(!w->par) {
+               rtk_clearfb(w);
+               rtk_invalfb(w);
+       }
+       w->x = x;
+       w->y = y;
+       w->flags |= GEOMCHG | DIRTY;
+       if(!w->par) {
+               rtk_invalfb(w);
+               inval_vport();
+       }
+}
+
+void rtk_pos(rtk_widget *w, int *xptr, int *yptr)
+{
+       *xptr = w->x;
+       *yptr = w->y;
+}
+
+void rtk_abspos(rtk_widget *w, int *xptr, int *yptr)
+{
+       *xptr = w->absx;
+       *yptr = w->absy;
+}
+
+void rtk_resize(rtk_widget *w, int xsz, int ysz)
+{
+       if(!w->par) {
+               rtk_clearfb(w);
+               rtk_invalfb(w);
+       }
+       w->width = xsz;
+       w->height = ysz;
+       w->flags |= GEOMCHG | DIRTY;
+       if(!w->par) {
+               rtk_invalfb(w);
+               inval_vport();
+       }
+}
+
+void rtk_size(rtk_widget *w, int *xptr, int *yptr)
+{
+       *xptr = w->width;
+       *yptr = w->height;
+}
+
+int rtk_get_width(rtk_widget *w)
+{
+       return w->width;
+}
+
+int rtk_get_height(rtk_widget *w)
+{
+       return w->height;
+}
+
+void rtk_get_rect(rtk_widget *w, rtk_rect *r)
+{
+       r->x = w->x;
+       r->y = w->y;
+       r->width = w->width;
+       r->height = w->height;
+}
+
+void rtk_get_absrect(rtk_widget *w, rtk_rect *r)
+{
+       r->x = w->absx;
+       r->y = w->absy;
+       r->width = w->width;
+       r->height = w->height;
+}
+
+void rtk_autosize(rtk_widget *w, unsigned int szopt)
+{
+       if(szopt & RTK_AUTOSZ_WIDTH) {
+               w->flags |= AUTOWIDTH;
+       } else {
+               w->flags &= ~AUTOWIDTH;
+       }
+
+       if(szopt & RTK_AUTOSZ_HEIGHT) {
+               w->flags |= AUTOHEIGHT;
+       } else {
+               w->flags &= ~AUTOHEIGHT;
+       }
+}
+
+int rtk_set_text(rtk_widget *w, const char *str)
+{
+       rtk_rect rect;
+       char *s = strdup(str);
+       if(!s) return -1;
+
+       free(w->text);
+       w->text = s;
+
+       if(w->type == RTK_TEXTBOX) {
+               rtk_textbox *tb = (rtk_textbox*)w;
+               tb->cursor = tb->scroll = 0;
+               tb->bufsz = strlen(str) + 1;
+       }
+
+       rtk_calc_widget_rect(w, &rect);
+       rtk_resize(w, rect.width, rect.height);
+       rtk_invalidate(w);
+       return 0;
+}
+
+const char *rtk_get_text(rtk_widget *w)
+{
+       return w->text;
+}
+
+void rtk_set_value(rtk_widget *w, int val)
+{
+       w->value = val;
+       rtk_invalidate(w);
+}
+
+int rtk_get_value(rtk_widget *w)
+{
+       return w->value;
+}
+
+void rtk_set_callback(rtk_widget *w, rtk_callback cbfunc, void *cls)
+{
+       w->cbfunc = cbfunc;
+       w->cbcls = cls;
+}
+
+void rtk_set_callback_closure(rtk_widget *w, void *cls)
+{
+       w->cbcls = cls;
+}
+
+rtk_callback rtk_get_callback(const rtk_widget *w)
+{
+       return w->cbfunc;
+}
+
+void *rtk_get_callback_closure(const rtk_widget *w)
+{
+       return w->cbcls;
+}
+
+void rtk_set_drawfunc(rtk_widget *w, rtk_callback drawfunc, void *cls)
+{
+       w->drawcb = drawfunc;
+       w->drawcls = cls;
+}
+
+void rtk_set_key_handler(rtk_widget *w, rtk_key_callback func)
+{
+       w->on_key = func;
+}
+
+void rtk_set_mbutton_handler(rtk_widget *w, rtk_mbutton_callback func)
+{
+       w->on_mbutton = func;
+}
+
+void rtk_set_click_handler(rtk_widget *w, rtk_click_callback func)
+{
+       w->on_click = func;
+}
+
+void rtk_set_drag_handler(rtk_widget *w, rtk_drag_callback func)
+{
+       w->on_drag = func;
+}
+
+void rtk_set_drop_handler(rtk_widget *w, rtk_drop_callback func)
+{
+       w->on_drop = func;
+}
+
+void rtk_show(rtk_widget *w)
+{
+       w->flags |= VISIBLE;
+       rtk_invalidate(w);
+}
+
+void rtk_show_modal(rtk_widget *w)
+{
+       rtk_show(w);
+       if(w->scr) {
+               w->scr->modal = w;
+       }
+}
+
+void rtk_hide(rtk_widget *w)
+{
+       w->flags &= ~VISIBLE;
+       if(w->scr && w->scr->modal == w) {
+               w->scr->modal = 0;
+       }
+       rtk_clearfb(w);
+       rtk_invalfb(w);
+       inval_vport();
+}
+
+int rtk_visible(const rtk_widget *w)
+{
+       return w->flags & VISIBLE;
+}
+
+void rtk_invalidate(rtk_widget *w)
+{
+       w->flags |= DIRTY;
+
+#if !defined(MSDOS) && !defined(__MSDOS__)
+       /* this is necessary because the GLUT backend will have to see a
+        * glutRedisplay in order for anything to be redrawn
+        */
+       rtk_invalfb(w);
+#endif
+}
+
+void rtk_validate(rtk_widget *w)
+{
+       w->flags &= ~DIRTY;
+}
+
+void rtk_win_layout(rtk_widget *w, int layout)
+{
+       ((rtk_window*)w)->layout = layout;
+}
+
+void rtk_win_clear(rtk_widget *w)
+{
+       rtk_widget *tmp;
+       rtk_window *win = (rtk_window*)w;
+
+       RTK_ASSERT_TYPE(w, RTK_WIN);
+
+       while(win->clist) {
+               tmp = win->clist;
+               win->clist = win->clist->next;
+               rtk_free_widget(tmp);
+       }
+
+       win->clist = win->ctail = 0;
+       rtk_invalidate(w);
+}
+
+void rtk_win_add(rtk_widget *par, rtk_widget *child)
+{
+       rtk_window *parwin = (rtk_window*)par;
+
+       RTK_ASSERT_TYPE(par, RTK_WIN);
+
+       if(rtk_win_has(par, child)) {
+               return;
+       }
+
+       if(child->par) {
+               rtk_win_rm((rtk_widget*)child->par, child);
+       }
+
+       if(parwin->clist) {
+               parwin->ctail->next = child;
+               parwin->ctail = child;
+       } else {
+               parwin->clist = parwin->ctail = child;
+       }
+       child->next = 0;
+
+       child->par = parwin;
+       rtk_invalidate(par);
+}
+
+void rtk_win_rm(rtk_widget *par, rtk_widget *child)
+{
+       rtk_widget *prev, dummy;
+       rtk_window *parwin = (rtk_window*)par;
+
+       RTK_ASSERT_TYPE(par, RTK_WIN);
+
+       dummy.next = parwin->clist;
+       prev = &dummy;
+       while(prev->next) {
+               if(prev->next == child) {
+                       if(!child->next) {
+                               parwin->ctail = prev;
+                       }
+                       prev->next = child->next;
+                       break;
+               }
+               prev = prev->next;
+       }
+       parwin->clist = dummy.next;
+       rtk_invalidate(par);
+}
+
+int rtk_win_has(rtk_widget *par, rtk_widget *child)
+{
+       rtk_widget *w;
+       rtk_window *parwin = (rtk_window*)par;
+
+       RTK_ASSERT_TYPE(par, RTK_WIN);
+
+       w = parwin->clist;
+       while(w) {
+               if(w == child) {
+                       return 1;
+               }
+               w = w->next;
+       }
+       return 0;
+}
+
+rtk_widget *rtk_win_child(const rtk_widget *par, int idx)
+{
+       rtk_widget *w;
+       rtk_window *win = (rtk_window*)par;
+
+       RTK_ASSERT_TYPE(par, RTK_WIN);
+
+       w = win->clist;
+       while(w && idx-- > 0) {
+               w = w->next;
+       }
+       return idx > 0 ? 0 : w;
+}
+
+int rtk_win_descendant(const rtk_widget *par, const rtk_widget *w)
+{
+       rtk_widget *c;
+       rtk_window *parwin = (rtk_window*)par;
+
+       RTK_ASSERT_TYPE(par, RTK_WIN);
+
+       c = parwin->clist;
+       while(c) {
+               if(c == w) {
+                       return 1;
+               }
+               if(c->type == RTK_WIN) {
+                       if(rtk_win_descendant(c, w)) {
+                               return 1;
+                       }
+               }
+               c = c->next;
+       }
+       return 0;
+}
+
+/* --- button functions --- */
+void rtk_bn_mode(rtk_widget *w, int mode)
+{
+       RTK_ASSERT_TYPE(w, RTK_BUTTON);
+       ((rtk_button*)w)->mode = mode;
+       rtk_invalidate(w);
+}
+
+void rtk_bn_set_icon(rtk_widget *w, rtk_icon *icon)
+{
+       rtk_rect rect;
+
+       RTK_ASSERT_TYPE(w, RTK_BUTTON);
+       ((rtk_button*)w)->icon = icon;
+
+       rtk_calc_widget_rect(w, &rect);
+       rtk_resize(w, rect.width, rect.height);
+       rtk_invalidate(w);
+}
+
+rtk_icon *rtk_bn_get_icon(rtk_widget *w)
+{
+       RTK_ASSERT_TYPE(w, RTK_BUTTON);
+       return ((rtk_button*)w)->icon;
+}
+
+/* --- slider functions --- */
+void rtk_slider_set_range(rtk_widget *w, int vmin, int vmax)
+{
+       rtk_slider *slider = (rtk_slider*)w;
+       RTK_ASSERT_TYPE(w, RTK_SLIDER);
+
+       slider->vmin = vmin;
+       slider->vmax = vmax;
+}
+
+void rtk_slider_get_range(const rtk_widget *w, int *vmin, int *vmax)
+{
+       rtk_slider *slider = (rtk_slider*)w;
+       RTK_ASSERT_TYPE(w, RTK_SLIDER);
+
+       *vmin = slider->vmin;
+       *vmax = slider->vmax;
+}
+
+
+/* --- constructors --- */
+
+rtk_widget *rtk_create_window(rtk_widget *par, const char *title, int x, int y,
+               int width, int height, unsigned int flags)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_WIN))) {
+               return 0;
+       }
+       if(par) {
+               rtk_win_add(par, w);
+       }
+       w->on_drag = on_window_drag;
+       rtk_set_text(w, title);
+       rtk_move(w, x, y);
+       rtk_resize(w, width, height);
+       rtk_win_layout(w, RTK_VBOX);
+
+       w->flags |= flags << 16;
+       return w;
+}
+
+rtk_widget *rtk_create_button(rtk_widget *par, const char *str, rtk_callback cbfunc)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_BUTTON))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_SIZE);
+       w->on_click = on_button_click;
+       rtk_set_text(w, str);
+       rtk_set_callback(w, cbfunc, 0);
+       return w;
+}
+
+rtk_widget *rtk_create_iconbutton(rtk_widget *par, rtk_icon *icon, rtk_callback cbfunc)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_BUTTON))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_SIZE);
+       w->on_click = on_button_click;
+       rtk_bn_set_icon(w, icon);
+       rtk_set_callback(w, cbfunc, 0);
+       return w;
+}
+
+rtk_widget *rtk_create_label(rtk_widget *par, const char *text)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_LABEL))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_SIZE);
+       rtk_set_text(w, text);
+       return w;
+}
+
+rtk_widget *rtk_create_checkbox(rtk_widget *par, const char *text, int chk, rtk_callback cbfunc)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_CHECKBOX))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_SIZE);
+       rtk_set_text(w, text);
+       rtk_set_value(w, chk ? 1 : 0);
+       rtk_set_callback(w, cbfunc, 0);
+       return w;
+}
+
+rtk_widget *rtk_create_textbox(rtk_widget *par, const char *text, rtk_callback cbfunc)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_TEXTBOX))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_HEIGHT);
+       w->on_key = on_textbox_key;
+       if(text) {
+               rtk_set_text(w, text);
+       }
+       rtk_set_callback(w, cbfunc, 0);
+       rtk_resize(w, 40, 1);
+
+       w->flags |= CANFOCUS;
+       return w;
+}
+
+rtk_widget *rtk_create_slider(rtk_widget *par, int vmin, int vmax, int val, rtk_callback cbfunc)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_SLIDER))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_HEIGHT);
+       rtk_set_callback(w, cbfunc, 0);
+       rtk_slider_set_range(w, vmin, vmax);
+       rtk_set_value(w, val);
+       return w;
+}
+
+rtk_widget *rtk_create_separator(rtk_widget *par)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_SEP))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_autosize(w, RTK_AUTOSZ_SIZE);
+       return w;
+}
+
+rtk_widget *rtk_create_drawbox(rtk_widget *par, int width, int height, rtk_callback cbfunc)
+{
+       rtk_widget *w;
+
+       if(!(w = rtk_create_widget(RTK_DRAWBOX))) {
+               return 0;
+       }
+       if(par) rtk_win_add(par, w);
+       rtk_resize(w, width, height);
+       rtk_set_drawfunc(w, cbfunc, 0);
+       return w;
+}
+
+/* --- compound widgets --- */
+rtk_widget *rtk_create_hbox(rtk_widget *par)
+{
+       rtk_widget *box = rtk_create_window(par, "hbox", 0, 0, 0, 0, 0);
+       rtk_autosize(box, RTK_AUTOSZ_SIZE);
+       rtk_win_layout(box, RTK_HBOX);
+       return box;
+}
+
+rtk_widget *rtk_create_vbox(rtk_widget *par)
+{
+       rtk_widget *box = rtk_create_window(par, "vbox", 0, 0, 0, 0, 0);
+       rtk_autosize(box, RTK_AUTOSZ_SIZE);
+       rtk_win_layout(box, RTK_VBOX);
+       return box;
+}
+
+rtk_widget *rtk_create_field(rtk_widget *par, const char *lbtext, rtk_callback cbfunc)
+{
+       rtk_widget *hbox;
+       rtk_widget *lb, *tb;
+
+       if(!(hbox = rtk_create_window(par, "field", 0, 0, 0, 0, 0))) {
+               return 0;
+       }
+       rtk_win_layout(hbox, RTK_HBOX);
+       if(!(lb = rtk_create_label(hbox, lbtext))) {
+               rtk_free_widget(hbox);
+               return 0;
+       }
+       if(!(tb = rtk_create_textbox(hbox, 0, cbfunc))) {
+               rtk_free_widget(hbox);
+               return 0;
+       }
+       return tb;
+}
+
+/* --- icon functions --- */
+rtk_iconsheet *rtk_load_iconsheet(const char *fname)
+{
+       /*
+        rtk_iconsheet *is;
+
+       if(!(is = malloc(sizeof *is))) {
+               return 0;
+       }
+       is->icons = 0;
+
+       if(!(is->pixels = img_load_pixels(fname, &is->width, &is->height, IMG_FMT_RGBA32))) {
+               free(is);
+               return 0;
+       }
+       return is;
+       */
+       return 0;
+}
+
+void rtk_free_iconsheet(rtk_iconsheet *is)
+{
+       /*
+       rtk_icon *icon;
+
+       img_free_pixels(is->pixels);
+
+       while(is->icons) {
+               icon = is->icons;
+               is->icons = is->icons->next;
+               free(icon->name);
+               free(icon);
+       }
+       free(is);
+       */
+}
+
+rtk_icon *rtk_define_icon(rtk_iconsheet *is, const char *name, int x, int y, int w, int h)
+{
+       rtk_icon *icon;
+
+       if(!(icon = malloc(sizeof *icon))) {
+               return 0;
+       }
+       if(!(icon->name = strdup(name))) {
+               free(icon);
+               return 0;
+       }
+       icon->width = w;
+       icon->height = h;
+       icon->scanlen = is->width;
+       icon->pixels = is->pixels + y * is->width + x;
+
+       icon->next = is->icons;
+       is->icons = icon;
+       return icon;
+}
+
+rtk_icon *rtk_lookup_icon(rtk_iconsheet *is, const char *name)
+{
+       rtk_icon *icon = is->icons;
+       while(icon) {
+               if(strcmp(icon->name, name) == 0) {
+                       return icon;
+               }
+               icon = icon->next;
+       }
+       return 0;
+}
+
+static void sethover(rtk_screen *scr, rtk_widget *w)
+{
+       if(scr->hover == w) return;
+
+       if(scr->hover) {
+               scr->hover->flags &= ~HOVER;
+
+               if(scr->hover->type != RTK_WIN) {
+                       rtk_invalidate(scr->hover);
+               }
+       }
+       scr->hover = w;
+       if(w) {
+               w->flags |= HOVER;
+
+               if(w->type != RTK_WIN) {
+                       rtk_invalidate(w);
+               }
+       }
+
+       /*dbgmsg("hover \"%s\"\n", w ? (w->text ? w->text : "?") : "-");*/
+}
+
+static void setpress(rtk_widget *w, int press)
+{
+       if(press) {
+               w->flags |= PRESS;
+       } else {
+               w->flags &= ~PRESS;
+       }
+       rtk_invalidate(w);
+}
+
+static void setfocus(rtk_screen *scr, rtk_widget *w)
+{
+       rtk_window *win;
+
+       if(scr->focus) {
+               scr->focus->flags &= ~FOCUS;
+       }
+
+       if(scr->focuswin) {
+               scr->focuswin->flags &= ~FOCUS;
+               rtk_invalidate((rtk_widget*)scr->focuswin);
+       }
+
+       if(!w) return;
+
+       if(w->flags & CANFOCUS) {
+               w->flags |= FOCUS;
+               scr->focus = w;
+       }
+
+       win = (rtk_window*)w;
+       while(win->par) {
+               win = win->par;
+       }
+       win->flags |= FOCUS;
+       scr->focuswin = win;
+
+       rtk_invalidate(w);
+       rtk_invalidate((rtk_widget*)win);
+}
+
+/* --- screen functions --- */
+rtk_screen *rtk_create_screen(void)
+{
+       rtk_screen *scr;
+
+       if(!(scr = calloc(1, sizeof *scr))) {
+               return 0;
+       }
+       return scr;
+}
+
+void rtk_free_screen(rtk_screen *scr)
+{
+       int i;
+
+       for(i=0; i<scr->num_win; i++) {
+               rtk_free_widget(scr->winlist[i]);
+       }
+       free(scr);
+}
+
+int rtk_add_window(rtk_screen *scr, rtk_widget *win)
+{
+       if(scr->num_win >= MAX_WINDOWS) {
+               return -1;
+       }
+       scr->winlist[scr->num_win++] = win;
+       win->scr = scr;
+       return 0;
+}
+
+static rtk_widget *find_widget_at(rtk_widget *w, int x, int y, unsigned int flags)
+{
+       rtk_widget *c, *res;
+       rtk_window *win;
+
+       if(!(w->flags & VISIBLE)) {
+               return 0;
+       }
+
+       if(w->type != RTK_WIN) {
+               if((w->flags & flags) && rtk_hittest(w, x, y)) {
+                       return w;
+               }
+               return 0;
+       }
+
+       win = (rtk_window*)w;
+       c = win->clist;
+       while(c) {
+               if((res = find_widget_at(c, x, y, flags))) {
+                       return res;
+               }
+               c = c->next;
+       }
+       return rtk_hittest(w, x, y) ? w : 0;
+}
+
+rtk_widget *rtk_find_widget_at(rtk_screen *scr, rtk_widget *win, int x, int y, unsigned int flags)
+{
+       int i;
+       rtk_widget *w;
+
+       if(!flags) flags = ~0;
+
+       if(win) {
+               RTK_ASSERT_TYPE(win, RTK_WIN);
+               return find_widget_at(win, x, y, flags);
+       }
+
+       for(i=0; i<scr->num_win; i++) {
+               if((w = find_widget_at(scr->winlist[i], x, y, flags))) {
+                       return w;
+               }
+       }
+       return 0;
+}
+
+int rtk_input_key(rtk_screen *scr, int key, int press)
+{
+       if(scr->focus && (!scr->modal || rtk_win_descendant(scr->modal, scr->focus))) {
+               scr->focus->on_key(scr->focus, key, press);
+               return 1;
+       }
+
+       if(scr->modal) {
+               if(!press) return 1;
+
+               switch(key) {
+               case 27:
+                       rtk_hide(scr->modal);
+                       break;
+
+               case '\n':
+                       /* TODO */
+                       break;
+
+               default:
+                       break;
+               }
+               return 1;
+       }
+       return 0;
+}
+
+int rtk_input_mbutton(rtk_screen *scr, int bn, int press, int x, int y)
+{
+       int handled = 0;
+       rtk_widget *w;
+
+       if((w = rtk_find_widget_at(scr, scr->modal, x, y, 0))) {
+               int relx = x - w->absx;
+               int rely = y - w->absy;
+               w->on_mbutton(w, bn, press, relx, rely);
+       }
+
+       if(press) {
+               scr->prev_mx = x;
+               scr->prev_my = y;
+               if(bn == 0) {
+                       scr->press = w;
+                       scr->press_x = x;
+                       scr->press_y = y;
+
+                       if(w) setpress(w, 1);
+               }
+               handled = w ? 1 : 0;
+       } else {
+               if(!w && scr->modal) {
+                       rtk_hide(scr->modal);
+                       return 1;
+               }
+
+               if(bn == 0) {
+                       rtk_widget *newfocus = 0;
+                       if(w) {
+                               if(scr->press == w) {
+                                       newfocus = rtk_find_widget_at(scr, scr->modal, x, y, 0);
+                                       w->on_click(w);
+                               } else {
+                                       w->on_drop(scr->press, w);
+                               }
+                               handled = 1;
+                       }
+
+                       setfocus(scr, newfocus);
+
+                       if(scr->press) {
+                               setpress(scr->press, 0);
+                               scr->press = 0;
+                       }
+               }
+       }
+       return scr->modal ? 1 : handled;
+}
+
+int rtk_input_mmotion(rtk_screen *scr, int x, int y)
+{
+       int dx, dy;
+       rtk_widget *w;
+
+       dx = x - scr->prev_mx;
+       dy = y - scr->prev_my;
+       scr->prev_mx = x;
+       scr->prev_my = y;
+
+       if(scr->press) {
+               w = scr->press;
+               if((dx | dy)) {
+                       w->on_drag(w, dx, dy, x - scr->press_x, y - scr->press_y);
+               }
+               return 1;
+       }
+
+       if(!(w = rtk_find_widget_at(scr, scr->modal, x, y, 0))) {
+               return scr->modal ? 1 : 0;
+       }
+       sethover(scr, w);
+       return 1;
+}
+
+void rtk_invalidate_screen(rtk_screen *scr)
+{
+       int i;
+       for(i=0; i<scr->num_win; i++) {
+               rtk_invalidate(scr->winlist[i]);
+       }
+}
+
+void rtk_draw_screen(rtk_screen *scr)
+{
+       int i;
+       for(i=0; i<scr->num_win; i++) {
+               rtk_draw_widget(scr->winlist[i]);
+       }
+}
+
+/* --- misc functions --- */
+void rtk_null_rect(rtk_rect *rect)
+{
+       rect->x = rect->y = INT_MAX / 2;
+       rect->width = rect->height = INT_MIN;
+}
+
+void rtk_fix_rect(rtk_rect *rect)
+{
+       int x, y, w, h;
+
+       x = rect->x;
+       y = rect->y;
+
+       if(rect->width < 0) {
+               w = -rect->width;
+               x += rect->width;
+       } else {
+               w = rect->width;
+       }
+       if(rect->height < 0) {
+               h = -rect->height;
+               y += rect->height;
+       } else {
+               h = rect->height;
+       }
+
+       rect->x = x;
+       rect->y = y;
+       rect->width = w;
+       rect->height = h;
+}
+
+void rtk_rect_union(rtk_rect *a, const rtk_rect *b)
+{
+       int x0, y0, x1, y1;
+
+       x0 = a->x;
+       y0 = a->y;
+       x1 = a->x + a->width;
+       y1 = a->y + a->height;
+
+       if(b->x < x0) x0 = b->x;
+       if(b->y < y0) y0 = b->y;
+       if(b->x + b->width > x1) x1 = b->x + b->width;
+       if(b->y + b->height > y1) y1 = b->y + b->height;
+
+       a->x = x0;
+       a->y = y0;
+       a->width = x1 - x0;
+       a->height = y1 - y0;
+}
+
+
+/* --- widget event handlers --- */
+static void on_any_nop()
+{
+}
+
+static void on_window_drag(rtk_widget *w, int dx, int dy, int total_dx, int total_dy)
+{
+       if(w->flags & MOVABLE) {
+               rtk_move(w, w->x + dx, w->y + dy);
+       }
+}
+
+static void on_button_click(rtk_widget *w)
+{
+       rtk_button *bn = (rtk_button*)w;
+
+       switch(w->type) {
+       case RTK_BUTTON:
+               if(bn->mode == RTK_TOGGLEBN) {
+               case RTK_CHECKBOX:
+                       bn->value ^= 1;
+               }
+               if(bn->cbfunc) {
+                       bn->cbfunc(w, bn->cbcls);
+               }
+               rtk_invalidate(w);
+               break;
+
+       default:
+               break;
+       }
+}
+
+static void on_textbox_key(rtk_widget *w, int key, int press)
+{
+       rtk_textbox *tb = (rtk_textbox*)w;
+
+       if(!press) return;
+
+       switch(key) {
+       case KEY_BACKSP:
+               if(tb->cursor > 0) {
+                       if(tb->cursor < tb->len) {
+                               memmove(tb->text + tb->cursor - 1, tb->text + tb->cursor, tb->len - tb->cursor + 1);
+                               tb->cursor--;
+                       } else {
+                               tb->text[--tb->cursor] = 0;
+                       }
+                       tb->len--;
+               }
+               rtk_invalidate(w);
+               return;
+
+       case KEY_HOME:
+               tb->cursor = 0;
+               tb->scroll = 0;
+               rtk_invalidate(w);
+               return;
+
+       case KEY_END:
+               tb->cursor = tb->len;
+               rtk_invalidate(w);
+               return;
+
+       case KEY_LEFT:
+               if(tb->cursor > 0) {
+                       tb->cursor--;
+               }
+               if(tb->cursor < tb->scroll) {
+                       tb->scroll = tb->cursor;
+               }
+               rtk_invalidate(w);
+               return;
+
+       case KEY_RIGHT:
+               if(tb->cursor < tb->len) {
+                       tb->cursor++;
+               }
+               rtk_invalidate(w);
+               return;
+
+       default:
+               if(!isprint(key)) {
+                       return;
+               }
+               break;
+       }
+
+       /* we end up here if it's a printable character */
+       if(tb->len >= tb->bufsz - 1) {
+               int nsz = tb->bufsz ? tb->bufsz * 2 : 16;
+               void *tmp = realloc(tb->text, nsz);
+               if(!tmp) return;
+               tb->text = tmp;
+               tb->bufsz = nsz;
+       }
+
+       if(tb->len <= 0 || tb->cursor >= tb->len) {
+               tb->text[tb->len++] = key;
+               tb->text[tb->len] = 0;
+               tb->cursor++;
+       } else {
+               memmove(tb->text + tb->cursor + 1, tb->text + tb->cursor, tb->len - tb->cursor + 1);
+               tb->text[tb->cursor++] = key;
+               tb->len++;
+       }
+
+       rtk_invalidate(w);
+}
+
+void rtk_dbg_showrect(rtk_widget *w, int show)
+{
+       if(show) {
+               w->flags |= DBGRECT;
+       } else {
+               w->flags &= ~DBGRECT;
+       }
+}