-
-
Save niklas-ourmachinery/4c1e089fe04ea86e8553cf7bea073c7b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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