#include #include #include #include #include #include #include "str.h" #define SELECT_COLOR DGreen typedef struct Command Command; struct Command { const char *name; union { void (*no_args)(void); void (*many_args)(uintptr nargs, const char **args); void (*str_arg)(const char *arg); } fn; enum { CMD_NO_ARGS, CMD_STR_ARG } mode; }; typedef struct Draw_Point Draw_Point; struct Draw_Point { uintptr rc; float x, y; }; typedef struct Draw_Line Draw_Line; struct Draw_Line { uintptr point_a, point_b; u8int active; u8int selected; }; typedef struct Canvas Canvas; struct Canvas { struct { Draw_Point *contents; uintptr capacity; } points; struct { Draw_Line *contents; uintptr capacity; } lines; struct { uintptr *ids; uintptr capacity; uintptr length; } selection; }; typedef struct State State; struct State { Str_Buf command; Str_Buf bottom_line; Canvas *active_canvas; Image *select_texture; struct { Point a, b; } select_window; struct { float x, y; } origin; float scale; float pixel_size; enum { ST_DRAW, ST_COMMAND, } state; u8int stopping; u8int selecting; }; static void calc_pixel_size(void); static Canvas *canvas_new(void); static void canvas_delete(Canvas *canvas); static void canvas_more_points(Canvas *canvas, uintptr n); static void canvas_mode_lines(Canvas *canvas, uintptr n); static uintptr canvas_add_line(Canvas *canvas, float x0, float y0, float x1, float y1); static void canvas_select(Canvas *canvas, float min_x, float min_y, float max_x, float max_y); static void canvas_desel(Canvas *canvas); static void canvas_del_lines(Canvas *canvas); static Draw_Line *canvas_get_line(Canvas *canvas, uintptr id); static Draw_Point *canvas_get_point(Canvas *canvas, uintptr id); static Point draw_point_screen_pos(Draw_Point p); static void canvas_draw(const Canvas *canvas); static void draw_bottom_line(void); static void run_cmd(void); static void cmd_exit(void); static void cmd_scale(const char *arg); static void cmd_del(void); static void cmd_join(void); #define CMD_NO_ARGS(NAME, FN) { .name = (NAME), .fn = { .no_args = (FN) }, .mode = CMD_NO_ARGS } #define CMD_STR_ARG(NAME, FN) { .name = (NAME), .fn = { .str_arg = (FN) }, .mode = CMD_STR_ARG } static const Command cmds[] = { CMD_NO_ARGS("exit", cmd_exit), CMD_STR_ARG("scale", cmd_scale), CMD_NO_ARGS("del", cmd_del), CMD_NO_ARGS("join", cmd_join), }; static const Point nil_point = { 0, 0 }; static const Rectangle unit_rect = { .min = { 0, 0 }, .max = { 1, 1 } }; static State state = { .origin = { 0.f, 0.f }, .scale = 1.f }; void main(void) { if(initdraw(nil, nil, "drawer") == -1) { fprint(2, "initdraw: %r\n"); exits("initdraw"); } einit(Emouse | Ekeyboard); state.active_canvas = canvas_new(); state.select_texture = allocimage(display, unit_rect, RGBA32, 1, SELECT_COLOR); calc_pixel_size(); uintptr current_line = 0; Point prev_point = nil_point; enum { DRAW_IDLE, DRAW_DRAG, DRAW_ADD, DRAW_SEL, DRAW_MOVE } draw_mode = DRAW_IDLE; while(!state.stopping) { Event e; switch(event(&e)) { case Emouse: { float x = (e.mouse.xy.x - (float)(screen->r.min.x + screen->r.max.x) / 2.f) * state.pixel_size + state.origin.x; float y = (e.mouse.xy.y - (float)(screen->r.min.y + screen->r.max.y) / 2.f) * state.pixel_size + state.origin.y; if(state.state == ST_DRAW) { static char buf[128]; sprint(buf, "x / y: %f / %f", x, y); str_buf_clear(&state.bottom_line); str_buf_append_cstr(&state.bottom_line, buf); } if(e.mouse.buttons == 1) { if(draw_mode == DRAW_ADD) { Draw_Point *p1 = canvas_get_point(state.active_canvas, canvas_get_line(state.active_canvas, current_line)->point_b); p1->x = x; p1->y = y; } else { current_line = canvas_add_line(state.active_canvas, x, y, x, y); draw_mode = DRAW_ADD; } } else if(e.mouse.buttons == 2) { if(draw_mode == DRAW_DRAG) { state.origin.x -= (e.mouse.xy.x - prev_point.x) * state.pixel_size; state.origin.y -= (e.mouse.xy.y - prev_point.y) * state.pixel_size; } draw_mode = DRAW_DRAG; prev_point = e.mouse.xy; } else if(e.mouse.buttons == 4) { state.select_window.b = e.mouse.xy; if(draw_mode != DRAW_SEL) { state.select_window.a = e.mouse.xy; canvas_desel(state.active_canvas); draw_mode = DRAW_SEL; state.selecting = 1; } } else { if(draw_mode == DRAW_SEL) { state.selecting = 0; float x1 = (state.select_window.a.x - (float)(screen->r.min.x + screen->r.max.x) / 2.f) * state.pixel_size + state.origin.x; float y1 = (state.select_window.a.y - (float)(screen->r.min.y + screen->r.max.y) / 2.f) * state.pixel_size + state.origin.y; float mix_x = x < x1 ? x : x1; float min_y = y < y1 ? y : y1; float max_x = x > x1 ? x : x1; float max_y = y > y1 ? y : y1; canvas_select(state.active_canvas, mix_x, min_y, max_x, max_y); } draw_mode = DRAW_IDLE; } } break; case Ekeyboard: switch(state.state) { case ST_DRAW: switch(e.kbdc) { case ':': state.state = ST_COMMAND; str_buf_clear(&state.command); break; } break; case ST_COMMAND: if(e.kbdc == '\n') { run_cmd(); state.state = ST_DRAW; } else if(e.kbdc == 8) // backspace { if(state.command.length > 0) state.command.length--; else state.state = ST_DRAW; } else if(isprint(e.kbdc)) str_buf_push_char(&state.command, e.kbdc); break; } break; } canvas_draw(state.active_canvas); draw_bottom_line(); // draw selection if needed if(state.selecting) { Point c = { state.select_window.a.x, state.select_window.b.y }; Point d = { state.select_window.b.x, state.select_window.a.y }; line(screen, state.select_window.a, c, Endsquare, Endsquare, 0, state.select_texture, nil_point); line(screen, state.select_window.a, d, Endsquare, Endsquare, 0, state.select_texture, nil_point); line(screen, state.select_window.b, c, Endsquare, Endsquare, 0, state.select_texture, nil_point); line(screen, state.select_window.b, d, Endsquare, Endsquare, 0, state.select_texture, nil_point); } } freeimage(state.select_texture); canvas_delete(state.active_canvas); exits(nil); } void eresized(int new) { if(new) getwindow(display, Refbackup); calc_pixel_size(); canvas_draw(state.active_canvas); draw_bottom_line(); } static void calc_pixel_size(void) { state.pixel_size = state.scale / ((float)(screen->r.max.y - screen->r.min.y) / 2.f); } static Canvas *canvas_new(void) { Canvas *res = malloc(sizeof(Canvas)); memset(res, 0, sizeof(Canvas)); return res; } static void canvas_delete(Canvas *canvas) { if(canvas->lines.contents) free(canvas->lines.contents); if(canvas->points.contents) free(canvas->points.contents); if(canvas->selection.ids) free(canvas->selection.ids); free(canvas); } static void canvas_more_points(Canvas *canvas, uintptr n) { uintptr new_capacity = canvas->points.capacity ? canvas->points.capacity : 1; while(new_capacity < canvas->points.capacity + n) new_capacity *= 2; Draw_Point *new_points = malloc(sizeof(Draw_Point) * new_capacity); memcpy(new_points, canvas->points.contents, sizeof(Draw_Point) * canvas->points.capacity); for(uintptr i = canvas->points.capacity; i < new_capacity; i++) new_points[i].rc = 0; if(canvas->points.contents) free(canvas->points.contents); canvas->points.contents = new_points; canvas->points.capacity = new_capacity; } static void canvas_mode_lines(Canvas *canvas, uintptr n) { uintptr new_capacity = canvas->lines.capacity ? canvas->lines.capacity : 1; while(new_capacity < canvas->lines.capacity + n) new_capacity *= 2; Draw_Line *new_lines = malloc(sizeof(Draw_Line) * new_capacity); memcpy(new_lines, canvas->lines.contents, sizeof(Draw_Line) * canvas->lines.capacity); for(uintptr i = canvas->lines.capacity; i < new_capacity; i++) new_lines[i].active = 0; if(canvas->lines.contents) free(canvas->lines.contents); canvas->lines.contents = new_lines; canvas->lines.capacity = new_capacity; } static uintptr canvas_add_line(Canvas *canvas, float x0, float y0, float x1, float y1) { // create points uintptr point_a, point_b; uintptr pos = 0; while(pos < canvas->points.capacity && canvas->points.contents[pos].rc > 0) pos++; point_a = pos; if(pos == canvas->points.capacity) { canvas_more_points(canvas, 2); point_b = pos + 1; } else { pos++; while(pos < canvas->points.capacity && canvas->points.contents[pos].rc > 0) pos++; if(pos == canvas->points.capacity) canvas_more_points(canvas, 1); point_b = pos; } Draw_Point a = { 1, x0, y0 }; Draw_Point b = { 1, x1, y1 }; canvas->points.contents[point_a] = a; canvas->points.contents[point_b] = b; uintptr line = 0; while(line < canvas->lines.capacity && canvas->lines.contents[line].active) line++; if(line == canvas->lines.capacity) canvas_mode_lines(canvas, 1); Draw_Line l = { point_a, point_b, 1, 0 }; canvas->lines.contents[line] = l; return line; } static void canvas_select(Canvas *canvas, float min_x, float min_y, float max_x, float max_y) { for(uintptr i = 0; i < canvas->lines.capacity; i++) { Draw_Line *line_ptr = &canvas->lines.contents[i]; if(line_ptr->active) { Draw_Point *a = &canvas->points.contents[line_ptr->point_a]; Draw_Point *b = &canvas->points.contents[line_ptr->point_b]; if((min_x < a->x && a->x < max_x && min_y < a->y && a->y < max_y) || (min_x < b->x && b->x < max_x && min_y < b->y && b->y < max_y)) { line_ptr->selected = 1; // add selection if(canvas->selection.length == canvas->selection.capacity) { uintptr new_capacity = canvas->selection.capacity ? canvas->selection.capacity * 2 : 1; uintptr *new_ids = malloc(sizeof(uintptr) * new_capacity); memcpy(new_ids, canvas->selection.ids, sizeof(uintptr) * canvas->selection.length); if(canvas->selection.ids) free(canvas->selection.ids); canvas->selection.ids = new_ids; canvas->selection.capacity = new_capacity; } canvas->selection.ids[canvas->selection.length++] = i; } } } } static void canvas_desel(Canvas *canvas) { for(uintptr i = 0; i < canvas->selection.length; i++) canvas->lines.contents[canvas->selection.ids[i]].selected = 0; canvas->selection.length = 0; } static void canvas_del_lines(Canvas *canvas) { for(uintptr i = 0; i < canvas->selection.length; i++) { Draw_Line *l = &canvas->lines.contents[canvas->selection.ids[i]]; canvas->points.contents[l->point_a].rc--; canvas->points.contents[l->point_b].rc--; l->active = 0; } canvas->selection.length = 0; } static Draw_Line *canvas_get_line(Canvas *canvas, uintptr id) { Draw_Line *res = &canvas->lines.contents[id]; return res->active ? res : nil; } static Draw_Point *canvas_get_point(Canvas *canvas, uintptr id) { Draw_Point *res = &canvas->points.contents[id]; return res->rc > 0 ? res : nil; } static Point draw_point_screen_pos(Draw_Point p) { int x = (screen->r.min.x + screen->r.max.x) / 2 + (intptr)((p.x - state.origin.x) / state.pixel_size); int y = (screen->r.min.y + screen->r.max.y) / 2 + (intptr)((p.y - state.origin.y) / state.pixel_size); Point res = { x, y }; return res; } static void canvas_draw(const Canvas *canvas) { draw(screen, screen->r, display->white, display->opaque, nil_point); for(uintptr l = 0; l < canvas->lines.capacity; l++) { Draw_Line *line_ptr = &canvas->lines.contents[l]; if(line_ptr->active) { Point a = draw_point_screen_pos(canvas->points.contents[line_ptr->point_a]); Point b = draw_point_screen_pos(canvas->points.contents[line_ptr->point_b]); line(screen, a, b, Endsquare, Endsquare, 0, line_ptr->selected ? state.select_texture : display->black, nil_point); } } } static void draw_bottom_line(void) { Rectangle line_rect = { .min = { screen->r.min.x, screen->r.max.y - font->height }, .max = { screen->r.max.x, screen->r.max.y } }; // screen->clipr = line_rect; draw(screen, line_rect, display->white, display->opaque, nil_point); Point pos = { screen->r.min.x, screen->r.max.y - font->height }; if(state.state == ST_COMMAND) { pos = string(screen, pos, display->black, nil_point, font, ":"); pos = string(screen, pos, display->black, nil_point, font, str_buf_to_cstr(&state.command)); Point cur_top = { pos.x + 2, screen->r.max.y - font->height }; Point cur_bot = { cur_top.x, screen->r.max.y }; line(screen, cur_top, cur_bot, Enddisc, Enddisc, 0, display->black, nil_point); } else string(screen, pos, display->black, nil_point, font, str_buf_to_cstr(&state.bottom_line)); } static void run_cmd(void) { uintptr cmd_start = 0; while(cmd_start < state.command.length && isspace(state.command.contents[cmd_start])) cmd_start++; if(cmd_start == state.command.length) return; uintptr cmd_end = cmd_start; while(cmd_end < state.command.length && !isspace(state.command.contents[cmd_end])) cmd_end++; char *cmd = malloc(cmd_end - cmd_start + 1); memcpy(cmd, state.command.contents + cmd_start, cmd_end - cmd_start); cmd[cmd_end - cmd_start] = 0; for(uintptr i = 0; i < nelem(cmds); i++) { if(!strcmp(cmds[i].name, cmd)) { switch(cmds[i].mode) { case CMD_NO_ARGS: cmds[i].fn.no_args(); break; case CMD_STR_ARG: cmds[i].fn.str_arg(state.command.length > cmd_end ? str_buf_to_cstr(&state.command) + cmd_end + 1 : 0); break; } goto found; } } str_buf_clear(&state.bottom_line); str_buf_append_cstr(&state.bottom_line, "Command wasn't found"); found: free(cmd); } static void cmd_exit(void) { state.stopping = 1; } static void cmd_scale(const char *arg) { if(arg) { if((state.scale = atof(arg)) == 0.f) state.scale = 1.f; } else state.scale = 1.; calc_pixel_size(); } static void cmd_del(void) { canvas_del_lines(state.active_canvas); } static void cmd_join(void) { if(state.active_canvas->selection.length != 2) { str_buf_clear(&state.bottom_line); str_buf_append_cstr(&state.bottom_line, "join works with 2 lines"); return; } Draw_Line *a = canvas_get_line(state.active_canvas, state.active_canvas->selection.ids[0]); Draw_Line *b = canvas_get_line(state.active_canvas, state.active_canvas->selection.ids[1]); Draw_Point *pairs[4][2] = { { canvas_get_point(state.active_canvas, a->point_a), canvas_get_point(state.active_canvas, b->point_a) }, { canvas_get_point(state.active_canvas, a->point_a), canvas_get_point(state.active_canvas, b->point_b) }, { canvas_get_point(state.active_canvas, a->point_b), canvas_get_point(state.active_canvas, b->point_a) }, { canvas_get_point(state.active_canvas, a->point_b), canvas_get_point(state.active_canvas, b->point_b) }, }; float min_dist = -1.f; uintptr best_pair = 0; for(uintptr i = 0; i < 4; i++) { if(pairs[i][0] == pairs[i][1]) { str_buf_clear(&state.bottom_line); str_buf_append_cstr(&state.bottom_line, "Those lines are already connected"); return; } float dx = pairs[i][0]->x - pairs[i][1]->x; float dy = pairs[i][0]->y - pairs[i][1]->y; float dist = sqrt(dx * dx + dy * dy); if(min_dist < 0.f || dist < min_dist) { min_dist = dist; best_pair = i; } } float x = (pairs[best_pair][0]->x + pairs[best_pair][1]->x) / 2.f; float y = (pairs[best_pair][0]->y + pairs[best_pair][1]->y) / 2.f; uintptr join_point_id = best_pair & 2 ? a->point_b : a->point_a; if(best_pair & 1) { canvas_get_point(state.active_canvas, b->point_b)->rc--; b->point_b = join_point_id; } else { canvas_get_point(state.active_canvas, b->point_a)->rc--; b->point_a = join_point_id; } Draw_Point *join_point = canvas_get_point(state.active_canvas, join_point_id); join_point->rc++; join_point->x = x; join_point->y = y; }