swk

static widget kit
git clone git://git.suckless.org/swk
Log | Files | Refs | README | LICENSE

swk.c (11178B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <stdio.h>
      3 #include <string.h>
      4 #include <stdlib.h>
      5 #include "swk.h"
      6 #include "config.h"
      7 
      8 #define BORDERCOLOR ((e->win->box==e->box)?ColorHI:ColorFG)
      9 static void setscrollbox(int delta);
     10 static SwkWindow *w = NULL;
     11 static int running = 0;
     12 static __thread int rendering = 0;
     13 
     14 int
     15 swk_use(SwkWindow *win) {
     16 	w = win = win->_e.win = win;
     17 	w->colpos = COLSPLIT;
     18 	if(win->box == NULL)
     19 		swk_focus_first(w);
     20 	if(w->r.w == 0 || w->r.h == 0) {
     21 		w->r.w = WINWIDTH;
     22 		w->r.h = WINHEIGHT;
     23 	}
     24 	if(!running && !swk_gi_init(w))
     25 		return 0;
     26 	running = 1;
     27 	swk_update();
     28 	return 1;
     29 }
     30 
     31 // TODO: merge with clean()
     32 void
     33 drawcol(SwkWindow *w) {
     34 	Rect r = {0};
     35 	r.h = 1;
     36 	if (!w->boxes[1]) {
     37 		r.x = 0;
     38 		r.y = 0;
     39 		r.w = w->r.w+2;
     40 		r.h = w->r.h+2;
     41 	} else
     42 	if (!w->col) {
     43 		r.x = 0;
     44 		r.y = 0;
     45 		r.w = w->colpos;
     46 		r.h = w->r.h+2;
     47 	} else {
     48 		r.x = w->colpos;
     49 		r.y = 0;
     50 		r.w = 100;
     51 		r.h = w->r.h+2;
     52 	}
     53 	swk_gi_fill(r, ColorCC, 0);
     54 }
     55 
     56 void
     57 swk_update() {
     58 	char text[8];
     59 	int roy, oy, scroll = 0;
     60 	int col = (w->boxes[1])?((w->colpos>w->r.w)?w->r.w:w->colpos):w->r.w;
     61 	if(rendering)
     62 		return;
     63 	// TODO: Handle scrollup by widget focus
     64 	//if(w->box->r.y > w->r.h)
     65 		//setscrollbox(-2);
     66 	rendering = 1;
     67 	w->_e.type = EExpose;
     68 	if(swk_gi_update(w)) {
     69 		int count = 2;
     70 		int orw = w->r.w;
     71 		SwkBox *b = w->boxes[0];
     72 
     73 		swk_fit(w);
     74 		swk_gi_clear();
     75 		//if(!w->colpos) {
     76 		if(w->colpos<2) {
     77 			b = w->boxes[1];
     78 			count--;
     79 			col = w->r.w;
     80 		}
     81 		drawcol(w);
     82 		for(w->r.w=col; ; b = w->boxes[1]) {
     83 			swk_fit(w);
     84 			roy = oy = 0;
     85 			if(b)
     86 			for(;b->cb; b++) {
     87 				w->_e.box = b;
     88 				if(IS_SCROLLBOX(b))
     89 					roy = oy+1;
     90 				if(b->scroll)
     91 					scroll = b->scroll;
     92 				if(roy && b->r.y < roy) {
     93 					Rect r = w->r;
     94 					r.x = col-1;
     95 					r.y = roy;
     96 					r.w = 3;
     97 					sprintf(text, "(%d)", scroll);
     98 					swk_gi_text(r, text);
     99 					//swk_gi_line(--r.x, roy, 2, 0, ColorHI);
    100 					swk_gi_line((b==w->boxes[0])?w->colpos:0, roy, w->r.w, 0, ColorHI);
    101 				} else b->cb(&w->_e);
    102 				oy = b->r.h;
    103 			}
    104 			if(!w->boxes[1] || !--count)
    105 				break;
    106 			col = orw-w->col;
    107 		}
    108 		swk_gi_flip();
    109 		w->r.w = orw;
    110 	}
    111 	rendering = 0;
    112 }
    113 
    114 // TODO: enqueue events here instead of use a global variable?
    115 void
    116 swk_exit() {
    117 	running = 0;
    118 }
    119 
    120 void
    121 swk_loop() {
    122 	SwkEvent *e;
    123 	do {
    124 		if((e = swk_next_event(w)))
    125 			swk_handle_event(e);
    126 	} while(!e || e->type != EQuit);
    127 }
    128 
    129 void
    130 swk_fontsize_increase() {
    131 	swk_gi_fontsize(1);
    132 	w->colpos--;
    133 	swk_update(w);
    134 }
    135 
    136 void
    137 swk_fontsize_decrease() {
    138 	swk_gi_fontsize(-1);
    139 	w->colpos++;
    140 	swk_update(w);
    141 }
    142 
    143 void
    144 swk_column_move_left() {
    145 	if(w->colpos>0)
    146 		w->colpos--;
    147 	swk_update(w);
    148 }
    149 
    150 void
    151 swk_column_move_right() {
    152 	if(w->colpos<w->r.w)
    153 		w->colpos++;
    154 	swk_update(w);
    155 }
    156 
    157 static void
    158 setscrollbox(int delta) {
    159 	SwkBox *r = NULL;
    160 	SwkBox *b = w->boxes[w->col];
    161 	for(; b->cb; b++) {
    162 		if(IS_SCROLLBOX(b))
    163 			r = b;
    164 		if(w->box==b && r)
    165 			break;
    166 	}
    167 	if(r) r->scroll += delta;
    168 }
    169 
    170 void
    171 swk_scroll_up() {
    172 	w->box = w->boxes[w->col];
    173 	setscrollbox(SCROLLSPEED);
    174 }
    175 
    176 void
    177 swk_scroll_down() {
    178 	w->box = w->boxes[w->col];
    179 	setscrollbox(-SCROLLSPEED);
    180 }
    181 
    182 static void
    183 swk_fit_row(SwkBox *a, SwkBox *b, int col, int y) {
    184 	int x = (col)?w->colpos:0, count = b-a;
    185 	if(count) {
    186 		int winc = w->r.w / count;
    187 		SwkBox *btmp = a;
    188 		for(; btmp<b; btmp++) {
    189 			btmp->r.x = x;
    190 			btmp->r.y = y;
    191 			btmp->r.w = winc;
    192 			if(!btmp->r.h)
    193 				btmp->r.h = 1;
    194 			x += winc;
    195 		}
    196 	}
    197 }
    198 
    199 static int
    200 countrows(SwkBox *b) {
    201 	int row = 17; // XXX hacky value to center widgets
    202 	for(; b->cb; b++)
    203 		if(IS_SCROLLBOX(b))
    204 			row += (int)(size_t)b->data;
    205 	return row;
    206 }
    207 
    208 void
    209 swk_fit() {
    210 	int i, x, y, skip, tskip;
    211 	SwkBox *b, *b2;
    212 	for(i=0;i<2;i++) {
    213 		skip = tskip = y = 0;
    214 		for(b=b2=w->boxes[i]; b->cb; b++) {
    215 			if(b->r.w==-1 && b->r.h==-1) {
    216 				x = (int)(size_t)b->data;
    217 				swk_fit_row(b2, b, i, y);
    218 				y += x-skip+tskip;
    219 				// vertical align //
    220 				tskip = 0;
    221 				if(x<0) y += w->r.h-countrows(b2);
    222 				b2 = b+1;
    223 			} else if(b->r.h>1)
    224 				tskip = b->r.h;
    225 			y += b->scroll;
    226 		}
    227 		swk_fit_row(b2, b, i, y);
    228 		if(!w->boxes[1])
    229 			break;
    230 	}
    231 }
    232 
    233 void
    234 swk_focus_activate() {
    235 	w->_e.box = w->box;
    236 	w->_e.type = EClick;
    237 }
    238 
    239 SwkEvent *
    240 swk_next_event() {
    241 	if(running)
    242 		return swk_gi_event(w, 1);
    243 	w->_e.type = EQuit;
    244 	w->_e.win = w;
    245 	return &w->_e;
    246 }
    247 
    248 void
    249 swk_handle_event(SwkEvent *e) {
    250 	int i;
    251 	SwkBox *b;
    252 	if(e->win->cb)
    253 		e->win->cb(e);
    254 	switch(e->type) {
    255 	case EKey:
    256 		for(i=0; keys[i].cb; i++) {
    257 			if(e->data.key.modmask == keys[i].modmask
    258 			&& e->data.key.keycode == keys[i].keycode) {
    259 				keys[i].cb(e->win);
    260 				break;
    261 			}
    262 		}
    263 		e->box = e->win->box;
    264 		if(e->win->box)
    265 			e->win->box->cb(e);
    266 		swk_update();
    267 		break;
    268 	case EMotion:
    269 		w->col = w->boxes[1]?((e->data.motion.x > w->colpos)?1:0):0;
    270 		for(b=e->win->boxes[w->col]; b->cb; b++) {
    271 			if(SWK_HIT(b->r, e->data.motion)) {
    272 				e->win->box = e->box = b;
    273 				b->cb(e);
    274 //				swk_update();
    275 				break;
    276 			}
    277 		}
    278 		break;
    279 	case EClick:
    280 		// TODO: move click events in config.h
    281 		switch(e->data.click.button) {
    282 		case 4:
    283 			swk_scroll_up(e->win);
    284 			break;
    285 		case 5:
    286 			swk_scroll_down(e->win);
    287 			break;
    288 		default:
    289 			for(b=e->win->boxes[w->col]; b->cb; b++) {
    290 				if(SWK_HIT(b->r, e->data.click.point)) {
    291 					e->box = e->win->box = b;
    292 					e->box->cb(e);
    293 				}
    294 			}
    295 		}
    296 		swk_update();
    297 		break;
    298 	case EExpose:
    299 		swk_update();
    300 		break;
    301 	case EQuit:
    302 		swk_gi_exit();
    303 		break;
    304 	default:
    305 		break;
    306 	}
    307 }
    308 
    309 void
    310 swk_focus_first() {
    311 	w->box = w->boxes[w->col];
    312 	while(w->box->cb == swk_filler)
    313 		w->box++;
    314 	if(w->box->cb == NULL)
    315 		w->box = w->boxes[w->col];
    316 }
    317 
    318 void
    319 swk_focus_next() {
    320 	w->box++;
    321 	if(w->box->cb == NULL)
    322 		w->box = w->boxes[w->col];
    323 	while(w->box->cb == swk_filler)
    324 		w->box++;
    325 	if(w->box->cb == NULL)
    326 		swk_focus_first(w);
    327 }
    328 
    329 void
    330 swk_focus_prev() {
    331 	if(w->box == w->boxes[w->col]) {
    332 		while(w->box->cb)
    333 			w->box++;
    334 		w->box--;
    335 	} else {
    336 		w->box--;
    337 		while(w->box->cb == swk_filler) {
    338 			w->box--;
    339 			if(w->box < w->boxes[w->col]) {
    340 				w->box = w->boxes[w->col];
    341 				swk_focus_prev(w);
    342 				return;
    343 			}
    344 		}
    345 	}
    346 }
    347 
    348 /* -- widgets -- */
    349 
    350 void
    351 swk_label(SwkEvent *e) {
    352 	char text[128]; // XXX
    353 	int cut, len;
    354 	Rect r;
    355 	switch(e->type) {
    356 	case EExpose:
    357 		r = e->box->r;
    358 		r.w += 4;
    359 		cut = r.w*2;
    360 		strncpy(text, e->box->text, sizeof(text)-1);
    361 		len = strlen(text);
    362 		if (len>cut)
    363 			text[cut]=0;
    364 		swk_gi_text(r, text);
    365 		r.w -= 4;
    366 		if(e->win->box == e->box)
    367 			swk_gi_line(r.x, r.y+1, r.w, 0, ColorHI);
    368 		break;
    369 	default:
    370 		break;
    371 	}
    372 }
    373 
    374 void
    375 swk_password(SwkEvent *e) {
    376 	char *str, *ptr;
    377 	int len;
    378 	Rect r;
    379 	switch(e->type) {
    380 	case EExpose:
    381 		r = e->box->r;
    382 		swk_gi_fill(r, ColorBG, 1);
    383 		if(e->win->box == e->box)
    384 			swk_gi_line(r.x, r.y+1, r.w, 0, ColorHI);
    385 		len = strlen(e->box->text);
    386 		if(len>0) {
    387 			ptr = str = malloc(len+1);
    388 			for(;len--;ptr++)
    389 				*ptr='*';
    390 			*ptr='\0';
    391 			swk_gi_text(r, str);
    392 			free(str);
    393 		}
    394 		break;
    395 	case EClick:
    396 		printf("password: %s\n", e->box->text);
    397 	default:
    398 		swk_entry(e);
    399 	}
    400 }
    401 
    402 void
    403 swk_entry(SwkEvent *e) {
    404 	int len, key;
    405 	char *ptr;
    406 	switch(e->type) {
    407 	case EKey:
    408 		if (e->data.key.modmask&Ctrl)
    409 			return;
    410 		key = e->data.key.keycode;
    411 		if(key == 8) {
    412 			ptr = (char*)malloc(strlen(e->box->text)+2);
    413 			strcpy(ptr, e->box->text);
    414 			if(e->box->data)
    415 				free(e->box->text);
    416 			if((len = strlen (ptr))>0)
    417 				ptr[len-1] = '\0';
    418 			e->box->text = e->box->data = ptr;
    419 		} else {
    420 			if(key>=' '&&key<='~') {
    421 				ptr = (char*)malloc(strlen(e->box->text)+2);
    422 				sprintf(ptr, "%s%c", e->box->text, e->data.key.keycode);
    423 				if(e->box->data)
    424 					free(e->box->text);
    425 				e->box->text = e->box->data = ptr;
    426 			}
    427 		}
    428 		break;
    429 	case EExpose:
    430 		// XXX: add support for cursor (handle arrow keys)
    431 		swk_gi_fill(e->box->r, ColorBG, 1);
    432 		swk_label(e);
    433 		/* cursor */ {
    434 			int cut = e->box->r.w*2;
    435 			#ifdef USE_SDL
    436 			len = 4*e->box->r.x;
    437 			#else
    438 			len = 3*e->box->r.x;
    439 			#endif
    440 			if (strlen(e->box->text)>cut)
    441 				len += cut;
    442 			#ifdef USE_SDL
    443 			else len += 2*strlen(e->box->text)+1;
    444 			#else
    445 			else len += strlen(e->box->text)+1;
    446 			#endif
    447 			Rect r = { len, e->box->r.y, 1, 1 };
    448 			swk_gi_fill(r, ColorFG, 2);
    449 		}
    450 		break;
    451 	default:
    452 		swk_label(e);
    453 		break;
    454 	}
    455 }
    456 
    457 void
    458 swk_button(SwkEvent *e) {
    459 	Rect r;
    460 	switch(e->type) {
    461 	case EExpose:
    462 		r = e->box->r;
    463 		r.w--;
    464 		swk_gi_fill(r, ColorBG, 0);
    465 		swk_gi_rect(r, BORDERCOLOR);
    466 		r = e->box->r;
    467 		r.x++;
    468 		swk_gi_text(r, e->box->text);
    469 		break;
    470 	default:
    471 		break;
    472 	}
    473 }
    474 
    475 void
    476 swk_bigbutton(SwkEvent *e) {
    477 	Rect r;
    478 	switch(e->type) {
    479 	case EExpose:
    480 		e->box->r.h = 3;
    481 		r = e->box->r;
    482 		r.x--;
    483 		r.y--;
    484 		r.w--;
    485 		r = e->box->r;
    486 		swk_gi_fill(r, ColorBG, 0);
    487 		swk_gi_rect(r, BORDERCOLOR);
    488 		r = e->box->r;
    489 		r.x += 2;
    490 		r.y++;
    491 		swk_gi_text(r, e->box->text);
    492 		break;
    493 	default:
    494 		break;
    495 	}
    496 }
    497 
    498 void
    499 swk_filler(SwkEvent *e) {
    500 	/* empty widget */
    501 }
    502 
    503 void
    504 swk_option(SwkEvent *e) {
    505 	Rect r;
    506 	SwkBox **b = (SwkBox**)e->box->data;
    507 	switch(e->type) {
    508 	case EClick:
    509 		if(b==(void*)0) e->box->data = (void*)1;
    510 		else if(b==(void*)1) e->box->data = (void*)0;
    511 		else *b = (e->box==*b)?NULL:e->box;
    512 		break;
    513 	case EExpose:
    514 		r = e->box->r;
    515 		if(e->win->box == e->box)
    516 			swk_gi_line(r.x, r.y+1, r.w, 0, ColorHI);
    517 		//r.w = r.h = 1;
    518 		r.w = 1;
    519 		if(b==(void*)1) swk_gi_fill(r, ColorHI, 1);
    520 		else if(b==(void*)0) swk_gi_fill(r, ColorFG, 1);
    521 		else if(e->box==*b) swk_gi_fill(r, ColorHI, 1);
    522 		else swk_gi_fill(r, ColorFG, 1);
    523 		r = e->box->r;
    524 		r.x += 2;
    525 		swk_gi_text(r, e->box->text);
    526 		break;
    527 	default:
    528 		break;
    529 	}
    530 }
    531 
    532 void
    533 swk_separator(SwkEvent *e) {
    534 	Rect r;
    535 	switch(e->type) {
    536 	case EExpose:
    537 		r = e->box->r;
    538 		swk_gi_line(r.x, r.y+1, r.w, 0, BORDERCOLOR);
    539 		break;
    540 	default:
    541 		break;
    542 	}
    543 }
    544 
    545 void
    546 swk_progress(SwkEvent *e) {
    547 	int pc, len;
    548 	Rect r;
    549 	switch(e->type) {
    550 	case EExpose:
    551 		r = e->box->r;
    552 		swk_gi_fill(r, ColorBG, 0);
    553 		r.x+=1;
    554 		swk_gi_text(r, e->box->text);
    555 		r.x-=1;
    556 		swk_gi_rect(r, ColorFG);
    557 		len = strlen(e->box->text)+2;
    558 		r.x += len*0.8;
    559 		r.w -= len*0.6;
    560 		pc = atoi(e->box->text);
    561 		if(pc<0) pc = 0; else if(pc>100) pc = 100;
    562 		r.w = (int)((float)r.w*((float)pc/100));
    563 		if(r.w>0)
    564 			swk_gi_fill(r, ColorFG, 1);
    565 		break;
    566 	default:
    567 		break;
    568 	}
    569 }
    570 
    571 /* -- */
    572 void
    573 swk_image_free(SwkBox *b) {
    574 	swk_gi_img_free(b->data);
    575 	b->data = NULL;
    576 }
    577 
    578 void
    579 swk_image(SwkEvent *e) {
    580 	if(e->box->data == NULL) {
    581 		e->box->data = swk_gi_img_load(e->box->text);
    582 		if(!e->box->data)
    583 			fprintf(stderr, "Cannot open image %s\n", e->box->text);
    584 	}
    585 	switch(e->type) {
    586 	case EExpose:
    587 		swk_gi_img(e->box->r, e->box->data);
    588 		//swk_gi_rect(e->box->r, ColorFG);
    589 		if(e->win->box == e->box) {
    590 			Rect r = e->box->r;
    591 			swk_gi_line(r.x, r.y+1, r.w, 0, ColorHI);
    592 		}
    593 		break;
    594 	default:
    595 		break;
    596 	}
    597 }
    598 
    599 void
    600 swk_sketch(SwkEvent *e) {
    601 	int x, y;
    602 	if(e->box->data == NULL)
    603 		e->box->data = swk_gi_img_new(e->box->r.w, e->box->r.h, ColorHI);
    604 	switch(e->type) {
    605 	case EClick:
    606 		// TODO: implement low-level primitives for pixel-level rendering
    607 		for(x=y=5;x<10;x=++y)
    608 			swk_gi_img_set(e->box->data, x, y, ColorFG);
    609 		for(x=y=5;x<10;x++,y--)
    610 			swk_gi_img_set(e->box->data, x, y, ColorFG);
    611 		printf("CLICKED %p %d %d\n", e->box->data, e->data.click.point.x, e->data.click.point.y);
    612 		break;
    613 	case EExpose:
    614 		swk_gi_rect(e->box->r, ColorHI); // border
    615 		swk_gi_img(e->box->r, e->box->data);
    616 		break;
    617 	default:
    618 		break;
    619 	}
    620 }