shithub: da

ref: 02c13a3ce3bcf6e19dcf794c4efa57d4056b2d4c
dir: /da.c/

View raw version
#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;
		}
	}
}