changeset 338:94828af916bb

js: add more core bindings
author David Demelier <markand@malikania.fr>
date Thu, 14 Oct 2021 21:21:28 +0200
parents 68b9d010e081
children 979960e65f76
files src/libmlk-core-js/CMakeLists.txt src/libmlk-core-js/core/js-clock.c src/libmlk-core-js/core/js-clock.h src/libmlk-core-js/core/js-event.c src/libmlk-core-js/core/js-event.h src/libmlk-core-js/core/js-painter.c src/libmlk-core-js/core/js-painter.h src/libmlk-core-js/core/js-texture.c src/libmlk-core-js/core/js-texture.h src/libmlk-core-js/core/js-window.c src/mlk-run/main.c
diffstat 11 files changed, 927 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/src/libmlk-core-js/CMakeLists.txt	Thu Oct 14 12:52:41 2021 +0200
+++ b/src/libmlk-core-js/CMakeLists.txt	Thu Oct 14 21:21:28 2021 +0200
@@ -20,6 +20,14 @@
 
 set(
 	SOURCES
+	${libmlk-core-js_SOURCE_DIR}/core/js-clock.c
+	${libmlk-core-js_SOURCE_DIR}/core/js-clock.h
+	${libmlk-core-js_SOURCE_DIR}/core/js-event.c
+	${libmlk-core-js_SOURCE_DIR}/core/js-event.h
+	${libmlk-core-js_SOURCE_DIR}/core/js-painter.c
+	${libmlk-core-js_SOURCE_DIR}/core/js-painter.h
+	${libmlk-core-js_SOURCE_DIR}/core/js-texture.c
+	${libmlk-core-js_SOURCE_DIR}/core/js-texture.h
 	${libmlk-core-js_SOURCE_DIR}/core/js-window.c
 	${libmlk-core-js_SOURCE_DIR}/core/js-window.h
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-clock.c	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,107 @@
+/*
+ * js-clock.c -- core clock binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <core/alloc.h>
+#include <core/clock.h>
+
+#include "js-clock.h"
+
+#define SIGNATURE DUK_HIDDEN_SYMBOL("Mlk.Clock")
+
+static struct clock *
+self(duk_context *ctx)
+{
+	struct clock *clk;
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, SIGNATURE);
+	clk = duk_to_pointer(ctx, -1);
+	duk_pop_2(ctx);
+
+	if (!clk)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Clock object");
+
+	return clk;
+}
+
+static duk_ret_t
+Clock_elapsed(duk_context *ctx)
+{
+	duk_push_number(ctx, clock_elapsed(self(ctx)));
+
+	return 1;
+}
+
+static duk_ret_t
+Clock_constructor(duk_context *ctx)
+{
+	struct clock *clk;
+
+	clk = alloc_new(sizeof (*clk));
+	clock_start(clk);
+
+	duk_push_this(ctx);
+	duk_push_pointer(ctx, clk);
+	duk_put_prop_string(ctx, -2, SIGNATURE);
+	duk_push_string(ctx, "elapsed");
+	duk_push_c_function(ctx, Clock_elapsed, 0);
+	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_GETTER);
+	duk_pop(ctx);
+
+	return 0;
+}
+
+static duk_ret_t
+Clock_start(duk_context *ctx)
+{
+	clock_start(self(ctx));
+
+	return 0;
+}
+
+static duk_ret_t
+Clock_destructor(duk_context *ctx)
+{
+	duk_get_prop_string(ctx, 0, SIGNATURE);
+	free(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+	duk_del_prop_string(ctx, 0, SIGNATURE);
+
+	return 0;
+}
+
+static const duk_function_list_entry methods[] = {
+	{ "start",      Clock_start,    0 },
+	{ NULL,         NULL,           0 }
+};
+
+void
+js_clock_bind(duk_context *ctx)
+{
+	assert(ctx);
+
+	duk_push_c_function(ctx, Clock_constructor, 0);
+	duk_push_object(ctx);
+	duk_put_function_list(ctx, -1, methods);
+	duk_push_c_function(ctx, Clock_destructor, 1);
+	duk_set_finalizer(ctx, -2);
+	duk_put_prop_string(ctx, -2, "prototype");
+	duk_put_global_string(ctx, "Clock");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-clock.h	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,27 @@
+/*
+ * js-clock.h -- core clock binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_CORE_JS_CLOCK_H
+#define MLK_CORE_JS_CLOCK_H
+
+#include <duktape.h>
+
+void
+js_clock_bind(duk_context *);
+
+#endif /* !MLK_CORE_JS_CLOCK_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-event.c	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,291 @@
+/*
+ * js-event.c -- core event binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <core/event.h>
+
+#include "js-event.h"
+
+static void
+push_click(duk_context *ctx, const union event *ev)
+{
+	duk_push_int(ctx, ev->click.button);
+	duk_put_prop_string(ctx, -2, "button");
+	duk_push_int(ctx, ev->click.x);
+	duk_put_prop_string(ctx, -2, "x");
+	duk_push_int(ctx, ev->click.y);
+	duk_put_prop_string(ctx, -2, "y");
+	duk_push_int(ctx, ev->click.clicks);
+	duk_put_prop_string(ctx, -2, "clicks");
+}
+
+static void
+push_key(duk_context *ctx, const union event *ev)
+{
+	duk_push_int(ctx, ev->key.key);
+	duk_put_prop_string(ctx, -2, "key");
+}
+
+static void
+push_mouse(duk_context *ctx, const union event *ev)
+{
+	duk_push_int(ctx, ev->mouse.buttons);
+	duk_put_prop_string(ctx, -2, "buttons");
+	duk_push_int(ctx, ev->mouse.x);
+	duk_put_prop_string(ctx, -2, "x");
+	duk_push_int(ctx, ev->mouse.y);
+	duk_put_prop_string(ctx, -2, "y");
+}
+
+static void
+push_quit(duk_context *ctx, const union event *ev)
+{
+	(void)ctx;
+	(void)ev;
+}
+
+static void (*push[])(duk_context *, const union event *) = {
+	[EVENT_CLICKDOWN] = push_click,
+	[EVENT_CLICKUP] = push_click,
+	[EVENT_KEYDOWN] = push_key,
+	[EVENT_KEYUP] = push_key,
+	[EVENT_MOUSE] = push_mouse,
+	[EVENT_QUIT] = push_quit,
+};
+
+static duk_ret_t
+Event_poll(duk_context *ctx)
+{
+	(void)ctx;
+
+	union event ev;
+
+	if (!event_poll(&ev))
+		duk_push_null(ctx);
+	else {
+		duk_push_object(ctx);
+		duk_push_int(ctx, ev.type);
+		duk_put_prop_string(ctx, -2, "type");
+		push[ev.type](ctx, &ev);
+	}
+
+	return 1;
+}
+
+static const duk_function_list_entry functions[] = {
+	{ "poll",       Event_poll,     0               },
+	{ NULL,         NULL,           0               }
+};
+
+static const duk_number_list_entry types[] = {
+	{ "CLICKDOWN",          EVENT_CLICKDOWN         },
+	{ "CLICKUP",            EVENT_CLICKUP           },
+	{ "KEYDOWN",            EVENT_KEYDOWN           },
+	{ "KEYUP",              EVENT_KEYUP             },
+	{ "MOUSE",              EVENT_MOUSE             },
+	{ "QUIT",               EVENT_QUIT              },
+	{ NULL,                 0                       }
+};
+
+static const duk_number_list_entry keys[] = {
+	{ "UNKNOWN",            KEY_UNKNOWN             },
+	{ "ENTER",              KEY_ENTER               },
+	{ "ESCAPE",             KEY_ESCAPE              },
+	{ "BACKSPACE",          KEY_BACKSPACE           },
+	{ "TAB",                KEY_TAB                 },
+	{ "SPACE",              KEY_SPACE               },
+	{ "EXCLAIM",            KEY_EXCLAIM             },
+	{ "DOUBLE_QUOTE",       KEY_DOUBLE_QUOTE        },
+	{ "HASH",               KEY_HASH                },
+	{ "PERCENT",            KEY_PERCENT             },
+	{ "DOLLAR",             KEY_DOLLAR              },
+	{ "AMPERSAND",          KEY_AMPERSAND           },
+	{ "QUOTE",              KEY_QUOTE               },
+	{ "LEFT_PAREN",         KEY_LEFT_PAREN          },
+	{ "RIGHT_PAREN",        KEY_RIGHT_PAREN         },
+	{ "ASTERISK",           KEY_ASTERISK            },
+	{ "PLUS",               KEY_PLUS                },
+	{ "COMMA",              KEY_COMMA               },
+	{ "MINUS",              KEY_MINUS               },
+	{ "PERIOD",             KEY_PERIOD              },
+	{ "SLASH",              KEY_SLASH               },
+	{ "0",                  KEY_0                   },
+	{ "1",                  KEY_1                   },
+	{ "2",                  KEY_2                   },
+	{ "3",                  KEY_3                   },
+	{ "4",                  KEY_4                   },
+	{ "5",                  KEY_5                   },
+	{ "6",                  KEY_6                   },
+	{ "7",                  KEY_7                   },
+	{ "8",                  KEY_8                   },
+	{ "9",                  KEY_9                   },
+	{ "COLON",              KEY_COLON               },
+	{ "SEMICOLON",          KEY_SEMICOLON           },
+	{ "LESS",               KEY_LESS                },
+	{ "EQUALS",             KEY_EQUALS              },
+	{ "GREATER",            KEY_GREATER             },
+	{ "QUESTION",           KEY_QUESTION            },
+	{ "AT",                 KEY_AT                  },
+	{ "LEFT_BRACKET",       KEY_LEFT_BRACKET        },
+	{ "BACKSLASH",          KEY_BACKSLASH           },
+	{ "RIGHT_BRACKET",      KEY_RIGHT_BRACKET       },
+	{ "CARET",              KEY_CARET               },
+	{ "UNDERSCORE",         KEY_UNDERSCORE          },
+	{ "BACKQUOTE",          KEY_BACKQUOTE           },
+	{ "a",                  KEY_a                   },
+	{ "b",                  KEY_b                   },
+	{ "c",                  KEY_c                   },
+	{ "d",                  KEY_d                   },
+	{ "e",                  KEY_e                   },
+	{ "f",                  KEY_f                   },
+	{ "g",                  KEY_g                   },
+	{ "h",                  KEY_h                   },
+	{ "i",                  KEY_i                   },
+	{ "j",                  KEY_j                   },
+	{ "k",                  KEY_k                   },
+	{ "l",                  KEY_l                   },
+	{ "m",                  KEY_m                   },
+	{ "n",                  KEY_n                   },
+	{ "o",                  KEY_o                   },
+	{ "p",                  KEY_p                   },
+	{ "q",                  KEY_q                   },
+	{ "r",                  KEY_r                   },
+	{ "s",                  KEY_s                   },
+	{ "t",                  KEY_t                   },
+	{ "u",                  KEY_u                   },
+	{ "v",                  KEY_v                   },
+	{ "w",                  KEY_w                   },
+	{ "x",                  KEY_x                   },
+	{ "y",                  KEY_y                   },
+	{ "z",                  KEY_z                   },
+	{ "CAPSLOCK",           KEY_CAPSLOCK            },
+	{ "F1",                 KEY_F1                  },
+	{ "F2",                 KEY_F2                  },
+	{ "F3",                 KEY_F3                  },
+	{ "F4",                 KEY_F4                  },
+	{ "F5",                 KEY_F5                  },
+	{ "F6",                 KEY_F6                  },
+	{ "F7",                 KEY_F7                  },
+	{ "F8",                 KEY_F8                  },
+	{ "F9",                 KEY_F9                  },
+	{ "F10",                KEY_F10                 },
+	{ "F11",                KEY_F11                 },
+	{ "F12",                KEY_F12                 },
+	{ "F13",                KEY_F13                 },
+	{ "F14",                KEY_F14                 },
+	{ "F15",                KEY_F15                 },
+	{ "F16",                KEY_F16                 },
+	{ "F17",                KEY_F17                 },
+	{ "F18",                KEY_F18                 },
+	{ "F19",                KEY_F19                 },
+	{ "F20",                KEY_F20                 },
+	{ "F21",                KEY_F21                 },
+	{ "F22",                KEY_F22                 },
+	{ "F23",                KEY_F23                 },
+	{ "F24",                KEY_F24                 },
+	{ "PRINTSCREEN",        KEY_PRINTSCREEN         },
+	{ "SCROLL_LOCK",        KEY_SCROLL_LOCK         },
+	{ "PAUSE",              KEY_PAUSE               },
+	{ "INSERT",             KEY_INSERT              },
+	{ "HOME",               KEY_HOME                },
+	{ "PAGEUP",             KEY_PAGEUP              },
+	{ "DELETE",             KEY_DELETE              },
+	{ "END",                KEY_END                 },
+	{ "PAGEDOWN",           KEY_PAGEDOWN            },
+	{ "RIGHT",              KEY_RIGHT               },
+	{ "LEFT",               KEY_LEFT                },
+	{ "DOWN",               KEY_DOWN                },
+	{ "UP",                 KEY_UP                  },
+	{ "NUMLOCKCLEAR",       KEY_NUMLOCKCLEAR        },
+	{ "KP_DIVIDE",          KEY_KP_DIVIDE           },
+	{ "KP_MULTIPLY",        KEY_KP_MULTIPLY         },
+	{ "KP_MINUS",           KEY_KP_MINUS            },
+	{ "KP_PLUS",            KEY_KP_PLUS             },
+	{ "KP_ENTER",           KEY_KP_ENTER            },
+	{ "KP_00",              KEY_KP_00               },
+	{ "KP_000",             KEY_KP_000              },
+	{ "KP_1",               KEY_KP_1                },
+	{ "KP_2",               KEY_KP_2                },
+	{ "KP_3",               KEY_KP_3                },
+	{ "KP_4",               KEY_KP_4                },
+	{ "KP_5",               KEY_KP_5                },
+	{ "KP_6",               KEY_KP_6                },
+	{ "KP_7",               KEY_KP_7                },
+	{ "KP_8",               KEY_KP_8                },
+	{ "KP_9",               KEY_KP_9                },
+	{ "KP_0",               KEY_KP_0                },
+	{ "KP_PERIOD",          KEY_KP_PERIOD           },
+	{ "KP_COMMA",           KEY_KP_COMMA            },
+	{ "MENU",               KEY_MENU                },
+	{ "MUTE",               KEY_MUTE                },
+	{ "VOLUME_UP",          KEY_VOLUME_UP           },
+	{ "VOLUME_DOWN",        KEY_VOLUME_DOWN         },
+	{ "LCTRL",              KEY_LCTRL               },
+	{ "LSHIFT",             KEY_LSHIFT              },
+	{ "LALT",               KEY_LALT                },
+	{ "LSUPER",             KEY_LSUPER              },
+	{ "RCTRL",              KEY_RCTRL               },
+	{ "RSHIFT",             KEY_RSHIFT              },
+	{ "RALT",               KEY_RALT                },
+	{ "RSUPER",             KEY_RSUPER              },
+	{ NULL,                 0                       }
+};
+
+static const duk_number_list_entry keymods[] = {
+	{ "LSHIFT",             KEYMOD_LSHIFT           },
+	{ "LCTRL",              KEYMOD_LCTRL            },
+	{ "LALT",               KEYMOD_LALT             },
+	{ "LSUPER",             KEYMOD_LSUPER           },
+	{ "RSHIFT",             KEYMOD_RSHIFT           },
+	{ "RTCRL",              KEYMOD_RCTRL            },
+	{ "RALT",               KEYMOD_RALT             },
+	{ "RSUPER",             KEYMOD_RSUPER           },
+	{ NULL,                 0                       }
+};
+
+static const duk_number_list_entry buttons[] = {
+	{ "LEFT",               MOUSE_BUTTON_LEFT       },
+	{ "MIDDLE",             MOUSE_BUTTON_MIDDLE     },
+	{ "RIGHT",              MOUSE_BUTTON_RIGHT      },
+	{ NULL,                 0                       }
+};
+
+void
+js_event_bind(duk_context *ctx)
+{
+	assert(ctx);
+
+	duk_push_global_object(ctx);
+	duk_push_object(ctx);
+	duk_put_function_list(ctx, -1, functions);
+	duk_push_object(ctx);
+	duk_put_number_list(ctx, -1, types);
+	duk_put_prop_string(ctx, -2, "type");
+	duk_push_object(ctx);
+	duk_put_number_list(ctx, -1, keys);
+	duk_put_prop_string(ctx, -2, "key");
+	duk_push_object(ctx);
+	duk_put_number_list(ctx, -1, keymods);
+	duk_put_prop_string(ctx, -2, "keymod");
+	duk_push_object(ctx);
+	duk_put_number_list(ctx, -1, buttons);
+	duk_put_prop_string(ctx, -2, "button");
+	duk_put_prop_string(ctx, -2, "Event");
+	duk_pop(ctx);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-event.h	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,27 @@
+/*
+ * js-event.h -- core event binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_CORE_JS_EVENT_H
+#define MLK_CORE_JS_EVENT_H
+
+#include <duktape.h>
+
+void
+js_event_bind(duk_context *);
+
+#endif /* !MLK_CORE_JS_EVENT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-painter.c	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,165 @@
+/*
+ * js-painter.c -- core painter binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <core/painter.h>
+
+#include "js-painter.h"
+#include "js-texture.h"
+
+static duk_ret_t
+Painter_getTarget(duk_context *ctx)
+{
+	struct texture *tex;
+
+	if (!(tex = painter_get_target()))
+		duk_push_null(ctx);
+	else
+		js_texture_push(ctx, tex);
+
+	return 1;
+}
+
+static duk_ret_t
+Painter_setTarget(duk_context *ctx)
+{
+	if (duk_is_null(ctx, 0))
+		painter_set_target(NULL);
+	else
+		painter_set_target(js_texture_require(ctx, 0));
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_getColor(duk_context *ctx)
+{
+	duk_push_number(ctx, painter_get_color());
+
+	return 1;
+}
+
+static duk_ret_t
+Painter_setColor(duk_context *ctx)
+{
+	const unsigned long color = duk_require_number(ctx, 0);
+
+	painter_set_color(color);
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_drawLine(duk_context *ctx)
+{
+	const int x1 = duk_require_int(ctx, 0);
+	const int y1 = duk_require_int(ctx, 1);
+	const int x2 = duk_require_int(ctx, 2);
+	const int y2 = duk_require_int(ctx, 3);
+
+	painter_draw_line(x1, y1, x2, y2);
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_drawPoint(duk_context *ctx)
+{
+	const int x = duk_require_int(ctx, 0);
+	const int y = duk_require_int(ctx, 1);
+
+	painter_draw_point(x, y);
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_drawRectangle(duk_context *ctx)
+{
+	const int x = duk_require_int(ctx, 0);
+	const int y = duk_require_int(ctx, 1);
+	const unsigned int w = duk_require_uint(ctx, 2);
+	const unsigned int h = duk_require_uint(ctx, 3);
+
+	painter_draw_rectangle(x, y, w, h);
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_drawCircle(duk_context *ctx)
+{
+	const int x = duk_require_int(ctx, 0);
+	const int y = duk_require_int(ctx, 1);
+	const int r = duk_require_int(ctx, 2);
+
+	painter_draw_circle(x, y, r);
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_clear(duk_context *ctx)
+{
+	(void)ctx;
+
+	painter_clear();
+
+	return 0;
+}
+
+static duk_ret_t
+Painter_present(duk_context *ctx)
+{
+	(void)ctx;
+
+	painter_present();
+
+	return 0;
+}
+
+static const duk_function_list_entry functions[] = {
+	{ "drawLine",           Painter_drawLine,       4       },
+	{ "drawPoint",          Painter_drawPoint,      2       },
+	{ "drawRectangle",      Painter_drawRectangle,  4       },
+	{ "drawCircle",         Painter_drawCircle,     3       },
+	{ "clear",              Painter_clear,          0       },
+	{ "present",            Painter_present,        0       },
+	{ NULL,                 NULL,                   0       }
+};
+
+void
+js_painter_bind(duk_context *ctx)
+{
+	assert(ctx);
+
+	duk_push_global_object(ctx);
+	duk_push_object(ctx);
+	duk_put_function_list(ctx, -1, functions);
+	duk_push_string(ctx, "color");
+	duk_push_c_function(ctx, Painter_getColor, 0);
+	duk_push_c_function(ctx, Painter_setColor, 1);
+	duk_def_prop(ctx, -4, DUK_DEFPROP_HAVE_SETTER | DUK_DEFPROP_HAVE_GETTER);
+	duk_push_string(ctx, "target");
+	duk_push_c_function(ctx, Painter_getTarget, 0);
+	duk_push_c_function(ctx, Painter_setTarget, 1);
+	duk_def_prop(ctx, -4, DUK_DEFPROP_HAVE_SETTER | DUK_DEFPROP_HAVE_GETTER);
+	duk_put_prop_string(ctx, -2, "Painter");
+	duk_pop(ctx);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-painter.h	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,27 @@
+/*
+ * js-painter.h -- core painter binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_CORE_JS_PAINTER_H
+#define MLK_CORE_JS_PAINTER_H
+
+#include <duktape.h>
+
+void
+js_painter_bind(duk_context *);
+
+#endif /* !MLK_CORE_JS_PAINTER_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-texture.c	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,216 @@
+/*
+ * js-texture.c -- core texture binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <core/alloc.h>
+#include <core/error.h>
+#include <core/texture.h>
+
+#include "js-texture.h"
+
+#define SIGNATURE DUK_HIDDEN_SYMBOL("Mlk.Texture")
+#define PROTOTYPE DUK_HIDDEN_SYMBOL("Mlk.Texture.Prototype")
+
+static inline struct texture *
+self(duk_context *ctx)
+{
+	struct texture *tex;
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, SIGNATURE);
+	tex = duk_to_pointer(ctx, -1);
+	duk_pop_2(ctx);
+
+	if (!tex)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Texture object");
+
+	return tex;
+}
+
+static duk_ret_t
+Texture_constructor(duk_context *ctx)
+{
+	const unsigned int w = duk_require_uint(ctx, 0);
+	const unsigned int h = duk_require_uint(ctx, 1);
+	struct texture *tex;
+
+	tex = alloc_new(sizeof (*tex));
+
+	if (texture_new(tex, w, h) < 0)
+		duk_error(ctx, DUK_ERR_ERROR, "%s", error());
+
+	duk_push_this(ctx);
+	duk_push_pointer(ctx, tex);
+	duk_put_prop_string(ctx, -2, SIGNATURE);
+	duk_pop(ctx);
+
+	return 0;
+}
+
+static duk_ret_t
+Texture_setBlendMode(duk_context *ctx)
+{
+	const int blend = duk_require_uint(ctx, 0);
+	struct texture *tex = self(ctx);
+
+	if (blend < 0 || blend >= TEXTURE_BLEND_LAST)
+		duk_error(ctx, DUK_ERR_ERROR, "invalid blend");
+	if (texture_set_blend_mode(tex, blend) < 0)
+		duk_error(ctx, DUK_ERR_ERROR, "%s", error());
+
+	return 0;
+}
+
+static duk_ret_t
+Texture_setAlphaMod(duk_context *ctx)
+{
+	const unsigned int alpha = duk_require_uint(ctx, 0);
+	struct texture *tex = self(ctx);
+
+	if (texture_set_alpha_mod(tex, alpha) < 0)
+		duk_error(ctx, DUK_ERR_ERROR, "%s", error());
+
+	return 0;
+}
+
+static duk_ret_t
+Texture_setColorMod(duk_context *ctx)
+{
+	const unsigned long color = duk_require_uint(ctx, 0);
+	struct texture *tex = self(ctx);
+
+	if (texture_set_color_mod(tex, color) < 0)
+		duk_error(ctx, DUK_ERR_ERROR, "%s", error());
+
+	return 0;
+}
+
+static duk_ret_t
+Texture_draw(duk_context *ctx)
+{
+	const int x = duk_require_int(ctx, 0);
+	const int y = duk_require_int(ctx, 1);
+	struct texture *tex = self(ctx);
+
+	if (texture_draw(tex, x, y) < 0)
+		duk_error(ctx, DUK_ERR_ERROR, "%s", error());
+
+	return 0;
+}
+
+static duk_ret_t
+Texture_scale(duk_context *ctx)
+{
+	const int srcx      = duk_require_int(ctx, 0);
+	const int srcy      = duk_require_int(ctx, 1);
+	const unsigned srcw = duk_require_uint(ctx, 2);
+	const unsigned srch = duk_require_uint(ctx, 3);
+	const int dstx      = duk_require_int(ctx, 4);
+	const int dsty      = duk_require_int(ctx, 5);
+	const unsigned dstw = duk_require_uint(ctx, 6);
+	const unsigned dsth = duk_require_uint(ctx, 7);
+	const double angle  = duk_get_number_default(ctx, 8, .0);
+	struct texture *tex = self(ctx);
+
+	if (texture_scale(tex, srcx, srcy, srcw, srch, dstx, dsty, dstw, dsth, angle) < 0)
+		duk_error(ctx, DUK_ERR_ERROR, "%s", error());
+
+	return 0;
+}
+
+static duk_ret_t
+Texture_destructor(duk_context *ctx)
+{
+	struct texture *tex;
+
+	duk_get_prop_string(ctx, 0, SIGNATURE);
+
+	if ((tex = duk_to_pointer(ctx, -1)))
+		texture_finish(tex);
+
+	duk_pop(ctx);
+	duk_del_prop_string(ctx, 0, SIGNATURE);
+
+	return 0;
+}
+
+static const struct duk_function_list_entry methods[] = {
+	{ "setBlendMode",       Texture_setBlendMode,   1               },
+	{ "setAlphaMod",        Texture_setAlphaMod,    1               },
+	{ "setColorMod",        Texture_setColorMod,    1               },
+	{ "draw",               Texture_draw,           2               },
+	{ "scale",              Texture_scale,          DUK_VARARGS     },
+	{ NULL,                 NULL,                   0               }
+};
+
+static const duk_number_list_entry blend[] = {
+	{ "NONE",       TEXTURE_BLEND_NONE      },
+	{ "BLEND",      TEXTURE_BLEND_BLEND     },
+	{ "ADD",        TEXTURE_BLEND_ADD       },
+	{ "MODULATE",   TEXTURE_BLEND_MODULATE  },
+	{ NULL,         0                       }
+};
+
+void
+js_texture_bind(duk_context *ctx)
+{
+	assert(ctx);
+
+	duk_push_global_object(ctx);
+	duk_push_c_function(ctx, Texture_constructor, 2);
+	duk_push_object(ctx);
+	duk_put_function_list(ctx, -1, methods);
+	duk_push_c_function(ctx, Texture_destructor, 1);
+	duk_set_finalizer(ctx, -2);
+	duk_push_object(ctx);
+	duk_put_number_list(ctx, -1, blend);
+	duk_put_prop_string(ctx, -2, "Blend");
+	duk_dup(ctx, -1);
+	duk_put_global_string(ctx, PROTOTYPE);
+	duk_put_prop_string(ctx, -2, "prototype");
+	duk_pop(ctx);
+}
+
+struct texture *
+js_texture_require(duk_context *ctx, duk_idx_t idx)
+{
+	struct texture *tex;
+
+	duk_get_prop_string(ctx, idx, SIGNATURE);
+	tex = duk_to_pointer(ctx, -1);
+	duk_pop(ctx);
+
+	if (!tex)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Texture object");
+
+	return tex;
+}
+
+void
+js_texture_push(duk_context *ctx, struct texture *tex)
+{
+	assert(ctx);
+	assert(tex);
+
+	duk_push_object(ctx);
+	duk_push_pointer(ctx, tex);
+	duk_put_prop_string(ctx, -2, SIGNATURE);
+	duk_get_global_string(ctx, PROTOTYPE);
+	duk_set_prototype(ctx, -2);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-core-js/core/js-texture.h	Thu Oct 14 21:21:28 2021 +0200
@@ -0,0 +1,35 @@
+/*
+ * js-texture.h -- core texture binding
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_CORE_JS_TEXTURE_H
+#define MLK_CORE_JS_TEXTURE_H
+
+#include <duktape.h>
+
+struct texture;
+
+void
+js_texture_bind(duk_context *);
+
+struct texture *
+js_texture_require(duk_context *, duk_idx_t);
+
+void
+js_texture_push(duk_context *, struct texture *);
+
+#endif /* !MLK_CORE_JS_TEXTURE_H */
--- a/src/libmlk-core-js/core/js-window.c	Thu Oct 14 12:52:41 2021 +0200
+++ b/src/libmlk-core-js/core/js-window.c	Thu Oct 14 21:21:28 2021 +0200
@@ -18,11 +18,11 @@
 
 #include <assert.h>
 
-#include <duktape.h>
-
 #include <core/error.h>
 #include <core/window.h>
 
+#include "js-window.h"
+
 #define SIGNATURE DUK_HIDDEN_SYMBOL("Mlk.Window")
 
 static duk_ret_t
--- a/src/mlk-run/main.c	Thu Oct 14 12:52:41 2021 +0200
+++ b/src/mlk-run/main.c	Thu Oct 14 21:21:28 2021 +0200
@@ -26,6 +26,10 @@
 #include <core/vfs-zip.h>
 #include <core/vfs.h>
 
+#include <core/js-clock.h>
+#include <core/js-event.h>
+#include <core/js-painter.h>
+#include <core/js-texture.h>
 #include <core/js-window.h>
 
 /* VFS loader to support zip and directories when loading game. */
@@ -34,9 +38,21 @@
 /* Javascript context. */
 static duk_context *ctx;
 
+static duk_ret_t
+print(duk_context *ctx)
+{
+	puts(duk_require_string(ctx, 0));
+
+	return 0;
+}
+
 static void
 core_bind(duk_context *ctx)
 {
+	js_clock_bind(ctx);
+	js_event_bind(ctx);
+	js_painter_bind(ctx);
+	js_texture_bind(ctx);
 	js_window_bind(ctx);
 }
 
@@ -50,6 +66,12 @@
 	/* Fireup Javascript. */
 	ctx = duk_create_heap_default();
 	core_bind(ctx);
+
+	/* Setup some convenient global functions. */
+	duk_push_global_object(ctx);
+	duk_push_c_function(ctx, print, 1);
+	duk_put_prop_string(ctx, -2, "print");
+	duk_pop(ctx);
 }
 
 static char *