initial commit
authorJohn Tsiombikas <nuclear@member.fsf.org>
Sat, 11 Aug 2018 06:14:55 +0000 (09:14 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Sat, 11 Aug 2018 06:14:55 +0000 (09:14 +0300)
34 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
src/game.cc [new file with mode: 0644]
src/game.h [new file with mode: 0644]
src/gamescr.cc [new file with mode: 0644]
src/goatkit/boolanm.cc [new file with mode: 0644]
src/goatkit/boolanm.h [new file with mode: 0644]
src/goatkit/button.cc [new file with mode: 0644]
src/goatkit/button.h [new file with mode: 0644]
src/goatkit/checkbox.cc [new file with mode: 0644]
src/goatkit/checkbox.h [new file with mode: 0644]
src/goatkit/event.cc [new file with mode: 0644]
src/goatkit/event.h [new file with mode: 0644]
src/goatkit/goatkit.h [new file with mode: 0644]
src/goatkit/label.cc [new file with mode: 0644]
src/goatkit/label.h [new file with mode: 0644]
src/goatkit/screen.cc [new file with mode: 0644]
src/goatkit/screen.h [new file with mode: 0644]
src/goatkit/slider.cc [new file with mode: 0644]
src/goatkit/slider.h [new file with mode: 0644]
src/goatkit/textbox.cc [new file with mode: 0644]
src/goatkit/textbox.h [new file with mode: 0644]
src/goatkit/theme.cc [new file with mode: 0644]
src/goatkit/theme.h [new file with mode: 0644]
src/goatkit/vec.h [new file with mode: 0644]
src/goatkit/widget.cc [new file with mode: 0644]
src/goatkit/widget.h [new file with mode: 0644]
src/main.cc [new file with mode: 0644]
src/menuscr.cc [new file with mode: 0644]
src/opengl.c [new file with mode: 0644]
src/opengl.h [new file with mode: 0644]
src/screen.cc [new file with mode: 0644]
src/screen.h [new file with mode: 0644]
src/uitheme.cc [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ad1a4b6
--- /dev/null
@@ -0,0 +1,4 @@
+*.o
+*.swp
+*.d
+game
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..4189eb2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,42 @@
+ccsrc = $(wildcard src/*.cc) \
+               $(wildcard src/goatkit/*.cc)
+csrc = $(wildcard src/*.c)
+obj = $(ccsrc:.cc=.o) $(csrc:.c=.o)
+dep = $(obj:.o=.d)
+bin = game
+
+warn = -pedantic -Wall
+dbg = -g
+opt = -O0
+inc =
+
+CFLAGS = $(warn) $(dbg) $(opt) $(inc)
+CXXFLAGS = $(warn) $(dbg) $(opt) $(inc)
+LDFLAGS = $(libgl) $(syslibs)
+
+sys := $(shell uname -s | sed 's/MINGW.*/mingw/')
+ifeq ($(sys), mingw)
+       libgl = -lopengl32 -lglu32 -lfreeglut -lglew32
+else
+       libgl = -lGL -lGLU -lglut -lGLEW
+       syslibs = -ldl
+endif
+
+$(bin): $(obj)
+       $(CXX) -o $@ $(obj) $(LDFLAGS)
+
+-include $(dep)
+
+%.d: %.c
+       @$(CPP) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@
+
+%.d: %.cc
+       @$(CPP) $(CXXFLAGS) $< -MM -MT $(@:.d=.o) >$@
+
+.PHONY: clean
+clean:
+       rm -f $(obj) $(bin)
+
+.PHONY: cleandep
+cleandep:
+       rm -f $(dep)
diff --git a/src/game.cc b/src/game.cc
new file mode 100644 (file)
index 0000000..cf3f62a
--- /dev/null
@@ -0,0 +1,82 @@
+#include <assert.h>
+#include "opengl.h"
+#include "game.h"
+#include "screen.h"
+
+int win_width, win_height;
+float win_aspect;
+long frame_time;
+float frame_dt;
+
+bool game_init()
+{
+       if(init_opengl() == -1) {
+               return false;
+       }
+
+       if(!init_screens()) {
+               return false;
+       }
+
+       glEnable(GL_MULTISAMPLE);
+       glEnable(GL_FRAMEBUFFER_SRGB);
+       glEnable(GL_DEPTH_TEST);
+       glEnable(GL_CULL_FACE);
+       glEnable(GL_LIGHTING);
+       glEnable(GL_LIGHT0);
+
+       active_screen->start();
+       return true;
+}
+
+void game_cleanup()
+{
+       cleanup_screens();
+}
+
+void game_draw()
+{
+       active_screen->draw();
+
+       assert(glGetError() == GL_NO_ERROR);
+}
+
+void game_reshape(int x, int y)
+{
+       active_screen->draw();
+}
+
+void game_keyboard(int key, bool pressed)
+{
+       if(pressed) {
+               switch(key) {
+               case 'q':
+               case 'Q':
+                       if(game_modkeys() & MODKEY_CTRL) {
+                               game_quit();
+                               return;
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       active_screen->keyboard(key, pressed);
+}
+
+void game_mbutton(int bn, bool pressed, int x, int y)
+{
+       active_screen->mbutton(bn, pressed, x, y);
+}
+
+void game_mmotion(int x, int y)
+{
+       active_screen->mmotion(x, y);
+}
+
+void game_mwheel(int dir, int x, int y)
+{
+       active_screen->mwheel(dir, x, y);
+}
diff --git a/src/game.h b/src/game.h
new file mode 100644 (file)
index 0000000..0d7e791
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef GAME_H_
+#define GAME_H_
+
+enum {
+       KEY_ESC = 27,
+       KEY_DEL = 127,
+
+       KEY_F1 = 256,
+       KEY_F2,
+       KEY_F3,
+       KEY_F4,
+       KEY_F5,
+       KEY_F6,
+       KEY_F7,
+       KEY_F8,
+       KEY_F9,
+       KEY_F10,
+       KEY_F11,
+       KEY_F12,
+       KEY_LEFT,
+       KEY_RIGHT,
+       KEY_UP,
+       KEY_DOWN,
+       KEY_HOME,
+       KEY_END,
+       KEY_PGUP,
+       KEY_PGDOWN,
+       KEY_INSERT
+};
+
+enum {
+       MODKEY_SHIFT    = 1,
+       MODKEY_CTRL             = 2,
+       MODKEY_ALT              = 4
+};
+
+
+extern int win_width, win_height;
+extern float win_aspect;
+extern long frame_time;
+extern float frame_dt;
+
+bool game_init();
+void game_cleanup();
+
+void game_draw();
+void game_reshape(int x, int y);
+
+void game_keyboard(int key, bool pressed);
+void game_mbutton(int bn, bool pressed, int x, int y);
+void game_mmotion(int x, int y);
+void game_mwheel(int dir, int x, int y);
+
+// defined in main.cc
+void game_quit();
+bool game_keystate(int key);
+bool game_bnstate(int bn);
+
+unsigned int game_modkeys();
+
+#endif /* GAME_H_ */
diff --git a/src/gamescr.cc b/src/gamescr.cc
new file mode 100644 (file)
index 0000000..0ae1672
--- /dev/null
@@ -0,0 +1,46 @@
+#include "game.h"
+#include "screen.h"
+#include "opengl.h"
+
+bool GameScreen::init()
+{
+       return true;
+}
+
+void GameScreen::destroy()
+{
+}
+
+
+void GameScreen::draw()
+{
+       glClearColor(1, 0, 0, 1);
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+
+void GameScreen::keyboard(int key, bool pressed)
+{
+       if(pressed) {
+               switch(key) {
+               case KEY_ESC:
+                       pop_screen();
+                       break;
+
+               default:
+                       break;
+               }
+       }
+}
+
+void GameScreen::mbutton(int bn, bool pressed, int x, int y)
+{
+}
+
+void GameScreen::mmotion(int x, int y)
+{
+}
+
+void GameScreen::mwheel(int dir, int x, int y)
+{
+}
diff --git a/src/goatkit/boolanm.cc b/src/goatkit/boolanm.cc
new file mode 100644 (file)
index 0000000..7ca38a6
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2015 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "boolanm.h"
+
+static long default_get_msec();
+
+BoolAnim::BoolAnim(bool st)
+{
+       set(st);
+       trans_start = 0;
+       trans_dur = 500;
+       get_msec = default_get_msec;
+}
+
+void BoolAnim::update(long tm) const
+{
+       if(trans_dir == 0.0) return;
+
+       float dt = (tm - trans_start) / 1000.0;
+       float t = dt / (trans_dur / 1000.0);
+
+       if(trans_dir > 0.0) {
+               value = t;
+       } else {
+               value = 1.0 - t;
+       }
+
+       if(value < 0.0) {
+               value = 0.0;
+               trans_dir = 0.0;
+       } else if(value > 1.0) {
+               value = 1.0;
+               trans_dir = 0.0;
+       }
+}
+
+void BoolAnim::set_transition_duration(long dur)
+{
+       trans_dur = dur;
+}
+
+long BoolAnim::get_transition_duration() const
+{
+       return trans_dur;
+}
+
+void BoolAnim::set_time_callback(long (*time_func)())
+{
+       get_msec = time_func;
+}
+
+void BoolAnim::set(bool st)
+{
+       value = st ? 1.0 : 0.0;
+       trans_dir = 0.0;
+}
+
+void BoolAnim::change(bool st)
+{
+       change(st, get_msec());
+}
+
+void BoolAnim::change(bool st, long tm)
+{
+       trans_dir = st ? 1.0 : -1.0;
+       trans_start = tm;
+}
+
+bool BoolAnim::get_state() const
+{
+       return get_state(get_msec());
+}
+
+bool BoolAnim::get_state(long tm) const
+{
+       update(tm);
+
+       // if we're not in transition use the value (should be 0 or 1)
+       if(trans_dir == 0.0) {
+               return value > 0.5;
+       }
+
+       // if we're in transition base it on the direction of the transition
+       return trans_dir > 0.0;
+}
+
+float BoolAnim::get_value() const
+{
+       return get_value(get_msec());
+}
+
+float BoolAnim::get_value(long tm) const
+{
+       update(tm);
+       return value;
+}
+
+float BoolAnim::get_dir() const
+{
+       return get_dir(get_msec());
+}
+
+float BoolAnim::get_dir(long tm) const
+{
+       update(tm);
+       return trans_dir;
+}
+
+BoolAnim::operator bool() const
+{
+       return get_state();
+}
+
+BoolAnim::operator float() const
+{
+       return get_value();
+}
+
+#ifdef WIN32
+#include <windows.h>
+
+static long default_get_msec()
+{
+       return GetTickCount();
+}
+#else
+#include <sys/time.h>
+
+static long default_get_msec()
+{
+       static struct timeval tv0;
+       struct timeval tv;
+
+       gettimeofday(&tv, 0);
+       if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
+               tv0 = tv;
+               return 0;
+       }
+       return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
+}
+#endif
diff --git a/src/goatkit/boolanm.h b/src/goatkit/boolanm.h
new file mode 100644 (file)
index 0000000..52233c1
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2015 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef BOOLANIM_H_
+#define BOOLANIM_H_
+
+class BoolAnim {
+private:
+       mutable float value;
+       mutable float trans_dir;        // transition direction (sign)
+       long trans_start;       // transition start time
+       long trans_dur;
+
+       long (*get_msec)();
+
+       void update(long tm) const;
+
+public:
+       BoolAnim(bool st = false);
+
+       void set_transition_duration(long dur);
+       long get_transition_duration() const;
+
+       void set_time_callback(long (*time_func)());
+
+       void set(bool st);
+
+       void change(bool st);
+       void change(bool st, long trans_start);
+
+       bool get_state() const;
+       bool get_state(long tm) const;
+
+       float get_value() const;
+       float get_value(long tm) const;
+
+       // transition direction (-1, 0, 1)
+       float get_dir() const;
+       float get_dir(long tm) const;
+
+       operator bool() const;  // equivalent to get_state
+       operator float() const; // equivalent to get_value
+};
+
+#endif // BOOLANIM_H_
diff --git a/src/goatkit/button.cc b/src/goatkit/button.cc
new file mode 100644 (file)
index 0000000..aa7f1d0
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "button.h"
+
+namespace goatkit {
+
+struct ButtonImpl {
+};
+
+Button::Button()
+{
+       button = new ButtonImpl;
+}
+
+Button::~Button()
+{
+       delete button;
+}
+
+const char *Button::get_type_name() const
+{
+       return "button";
+}
+
+}      // namespace goatkit
diff --git a/src/goatkit/button.h b/src/goatkit/button.h
new file mode 100644 (file)
index 0000000..a0339af
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_BUTTON_H_
+#define GOATKIT_BUTTON_H_
+
+#include "widget.h"
+
+namespace goatkit {
+
+struct ButtonImpl;
+
+class Button : public Widget {
+private:
+       ButtonImpl *button;
+
+public:
+       Button();
+       virtual ~Button();
+
+       virtual const char *get_type_name() const;
+};
+
+}      // namespace goatkit
+
+#endif // GOATKIT_BUTTON_H_
diff --git a/src/goatkit/checkbox.cc b/src/goatkit/checkbox.cc
new file mode 100644 (file)
index 0000000..3492fa6
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2015  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "checkbox.h"
+#include "boolanm.h"
+
+namespace goatkit {
+
+struct CheckBoxImpl {
+       BoolAnim checked;
+};
+
+CheckBox::CheckBox()
+{
+       cbox = new CheckBoxImpl;
+       cbox->checked = false;
+       cbox->checked.set_transition_duration(60);
+}
+
+CheckBox::~CheckBox()
+{
+       delete cbox;
+}
+
+const char *CheckBox::get_type_name() const
+{
+       return "checkbox";
+}
+
+
+void CheckBox::check()
+{
+       cbox->checked.change(true);
+}
+
+void CheckBox::uncheck()
+{
+       cbox->checked.change(false);
+}
+
+float CheckBox::get_checked() const
+{
+       return cbox->checked.get_value();
+}
+
+bool CheckBox::is_checked() const
+{
+       return cbox->checked.get_state();
+}
+
+void CheckBox::toggle()
+{
+       if(is_checked()) {
+               uncheck();
+       } else {
+               check();
+       }
+}
+
+void CheckBox::set_toggle_transition(long msec)
+{
+       cbox->checked.set_transition_duration(msec);
+}
+
+long CheckBox::get_toggle_transition() const
+{
+       return cbox->checked.get_transition_duration();
+}
+
+void CheckBox::on_click()
+{
+       toggle();
+
+       Event ev;
+       ev.type = EV_CHANGE;
+       handle_event(ev);
+}
+
+}      // namespace goatkit
diff --git a/src/goatkit/checkbox.h b/src/goatkit/checkbox.h
new file mode 100644 (file)
index 0000000..66e3735
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_CHECKBOX_H_
+#define GOATKIT_CHECKBOX_H_
+
+#include "widget.h"
+
+namespace goatkit {
+
+struct CheckBoxImpl;
+
+class CheckBox : public Widget {
+private:
+       CheckBoxImpl *cbox;
+
+public:
+       CheckBox();
+       virtual ~CheckBox();
+
+       virtual const char *get_type_name() const;
+
+       virtual void check();
+       virtual void uncheck();
+       virtual float get_checked() const;
+       virtual bool is_checked() const;
+       virtual void toggle();
+       virtual void set_toggle_transition(long msec);
+       virtual long get_toggle_transition() const;
+
+       virtual void on_click();
+};
+
+}      // namespace goatkit
+
+#endif // GOATKIT_CHECKBOX_H_
diff --git a/src/goatkit/event.cc b/src/goatkit/event.cc
new file mode 100644 (file)
index 0000000..c7a59cc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "event.h"
+
+
+const char *goatkit::event_type_name(EventType type)
+{
+       // XXX keep this array synchronized with enum EventType
+       static const char *names[NUM_EVENTS] = {
+               "mouse button",
+               "mouse motion",
+               "focus",
+               "keyboard",
+               "click",
+               "double click",
+               "change"
+       };
+
+       return names[type];
+}
diff --git a/src/goatkit/event.h b/src/goatkit/event.h
new file mode 100644 (file)
index 0000000..9dc0a07
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_EVENT_H_
+#define GOATKIT_EVENT_H_
+
+#include "vec.h"
+
+namespace goatkit {
+
+enum EventType {
+       // primary events
+       EV_MOUSE_BUTTON,
+       EV_MOUSE_MOTION,
+       EV_MOUSE_FOCUS,
+       EV_KEY,
+
+       // derived events
+       EV_CLICK,
+       EV_DOUBLE_CLICK,
+       EV_CHANGE,
+
+       NUM_EVENTS
+};
+
+enum SpecialKeys {
+       KEY_ESCAPE = 27,
+       KEY_DELETE = 127,
+
+       KEY_F1 = 256, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6,
+       KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12,
+       KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
+       KEY_HOME, KEY_END, KEY_PGUP, KEY_PGDOWN,
+       KEY_INSERT
+};
+
+struct ButtonEvent {
+       Vec2 pos;
+       int button;
+       bool press;
+};
+
+struct MotionEvent {
+       Vec2 pos;
+};
+
+struct FocusEvent {
+       bool enter;
+};
+
+struct KeyEvent {
+       int key;
+       bool press;
+};
+
+struct Event {
+       EventType type;
+
+       ButtonEvent button;
+       MotionEvent motion;
+       FocusEvent focus;
+       KeyEvent key;
+};
+
+const char *event_type_name(EventType type);
+
+}      // namespace goatkit
+
+#endif // GOATKIT_EVENT_H_
diff --git a/src/goatkit/goatkit.h b/src/goatkit/goatkit.h
new file mode 100644 (file)
index 0000000..ce7f5ce
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_H_
+#define GOATKIT_H_
+
+#include "screen.h"
+#include "widget.h"
+#include "button.h"
+#include "checkbox.h"
+#include "label.h"
+#include "textbox.h"
+#include "slider.h"
+#include "event.h"
+#include "theme.h"
+
+#endif // GOATKIT_H_
diff --git a/src/goatkit/label.cc b/src/goatkit/label.cc
new file mode 100644 (file)
index 0000000..31c5623
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "label.h"
+
+namespace goatkit {
+
+struct LabelImpl {
+};
+
+Label::Label()
+{
+       label = new LabelImpl;
+}
+
+Label::~Label()
+{
+       delete label;
+}
+
+const char *Label::get_type_name() const
+{
+       return "label";
+}
+
+}      // namespace goatkit
diff --git a/src/goatkit/label.h b/src/goatkit/label.h
new file mode 100644 (file)
index 0000000..8b7ee1f
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_LABEL_H_
+#define GOATKIT_LABEL_H_
+
+#include "widget.h"
+
+namespace goatkit {
+
+struct LabelImpl;
+
+class Label : public Widget {
+private:
+       LabelImpl *label;
+
+public:
+       Label();
+       virtual ~Label();
+
+       virtual const char *get_type_name() const;
+};
+
+}      // namespace goatkit
+
+#endif // GOATKIT_LABEL_H_
diff --git a/src/goatkit/screen.cc b/src/goatkit/screen.cc
new file mode 100644 (file)
index 0000000..65e734d
--- /dev/null
@@ -0,0 +1,318 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2015  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vector>
+#include "screen.h"
+#include "widget.h"
+#include "boolanm.h"
+
+#define MAX_BUTTONS            16
+
+namespace goatkit {
+
+struct ScreenImpl {
+       BoolAnim visible;
+       std::vector<Widget*> widgets;
+       BBox box;
+
+       Widget *inp_focused, *over, *pressed[MAX_BUTTONS];
+       Widget *mgrab;
+};
+
+static Vec2 world_to_scr(const ScreenImpl *scr, const Vec2 &v);
+//static Vec2 scr_to_world(const ScreenImpl *scr, const Vec2 &v)
+
+Screen::Screen()
+{
+       scr = new ScreenImpl;
+
+       scr->box.bmin = Vec2(0, 0);
+       scr->box.bmax = Vec2(1, 1);
+
+       scr->inp_focused = scr->over = 0;
+       for(int i=0; i<MAX_BUTTONS; i++) {
+               scr->pressed[i] = 0;
+       }
+
+       scr->visible = true;
+       scr->mgrab = 0;
+}
+
+Screen::~Screen()
+{
+       delete scr;
+}
+
+void Screen::set_position(float x, float y)
+{
+       set_position(Vec2(x, y));
+}
+
+void Screen::set_position(const Vec2 &pos)
+{
+       Vec2 sz = get_size();
+
+       scr->box.bmin = pos;
+       scr->box.bmax.x = pos.x + sz.x;
+       scr->box.bmax.y = pos.y + sz.y;
+}
+
+const Vec2 &Screen::get_position() const
+{
+       return scr->box.bmin;
+}
+
+void Screen::set_size(float x, float y)
+{
+       set_size(Vec2(x, y));
+}
+
+void Screen::set_size(const Vec2 &sz)
+{
+       scr->box.bmax.x = scr->box.bmin.x + sz.x;
+       scr->box.bmax.y = scr->box.bmin.y + sz.y;
+}
+
+const Vec2 Screen::get_size() const
+{
+       return Vec2(scr->box.bmax.x - scr->box.bmin.x,
+                       scr->box.bmax.y - scr->box.bmin.y);
+}
+
+const BBox &Screen::get_box() const
+{
+       return scr->box;
+}
+
+void Screen::add_widget(Widget *w)
+{
+       scr->widgets.push_back(w);
+       if(scr->visible) {
+               w->show();
+       } else {
+               w->hide();
+       }
+
+       w->set_screen(this);
+}
+
+int Screen::get_widget_count() const
+{
+       return (int)scr->widgets.size();
+}
+
+Widget *Screen::get_widget(int idx) const
+{
+       if(idx < 0 || idx >= (int)scr->widgets.size()) {
+               return 0;
+       }
+       return scr->widgets[idx];
+}
+
+Widget *Screen::get_widget(const char *name) const
+{
+       for(size_t i=0; i<scr->widgets.size(); i++) {
+               if(strcmp(scr->widgets[i]->get_name(), name) == 0) {
+                       return scr->widgets[i];
+               }
+       }
+       return 0;
+}
+
+void Screen::show()
+{
+       scr->visible.change(true);
+
+       for(size_t i=0; i<scr->widgets.size(); i++) {
+               scr->widgets[i]->show();
+       }
+}
+
+void Screen::hide()
+{
+       scr->visible.change(false);
+
+       for(size_t i=0; i<scr->widgets.size(); i++) {
+               scr->widgets[i]->hide();
+       }
+}
+
+bool Screen::is_visible() const
+{
+       return scr->visible;
+}
+
+float Screen::get_visibility() const
+{
+       return scr->visible.get_value();
+}
+
+void Screen::set_visibility_transition(long msec)
+{
+       scr->visible.set_transition_duration(msec);
+       for(size_t i=0; i<scr->widgets.size(); i++) {
+               scr->widgets[i]->set_visibility_transition(msec);
+       }
+}
+
+long Screen::get_visibility_transition() const
+{
+       return scr->visible.get_transition_duration();
+}
+
+bool Screen::grab_mouse(Widget *w)
+{
+       if(!scr->mgrab || !w) {
+               scr->mgrab = w;
+               return true;
+       }
+       return false;
+}
+
+void Screen::draw() const
+{
+       for(size_t i=0; i<scr->widgets.size(); i++) {
+               scr->widgets[i]->draw();
+       }
+}
+
+static Widget *find_widget_at(const ScreenImpl *scr, const Vec2 &pt)
+{
+       for(size_t i=0; i<scr->widgets.size(); i++) {
+               Widget *w = scr->widgets[i];
+
+               if(w->hit_test(pt)) {
+                       return w;
+               }
+       }
+       return 0;
+}
+
+void Screen::sysev_keyboard(int key, bool press)
+{
+       Event ev;
+
+       if(scr->inp_focused) {
+               ev.type = EV_KEY;
+               ev.key.key = key;
+               ev.key.press = press;
+               scr->inp_focused->handle_event(ev);
+       }
+}
+
+void Screen::sysev_mouse_button(int bn, bool press, float x, float y)
+{
+       Event ev;
+       Vec2 pt = world_to_scr(scr, Vec2(x, y));
+       Widget *new_over = scr->mgrab ? scr->mgrab : find_widget_at(scr, pt);
+
+       ev.type = EV_MOUSE_BUTTON;
+       ev.button.button = bn;
+       ev.button.pos = pt;
+       ev.button.press = press;
+
+       if(press) {
+               if(bn == 0) {
+                       // left click gives input focus
+                       // TODO: add input focus event in widget
+                       if(new_over && new_over != scr->inp_focused && new_over->can_focus()) {
+                               printf("input focus %p -> %p\n", (void*)scr->inp_focused, (void*)new_over);
+                               new_over->focusin();
+
+                               if(scr->inp_focused) {
+                                       scr->inp_focused->focusout();
+                               }
+                               scr->inp_focused = new_over;
+                       }
+               }
+
+               scr->pressed[bn] = new_over;
+               scr->over = new_over;
+
+               if(new_over) {
+                       new_over->handle_event(ev);
+               }
+
+       } else {
+               // send the mouse release event to the widget that got the matching press
+               if(scr->pressed[bn]) {
+                       scr->pressed[bn]->handle_event(ev);
+                       scr->pressed[bn] = 0;
+               }
+       }
+
+       // if we're not over the same widget any more send the leave/enter events
+       // TODO also add drag/drop events
+       if(scr->over != new_over) {
+               ev.type = EV_MOUSE_FOCUS;
+               if(scr->over) {
+                       ev.focus.enter = false;
+                       scr->over->handle_event(ev);
+               }
+               if(new_over) {
+                       ev.focus.enter = true;
+                       new_over->handle_event(ev);
+               }
+               scr->over = new_over;
+       }
+}
+
+void Screen::sysev_mouse_motion(float x, float y)
+{
+       Event ev;
+       Vec2 pt = world_to_scr(scr, Vec2(x, y));
+       Widget *new_over = scr->mgrab ? scr->mgrab : find_widget_at(scr, pt);
+
+       // if we're not over the same widget any more send the leave/enter events
+       if(scr->over != new_over) {
+               ev.type = EV_MOUSE_FOCUS;
+               if(scr->over) {
+                       ev.focus.enter = false;
+                       scr->over->handle_event(ev);
+               }
+               if(new_over) {
+                       ev.focus.enter = true;
+                       new_over->handle_event(ev);
+               }
+               scr->over = new_over;
+       }
+
+       if(new_over) {
+               // send motion event
+               ev.type = EV_MOUSE_MOTION;
+               ev.motion.pos = pt;
+               new_over->handle_event(ev);
+       }
+}
+
+static Vec2 world_to_scr(const ScreenImpl *scr, const Vec2 &v)
+{
+       return Vec2(v.x - scr->box.bmin.x, v.y - scr->box.bmin.y);
+}
+
+/*
+static Vec2 scr_to_world(const ScreenImpl *scr, const Vec2 &v)
+{
+       return Vec2(v.x + scr->box.bmin.x, v.y + scr->box.bmin.y);
+}
+*/
+
+
+} // namespace goatkit
diff --git a/src/goatkit/screen.h b/src/goatkit/screen.h
new file mode 100644 (file)
index 0000000..d587bae
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_SCREEN_H_
+#define GOATKIT_SCREEN_H_
+
+#include "vec.h"
+#include "widget.h"
+
+namespace goatkit {
+
+class ScreenImpl;
+
+class Screen {
+private:
+       ScreenImpl *scr;
+
+public:
+       Screen();
+       ~Screen();
+
+       void set_position(float x, float y);
+       void set_position(const Vec2 &pos);
+       const Vec2 &get_position() const;
+       void set_size(float x, float y);
+       void set_size(const Vec2 &sz);
+       const Vec2 get_size() const;
+       const BBox &get_box() const;
+
+       void add_widget(Widget *w);
+       int get_widget_count() const;
+       Widget *get_widget(int idx) const;
+       Widget *get_widget(const char *name) const;
+
+       void show();
+       void hide();
+       bool is_visible() const;
+       float get_visibility() const;
+       void set_visibility_transition(long msec);
+       long get_visibility_transition() const;
+
+       bool grab_mouse(Widget *w);
+
+       void draw() const;
+
+       // window system events used to generate widget events (see event.h)
+       void sysev_keyboard(int key, bool press);
+       // mouse position as reported by the window system. might fall outside
+       void sysev_mouse_button(int bn, bool press, float x, float y);
+       void sysev_mouse_motion(float x, float y);
+};
+
+}      // namespace goatkit
+
+#endif /* GOATKIT_SCREEN_H_ */
diff --git a/src/goatkit/slider.cc b/src/goatkit/slider.cc
new file mode 100644 (file)
index 0000000..6f03571
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <math.h>
+#include "slider.h"
+#include "screen.h"
+
+namespace goatkit {
+
+struct SliderImpl {
+       float value, prev_value;
+       float range_min, range_max;
+       float padding;
+       float step;
+       bool dragging;
+       bool cont_change;
+};
+
+static float remap(float val, float inlow, float inhigh, float outlow, float outhigh);
+
+Slider::Slider()
+{
+       slider = new SliderImpl;
+       slider->value = slider->prev_value = 0.0f;
+       slider->dragging = false;
+       slider->cont_change = true;
+
+       slider->range_min = 0.0;
+       slider->range_max = 1.0;
+       slider->step = 0.0;
+
+       slider->padding = -1.0;
+}
+
+Slider::~Slider()
+{
+       delete slider;
+}
+
+const char *Slider::get_type_name() const
+{
+       return "slider";
+}
+
+void Slider::set_value(float val)
+{
+       slider->value = remap(val, slider->range_min, slider->range_max, 0, 1);
+}
+
+float Slider::get_value() const
+{
+       return remap(slider->value, 0, 1, slider->range_min, slider->range_max);
+}
+
+void Slider::set_value_norm(float val)
+{
+       slider->value = val < 0.0 ? 0.0 : (val > 1.0 ? 1.0 : val);
+}
+
+float Slider::get_value_norm() const
+{
+       return slider->value;
+}
+
+void Slider::set_padding(float pad)
+{
+       slider->padding = pad;
+}
+
+float Slider::get_padding() const
+{
+       if(slider->padding < 0.0) {
+               BBox box = get_box();
+               return (box.bmax.y - box.bmin.y) * 0.25;
+       }
+       return slider->padding;
+}
+
+void Slider::set_continuous_change(bool cont)
+{
+       slider->cont_change = cont;
+}
+
+bool Slider::get_continuous_change() const
+{
+       return slider->cont_change;
+}
+
+void Slider::set_range(float min, float max)
+{
+       slider->range_min = min;
+       slider->range_max = max;
+}
+
+float Slider::get_range_min() const
+{
+       return slider->range_min;
+}
+
+float Slider::get_range_max() const
+{
+       return slider->range_max;
+}
+
+void Slider::set_step(float step)
+{
+       slider->step = step;
+}
+
+float Slider::get_step() const
+{
+       return slider->step;
+}
+
+void Slider::on_mouse_button(const ButtonEvent &ev)
+{
+       if(ev.button == 0) {
+               Screen *scr = get_screen();
+
+               slider->dragging = ev.press;
+               if(ev.press) {
+                       if(scr) scr->grab_mouse(this);
+               } else {
+                       if(scr) scr->grab_mouse(0);
+
+                       // on release, if the value has changed send the appropriate event
+                       if(slider->prev_value != slider->value) {
+                               Event ev;
+                               ev.type = EV_CHANGE;
+                               handle_event(ev);
+                               slider->prev_value = slider->value;
+                       }
+               }
+       }
+}
+
+#define ROUND(x)       floor((x) + 0.5)
+
+void Slider::on_mouse_motion(const MotionEvent &ev)
+{
+       if(!slider->dragging) {
+               return;
+       }
+
+       BBox box = get_box();
+
+       float padding = get_padding();
+       float start = box.bmin.x + padding;
+       float end = box.bmax.x - padding;
+       float new_val = (ev.pos.x - start) / (end - start);
+
+       // if we have a non-zero step, snap to the nearest value
+       if(slider->step > 0.0) {
+               float range = slider->range_max - slider->range_min;
+               float st = slider->step / range;
+
+               new_val = ROUND(new_val / st) * st;
+       }
+
+       if(new_val < 0.0) new_val = 0.0;
+       if(new_val > 1.0) new_val = 1.0;
+
+       if(new_val != slider->value) {
+               slider->value = new_val;
+               if(slider->cont_change) {
+                       Event cev;
+                       cev.type = EV_CHANGE;
+                       handle_event(cev);
+               }
+       }
+}
+
+static float remap(float val, float inlow, float inhigh, float outlow, float outhigh)
+{
+       float t = (val - inlow) / (inhigh - inlow);
+       if(t < 0.0) t = 0.0;
+       if(t > 1.0) t = 1.0;
+       return t * (outhigh - outlow) + outlow;
+}
+
+}      // namespace goatkit
diff --git a/src/goatkit/slider.h b/src/goatkit/slider.h
new file mode 100644 (file)
index 0000000..7b5899f
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_SLIDER_H_
+#define GOATKIT_SLIDER_H_
+
+#include "widget.h"
+
+namespace goatkit {
+
+struct SliderImpl;
+
+class Slider : public Widget {
+private:
+       SliderImpl *slider;
+
+public:
+       Slider();
+       virtual ~Slider();
+
+       virtual const char *get_type_name() const;
+
+       virtual void set_value(float val);
+       virtual float get_value() const;
+
+       virtual void set_value_norm(float val);
+       virtual float get_value_norm() const;
+
+       virtual void set_padding(float pad);
+       virtual float get_padding() const;
+
+       virtual void set_continuous_change(bool cont);
+       virtual bool get_continuous_change() const;
+
+       virtual void set_range(float min, float max);
+       virtual float get_range_min() const;
+       virtual float get_range_max() const;
+
+       virtual void set_step(float step);
+       virtual float get_step() const;
+
+       virtual void on_mouse_button(const ButtonEvent &ev);
+       virtual void on_mouse_motion(const MotionEvent &ev);
+};
+
+}      // namespace goatkit
+
+#endif // GOATKIT_SLIDER_H_
diff --git a/src/goatkit/textbox.cc b/src/goatkit/textbox.cc
new file mode 100644 (file)
index 0000000..60f82ed
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <ctype.h>
+#include <limits.h>
+#include <string>
+#include "textbox.h"
+
+namespace goatkit {
+
+struct TextBoxImpl {
+       std::string text;
+       int cursor;
+};
+
+TextBox::TextBox()
+{
+       tbox = new TextBoxImpl;
+       tbox->cursor = 0;
+}
+
+TextBox::~TextBox()
+{
+       delete tbox;
+}
+
+const char *TextBox::get_type_name() const
+{
+       return "textbox";
+}
+
+bool TextBox::can_focus() const
+{
+       return true;
+}
+
+void TextBox::clear()
+{
+       tbox->text.clear();
+}
+
+void TextBox::set_text(const char *t)
+{
+       tbox->text = std::string(t);
+}
+
+const char *TextBox::get_text() const
+{
+       return tbox->text.c_str();
+}
+
+int TextBox::set_cursor(int idx)
+{
+       int len = tbox->text.size();
+
+       if(idx < 0) {
+               tbox->cursor = 0;
+       } else if(idx > len) {
+               tbox->cursor = len;
+       } else {
+               tbox->cursor = idx;
+       }
+       return tbox->cursor;
+}
+
+int TextBox::get_cursor() const
+{
+       return tbox->cursor;
+}
+
+int TextBox::cursor_begin()
+{
+       return tbox->cursor = 0;
+}
+
+int TextBox::cursor_end()
+{
+       return set_cursor(INT_MAX);
+}
+
+int TextBox::cursor_prev()
+{
+       return set_cursor(tbox->cursor - 1);
+}
+
+int TextBox::cursor_next()
+{
+       return set_cursor(tbox->cursor + 1);
+}
+
+void TextBox::insert(char c)
+{
+       int len = tbox->text.size();
+       if(tbox->cursor >= len) {
+               tbox->text.push_back(c);
+               tbox->cursor++;
+       } else {
+               tbox->text.insert(tbox->cursor++, 1, c);
+       }
+}
+
+void TextBox::on_key(const KeyEvent &ev)
+{
+       if(!ev.press) return;   // ignore key release events
+
+       switch(ev.key) {
+       case KEY_LEFT:
+               cursor_prev();
+               break;
+
+       case KEY_RIGHT:
+               cursor_next();
+               break;
+
+       case KEY_HOME:
+               cursor_begin();
+               break;
+
+       case KEY_END:
+               cursor_end();
+               break;
+
+       case KEY_DELETE:
+               tbox->text.erase(tbox->cursor, 1);
+               break;
+
+       case '\b':
+               if(tbox->cursor > 0) {
+                       tbox->text.erase(--tbox->cursor, 1);
+               }
+               break;
+
+       case '\n':
+       case '\t':
+               {
+                       Event ev;
+                       ev.type = EV_CHANGE;
+                       handle_event(ev);
+               }
+               break;
+
+       default:
+               if(isprint(ev.key)) {
+                       insert(ev.key);
+               }
+       }
+}
+
+void TextBox::on_click()
+{
+       // TODO place cursor
+}
+
+}      // namespace goatkit
diff --git a/src/goatkit/textbox.h b/src/goatkit/textbox.h
new file mode 100644 (file)
index 0000000..59ea8e0
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_TEXTBOX_H_
+#define GOATKIT_TEXTBOX_H_
+
+#include "widget.h"
+
+namespace goatkit {
+
+struct TextBoxImpl;
+
+class TextBox : public Widget {
+private:
+       TextBoxImpl *tbox;
+
+public:
+       TextBox();
+       virtual ~TextBox();
+
+       virtual const char *get_type_name() const;
+       virtual bool can_focus() const;
+
+       virtual void clear();
+
+       virtual void set_text(const char *t);
+       virtual const char *get_text() const;
+
+       virtual int set_cursor(int idx);
+       virtual int get_cursor() const;
+
+       virtual int cursor_begin();
+       virtual int cursor_end();
+       virtual int cursor_prev();
+       virtual int cursor_next();
+
+       virtual void insert(char c);
+
+       virtual void on_key(const KeyEvent &ev);
+       virtual void on_click();
+};
+
+}      // namespace goatkit
+
+#endif // GOATKIT_TEXTBOX_H_
diff --git a/src/goatkit/theme.cc b/src/goatkit/theme.cc
new file mode 100644 (file)
index 0000000..8f46615
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <vector>
+#include <string>
+#include <map>
+#include <algorithm>
+#include <string.h>
+#include "theme.h"
+#include "widget.h"
+
+#ifdef WIN32
+#include <windows.h>
+
+static void *dlopen(const char *name, int flags);
+static void dlclose(void *so);
+static void *dlsym(void *so, const char *symbol);
+#else
+#include <unistd.h>
+#include <dlfcn.h>
+#endif
+
+#ifdef HAVE_OPENGL_H
+#include "opengl.h"
+
+#else
+
+#ifdef __APPLE__
+#include <OpenGL/gl.h>
+#else
+#include <GL/gl.h>
+#endif
+
+#endif /* HAVE_OPENGL_H_ */
+
+#ifndef PREFIX
+#define PREFIX "/usr/local"
+#endif
+
+namespace goatkit {
+
+struct ThemeImpl {
+       void *so;
+       WidgetDrawFunc (*lookup_theme_draw_func)(const char*);
+       mutable std::map<std::string, WidgetDrawFunc> func_cache;
+};
+
+Theme *theme;
+static std::vector<std::string> search_paths;
+static const char *fallback_paths[] = {
+       PREFIX "/share/goatkit",
+       0
+};
+
+typedef std::map<std::string, Theme*> ThemeMap;
+static ThemeMap *themes;
+
+
+void add_theme_path(const char *path)
+{
+       if(!path || !*path) return;
+
+       std::string s = path;
+       int last = s.length() - 1;
+       if(s[last] == '/' || s[last] == '\\') {
+               s.erase(last);
+       }
+
+       if(std::find(search_paths.begin(), search_paths.end(), s) != search_paths.end()) {
+               return;
+       }
+
+       search_paths.push_back(s);
+}
+
+void register_theme(const char *name, Theme *theme)
+{
+       if(!themes) {
+               themes = new ThemeMap;
+       }
+
+       Theme *prev = (*themes)[name];
+       if(prev) {
+               delete prev;
+       }
+       (*themes)[name] = theme;
+}
+
+Theme *get_theme(const char *name)
+{
+       // first search in the already registered themes
+       ThemeMap::const_iterator it = themes->find(name);
+       if(it != themes->end()) {
+               return it->second;
+       }
+
+       // then try loading it from a theme plugin
+       Theme *theme = new Theme;
+       if(theme->load(name)) {
+               return theme;
+       }
+
+       fprintf(stderr, "[goatkit] theme \"%s\" not found!\n", name);
+       delete theme;
+       return 0;
+}
+
+Theme::Theme()
+{
+       impl = new ThemeImpl;
+       impl->so = 0;
+       impl->lookup_theme_draw_func = 0;
+}
+
+Theme::Theme(const char *name, WidgetLookupFunc func)
+{
+       impl = new ThemeImpl;
+       impl->so = 0;
+       impl->lookup_theme_draw_func = func;
+
+       register_theme(name, this);
+}
+
+Theme::~Theme()
+{
+       unload();
+       delete impl;
+}
+
+typedef WidgetDrawFunc (*LookupFunc)(const char*);
+
+bool Theme::load(const char *name)
+{
+       unload();
+
+       std::string fname = std::string(name) + ".gtheme";
+       if(!(impl->so = dlopen(fname.c_str(), RTLD_LAZY))) {
+               for(size_t i=0; i<search_paths.size(); i++) {
+                       std::string path = search_paths[i] + "/" + fname;
+
+                       if((impl->so = dlopen(path.c_str(), RTLD_LAZY))) {
+                               break;
+                       }
+               }
+
+               // try the fallback paths
+               if(!impl->so) {
+                       for(int i=0; fallback_paths[i]; i++) {
+                               std::string path = std::string(fallback_paths[i]) + "/" + fname;
+
+                               if((impl->so = dlopen(path.c_str(), RTLD_LAZY))) {
+                                       break;
+                               }
+                       }
+               }
+
+               if(!impl->so) {
+                       fprintf(stderr, "%s: failed to load theme plugin: %s\n", __func__, name);
+                       return false;
+               }
+       }
+
+       // loaded the shared object, now get the lookup function
+       impl->lookup_theme_draw_func = (LookupFunc)dlsym(impl->so, "get_widget_func");
+       if(!impl->lookup_theme_draw_func) {
+               fprintf(stderr, "%s: invalid theme plugin %s\n", __func__, name);
+               unload();
+               return false;
+       }
+
+       register_theme(name, this);
+       return true;
+}
+
+void Theme::unload()
+{
+       if(impl->so) {
+               dlclose(impl->so);
+               impl->so = 0;
+       }
+       impl->func_cache.clear();
+}
+
+WidgetDrawFunc Theme::get_draw_func(const char *type) const
+{
+       std::map<std::string, WidgetDrawFunc>::const_iterator it = impl->func_cache.find(type);
+       if(it == impl->func_cache.end()) {
+               // don't have it cached, try to look it up
+               WidgetDrawFunc func;
+               if(impl->lookup_theme_draw_func && (func = impl->lookup_theme_draw_func(type))) {
+                       impl->func_cache[type] = func;
+                       return func;
+               }
+
+               // can't look it up, return the default
+               return default_draw_func;
+       }
+       return it->second;
+}
+
+#define LERP(a, b, t)  ((a) + ((b) - (a)) * t)
+#define DEF_TEX_SZ     32
+void default_draw_func(const Widget *w)
+{
+       static unsigned int tex;
+
+       if(!tex) {
+               unsigned char *pixels = new unsigned char[DEF_TEX_SZ * DEF_TEX_SZ * 3];
+               unsigned char *ptr = pixels;
+               for(int i=0; i<DEF_TEX_SZ; i++) {
+                       for(int j=0; j<DEF_TEX_SZ; j++) {
+                               bool stripe = (((j + i) / 8) & 1) == 1;
+                               ptr[0] = ptr[1] = ptr[2] = stripe ? 255 : 0;
+                               ptr += 3;
+                       }
+               }
+
+               glGenTextures(1, &tex);
+               glBindTexture(GL_TEXTURE_2D, tex);
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+               glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, DEF_TEX_SZ, DEF_TEX_SZ, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
+               delete [] pixels;
+       }
+
+       Vec2 pos = w->get_position();
+       Vec2 sz = w->get_size();
+       float aspect = sz.x / sz.y;
+
+#if !defined(GL_ES_VERSION_2_0)
+       glPushAttrib(GL_ENABLE_BIT);
+       glEnable(GL_TEXTURE_2D);
+#endif
+       glBindTexture(GL_TEXTURE_2D, tex);
+
+       float offs = w->get_pressed() * 0.1 * sz.y;
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glTranslatef(offs, -offs, 0);
+
+       float active = w->get_active();
+       float hover = w->get_under_mouse();
+
+       float rg = LERP(0.4, 1.0, hover);
+       float b = LERP(rg, 0, active);
+       glColor3f(rg, rg, b);
+
+       glBegin(GL_QUADS);
+       glTexCoord2f(0, 1);
+       glVertex2f(pos.x, pos.y);
+       glTexCoord2f(aspect, 1);
+       glVertex2f(pos.x + sz.x, pos.y);
+       glTexCoord2f(aspect, 0);
+       glVertex2f(pos.x + sz.x, pos.y + sz.y);
+       glTexCoord2f(0, 0);
+       glVertex2f(pos.x, pos.y + sz.y);
+       glEnd();
+
+       glPopMatrix();
+
+#ifndef GL_ES_VERSION_2_0
+       glPopAttrib();
+#endif
+}
+
+}      // namespace goatkit
+
+#ifdef WIN32
+// XXX untested
+static void *dlopen(const char *name, int flags)
+{
+       return LoadLibrary(name);
+}
+
+static void dlclose(void *so)
+{
+       FreeLibrary(so);
+}
+
+static void *dlsym(void *so, const char *symbol)
+{
+       if(!so) {
+               so = GetModuleHandle(0);
+       }
+       return (void*)GetProcAddress(so, symbol);
+}
+#endif
diff --git a/src/goatkit/theme.h b/src/goatkit/theme.h
new file mode 100644 (file)
index 0000000..7845230
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_THEME_H_
+#define GOATKIT_THEME_H_
+
+#define GOATKIT_BUILTIN_THEME(n, f)    \
+       static goatkit::Theme goatkit_theme##__LINE__(n, f)
+
+namespace goatkit {
+
+class Widget;
+class Theme;
+struct ThemeImpl;
+
+typedef void (*WidgetDrawFunc)(const Widget*);
+typedef WidgetDrawFunc (*WidgetLookupFunc)(const char*);
+
+void add_theme_path(const char *path);
+void default_draw_func(const Widget *w);
+
+void register_theme(const char *name, Theme *theme);
+Theme *get_theme(const char *name);
+
+class Theme {
+private:
+       ThemeImpl *impl;
+
+public:
+       Theme();
+       Theme(const char *name, WidgetLookupFunc func);
+       ~Theme();
+
+       bool load(const char *name);
+       void unload();
+
+       WidgetDrawFunc get_draw_func(const char *type) const;
+};
+
+extern Theme *theme;   // the current theme
+
+}      // namespace goatkit
+
+#endif // GOATKIT_THEME_H_
diff --git a/src/goatkit/vec.h b/src/goatkit/vec.h
new file mode 100644 (file)
index 0000000..7819a4b
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_VEC_H_
+#define GOATKIT_VEC_H_
+
+namespace goatkit {
+
+class Vec2 {
+public:
+       float x, y;
+
+       Vec2() : x(0), y(0) {}
+       Vec2(float xx, float yy) : x(xx), y(yy) {}
+};
+
+}      // namespace goatkit
+
+#endif // GOATKIT_VEC_H_
diff --git a/src/goatkit/widget.cc b/src/goatkit/widget.cc
new file mode 100644 (file)
index 0000000..aeee9e0
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2015 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <string>
+#include <sstream>
+#include "widget.h"
+#include "boolanm.h"
+#include "theme.h"
+#include "screen.h"
+
+namespace goatkit {
+
+struct WidgetImpl {
+       Screen *scr;
+       std::string name, text;
+       BBox box;
+
+       BoolAnim visible, active, press, hover, focus;
+
+       struct {
+               EventCallback func;
+               void *cls;
+       } cb[NUM_EVENTS];
+};
+
+
+Widget::Widget()
+{
+       static int widget_count;
+
+       widget = new WidgetImpl;
+       widget->scr = 0;
+
+       std::stringstream sstr;
+       sstr << get_type_name() << widget_count++;
+       widget->name = sstr.str();
+
+       widget->box.bmin = Vec2(0, 0);
+       widget->box.bmax = Vec2(1, 1);
+
+       widget->visible.set(true);
+       widget->active.set(true);
+
+       widget->hover.set_transition_duration(250);
+       widget->press.set_transition_duration(50);
+
+       memset(widget->cb, 0, sizeof widget->cb);
+}
+
+Widget::~Widget()
+{
+       delete widget;
+}
+
+void Widget::set_screen(Screen *scr)
+{
+       widget->scr = scr;
+}
+
+Screen *Widget::get_screen() const
+{
+       return widget->scr;
+}
+
+const char *Widget::get_type_name() const
+{
+       return "widget";
+}
+
+void Widget::set_name(const char *name)
+{
+       widget->name = std::string(name);
+}
+
+const char *Widget::get_name() const
+{
+       return widget->name.c_str();
+}
+
+void Widget::set_text(const char *text)
+{
+       widget->text = std::string(text);
+}
+
+const char *Widget::get_text() const
+{
+       return widget->text.c_str();
+}
+
+void Widget::show()
+{
+       widget->visible.change(true);
+}
+
+void Widget::hide()
+{
+       widget->visible.change(false);
+}
+
+float Widget::get_visibility() const
+{
+       return widget->visible.get_value();
+}
+
+bool Widget::is_visible() const
+{
+       return widget->visible.get_state();
+}
+
+void Widget::set_visibility_transition(long msec)
+{
+       widget->visible.set_transition_duration(msec);
+}
+
+long Widget::get_visibility_transition() const
+{
+       return widget->visible.get_transition_duration();
+}
+
+void Widget::activate()
+{
+       widget->active.change(true);
+}
+
+void Widget::deactivate()
+{
+       widget->active.change(false);
+}
+
+float Widget::get_active() const
+{
+       return widget->active.get_value();
+}
+
+bool Widget::is_active() const
+{
+       return widget->active.get_state();
+}
+
+void Widget::set_active_transition(long msec)
+{
+       widget->active.set_transition_duration(msec);
+}
+
+long Widget::get_active_transition() const
+{
+       return widget->active.get_transition_duration();
+}
+
+void Widget::press()
+{
+       widget->press.change(true);
+}
+
+void Widget::release()
+{
+       widget->press.change(false);
+}
+
+float Widget::get_pressed() const
+{
+       return widget->press.get_value();
+}
+
+bool Widget::is_pressed() const
+{
+       return widget->press.get_state();
+}
+
+void Widget::set_press_transition(long msec)
+{
+       widget->press.set_transition_duration(msec);
+}
+
+long Widget::get_press_transition() const
+{
+       return widget->press.get_transition_duration();
+}
+
+void Widget::mousein()
+{
+       widget->hover.change(true);
+}
+
+void Widget::mouseout()
+{
+       widget->hover.change(false);
+       if(widget->press) {
+               widget->press.change(false);
+       }
+}
+
+float Widget::get_under_mouse() const
+{
+       return widget->hover.get_value();
+}
+
+bool Widget::is_under_mouse() const
+{
+       return widget->hover.get_state();
+}
+
+void Widget::set_hover_transition(long msec)
+{
+       widget->hover.set_transition_duration(msec);
+}
+
+long Widget::get_hover_transition() const
+{
+       return widget->hover.get_transition_duration();
+}
+
+bool Widget::can_focus() const
+{
+       return false;
+}
+
+void Widget::focusin()
+{
+       widget->focus.change(true);
+}
+
+void Widget::focusout()
+{
+       widget->focus.change(false);
+}
+
+float Widget::get_focus() const
+{
+       return widget->focus.get_value();
+}
+
+bool Widget::is_focused() const
+{
+       return widget->focus.get_state();
+}
+
+void Widget::set_focus_transition(long msec)
+{
+       widget->focus.set_transition_duration(msec);
+}
+
+long Widget::get_focus_transition() const
+{
+       return widget->focus.get_transition_duration();
+}
+
+void Widget::set_position(float x, float y)
+{
+       set_position(Vec2(x, y));
+}
+
+void Widget::set_position(const Vec2 &pos)
+{
+       Vec2 sz = get_size();
+
+       widget->box.bmin = pos;
+       widget->box.bmax.x = pos.x + sz.x;
+       widget->box.bmax.y = pos.y + sz.y;
+}
+
+const Vec2 &Widget::get_position() const
+{
+       return widget->box.bmin;
+}
+
+void Widget::set_size(float x, float y)
+{
+       set_size(Vec2(x, y));
+}
+
+void Widget::set_size(const Vec2 &sz)
+{
+       widget->box.bmax.x = widget->box.bmin.x + sz.x;
+       widget->box.bmax.y = widget->box.bmin.y + sz.y;
+}
+
+const Vec2 Widget::get_size() const
+{
+       return Vec2(widget->box.bmax.x - widget->box.bmin.x,
+                       widget->box.bmax.y - widget->box.bmin.y);
+}
+
+
+const BBox &Widget::get_box() const
+{
+       return widget->box;
+}
+
+bool Widget::hit_test(const Vec2 &pt) const
+{
+       return pt.x >= widget->box.bmin.x && pt.x < widget->box.bmax.x &&
+               pt.y >= widget->box.bmin.y && pt.y < widget->box.bmax.y;
+}
+
+void Widget::draw() const
+{
+       WidgetDrawFunc draw_func = default_draw_func;
+
+       if(theme) {
+               draw_func = theme->get_draw_func(get_type_name());
+       }
+
+       draw_func(this);
+}
+
+// dummy event handlers
+void Widget::on_mouse_button(const ButtonEvent &ev)
+{
+}
+
+void Widget::on_mouse_motion(const MotionEvent &ev)
+{
+}
+
+void Widget::on_mouse_focus(const FocusEvent &ev)
+{
+}
+
+void Widget::on_key(const KeyEvent &ev)
+{
+}
+
+void Widget::on_click()
+{
+}
+
+void Widget::on_double_click()
+{
+}
+
+void Widget::on_change()
+{
+}
+
+
+#define CALL_CB(w, ev) \
+       do { \
+               if((w)->widget->cb[ev.type].func) { \
+                       (w)->widget->cb[ev.type].func((w), ev, (w)->widget->cb[ev.type].cls); \
+               } \
+       } while(0)
+
+#define CALL_CB_TYPE(w, t) \
+       do { \
+               Event ev; \
+               ev.type = (t); \
+               CALL_CB(w, ev); \
+       } while(0)
+
+/* the event dispatcher generates high-level events (click, etc)
+ * and calls the on_whatever() functions for both low and high-level
+ * events.
+ * The on_whatever functions are called *after* any other actions performed
+ * here, to give subclasses the opportunity to override them easily, by
+ * overriding the on_ functions, without having to override handle_event itself
+ */
+// TODO also call callbacks here I guess...
+void Widget::handle_event(const Event &ev)
+{
+       switch(ev.type) {
+       case EV_MOUSE_BUTTON:
+               if(ev.button.press) {
+                       press();
+               } else {
+                       if(is_pressed()) {
+                               CALL_CB_TYPE(this, EV_CLICK);
+                               on_click();
+                       }
+                       release();
+               }
+
+               on_mouse_button(ev.button);
+               break;
+
+       case EV_MOUSE_MOTION:
+               on_mouse_motion(ev.motion);
+               break;
+
+       case EV_MOUSE_FOCUS:
+               if(ev.focus.enter) {
+                       mousein();
+               } else {
+                       mouseout();
+               }
+               on_mouse_focus(ev.focus);
+               break;
+
+       case EV_KEY:
+               on_key(ev.key);
+               break;
+
+       case EV_CHANGE:
+               on_change();
+               break;
+
+       default:
+               break;
+       }
+
+       CALL_CB(this, ev);
+}
+
+
+void Widget::set_callback(EventType evtype, EventCallback func, void *cls)
+{
+       widget->cb[evtype].func = func;
+       widget->cb[evtype].cls = cls;
+}
+
+
+}      // namespace goatkit
diff --git a/src/goatkit/widget.h b/src/goatkit/widget.h
new file mode 100644 (file)
index 0000000..3cdee78
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+GoatKit - a themable/animated widget toolkit for games
+Copyright (C) 2014-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef GOATKIT_WIDGET_H_
+#define GOATKIT_WIDGET_H_
+
+#include "vec.h"
+#include "event.h"
+
+namespace goatkit {
+
+struct BBox {
+       Vec2 bmin, bmax;
+};
+
+class Screen;
+class Widget;
+struct WidgetImpl;
+
+typedef void (*EventCallback)(Widget*, const Event &ev, void *cls);
+
+class Widget {
+protected:
+       WidgetImpl *widget;
+
+       virtual void set_screen(Screen *scr);
+       Screen *get_screen() const;
+
+public:
+       Widget();
+       virtual ~Widget();
+
+       virtual const char *get_type_name() const;
+
+       virtual void set_name(const char *name);
+       virtual const char *get_name() const;
+
+       virtual void set_text(const char *text);
+       virtual const char *get_text() const;
+
+       virtual void show();
+       virtual void hide();
+       virtual float get_visibility() const;
+       virtual bool is_visible() const;
+       virtual void set_visibility_transition(long msec);
+       virtual long get_visibility_transition() const;
+
+       virtual void activate();
+       virtual void deactivate();
+       virtual float get_active() const;
+       virtual bool is_active() const;
+       virtual void set_active_transition(long msec);
+       virtual long get_active_transition() const;
+
+       virtual void press();
+       virtual void release();
+       virtual float get_pressed() const;
+       virtual bool is_pressed() const;
+       virtual void set_press_transition(long msec);
+       virtual long get_press_transition() const;
+
+       virtual void mousein();
+       virtual void mouseout();
+       virtual float get_under_mouse() const;
+       virtual bool is_under_mouse() const;
+       virtual void set_hover_transition(long msec);
+       virtual long get_hover_transition() const;
+
+       // input focus, managed by the screen
+       virtual bool can_focus() const;
+       virtual void focusin();
+       virtual void focusout();
+       virtual float get_focus() const;
+       virtual bool is_focused() const;
+       virtual void set_focus_transition(long msec);
+       virtual long get_focus_transition() const;
+
+       virtual void set_position(float x, float y);
+       virtual void set_position(const Vec2 &pos);
+       virtual const Vec2 &get_position() const;
+
+       virtual void set_size(float x, float y);
+       virtual void set_size(const Vec2 &size);
+       virtual const Vec2 get_size() const;
+
+       virtual const BBox &get_box() const;
+
+       virtual bool hit_test(const Vec2 &pt) const;
+
+       virtual void draw() const;
+
+       // low level events
+       virtual void on_mouse_button(const ButtonEvent &ev);
+       virtual void on_mouse_motion(const MotionEvent &ev);
+       virtual void on_mouse_focus(const FocusEvent &ev);
+       virtual void on_key(const KeyEvent &ev);
+
+       // high level events
+       virtual void on_click();
+       virtual void on_double_click();
+       virtual void on_change();
+       //virtual void on_drag_move(int bn, const Vec2 &pt);
+       //virtual void on_drag_release(int bn, const Vec2 &pt);
+
+       // event dispatcher
+       virtual void handle_event(const Event &ev);
+
+       // external callback setting
+       virtual void set_callback(EventType evtype, EventCallback func, void *cls = 0);
+
+       friend class Screen;
+};
+
+}
+
+#endif // GOATKIT_WIDGET_H_
diff --git a/src/main.cc b/src/main.cc
new file mode 100644 (file)
index 0000000..1ee34ad
--- /dev/null
@@ -0,0 +1,190 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <GL/freeglut.h>
+#include "game.h"
+
+#define KEYST_SZ       65536 / 32
+
+static void display();
+static void idle();
+static void reshape(int x, int y);
+static void keydown(unsigned char key, int x, int y);
+static void keyup(unsigned char key, int x, int y);
+static void skeydown(int key, int x, int y);
+static void skeyup(int key, int x, int y);
+static void mouse(int bn, int st, int x, int y);
+static void motion(int x, int y);
+static void wheel(int wheel, int dir, int x, int y);
+
+static long prev_time;
+static uint32_t keystate[KEYST_SZ];
+static bool bnstate[16];
+static unsigned int modkeys;
+
+int main(int argc, char **argv)
+{
+       glutInit(&argc, argv);
+       glutInitWindowSize(1024, 600);
+       glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_SRGB | GLUT_MULTISAMPLE);
+       glutCreateWindow("ludum dare 42");
+
+       glutDisplayFunc(display);
+       glutIdleFunc(idle);
+       glutReshapeFunc(reshape);
+       glutKeyboardFunc(keydown);
+       glutKeyboardUpFunc(keyup);
+       glutSpecialFunc(skeydown);
+       glutSpecialUpFunc(skeyup);
+       glutMouseFunc(mouse);
+       glutMotionFunc(motion);
+       glutPassiveMotionFunc(motion);
+       glutMouseWheelFunc(wheel);
+
+       if(!game_init()) {
+               return 1;
+       }
+       atexit(game_cleanup);
+
+       prev_time = glutGet(GLUT_ELAPSED_TIME);
+
+       glutMainLoop();
+       return 0;
+}
+
+void game_quit()
+{
+       exit(0);
+}
+
+bool game_keystate(int key)
+{
+       int idx = key / 32;
+       int bit = key % 32;
+       return keystate[idx] & (1 << bit);
+}
+
+bool game_bnstate(int bn)
+{
+       return bnstate[bn];
+}
+
+unsigned int game_modkeys()
+{
+       return modkeys;
+}
+
+static void display()
+{
+       frame_time = glutGet(GLUT_ELAPSED_TIME);
+       frame_dt = (frame_time - prev_time) / 1000.0f;
+
+       game_draw();
+       glutSwapBuffers();
+}
+
+static void idle()
+{
+       glutPostRedisplay();
+}
+
+static void reshape(int x, int y)
+{
+       glViewport(0, 0, x, y);
+       win_width = x;
+       win_height = y;
+       win_aspect = (float)x / (float)y;
+
+       game_reshape(x, y);
+}
+
+static void keydown(unsigned char key, int x, int y)
+{
+       modkeys = glutGetModifiers();
+       keystate[key / 32] |= (1 << (key % 32));
+       game_keyboard(key, true);
+}
+
+static void keyup(unsigned char key, int x, int y)
+{
+       modkeys = glutGetModifiers();
+       keystate[key / 32] &= ~(1 << (key % 32));
+       game_keyboard(key, false);
+}
+
+static int conv_skey(int key)
+{
+       if(key >= GLUT_KEY_F1 && key <= GLUT_KEY_F12) {
+               return KEY_F1 + (key - GLUT_KEY_F1);
+       }
+
+       switch(key) {
+       case GLUT_KEY_LEFT:
+               return KEY_LEFT;
+       case GLUT_KEY_UP:
+               return KEY_UP;
+       case GLUT_KEY_RIGHT:
+               return KEY_RIGHT;
+       case GLUT_KEY_DOWN:
+               return KEY_DOWN;
+       case GLUT_KEY_PAGE_UP:
+               return KEY_PGUP;
+       case GLUT_KEY_PAGE_DOWN:
+               return KEY_PGDOWN;
+       case GLUT_KEY_HOME:
+               return KEY_HOME;
+       case GLUT_KEY_END:
+               return KEY_END;
+       case GLUT_KEY_INSERT:
+               return KEY_INSERT;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static void skeydown(int key, int x, int y)
+{
+       modkeys = glutGetModifiers();
+       keystate[key / 32] |= (1 << (key % 32));
+       game_keyboard(conv_skey(key), true);
+}
+
+static void skeyup(int key, int x, int y)
+{
+       modkeys = glutGetModifiers();
+       keystate[key / 32] &= ~(1 << (key % 32));
+       game_keyboard(conv_skey(key), false);
+}
+
+static void mouse(int bn, int st, int x, int y)
+{
+       int idx = bn - GLUT_LEFT_BUTTON;
+       bool pressed = st == GLUT_DOWN;
+
+       modkeys = glutGetModifiers();
+
+       if(idx == 3) {
+               wheel(0, 1, x, y);
+               return;
+       } else if(idx == 4) {
+               wheel(0, -1, x, y);
+               return;
+       }
+
+       if(idx < 16) {
+               bnstate[idx] = pressed;
+       }
+       game_mbutton(idx, pressed, x, y);
+}
+
+static void motion(int x, int y)
+{
+       game_mmotion(x, y);
+}
+
+static void wheel(int wheel, int dir, int x, int y)
+{
+       game_mwheel(dir, x, y);
+}
diff --git a/src/menuscr.cc b/src/menuscr.cc
new file mode 100644 (file)
index 0000000..7e910c4
--- /dev/null
@@ -0,0 +1,142 @@
+#include <stdio.h>
+#include "game.h"
+#include "screen.h"
+#include "opengl.h"
+#include "goatkit/goatkit.h"
+
+enum {
+       BN_START,
+       BN_EXIT
+};
+
+static void bn_handler(goatkit::Widget *w, const goatkit::Event &ev, void *cls);
+
+extern GameScreen *scr_game;
+
+static int virt_width = 800;
+static int virt_height = 600;
+static goatkit::Screen ui;
+static goatkit::Button *bn_start, *bn_exit;
+static bool start_pending, exit_pending;
+static bool stopped;
+
+bool MenuScreen::init()
+{
+       ui.hide();
+
+       int bnwidth = 200;
+       int bnheight = 40;
+
+       int xpos = (virt_width - bnwidth) / 2;
+       int ypos = 100;
+       int vsep = 80;
+
+       bn_start = new goatkit::Button;
+       bn_start->set_position(xpos, ypos += vsep);
+       bn_start->set_size(bnwidth, bnheight);
+       bn_start->set_text("Start");
+       bn_start->set_callback(goatkit::EV_CLICK, bn_handler, (void*)BN_START);
+       ui.add_widget(bn_start);
+
+       bn_exit = new goatkit::Button;
+       bn_exit->set_position(xpos, ypos += vsep);
+       bn_exit->set_size(bnwidth, bnheight);
+       bn_exit->set_text("Exit");
+       bn_exit->set_callback(goatkit::EV_CLICK, bn_handler, (void*)BN_EXIT);
+       ui.add_widget(bn_exit);
+
+       ui.set_visibility_transition(300);
+
+       if(!(goatkit::theme = goatkit::get_theme("simple"))) {
+               return false;
+       }
+
+       return true;
+}
+
+void MenuScreen::destroy()
+{
+}
+
+
+void MenuScreen::start()
+{
+       ui.show();
+       stopped = false;
+}
+
+void MenuScreen::stop()
+{
+       ui.hide();
+       stopped = true;
+}
+
+void MenuScreen::draw()
+{
+       if(ui.get_visibility() == 0.0f) {
+               if(start_pending) {
+                       start_pending = false;
+                       push_screen(scr_game);
+                       stop();
+                       return;
+               }
+               if(exit_pending) {
+                       exit_pending = false;
+                       game_quit();
+                       return;
+               }
+       }
+
+       if(stopped) {
+               start();
+       }
+
+       glClearColor(0, 0, 0, 1);
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+       glOrtho(0, virt_width, virt_height, 0, -1, 1);
+
+       glMatrixMode(GL_MODELVIEW);
+       glLoadIdentity();
+
+       ui.draw();
+
+}
+
+void MenuScreen::reshape(int x, int y)
+{
+       ui.set_size(virt_width, virt_height);
+}
+
+
+void MenuScreen::keyboard(int key, bool pressed)
+{
+       ui.sysev_keyboard(key, pressed);
+}
+
+void MenuScreen::mbutton(int bn, bool pressed, int x, int y)
+{
+       x = x * virt_width / win_width;
+       y = y * virt_height / win_height;
+       ui.sysev_mouse_button(bn, pressed, x, y);
+}
+
+void MenuScreen::mmotion(int x, int y)
+{
+       x = x * virt_width / win_width;
+       y = y * virt_height / win_height;
+       ui.sysev_mouse_motion(x, y);
+}
+
+static void bn_handler(goatkit::Widget *w, const goatkit::Event &ev, void *cls)
+{
+       if(w == bn_start) {
+               start_pending = true;
+               ui.hide();
+       } else if(w == bn_exit) {
+               exit_pending = true;
+               ui.hide();
+       }
+}
diff --git a/src/opengl.c b/src/opengl.c
new file mode 100644 (file)
index 0000000..7042514
--- /dev/null
@@ -0,0 +1,150 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "opengl.h"
+
+
+static void GLAPIENTRY gldebug_logger(GLenum src, GLenum type, GLuint id, GLenum severity,
+               GLsizei len, const char *msg, const void *cls);
+
+static const char *gldebug_srcstr(unsigned int src);
+static const char *gldebug_typestr(unsigned int type);
+
+struct GLCaps glcaps;
+
+int init_opengl(void)
+{
+       glewInit();
+
+       glcaps.debug = GLEW_ARB_debug_output;
+
+#ifndef NDEBUG
+       if(glcaps.debug) {
+               printf("Installing OpenGL debug callback\n");
+               glDebugMessageCallbackARB(gldebug_logger, 0);
+       }
+#endif
+
+       return 0;
+}
+
+int next_pow2(int x)
+{
+       x--;
+       x = (x >> 1) | x;
+       x = (x >> 2) | x;
+       x = (x >> 4) | x;
+       x = (x >> 8) | x;
+       x = (x >> 16) | x;
+       return x + 1;
+}
+
+void dump_gl_texture(unsigned int tex, const char *fname)
+{
+       FILE *fp;
+       int i, width, height;
+       unsigned char *pixels, *pptr;
+
+       glBindTexture(GL_TEXTURE_2D, tex);
+       glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
+       glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
+
+       if(!(pixels = malloc(width * height * 4))) {
+               return;
+       }
+       pptr = pixels;
+       glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
+       if(!(fp = fopen(fname, "wb"))) {
+               free(pixels);
+               return;
+       }
+       fprintf(fp, "P6\n%d %d\n255\n", width, height);
+       for(i=0; i<width * height; i++) {
+               fputc(*pptr++, fp);
+               fputc(*pptr++, fp);
+               fputc(*pptr++, fp);
+               pptr++;
+       }
+       fclose(fp);
+       free(pixels);
+}
+
+
+static void GLAPIENTRY gldebug_logger(GLenum src, GLenum type, GLuint id, GLenum severity,
+               GLsizei len, const char *msg, const void *cls)
+{
+       static const char *fmt = "[GLDEBUG] (%s) %s: %s\n";
+       switch(type) {
+       case GL_DEBUG_TYPE_ERROR:
+               fprintf(stderr, fmt, gldebug_srcstr(src), gldebug_typestr(type), msg);
+               break;
+
+       case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+       case GL_DEBUG_TYPE_PORTABILITY:
+       case GL_DEBUG_TYPE_PERFORMANCE:
+               printf(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg);
+               break;
+
+       default:
+               printf(fmt, gldebug_srcstr(src), gldebug_typestr(type), msg);
+       }
+}
+
+static const char *gldebug_srcstr(unsigned int src)
+{
+       switch(src) {
+       case GL_DEBUG_SOURCE_API:
+               return "api";
+       case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+               return "wsys";
+       case GL_DEBUG_SOURCE_SHADER_COMPILER:
+               return "sdrc";
+       case GL_DEBUG_SOURCE_THIRD_PARTY:
+               return "3rdparty";
+       case GL_DEBUG_SOURCE_APPLICATION:
+               return "app";
+       case GL_DEBUG_SOURCE_OTHER:
+               return "other";
+       default:
+               break;
+       }
+       return "unknown";
+}
+
+static const char *gldebug_typestr(unsigned int type)
+{
+       switch(type) {
+       case GL_DEBUG_TYPE_ERROR:
+               return "error";
+       case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+               return "deprecated";
+       case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+               return "undefined behavior";
+       case GL_DEBUG_TYPE_PORTABILITY:
+               return "portability warning";
+       case GL_DEBUG_TYPE_PERFORMANCE:
+               return "performance warning";
+       case GL_DEBUG_TYPE_OTHER:
+               return "other";
+       default:
+               break;
+       }
+       return "unknown";
+}
+
+/*
+static const char *gldebug_sevstr(unsigned int sev)
+{
+       switch(sev) {
+       case GL_DEBUG_SEVERITY_HIGH:
+               return "high";
+       case GL_DEBUG_SEVERITY_MEDIUM:
+               return "medium";
+       case GL_DEBUG_SEVERITY_LOW:
+               return "low";
+       default:
+               break;
+       }
+       return "unknown";
+}
+*/
diff --git a/src/opengl.h b/src/opengl.h
new file mode 100644 (file)
index 0000000..80e4875
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef OPENGL_H_
+#define OPENGL_H_
+
+#include <GL/glew.h>
+
+struct GLCaps {
+       int debug;      /* ARB_debug_output */
+};
+
+extern struct GLCaps glcaps;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int init_opengl(void);
+
+int next_pow2(int x);
+
+void dump_gl_texture(unsigned int tex, const char *fname);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* OPENGL_H_ */
diff --git a/src/screen.cc b/src/screen.cc
new file mode 100644 (file)
index 0000000..6b9a274
--- /dev/null
@@ -0,0 +1,95 @@
+#include "screen.h"
+
+ScreenBase *active_screen;
+MenuScreen *scr_menu;
+GameScreen *scr_game;
+
+bool init_screens()
+{
+       scr_menu = new MenuScreen;
+       if(!scr_menu->init()) {
+               return false;
+       }
+
+       scr_game = new GameScreen;
+       if(!scr_game->init()) {
+               return false;
+       }
+
+       push_screen(scr_menu);
+       return true;
+}
+
+void cleanup_screens()
+{
+       scr_game->destroy();
+       delete scr_game;
+
+       scr_menu->destroy();
+       delete scr_menu;
+}
+
+void push_screen(ScreenBase *scr)
+{
+       scr->next = active_screen;
+       active_screen = scr;
+
+       scr->start();
+}
+
+void pop_screen()
+{
+       ScreenBase *scr = active_screen;
+       active_screen = scr->next;
+
+       scr->stop();
+}
+
+
+ScreenBase::ScreenBase()
+{
+       next = 0;
+}
+
+ScreenBase::~ScreenBase()
+{
+}
+
+bool ScreenBase::init()
+{
+       return true;
+}
+
+void ScreenBase::destroy()
+{
+}
+
+void ScreenBase::start()
+{
+}
+
+void ScreenBase::stop()
+{
+}
+
+
+void ScreenBase::reshape(int x, int y)
+{
+}
+
+
+void ScreenBase::keyboard(int key, bool pressed)
+{
+}
+
+void ScreenBase::mbutton(int bn, bool pressed, int x, int y)
+{
+}
+
+void ScreenBase::mmotion(int x, int y)
+{
+}
+
+void ScreenBase::mwheel(int dir, int x, int y)
+{
+}
diff --git a/src/screen.h b/src/screen.h
new file mode 100644 (file)
index 0000000..c5422f9
--- /dev/null
@@ -0,0 +1,65 @@
+#ifndef SCREEN_H_
+#define SCREEN_H_
+
+class ScreenBase;
+
+bool init_screens();
+void cleanup_screens();
+void push_screen(ScreenBase *scr);
+void pop_screen();
+
+extern ScreenBase *active_screen;
+
+
+class ScreenBase {
+public:
+       ScreenBase *next;
+
+       ScreenBase();
+       virtual ~ScreenBase();
+
+       virtual bool init();
+       virtual void destroy();
+
+       virtual void start();
+       virtual void stop();
+
+       virtual void draw() = 0;
+       virtual void reshape(int x, int y);
+
+       virtual void keyboard(int key, bool pressed);
+       virtual void mbutton(int bn, bool pressed, int x, int y);
+       virtual void mmotion(int x, int y);
+       virtual void mwheel(int dir, int x, int y);
+};
+
+class MenuScreen : public ScreenBase {
+public:
+       bool init();
+       void destroy();
+
+       void start();
+       void stop();
+
+       void draw();
+       void reshape(int x, int y);
+
+       void keyboard(int key, bool pressed);
+       void mbutton(int bn, bool pressed, int x, int y);
+       void mmotion(int x, int y);
+};
+
+class GameScreen : public ScreenBase {
+public:
+       bool init();
+       void destroy();
+
+       void draw();
+
+       void keyboard(int key, bool pressed);
+       void mbutton(int bn, bool pressed, int x, int y);
+       void mmotion(int x, int y);
+       void mwheel(int dir, int x, int y);
+};
+
+#endif // SCREEN_H_
diff --git a/src/uitheme.cc b/src/uitheme.cc
new file mode 100644 (file)
index 0000000..28c497c
--- /dev/null
@@ -0,0 +1,411 @@
+#include <string.h>
+#include <string>
+#include <map>
+#include "goatkit/goatkit.h"
+
+#if !defined(WIN32) && !defined(__WIN32__)
+#include <alloca.h>
+#else
+#include <malloc.h>
+#endif
+
+#include <GL/glut.h>
+
+#define FONT   GLUT_BITMAP_HELVETICA_18
+
+using namespace goatkit;
+
+typedef void (*DrawFunc)(const Widget*);
+
+static void draw_button(const Widget *w);
+static void draw_checkbox(const Widget *w);
+static void draw_label(const Widget *w);
+static void draw_slider(const Widget *w);
+static void draw_textbox(const Widget *w);
+static float calc_text_width(const char *text);
+static void draw_text(float x, float y, const char *text);
+static void draw_rect(const Widget *w, float x, float y, float xsz, float ysz);
+
+static void get_fgcolor(const Widget *w, float *col);
+static void get_bgcolor(const Widget *w, float *col);
+
+static struct {
+       const char *name;
+       DrawFunc func;
+} widget_funcs[] = {
+       { "button", draw_button },
+       { "checkbox", draw_checkbox },
+       { "label", draw_label },
+       { "slider", draw_slider },
+       { "textbox", draw_textbox },
+       { 0, 0 }
+};
+
+static bool initialized;
+static std::map<std::string, DrawFunc> funcmap;
+
+/* theme colors */
+static float fgcol[] = {0.8, 0.6, 0.4, 1.0};
+static float fgcol_off[] = {0.65, 0.65, 0.6, 1.0};
+static float fgcol_inact[] = {0.4, 0.4, 0.4, 1.0};
+static float bgcol[] = {0.3, 0.3, 0.3, 1.0};
+static float bgcol_off[] = {0.3, 0.3, 0.3, 1.0};
+static float bgcol_inact[] = {0.3, 0.3, 0.3, 1.0};
+
+
+extern "C" goatkit::WidgetDrawFunc get_widget_func(const char *name)
+{
+       int i;
+
+       if(!initialized) {
+               for(i=0; widget_funcs[i].func; i++) {
+                       funcmap[widget_funcs[i].name] = widget_funcs[i].func;
+               }
+
+               initialized = true;
+       }
+       return funcmap[name];
+}
+
+// register ourselves as a built-in theme
+GOATKIT_BUILTIN_THEME("simple", get_widget_func);
+
+
+#define VIS_THRES      0.0001
+
+static void begin_drawing(const Widget *w)
+{
+       Vec2 pos = w->get_position();
+
+       glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
+       glDisable(GL_LIGHTING);
+       glDisable(GL_DEPTH_TEST);
+       glEnable(GL_BLEND);
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glTranslatef(pos.x, pos.y, 0);
+}
+
+static void end_drawing(const Widget *w)
+{
+       glPopMatrix();
+       glPopAttrib();
+}
+
+
+#define LERP(a, b, t)  ((a) + ((b) - (a)) * t)
+
+static void draw_button(const Widget *w)
+{
+       Vec2 sz = w->get_size();
+       float bnaspect = sz.x / sz.y;
+
+       float pressed = w->get_pressed();
+       float vis = w->get_visibility();
+
+       if(vis < VIS_THRES) {
+               return;
+       }
+
+       float tor_rad = sz.x * 0.5 * vis;
+       float scale = LERP(1.0, 0.85, pressed);
+
+       float fg[4], bg[4];
+       get_fgcolor(w, fg);
+       get_bgcolor(w, bg);
+
+       begin_drawing(w);
+
+       glTranslatef(sz.x / 2.0, sz.y / 2.0, 0.0);
+       glScalef(1, 1, 1.0 / sz.x);
+       glRotatef(90, 1, 0, 0);
+
+       glColor4fv(fg);
+       glutSolidTorus(scale * sz.y / 2.0, scale * tor_rad / 2.0, 18, 16);
+
+       glScalef(1.0 - 0.1 / bnaspect, 1.0, 1.0 - 0.1);
+       glColor4fv(bg);
+       glutSolidTorus(scale * sz.y / 2.0, scale * tor_rad / 2.0, 18, 16);
+
+       if(vis >= 1.0) {
+               glTranslatef(-calc_text_width(w->get_text()) / 2.0, 0, -5);
+               glColor4fv(fg);
+               draw_text(0, 0, w->get_text());
+       }
+
+       end_drawing(w);
+}
+
+static void draw_checkbox(const Widget *w)
+{
+       Vec2 sz = w->get_size();
+       float vis = w->get_visibility();
+
+       if(vis < VIS_THRES) {
+               return;
+       }
+
+       float fg[4];
+       get_fgcolor(w, fg);
+
+       goatkit::CheckBox *cbox = (goatkit::CheckBox*)w;
+
+       begin_drawing(w);
+
+       draw_rect(w, 0, 0, sz.y, sz.y);
+
+       float state = cbox->get_checked();
+
+       float bottom[2] = {sz.y / 2, sz.y - 1};
+       float left[2] = {-2, sz.y / 4};
+       float right[2] = {sz.y, -4};
+
+       if(state > 0.0) {
+               // draw tickmark
+               glBegin(GL_TRIANGLES);
+               glColor4fv(fg);
+               float t = state * 2.0 > 1.0 ? 1.0 : state * 2.0;
+               glVertex2f(left[0], left[1]);
+               glVertex2f(LERP(left[0], bottom[0], t), LERP(left[1], bottom[1], t));
+               glVertex2f(LERP(left[0], bottom[0], t), LERP(left[1], bottom[1] - sz.y / 3, t));
+
+               if((t = state * 2.0 - 1.0) > 0.0) {
+                       glVertex2f(bottom[0], bottom[1]);
+                       glVertex2f(LERP(bottom[0], right[0], t), LERP(bottom[1], right[1], t));
+                       glVertex2f(bottom[0], bottom[1] - sz.y / 3);
+               }
+               glEnd();
+       }
+
+       glTranslatef(sz.y * 1.5, sz.y / 2.0 + 5, 0);
+       glColor4fv(fg);
+       draw_text(0, 0, w->get_text());
+
+       end_drawing(w);
+}
+
+static void draw_label(const Widget *w)
+{
+       Vec2 sz = w->get_size();
+       float vis = w->get_visibility();
+
+       if(vis < VIS_THRES) {
+               return;
+       }
+
+       float fg[4];
+       get_fgcolor(w, fg);
+
+       begin_drawing(w);
+
+       glTranslatef((sz.x - calc_text_width(w->get_text())) / 2.0, sz.y / 2.0, 0);
+       glColor4fv(fg);
+       draw_text(0, 0, w->get_text());
+
+       end_drawing(w);
+}
+
+static void draw_slider(const Widget *w)
+{
+       Vec2 sz = w->get_size();
+       float vis = w->get_visibility();
+
+       if(vis < VIS_THRES) {
+               return;
+       }
+
+       float fg[4], bg[4];
+       get_fgcolor(w, fg);
+       get_bgcolor(w, bg);
+
+       Slider *slider = (Slider*)w;
+       float pad = slider->get_padding();
+       float handle_width = pad * 2.0;
+
+       float value = slider->get_value();
+       char valtext[16];
+       sprintf(valtext, "%g", value);
+
+       float trough_sz = sz.x - 2.0 * pad;
+       float x = pad + trough_sz * slider->get_value_norm();
+
+       float act_height = sz.y / 2.0;
+
+       begin_drawing(w);
+
+       float step = slider->get_step();
+       if(step > 0.0) {
+               float beg = slider->get_range_min();
+               float end = slider->get_range_max();
+               int num_seg = (end - beg) / step;
+               int num_ticks = num_seg + 1;
+               float x = pad;
+               float dx = trough_sz / num_seg;
+
+               glLineWidth(1.0);
+               glBegin(GL_LINES);
+               glColor4fv(fg);
+               for(int i=0; i<num_ticks; i++) {
+                       glVertex2f(x + 0.5, sz.y / 3.0 + 0.5);
+                       glVertex2f(x + 0.5, sz.y / 2.0 + 0.5);
+                       x += dx;
+               }
+               glEnd();
+       }
+
+       draw_rect(w, 0, sz.y - act_height + act_height / 3, sz.x, act_height / 3);
+       draw_rect(w, x - handle_width / 2.0, sz.y - act_height, handle_width, act_height);
+       draw_text(x - calc_text_width(valtext) / 2.0, act_height / 2.0, valtext);
+
+       end_drawing(w);
+}
+
+static void draw_textbox(const Widget *w)
+{
+       if(!w->is_visible()) {
+               return;
+       }
+
+       Vec2 sz = w->get_size();
+
+       float fg[4];
+       get_fgcolor(w, fg);
+
+       TextBox *tbox = (TextBox*)w;
+
+       begin_drawing(w);
+
+       draw_rect(w, 0, 0, sz.x, sz.y);
+
+       const char *str = tbox->get_text();
+       char *buf = (char*)alloca(strlen(str) + 1);
+
+       // figure out how many characters fit in the textbox
+       float tsz = 0.0;
+       const char *sptr = str;
+       char *dptr = buf;
+       while(*sptr) {
+               float nsz = tsz + glutBitmapWidth(FONT, *sptr);
+               if(nsz >= sz.x) {
+                       break;
+               }
+               *dptr++ = *sptr++;
+               tsz = nsz;
+       }
+       *dptr = 0;
+
+       glColor4fv(fg);
+       draw_text(2, 2.0 * sz.y / 3.0, buf);
+
+       // draw the cursor
+       int cursor = tbox->get_cursor();
+       float cx = 1.0;
+
+       if(cursor > 0) {
+               memcpy(buf, str, cursor);
+               buf[cursor] = 0;
+               cx += calc_text_width(buf);
+       }
+
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+       glColor4f(1.0 - fg[0], 1.0 - fg[1], 1.0 - fg[2], fg[3]);
+       draw_text(cx, 2.0 * sz.y / 3.0, "|");
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+       end_drawing(w);
+}
+
+static float calc_text_width(const char *text)
+{
+       float res = 0.0f;
+
+       while(*text) {
+               res += glutBitmapWidth(FONT, *text++);
+       }
+       return res;
+}
+
+static void draw_text(float x, float y, const char *text)
+{
+       glRasterPos2f(x, y);
+
+       while(*text) {
+               glutBitmapCharacter(FONT, *text++);
+       }
+}
+
+#define DISP(s, t)             LERP((s) * 2.0, 0, (t))
+static void draw_rect(const Widget *w, float x, float y, float xsz, float ysz)
+{
+       float fg[4], bg[4];
+       float vis = w->get_visibility();
+
+       get_fgcolor(w, fg);
+       get_bgcolor(w, bg);
+
+       glBegin(GL_QUADS);
+       glColor4fv(bg);
+       glVertex2f(x, y);
+       glVertex2f(x + xsz, y);
+       glVertex2f(x + xsz, y + ysz);
+       glVertex2f(x, y + ysz);
+       glEnd();
+
+       glLineWidth(w->get_focus() + 1.0);
+
+       glPushMatrix();
+       glTranslatef(0.5, 0.5, 0);
+
+       glBegin(GL_LINES);
+       glColor4fv(fg);
+
+       // top
+       glVertex2f(x - DISP(xsz, vis), y);
+       glVertex2f(x + xsz - DISP(xsz, vis), y);
+
+       // right
+       glVertex2f(x + xsz, y - DISP(ysz, vis));
+       glVertex2f(x + xsz, y + ysz - DISP(ysz, vis));
+
+       // bottom
+       glVertex2f(x + xsz + DISP(xsz, vis), y + ysz);
+       glVertex2f(x + DISP(xsz, vis), y + ysz);
+
+       // left
+       glVertex2f(x, y + ysz + DISP(ysz, vis));
+       glVertex2f(x, y + DISP(ysz, vis));
+
+       glEnd();
+       glPopMatrix();
+
+       glLineWidth(1);
+}
+
+
+static void get_fgcolor(const Widget *w, float *col)
+{
+       float hover = w->get_under_mouse();
+       float act = w->get_active();
+       float vis = w->get_visibility();
+
+       for(int i=0; i<4; i++) {
+               float c = LERP(fgcol_off[i], fgcol[i], hover);
+               col[i] = LERP(fgcol_inact[i], c, act);
+       }
+       col[3] *= vis;
+}
+
+static void get_bgcolor(const Widget *w, float *col)
+{
+       float hover = w->get_under_mouse();
+       float act = w->get_active();
+       float vis = w->get_visibility();
+
+       for(int i=0; i<4; i++) {
+               float c = LERP(bgcol_off[i], bgcol[i], hover);
+               col[i] = LERP(bgcol_inact[i], c, act);
+       }
+       col[3] *= vis;
+}