#include #include #include #include extern const char *__progname; #include "2dmath.h" typedef enum { DRAG_NONE = 1, DRAG_SEGMENT, } DRAG_MODE; typedef struct ends ENDS; typedef struct line LINE; typedef struct arcs ARCS; typedef struct tool TOOL; typedef struct ds_seg DS_SEG; struct tool { void (* const selclick)(const LX_EVENT_ButtonPress *); void (* const drawclick)(const LX_EVENT_ButtonPress *); const unsigned char * const bits; unsigned char *image; LX_XID unselwin; LX_XID selwin; LX_XID inwin; int selected; } ; struct ends { double e1; double e2; } ; struct line { FPOINT p1; FPOINT p2; int nsegs; ENDS *segs; } ; // for a full circle, narc is -1 and arcs is nil struct arcs { FPOINT c; double r; int narc; ENDS *arcs; } ; struct ds_seg { FPOINT fixf; IPOINT fixi; FPOINT curf; IPOINT curi; } ; //static FPOINT panwinmin; //static FPOINT panwinmax; //static FPOINT pandelta; static LX_CONN *disp; static int scr; static LX_XID root; static int depth; static int cmap; static unsigned int wpix; static unsigned int bpix; static LX_XID topwin; static LX_XID toolwin; static LX_XID drawwin; static LX_XID xhhwin; static LX_XID xhvwin; static LX_XID inwin; static LX_XID nilcurs; static const LX_VISINFO *vi; static unsigned int rw; static unsigned int rh; static unsigned int tw; static unsigned int th; static LX_SGC wingc; static LX_SGC bitgc; static int xhx; static int xhy; static int outside; static int drawloc_state = 0; static LX_QUERYPOINTER_STATUS drawloc_qps; static DRAG_MODE drag; static TOOL *tool; static LX_XID drawpm; static DS_SEG ds_seg; static int drag_up; static FPOINT winmin; static FPOINT winmax; static int end_batch; static int want_rerender; static int npoints; static FPOINT *points; static int nlines; static LINE *lines; static int narcs; static ARCS *arcs; #include "tools.inc" #define N_TOOLS (sizeof(tools) / sizeof(tools[0])) #define TOOL_ROW_BYTES ((TOOLSIZE + 7) >> 3) static int check_visual(void *vvv, const LX_VISINFO *vi) { void **vv; vv = vvv; if (vi->id == *(LX_XID *)vv[0]) { *(const LX_VISINFO **)vv[1] = vi; return(-1); } return(0); } static void find_visual(void) { LX_XID id; void *v[2]; id = lx_root_visual(disp,scr); v[0] = &id; v[1] = &vi; if (! lx_map_visuals(disp,&check_visual,&v[0])) { fprintf(stderr,"%s: can't find root visual\n",__progname); exit(1); } } static void set_xhx(int x) { if (x == xhx) return; xhx = x; lx_ConfigureWindow_va(disp,xhvwin,LX_CWV_X(x),LX_CWV_END); } static void set_xhy(int y) { if (y == xhy) return; xhy = y; lx_ConfigureWindow_va(disp,xhhwin,LX_CWV_Y(y),LX_CWV_END); } static void hide_drag(void) { if (drag_up) { lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Xor),LX_GCV_Foreground(wpix^bpix),LX_GCV_END); switch (drag) { case DRAG_NONE: break; case DRAG_SEGMENT: lx_draw_line(disp,drawwin,lx_SGC_GC(disp,wingc),ds_seg.fixi.x,ds_seg.fixi.y,ds_seg.curi.x,ds_seg.curi.y); break; } drag_up = 0; lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Copy),LX_GCV_END); end_batch = 1; } } static void show_drag(void) { if (! drag_up) { lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Xor),LX_GCV_Foreground(wpix^bpix),LX_GCV_END); switch (drag) { case DRAG_NONE: break; case DRAG_SEGMENT: lx_draw_line(disp,drawwin,lx_SGC_GC(disp,wingc),ds_seg.fixi.x,ds_seg.fixi.y,ds_seg.curi.x,ds_seg.curi.y); break; } drag_up = 1; lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Copy),LX_GCV_END); end_batch = 1; } } static void arena_move(int x, int y) { hide_drag(); switch (drag) { case DRAG_NONE: set_xhx(x); set_xhy(y); break; case DRAG_SEGMENT: set_xhx(x); set_xhy(y); ds_seg.curi.x = x; ds_seg.curi.y = y; break; } show_drag(); } static void arena_click(const LX_EVENT_ButtonPress *e) { set_xhx(e->eventx); set_xhy(e->eventy); (*tool->drawclick)(e); } static void arena_hide_xhair(void) { set_xhx(-1); set_xhy(-1); } static void draw_loc_query(void); // forward static void drawloc_qdone(void *arg __attribute__((__unused__))) { if (outside || !drawloc_qps.samescreen) { arena_hide_xhair(); drawloc_state = 0; return; } switch (drawloc_state) { case 0: abort(); break; case 1: arena_move(drawloc_qps.winx,drawloc_qps.winy); drawloc_state = 0; break; case 2: drawloc_state = 0; draw_loc_query(); break; } } static void draw_loc_query(void) { switch (drawloc_state) { case 0: drawloc_state = 1; lx_op_callback(lx_QueryPointer_status(disp,inwin,&drawloc_qps),&drawloc_qdone,0,0); break; case 1: case 2: drawloc_state = 2; break; default: abort(); break; } } static void resize_top(unsigned int w, unsigned int h) { FPOINT c; if ((w == tw) && (h == th)) return; if (w < 2) w = 2; if (h < 2) h = 2; c = scalef(addf2(winmin,winmax),.5); winmin.x = c.x + (((winmin.x - c.x) * (w-1)) / (tw-1)); winmin.y = c.y + (((winmin.y - c.y) * (h-1)) / (th-1)); winmax.x = c.x + (((winmax.x - c.x) * (w-1)) / (tw-1)); winmax.y = c.y + (((winmax.y - c.y) * (h-1)) / (th-1)); tw = w; th = h; want_rerender = 1; draw_loc_query(); } static void button_press(const LX_EVENT_ButtonPress *e) { int i; TOOL *t; if (e->eventw == inwin) { arena_click(e); return; } for (i=N_TOOLS-1;i>=0;i--) { t = &tools[i]; if (e->eventw == t->inwin) { (*t->selclick)(e); return; } } } static void enter_notify(const LX_EVENT_EnterNotify *e) { if (e->eventw != inwin) return; outside = 0; arena_move(e->eventx,e->eventy); } static void leave_notify(const LX_EVENT_EnterNotify *e) { if (e->eventw != inwin) return; outside = 1; arena_hide_xhair(); } static void motion_notify(const LX_EVENT_MotionNotify *e) { if (e->eventw != inwin) return; if (outside) return; draw_loc_query(); } static void configure_notify(const LX_EVENT_ConfigureNotify *e) { if (e->eventw != topwin) return; resize_top(e->w,e->h); } static void x_event(LX_CONN *xc, const LX_EVENT *e) { if (xc != disp) abort(); switch (e->type) { default: break; case LX_EV_ButtonPress: button_press(&e->u.ButtonPress); break; case LX_EV_EnterNotify: enter_notify(&e->u.EnterNotify); break; case LX_EV_LeaveNotify: leave_notify(&e->u.EnterNotify); break; case LX_EV_MotionNotify: motion_notify(&e->u.MotionNotify); break; case LX_EV_ConfigureNotify: configure_notify(&e->u.ConfigureNotify); break; } } static void set_tool_select(TOOL *t, int sel) { if (sel == t->selected) return; t->selected = sel; lx_MapWindow(disp,sel?t->selwin:t->unselwin); lx_UnmapWindow(disp,sel?t->unselwin:t->selwin); } static void use_tool(int tno) { tool = &tools[tno]; set_tool_select(&tools[TOOL_SEGMENT],tno==TOOL_SEGMENT); set_tool_select(&tools[TOOL_EXTEND],tno==TOOL_EXTEND); set_tool_select(&tools[TOOL_CIRCLE],tno==TOOL_CIRCLE); set_tool_select(&tools[TOOL_ARCS],tno==TOOL_ARCS); } static int maybe_end_batch(void *arg __attribute__((__unused__))) { if (! end_batch) return(AIO_BLOCK_NIL); end_batch = 0; lx_end_batch(disp); return(AIO_BLOCK_LOOP); } static FPOINT screen_to_world(IPOINT p) { return((FPOINT){ .x = (p.x / (tw-1.0)) * (winmax.x - winmin.x), .y = (p.y / (th-1.0)) * (winmax.y - winmin.y) }); } static IPOINT world_to_screen(FPOINT p) { return((IPOINT){ .x = rint(((p.x - winmin.x) * (tw - 1)) / (winmax.x - winmin.x)), .y = rint(((p.y - winmin.y) * (th - 1)) / (winmax.y - winmin.y)) }); } static void render_segment(LINE *l, int sx) { ENDS *e; FPOINT d; FPOINT e1; FPOINT e2; IPOINT e1i; IPOINT e2i; e = &l->segs[sx]; d = subf2(l->p2,l->p1); e1 = addf2(l->p1,scalef(d,e->e1)); e2 = addf2(l->p1,scalef(d,e->e2)); printf("render_segment: (%g,%g) - (%g,%g)\n",e1.x,e1.y,e2.x,e2.y); #define CLIP(end,oend,coord,test,minmax) do {\ if (end.coord test win##minmax.coord) \ { if (oend.coord test win##minmax.coord) \ { printf(" both %s %s.%s\n",#test,#minmax,#coord); \ return; \ } \ end = addf2(end,scalef(subf2(oend,end),(win##minmax.coord-end.coord)/(oend.coord-end.coord)));\ printf(" %s.%s clipped %s (%g,%g)\n",#minmax,#coord,#end,end.x,end.y);\ } \ } while (0) CLIP(e1,e2,x,<,min); CLIP(e1,e2,x,>,max); CLIP(e2,e1,x,<,min); CLIP(e2,e1,x,>,max); CLIP(e1,e2,y,<,min); CLIP(e1,e2,y,>,max); CLIP(e2,e1,y,<,min); CLIP(e2,e1,y,>,max); #undef CLIP printf(" clipped line is (%g,%g) - (%g,%g)\n",e1.x,e1.y,e2.x,e2.y); e1i = world_to_screen(e1); e2i = world_to_screen(e2); printf(" drawing (%d,%d)-(%d,%d)\n",e1i.x,e1i.y,e2i.x,e2i.y); lx_draw_line(disp,drawpm,lx_SGC_GC(disp,wingc),e1i.x,e1i.y,e2i.x,e2i.y); end_batch = 1; } static int maybe_rerender(void *arg __attribute__((__unused__))) { int i; int j; LINE *l; if (! want_rerender) return(AIO_BLOCK_NIL); want_rerender = 0; printf("rerender: bounds (%g..%g) x (%g..%g)\n",winmin.x,winmax.x,winmin.y,winmax.y); lx_fill_rectangle(disp,drawpm,lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(bpix),LX_GCV_PUSH),0,0,tw,th); lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); for (i=nlines-1;i>=0;i--) { l = &lines[i]; for (j=l->nsegs-1;j>=0;j--) { render_segment(l,j); } } lx_ChangeWindowAttributes_va(disp,drawwin,LX_CWV_BackPixmap(drawpm),LX_CWV_END); lx_ClearArea(disp,drawwin,0,0,0,0,0); return(AIO_BLOCK_LOOP); } static void x_open_done(LX_CONN *xc, void *arg __attribute__((__unused__))) { int i; TOOL *t; LX_XID pmunsel; LX_XID pmsel; int x; int y; LX_XID pmc; disp = xc; lx_set_event_handler(disp,&x_event); scr = lx_default_screen(disp); root = lx_root(disp,scr); depth = lx_root_depth(disp,scr); cmap = lx_root_colormap(disp,scr); wpix = lx_white_pixel(disp,scr); bpix = lx_black_pixel(disp,scr); rw = lx_root_width(disp,scr); rh = lx_root_height(disp,scr); wingc = lx_CreateSGC_va(disp,root,LX_GCV_END); pmc = lx_CreatePixmap(disp,root,1,1,1); bitgc = lx_CreateSGC_va(disp,pmc,LX_GCV_Foreground(0),LX_GCV_END); lx_draw_point(disp,pmc,lx_SGC_GC(disp,bitgc),0,0); nilcurs = lx_CreateCursor(disp,pmc,pmc,0,0,0,0,0,0,0,0); lx_FreePixmap(disp,pmc); find_visual(); tw = (rw * 2) / 3; th = (rh * 2) / 3; if (tw < 2) tw = 2; if (th < 2) th = 2; topwin = lx_CreateWindow_va(disp,root,(rw-tw-2)/2,(rh-th-2)/2,tw,th,1,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(bpix), LX_CWV_BorderPixel(wpix), LX_CWV_EventMask(LX_EM_StructureNotify), LX_CWV_Colormap(cmap), LX_CWV_END); drawwin = lx_CreateWindow_va(disp,topwin,0,0,tw,th,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(bpix), LX_CWV_EventMask(LX_EM_Exposure), LX_CWV_Colormap(cmap), LX_CWV_END); xhhwin = lx_CreateWindow_va(disp,drawwin,-1,-1,65535,1,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); xhvwin = lx_CreateWindow_va(disp,drawwin,-1,-1,1,65535,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); xhx = -1; xhy = -1; outside = 1; inwin = lx_CreateWindow_va(disp,drawwin,0,0,tw,th,0,0,LX_WCLASS_InputOnly,vi->id, LX_CWV_EventMask( LX_EM_ButtonPress | LX_EM_EnterWindow | LX_EM_LeaveWindow | LX_EM_PointerMotion | LX_EM_PointerMotionHint ), LX_CWV_Cursor(nilcurs), LX_CWV_END); toolwin = lx_CreateWindow_va(disp,topwin,-1,-1,((TOOLSIZE+1)*N_TOOLS)-1,TOOLSIZE,1,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Copy),LX_GCV_END); for (i=N_TOOLS-1;i>=0;i--) { t = &tools[i]; pmunsel = lx_CreatePixmap(disp,topwin,depth,TOOLSIZE,TOOLSIZE); lx_fill_rectangle(disp,pmunsel,lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(bpix),LX_GCV_PUSH),0,0,TOOLSIZE,TOOLSIZE); lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); for (y=TOOLSIZE-1;y>=0;y--) for (x=TOOLSIZE-1;x>=0;x--) { if ((t->bits[(y*TOOL_ROW_BYTES)+(x>>3)] << (x & 7)) & 0x80) { lx_draw_point(disp,pmunsel,lx_SGC_GC(disp,wingc),x,y); } } pmsel = lx_CreatePixmap(disp,topwin,depth,TOOLSIZE,TOOLSIZE); lx_CopyArea(disp,pmunsel,pmsel,lx_SGC_GC(disp,wingc),0,0,0,0,TOOLSIZE,TOOLSIZE); lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); lx_fill_rectangle(disp,pmsel,lx_SGC_GC(disp,wingc),0,0,2,TOOLSIZE-2); lx_fill_rectangle(disp,pmsel,lx_SGC_GC(disp,wingc),0,TOOLSIZE-2,TOOLSIZE-2,2); lx_fill_rectangle(disp,pmsel,lx_SGC_GC(disp,wingc),TOOLSIZE-2,2,2,TOOLSIZE-2); lx_fill_rectangle(disp,pmsel,lx_SGC_GC(disp,wingc),2,0,TOOLSIZE-2,2); t->unselwin = lx_CreateWindow_va(disp,toolwin,i*(TOOLSIZE+1),0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(pmunsel), LX_CWV_END); t->selwin = lx_CreateWindow_va(disp,toolwin,i*(TOOLSIZE+1),0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(pmsel), LX_CWV_END); t->inwin = lx_CreateWindow_va(disp,toolwin,i*(TOOLSIZE+1),0,TOOLSIZE,TOOLSIZE,0,0,LX_WCLASS_InputOnly,vi->id, LX_CWV_EventMask(LX_EM_ButtonPress), LX_CWV_END); lx_MapWindow(disp,t->unselwin); lx_MapWindow(disp,t->inwin); t->selected = 0; } lx_MapSubwindows(disp,drawwin); lx_MapSubwindows(disp,topwin); lx_MapWindow(disp,topwin); drag = DRAG_NONE; use_tool(TOOL_SEGMENT); set_tool_select(&tools[TOOL_SETCOMPASS],0); drawpm = lx_CreatePixmap(disp,topwin,depth,tw,th); npoints = 0; points = 0; nlines = 0; lines = 0; narcs = 0; arcs = 0; winmin.x = 0; winmin.y = 0; winmax.x = tw - 1; winmax.y = th - 1; aio_add_block(&maybe_end_batch,0); aio_add_block(&maybe_rerender,0); } static void add_segment(void) { LINE *l; if ((ds_seg.fixi.x == ds_seg.curi.x) && (ds_seg.fixi.y == ds_seg.curi.y)) return; lines = realloc(lines,(nlines+1)*sizeof(LINE)); l = &lines[nlines++]; l->p1 = screen_to_world(ds_seg.fixi); l->p2 = screen_to_world(ds_seg.curi); l->nsegs = 1; l->segs = malloc(sizeof(ENDS)); l->segs[0].e1 = 0; l->segs[0].e2 = 1; want_rerender = 1; } static void drawclick_segment(const LX_EVENT_ButtonPress *e) { switch (drag) { case DRAG_NONE: if (e->button != 1) return; drag = DRAG_SEGMENT; ds_seg.fixi.x = xhx; ds_seg.fixi.y = xhy; drag_up = 0; break; case DRAG_SEGMENT: hide_drag(); drag = DRAG_NONE; if (e->button == 1) add_segment(); break; } } static void drawclick_extend(const LX_EVENT_ButtonPress *e __attribute__((__unused__))) { } static void drawclick_circle(const LX_EVENT_ButtonPress *e __attribute__((__unused__))) { } static void drawclick_arcs(const LX_EVENT_ButtonPress *e __attribute__((__unused__))) { } static void drawclick_setcompass(const LX_EVENT_ButtonPress *e __attribute__((__unused__))) { abort(); } static void drawclick_zoomin(const LX_EVENT_ButtonPress *e __attribute__((__unused__))) { abort(); } static void drawclick_zoomout(const LX_EVENT_ButtonPress *e __attribute__((__unused__))) { abort(); } static void selclick_segment(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_tool(TOOL_SEGMENT); } static void selclick_extend(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_tool(TOOL_EXTEND); } static void selclick_circle(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_tool(TOOL_CIRCLE); } static void selclick_arcs(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_tool(TOOL_ARCS); } static void selclick_setcompass(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; set_tool_select(&tools[TOOL_SETCOMPASS],!tools[TOOL_SETCOMPASS].selected); } static void selclick_zoomin(const LX_EVENT_ButtonPress *e) { FPOINT c; double f; switch (e->button) { case 1: f = 2. / 3; break; case 2: f = 3. / 4; break; case 3: f = 9. / 10; break; default: return; break; } c = scalef(addf2(winmin,winmax),.5); winmin = addf2(c,scalef(subf2(winmin,c),f)); winmax = addf2(c,scalef(subf2(winmax,c),f)); want_rerender = 1; } static void selclick_zoomout(const LX_EVENT_ButtonPress *e) { FPOINT c; double f; switch (e->button) { case 1: f = 3. / 2; break; case 2: f = 4. / 3; break; case 3: f = 10. / 9; break; } c = scalef(addf2(winmin,winmax),.5); winmin = addf2(c,scalef(subf2(winmin,c),f)); winmax = addf2(c,scalef(subf2(winmax,c),f)); want_rerender = 1; } int main(void); int main(void) { aio_poll_init(); lx_open(0,0,&x_open_done,0,0,0); aio_event_loop(); return(0); }