This commit is contained in:
2025-11-14 22:28:31 +02:00
commit 8d2698ae02
7 changed files with 688 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

6
compile_flags.txt Normal file
View File

@@ -0,0 +1,6 @@
-xc
-Wall
-Wextra
-Wno-c23-extensions
-I/usr/lib/plan9/include
-DIN_EDITOR

1
local.vim Normal file
View File

@@ -0,0 +1 @@
set noexpandtab

14
mkfile Normal file
View File

@@ -0,0 +1,14 @@
</$objtype/mkfile
src=`{ls src/*.c >[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

598
src/main.c Normal file
View File

@@ -0,0 +1,598 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <draw.h>
#include <event.h>
#include <cursor.h>
#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;
}

46
src/str.c Normal file
View File

@@ -0,0 +1,46 @@
#include <u.h>
#include <libc.h>
#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;
}

22
src/str.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef STR_H
#define STR_H
#ifdef IN_EDITOR
#include <u.h>
#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