--- /dev/null
+*.o
+*.swp
+*.d
+game
--- /dev/null
+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)
--- /dev/null
+#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);
+}
--- /dev/null
+#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_ */
--- /dev/null
+#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)
+{
+}
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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];
+}
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_ */
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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_
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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_
--- /dev/null
+#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);
+}
--- /dev/null
+#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();
+ }
+}
--- /dev/null
+#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";
+}
+*/
--- /dev/null
+#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_ */
--- /dev/null
+#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)
+{
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}