From 8d2698ae021137978701a7394fa76fd10dc497f1 Mon Sep 17 00:00:00 2001 From: abirvalarg Date: Fri, 14 Nov 2025 22:28:31 +0200 Subject: [PATCH] initial --- .gitignore | 1 + compile_flags.txt | 6 + local.vim | 1 + mkfile | 14 ++ src/main.c | 598 ++++++++++++++++++++++++++++++++++++++++++++++ src/str.c | 46 ++++ src/str.h | 22 ++ 7 files changed, 688 insertions(+) create mode 100644 .gitignore create mode 100644 compile_flags.txt create mode 100644 local.vim create mode 100644 mkfile create mode 100644 src/main.c create mode 100644 src/str.c create mode 100644 src/str.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..11f6e35 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,6 @@ +-xc +-Wall +-Wextra +-Wno-c23-extensions +-I/usr/lib/plan9/include +-DIN_EDITOR diff --git a/local.vim b/local.vim new file mode 100644 index 0000000..0147abe --- /dev/null +++ b/local.vim @@ -0,0 +1 @@ +set noexpandtab diff --git a/mkfile b/mkfile new file mode 100644 index 0000000..faa041e --- /dev/null +++ b/mkfile @@ -0,0 +1,14 @@ +[2] /dev/null} +hdr=`{ls src/*.h >[2] /dev/null} +obj=${src:src/%.c=build/obj/%.$O} + +build/drawer: $obj + $LD -o $target $prereq + +build/obj/%.$O: src/%.c $hdr mkfile structure + $CC $CFLAGS -o $target src/$stem.c + +structure:V: + mkdir -p build/obj diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..965ad17 --- /dev/null +++ b/src/main.c @@ -0,0 +1,598 @@ +#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; +} diff --git a/src/str.c b/src/str.c new file mode 100644 index 0000000..a93d129 --- /dev/null +++ b/src/str.c @@ -0,0 +1,46 @@ +#include +#include +#include "str.h" + +void str_buf_clear(Str_Buf *buf) +{ + buf->length = 0; +} + +void str_buf_delete(Str_Buf *buf) +{ + if(buf->contents) + { + free(buf->contents); + buf->contents = 0; + } + buf->length = buf->capacity = 0; +} + +void str_buf_push_char(Str_Buf *buf, char ch) +{ + if(buf->length == buf->capacity) + { + uintptr new_capacity = buf->capacity ? buf->capacity * 2 : 8; + char *new_contents = malloc(new_capacity); + memcpy(new_contents, buf->contents, buf->length); + if(buf->contents) + free(buf->contents); + buf->contents = new_contents; + buf->capacity = new_capacity; + } + buf->contents[buf->length++] = ch; +} + +void str_buf_append_cstr(Str_Buf *buf, const char *str) +{ + while(*str) + str_buf_push_char(buf, *(str++)); +} + +char *str_buf_to_cstr(Str_Buf *buf) +{ + str_buf_push_char(buf, 0); + buf->length--; + return buf->contents; +} diff --git a/src/str.h b/src/str.h new file mode 100644 index 0000000..9f6709c --- /dev/null +++ b/src/str.h @@ -0,0 +1,22 @@ +#ifndef STR_H +#define STR_H + +#ifdef IN_EDITOR +#include +#endif + +typedef struct Str_Buf Str_Buf; +struct Str_Buf +{ + char *contents; + uintptr length; + uintptr capacity; +}; + +void str_buf_clear(Str_Buf *buf); +void str_buf_delete(Str_Buf *buf); +void str_buf_push_char(Str_Buf *buf, char ch); +void str_buf_append_cstr(Str_Buf *buf, const char *str); +char *str_buf_to_cstr(Str_Buf *buf); + +#endif