ref: 6379f646f2688d198d28d30e34006c6cea0e3571
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);
void delbox(int i);
int addbox(Point p);
int boxat(Point p);
static ulong palette[] = {
0xFF69B4FF, 0x00BFFFFF, 0x00FF00FF, 0xFFD700FF,
0xFF8C00FF, 0x9400D3FF, 0xFF7F50FF, 0x00FFFFFF,
0xFF1493FF, 0x4B0082FF, 0xDC143CFF, 0x40E0D0FF,
0xFFD700FF, 0x87CEEBFF, 0xB22222FF, 0x00FA9AFF
};
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);
Bprint(b, "count %d\n", canvas.nboxes);
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);
}
Bprint(b, "types\n");
for(i = 0; i < canvas.nboxes; i++){
Bprint(b, "%d\n", canvas.boxes.type[i]);
}
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;
}
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;
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 {
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;
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) {
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; /* 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;
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);
}
}
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);
}
}
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;
}
void
cmd_cycle_emoji(void)
{
static int emoji_set = 0;
int i;
emoji_set = (emoji_set + 1) % 51; /* 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');
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, 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;
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);
Point p = Pt(atoi(args[0]), atoi(args[1]));
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;
}
}
}