ref: 02c13a3ce3bcf6e19dcf794c4efa57d4056b2d4c
dir: /da.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <event.h> #include <keyboard.h> #include <bio.h> #define CONFIG_FILE "/usr/glenda/da/da.conf" #define DEFAULT_CTL_FILE "/usr/glenda/da/da.ctl" #define DEFAULT_SAVE_PATH "/usr/glenda/da/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 9000 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 { Rectangle r[MAXBOXES]; int color_idx[MAXBOXES]; Point pos[MAXBOXES]; int type[MAXBOXES]; int selected[MAXBOXES]; }; typedef struct BoxType BoxType; struct BoxType { char *name; void (*draw)(int idx, Image*); int default_color; }; typedef struct Canvas Canvas; struct Canvas { Box boxes; 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 enterfnm(int sm, int dp); 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(int idx, Image *dst); 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); int addbox(Point p); void delbox(int i); int boxat(Point p); 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, 9}, [T_BIGBOX] = {"bigbox", draw_box, 7}, }; 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:/usr/glenda/da/ 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 *kirby_dance[] = { "<('.')>", "<('.')<", "^('.')^", "v('.')v" }; char *lambda_dance[] = { "L(^_^)L", "L(>_<)L", "L(o_o)L", "L(*_*)L" }; char *rcc_style[] = { "(-(-_-(-_(-_-)_-)_-)-)", "[~o-o]~", "~(o_o)~", "*(^o^)/*" }; char *cat_faces[] = { "=^.^=", "=^.o=", "=o.^=", "=o.o=" }; char *shrug_guys[] = { "~\\_('.')_/~", "~\\_(o.o)_/~", "~\\_(-.-)_/~", "~\\_(^.^)_/~" }; char *classic_emotions[] = { ":-)", ":-D", ";-)", ":-P" }; char *bear_moods[] = { "(o.o)", "(^.^)", "(*.*)", "(-.-)zzz" }; char *bunny_hop[] = { "(\\(\\", "( -.-)(", "o(\")(\")o", "(\")_(\")" }; char *robot_faces[] = { "[o_o]", "[O_O]", "[^_^]", "[x_x]" }; char *owl_looks[] = { "(O,O)", "(o,o)", "(0,0)", "(@,@)" }; char *fish_swim[] = { "><>", "<><", "><(((o>", "<o)))><" }; char *music_notes[] = { "d(^_^)b", "q(^_^)p", "b(^_^)d", "p(^_^)q" }; char *wizard_cast[] = { "(o)===*", "*(===o)", "~(o)~===*", "*===~(o)~" }; char *table_flip[] = { "(o_o)", "(o_O)", "(O_O)", "(x_x)===___" }; char *love_hearts[] = { "<3", "</3", "<3<3<3", "~<3~" }; char *stick_figure[] = { "o/", "\\o", "|o|", "_o_" }; char *cool_shades[] = { "B-)", "8-)", "B-D", "8-D" }; char *space_invaders[] = { "/o\\", "\\o/", "|o|", "-o-" }; char *bat_fly[] = { "^v^", "vVv", "^V^", "VvV" }; char *spider_crawl[] = { "//\\\\(oo)//\\\\", "//\\\\( )//\\\\", "\\\\//(..)\\\\//", "\\\\//()\\\\//", }; char *boxing[] = { "(o_o)-Q", "Q-(o_o)", "Q-(>_<)-Q", "(^o^)9" }; char *flex_arms[] = { "o==[]::::::>", "@==[]::::::>", "O==[]::::::>", "*==[]::::::>" }; char *crab_walk[] = { "V(o_o)V", "V(._.)V", "V(^_^)V", "V(*_*)V" }; char *penguin[] = { "<(^_^)>", "<('_')>", "<(o_o)>", "<(._.)>" }; char *excited[] = { "\\(^o^)/", "~(^o^)~", "*(^o^)*", "/(^o^)\\" }; char *ghosts[] = { "~(o.o)~", "~(O.O)~", "~(x.x)~", "~(-.-)~" }; char *zombies[] = { "[x_x]", "[X_X]", "]x_x[", "]X_X[" }; char *snails[] = { "@__)", "(__@", "@__]", "[__@" }; char *ufo[] = { "<o>", "=<o>=", "==<o>==", "=<o>=" }; char *rockets[] = { "=>", "==>", "===>", "===>" }; char *weather[] = { "* * *", "' ' '", ". . .", "~ ~ ~" }; char *math_dance[] = { "+(^_^)+", "-(^_^)-", "*(^_^)*", "/(^_^)/" }; char *pacman[] = { "C", "c", "<", "(" }; char *dice[] = { "[1]", "[2]", "[3]", "[4]" }; char *stars[] = { "*", "+", "x", "." }; char *arrows[] = { "->", "-->", "--->", "---->" }; char *code_brackets[] = { "{^_^}", "[^_^]", "(^_^)", "<^_^>" }; char *swords[] = { "o==|::::>", "<::::|==o", "o==|::::>", "<::::|==o" }; char *dragon[] = { ">=<>", ">==<>", ">===<>", ">====<>" }; char *butterfly[] = { "}i{", "}I{", "}!{", "}|{" }; char *skulls[] = { "8-X", "8-(", "X-X", "x-x" }; char *money[] = { "[$]", "[$$]", "[$$$]", "[$$$$]" }; char *flower[] = { "@}->--", "@}>---", "@}>----", "@}>-----" }; char *chess[] = { "|\\*", "|\\#", "|\\+", "|\\@" }; char *lenny[] = { "( *_*)", "( o_o)", "( ^_^)", "( >_>)" }; char *minimal[] = { "._.", "o_o", "^_^", "-_-" }; char *surprised[] = { "o.o", "O.O", "0.0", "@.@" }; char *winking[] = { ";)", ";]", ";D", ";P" }; char *nyan[] = { "=^.^=", "=^.o=", "=o.^=", "=o.o=" }; char *keyboard_smash[] = { "asdf", "jkl;", "qwer", "zxcv" }; char *binary[] = { "0101", "1010", "1100", "0011" }; char *waves[] = { "~~~~~", "^^^^^", "vvvvv", "~v~v~" }; void save_da(char *file) { int fd, i; Biobuf *b; fd = create(file, OWRITE, 0644); if(fd < 0){ fprint(2, "cannot create %s: %r\n", file); return; } b = Bfdopen(fd, OWRITE); // New format - write arrays in bulk Bprint(b, "count %d\n", canvas.nboxes); // Write all data in array chunks - much more efficient! // Positions array Bprint(b, "positions\n"); for(i = 0; i < canvas.nboxes; i++){ Bprint(b, "%d %d\n", canvas.boxes.pos[i].x, canvas.boxes.pos[i].y); } // Types array Bprint(b, "types\n"); for(i = 0; i < canvas.nboxes; i++){ Bprint(b, "%d\n", canvas.boxes.type[i]); } // Colors array Bprint(b, "colors\n"); for(i = 0; i < canvas.nboxes; i++){ Bprint(b, "%d\n", canvas.boxes.color_idx[i]); } Bterm(b); close(fd); } void load_da(char *file) { Biobuf *b; char *line; char *fields[10]; int nf, i; enum { NONE, POSITIONS, TYPES, COLORS } section = NONE; int idx = 0; int count = 0; b = Bopen(file, OREAD); if(b == nil){ fprint(2, "cannot open %s: %r\n", file); return; } // Clear canvas memset(&canvas.boxes, 0, sizeof(canvas.boxes)); canvas.nboxes = 0; canvas.selected = -1; canvas.current_color = 8; 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 == 0) continue; // Check for section headers if(strcmp(fields[0], "count") == 0 && nf >= 2){ count = atoi(fields[1]); if(count > MAXBOXES) count = MAXBOXES; } else if(strcmp(fields[0], "positions") == 0){ section = POSITIONS; idx = 0; } else if(strcmp(fields[0], "types") == 0){ section = TYPES; idx = 0; } else if(strcmp(fields[0], "colors") == 0){ section = COLORS; idx = 0; } else { // Parse data based on current section switch(section){ case POSITIONS: if(nf >= 2 && idx < count){ canvas.boxes.pos[idx] = Pt(atoi(fields[0]), atoi(fields[1])); idx++; } break; case TYPES: if(nf >= 1 && idx < count){ canvas.boxes.type[idx] = atoi(fields[0]); idx++; } break; case COLORS: if(nf >= 1 && idx < count){ canvas.boxes.color_idx[idx] = atoi(fields[0]); idx++; } break; } } } canvas.nboxes = count; // Reconstruct rectangles from positions and types for(i = 0; i < canvas.nboxes; i++){ Point p = canvas.boxes.pos[i]; if(canvas.boxes.type[i] == T_BIGBOX){ canvas.boxes.r[i] = Rect(p.x, p.y, p.x + config.bigbox_width, p.y + config.bigbox_height); } else { canvas.boxes.r[i] = Rect(p.x, p.y, p.x + config.box_width, p.y + config.box_height); } canvas.boxes.selected[i] = 0; // Clear selection on load } 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.color_idx[canvas.selected] = 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; for(i = canvas.nboxes - 1; i >= 0; i--){ if(ptinrect(p, canvas.boxes.r[i])) return i; } return -1; } int addbox(Point p) { int idx; if(canvas.nboxes >= MAXBOXES) return -1; idx = canvas.nboxes; if(canvas.gridsnap) { p.x = (p.x / canvas.gridsize) * canvas.gridsize; p.y = (p.y / canvas.gridsize) * canvas.gridsize; } canvas.boxes.pos[idx] = p; canvas.boxes.type[idx] = canvas.next_box_type; canvas.boxes.color_idx[idx] = canvas.current_color; canvas.boxes.selected[idx] = 0; if(canvas.boxes.type[idx] == T_BIGBOX) { canvas.boxes.r[idx] = Rect(p.x, p.y, p.x + config.bigbox_width, p.y + config.bigbox_height); } else { canvas.boxes.r[idx] = Rect(p.x, p.y, p.x + config.box_width, p.y + config.box_height); } return canvas.nboxes++; } void delbox(int i) { int last; if(i < 0 || i >= canvas.nboxes) return; last = canvas.nboxes - 1; if(i != last) { canvas.boxes.pos[i] = canvas.boxes.pos[last]; canvas.boxes.r[i] = canvas.boxes.r[last]; canvas.boxes.type[i] = canvas.boxes.type[last]; canvas.boxes.selected[i] = canvas.boxes.selected[last]; canvas.boxes.color_idx[i] = canvas.boxes.color_idx[last]; } canvas.nboxes--; } void draw_box(int idx, Image *dst) { Image *bg = colors[canvas.boxes.color_idx[idx]]; if(canvas.boxes.selected[idx]) bg = boxselected; draw(dst, canvas.boxes.r[idx], 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; // First pass: Draw all regular boxes for(i = 0; i < canvas.nboxes; i++) { if(canvas.boxes.type[i] == T_BOX) { Image *color = colors[canvas.boxes.color_idx[i]]; draw(screen, canvas.boxes.r[i], color, nil, ZP); } } // Second pass: Draw all big boxes for(i = 0; i < canvas.nboxes; i++) { if(canvas.boxes.type[i] == T_BIGBOX) { Image *color = colors[canvas.boxes.color_idx[i]]; draw(screen, canvas.boxes.r[i], color, nil, ZP); } } // Third pass: Draw selection highlights for(i = 0; i < canvas.nboxes; i++) { if(canvas.boxes.selected[i]) { border(screen, canvas.boxes.r[i], 2, boxselected, ZP); } } } void draw_status(void) { char buf[256]; InputMode *mode = &input_modes[canvas.current_mode]; snprint(buf, sizeof(buf), "Color: %d Type: %d Selected: %d Mode: %s Boxes: %d %s", canvas.current_color, canvas.next_box_type, 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) % 51; /* 45 total emoji sets */ switch(emoji_set) { case 0: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = rcc_style[i]; break; case 1: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = kirby_dance[i]; break; case 2: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = lambda_dance[i]; break; case 3: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = dancing_guy[i]; break; case 4: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = happy_faces[i]; break; case 5: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = classic_emotions[i]; break; case 6: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = bear_moods[i]; break; case 7: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = bunny_hop[i]; break; case 8: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = robot_faces[i]; break; case 9: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = owl_looks[i]; break; case 10: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = fish_swim[i]; break; case 11: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = music_notes[i]; break; case 12: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = wizard_cast[i]; break; case 13: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = table_flip[i]; break; case 14: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = love_hearts[i]; break; case 15: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = stick_figure[i]; break; case 16: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = cool_shades[i]; break; case 17: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = space_invaders[i]; break; case 18: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = bat_fly[i]; break; case 19: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = spider_crawl[i]; break; case 20: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = boxing[i]; break; case 21: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = flex_arms[i]; break; case 22: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = crab_walk[i]; break; case 23: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = penguin[i]; break; case 24: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = excited[i]; break; case 25: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = ghosts[i]; break; case 26: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = zombies[i]; break; case 27: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = snails[i]; break; case 28: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = ufo[i]; break; case 29: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = rockets[i]; break; case 30: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = weather[i]; break; case 31: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = math_dance[i]; break; case 32: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = pacman[i]; break; case 33: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = dice[i]; break; case 34: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = stars[i]; break; case 35: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = arrows[i]; break; case 36: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = code_brackets[i]; break; case 37: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = swords[i]; break; case 38: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = dragon[i]; break; case 39: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = butterfly[i]; break; case 40: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = skulls[i]; break; case 41: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = money[i]; break; case 42: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = flower[i]; break; case 43: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = chess[i]; break; case 44: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = minimal[i]; break; case 45: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = surprised[i]; break; case 46: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = winking[i]; break; case 47: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = nyan[i]; break; case 48: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = keyboard_smash[i]; break; case 49: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = binary[i]; break; case 50: for(i = 0; i < 4; i++) canvas.emoji_frames[i] = waves[i]; break; } canvas.needredraw = 1; } void cmd_quit(void) { cleanup(); exits(nil); } void enterfnm(int sm, int dp) { canvas.current_mode = 1; canvas.fnkey = 1; canvas.save_mode = sm; if (dp) { strcpy(canvas.fnbuf, config.default_save_path); canvas.fnpos = strlen(canvas.fnbuf); } else { canvas.fnbuf[0] = '\0'; canvas.fnpos = 0; } canvas.needredraw = 1; } void cmd_save(void) { enterfnm(1, 1); } void cmd_save_as(void) { enterfnm(1, 0); } void cmd_open(void) { enterfnm(2, 1); } void cmd_open_file(void) { enterfnm(2, 0); } 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'); // Pass the index return; } if(key >= 'a' && key <= 'f') { cmd_set_color(10 + (key - 'a')); // Pass the index 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, "/usr/glenda/da/")) { strcat(canvas.fnbuf, "/usr/glenda/da/"); 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, j; if(m.buttons & 1){ j = boxat(m.xy); if(j >= 0){ return; } else { 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; // Access arrays directly, not through pointer int type = canvas.boxes.type[i]; int width = (type == T_BIGBOX) ? config.bigbox_width : config.box_width; int height = (type == T_BIGBOX) ? config.bigbox_height : config.box_height; canvas.boxes.selected[i] = 1; while(m.buttons & 2){ Point new_pos = subpt(m.xy, Pt(width/2, height/2)); if(canvas.gridsnap){ new_pos.x = (new_pos.x / canvas.gridsize) * canvas.gridsize; new_pos.y = (new_pos.y / canvas.gridsize) * canvas.gridsize; } canvas.boxes.pos[i] = new_pos; canvas.boxes.r[i] = Rect(new_pos.x, new_pos.y, new_pos.x + width, new_pos.y + height); redraw(); m = emouse(); } canvas.boxes.selected[i] = 0; 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) { USED(nargs); // Add this Point p = Pt(atoi(args[0]), atoi(args[1])); addbox(p); // Don't need to capture idx if not using it 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; } } }