Skip to content

Instantly share code, notes, and snippets.

@niklas-ourmachinery
Created December 1, 2021 22:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niklas-ourmachinery/4c1e089fe04ea86e8553cf7bea073c7b to your computer and use it in GitHub Desktop.
Save niklas-ourmachinery/4c1e089fe04ea86e8553cf7bea073c7b to your computer and use it in GitHub Desktop.
#include <foundation/api_registry.h>
#include <foundation/api_types.h>
#include <foundation/carray.inl>
#include <foundation/input.h>
#include <foundation/log.h>
#include <foundation/math.inl>
#include <foundation/os.h>
#include <foundation/rect.inl>
#include <foundation/temp_allocator.h>
#include <foundation/the_truth.h>
#include <foundation/the_truth_types.h>
#include <foundation/unicode.h>
#include <plugins/editor_views/properties.h>
#include <plugins/ui/draw2d.h>
#include <plugins/ui/ttf_baker.h>
#include <plugins/ui/ui.h>
#include <plugins/ui/ui_custom.h>
#include <GLES2/gl2.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#define STB_IMAGE_IMPLEMENTATION
#include <plugins/stb_image_loader/stb_image.inl>
#define TM_NO_LOCALIZE(x) x
// COMPILE INSTRUCTIONS:
//
// * You need to compile this from an Emscripten enabled command prompt: `source emsdk_env.sh`
// * You need to add `utils/webgltest/data/arial.ttf` (not checked in, since it's a binary file)
// * Compile with `tmbuild -platform web`
// * To run, run `python3 -m http.server` in the `bin/Debug` directory.
// * Go to `http://localhost:8080/webgltest.html`
// TODO:
//
// * Merge into master
// * Dynamic linking (reference https://jprendes.github.io/emscripten-dylink-demo/)
// * Touch support (emscripten_set_touchstart_callback() gives link error on Safari).
// * Compile for OS X
struct vertex
{
tm_vec2_t pos;
tm_vec4_t col;
tm_rect_t clip;
tm_vec2_t uv;
};
enum mirror_t {
DRAW_SETTINGS__MIRROR__NONE,
DRAW_SETTINGS__MIRROR__HORIZONTAL,
DRAW_SETTINGS__MIRROR__VERTICAL,
DRAW_SETTINGS__MIRROR__AMBIGRAM,
DRAW_SETTINGS__MIRROR__FOUR_WAY,
DRAW_SETTINGS__MIRROR__EIGHT_WAY,
};
typedef struct
{
tm_color_srgb_t color;
enum mirror_t mirror;
uint32_t offset;
uint32_t num_points;
} line_t;
struct app
{
tm_allocator_i *allocator;
tm_clock_o time;
double t;
tm_rect_t rect;
float width;
float height;
tm_ui_o *ui;
uint64_t next_input_event;
struct vertex *vertices;
uint32_t frames_without_input;
// Simple Draw
tm_the_truth_o *tt;
tm_tt_id_t settings_object;
tm_properties_view_o *properties_view;
float splitter_bias;
bool is_drawing;
bool is_panning;
TM_PAD(2);
/* carray */ line_t *lines;
/* carray */ tm_vec2_t *points;
/* carray */ float *widths;
uint32_t active_points;
tm_vec2_t offset;
float scale;
/* carray */ tm_vec2_t *transformed;
/* carray */ float *transformed_widths;
tm_vec2_t pan_reference;
tm_localizer_o *localizer;
} *app = &(struct app){ 0 };
typedef struct app simple_draw_o;
#define TT_TYPE__SIMPLE_DRAW_SETTINGS "simple_draw_settings"
#define TT_TYPE_HASH__SIMPLE_DRAW_SETTINGS TM_STATIC_HASH("simple_draw_settings", 0x72d25c3fddade0cbULL)
enum {
TM_TT_PROP__SIMPLE_DRAW_SETTINGS__COLOR, // subobject [[TM_TT_TYPE__COLOR_RGB]]
TM_TT_PROP__SIMPLE_DRAW_SETTINGS__OPACITY, // float
TM_TT_PROP__SIMPLE_DRAW_SETTINGS__LINE_WIDTH, // float
TM_TT_PROP__SIMPLE_DRAW_SETTINGS__MIRROR, // uint32_t
};
enum {
ATTRIB__POS,
ATTRIB__COL,
ATTRIB__CLIP,
ATTRIB__UV,
};
const char vertex_shader[] = //
"attribute vec4 apos;"
"attribute vec4 acolor;"
"attribute vec4 aclip;"
"attribute vec2 auv;"
"varying vec4 color;"
"varying vec4 clip;"
"varying vec2 texCoord;"
"void main() {"
" color = acolor;"
" clip = aclip;"
" texCoord = auv;"
" gl_Position = apos;"
"}";
const char fragment_shader[] = //
"precision lowp float;"
"varying vec4 color;"
"varying vec4 clip;"
"varying vec2 texCoord;"
"uniform sampler2D texSampler;"
"void main() {"
" if (gl_FragCoord.x < clip.x || gl_FragCoord.x > clip.x + clip.z || gl_FragCoord.y < clip.y || gl_FragCoord.y > clip.y + clip.w)"
" discard;"
" if (texCoord.x != 0.0 || texCoord.y != 0.0) {"
" gl_FragColor = texture2D(texSampler, texCoord);"
" gl_FragColor.rgb = color.rgb;"
" } else"
" gl_FragColor = color;"
"}";
const char *font_filename = "data/arial.ttf";
GLuint font_texture_obj = 0;
tm_font_glyph_t glyphs[256] = { 0 };
tm_font_t font = { 0 };
const float window_padding = 4.0f;
GLuint compile_shader(GLenum shaderType, const char *src)
{
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
if (!isCompiled) {
GLint maxLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
char *buf = (char *)malloc(maxLength + 1);
glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
TM_LOG("%s\n", buf);
free(buf);
return 0;
}
return shader;
}
GLuint create_program(GLuint vertexShader, GLuint fragmentShader)
{
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glBindAttribLocation(program, ATTRIB__POS, "apos");
glBindAttribLocation(program, ATTRIB__COL, "acolor");
glBindAttribLocation(program, ATTRIB__CLIP, "aclip");
glBindAttribLocation(program, ATTRIB__UV, "auv");
glLinkProgram(program);
return program;
}
static inline tm_rect_t rect_to_open_gl(tm_rect_t r)
{
r.w = 2.0f * r.w / app->rect.w;
r.h = 2.0f * r.h / app->rect.h;
r.x = 2.0f * (r.x - app->rect.x) / app->rect.w - 1.0f;
r.y = 1.0f - 2.0f * (r.y - app->rect.y) / app->rect.h - r.h;
return r;
}
static inline tm_vec2_t point_to_open_gl(tm_vec2_t p)
{
p.x = 2.0f * (p.x - app->rect.x) / app->rect.w - 1.0f;
p.y = 1.0f - 2.0f * (p.y - app->rect.y) / app->rect.h;
return p;
}
static inline void add_rect(struct vertex **buffer, tm_rect_t r_in, tm_color_srgb_t c, tm_rect_t clip, tm_allocator_i *ta)
{
struct vertex *vs = *buffer;
const tm_vec4_t col = { c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f };
const tm_rect_t r = rect_to_open_gl(r_in);
const struct vertex v[6] = {
{ tm_rect_bottom_left(r), col, clip },
{ tm_rect_top_left(r), col, clip },
{ tm_rect_top_right(r), col, clip },
{ tm_rect_top_right(r), col, clip },
{ tm_rect_bottom_right(r), col, clip },
{ tm_rect_bottom_left(r), col, clip },
};
tm_carray_push_array(vs, v, 6, ta);
*buffer = vs;
}
static inline void add_textured_rect(struct vertex **buffer, tm_rect_t r_in, tm_color_srgb_t c, tm_rect_t clip, tm_rect_t uv_in, tm_allocator_i *ta)
{
struct vertex *vs = *buffer;
const tm_vec4_t col = { c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f };
const tm_rect_t r = rect_to_open_gl(r_in);
const tm_rect_t uv = { uv_in.x, uv_in.y + uv_in.h, uv_in.w, -uv_in.h };
const struct vertex v[6] = {
{ tm_rect_bottom_left(r), col, clip, tm_rect_bottom_left(uv) },
{ tm_rect_top_left(r), col, clip, tm_rect_top_left(uv) },
{ tm_rect_top_right(r), col, clip, tm_rect_top_right(uv) },
{ tm_rect_top_right(r), col, clip, tm_rect_top_right(uv) },
{ tm_rect_bottom_right(r), col, clip, tm_rect_bottom_right(uv) },
{ tm_rect_bottom_left(r), col, clip, tm_rect_bottom_left(uv) },
};
tm_carray_push_array(vs, v, 6, ta);
*buffer = vs;
}
static inline void add_vertex(struct vertex **buffer, tm_vec2_t v_in, tm_color_srgb_t c, tm_rect_t clip, tm_allocator_i *ta)
{
const struct vertex v = { point_to_open_gl(v_in), { c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f }, clip };
tm_carray_push(*buffer, v, ta);
}
static tm_rect_t get_clip_rect(tm_draw2d_vbuffer_t *vbuffer, uint32_t clip)
{
if (!clip)
return (tm_rect_t){ 0, 0, FLT_MAX, FLT_MAX };
tm_rect_t r = *(tm_rect_t *)(vbuffer->vbuffer + clip);
r.y = app->rect.h - r.y - r.h;
return r;
}
void convert_draw2d_buffers(struct vertex **buffer, tm_draw2d_vbuffer_t *vbuffer, tm_draw2d_ibuffer_t *ibuffer, tm_allocator_i *a)
{
uint32_t *i = ibuffer->ibuffer;
uint32_t *end = i + ibuffer->in;
while (i < end) {
const uint32_t idx = *i;
const uint32_t primitive = idx & 0xfc000000;
const uint32_t is_glyph = primitive & 0x80000000;
const uint32_t word_offset = idx & 0x00ffffff;
const uint32_t byte_offset = word_offset * 4;
if (primitive == TM_DRAW2D_PRIMITIVE_RECT) {
tm_draw2d_rect_vertex_t *r = (tm_draw2d_rect_vertex_t *)(vbuffer->vbuffer + byte_offset);
const uint32_t clip = r->clip ? byte_offset - r->clip : 0;
add_rect(buffer, r->rect, r->color, get_clip_rect(vbuffer, clip), a);
i += 6;
} else if (primitive == TM_DRAW2D_PRIMITIVE_TRIANGLE) {
tm_draw2d_triangle_vertex_t *r = (tm_draw2d_triangle_vertex_t *)(vbuffer->vbuffer + byte_offset);
const uint32_t clip = r->clip ? byte_offset - r->clip : 0;
add_vertex(buffer, r->pos, r->color, get_clip_rect(vbuffer, clip), a);
++i;
} else if (primitive == TM_DRAW2D_PRIMITIVE_RECT_TEXTURED) {
tm_draw2d_rect_textured_vertex_t *r = (tm_draw2d_rect_textured_vertex_t *)(vbuffer->vbuffer + byte_offset);
const uint32_t clip = r->clip ? byte_offset - r->clip : 0;
add_textured_rect(buffer, r->rect, r->tint, get_clip_rect(vbuffer, clip), r->uv, a);
i += 6;
} else if (is_glyph) {
tm_draw2d_glyph_range_vertex_t *r = (tm_draw2d_glyph_range_vertex_t *)(vbuffer->vbuffer + byte_offset);
uint32_t clip = r->clip ? byte_offset - r->clip : 0;
clip = 0;
const uint32_t index = (primitive & 0x7c000000) >> 26;
tm_draw2d_glyph_vertex_t *g = (tm_draw2d_glyph_vertex_t *)(r + 1) + index;
const uint32_t glyph = g->glyph;
const tm_font_glyph_t *glyph_data = (tm_font_glyph_t *)(vbuffer->vbuffer + r->font) + glyph;
const float x = r->origin.x + (g->x_offset + glyph_data->offset.x) * r->scale;
const float y = r->origin.y + (glyph_data->offset.y) * r->scale;
const float w = glyph_data->uv.w * r->scale;
const float h = glyph_data->uv.h * r->scale;
const tm_rect_t rect = { x, y, w, h };
const float texture_size = 512.0f;
const tm_rect_t uv = { glyph_data->uv.x / texture_size, glyph_data->uv.y / texture_size, glyph_data->uv.w / texture_size, glyph_data->uv.h / texture_size };
add_textured_rect(buffer, rect, r->color, get_clip_rect(vbuffer, clip), uv, a);
i += 6;
} else
++i;
}
}
static inline float tm_vec2_distance_squared(tm_vec2_t a, tm_vec2_t b)
{
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
static void truncate_to_active_points(simple_draw_o *sd)
{
line_t *line = tm_carray_size(sd->lines) > 0 ? tm_carray_end(sd->lines) - 1 : NULL;
while (line && sd->active_points < line->offset + line->num_points) {
if (sd->active_points <= line->offset) {
tm_carray_pop(sd->lines);
line = tm_carray_size(sd->lines) > 0 ? tm_carray_end(sd->lines) - 1 : NULL;
} else {
line->num_points = sd->active_points - line->offset;
break;
}
}
tm_carray_shrink(sd->points, sd->active_points);
tm_carray_shrink(sd->widths, sd->active_points);
}
static void model_to_display(tm_vec2_t *dst, const tm_vec2_t *src, uint32_t n, tm_vec2_t offset, float scale)
{
while (n) {
*dst = (tm_vec2_t){ src->x * scale + offset.x, src->y * scale + offset.y };
++dst, ++src, --n;
}
}
static void model_to_display_widths(float *dst, const float *src, uint32_t n, float scale)
{
while (n) {
*dst = *src * scale;
++dst, ++src, --n;
}
}
static void display_to_model(tm_vec2_t *dst, const tm_vec2_t *src, uint32_t n, tm_vec2_t offset, float scale)
{
while (n) {
*dst = (tm_vec2_t){ (src->x - offset.x) / scale, (src->y - offset.y) / scale };
++dst, ++src, --n;
}
}
static void mirror(tm_vec2_t *dst, const tm_vec2_t *src, uint32_t n, tm_vec2_t plane_n)
{
while (n) {
const tm_vec2_t a = tm_vec2_mul(plane_n, 2.0f * tm_vec2_dot(*src, plane_n));
*dst = tm_vec2_sub(*src, a);
++dst, ++src, --n;
}
}
static void start_new_line(simple_draw_o *sd, tm_vec2_t pos, float pressure, bool erase)
{
truncate_to_active_points(sd);
const tm_the_truth_o *tt = sd->tt;
const tm_the_truth_object_o *settings_r = tm_the_truth_api->read(tt, sd->settings_object);
tm_color_srgb_t color = tm_the_truth_common_types_api->get_color_srgb(tt, settings_r, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__COLOR);
const float opacity = tm_the_truth_api->get_float(tt, settings_r, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__OPACITY);
const enum mirror_t mirror = tm_the_truth_api->get_uint32_t(tt, settings_r, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__MIRROR);
const float w = tm_the_truth_api->get_float(tt, settings_r, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__LINE_WIDTH) * tm_max(pressure, 0.1f);
color.a = (uint8_t)tm_clamp(opacity * 255, 0, 255);
line_t line = {
.color = color,
.mirror = mirror,
.offset = (uint32_t)tm_carray_size(sd->points),
.num_points = 2,
};
tm_carray_push(sd->lines, line, sd->allocator);
tm_carray_push(sd->points, pos, sd->allocator);
tm_carray_push(sd->points, pos, sd->allocator);
tm_carray_push(sd->widths, w, sd->allocator);
tm_carray_push(sd->widths, w, sd->allocator);
}
static void add_point(simple_draw_o *sd, tm_vec2_t pos, float pressure)
{
const tm_the_truth_o *tt = sd->tt;
const tm_the_truth_object_o *settings_r = tm_the_truth_api->read(tt, sd->settings_object);
const float w = tm_the_truth_api->get_float(tt, settings_r, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__LINE_WIDTH) * tm_max(pressure, 0.1f);
line_t *line = tm_carray_end(sd->lines) - 1;
tm_vec2_t *point = tm_carray_end(sd->points) - 1;
float *width = tm_carray_end(sd->widths) - 1;
*point = pos;
*width = w;
const float d2 = tm_vec2_distance_squared(point[0], point[-1]);
const float wp = width[-1];
const float sep = 1 + tm_max(w, wp);
if (d2 > sep * sep) {
tm_carray_push(sd->points, *point, sd->allocator);
tm_carray_push(sd->widths, *width, sd->allocator);
line->num_points++;
}
sd->active_points = (uint32_t)tm_carray_size(sd->points);
}
static void update_drawing(simple_draw_o *sd, tm_ui_o *ui, tm_rect_t rect)
{
tm_ui_buffers_t uib = tm_ui_api->buffers(ui);
const bool is_in_rect = tm_vec2_in_rect(uib.input->mouse_pos, rect);
tm_vec2_t model_pos;
display_to_model(&model_pos, &uib.input->mouse_pos, 1, sd->offset, sd->scale);
const float pressure = uib.input->pressure * (uib.input->pen_erase ? 10.0f : 1.0f);
if (uib.input->left_mouse_pressed && is_in_rect) {
start_new_line(sd, model_pos, pressure, uib.input->pen_erase);
sd->is_drawing = true;
}
if (sd->is_drawing && !uib.input->left_mouse_is_down)
sd->is_drawing = false;
if (sd->is_drawing)
add_point(sd, model_pos, pressure);
if (uib.input->mouse_wheel && is_in_rect) {
sd->scale = sd->scale * powf(1.1f, uib.input->mouse_wheel);
const tm_vec2_t p = uib.input->mouse_pos;
sd->offset = (tm_vec2_t){ p.x - model_pos.x * sd->scale, p.y - model_pos.y * sd->scale };
}
if (uib.input->middle_mouse_pressed && is_in_rect) {
sd->is_panning = true;
sd->pan_reference = uib.input->mouse_pos;
}
if (uib.input->middle_mouse_released)
sd->is_panning = false;
if (sd->is_panning) {
sd->offset = tm_vec2_add(sd->offset, tm_vec2_sub(uib.input->mouse_pos, sd->pan_reference));
sd->pan_reference = uib.input->mouse_pos;
}
}
static void draw(simple_draw_o *sd, tm_ui_o *ui, tm_rect_t rect)
{
// Transform points
tm_carray_resize(sd->transformed, sd->active_points, sd->allocator);
tm_carray_resize(sd->transformed_widths, sd->active_points, sd->allocator);
model_to_display(sd->transformed, sd->points, sd->active_points, sd->offset, sd->scale);
model_to_display_widths(sd->transformed_widths, sd->widths, sd->active_points, sd->scale);
tm_ui_buffers_t uib = tm_ui_api->buffers(ui);
tm_draw2d_style_t *style = &(tm_draw2d_style_t){
.clip = tm_draw2d_api->add_clip_rect(uib.vbuffer, rect),
.feather_width = tm_ui_api->feather_width(ui),
};
for (line_t *line = sd->lines; line != tm_carray_end(sd->lines); ++line) {
const uint32_t bytes = 8 * (line->num_points + 16) * 256;
if (uib.vbuffer->vbytes + bytes > uib.vbuffer->vbytes_allocated || uib.ibuffers[0]->in + bytes / sizeof(uint32_t) > uib.ibuffers[0]->in_allocated) {
tm_ui_api->reserve_draw_memory_detailed(ui, bytes, bytes, 0);
uib = tm_ui_api->buffers(ui);
}
style->color = line->color;
style->line_width = 1.0f;
const int32_t points_to_draw = tm_min((int32_t)line->num_points, (int32_t)sd->active_points - (int32_t)line->offset);
if (points_to_draw < 0)
break;
const uint32_t n = (uint32_t)points_to_draw;
const tm_vec2_t *points = sd->transformed + line->offset;
const float *w = sd->transformed_widths + line->offset;
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
if (line->mirror) {
const float sqrt05 = 0.70710678118f;
if (line->mirror == DRAW_SETTINGS__MIRROR__HORIZONTAL || line->mirror >= DRAW_SETTINGS__MIRROR__FOUR_WAY) {
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .x = 1 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
}
if (line->mirror == DRAW_SETTINGS__MIRROR__VERTICAL || line->mirror >= DRAW_SETTINGS__MIRROR__FOUR_WAY) {
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .y = 1 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
}
if (line->mirror == DRAW_SETTINGS__MIRROR__AMBIGRAM || line->mirror >= DRAW_SETTINGS__MIRROR__FOUR_WAY) {
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .x = 1 });
mirror(sd->transformed + line->offset, sd->transformed + line->offset, n, (tm_vec2_t){ .y = 1 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
}
if (line->mirror >= DRAW_SETTINGS__MIRROR__EIGHT_WAY) {
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .x = sqrt05, .y = sqrt05 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .x = 1 });
mirror(sd->transformed + line->offset, sd->transformed + line->offset, n, (tm_vec2_t){ .x = sqrt05, .y = sqrt05 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .y = 1 });
mirror(sd->transformed + line->offset, sd->transformed + line->offset, n, (tm_vec2_t){ .x = sqrt05, .y = sqrt05 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
mirror(sd->transformed + line->offset, sd->points + line->offset, n, (tm_vec2_t){ .x = 1 });
mirror(sd->transformed + line->offset, sd->transformed + line->offset, n, (tm_vec2_t){ .y = 1 });
mirror(sd->transformed + line->offset, sd->transformed + line->offset, n, (tm_vec2_t){ .x = sqrt05, .y = sqrt05 });
model_to_display(sd->transformed + line->offset, sd->transformed + line->offset, n, sd->offset, sd->scale);
tm_draw2d_api->stroke_polyline_widths(uib.vbuffer, *uib.ibuffers, style, points, w, n, false);
}
}
}
}
static line_t *last_drawn_line(simple_draw_o *sd)
{
for (line_t *line = tm_carray_end(sd->lines) - 1; line >= sd->lines; --line) {
if (sd->active_points > line->offset)
return line;
}
return NULL;
}
static void undo(simple_draw_o *sd)
{
if (sd->is_drawing)
return;
line_t *last_line = last_drawn_line(sd);
if (last_line)
sd->active_points = last_line->offset;
}
static void redo(simple_draw_o *sd)
{
if (sd->is_drawing)
return;
line_t *last_line = last_drawn_line(sd);
if (last_line && last_line + 1 < tm_carray_end(sd->lines))
sd->active_points = (last_line + 1)->offset + (last_line + 1)->num_points;
else if (last_line == NULL && tm_carray_size(sd->lines) > 0)
sd->active_points = sd->lines->offset + sd->lines->num_points;
}
static void clear(simple_draw_o *sd)
{
tm_carray_shrink(sd->lines, 0);
tm_carray_shrink(sd->points, 0);
tm_carray_shrink(sd->widths, 0);
sd->active_points = 0;
}
static inline tm_draw2d_style_t *set_color(tm_draw2d_style_t *style, tm_color_srgb_t color)
{
style->color = color;
return style;
}
bool update_simple_draw(simple_draw_o *sd, tm_ui_o *ui, const tm_ui_style_t *uistyle, tm_rect_t rect)
{
tm_ui_buffers_t uib = tm_ui_api->buffers(ui);
// Menu bar
enum {
MENU_NONE,
MENU_FILE,
MENU_FILE_EXIT,
MENU_EDIT,
MENU_EDIT_UNDO,
MENU_EDIT_REDO,
MENU_EDIT_CLEAR,
MENU_LANGUAGE,
MENU_LANGUAGE_ENGLISH,
MENU_LANGUAGE_SWEDISH,
};
tm_ui_menu_item_t root_items[] = {
{ .text = TM_NO_LOCALIZE("File"), .item_id = MENU_FILE },
{ .text = TM_NO_LOCALIZE("Edit"), .item_id = MENU_EDIT },
// { .text = TM_NO_LOCALIZE("Language"), .item_id = MENU_LANGUAGE },
};
const tm_ui_menubar_t menubar = {
.pos.x = rect.x,
.pos.y = rect.y,
.width = rect.w,
.padding_left_right = window_padding,
.items = root_items,
.num_items = TM_ARRAY_COUNT(root_items),
};
tm_ui_menu_result_t res = tm_ui_api->menubar(ui, uistyle, &menubar);
if (res.selected_item_id == MENU_FILE) {
const tm_ui_menu_item_t items[] = {
{ .text = TM_NO_LOCALIZE("Exit"), .accelerator = "Ctrl+Q", .item_id = MENU_FILE_EXIT },
};
const tm_ui_menu_t menu = { .pos = res.submenu_pos, .items = items, .num_items = TM_ARRAY_COUNT(items) };
res = tm_ui_api->menu(ui, uistyle, &menu);
} else if (res.selected_item_id == MENU_EDIT) {
const tm_ui_menu_item_t items[] = {
{ .text = TM_NO_LOCALIZE("Undo"), .accelerator = "Ctrl+Z", .item_id = MENU_EDIT_UNDO },
{ .text = TM_NO_LOCALIZE("Redo"), .accelerator = "Ctrl+Y", .item_id = MENU_EDIT_REDO },
{ 0 },
{ .text = TM_NO_LOCALIZE("Clear"), .accelerator = "Del", .item_id = MENU_EDIT_CLEAR },
};
const tm_ui_menu_t menu = { .pos = res.submenu_pos, .items = items, .num_items = TM_ARRAY_COUNT(items) };
res = tm_ui_api->menu(ui, uistyle, &menu);
} else if (res.selected_item_id == MENU_LANGUAGE) {
/*
const tm_strhash_t lang = localizer_language(sd->localizer);
const tm_ui_menu_item_t items[] = {
{ .text = TM_NO_LOCALIZE("English"), .item_id = MENU_LANGUAGE_ENGLISH, .is_checked = TM_STRHASH_EQUAL(lang, TM_LANGUAGE_ENGLISH) },
{ .text = TM_NO_LOCALIZE("Swedish"), .item_id = MENU_LANGUAGE_SWEDISH, .is_checked = TM_STRHASH_EQUAL(lang, TM_LANGUAGE_SWEDISH) },
};
const tm_ui_menu_t menu = { .pos = res.submenu_pos, .items = items, .num_items = TM_ARRAY_COUNT(items) };
res = tm_ui_api->menu(ui, uistyle, &menu);
*/
}
uint64_t menu_action = MENU_NONE;
if (res.selected_item_id)
menu_action = res.selected_item_id;
else if (uib.input->key_pressed[TM_INPUT_KEYBOARD_ITEM_Q] && uib.input->modifiers == TM_UI_MODIFIERS_CTRL)
menu_action = MENU_FILE_EXIT;
else if (uib.input->key_pressed[TM_INPUT_KEYBOARD_ITEM_Z] && uib.input->modifiers == TM_UI_MODIFIERS_CTRL)
menu_action = MENU_EDIT_UNDO;
else if (uib.input->key_pressed[TM_INPUT_KEYBOARD_ITEM_Y] && uib.input->modifiers == TM_UI_MODIFIERS_CTRL)
menu_action = MENU_EDIT_REDO;
else if (uib.input->key_pressed[TM_INPUT_KEYBOARD_ITEM_DELETE])
menu_action = MENU_EDIT_CLEAR;
if (menu_action == MENU_EDIT_UNDO)
undo(sd);
else if (menu_action == MENU_EDIT_REDO)
redo(sd);
else if (menu_action == MENU_EDIT_CLEAR)
clear(sd);
/*
else if (menu_action == MENU_LANGUAGE_ENGLISH)
localizer_set_language(sd->localizer, TM_LANGUAGE_ENGLISH);
else if (menu_action == MENU_LANGUAGE_SWEDISH)
localizer_set_language(sd->localizer, TM_LANGUAGE_SWEDISH);
*/
const float menu_bar_height = uib.metrics[TM_UI_METRIC_MENUBAR_HEIGHT];
const tm_rect_t content_r = tm_rect_split_top(rect, menu_bar_height, 0, 1);
tm_rect_t ui_r, right_r;
tm_ui_api->splitter_x(ui, uistyle, &(tm_ui_splitter_t){ .rect = content_r }, &sd->splitter_bias, &ui_r, &right_r);
const tm_rect_t draw_r = tm_rect_split_bottom(right_r, 30.0f, 0, 0);
const tm_rect_t undo_r = tm_rect_split_bottom(right_r, 30.0f, 0, 1);
float active_points = (float)sd->active_points;
tm_ui_api->pane(ui, uistyle, undo_r);
const tm_rect_t undo_inset_r = tm_rect_inset(undo_r, 10, 0);
const tm_rect_t undo_label_r = tm_rect_split_left(undo_inset_r, 50, 5, 0);
const tm_rect_t undo_slider_r = tm_rect_split_left(undo_inset_r, 50, 5, 1);
tm_properties_view_api->ui(sd->properties_view, (tm_tt_id_t){ 0 }, ui, uistyle, ui_r, 0);
tm_properties_view_api->update(sd->properties_view, ui);
tm_ui_api->label(ui, uistyle, &(tm_ui_label_t){ .rect = undo_label_r, .text = TM_NO_LOCALIZE("Undo:") });
tm_ui_api->slider(ui, uistyle, &(tm_ui_slider_t){ .rect = undo_slider_r, .min = 0, .max = (float)(uint32_t)tm_carray_size(sd->points), .step = 1 }, &active_points, NULL);
sd->active_points = (uint32_t)(active_points + 0.5f);
tm_draw2d_style_t style[1] = { 0 };
tm_ui_api->to_draw_style(ui, style, uistyle);
tm_draw2d_api->fill_rect(uib.vbuffer, *uib.ibuffers, set_color(style, (tm_color_srgb_t){ .r = 255, .g = 255, .b = 255, .a = 255 }), draw_r);
if (sd->offset.x == FLT_MAX || sd->offset.y == FLT_MAX)
sd->offset = (tm_vec2_t){ draw_r.x + draw_r.w / 2, draw_r.y + draw_r.h / 2 };
update_drawing(sd, ui, draw_r);
draw(sd, ui, draw_r);
return menu_action == MENU_FILE_EXIT;
}
EM_BOOL main_loop(double time, void *userdata)
{
const int width = EM_ASM_INT({ return document.getElementById('canvas').width; });
const int height = EM_ASM_INT({ return document.getElementById('canvas').height; });
if (width != app->rect.w || height != app->rect.h) {
TM_LOG("width: %d, height: %d\n", width, height);
app->rect = (tm_rect_t){ .w = width, .h = height };
app->frames_without_input = 0;
glViewport(0, 0, width, height);
}
{
tm_input_event_t events[32];
const uint64_t n = tm_input_api->events(app->next_input_event, events, 32);
app->frames_without_input = n ? 0 : app->frames_without_input + 1;
if (app->frames_without_input > 10)
return EM_TRUE;
}
const tm_clock_o now = tm_os_api->time->now();
const double delta = tm_os_api->time->delta(now, app->time);
app->time = now;
app->t += delta;
tm_ui_api->clear(app->ui);
tm_ui_api->add_font(app->ui, (tm_strhash_t){ 0 }, 16, &font);
while (true) {
tm_input_event_t events[32];
const uint64_t n = tm_input_api->events(app->next_input_event, events, 32);
app->next_input_event += n;
tm_ui_api->feed_events(app->ui, events, n, (tm_vec2_t){ 0, 0 }, (tm_vec2_t){ 1, 1 });
if (n < 32)
break;
}
tm_ui_style_t *uistyle = (tm_ui_style_t[1]){ tm_ui_api->default_style(app->ui) };
update_simple_draw(app, app->ui, uistyle, app->rect);
tm_ui_api->merge_overlay(app->ui);
tm_ui_buffers_t uib = tm_ui_api->buffers(app->ui);
tm_carray_shrink(app->vertices, 0);
convert_draw2d_buffers(&app->vertices, uib.vbuffer, *uib.ibuffers, app->allocator);
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, tm_carray_bytes(app->vertices), app->vertices, GL_STATIC_DRAW);
glVertexAttribPointer(ATTRIB__POS, tm_sizeof_member(struct vertex, pos) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void *)tm_offset_of(struct vertex, pos));
glVertexAttribPointer(ATTRIB__COL, tm_sizeof_member(struct vertex, col) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void *)tm_offset_of(struct vertex, col));
glVertexAttribPointer(ATTRIB__CLIP, tm_sizeof_member(struct vertex, clip) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void *)tm_offset_of(struct vertex, clip));
glVertexAttribPointer(ATTRIB__UV, tm_sizeof_member(struct vertex, uv) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void *)tm_offset_of(struct vertex, uv));
glEnableVertexAttribArray(ATTRIB__POS);
glEnableVertexAttribArray(ATTRIB__COL);
glEnableVertexAttribArray(ATTRIB__CLIP);
glEnableVertexAttribArray(ATTRIB__UV);
glClearColor(0.3f, 0.3f, 0.3f, 1);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, tm_carray_size(app->vertices));
return EM_TRUE;
}
void init_font()
{
const tm_file_stat_t stat = tm_os_api->file_system->stat(font_filename);
if (stat.size) {
TM_INIT_TEMP_ALLOCATOR(ta)
uint8_t *ttf = tm_temp_alloc(ta, stat.size);
tm_file_o f = tm_os_api->file_io->open_input(font_filename);
tm_os_api->file_io->read(f, ttf, stat.size);
tm_os_api->file_io->close(f);
const int width = 1024;
const int height = 1024;
uint8_t *image = tm_temp_alloc(ta, width * height);
tm_ttf_range_t range = { 0, 128 };
uint64_t bytes = 0;
font = *tm_ttf_baker_api->bake(ttf, 0, 12.0f, (float[1]){ 1.0f }, 1, 0, image, width, height, &range, 1, false, app->allocator, &bytes);
// Generate a GL texture object
glGenTextures(1, &font_texture_obj);
// Bind GL texture
glBindTexture(GL_TEXTURE_2D, font_texture_obj);
// Set the GL texture's wrapping and stretching properties
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
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_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, image);
TM_SHUTDOWN_TEMP_ALLOCATOR(ta);
} else
TM_LOG("Font file not found %s", font_filename);
}
// ----------------------------------------------------------------------------
enum { MOUSE_EVENTS_BUFFER_SIZE = 128 };
static uint64_t mice[1] = { 0 };
static uint64_t mouse_events_n;
static tm_input_event_t mouse_events[MOUSE_EVENTS_BUFFER_SIZE];
static tm_input_data_t mouse_state[1][TM_INPUT_MOUSE_ITEM_COUNT];
static tm_input_item_t mouse_items[TM_INPUT_MOUSE_ITEM_COUNT] = {
{ TM_INPUT_MOUSE_ITEM_NONE, "", 1 },
{ TM_INPUT_MOUSE_ITEM_BUTTON_LEFT, "button_left", 1 },
{ TM_INPUT_MOUSE_ITEM_BUTTON_RIGHT, "button_right", 1 },
{ TM_INPUT_MOUSE_ITEM_BUTTON_MIDDLE, "button_middle", 1 },
{ TM_INPUT_MOUSE_ITEM_BUTTON_4, "button_4", 1 },
{ TM_INPUT_MOUSE_ITEM_BUTTON_5, "button_5", 1 },
{ TM_INPUT_MOUSE_ITEM_WHEEL, "wheel", 2 },
{ TM_INPUT_MOUSE_ITEM_MOVE, "move", 2 },
{ TM_INPUT_MOUSE_ITEM_POSITION, "position", 2 },
};
static uint32_t input_source__mouse__controllers(uint64_t **ids)
{
static uint64_t mice[1] = { 0 };
*ids = mice;
return 1;
}
static uint32_t input_source__mouse__items(tm_input_item_t **items)
{
*items = mouse_items;
return sizeof(mouse_items) / sizeof(tm_input_item_t);
}
static uint64_t input_source__mouse__events(uint64_t start, tm_input_event_t *events, uint64_t buffer_size)
{
if (!events)
return start > mouse_events_n ? 0 : mouse_events_n - start;
tm_input_event_t nil_event = { 0 };
uint64_t c = 0;
while (start < mouse_events_n && buffer_size > 0) {
if (mouse_events_n > MOUSE_EVENTS_BUFFER_SIZE && start < mouse_events_n - MOUSE_EVENTS_BUFFER_SIZE)
*events = nil_event;
else
*events = mouse_events[start % MOUSE_EVENTS_BUFFER_SIZE];
++start, ++events, --buffer_size, ++c;
}
return c;
}
static tm_input_data_t input_source__mouse__state(uint64_t controller, uint64_t item)
{
return mouse_state[0][item];
}
tm_input_source_i mouse_input_source = {
.controller_name = "emscripten_mouse",
.controller_type = TM_INPUT_CONTROLLER_TYPE_MOUSE,
.controllers = input_source__mouse__controllers,
.items = input_source__mouse__items,
.events = input_source__mouse__events,
.state = input_source__mouse__state,
};
void add_mouse_event(const tm_input_event_t *e)
{
mouse_events[mouse_events_n % MOUSE_EVENTS_BUFFER_SIZE] = *e;
++mouse_events_n;
mouse_state[0][e->item_id] = e->data;
}
EM_BOOL mouse_event(int event_type, const EmscriptenMouseEvent *e, void *userdata)
{
tm_input_event_t ev = {
.time = tm_os_api->time->now().opaque,
.controller_id = mice[0],
.source = &mouse_input_source,
.type = TM_INPUT_EVENT_TYPE_DATA_CHANGE,
};
// Handle move
if (event_type == EMSCRIPTEN_EVENT_MOUSEMOVE) {
ev.item_id = TM_INPUT_MOUSE_ITEM_MOVE;
ev.data.f = (tm_vec4_t){ e->movementX, e->movementY, 0, 0 };
add_mouse_event(&ev);
ev.item_id = TM_INPUT_MOUSE_ITEM_POSITION;
ev.data.f = (tm_vec4_t){ e->targetX, e->targetY, 0, 0 };
add_mouse_event(&ev);
} else if (event_type == EMSCRIPTEN_EVENT_MOUSEDOWN || event_type == EMSCRIPTEN_EVENT_MOUSEUP) {
ev.item_id = e->button == 0 ? TM_INPUT_MOUSE_ITEM_BUTTON_LEFT : e->button == 1 ? TM_INPUT_MOUSE_ITEM_BUTTON_MIDDLE : TM_INPUT_MOUSE_ITEM_BUTTON_RIGHT;
ev.data.f.x = event_type == EMSCRIPTEN_EVENT_MOUSEDOWN ? 1.0f : 0.0f;
add_mouse_event(&ev);
}
return true;
}
enum { KEYBOARD_EVENTS_BUFFER_SIZE = 128 };
static uint64_t keyboards[1] = { 0 };
static uint64_t keyboard_events_n;
static tm_input_event_t keyboard_events[KEYBOARD_EVENTS_BUFFER_SIZE];
static float keyboard_state[1][TM_INPUT_KEYBOARD_ITEM_COUNT];
static tm_input_item_t keyboard_items[TM_INPUT_KEYBOARD_ITEM_COUNT];
static uint32_t input_source__keyboard__controllers(uint64_t **ids)
{
static uint64_t keyboards[1] = { 0 };
*ids = keyboards;
return 1;
}
static uint32_t input_source__keyboard__items(tm_input_item_t **items)
{
if (keyboard_items[0].components == 0) {
const char **names = tm_input_api->keyboard_item_names();
for (uint32_t i = 0; i < TM_INPUT_KEYBOARD_ITEM_COUNT; ++i) {
keyboard_items[i] = (tm_input_item_t){
.id = i,
.components = 1,
.name = names[i],
};
}
}
*items = keyboard_items;
return sizeof(keyboard_items) / sizeof(tm_input_item_t);
}
static uint64_t input_source__keyboard__events(uint64_t start, tm_input_event_t *events, uint64_t buffer_size)
{
if (!events)
return start > keyboard_events_n ? 0 : keyboard_events_n - start;
tm_input_event_t nil_event = { 0 };
uint64_t c = 0;
while (start < keyboard_events_n && buffer_size > 0) {
if (keyboard_events_n > KEYBOARD_EVENTS_BUFFER_SIZE && start < keyboard_events_n - KEYBOARD_EVENTS_BUFFER_SIZE)
*events = nil_event;
else
*events = keyboard_events[start % KEYBOARD_EVENTS_BUFFER_SIZE];
++start, ++events, --buffer_size, ++c;
}
return c;
}
static tm_input_data_t input_source__keyboard__state(uint64_t controller, uint64_t item)
{
return (tm_input_data_t){ .f = { keyboard_state[0][item] } };
}
tm_input_source_i keyboard_input_source = {
.controller_name = "emscripten_keyboard",
.controller_type = TM_INPUT_CONTROLLER_TYPE_KEYBOARD,
.controllers = input_source__keyboard__controllers,
.items = input_source__keyboard__items,
.events = input_source__keyboard__events,
.state = input_source__keyboard__state,
};
void add_keyboard_event(const tm_input_event_t *e)
{
keyboard_events[keyboard_events_n % KEYBOARD_EVENTS_BUFFER_SIZE] = *e;
++keyboard_events_n;
if (e->type == TM_INPUT_EVENT_TYPE_DATA_CHANGE)
keyboard_state[0][e->item_id] = e->data.f.x;
}
struct
{
const char *name;
uint32_t code;
} keys[] = {
{ "KeyA", TM_INPUT_KEYBOARD_ITEM_A },
{ "KeyB", TM_INPUT_KEYBOARD_ITEM_B },
{ "KeyC", TM_INPUT_KEYBOARD_ITEM_C },
{ "KeyD", TM_INPUT_KEYBOARD_ITEM_D },
{ "KeyE", TM_INPUT_KEYBOARD_ITEM_E },
{ "KeyF", TM_INPUT_KEYBOARD_ITEM_F },
{ "KeyG", TM_INPUT_KEYBOARD_ITEM_G },
{ "KeyH", TM_INPUT_KEYBOARD_ITEM_H },
{ "KeyI", TM_INPUT_KEYBOARD_ITEM_I },
{ "KeyJ", TM_INPUT_KEYBOARD_ITEM_J },
{ "KeyK", TM_INPUT_KEYBOARD_ITEM_K },
{ "KeyL", TM_INPUT_KEYBOARD_ITEM_L },
{ "KeyM", TM_INPUT_KEYBOARD_ITEM_M },
{ "KeyN", TM_INPUT_KEYBOARD_ITEM_N },
{ "KeyO", TM_INPUT_KEYBOARD_ITEM_O },
{ "KeyP", TM_INPUT_KEYBOARD_ITEM_P },
{ "KeyQ", TM_INPUT_KEYBOARD_ITEM_Q },
{ "KeyR", TM_INPUT_KEYBOARD_ITEM_R },
{ "KeyS", TM_INPUT_KEYBOARD_ITEM_S },
{ "KeyT", TM_INPUT_KEYBOARD_ITEM_T },
{ "KeyU", TM_INPUT_KEYBOARD_ITEM_U },
{ "KeyV", TM_INPUT_KEYBOARD_ITEM_V },
{ "KeyW", TM_INPUT_KEYBOARD_ITEM_W },
{ "KeyX", TM_INPUT_KEYBOARD_ITEM_X },
{ "KeyY", TM_INPUT_KEYBOARD_ITEM_Y },
{ "KeyZ", TM_INPUT_KEYBOARD_ITEM_Z },
{ "Digit1", TM_INPUT_KEYBOARD_ITEM_1 },
{ "Digit2", TM_INPUT_KEYBOARD_ITEM_2 },
{ "Digit3", TM_INPUT_KEYBOARD_ITEM_3 },
{ "Digit4", TM_INPUT_KEYBOARD_ITEM_4 },
{ "Digit5", TM_INPUT_KEYBOARD_ITEM_5 },
{ "Digit6", TM_INPUT_KEYBOARD_ITEM_6 },
{ "Digit7", TM_INPUT_KEYBOARD_ITEM_7 },
{ "Digit8", TM_INPUT_KEYBOARD_ITEM_8 },
{ "Digit9", TM_INPUT_KEYBOARD_ITEM_9 },
{ "Digit0", TM_INPUT_KEYBOARD_ITEM_0 },
{ "F1", TM_INPUT_KEYBOARD_ITEM_F1 },
{ "F2", TM_INPUT_KEYBOARD_ITEM_F2 },
{ "F3", TM_INPUT_KEYBOARD_ITEM_F3 },
{ "F4", TM_INPUT_KEYBOARD_ITEM_F4 },
{ "F5", TM_INPUT_KEYBOARD_ITEM_F5 },
{ "F6", TM_INPUT_KEYBOARD_ITEM_F6 },
{ "F7", TM_INPUT_KEYBOARD_ITEM_F7 },
{ "F8", TM_INPUT_KEYBOARD_ITEM_F8 },
{ "F9", TM_INPUT_KEYBOARD_ITEM_F9 },
{ "F10", TM_INPUT_KEYBOARD_ITEM_F10 },
{ "F11", TM_INPUT_KEYBOARD_ITEM_F11 },
{ "F12", TM_INPUT_KEYBOARD_ITEM_F12 },
{ "Escape", TM_INPUT_KEYBOARD_ITEM_ESCAPE },
{ "Backquote", TM_INPUT_KEYBOARD_ITEM_GRAVE },
{ "Minus", TM_INPUT_KEYBOARD_ITEM_MINUS },
{ "Equal", TM_INPUT_KEYBOARD_ITEM_EQUAL },
{ "Backspace", TM_INPUT_KEYBOARD_ITEM_BACKSPACE },
{ "Tab", TM_INPUT_KEYBOARD_ITEM_TAB },
{ "BracketLeft", TM_INPUT_KEYBOARD_ITEM_LEFTBRACE },
{ "BracketRight", TM_INPUT_KEYBOARD_ITEM_RIGHTBRACE },
{ "Backslash", TM_INPUT_KEYBOARD_ITEM_BACKSLASH },
{ "CapsLock", TM_INPUT_KEYBOARD_ITEM_CAPSLOCK },
{ "Semicolon", TM_INPUT_KEYBOARD_ITEM_SEMICOLON },
{ "Quote", TM_INPUT_KEYBOARD_ITEM_APOSTROPHE },
{ "Enter", TM_INPUT_KEYBOARD_ITEM_ENTER },
{ "ShiftLeft", TM_INPUT_KEYBOARD_ITEM_LEFTSHIFT },
{ "Comma", TM_INPUT_KEYBOARD_ITEM_COMMA },
{ "Period", TM_INPUT_KEYBOARD_ITEM_DOT },
{ "Slash", TM_INPUT_KEYBOARD_ITEM_SLASH },
{ "ShiftRight", TM_INPUT_KEYBOARD_ITEM_RIGHTSHIFT },
{ "ControlLeft", TM_INPUT_KEYBOARD_ITEM_LEFTCONTROL },
{ "AltLeft", TM_INPUT_KEYBOARD_ITEM_LEFTALT },
{ "MetaLeft", TM_INPUT_KEYBOARD_ITEM_LEFTMETA },
{ "Space", TM_INPUT_KEYBOARD_ITEM_SPACE },
{ "MetaRight", TM_INPUT_KEYBOARD_ITEM_RIGHTMETA },
{ "AltRight", TM_INPUT_KEYBOARD_ITEM_RIGHTALT },
{ "ControlRight", TM_INPUT_KEYBOARD_ITEM_RIGHTCONTROL },
{ "ArrowUp", TM_INPUT_KEYBOARD_ITEM_UP },
{ "ArrowDown", TM_INPUT_KEYBOARD_ITEM_DOWN },
{ "ArrowLeft", TM_INPUT_KEYBOARD_ITEM_LEFT },
{ "ArrowRight", TM_INPUT_KEYBOARD_ITEM_RIGHT },
};
uint32_t vkey(const char *name)
{
for (uint32_t i = 0; i < TM_ARRAY_COUNT(keys); ++i) {
if (strcmp(name, keys[i].name) == 0)
return keys[i].code;
}
TM_LOG("Unknown key: %s", name);
return 0;
}
EM_BOOL key_down_event(int event_type, const EmscriptenKeyboardEvent *e, void *userdata)
{
tm_input_event_t ev = {
.time = tm_os_api->time->now().opaque,
.controller_id = keyboards[0],
.source = &keyboard_input_source,
.type = TM_INPUT_EVENT_TYPE_DATA_CHANGE,
.item_id = vkey(e->code),
.data.f.x = 1.0f,
};
add_keyboard_event(&ev);
if (tm_unicode_api->utf8_num_codepoints(e->key) == 1) {
const char *p = e->key;
const uint32_t codepoint = tm_unicode_api->utf8_decode(&p);
tm_input_event_t text = {
.time = tm_os_api->time->now().opaque,
.source = &keyboard_input_source,
.type = TM_INPUT_EVENT_TYPE_TEXT,
.data.codepoint = codepoint,
};
add_keyboard_event(&text);
}
return true;
}
EM_BOOL key_up_event(int event_type, const EmscriptenKeyboardEvent *e, void *userdata)
{
tm_input_event_t ev = {
.time = tm_os_api->time->now().opaque,
.controller_id = keyboards[0],
.source = &keyboard_input_source,
.type = TM_INPUT_EVENT_TYPE_DATA_CHANGE,
.item_id = vkey(e->code),
.data.f.x = 0.0f,
};
add_keyboard_event(&ev);
return true;
}
// ----------------------------------------------------------------------------
static float settings_properties_ui(struct tm_properties_ui_args_t *args, tm_rect_t item_rect, tm_tt_id_t object)
{
const tm_tt_id_t color = tm_the_truth_api->get_subobject(args->tt, tm_tt_read(args->tt, object), TM_TT_PROP__SIMPLE_DRAW_SETTINGS__COLOR);
item_rect.y = tm_properties_view_api->ui_color_picker(args, item_rect, TM_NO_LOCALIZE("Color"), TM_NO_LOCALIZE("Color of line."), color);
item_rect.y = tm_properties_view_api->ui_float_slider(args, item_rect, TM_NO_LOCALIZE("Opacity"), TM_NO_LOCALIZE("Controls how transparent the line is."), object, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__OPACITY,
&(tm_properties_float_slider_t){ .min = 0.0f, .max = 1.0f, .step = 0.01f, .show_edit_box = true });
item_rect.y = tm_properties_view_api->ui_float_slider(args, item_rect, TM_NO_LOCALIZE("Line Width"), TM_NO_LOCALIZE("Width of line."), object, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__LINE_WIDTH,
&(tm_properties_float_slider_t){ .min = 1.0f, .max = 50.0f, .step = 0.1f, .show_edit_box = true, .edit_min = 1.0f, .edit_max = 1000.0f });
item_rect.y = tm_properties_view_api->ui_uint32_dropdown(args, item_rect, TM_NO_LOCALIZE("Mirror"), TM_NO_LOCALIZE("Enables/disables automatic mirroring of line."), object, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__MIRROR,
(const char * [6]){ TM_NO_LOCALIZE("None"), TM_NO_LOCALIZE("Horizontal"), TM_NO_LOCALIZE("Vertical"), TM_NO_LOCALIZE("Ambigram"), TM_NO_LOCALIZE("4-Way"), TM_NO_LOCALIZE("8-Way") }, NULL, NULL, 6);
return item_rect.y;
}
// ----------------------------------------------------------------------------
tm_properties_aspect_i *properties_aspect = &(tm_properties_aspect_i){
.custom_ui = settings_properties_ui,
};
int main()
{
app->allocator = tm_allocator_api->system;
EmscriptenWebGLContextAttributes attr;
emscripten_webgl_init_context_attributes(&attr);
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
emscripten_webgl_make_context_current(ctx);
emscripten_set_mousemove_callback("#canvas", 0, 1, mouse_event);
emscripten_set_mousedown_callback("#canvas", 0, 1, mouse_event);
emscripten_set_mouseup_callback("#canvas", 0, 1, mouse_event);
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, key_down_event);
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 1, key_up_event);
tm_input_api->add_source(&mouse_input_source);
tm_input_api->add_source(&keyboard_input_source);
GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
GLuint program = create_program(vs, fs);
glUseProgram(program);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
init_font();
const int width = EM_ASM_INT({ return document.getElementById('canvas').width; });
const int height = EM_ASM_INT({ return document.getElementById('canvas').height; });
TM_LOG("width: %d, height: %d\n", width, height);
app->rect = (tm_rect_t){ .w = width, .h = height };
app->time = tm_os_api->time->now();
static tm_the_truth_property_definition_t simple_draw_settings_properties[] = {
{ "color", TM_THE_TRUTH_PROPERTY_TYPE_SUBOBJECT, .type_hash = TM_TT_TYPE_HASH__COLOR_RGB },
{ "opacity", TM_THE_TRUTH_PROPERTY_TYPE_FLOAT },
{ "line_width", TM_THE_TRUTH_PROPERTY_TYPE_FLOAT },
{ "mirror", TM_THE_TRUTH_PROPERTY_TYPE_UINT32_T },
};
tm_the_truth_o *tt = tm_the_truth_api->create(app->allocator, TM_THE_TRUTH_CREATE_TYPES_NONE);
tm_the_truth_common_types_api->create_common_types(tt);
app->tt = tt;
const tm_tt_type_t settings_type = tm_the_truth_api->create_object_type(tt, TT_TYPE__SIMPLE_DRAW_SETTINGS, simple_draw_settings_properties, TM_ARRAY_COUNT(simple_draw_settings_properties));
const tm_tt_id_t settings_object = tm_the_truth_api->create_object_of_type(tt, settings_type, TM_TT_NO_UNDO_SCOPE);
tm_the_truth_object_o *settings_object_w = tm_the_truth_api->write(tt, settings_object);
tm_the_truth_api->set_float(tt, settings_object_w, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__OPACITY, 1.0f);
tm_the_truth_api->set_float(tt, settings_object_w, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__LINE_WIDTH, 4.0f);
tm_the_truth_common_types_api->set_color_rgb(tt, settings_object_w, TM_TT_PROP__SIMPLE_DRAW_SETTINGS__COLOR, (tm_vec3_t){ 1, 0, 0 }, TM_TT_NO_UNDO_SCOPE);
tm_the_truth_api->commit(tt, settings_object_w, TM_TT_NO_UNDO_SCOPE);
tm_tt_set_aspect(tt, settings_type, tm_properties_aspect_i, properties_aspect);
tm_properties_view_o *properties_view = tm_properties_view_api->create_properties_view(app->allocator,
&(tm_properties_view_config_t){
.tt = tt,
});
tm_properties_view_api->set_object(properties_view, settings_object);
app->settings_object = settings_object;
app->properties_view = properties_view;
app->splitter_bias = -0.5f;
app->scale = 1.0f;
app->offset = (tm_vec2_t){ FLT_MAX, FLT_MAX };
app->ui = tm_ui_api->create(tm_allocator_api->system);
// TODO: emscripten_set_main_loop() seems preferable, but returns a LinkError in the browser.
emscripten_request_animation_frame_loop(main_loop, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment