ref: 091851703eba8be72f145fdf961cad0e1f250611
dir: /da.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <event.h> #include <keyboard.h> #include <bio.h> #include <ctype.h> #define CONFIG_FILE "/tmp/da.conf" #define DEFAULT_CTL_FILE "/tmp/da.ctl" #define DEFAULT_SAVE_PATH "/tmp/canvas.da" #define DEFAULT_CTL_CHECK_INTERVAL 200 #define DEFAULT_REDRAW_INTERVAL 30 #define DEFAULT_BANNER_HEIGHT 25 #define DEFAULT_STATUS_HEIGHT 25 #define DEFAULT_STATUS_MARGIN 10 #define DEFAULT_EMOJI_SPEED 3 #define DEFAULT_EMOJI_FRAME_DELAY 10 #define DEFAULT_BOXWIDTH 16 #define DEFAULT_BOXHEIGHT 16 #define DEFAULT_BIGBOXWIDTH 24 #define DEFAULT_BIGBOXHEIGHT 24 #define DEFAULT_GRIDSIZE 16 #define DEFAULT_GRIDSNAP 1 #define DEFAULT_EMOJI 1 /* const */ #define MAXBOXES 2000 enum { T_BOX = 0, T_BIGBOX, MAXBOXTYPES, }; typedef struct Config Config; struct Config { int banner_height; int status_height; int status_margin; int box_width; int box_height; int bigbox_width; int bigbox_height; int emoji_enabled; int emoji_speed; int emoji_frame_delay; int ctl_check_interval; int redraw_interval; int gridsnap; int gridsize; char ctl_file[256]; char default_save_path[256]; ulong color_bg; ulong color_fg; ulong color_selected; ulong color_grid; }; typedef enum { CFG_INT, CFG_STRING, CFG_BOOL, CFG_COLOR, } ConfigType; typedef struct ConfigField ConfigField; struct ConfigField { char *name; ConfigType type; void *ptr; int maxlen; }; typedef struct Box Box; struct Box { Point pos; Rectangle r; int type; int selected; ulong color; }; typedef struct BoxType BoxType; struct BoxType { char *name; void (*draw)(Box*, Image*); }; typedef struct Canvas Canvas; struct Canvas { Box boxes[MAXBOXES]; int nboxes; int selected; int fnkey; char fnbuf[256]; int fnpos; int save_mode; int current_mode; Point offset; int needredraw; int gridsnap; int gridsize; int emoji_pos; int emoji_frame; int emoji_dir; /* direction */ char *emoji_frames[4]; int emoji_enabled; int current_color; int next_box_type; }; typedef void (*KeyHandler)(int key); typedef void (*MouseHandler)(Mouse m); typedef struct InputMode InputMode; struct InputMode { char *name; KeyHandler handler; MouseHandler mouse_handler; void (*draw)(void); char *status; }; typedef struct Command Command; struct Command { int key; void (*action)(void); }; typedef struct DrawStep DrawStep; struct DrawStep { void (*draw)(void); int condition; }; typedef struct CtlHandler CtlHandler; struct CtlHandler { char *name; int minargs; void (*execute)(char **args, int nargs); }; Image *colors[16]; Image *boxselected; Image *gridcolor; Image *cfg; Image *cbg; Config config; Canvas canvas; void save_da(char *file); void load_da(char *file); void redraw(void); void load_config(char *path); void save_config(char *path); void apply_config(void); void norm_key(int key); void norm_mouse(Mouse m); void file_key(int key); void fn_mouse(Mouse m); void handlekey(int key); void handlemouse(Mouse m); void cleanup(void); void draw_box(Box*, Image*); void draw_bigbox(Box*, Image*); void draw_nol(void); void draw_fnol(void); void draw_bg(void); void draw_grid(void); void draw_all(void); void draw_emoji_banner(void); void draw_status(void); void cmd_set_color(int idx); void cmd_quit(void); void cmd_save(void); void cmd_save_as(void); void cmd_open(void); void cmd_open_file(void); void cmd_toggle_grid(void); void cmd_toggle_boxtype(void); void cmd_toggle_emoji(void); void cmd_delete_box(void); void cmd_cycle_emoji(void); void cmd_reload_config(void); void ctl_addbox(char**, int); void ctl_load(char**, int); void ctl_save(char**, int); void ctl_quit(char**, int); void init_config_defaults(void); void init_emoji(void); void initcolors(void); void validate_config(void); void apply_config(void); static ulong palette[] = { 0x2ECC71FF, 0x34495EFF, 0x95A5A6FF, 0xE67E22FF, 0xF39C12FF, 0x3498DBFF, 0x7F8C8DFF, 0x1ABC9CFF, 0xE74C3CFF, 0x9B59B6FF, 0xF1C40FFF, 0xECF0F1FF, 0x2C3E50FF, 0x16A085FF, 0xD35400FF, 0x27AE60FF }; ConfigField config_fields[] = { {"banner_height", CFG_INT, &config.banner_height, 0}, {"status_height", CFG_INT, &config.status_height, 0}, {"status_margin", CFG_INT, &config.status_margin, 0}, {"box_width", CFG_INT, &config.box_width, 0}, {"box_height", CFG_INT, &config.box_height, 0}, {"bigbox_width", CFG_INT, &config.bigbox_width, 0}, {"bigbox_height", CFG_INT, &config.bigbox_height, 0}, {"emoji_enabled", CFG_BOOL, &config.emoji_enabled, 0}, {"emoji_speed", CFG_INT, &config.emoji_speed, 0}, {"emoji_frame_delay", CFG_INT, &config.emoji_frame_delay, 0}, {"ctl_check_interval", CFG_INT, &config.ctl_check_interval, 0}, {"redraw_interval", CFG_INT, &config.redraw_interval, 0}, {"gridsize", CFG_INT, &config.gridsize, 0}, {"gridsnap", CFG_BOOL, &config.gridsnap, 0}, {"ctl_file", CFG_STRING, config.ctl_file, 256}, {"default_save", CFG_STRING, config.default_save_path, 256}, {"color_bg", CFG_COLOR, &config.color_bg, 0}, {"color_fg", CFG_COLOR, &config.color_fg, 0}, {"color_selected", CFG_COLOR, &config.color_selected, 0}, {"color_grid", CFG_COLOR, &config.color_grid, 0}, {nil, 0, nil, 0} /* sentinel */ }; BoxType boxtypes[] = { [T_BOX] = {"box", draw_box}, [T_BIGBOX] = {"bigbox", draw_bigbox}, }; InputMode input_modes[] = { [0] = { "normal", norm_key, norm_mouse, draw_nol, "0-F: Colors H:Emoji h:cycle S:ave O:pen g:rid r:eload-cfg " }, [1] = { "filename", file_key, fn_mouse, draw_fnol, "Tab:/tmp/ Enter:confirm Esc:cancel" }, }; Command commands[] = { {Kdel, cmd_quit}, {'s', cmd_save}, {'S', cmd_save_as}, {'o', cmd_open}, {'O', cmd_open_file}, {'g', cmd_toggle_grid}, {'d', cmd_delete_box}, {'H', cmd_toggle_emoji}, {'h', cmd_cycle_emoji}, {'r', cmd_reload_config}, {'t', cmd_toggle_boxtype}, {0, nil} }; DrawStep draw_steps[] = { {draw_bg, 0}, {draw_grid, 1}, {draw_all, 0}, {draw_status, 0}, {nil, 0} }; CtlHandler ctl_handlers[] = { {"addbox", 2, ctl_addbox}, {"load", 1, ctl_load}, {"save", 1, ctl_save}, {"quit", 0, ctl_quit}, {nil, 0, nil} }; char *happy_faces[] = { "^_^", "^o^", "^_^", "^-^" }; char *dancing_guy[] = { "\\o/", "_o_", "/o\\", "_o_" }; char *lenny[] = { "( *_*)", "( o_o)", "( ^_^)", "( >_>)" }; void save_da(char *file) { int fd; Biobuf *b; int i; Box *box; fd = create(file, OWRITE, 0644); if(fd < 0){ fprint(2, "cannot create %s: %r\n", file); return; } b = Bfdopen(fd, OWRITE); for(i = 0; i < canvas.nboxes; i++){ box = &canvas.boxes[i]; Bprint(b, "box %d\n", i); Bprint(b, " pos %d %d\n", box->pos.x, box->pos.y); Bprint(b, " type %d\n", box->type); Bprint(b, " color %08lux\n", box->color); } Bterm(b); close(fd); } void load_da(char *file) { Biobuf *b; char *line; char *fields[10]; int nf; b = Bopen(file, OREAD); if(b == nil){ fprint(2, "cannot open %s: %r\n", file); return; } memset(&canvas, 0, sizeof(canvas)); canvas.selected = -1; canvas.fnkey = 0; canvas.current_mode = 0; canvas.current_color = 8; canvas.gridsize = config.gridsize; canvas.gridsnap = config.gridsnap; init_emoji(); while((line = Brdline(b, '\n')) != nil){ line[Blinelen(b)-1] = '\0'; if(line[0] == '#' || line[0] == '\0') continue; nf = tokenize(line, fields, nelem(fields)); if(nf >= 2 && strcmp(fields[0], "box") == 0){ if(canvas.nboxes < MAXBOXES){ Box *box = &canvas.boxes[canvas.nboxes]; memset(box, 0, sizeof(Box)); box->r = Rect(0, 0, config.box_width, config.box_height); canvas.nboxes++; } } else if(nf >= 3 && strcmp(fields[0], "pos") == 0){ Box *box = &canvas.boxes[canvas.nboxes-1]; box->pos.x = atoi(fields[1]); box->pos.y = atoi(fields[2]); box->r = Rect(box->pos.x, box->pos.y, box->pos.x + config.box_width, box->pos.y + config.box_height); } else if(nf >= 2 && strcmp(fields[0], "type") == 0){ int type = atoi(fields[1]); if (type >= 0 && type < MAXBOXTYPES) { Box *box = &canvas.boxes[canvas.nboxes-1]; box->type = type; if(type == T_BIGBOX){ box->r = Rect(box->pos.x, box->pos.y, box->pos.x + config.bigbox_width, box->pos.y + config.bigbox_height); } } } else if(nf >= 2 && strcmp(fields[0], "color") == 0){ canvas.boxes[canvas.nboxes-1].color = strtoul(fields[1], nil, 16); } } Bterm(b); redraw(); } void save_config(char *path) { int fd; Biobuf *b; fd = create(path, OWRITE, 0644); if(fd < 0) { fprint(2, "cannot create config %s: %r\n", path); return; } b = Bfdopen(fd, OWRITE); Bprint(b, "# auto-gen da.conf $\n"); Bprint(b, "banner_height %d\n", config.banner_height); Bprint(b, "status_height %d\n", config.status_height); Bprint(b, "status_margin %d\n", config.status_margin); Bprint(b, "box_width %d\n", config.box_width); Bprint(b, "box_height %d\n", config.box_height); Bprint(b, "bigbox_width %d\n", config.bigbox_width); Bprint(b, "bigbox_height %d\n", config.bigbox_height); Bprint(b, "emoji_enabled %d\n", config.emoji_enabled); Bprint(b, "emoji_speed %d\n", config.emoji_speed); Bprint(b, "emoji_frame_delay %d\n", config.emoji_frame_delay); Bprint(b, "ctl_check_interval %d\n", config.ctl_check_interval); Bprint(b, "redraw_interval %d\n", config.redraw_interval); Bprint(b, "gridsnap %d\n", config.gridsnap); Bprint(b, "gridsize %d\n", config.gridsize); Bprint(b, "ctl_file %s\n", config.ctl_file); Bprint(b, "default_save %s\n", config.default_save_path); Bprint(b, "color_bg %08lux\n", config.color_bg); Bprint(b, "color_fg %08lux\n", config.color_fg); Bprint(b, "color_selected %08lux\n", config.color_selected); Bprint(b, "color_grid %08lux\n", config.color_grid); Bterm(b); close(fd); } void init_config_defaults(void) { config.banner_height = DEFAULT_BANNER_HEIGHT; config.status_height = DEFAULT_STATUS_HEIGHT; config.status_margin = DEFAULT_STATUS_MARGIN; config.box_width = DEFAULT_BOXWIDTH; config.box_height = DEFAULT_BOXHEIGHT; config.bigbox_width = DEFAULT_BIGBOXWIDTH; config.bigbox_height = DEFAULT_BIGBOXHEIGHT; config.emoji_enabled = DEFAULT_EMOJI; config.emoji_speed = DEFAULT_EMOJI_SPEED; config.emoji_frame_delay = DEFAULT_EMOJI_FRAME_DELAY; config.ctl_check_interval = DEFAULT_CTL_CHECK_INTERVAL; config.redraw_interval = DEFAULT_REDRAW_INTERVAL; config.gridsnap = DEFAULT_GRIDSNAP; config.gridsize = DEFAULT_GRIDSIZE; config.color_bg = 0xEEEEEEFF; config.color_fg = 0x000000FF; config.color_selected = 0x4444FFFF; config.color_grid = 0xCCCCCCFF; strcpy(config.ctl_file, DEFAULT_CTL_FILE); strcpy(config.default_save_path, DEFAULT_SAVE_PATH); } void load_config(char *path) { Biobuf *b; char *line; char *fields[3]; int nf; ConfigField *cf; init_config_defaults(); b = Bopen(path, OREAD); if(b == nil) return; while((line = Brdline(b, '\n')) != nil) { line[Blinelen(b)-1] = '\0'; if(line[0] == '#' || line[0] == '\0') continue; nf = tokenize(line, fields, nelem(fields)); if(nf < 2) continue; for(cf = config_fields; cf->name != nil; cf++) { if(strcmp(fields[0], cf->name) == 0) { /* Process based on type */ switch(cf->type) { case CFG_INT: *(int*)cf->ptr = atoi(fields[1]); break; case CFG_BOOL: *(int*)cf->ptr = atoi(fields[1]) ? 1 : 0; break; case CFG_STRING: strncpy((char*)cf->ptr, fields[1], cf->maxlen - 1); ((char*)cf->ptr)[cf->maxlen - 1] = '\0'; break; case CFG_COLOR: *(ulong*)cf->ptr = strtoul(fields[1], nil, 16); break; } break; /* Found, next line */ } } if(cf->name == nil) { fprint(2, "Warning: unknown config field '%s'\n", fields[0]); } } Bterm(b); } void validate_config(void) { if(config.banner_height < 0) config.banner_height = 0; if(config.banner_height > 100) config.banner_height = 100; if(config.status_height < 0) config.status_height = 0; if(config.status_height > 100) config.status_height = 100; if(config.status_margin < 0) config.status_margin = 0; if(config.status_margin > 50) config.status_margin = 50; if(config.box_width < 1) config.box_width = 1; if(config.box_width > 512) config.box_width = 512; if(config.box_height < 1) config.box_height = 1; if(config.box_height > 512) config.box_height = 512; if(config.bigbox_width < 2) config.bigbox_width = 2; if(config.bigbox_width > 1024) config.bigbox_width = 1024; if(config.bigbox_height < 2) config.bigbox_height = 2; if(config.bigbox_height > 1024) config.bigbox_height = 1024; if(config.gridsize < 1) config.gridsize = 1; if(config.gridsize > 512) config.gridsize = 512; if(config.emoji_speed < 1) config.emoji_speed = 1; if(config.emoji_speed > 20) config.emoji_speed = 20; if(config.emoji_frame_delay < 1) config.emoji_frame_delay = 1; if(config.emoji_frame_delay > 100) config.emoji_frame_delay = 100; if(config.ctl_check_interval < 10) config.ctl_check_interval = 10; if(config.ctl_check_interval > 1000) config.ctl_check_interval = 1000; if(config.redraw_interval < 10) config.redraw_interval = 10; if(config.redraw_interval > 1000) config.redraw_interval = 1000; if(config.ctl_file[0] == '\0') strcpy(config.ctl_file, DEFAULT_CTL_FILE); if(config.default_save_path[0] == '\0') strcpy(config.default_save_path, DEFAULT_SAVE_PATH); config.emoji_enabled = config.emoji_enabled ? 1 : 0; config.gridsnap = config.gridsnap ? 1 : 0; /* Note: Color values don't need validation as they're ulong hex values */ /* Any value is technically valid for a color */ } void apply_config(void) { canvas.emoji_enabled = config.emoji_enabled; canvas.gridsnap = config.gridsnap; canvas.gridsize = config.gridsize; if(cfg) { freeimage(cfg); cfg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_fg); } if(cbg) { freeimage(cbg); cbg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_bg); } if(boxselected) { freeimage(boxselected); boxselected = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_selected); } if(gridcolor) { freeimage(gridcolor); gridcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_grid); } } void init_emoji(void) { canvas.emoji_pos = 0; canvas.emoji_frame = 0; canvas.emoji_dir = 1; canvas.emoji_enabled = config.emoji_enabled; int i; for(i = 0; i < 4; i++) canvas.emoji_frames[i] = lenny[i]; } void initcolors(void) { int i; for(i = 0; i < 16; i++) { colors[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, palette[i]); if(!colors[i]) sysfatal("allocimage failed"); } cbg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_bg); boxselected = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_selected); cfg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_fg); gridcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_grid); } void cleanup(void) { int i; for(i = 0; i < 16; i++) { if(colors[i]) freeimage(colors[i]); } } void cmd_set_color(int idx) { if(idx >= 0 && idx < 16) { canvas.current_color = idx; if(canvas.selected >= 0 && canvas.selected < canvas.nboxes) { canvas.boxes[canvas.selected].color = palette[idx]; } canvas.needredraw = 1; } } void cmd_toggle_boxtype(void) { canvas.next_box_type = !canvas.next_box_type; canvas.needredraw = 1; } int boxat(Point p) { int i; Box *b; for(i = canvas.nboxes - 1; i >= 0; i--){ b = &canvas.boxes[i]; if(ptinrect(p, b->r)) return i; } return -1; } int addbox(Point p) { Box *b; if(canvas.nboxes >= MAXBOXES) return -1; b = &canvas.boxes[canvas.nboxes]; memset(b, 0, sizeof(Box)); if(canvas.gridsnap){ p.x = (p.x / canvas.gridsize) * canvas.gridsize; p.y = (p.y / canvas.gridsize) * canvas.gridsize; } b->pos = p; b->type = canvas.next_box_type; b->color = palette[canvas.current_color]; if(b->type == T_BIGBOX) { b->r = Rect(p.x, p.y, p.x + config.bigbox_width, p.y + config.bigbox_height); } else { b->r = Rect(p.x, p.y, p.x + config.box_width, p.y + config.box_height); } return canvas.nboxes++; } void delbox(int i) { if(i < 0 || i >= canvas.nboxes) return; memmove(&canvas.boxes[i], &canvas.boxes[i+1], (canvas.nboxes - i - 1) * sizeof(Box)); canvas.nboxes--; } void draw_box(Box *b, Image *dst) { Image *bg = colors[8]; // default color if(b->color != 0) { int i; for(i = 0; i < 16; i++) { if(palette[i] == b->color) { bg = colors[i]; break; } } } if(b->selected) bg = boxselected; draw(dst, b->r, bg, nil, ZP); } void draw_bigbox(Box *b, Image *dst) { Image *bg = colors[4]; if(b->color != 0) { int i; for(i = 0; i < 16; i++) { if(palette[i] == b->color) { bg = colors[i]; break; } } } if(b->selected) bg = boxselected; draw(dst, b->r, bg, nil, ZP); } void drawgrid(Image *dst) { int x, y; Rectangle r = screen->r; int startx = (r.min.x / canvas.gridsize) * canvas.gridsize; int starty = (r.min.y / canvas.gridsize) * canvas.gridsize; for(x = startx; x <= r.max.x; x += canvas.gridsize){ line(dst, Pt(x, r.min.y), Pt(x, r.max.y), 0, 0, 0, gridcolor, ZP); } for(y = starty; y <= r.max.y; y += canvas.gridsize){ line(dst, Pt(r.min.x, y), Pt(r.max.x, y), 0, 0, 0, gridcolor, ZP); } } void draw_nol(void) { } void draw_fnol(void) { Rectangle r; int w = 400; int h = 64; Point center = Pt(screen->r.min.x + Dx(screen->r)/2, screen->r.min.y + Dy(screen->r)/2); r = Rect(center.x - w/2, center.y - h/2, center.x + w/2, center.y + h/2); draw(screen, r,cbg, nil, ZP); border(screen, r, 2, cfg, ZP); char *title = canvas.save_mode == 1 ? "Save As:" : "Open File:"; string(screen, Pt(r.min.x + 10, r.min.y + 5), cfg, ZP, font, title); char display[256]; snprint(display, sizeof(display), "%s_", canvas.fnbuf); string(screen, Pt(r.min.x + 10, r.min.y + 20), cfg, ZP, font, display); } void draw_bg(void) { draw(screen, screen->r, cbg, nil, ZP); } void draw_grid(void) { if(!canvas.gridsnap) return; drawgrid(screen); } void draw_all(void) { int i; for(i = 0; i < canvas.nboxes; i++) { Box *b = &canvas.boxes[i]; if (b->type >= 0 && b->type < MAXBOXTYPES) { BoxType *bt = &boxtypes[b->type]; if (bt->draw) { bt->draw(b, screen); } } } } void draw_status(void) { char buf[256]; InputMode *mode = &input_modes[canvas.current_mode]; snprint(buf, sizeof(buf), "Color: %d | Selected: %d | Mode: %s | Boxes: %d | %s", canvas.current_color, canvas.selected, mode->name, canvas.nboxes, mode->status); string(screen, Pt(screen->r.min.x + config.status_margin, screen->r.max.y - config.status_height), cfg, ZP, font, buf); } void draw_emoji_banner(void) { if(!canvas.emoji_enabled) return; if(canvas.emoji_frame < 0 || canvas.emoji_frame >= 4) canvas.emoji_frame = 0; char *emoji = canvas.emoji_frames[canvas.emoji_frame]; if(emoji == nil) return; int emoji_width = strlen(emoji) * font->width; Rectangle banner = Rect(screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.min.y + config.banner_height); draw(screen, banner, cbg, nil, ZP); Point pos = Pt(canvas.emoji_pos, screen->r.min.y + 5); string(screen, pos, cfg, ZP, font, emoji); canvas.emoji_pos += canvas.emoji_dir * config.emoji_speed; if(canvas.emoji_pos > screen->r.max.x - emoji_width) { canvas.emoji_pos = screen->r.max.x - emoji_width; canvas.emoji_dir = -1; } if(canvas.emoji_pos < screen->r.min.x) { canvas.emoji_pos = screen->r.min.x; canvas.emoji_dir = 1; } static int frame_counter = 0; frame_counter++; if(frame_counter % config.emoji_frame_delay == 0) { canvas.emoji_frame = (canvas.emoji_frame + 1) % 4; } } void redraw(void) { DrawStep *step; draw_emoji_banner(); Rectangle clip = screen->r; clip.min.y += config.banner_height; replclipr(screen, 0, clip); for(step = draw_steps; step->draw; step++) { if(step->condition == 0 || (step->condition == 1 && canvas.gridsnap)) { step->draw(); } } replclipr(screen, 0, screen->r); InputMode *mode = &input_modes[canvas.current_mode]; if(mode->draw) mode->draw(); flushimage(display, 1); } void cmd_reload_config(void) { load_config(CONFIG_FILE); apply_config(); validate_config(); canvas.needredraw = 1; fprint(2, "Configuration reloaded from %s\n", CONFIG_FILE); } void cmd_cycle_emoji(void) { static int emoji_set = 0; int i; emoji_set = (emoji_set + 1) % 3; /* 3 total emoji sets */ switch(emoji_set) { case 0: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = lenny[i]; break; case 1: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = dancing_guy[i]; break; case 2: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = happy_faces[i]; break; } canvas.needredraw = 1; } void cmd_quit(void) { cleanup(); exits(nil); } void cmd_save(void) { canvas.current_mode = 1; canvas.fnkey = 1; canvas.save_mode = 1; strcpy(canvas.fnbuf, config.default_save_path); canvas.fnpos = strlen(canvas.fnbuf); canvas.needredraw = 1; } void cmd_save_as(void) { canvas.current_mode = 1; canvas.fnkey = 1; canvas.save_mode = 1; canvas.fnbuf[0] = '\0'; canvas.fnpos = 0; canvas.needredraw = 1; } void cmd_open(void) { canvas.current_mode = 1; canvas.fnkey = 1; canvas.save_mode = 2; strcpy(canvas.fnbuf, config.default_save_path); canvas.fnpos = strlen(canvas.fnbuf); canvas.needredraw = 1; } void cmd_open_file(void) { canvas.current_mode = 1; canvas.fnkey = 1; canvas.save_mode = 2; canvas.fnbuf[0] = '\0'; canvas.fnpos = 0; canvas.needredraw = 1; } void cmd_toggle_grid(void) { canvas.gridsnap = !canvas.gridsnap; config.gridsnap = canvas.gridsnap; canvas.needredraw = 1; } void cmd_delete_box(void) { if(canvas.selected >= 0) { delbox(canvas.selected); canvas.selected = -1; canvas.needredraw = 1; } } void cmd_toggle_emoji(void) { canvas.emoji_enabled = !canvas.emoji_enabled; config.emoji_enabled = canvas.emoji_enabled; canvas.needredraw = 1; } void handlekey(int key) { InputMode *mode = &input_modes[canvas.current_mode]; if(mode->handler) mode->handler(key); } void handlemouse(Mouse m) { InputMode *mode = &input_modes[canvas.current_mode]; if(mode->mouse_handler) mode->mouse_handler(m); } void norm_key(int key) { Command *cmd; if(key >= '0' && key <= '9') { cmd_set_color(key - '0'); return; } if(key >= 'a' && key <= 'f') { cmd_set_color(10 + (key - 'a')); return; } for(cmd = commands; cmd->action; cmd++) { if(cmd->key == key) { cmd->action(); return; } } } void file_key(int key) { if(key == '\n') { if(canvas.fnbuf[0] == '\0') { strcpy(canvas.fnbuf, config.default_save_path); } if(canvas.save_mode == 1) { save_da(canvas.fnbuf); } else { load_da(canvas.fnbuf); } canvas.fnkey = 0; canvas.current_mode = 0; canvas.needredraw = 1; } else if(key == Kesc) { canvas.fnkey = 0; canvas.current_mode = 0; canvas.needredraw = 1; } else if(key == '\t') { if(!strstr(canvas.fnbuf, "/tmp/")) { strcat(canvas.fnbuf, "/tmp/"); canvas.fnpos = strlen(canvas.fnbuf); canvas.needredraw = 1; } } else if(key == Kbs) { if(canvas.fnpos > 0) { canvas.fnpos--; canvas.fnbuf[canvas.fnpos] = '\0'; canvas.needredraw = 1; } } else if(key >= 32 && key < 127 && canvas.fnpos < 250) { canvas.fnbuf[canvas.fnpos++] = key; canvas.fnbuf[canvas.fnpos] = '\0'; canvas.needredraw = 1; } } void norm_mouse(Mouse m) { int i; if(m.buttons & 1){ i = addbox(m.xy); if(i >= 0){ canvas.selected = i; canvas.needredraw = 1; } } if(m.buttons & 2){ i = boxat(m.xy); if(i >= 0){ canvas.selected = i; Box *box = &canvas.boxes[i]; int width = (box->type == T_BIGBOX) ? config.bigbox_width : config.box_width; int height = (box->type == T_BIGBOX) ? config.bigbox_height : config.box_height; while(m.buttons & 2){ box->pos = subpt(m.xy, Pt(width/2, height/2)); if(canvas.gridsnap){ box->pos.x = (box->pos.x / canvas.gridsize) * canvas.gridsize; box->pos.y = (box->pos.y / canvas.gridsize) * canvas.gridsize; } box->r = Rect(box->pos.x, box->pos.y, box->pos.x + width, box->pos.y + height); redraw(); m = emouse(); } canvas.needredraw = 1; } } if(m.buttons & 4){ i = boxat(m.xy); if(i >= 0){ delbox(i); if(canvas.selected == i) canvas.selected = -1; canvas.needredraw = 1; } } } void fn_mouse(Mouse m) { USED(m); } void ctl_addbox(char **args, int nargs) { Point p = Pt(atoi(args[0]), atoi(args[1])); int idx = addbox(p); canvas.needredraw = 1; } void ctl_load(char **args, int nargs) { if(nargs >= 1) { load_da(args[0]); canvas.needredraw = 1; } } void ctl_save(char **args, int nargs) { if(nargs >= 1) { save_da(args[0]); } } void ctl_quit(char **args, int nargs) { USED(args); USED(nargs); cmd_quit(); } void check_ctl_file(void) { int fd, nt; fd = open(config.ctl_file, OREAD); if(fd < 0) { return; } Biobuf bin; Binit(&bin, fd, OREAD); char *line; while((line = Brdline(&bin, '\n'))) { line[Blinelen(&bin)-1] = '\0'; char *tokens[10]; nt = tokenize(line, tokens, nelem(tokens)); if(nt == 0) continue; CtlHandler *h; for(h = ctl_handlers; h->name; h++) { if(strcmp(tokens[0], h->name) == 0) { if(nt-1 < h->minargs) { fprint(2, "%s: needs %d arguments\n", h->name, h->minargs); } else { h->execute(&tokens[1], nt-1); } break; } } } Bterm(&bin); close(fd); fd = create(config.ctl_file, OWRITE, 0644); if(fd >= 0) close(fd); } void eresized(int new) { if(new && getwindow(display, Refnone) < 0) sysfatal("can't reattach to window"); } void main(int argc, char *argv[]) { Event e; char *configfile = CONFIG_FILE; if(argc > 1) configfile = argv[1]; load_config(configfile); validate_config(); if(access(configfile, AEXIST) < 0) { save_config(configfile); fprint(2, "Created default configuration at %s\n", configfile); } if(initdraw(nil, nil, "dynamica") < 0) sysfatal("initdraw: %r"); initcolors(); int ticks = 0; einit(Emouse | Ekeyboard); eresized(0); int ctlfd = create(config.ctl_file, OWRITE, 0644); if(ctlfd >= 0) close(ctlfd); memset(&canvas, 0, sizeof(canvas)); canvas.selected = -1; canvas.fnkey = 0; canvas.current_mode = 0; canvas.current_color = 8; canvas.next_box_type = T_BOX; canvas.gridsize = config.gridsize; canvas.gridsnap = config.gridsnap; init_emoji(); redraw(); for(;;){ switch(event(&e)){ case Emouse: handlemouse(e.mouse); break; case Ekeyboard: handlekey(e.kbdc); break; } ticks++; if(ticks % config.ctl_check_interval == 0) { check_ctl_file(); } if(ticks % config.redraw_interval == 0) { canvas.needredraw = 1; } if(canvas.needredraw){ redraw(); canvas.needredraw = 0; } } }