ref: 6891d9b8fd7a7e7cccb11095b467aac9a0a2feec
author: glenda <glenda@krsna>
date: Tue Aug 19 18:20:03 EDT 2025
new-dynamic-app
--- /dev/null
+++ b/da.c
@@ -1,0 +1,1297 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+#include <bio.h>
+#include <ctype.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 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:/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 *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, "/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;
+
+ 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;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,26 @@
+
+</$objtype/mkfile
+
+# Compiler flags
+CFLAGS=-FVTw
+
+# Targets
+BIN=$home/bin/amd64
+TARG=da
+
+# Source files
+APP=$TARG.6
+
+# Build rules
+all:V: $BIN/$TARG
+
+$APP: $TARG.c
+ 6c $CFLAGS $TARG.c
+
+$BIN/$TARG: $APP
+ 6l -o $target $APP
+
+nuke:
+ rm -f *.6 $BIN/$TARG
+clean:
+ rm -f *.6
--
⑨