`} save_text(n,d) } writegif=(frames,delays,palette,pal_size)=>{ let paltrans=-1;for(let z=0;zrmax(s,f.size),rect(1,1)), pal=deck.patterns.pal.pix; let frame_index=0, payload=[] const anim_ants =(x,y)=>(0|((x+y+frame_index)/3))%2?15:0 const draw_pattern =(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal_pat(pal,pix,x,y)&1 const draw_color_trans=(pix,x,y)=>pix==ANTS?anim_ants(x,y): pix==0?paltrans: pix>=(32+PAL_COLORS)?0: pix>31?pix-32: draw_pattern(pix,x,y)?15:0 const b=x=>payload.push(x&0xFF), s=x=>{b(x);b(x>>8)}, t=x=>x.split('').forEach(x=>b(x.charCodeAt(0))) t('GIF89a'),s(size.x),s(size.y) // header, dimensions let lws=0; if(pal_size){ const n=Math.log2(pal_size)-1;lws=max(2,Math.log2(pal_size)) b(0xF0|n) // global colortable, 8-bits per channel, 2^N+1 colors b(0),b(0) // background color is 0, 1:1 pixel aspect ratio for(let z=0;z>16),b(palette[z]>>8),b(palette[z]) // global colortable }else{ let mc=0;frames.forEach(f=>f.pix.forEach(d=>mc=max(mc,draw_color_trans(d,0,0))));mc=mc>15?32:16 const n=Math.log2(mc);paltrans=mc,lws=Math.log2(mc)+1 b(0xF0|n) // global colortable, 8-bits per channel, 2^N+1 colors b(0),b(0) // background color is 0, 1:1 pixel aspect ratio for(let z=0;z>16),b(COLORS[z]>>8),b(COLORS[z]) // global colortable for(let z=0;zpayload.push(x)) b(0),frame_index++ // end of frame };b(0x3B);return payload } writewav=sound=>{ const payload=[], b=x=>payload.push(x&0xFF), s=x=>(b(x),b(x>>8)) const d=x=>(b(x),b(x>>8),b(x>>16),b(x>>24)), t=x=>x.split('').forEach(x=>b(x.charCodeAt(0))) t('RIFF'),d(4+24+(8+sound.data.length)+(sound.data.length%2)) t('WAVE'),t('fmt ') d(16) // chunk size s(1),s(1),d(8000) // pcm, 1 channel, 8khz d(8000) // 8000*(1 byte per sample)*(1 channel) s(1) // (1 byte per sample)*(1 channel) s(8) // 8 bits per sample t('data'),d(sound.data.length) for(let z=0;z{ const payload=[] for(let z=0;z=0&&ix{if(!ev.shift||s.x==0||s.y==0)return r;const scale=max(r.w/(s.x*1.0),r.h/(s.y*1.0));return rect(r.x,r.y,scale*s.x,scale*s.y)} draw_frame=(image,clip)=>({brush:0,pattern:0,font:FONT_BODY,size:image.size,clip:clip||rect(0,0,image.size.x,image.size.y),image}) draw_pix=(x,y,pattern)=>{const h=rect(x,y);if(inclip(h))pix(h,pattern)} draw_invert=(pal,r)=>{ const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal_pat(pal,pix,x,y)&1; r=rclip(r,frame.clip) for(let a=r.y;a{ if(solid)draw_rect(rclip(r,frame.clip),bcol);draw_box(r,0,fcol),draw_hline(r.x+3,r.x+r.w,r.y+r.h,fcol),draw_vline(r.x+r.w,r.y+2,r.y+r.h+1,fcol) } draw_boxr=(r,fcol,bcol,background)=>{ r=rint(r) draw_hline(r.x+2,r.x+r.w-2,r.y,fcol),draw_hline(r.x+2,r.x+r.w-2,r.y+r.h-1,fcol),draw_vline(r.x,r.y+2,r.y+r.h-2,fcol),draw_vline(r.x+r.w-1,r.y+2,r.y+r.h-2,fcol) draw_pix(r.x+1,r.y+1,fcol),draw_pix(r.x+r.w-2,r.y+1,fcol),draw_pix(r.x+1,r.y+r.h-2,fcol),draw_pix(r.x+r.w-2,r.y+r.h-2,fcol) if(background)draw_hline(r.x+2,r.x+r.w-2,r.y+1,bcol),draw_hline(r.x+2,r.x+r.w-2,r.y+r.h-2,bcol),draw_rect(rect(r.x+1,r.y+2,r.w-2,r.h-4),bcol) } draw_boxinv=(pal,r)=>{ draw_invert(pal,rect(r.x,r.y,1,r.h)) ,draw_invert(pal,rect(r.x+r.w-1,r.y,1,r.h)) draw_invert(pal,rect(r.x+1,r.y,r.w-2,1)),draw_invert(pal,rect(r.x+1,r.y+r.h-1,r.w-2,1)) } draw_modalbox=s=>{ const menu=16, r=rcenter(rect(0,menu,frame.size.x,frame.size.y-menu),s), o=inset(r,-5) draw_rect(inset(o,-5),32),draw_box(inset(o,-5),0,1),draw_box(inset(o,-2),0,1),draw_box(inset(o,-1),0,1);return r } draw_modal_rtext=extra=>{ const size=rect(200,100) const l=lit(ms.message)?layout_richtext(deck,ms.message,FONT_BODY,ALIGN.center,size.x):layout_plaintext(ls(ms.message),FONT_BODY,ALIGN.center,size) const b=draw_modalbox(radd(l.size,extra)), tbox=rclip(rect(b.x,b.y,b.w,l.size.y),frame.clip) if(lit(ms.message)){draw_text_rich(tbox,l,1,1)}else{draw_text_wrap(tbox,l,1)} return b } draw_text_outlined=(pos,text,f)=>{ ([[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]).map(([x,y])=>draw_text(rect(pos.x+x,pos.y+y),text,f,32)) draw_text(pos,text,f,1) } draw_textr=(r,text,font,pattern)=>{ const size=font_textsize(font,text) if(size.x{ const size=font_textsize(font,text) if(pattern==-1){draw_text_outlined(rcenter(r,size),text,font)} else if(size.x{ const glyphs=[], glyph_push=(pos,c)=>glyphs.push({pos,c}) let x=0,y=0,fh=font_h(font),ew=font_gw(font,ELLIPSIS) for(let z=0;z=(r.w-ew)){glyph_push(rect(x,y),ELLIPSIS);while(z{g.pos.x+=r.x,g.pos.y+=yo+r.y,draw_char(g.pos,font,g.c,pattern)}) } draw_scaled=(r,image,opaque)=>{ if(r.w==0||r.h==0)return;const s=image.size if(r.w==s.x&&r.h==s.y){image_paste(r,frame.clip,image,frame.image,opaque);return} image_paste_scaled(r,frame.clip,image,frame.image,opaque) } draw_invert_scaled=(pal,r,image)=>{ if(r.w==0||r.h==0)return;const s=image.size, fb=frame.image.pix, fs=frame.image.size.x, sc=lerp_scale(r,s) const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal[(x%8)+(8*(y%8))+(8*8*pix)]&1 const gpix=p=>fb[p.x+p.y*fs], pix=(p,v)=>fb[p.x+p.y*fs]=v for(let a=0;a{ const anim=deck.patterns.anim const anim_pattern=(pix,x,y)=>pix<28||pix>31?pix: anim[pix-28][(0|(frame_count/4))%max(1,anim[pix-28].length)] const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal[(x%8)+(8*(y%8))+(8*8*pix)]&1 const s=image.size;for(let y=0;y=s.x||offset.y+y>=s.y||offset.x+x<0||offset.y+y<0)continue const v=image.pix[(offset.x+x)+(offset.y+y)*s.x];if(v==mask)continue const c=anim_pattern(v,offset.x+x,offset.y+y),p=draw_pattern(c,offset.x+x,offset.y+y) draw_rect(radd(rect(x*scale,y*scale,scale,scale),rect(r.x,r.y)),c>=32?c: c==0?0: p?1:32) } } draw_fat_scaled=(r,image,opaque,pal,frame_count,scale,offset)=>{ const anim=deck.patterns.anim const anim_pattern=(pix,x,y)=>pix<28||pix>31?pix: anim[pix-28][(0|(frame_count/4))%max(1,anim[pix-28].length)] const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal[(x%8)+(8*(y%8))+(8*8*pix)]&1 if(r.w==0||r.h==0)return;const s=image.size, sc=lerp_scale(r,s) for(let a=0;a=32?c: p?1:0) } } draw_dithered=(r,image,opaque,mask,threshold)=>{ if(r.w==0||r.h==0)return;const s=image.size, stride=2*r.w, m=[0,1,r.w-2,r.w-1,r.w,stride-1], dither_err=new Float32Array(stride) for(let ei=0,a=0;athreshold?1:0, err=(p-col)/8.0 dither_err[ei]=0, ei=(ei+1)%stride; for(let z=0;z<6;z++)dither_err[(ei+m[z])%stride]+=err const c=!col;if(ms&&(opaque||c!=0)){const h=rect(r.x+b,r.y+a);if(inclip(h))pix(h,c)} } } draw_widget=w=>{ if(canvas_is(w))return container_image(w,1) const im=ms.in_modal,it=ms.type;ms.in_modal=1,ms.type='about' const rsize=getpair(ifield(w,'size')),r=image_make(rsize),t=frame,te=copy_object(ev);frame=draw_frame(r),ev=event_state(),menus_clear() // !!! if (button_is (w)){const p=unpack_button(w);p.size.x=0,p.size.y=0;widget_button(w,p,lb(ifield(w,'value')))} else if(slider_is (w)){const p=unpack_slider(w);p.size.x=0,p.size.y=0;widget_slider(w,p)} else if(grid_is (w)){const p=unpack_grid (w);p.size.x=0,p.size.y=0;widget_grid (w,p,unpack_grid_value(w))} else if(field_is (w)){const p=unpack_field (w);p.size.x=0,p.size.y=0;widget_field (w,p,unpack_field_value(w))} else if(contraption_is(w)){const o=w.pos;w.pos=rect(0,0);widget_contraption(w);w.pos=o} return ev=te,frame=t,ms.in_modal=im,ms.type=it,r } draw_con=(card,active)=>{ const im=ms.in_modal,it=ms.type;ms.in_modal=active;if(active){ms.type='about',menus_clear()} const rsize=getpair(ifield(card,'size')),r=image_make(rsize),t=frame,te=copy_object(ev);frame=draw_frame(r),ev=event_state() const back=ifield(card,'image'), bsize=back.size, wids=card.widgets if(bsize.x!=0&&bsize.y!=0)image_paste(rpair(rect(),bsize),frame.clip,back,frame.image,1) if(uimode!='draw'||dr.show_widgets)wids.v.map(w=>{ if(button_is (w))widget_button(w,unpack_button(w),lb(ifield(w,'value'))) if(slider_is (w))widget_slider(w,unpack_slider(w)) if(canvas_is (w))widget_canvas(w,unpack_canvas(w),container_image(w,0)) if(grid_is (w))widget_grid (w,unpack_grid (w),unpack_grid_value(w)) if(field_is (w))widget_field (w,unpack_field (w),unpack_field_value(w)) if(contraption_is(w))widget_contraption(w) });return ev=te,frame=t,ms.in_modal=im,ms.type=it,r } draw_thumbnail=(card,r)=>{ const back=ifield(card,'image');r=inset(r,1),draw_rect(r,0);if(back.size.x>0||back.size.y>0)draw_scaled(r,back,1) const wids=ifield(card,'widgets'),s=getpair(ifield(card,'size')), xr=r.w*(1.0/s.x), yr=r.h*(1.0/s.y) wids.v.map(wid=>{const w=unpack_widget(wid);draw_box(rclip(rect(r.x+w.size.x*xr,r.y+w.size.y*yr,w.size.w*xr,w.size.h*yr),r),0,w.show=='invert'?0:1)}) } draw_lil=(size,align,bare,x)=>{ const GAP=50, w=size.x-GAP; let xo=align==ALIGN.right?GAP: align==ALIGN.left?0: 0|(GAP/2) const r=image_make(size), t=frame; frame=draw_frame(r) if(lit(x)){ const tk=tab_cols(x), tc=tk.length, tr=tab_rowcount(x) const hh=tc?3+font_h(FONT_BODY):3, ch=font_h(FONT_MONO), fh=font_h(FONT_BODY), rows=0|min((size.y-(hh+fh))/ch,tr) const f=dyad.take(ZERO,x), cw=range(256).map(x=>0) for(let c=0;c=w){ve=1;break};cols++;if(c+1<=tc&&c+1<=256)tw+=cw[c]} xo=align==ALIGN.right?size.x-tw: align==ALIGN.left?0: 0|((size.x-tw)/2) let cx=xo;for(let c=0;c0;y--){let f=0;for(let x=0;x({ size :rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), show :ls(ifield(x,'show' )), locked:lb(ifield(x,'locked')), }) unpack_button=x=>({ size :rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), text :ls(ifield(x,'text' )), font : ifield(x,'font' ) , pattern :ln(ifield(x,'pattern' )), style :ls(ifield(x,'style' )), show :ls(ifield(x,'show' )), locked :lb(ifield(x,'locked' )), shortcut:ls(ifield(x,'shortcut')), }) unpack_slider=x=>({ size :rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), font : ifield(x,'font' ) , pattern:ln(ifield(x,'pattern')), format :ls(ifield(x,'format' )), show :ls(ifield(x,'show' )), style :ls(ifield(x,'style' )), locked :lb(ifield(x,'locked' )), step :ln(ifield(x,'step' )), value :ln(ifield(x,'value' )), min :getpair(ifield(x,'interval')).x, max :getpair(ifield(x,'interval')).y, }) unpack_canvas=x=>({ size :rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), scale :ln(ifield(x,'scale' )), border :lb(ifield(x,'border' )), draggable:lb(ifield(x,'draggable')), show :ls(ifield(x,'show' )), locked :lb(ifield(x,'locked' )), }) unpack_field=x=>({ size :rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), font : ifield(x,'font' ) , show :ls(ifield(x,'show' )), scrollbar:lb(ifield(x,'scrollbar')), border :lb(ifield(x,'border' )), pattern :ln(ifield(x,'pattern' )), style :ls(ifield(x,'style' )), locked :lb(ifield(x,'locked' )), align :ALIGN[ls(ifield(x,'align'))], }) unpack_field_value=x=>({ table : ifield(x,'value' ) , scroll:ln(ifield(x,'scroll')), }) unpack_grid=x=>({ size :rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), widths :ll(ifield(x,'widths')).map(ln), font : ifield(x,'font' ) , format :ls(ifield(x,'format' )), headers :lb(ifield(x,'headers' )), scrollbar:lb(ifield(x,'scrollbar')), lines :lb(ifield(x,'lines' )), bycell :lb(ifield(x,'bycell' )), show :ls(ifield(x,'show' )), locked :lb(ifield(x,'locked' )), }) unpack_grid_value=x=>({ table : ifield(x,'value' ) , scroll:ln(ifield(x,'scroll')), row :ln(ifield(x,'row' )), col :ln(ifield(x,'col' )), }) let cursor={default:'default',point:'pointer',ibeam:'text',drag:'grabbing'} let cursor_names={default:'default',point:'pointer',insert:'text',drag:'grabbing',none:'none',wait:'wait',no:'not-allowed',crosshair:'crosshair'} image_tiles=(count,size,image)=>range(count).map(x=>image_copy(image,rect(0,x*size,size,size))) TOOLS=image_tiles(12,16,image_read( '%%IMG3ABAAwAKEH3HLoN7aexEZWqHOGq90XQk2dRAYOqXCWptFlRHJed7H3Gk+9xIHDAqHlZHx6Kq9arpZU/LkRUWio/VjNR'+ 'K33O5WqxpVpwjyzqsUooJroNn7bjNRyPB7To6lz1A+L7wXRZOhR4hj6HeYmDQFNqcWhyZJeJUjR7WIY3ZZtLlkubImZlll2O'+ 'LGWUQUOdk6lNr5daciW7s6C+uquwvolqRKq8QiGArcITfY+3IKeEMySgndI9Y0QfdTevY8aOPszBsNpTdcRtsyTG13sn5+zn'+ '6S7t7+Li9Pb00fTFXfN98pDS5gtCxZXsU5aDAhJIVsDhIsKDBil4cE87UDYW9eRngYLZrzqGUjOZHqSHYUSRGixJUsK/Uhpc'+ 'jHpx8IF6qZ+Qcnn0upXLL8ObGOOHY2LI4zCpKjyY8ok4pCioWfE0csCwA=' )) ARROWS=image_tiles(8,12,image_read( '%%IMG3AAwAYAKEj2kReg1gknEqSVFjTPOnXaEFTlznpKp5iuW4uPHyPV/24hmFV76DWgmFwUuNdaSdgstlpOlUJqVFqi46zN'+ 'payOqT6wFDnWMmtNsDG7Wqnq39I2HTM5cbAxxhY24acdXnB7fDxZNXJ5cCQUeFGNLnBQPXBhn5NTdmVVi21RS29wUYGCejGD'+ 'iV2YmZWraYeqkWRgmoUAA=' )) CHECK=image_read('%%IMG0AAkABwCAAcGDY8Y2bBw4CBAA') CHECKS=[ '%%IMG0AA8ADQAAf/BAEEAQQBBAEEAQQBBAEEAQQBB/8AAA', '%%IMG0AA8ADQAAf/BgMFBQSJBFEEIQRRBIkFBQYDB/8AAA', '%%IMG0AA8ADQAAVVAAAEAQAABAEAAAQBAAAEAQAABVUAAA', '%%IMG0AA8ADQAAVVAgIFBQCIBFEAIARRAIgFBQICBVUAAA', ].map(image_read) LOCK =image_read('%%IMG0AAgACDhERP7+/v4A') ANIM =image_read('%%IMG0AAgACBAoKER8goIA') ZOOM =image_read('%%IMG0AAwADB4AIQBMgIxAv0C/QIxATIAhwB7gAHAAMA==') CORNERS=['%%IMG0AAUABf/mxISA','%%IMG0AAUABfk4GAgI','%%IMG0AAUABYSGx+f4','%%IMG0AAUABQgIGT/4'].map(image_read) GESTURES={ left: image_read('%%IMG3ABAAEAZAgHBILBqLoCToOFQGAsuj8wk1TqnVZhKLVV65XRB4DP2So2aqkpiOWrdqpnDqlnvl+DwxCA=='), right:image_read('%%IMG3ABAAEAZAgHBILA5BSJDRCAoEksuj0wldNqdUJDGJxVav3W4SHBaTy8/xeVoVrttSttaaVUYB47t+zw8C'), up: image_read('%%IMG3ABAAEAZAgHAIABmJSCIoAEomjcymc7gMMKdCqPU4rVqvTu2Xi/R+wUrzWEo9u9lF9Zksf7fd9qxRvCcrx1h6e0hB'), down: image_read('%%IMG3ABAAEAZAgFAIKhaHSCQowAQlicZlMzqUMq9YJxHLbVat3WO1e9V+yWIl+gkoctNJcMD8dE/ZYy8eOqfjl35/cENB'), } RADIOS=[ '%%IMG0ABAADgAAB4AYYCAQIBBACEAIQAhACCAQIBAYYAeAAAA=', '%%IMG0ABAADgAAB4Af4DhwMDBgGGAYYBhgGDAwOHAf4AeAAAA=', '%%IMG0ABAADgAAAAAAAAAAB4APwA/AD8APwAeAAAAAAAAAAAA=', '%%IMG0ABAADgAAB4Af4D/wP/B/+H/4f/h/+D/wP/Af4AeAAAA=', ].map(image_read) ICONS=[ '%%IMG0AAwADAAAAAM8BEPkQBRAFEAUQBRAFEAXf/AAAA==', '%%IMG0AAwADAABHwERgRFBEeEQIRAhECEQIRAhH+AAAA==', '%%IMG0AAwADAAAAgAGQA4jPqM+oz6gDiAGQAIAAAAAAA==', '%%IMG0AAwADAABH4EfwAHBH8M/wznDOcM5wz/hH+AAAA==', '%%IMG0AAwADAAAAgAFAAiBEEIjpERSLiEScAnwBWACAA==', '%%IMG0AAwADAAAAAEf4RShF6EUoRShF6EQoR/gAAAAAA==', '%%IMG0AAwADAABFUIqoRVCKqEVQiqhFUIqoRVCKqAAAA==', '%%IMG0AAwADAAAAAAAIABgAMRBhmMDNgEcAAgAAAAAAA==', ].map(image_read) HANDLES=[ '%%IMG3AAcACgZAQGA4FIKOxyEyGViClEsoUpo0VgHGAHarxQYB', '%%IMG3AAoABwZAQGBIBBgDoKQycFQ6mUjnMiqFSkHMI3FoBAQB', ].map(image_read) ICON={dir:0,doc:1,sound:2,font:3,app:4,lil:5,pat:6,chek:7,none:8} PANGRAM='How razorback jumping-frogs can level six piqued gymnasts.' // State let uimode='interact', ui_container=null, uicursor=0, enable_touch=0, set_touch=0, toolbar_scroll=0 let profiler=0, profiler_ix=0, profiler_hist=new Uint8Array(200) mark_dirty=_=>{dirty=1} con_set=x=>{ if(x!=ui_container)setmode(uimode),msg.next_view=1 if(x!=ui_container&&prototype_is(ui_container))contraption_update(deck,ui_container);ui_container=x } con=_=>ui_container?ui_container:ifield(deck,'card') con_wids=_=>con().widgets con_image=_=>ifield(con(),'image') con_size=_=>getpair(ifield(con(),'size')) con_dim=_=>rpair(rect(),con_size()) con_clip=_=>{const size=con_size();return dr.fatbits?rcenter(frame.clip,rmin(frame.size,rmul(size,dr.zoom))): rclip(frame.clip,rcenter(frame.clip,size))} con_offset=_=>{const r=con_clip();return rect(r.x,r.y)} con_to_screen=a=>radd(dr.fatbits?rmul(rsub(a,dr.offset),dr.zoom):a,con_offset()) screen_to_con=a=>{a=rsub(a,con_offset());return dr.fatbits?radd(rdiv(a,dr.zoom),dr.offset):a} con_view_dim=_=>{const a=screen_to_con(rect(0,0)),b=screen_to_con(frame.size);return rpair(a,rsub(b,a))} clamp_fatbits=_=>{dr.offset=rclamp(rect(0,0),dr.offset,rmax(rsub(con_size(),rdiv(frame.size,dr.zoom)),rect(0,0)))} center_fatbits=p=>{dr.offset=rsub(p,rdiv(rdiv(frame.size,dr.zoom),2)),clamp_fatbits()} ev_to_con=e=>{e.pos=screen_to_con(e.opos=rcopy(e.pos)),e.dpos=screen_to_con(e.odpos=rcopy(e.dpos)),pointer.prev=screen_to_con(pointer.prev);return e} con_to_ev=e=>{e.pos=e.opos,e.dpos=e.odpos,pointer.prev=con_to_screen(pointer.prev);return e} tracking=_=>{ const c=con() if(prototype_is(c)){ const defs=deck.contraptions, i=dkix(defs,ifield(c,'name')), n=count(defs) if(ev.dir=='left' )con_set(defs.v[(i+(n-1))%n]) if(ev.dir=='right')con_set(defs.v[(i+1 )%n]) } if(card_is(c)){ if(ev.dir=='left' )n_go([lms('Prev')],deck) if(ev.dir=='right')n_go([lms('Next')],deck) } } is_fullscreen=_=>(document.fullscreenElement||document.webkitFullscreenElement)!=null toggle_fullscreen=_=>{ if(is_fullscreen()){ if(document.exitFullscreen)document.exitFullscreen() if(document.webkitExitFullscreen)document.webkitExitFullscreen() }else{ const e=q('body'), o={navigationUI:'hide'} if(e.requestFullscreen)e.requestFullscreen(o) if(e.webkitRequestFullscreen)e.webkitRequestFullscreen(o) }setTimeout(resize,500) } set_fullscreen=x=>{ const i=is_fullscreen() if(i&&!x){toggle_fullscreen()} else if(!i&&x){modal_enter('fullscreen_lil')} } setmode=mode=>{ n_play([NIL,lms('loop')]) grid_exit(),field_exit(),bg_end_selection(),bg_end_lasso(),ob.sel=[],wid.active=-1,sc.others=[],dr.poly=[] msg.next_view =(uimode!=mode)&&mode=='interact' msg.pending_loop=(uimode!=mode)&&mode=='interact' uimode=mode;if(mode!='interact')msg.pending_halt=1;if(mode!='draw'&&!prototype_is(con()))dr.fatbits=0;if(mode=='interact')dr.fatbits=0 } event_state=_=>({ // event state mu:0,md:0, clicktime:0,click:0,rdown:0,rup:0,mdown:0, dclick:0, clicklast:0, down_modal:0, down_uimode:0, down_caps:0, drag:0, tab:0, shift:0, alt:0, action:0, dir:0, exit:0, eval:0, scroll:0, hidemenu:0, pos:rect(), dpos:rect(), rawpos:rect(), rawdpos:rect(), shortcuts:{}, opos:rect(),odpos:rect(), callback:null, callback_rect:null, callback_drag:0 }) let ev=event_state() over=r=>rin(r,ev.pos) dover=r=>rin(r,ev.dpos) wid_state=_=>({ // widget state active:0,count:0, scrolls:0,thumbid:0,thumbo:0, col_drag:0,col_num:0,col_orig:0, ingrid:0,g:null,gt:null,gv:null,pending_grid_edit:0, infield:0,f:null,ft:null,fv:null,field_dirty:0,change_timer:0, cursor:rect(),cursor_timer:0, hist:[],hist_cursor:0, }) wid_state_clone=x=>{const r=Object.assign({},x);r.cursor=rcopy(r.cursor),r.hist=r.hist.slice(0);return r} let wid=wid_state() modal_state=_=>({ // modal state type:null,subtype:null,in_modal:0,edit_json:0,old_wid:null, filter:0, grid:null,grid2:null, text:null,name:null,form0:null,form1:null,form2:null, desc:'',path:'',path_suffix:'',filter:'', message:null,verb:null, cell:rect(), from_listener:0,from_action:0,from_keycaps:0, act_go:0,act_card:0,act_gomode:0,act_trans:0,act_transo:0,act_sound:0, time_curr:0,time_end:0,time_start:0, carda:null,cardb:null,trans:null,canvas:null,hint:null,pending_grid_cell:rect(),pending_grid_cell_rich:0, }) modal_state_clone=x=>{const r=Object.assign({},x);r.cell=rcopy(r.cell);return r} let ms=modal_state(), ms_stack=[] modal_push=type=>{if(ms.type){ms_stack.push({ms:modal_state_clone(ms),wid:wid_state_clone(wid)})}modal_enter(type)} modal_pop=value=>{ const l=ms.type=='link'&&value?rtext_string(ms.text.table):null const p=value!=-1&&ms.type=='spanpattern' modal_exit(value);if(ms_stack.length){const c=ms_stack.pop();ms=c.ms,wid=c.wid} if(l){const c=rcopy(wid.cursor),s=field_linkspan(l);if(c.x{kc.shift=0,kc.lock=0,kc.alt=0,kc.comb=0,kc.on=1,ev.mu=ev.md=0} keycaps_enter=_=>{if(!enable_touch||kc.on)return;keycaps_force_enter()} let msg={ // interpreter event messages pending_halt:0,pending_view:0,pending_loop:0,next_view:0,overshoot:0, target_click:null,target_drag:null,target_release:null,target_order:null,target_run:null,target_link:null,target_ccell:null,target_change:null,target_navigate:null, arg_click:rect(),arg_drag:rect(),lastdrag:rect(),arg_release:rect(),arg_order:null,arg_run:null,arg_link:null,arg_ccell:null,arg_change:null,arg_navigate:null, } let li={hist:[],vars:new Map(),scroll:0} // listener state let ob={sel:[],show_bounds:1,show_names:0,show_cursor:0,show_margins:0,show_guides:1,move:0,move_first:0,resize:0,resize_first:0,handle:-1,prev:rect(),orig:rect(),pending_pattern:0} // object editor state let sc={target:null,others:[],next:null, f:null,prev_mode:null,xray:0,status:''} // script editor state script_save=x=>{const k=lms('script');mark_dirty();if(sc.target)iwrite(sc.target,k,x);if(sc.others)sc.others.map(o=>iwrite(o,k,x))} draw_state=_=>({ // drawing tools state tool:'pencil',brush:0,pattern:1,fill:0,erasing:0, dither_threshold:0, show_widgets:1,show_anim:1,trans:0,trans_mask:0,under:0,color:0,fatbits:0,offset:rect(), show_grid:0,snap:0,grid_size:rect(16,16), sel_here:rect(),sel_start:rect(),limbo:null,limbo_dither:0, scratch:null,mask:null,omask:null, pickfill:0, poly:[], zoom:4, lasso_dirty:0, }) let dr=draw_state() settool=tool=>{setmode('draw'),dr.tool=tool} bg_pat=_=>(dr.trans_mask&&dr.pattern==0)?32:dr.pattern bg_fill=_=>(dr.trans_mask&&dr.fill==0)?32:dr.fill bg_has_sel=_=>dr.tool=='select'&&(dr.sel_here.w>0||dr.sel_here.h>0) bg_has_lasso=_=>dr.tool=='lasso'&&dr.mask!=null sint=(x,a)=>a*(0|((0|(x+((x<0?-1:1)*a/2)))/a)) snap=p=>!dr.snap?p:rect(sint(p.x,dr.grid_size.x),sint(p.y,dr.grid_size.y),p.w,p.h) // position only snapr=r=>rpair(snap(r),snap(rect(r.w,r.h))) // position + dimensions snap_delta=p=>{const a=snap(p);return rect(a.x-p.x,a.y-p.y)} let au={target:null,mode:'stopped', head:0,sel:rect(), hist:[],hist_cursor:0, clip:null,tick:null, record_stream:null,norecord:0} // audio editor state byte_to_sample=b=>(b<<24>>24)/128 sample_to_byte=s=>0xFF&clamp(-127,s*128,127) // Menus const menu={heads:[],items:[],x:0,active:-1,stick:-1,lw:-1,sz:rect()} menu_label=_=>rect(menu.x,1,context.size.x-menu.x-2,1+font_h(FONT_MENU)) no_menu=_=>menu.active==-1&&menu.stick==-1 in_layer=_=>no_menu()&&(ms.type?ms.in_modal:1)&&((!running()&&!msg.overshoot)||ms.type!=null) in_widgets=_=>ms.type!=null?ms.in_modal:1 menus_off=_=>lb(ifield(deck,'locked')) menus_hidden=_=>uimode=='draw'&&ev.hidemenu&&ms.type==null menu_head=(name,enabled,t,b)=>({name,enabled,t,b}) // string,bool,rect,rect menu_entry=(name,enabled,check,shortcut,t,b)=>({name,enabled,check,shortcut,t,b}) // string,bool,bool,char,rect,rect menus_clear=_=>(menu.active=-1,menu.stick=-1) menu_setup=_=>(menu.x=10,menu.heads=[],menu.sz=rect(),menu.active=-1) menu_bar=(name,enabled)=>{ if(menus_off())enabled=0 const t=rpair(rect(menu.x,2),font_textsize(FONT_MENU,name)), b=rect(t.x-5,0,t.w+10,t.h+3), i=menu.heads.length menu.heads.push(menu_head(name,enabled,t,b)), menu.x=b.x+b.w+5; if(menus_hidden())return if(ev.click&&enabled&&over(b)){ev.mu=0;if(menu.stick==-1)menu.stick=i} if(menu.stick!=-1&&enabled&&over(b))menu.stick=i,menu.lw=0 if(menu.stick==-1){ if(ev.drag&&enabled&&over(b)&&ev.dpos.y!c?0: 10+font_textsize(FONT_MENU,'^'+c).x menu_check=(name,enabled,check,shortcut,func)=>{ if(!last(menu.heads).enabled)return 0 const sc=enabled&&shortcut&&ev.shortcuts[shortcut]; if(sc)delete ev.shortcuts[shortcut]; if(menu.heads.length-1!=menu.active&&menu.heads.length-1!=menu.stick)return sc const t=name?rpair(rect(menu.sz.x+5+8,menu.sz.y+menu.sz.h+2),font_textsize(FONT_MENU,name)): rect(menu.sz.x,menu.sz.y+menu.sz.h+2,1,1) if(shortcut)t.w+=shortcut_w(shortcut) const b=rect(menu.sz.x,menu.sz.y+menu.sz.h,max(menu.sz.w,t.w+10+8),t.h+4) menu.items.push(menu_entry(name,enabled,check,shortcut,t,b)), menu.sz=runion(menu.sz,b) if(enabled&&over(b)&&func)ev.callback=func,ev.callback_rect=rcopy(b),ev.callback_drag=1 return sc||(enabled&&ev.mu&&over(b)) } menu_item=(name,enabled,shortcut,func)=>menu_check(name,enabled,-1,shortcut,func) menu_separator=_=>menu_check(0,0,0,0,0) menu_finish=_=>{ if(menus_off()||menus_hidden())return const b=rect(0,0,context.size.x,3+font_h(FONT_MENU)); draw_rect(b,32),draw_hline(0,b.w,b.h,1); const pal=deck.patterns.pal.pix menu.heads.map((x,i)=>{ let a=x.enabled&&(over(x.b)||i==menu.stick||i==menu.active);if(ev.drag&&!dover(b))a=0 draw_text(x.t,x.name,FONT_MENU,x.enabled?1:13);if(a)draw_invert(pal,x.b) }) if(!menu.sz.w)return draw_shadow(menu.sz,1,32,1);menu.lw=0;let sw=0;menu.items.map(x=>{menu.lw=max(menu.lw,x.b.w),sw=max(sw,shortcut_w(x.shortcut))}) menu.items.map(x=>{ const o=over(x.b)&&x.name&&x.enabled if(x.name){draw_text(x.t,x.name,FONT_MENU,x.enabled?1:13)}else{draw_rect(rect(x.t.x+2,x.t.y,menu.sz.w-5,1),19)} if(x.check==1)draw_icon(rect(menu.sz.x+2,x.t.y+3),CHECK,x.enabled?1:13) if(x.shortcut)draw_text(rect(menu.sz.x+menu.sz.w-3-sw+10,x.t.y),'^'+x.shortcut,FONT_MENU,x.enabled?1:13) if(o)draw_invert(pal,inset(x.b,1)) });if(ev.mu)menu.stick=-1 } // Widgets widget_setup=_=>{ if(ev.mu||wid.active==-1)wid.col_drag=0;if(wid.active>=wid.count)wid.active=0 if((uimode=='interact'||ms.type!=null)&&ev.tab&&wid.count&&!(wid.infield&&wid.f.style=='code')&&!kc.on){ if(wid.ingrid)grid_exit() if(wid.infield)field_exit() wid.active+=ev.shift?-1:1 if(wid.active<0)wid.active+=wid.count;wid.cursor=rect() } if(uimode!='interact'&&uimode!='script'&&ms.type==null){ if(wid.ingrid ||wid.gv!=null)grid_exit() if(wid.infield||wid.fv!=null)field_exit() wid.active=-1 }wid.count=0,wid.scrolls=0,wid.ingrid=0,wid.infield=0 } scrollbar=(r,n,line,page,scroll,visible,inverted)=>{ const addscroll=x=>scroll=clamp(0,scroll+x,n) addscroll(0);const sz=ARROWS[0].size.x+2, fcol=!in_layer()?13:inverted?32:1, bcol=inverted?1:32 const b=rect(r.x+r.w-sz-2,r.y,sz+2,r.h), rr=rect(r.x+1,r.y,visible?r.w-b.w-1:r.w-2,r.h-2), pal=deck.patterns.pal.pix let dragging_thumb=(wid.thumbid==wid.scrolls++)&&dover(b) if(visible){ draw_box(b,0,fcol) arrow=(bb,base,dir)=>{ const a=n>0&&over(bb)&&!dragging_thumb, o=a&&(ev.mu||ev.drag)&&dover(r) draw_box(bb,0,fcol),draw_icon(rect(bb.x+2,bb.y+2),ARROWS[base+(o?2:0)],fcol) if(o&&ev.md)addscroll(line*dir);if(a&&!ev.drag)uicursor=cursor.point } arrow(rect(b.x,b.y ,sz+2,sz+2),0,-1) arrow(rect(b.x,b.y+b.h-sz-2,sz+2,sz+2),1, 1) if(n<=0||!in_layer())return {size:rr,scroll} const s=rect(b.x+1,b.y+sz+2,b.w-2,b.h-2*(sz+2));draw_rect(s,inverted?9:12) const thumb_height=0|max(16,s.h/(1+n)), thumb_y=0|((s.h-thumb_height)*(scroll/n)), thumb=rect(s.x,s.y+thumb_y,s.w,thumb_height) if(in_layer()&&ev.md&&over(thumb)){wid.thumbid=wid.scrolls-1,wid.thumbo=ev.dpos.y-thumb.y,dragging_thumb=1} if(in_layer()&&ev.drag&&dragging_thumb){ const capped=max(s.y,min(s.y+s.h-thumb.h,ev.pos.y-wid.thumbo))-s.y thumb.y=capped+s.y,scroll=max(0,0|min((capped/(s.h-thumb_height))*n,n)),uicursor=cursor.drag } if(in_layer()&&ev.mu&&dragging_thumb)wid.thumbid=-1 draw_rect(thumb,bcol),draw_box(thumb,0,fcol) inner=(bb,dir)=>{ if(!dragging_thumb&&over(bb))uicursor=cursor.point if(!dragging_thumb&&ev.mu&&over(bb))draw_invert(pal,bb),addscroll(page*dir) } if(thumb_y>0 )inner(rect(s.x,s.y ,s.w, thumb_y ),-1) if(thumb_y+thumb.h{ const l=x.locked||!in_layer(), pal=deck.patterns.pal.pix, font=x.font||FONT_MENU;let b=x.size const inv=x.show=='invert', fcol=l?13:inv?x.pattern:1, bcol=inv?1:x.pattern, scol=inv?x.pattern:1 const sel=!l&&x.show!='none'&&x.style!='invisible'&&wid.active==wid.count let sh=0,shh=0;if(!l&&uimode=='interact'&&!wid.fv&&!ev.shift&&x.show!='none'&&x.shortcut){if(keyup[x.shortcut]){shh=1}else if(keydown[x.shortcut]){sh=1}} const a=!l&&dover(b)&&over(b), cs=sel&&!func&&ev.action, cl=cs||sh||((ev.md||ev.drag)&&a), cr=cs||shh|(ev.mu&&a), so=x.pattern>=2&&x.pattern<=27 if(func&&a){ev.callback=func,ev.callback_rect=rcopy(b)} if(!l&&over(b)&&!ev.drag&&x.show!='none')uicursor=cursor.point if(x.show=='none')return 0; let ar=inset(b,2) if(x.style=='round'){ draw_boxr(b,fcol,bcol,x.show!='transparent') if(cl)draw_rect(ar,fcol) draw_text_align(inset(b,3),x.text,font,!cl?fcol: !so?bcol: inv?1:32,ALIGN.center,!so?0: cl?(fcol==1?1:32):(fcol==1?32:1)) if(sel)draw_box(ar,0,13) } if(x.style=='rect'){ if(cl){b=rect(b.x+1,b.y+1,b.w-1,b.h-1),ar=rect(ar.x+1,ar.y+1,ar.w-1,ar.h-1);if(x.show!='transparent')draw_rect(b,bcol);draw_box(b,0,fcol)} else {b=rect(b.x ,b.y ,b.w-1,b.h-1),ar=rect(ar.x ,ar.y ,ar.w-1,ar.h-1);draw_shadow(b,fcol,bcol,x.show!='transparent')} draw_text_align(inset(b,3),x.text,font,fcol,ALIGN.center,!so?0: fcol==1?32:1);if(sel)draw_box(ar,0,13) } if(x.style=='check'||x.style=='radio'){ if(x.show!='transparent')draw_rect(b,bcol) const ts=font_textsize(font,x.text), cdim=(x.style=='check'?CHECKS[0]:RADIOS[0]).size; ts.y=min(ts.y,b.h); const bh=max(ts.y,cdim.y) const br=rect(b.x,b.y+(0|((b.h-bh)/2)),b.w,bh), to=rclip(b,rect(br.x+cdim.x,0|(br.y+(br.h-ts.y)/2),b.w-cdim.x,ts.y)) draw_rect(rect(br.x+1,br.y+1,cdim.x-4,cdim.y-3),bcol) if(x.style=='check'){draw_icon(rect(br.x,br.y),CHECKS[(value^(cl||cr))+2*x.locked],scol)} else{const p=rect(br.x,br.y);draw_icon(p,RADIOS[3],bcol),draw_icon(p,RADIOS[cl||cr?1:0],fcol);if(value)draw_icon(p,RADIOS[2],fcol)} if(cl)draw_rect(to,fcol);draw_text_fit(to,x.text,font,cl?bcol:fcol);if(sel)draw_box(rect(to.x-2,to.y-1,to.w+2,to.h+2),0,13) } if(x.style=='invisible'){draw_text_align(inset(b,3),x.text,font,fcol,ALIGN.center,!so?0: fcol==1?32:1);if(cl&&x.show!='transparent')draw_invert(pal,ar)} if(target&&cr)msg.target_click=target if(!x.locked&&in_widgets())wid.count++ return cr } widget_slider=(target,x)=>{ const l=x.locked||!in_layer(), pal=deck.patterns.pal.pix, font=x.font||FONT_MENU let b=x.size, fcol=l?13:x.show=='invert'?32:1, bcol=x.show=='invert'?1:32, bpat=x.show=='invert'?9:12 const sel=!l&&x.show!='none'&&wid.active==wid.count, ov=x.value if(x.show=='none')return const t=ls((x.style=='bar'||x.style=='compact')?dyad.format(lms(x.format),lmn(x.value)):lms('')) const oc=frame.clip; frame.clip=rclip(b,frame.clip) const hv_btn=(bb,dir,ba)=>{ const a=!l&&over(bb), o=a&&(ev.mu||ev.drag)&&dover(bb) if(o&&ev.md)x.value+=(dir*x.step); if(a)uicursor=cursor.point draw_rect(bb,bcol),draw_box(bb,0,fcol),draw_icon(rint(rect(bb.x+(bb.w-12)/2,bb.y+(bb.h-12)/2)),ARROWS[ba+(o?2:0)],fcol) } const hv_tsz=(axis,tf,av,as)=>{ let drag=(wid.thumbid==wid.scrolls++)&&dover(b), n=0|((x.max-x.min)/x.step), ts=0|max(min(axis,16),axis/(1+n)), tp=0|(((x.value-x.min)/max(1,(x.max-x.min)))*(axis-ts)), thumb=rint(tf(tp,ts)) if(!l){ if(ev.md&&over(thumb))wid.thumbid=wid.scrolls-1,wid.thumbo=ev.dpos[av]-thumb[av],drag=1 if(ev.drag&&drag){ const capped=max(b[av],min(b[av]+b[as]-thumb[as],ev.pos[av]-wid.thumbo))-b[av] thumb[av]=capped+b[av],uicursor=cursor.drag,x.value=x.min+capped*((x.max-x.min)/(b[as]-ts)) }if(ev.mu&&drag)wid.thumbid=-1 }draw_rect(thumb,bcol),draw_box(thumb,0,fcol);return {drag,thumb} } const hv_gap=(drag,bb,dir)=>{if(!l&&!drag&&over(bb)){uicursor=cursor.point;if(ev.mu)draw_invert(pal,bb),x.value+=(dir*10*x.step);}} if(x.style=='horiz'||x.style=='vert'){if(x.show!='transparent')draw_rect(b,bpat);draw_box(b,0,sel?13:fcol)} if(x.style=='horiz'){ hv_btn(rect(b.x,b.y,16,b.h),-1,4),hv_btn(rect(b.x+b.w-16,b.y,16,b.h),1,5),b=rect(b.x+16,b.y+1,b.w-32,b.h-2) const a=hv_tsz(b.w,(tp,ts)=>rect(b.x+tp,b.y,ts,b.h),'x','w'), drag=a.drag, thumb=a.thumb hv_gap(drag,rect(b.x,b.y,thumb.x-b.x,b.h),-1),hv_gap(drag,rect(thumb.x+thumb.w,b.y,b.w-(thumb.x+thumb.w-b.x),b.h),1) } if(x.style=='vert'){ hv_btn(rect(b.x,b.y,b.w,16),-1,0);hv_btn(rect(b.x,b.y+b.h-16,b.w,16),1,1),b=rect(b.x+1,b.y+16,b.w-2,b.h-32) const a=hv_tsz(b.h,(tp,ts)=>rect(b.x,b.y+tp,b.w,ts),'y','h'), drag=a.drag, thumb=a.thumb hv_gap(drag,rect(b.x,b.y,b.w,thumb.y-b.y),-1),hv_gap(drag,rect(b.x,thumb.y+thumb.h,b.w,b.h-(thumb.y+thumb.h-b.y)),1) } if(x.style=='bar'){ fcol=x.locked?(x.show=='invert'?32:1):fcol if(x.show!='transparent')draw_rect(b,bcol);draw_box(b,0,sel?13:fcol) b=inset(b,2),draw_textc(b,t,font,fcol);if(!l&&over(b))uicursor=cursor.point const f=(x.max==x.min)?0:((x.value-x.min)/(x.max-x.min))*b.w;draw_invert(pal,rect(b.x,b.y,0|f,b.h)) if(!l&&dover(b)&&(ev.md||ev.drag)){x.value=x.min+(ev.pos.x-b.x)*((x.max-x.min)/b.w),uicursor=cursor.drag} } if(x.style=='compact'){ if(x.show=='transparent')draw_rect(rect(b.x+1,b.y+1,13,b.h-2),bcol),draw_rect(rect(b.x+b.w-14,b.y+1,13,b.h-2),bcol) draw_boxr(b,fcol,bcol,x.show!='transparent') const tr=rect(b.x+14,b.y,b.w-28,b.h), ccol=x.show=='invert'?1:x.pattern draw_rect(rect(tr.x,tr.y+1,tr.w,tr.h-2),ccol),draw_textc(tr,t,font,fcol==ccol?bcol:fcol) const comp_btn=(xo,dir,ba,li,en)=>{ const bb=rect(b.x+xo,b.y,14,b.h), a=en&&!l&&over(bb), o=a&&(ev.mu||ev.drag)&&dover(bb) if(o&&ev.md)x.value+=(dir*x.step); if(a)uicursor=cursor.point draw_icon(rect(bb.x+1,0|(b.y+(b.h-12)/2)),ARROWS[ba+(o?2:0)],en?fcol:13),draw_vline(bb.x+li,b.y+1,b.y+b.h-1,sel?13:fcol) };comp_btn(0,-1,4,13,x.value!=x.min),comp_btn(b.w-14,1,5,0,x.value!=x.max) } if(sel&&ev.dir=='up')x.value-=x.step;if(sel&&ev.dir=='down')x.value+=x.step if(!x.locked&&in_layer()&&over(frame.clip)&&ev.scroll)x.value+=x.step*ev.scroll; x.value=slider_normalize(target,x.value) if(target&&Math.abs(ov-x.value)>(x.step/2)){msg.target_change=target,msg.arg_change=lmn(x.value);iwrite(target,lms('value'),msg.arg_change),mark_dirty()} if(!x.locked&&in_widgets())wid.count++ frame.clip=oc } widget_canvas=(target,x,image)=>{ if(x.show=='none')return; const b=x.size, pal=deck.patterns.pal.pix if(x.show=='solid'){if(image){draw_scaled(b,image,1)}else{draw_rect(b,0)}} if(image&&x.show=='transparent')draw_scaled(b,image,0) if(image&&x.show=='invert')draw_invert_scaled(pal,b,image) if(x.border){if(x.show=='invert'){draw_boxinv(pal,b)}else{draw_box(b,0,1)}} if(!target||!in_layer())return if(x.draggable){ const sel=ob.sel.length&&ob.sel[0]==target if (ev.md&&dover(b))msg.target_click =target,msg.arg_click =rect(b.x,b.y),ob.sel=[target],ob.prev=rint(rsub(ev.pos,b)) else if(ev.mu &&sel)msg.target_release=target,msg.arg_release=rsub(ev.dpos,ob.prev),ob.sel=[] else if(ev.drag &&sel)msg.target_drag =target,msg.arg_drag =rsub(ev.dpos,ob.prev) }else if(dover(b)){ const p=rint(rect((ev.pos.x-b.x)/x.scale,(ev.pos.y-b.y)/x.scale)), dp=p.x!=msg.lastdrag.x||p.y!=msg.lastdrag.y, im=over(b) if (ev.md )msg.target_click =target,msg.arg_click =p,msg.lastdrag=p else if(ev.mu )msg.target_release=target,msg.arg_release=p else if(ev.drag&&dp&&im)msg.target_drag =target,msg.arg_drag =p,msg.lastdrag=p } } grid_exit=_=>{ wid.ingrid=0,wid.gv=null,wid.gt=null if(wid.hist)wid.hist=[],wid.hist_cursor=0 } widget_grid=(target,x,value)=>{ if(x.show=='none')return 0; const hfnt=FONT_BODY, hsize=x.headers?font_h(hfnt)+5:0, showscroll=x.size.h<=(50+hsize)||x.size.w<16?0:x.scrollbar const fnt=x.font?x.font:FONT_MONO, os=value.scroll, or=value.row, oc=value.col, files=x.headers==2, headers=files||x.size.h<=hsize?0:x.headers const tk=tab_cols(value.table), nc=tk.length, rh=font_h(fnt)+(x.lines?5:3), _bg=tk.indexOf('_bg'), _fg=tk.indexOf('_fg'), _hideby=tk.indexOf('_hideby') const p=grid_pv(target), nr=p.pv?p.pv.length: tab_rowcount(value.table) const grid_cell_at=(col,row)=>tab_cell(value.table,col,p.permuted_row(row)) const fcol=!in_layer()?13:x.show=='invert'?32:1, bcol=x.show=='invert'?1:32, b=x.size, pal=deck.patterns.pal.pix let sel=in_layer()&&x.show!='none'&&wid.active==wid.count if(in_layer()&&dover(b)&&(ev.md||ev.mu||ev.drag)){if(!sel&&wid.gv)grid_exit(); wid.active=wid.count,sel=1} if(sel&&in_layer()&&!over(b)&&ev.md)sel=0,wid.active=-1,grid_exit() if(sel){if(wid.fv)field_exit();wid.ingrid=1,wid.g=x,wid.gv=value,wid.gt=target}; if(x.show!='transparent')draw_rect(b,bcol) const bh=rect(b.x,b.y,b.w,headers?font_h(hfnt)+5:0), nrd=0|min(nr,((b.h-bh.h+1)/rh)), scrollmax=nr-nrd const sbar=scrollbar(rect(b.x,b.y+(headers?bh.h-1:0),b.w,b.h-(headers?bh.h-1:0)),scrollmax,1,nrd,value.scroll,showscroll,x.show=='invert') const bb=sbar.size; value.scroll=sbar.scroll draw_box(x.lines?b:rect(b.x,bb.y,b.w,b.h-(bb.y-b.y)),0,sel?13:fcol) const cw=[];for(let z=0;z=x.widths.length?-1:x.widths[z]) if(_bg!=-1)cw[_bg]=0;if(_fg!=-1)cw[_fg]=0;if(_hideby!=-1)cw[_hideby]=0 const hwt=x.widths.reduce((sofar,_,i)=>sofar+cw[i],0), nwt=cw.filter(x=>x==-1).length for(let z=x.widths.length;z{let cx=0;for(let z=0;z{const r=inset(grid_cell(pos),x.lines?1:0);if(pos.x&&x.lines)r.x+=1,r.w-=1;return r} if(x.lines)draw_rect(bh,fcol);if(nc<=0)draw_textc(inset(bb,1),'(no data)',hfnt,fcol) const rowb=n=>rect(bb.x,bb.y+rh*n,bb.w,rh) const rowh=n=>inset(rect(bb.x+1,bb.y+rh*n+2,bb.w-2,rh-3),x.lines?0:-1) let clicked=0,rsel=0,hrow=-1,hcol=-1;for(let y=0;y=0&&rr=0&&value.col=0?grid_hcell(rect(value.col,rr)): rowh(rr),fcol) } let drawncol=0;for(let z=0,cols=0,cx=0;zr.h)continue; pos.x+=r.x, pos.y+=r.y // coarse clip if(lis(g.arg)&&count(g.arg)&&dover(pos)&&over(pos)){ if(ev.md||ev.drag){alink=g.arg;break} if(ev.mu){msg.target_link=target,msg.arg_link=g.arg} } } draw_text_rich_raw(r,l,ccol,0,alink) } else{('fcCihH'.indexOf(x.format[z])>=0?draw_textr:draw_text_fit)(rect(hs.x+1,bb.y+rh*y,hs.w-2,rh),cf,fnt,ccol)} // right-align numeric frame.clip=oc if(!x.locked&&sel&& ((ev.dclick&&over(cell)) || (ev.action&&x.bycell&&z==value.col&&y+value.scroll==p.display_row(value.row)))){ const f=x.format[z]||'s', tc=rect(z,y+value.scroll), tpc=rect(tc.x,p.permuted_row(tc.y)) if (f=='I'||f=='L'||f=='T'){} // no editing allowed else if(f=='B'||f=='b'){grid_edit_cell(tpc,lms(0+(!lb(v))))} // toggle else{ ms.pending_grid_cell_rich=f=='t' wid.pending_grid_edit=1,ms.pending_grid_cell=grid_cell(rect(tc.x,tc.y-value.scroll)) ms.pending_grid_cell.y-=1,ms.pending_grid_cell.w+=1,ms.pending_grid_cell.h+=2 ms.cell=tpc,ms.text=fieldstr(f=='t'?v:lms(cf)) } } } } if(x.lines)for(let y=1;yb.x+b.w)break if(over(h))draw_vline(h.x+2,h.y,h.y+h.h,13) if(ev.md&&dover(h))wid.col_drag=1,wid.col_num=z,wid.col_orig=cw[z] if(sel&&wid.col_drag&&wid.col_num==z&&ev.drag){ const s=min(max(10,wid.col_orig+(ev.pos.x-ev.dpos.x)),bb.w-10),i=z;uicursor=cursor.drag iwrite(target,lms('widths'),lml(range(max(x.widths.length,i+1)).map(z=>lmn(i==z?s:cw[z])))),mark_dirty() } } if(target&&os!=value.scroll)iwrite(target,lms('scroll'),lmn(value.scroll)),mark_dirty() if(target&&or!=value.row)iwrite(target,lms('row'),lmn(value.row)),mark_dirty() if(target&&oc!=value.col)iwrite(target,lms('col'),lmn(value.col)),mark_dirty() if(target&&clicked)msg.target_click=target,msg.arg_click=rect(0,value.row) if(in_widgets())wid.count++ return files?((clicked&&ev.dclick)||(sel&&ev.action)): clicked } grid_format=_=>lms(wid.g.format.length?wid.g.format:'s'.repeat(tab_cols(wid.gv.table).length)) grid_apply=v=>{ wid.gv.table=v,wid.gv.row=-1;if(!wid.gt)return iwrite(wid.gt,lms('value'),v),iwrite(wid.gt,lms('row'),lmn(-1)),mark_dirty() msg.target_change=wid.gt,msg.arg_change=v } grid_undo=_=>{const x=wid.hist[--(wid.hist_cursor)];grid_apply(x[0])} grid_redo=_=>{const x=wid.hist[(wid.hist_cursor)++];grid_apply(x[1])} grid_edit=v=>{wid.hist=wid.hist.slice(0,wid.hist_cursor),wid.hist.push([wid.gv.table,v]),grid_redo()} grid_deleterow=_=>grid_edit(dyad.drop(lml([lmn(wid.gv.row)]),wid.gv.table)) grid_insertrow=_=>{ const f=ls(grid_format()), x=wid.gv.table, r=lmt(), s=wid.gv.row+1 tab_cols(x).map((k,col)=>{ const o=tab_get(x,k) tab_set(r,k,range(o.length+1).map(i=>(i==s)?('slurotT'.indexOf(f[col])>=0?lms(''):ZERO): o[i-(i>=s?1:0)])) }) if(count(r)==0)tab_set(r,'value',[NIL]) grid_edit(r),iwrite(wid.gt,lms('col'),ZERO),iwrite(wid.gt,lms('row'),lmn(s)) const os=wid.gv.scroll,ns=grid_scrollto(wid.gt,wid.g,os,s);if(os!=ns){wid.gv.scroll=ns,iwrite(wid.gt,lms('scroll'),lmn(ns))} } grid_edit_cell=(cell,v)=>{ wid.gv.col=cell.x,iwrite(wid.gt,lms('col'),lmn(cell.x)) wid.gv.row=cell.y,iwrite(wid.gt,lms('row'),lmn(cell.y)) msg.target_ccell=wid.gt,msg.arg_ccell=v } grid_keys=(code,shift)=>{ const fnt=wid.g.font?wid.g.font:FONT_MONO, hfnt=FONT_BODY, tk=tab_cols(wid.gv.table), nc=tk.length const p=grid_pv(wid.gt), npr=tab_rowcount(wid.gv.table), nr=p.pv?p.pv.length: npr let m=0, r=wid.gv.row, c=wid.gv.col const rh=font_h(fnt)+5, bh=wid.g.headers?font_h(hfnt)+5:0, nrd=min(nr,0|((wid.g.size.h-bh+1)/rh)), wd=wid.g.widths const cw=[];for(let z=0;z=wd.length?1:wd[z]) {const _bg =tk.indexOf('_bg' );if(_bg !=-1)cw[_bg ]=0} {const _fg =tk.indexOf('_fg' );if(_fg !=-1)cw[_fg ]=0} {const _hideby=tk.indexOf('_hideby');if(_hideby!=-1)cw[_hideby]=0} if(code=='Home' ){m=1,r=p.pv?first(p.pv): 0} if(code=='End' ){m=1,r=p.pv?last (p.pv): npr-1} if(code=='ArrowUp' ){m=1;if(r==-1){r=p.pv?first(p.pv):0}else if(!p.pv){r-=1}else{for(let z=p.pv.length-1;z>=0;z--){const n=p.pv[z];if(nr){r=n;break}}}} if(code=='PageUp' ){m=1;if(r==-1){r=p.pv?first(p.pv):0}if(!p.pv){r-=nrd}else{for(let c=0,z=p.pv.length-1;z>=0&&cr)r=n,c++}}} if(code=='ArrowLeft' ){m=1;if(c==-1){c=0}else{let d=c-1;while(d>0 &&cw[d]==0)d--;if(d>=nc||((d!=-1)&&cw[d]))c=d}} if(code=='ArrowRight'){m=1;if(c==-1){c=0}else{let d=c+1;while(d=nc||((d!=-1)&&cw[d]))c=d}} if(!wid.g.locked&&(code=='Backspace'||code=='Delete'))grid_deleterow() if(!m)return;if(ms.type=='prototype_attrs')ms.text.table=ms.name.table=null wid.gv.row=r=max(0,min(r,npr-1)),wid.gv.col=c=max(0,min(c,nc-1));if(wid.gt){ iwrite(wid.gt,lms('row'),lmn(r)),iwrite(wid.gt,lms('col'),lmn(c)),mark_dirty() msg.target_click=wid.gt,msg.arg_click=rect(0,r) } const os=wid.gv.scroll,rs=p.display_row(r);if(rs-os<0)wid.gv.scroll=rs;if(rs-os>=nrd)wid.gv.scroll=rs-(nrd-1) if(wid.gt&&os!=wid.gv.scroll)iwrite(wid.gt,lms('scroll'),lmn(wid.gv.scroll)),mark_dirty() } layout_index=(x,p)=>{ for(let z=0;zl.y+l.h)continue const r=x.lines[z].range;if(p.yx.layout.length>0?last(x.layout): {pos:rect(0,0,1,font_h(font)),line:0,char:'\0',font,arg:ZERO,pat:1} layout_cursor=(x,index,font,f)=>{ const bw=f.size.w-5-(f.scrollbar?ARROWS[0].size.x+3:0), bx=f.align=='center'?0|(bw/2): f.align==ALIGN.right?bw: 0 const r=x.layout.length>0?rcopy(x.layout[min(index,x.layout.length-1)].pos):rect(bx,0,1,font_h(font)) if(index>=x.layout.length){if(layout_last(x,font).char=='\n'){r.x=0,r.y+=r.h}else{r.x+=r.w-1}} r.w=1;return r } field_change=_=>{ if(!wid.field_dirty||!wid.ft)return field_notify_disable=1,iwrite(wid.ft,lms('value'),wid.fv.table),mark_dirty(),field_notify_disable=0 msg.target_change=wid.ft, msg.arg_change=rtext_string(wid.fv.table) } field_exit=_=>{field_change(),kc.on=0;wid.infield=0,wid.fv=null,wid.ft=null,wid.cursor=rect(),wid.field_dirty=0,wid.change_timer=0} widget_field=(target,x,value)=>{ if(x.show=='none')return; if(x.size.h<=50||x.size.w<16)x.scrollbar=0 const l=!in_layer(), fnt=x.font?x.font: x.style=='code'?FONT_MONO: FONT_BODY, b=x.size, pal=deck.patterns.pal.pix const fcol=(l&&!x.locked)?13:x.show=='invert'?32:1, bcol=x.show=='invert'?x.pattern:32, os=value.scroll const tfcol=(l&&!x.locked&&x.pattern==1)?13:x.show=='invert'?32:x.pattern if(x.show!='transparent')draw_rect(rclip(b,frame.clip),bcol); if(x.border)draw_box(b,0,fcol) let bi=inset(b,2);if(x.scrollbar)bi.w-=ARROWS[0].size.x+3 if(!l&&!x.locked&&over(bi)&&(ev.drag?dover(bi):1))uicursor=cursor.ibeam const layout=layout_richtext(deck,value.table,fnt,x.align,bi.w), last=layout_last(layout,fnt), eol=last.char!='\n'?0: last.pos.h const sbar=scrollbar(b,max(0,(last.pos.y+last.pos.h+eol)-bi.h),10,bi.h,value.scroll,x.scrollbar,x.show=='invert');value.scroll=sbar.scroll let sel=!x.locked&&!l&&wid.active==wid.count // find active link (if any) let alink=null;if(x.locked&&!sel&&in_layer()&&x.locked&&(ev.md||ev.drag))for(let z=0;zbi.y+bi.h)continue; pos.x+=bi.x, pos.y+=bi.y // coarse clip if(lis(g.arg)&&count(g.arg)&&dover(pos)&&over(pos)){alink=g.arg;break} } if(!x.locked&&!l&&dover(bi)&&(ev.md||ev.mu||ev.drag)){ const i=layout_index(layout,rect(ev.pos.x-bi.x,ev.pos.y-bi.y+value.scroll)) if(ev.md&&!ev.shift){wid.cursor.x=wid.cursor.y=i}else{wid.cursor.y=i} if(ev.dclick){ // double-click to select a word or whitespace span: let a=0, w=layout.layout.length&&/\s/g.test(layout.layout[min(wid.cursor.y,layout.layout.length-1)].char) a=wid.cursor.y;while(a>=0&&abi.h)value.scroll+=clamp(1,(c.y+ch)-bi.h,4) // drag to scroll! if(!sel&&wid.fv&&!kc.on)field_exit(); wid.active=wid.count,sel=1 } if(sel&&in_layer()&&!over(b)&&ev.md&&!kc.on)sel=0,wid.active=-1,field_exit() if(sel){if(wid.gv)grid_exit();wid.infield=1,wid.f=x,wid.fv=value,wid.ft=target,keycaps_enter()} // render const bc=rclip(frame.clip,bi); const oc=frame.clip;frame.clip=bc const selcol=((tfcol==0||tfcol==32)&&(bcol==0||bcol==32))?47: // try to avoid completely illegible text/selection ((tfcol==1||tfcol==47)&&(bcol==1||bcol==47))?32: tfcol for(let z=0;zbc.y+bc.h)continue; pos.x+=bi.x, pos.y+=bi.y // coarse clip if(lis(g.arg)&&count(g.arg)){ draw_hline(pos.x,pos.x+pos.w,pos.y+pos.h-1,alink==g.arg?fcol:19) const a=x.locked&&in_layer()&&over(pos)&⌖if(a&&!ev.drag)uicursor=cursor.point if(a&&ev.mu&&dover(pos))msg.target_link=target,msg.arg_link=g.arg } const csel=sel&&wid.cursor.x!=wid.cursor.y&&z>=min(wid.cursor.x,wid.cursor.y)&&z{ const b=wid.f.size, bi=inset(b,2);if(wid.f.scrollbar)bi.w-=ARROWS[0].size.x+3 const fnt=wid.f.font?wid.f.font: wid.f.style=='code'?FONT_MONO: FONT_BODY const layout=layout_richtext(deck,wid.fv.table,fnt,wid.f.align,bi.w), os=wid.fv.scroll const c=layout_cursor(layout,wid.cursor.y,fnt,wid.f), ch=min(bi.h,c.h);c.y-=wid.fv.scroll if(c.y<0){wid.fv.scroll+=c.y}if(c.y+ch>=bi.h){wid.fv.scroll+=((c.y+ch)-bi.h)} if(wid.ft&&os!=wid.fv.scroll)iwrite(wid.ft,lms('scroll'),lmn(wid.fv.scroll)),mark_dirty() return layout } field_apply=(v,c)=>{ wid.fv.table=v,wid.cursor=rect(c.x,c.y);if(wid.cursor.x<0)wid.cursor.x=0;if(wid.cursor.y<0)wid.cursor.y=0 field_showcursor(),wid.field_dirty=1,wid.change_timer=FIELD_CHANGE_DELAY } field_undo=_=>{const x=wid.hist[--(wid.hist_cursor)];field_apply(x[0],x[1])} field_redo=_=>{const x=wid.hist[(wid.hist_cursor)++];field_apply(x[2],x[3])} field_edit=(font,arg,pat,text,pos)=>{ const c=rect(), spliced=rtext_splice(wid.fv.table,font,arg,pat,text,pos,c); wid.hist=wid.hist.slice(0,wid.hist_cursor) wid.hist.push([wid.fv.table,rect(wid.cursor.x,wid.cursor.y), spliced,c]),field_redo() } field_editr=(rtext,pos)=>{ const c=rect(), spliced=rtext_splicer(wid.fv.table,rtext,pos,c); wid.hist=wid.hist.slice(0,wid.hist_cursor) wid.hist.push([wid.fv.table,rect(wid.cursor.x,wid.cursor.y), spliced,c]),field_redo() } field_sel_lines=_=>{ let a=min(wid.cursor.x,wid.cursor.y),b=max(wid.cursor.x,wid.cursor.y),l=field_showcursor() while(a &&l.layout[a-1].char!='\n')a-- while(b{ const s=field_sel_lines(), p=s.sel, layout=s.layout.layout; let ac=1,z=p.x;while(z{ const s=field_sel_lines(), p=s.sel, layout=s.layout.layout; let r='',z=p.x;while(z{const s=rtext_span(wid.fv.table,wid.cursor);tab_get(s,'font').fill(font ),field_editr(rtext_cat([s]),wid.cursor)} field_patspan =pat =>{const s=rtext_span(wid.fv.table,wid.cursor);tab_get(s,'pat' ).fill(lmn(pat)),field_editr(rtext_cat([s]),wid.cursor)} field_linkspan=link=>{ const s=rtext_span(wid.fv.table,wid.cursor),t=tab_get(s,'text'),a=tab_get(s,'arg');let sk=0 a.map((v,i)=>{if(image_is(v)){t[i]=lms(''),sk++}else{a[i]=link}}) field_editr(rtext_cat([s]),wid.cursor);return sk } field_input_raw=text=>{ const t=wid.fv.table, i=rtext_get(t,wid.cursor.y), f=i<0?lms(''):tab_cell(t,'font',i), p=i<0?1:ln(tab_cell(t,'pat',i)) field_edit(f,lms(''),p,clchars(text),wid.cursor) } field_input=text=>{ if(text=='\n'){if(ms.type=='save')ev.action=1;if(ms.type=='save'||ev.shift)return} field_input_raw(text) } field_keys=(code,shift)=>{ if(code=='Enter'&&ms.type=='gridcell'){modal_exit(1),ev.action=0;return} const b=wid.f.size, bi=inset(b,2);if(wid.f.scrollbar)bi.w-=ARROWS[0].size.x+3 const fnt=wid.f.font?wid.f.font: wid.f.style=='code'?FONT_MONO: FONT_BODY, layout=layout_richtext(deck,wid.fv.table,fnt,wid.f.align,bi.w) let m=0, s=wid.cursor.x!=wid.cursor.y const l=wid.cursor.y>=layout.layout.length?layout.lines.length-1:layout.layout[wid.cursor.y].line, c=layout_cursor(layout,wid.cursor.y,fnt,wid.f) if(code=='ArrowLeft' ){m=1;if(s&&!shift){wid.cursor.x=wid.cursor.y=min(wid.cursor.x,wid.cursor.y)}else{wid.cursor.y--}} if(code=='ArrowRight' ){m=1;if(s&&!shift){wid.cursor.x=wid.cursor.y=max(wid.cursor.x,wid.cursor.y)}else{wid.cursor.y++}} if(code=='ArrowUp' ){m=1;if(l>=0)wid.cursor.y=layout_index(layout,rect(c.x-1,layout.lines[l].pos.y -1 ))} if(code=='ArrowDown' ){m=1;if(l>=0)wid.cursor.y=layout_index(layout,rect(c.x-1,layout.lines[l].pos.y+layout.lines[l].pos.h+1 ))} if(code=='PageUp' ){m=1;if(l>=0)wid.cursor.y=layout_index(layout,rect(c.x-1,layout.lines[l].pos.y -bi.h))} if(code=='PageDown' ){m=1;if(l>=0)wid.cursor.y=layout_index(layout,rect(c.x-1,layout.lines[l].pos.y+layout.lines[l].pos.h+bi.h))} if(code=='Home' ){m=1;if(ev.alt){wid.cursor.y=0 }else if(l>=0)wid.cursor.y=layout.lines[l].range.x;} if(code=='End' ){m=1;if(ev.alt){wid.cursor.y=layout.layout.length}else if(l>=0)wid.cursor.y=layout.lines[l].range.y+(l==layout.lines.length-1?1:0);} if(code=='Backspace' ){field_edit(lms(''),lms(''),1,'',s?wid.cursor:rect(wid.cursor.y-1,wid.cursor.y))} if(code=='Delete' ){field_edit(lms(''),lms(''),1,'',s?wid.cursor:rect(wid.cursor.y,wid.cursor.y+1))} if(code=='Enter' ){ if(shift&&wid.ft){field_change(),msg.target_run=wid.ft,msg.arg_run=rtext_string(wid.fv.table,s?wid.cursor:rect(0,RTEXT_END))} else{ let i=0;if(wid.f.style=='code'){ const sl=field_sel_lines(),s=sl.sel,layout=sl.layout.layout while(s.x{ const show=ls(ifield(x,'show'));if(show=='none')return const b=rpair(getpair(ifield(x,'pos')),getpair(ifield(x,'size'))), image=ifield(x,'image') const oc=frame.clip,pal=deck.patterns.pal.pix;frame.clip=rclip(frame.clip,b) draw_9seg(b,frame.image,image,getrect(ifield(x.def,'margin')),frame.clip,show=='solid',show=='invert'?pal:null) handle_widgets(x.widgets,rect(b.x,b.y)),frame.clip=oc } let attrs=[], attrs_scroll=0 widget_attributes=b=>{ draw_box(b,0,1) let h=5,lw=0;attrs.map(a=>{h+=a.height+5;if(a.type!='bool')lw=max(lw,font_textsize(FONT_MENU,a.label).x)}) const sbar=scrollbar(b,max(0,h-b.h),10,b.h,attrs_scroll,1,0), bi=sbar.size;attrs_scroll=sbar.scroll,bi.y+=1 const oc=frame.clip;frame.clip=bi,lw=0|min(lw+10,bi.w*.6) const bp=ev.pos,bd=ev.dpos,bs=ev.scroll;ev.scroll=0;if(!over(bi))ev.pos=rect(-1,-1);if(!dover(bi))ev.dpos=rect(-1,-1) let y=5;attrs.map(a=>{ const lb=rect(bi.x+5,bi.y+y-attrs_scroll,lw,a.height), wb=rect(lb.x+lb.w+5,lb.y,(bi.w-15)-lb.w,lb.h);y+=lb.h+5 if (a.type=='bool'){lb.w=bi.w-10;if(ui_checkbox(lb,a.label,1,a.bval))a.bval^=1;return} else if(a.type=='note'){lb.w=bi.w-10;const l=layout_plaintext(a.label,FONT_BODY,ALIGN.left,rect(lb.w,lb.h));draw_text_rich_raw(lb,l,1,1,null);return;} else{draw_text_fit(lb,a.label,FONT_MENU,1)} if(a.type=='number')ui_field(wb,a.value) if(a.type=='string')ui_field(wb,a.value) if(a.type=='code' )ui_codeedit(wb,1,a.value) if(a.type=='data' )ui_codeedit(wb,1,a.value) if(a.type=='rich' )ui_richedit(wb,1,a.value) });frame.clip=oc,ev.pos=bp,ev.dpos=bd,ev.scroll=bs } handle_widgets=(x,offset)=>{ x.v.map(w=>{ if(button_is(w)){ const v=lb(ifield(w,'value')), p=unpack_button(w);p.size=radd(p.size,offset) if(widget_button(w,p,v)&&p.style=='check')iwrite(w,lms('value'),lmn(!v)),mark_dirty() } if(slider_is(w)){const p=unpack_slider(w);p.size=radd(p.size,offset);widget_slider(w,p)} if(canvas_is(w)){const p=unpack_canvas(w);p.size=radd(p.size,offset);widget_canvas(w,p,container_image(w,0))} if(grid_is (w)){const p=unpack_grid(w),v=unpack_grid_value(w);p.size=radd(p.size,offset);widget_grid(w,p,v);if(wid.gt==w)wid.gv=v} if(field_is(w)){ if(wid.ft==w){widget_field(w,wid.f,wid.fv)} else{const p=unpack_field(w),v=unpack_field_value(w);p.size=radd(p.size,offset);widget_field(w,p,v);if(wid.ft==w)wid.fv=v} } if(contraption_is(w))widget_contraption(w) }) } ui_button =(r,label, enable,func )=>widget_button(null,{text:label,size:r,font:FONT_MENU,pattern:32,style:'round',show: 'solid',locked:!enable},0,func) ui_toggle =(r,label,inv,enable,func )=>widget_button(null,{text:label,size:r,font:FONT_MENU,pattern:32,style:'round',show:inv?'invert':'solid',locked:!enable},0,func) ui_radio =(r,label, enable,value)=>widget_button(null,{text:label,size:r,font:FONT_BODY,pattern:32,style:'radio',show: 'solid',locked:!enable},value) ui_checkbox=(r,label, enable,value)=>widget_button(null,{text:label,size:r,font:FONT_BODY,pattern:32,style:'check',show: 'solid',locked:!enable},value) ui_field =(r, value)=>widget_field(null,{size:r,font:FONT_BODY,show:'solid',scrollbar:0,border:1,style:'plain',align:ALIGN.left,locked:0,pattern:1},value) ui_dfield =(r,enable,value)=>widget_field(null,{size:r,font:FONT_BODY,show:'solid',scrollbar:0,border:1,style:'plain',align:ALIGN.left,locked:!enable,pattern:1},value) ui_textedit=(r,border,value)=>widget_field(null,{size:r,font:FONT_BODY,show:'solid',scrollbar:1,border ,style:'plain',align:ALIGN.left,locked:0,pattern:1},value) ui_codeedit=(r,border,value)=>widget_field(null,{size:r,font:FONT_MONO,show:'transparent',scrollbar:1,border ,style:'code' ,align:ALIGN.left,locked:running(),pattern:1},value) ui_richedit=(r,border,value)=>widget_field(null,{size:r,font:FONT_BODY,show:'solid',scrollbar:1,border ,style:'rich' ,align:ALIGN.left,locked:0,pattern:1},value) ui_table =(r,widths,format,value)=>widget_grid(null,{size:r,font:FONT_BODY,widths ,format ,headers:2,scrollbar:1,lines:0,bycell:0,show:'solid',locked:1},value) ui_list =(r, value)=>widget_grid(null,{size:r,font:FONT_BODY,widths:[],format:'',headers:0,scrollbar:1,lines:0,bycell:0,show:'solid',locked:1},value) // The Listener listen_show_image=(x,v)=>{ frame=context;while(li.hist.length>=LISTEN_LINES)li.hist.shift() li.hist.push([x,v]),li.scroll=RTEXT_END } listen_show=(align,bare,x)=>listen_show_image(draw_lil(rsub(LISTEN_SIZE(),rect(18,5)),align,bare,x),x) n_show=(a)=>{a[0]=a[0]||NIL;if(a.length<2){listen_show(ALIGN.right,0,a[0])}else{listen_show(ALIGN.right,1,lms(a.map(show).join(' ')))};return a[0]} n_print=(a)=>{a[0]=a[0]||NIL;if(a.length<2){listen_show(ALIGN.right,1,lms(ls(a[0])))}else{listen_show(ALIGN.right,1,a[0]=dyad.format(a[0],lml(a.slice(1))))}return a[0]} n_pre_listen=([a])=>{ const ev=getev(); for(let name of li.vars.keys()){if(!ev.v.get(name))ev.v.set(name,li.vars.get(name))} if(uimode=='object'&&ob.sel.length)ev.v.set('selected',lml(ob.sel.slice(0))) if(uimode=='draw'&&(bg_has_sel()||bg_has_lasso()))ev.v.set('selected',bg_has_lasso()?image_mask(dr.limbo,dr.mask): dr.limbo?bg_scaled_limbo():bg_copy_selection(dr.sel_here)) return a } n_post_listen=([a])=>{ const ev=getev();for(let name of ev.v.keys())li.vars.set(name,ev.v.get(name)) li.vars.set('_',a),listen_show(ALIGN.right,0,a);return a } n_post_query=([a])=>{ms.grid=gridtab(lt(a));return a} listener_eval=_=>{ const str=rtext_string(ms.text.table);if(count(str)<1)return try{ const prog=parse(ls(str)), b=lmblk(); ms.text=fieldstr(lms('')),listen_show(ALIGN.left,1,str) const target=uimode=='script'?sc.target: ob.sel.length==1?ob.sel[0]: con() blk_opa(b,op.BUND,1),blk_lit(b,lmnat(n_pre_listen )),blk_lit(b,NIL ),blk_op(b,op.CALL),blk_op(b,op.DROP),blk_cat(b,prog) blk_opa(b,op.BUND,1),blk_lit(b,lmnat(n_post_listen)),blk_op (b,op.SWAP),blk_op(b,op.CALL),blk_op(b,op.DROP),fire_hunk_async(target,b) }catch(e){listen_show(ALIGN.right,1,lms(`error: ${e.x}`));return} } listener=r=>{ const size=LISTEN_SIZE(), th=li.hist.reduce((x,y)=>x+y[0].size.y+5,0), h=min(th,size.y) const esize=rect(0|(r.x+(r.w-size.x)/2),r.y+r.h-49,size.x,50), tsize=rect(esize.x,esize.y-(h?h+5:0),esize.w,h) const bsize=rect(esize.x-5,esize.y-5-(h?tsize.h+5:0),esize.w+10,(h?tsize.h+5:0)+esize.h+20), pal=deck.patterns.pal.pix draw_shadow(bsize,1,32,1),ui_codeedit(esize,1,ms.text) if(h){ const sbar=scrollbar(tsize,max(0,th-size.y),10,tsize.h,li.scroll,h>=size.y,0), b=sbar.size; li.scroll=sbar.scroll let cy=0;li.hist.map(x=>{ const l=x[0], t=x[1], s=l.size; let lb=rect(b.x,b.y+cy-li.scroll,s.x,s.y) image_paste(lb,b,l,frame.image,0),cy+=s.y+5 lb=rclip(b,lb);const v=over(lb), a=v&&dover(lb) if(v)uicursor=cursor.point,draw_box(inset(lb,-1),0,13); if(a&&(ev.md||ev.drag))draw_invert(pal,lb) if(a&&ev.mu)ms.text=fieldstr(image_is(t)?lms(`image["${ls(ifield(t,'encoded'))}"]`): !lis(t)?lms(show(t)): t) }) } } n_panic=z=>{ do_panic=1,halt(),states.map(x=>x.t=[]),modal_enter('listen') const s=rect((512-22)-18,16),b=rpair(rect(0,0),s),r=image_make(s),t=frame;frame=draw_frame(r) draw_box(b,0,35),draw_textc(inset(b,2),'PANIC',FONT_MONO,35),frame=t,listen_show_image(r,NIL) n_show(z),li.vars.set('_',z[0]||NIL);return NIL } // Audio let audio=null, samples_playing=0, audio_loop=null, audio_loop_playing=null const audioContext=window.AudioContext||window.webkitAudioContext, offline=window.OfflineAudioContext||window.webkitOfflineAudioContext initaudio=_=>{if(!audio)audio=new audioContext({sampleRate:44100})} load_sound=(file,hint,after)=>{ decode_sound=data=>{ if(after&&hint=='samples'){ const r=[];for(let z=0;z{ initaudio(),audio.decodeAudioData(buffer,payload=>{ const conv=new offline(1,payload.length*64000/payload.sampleRate,64000),buff=conv.createBufferSource() conv.oncomplete=e=>{decode_sound(e.renderedBuffer.getChannelData(0))} buff.buffer=payload,buff.connect(conv.destination),buff.start(0),conv.startRendering() }) } const r=new FileReader();r.onload=_=>import_sound(r.result),r.readAsArrayBuffer(file) } sfx_stoploop=_=>{audio_loop_playing.onended=null,audio_loop_playing.stop(),audio_loop=audio_loop_playing=null} sfx_doloop=clear=>{ const a=audio_loop||NIL,b=lmblk(),pp=pending_popstate;let r=NIL, quota=LOOP_QUOTA blk_get(b,lms('loop')),blk_lit(b,lml([a,lmbool(clear)])),blk_op(b,op.CALL) fire_hunk_async(ifield(deck,'card'),b) while(quota>0&&running()){runop(),quota--} if(!running())r=arg();popstate(),pending_popstate=pp if(clear&&audio_loop)sfx_stoploop() n_play([r,lms('loop')]),msg.pending_loop=0 } n_play=([x,hint])=>{ const prepare=sfx=>{ const playback=audio.createBuffer(1,sfx.data.length*8,64000),dest=playback.getChannelData(0) for(let z=0;z0){ if(audio_loop)sfx_stoploop() audio_loop=x,audio_loop_playing=prepare(x) audio_loop_playing.onended=_=>{sfx_doloop(1)} audio_loop_playing.start() } else if(audio_loop){sfx_stoploop()} return NIL } const sfx=!x?x: sound_is(x)?x: dget(deck.sounds,lms(ls(x)));if(!sfx||ln(ifield(sfx,'size'))<1)return NIL;initaudio();if(!audio)return const playing=prepare(sfx);playing.addEventListener('ended',_=>{samples_playing--,audio_playing=samples_playing>0}) playing.start(),samples_playing++,audio_playing=1;return NIL } stop_sound_pump=_=>{ if(au.clip)au.clip.stop();au.clip=null,clearInterval(au.tick),au.mode='stopped' au.head=au.sel.x==au.sel.y?0:au.sel.x } play_sound_pump=sfx=>{ initaudio();if(sfx.data.length<1)return const playback=audio.createBuffer(1,sfx.data.length*8,64000),dest=playback.getChannelData(0) for(let z=0;zstop_sound_pump),au.clip=playing;let counter=0 au.tick=setInterval(_=>{const d=0|(8000*0.05);au.head+=d,counter+=d;if(au.mode!='playing'||counter>=sfx.data.length)stop_sound_pump()},50) au.mode='playing',playing.start() } sfx_any=_=>samples_playing>0 sound_slice=range=>{const r=[];for(let z=range.x;zsound_slice(au.sel.x==au.sel.y?rect(0,au.target.data.length):au.sel) sound_apply=v=>{au.target.data=Uint8Array.from(v.data),mark_dirty()} sound_undo=_=>{const x=au.hist[--(au.hist_cursor)];sound_apply(x[0])} sound_redo=_=>{const x=au.hist[(au.hist_cursor)++];sound_apply(x[1])} sound_edit=v=>{au.hist=au.hist.slice(0,au.hist_cursor),au.hist.push([sound_slice(rect(0,au.target.data.length)),v]),sound_redo()} sound_delete=_=>{ const len=au.target.data.length, sel=au.sel.x==au.sel.y?rect(0,len):rcopy(au.sel), r=[] for(let z=0;z< sel.x;z++)r.push(au.target.data[z ]) for(let z=0;z{ r=[] for(let z=0 ;z{ au.head=(au.sel.x!=au.sel.y)?au.sel.x:0 sound_undo(),sound_redo(),au.mode='stopped' } sound_begin_record=_=>{ stop_sound_pump(),sound_edit(sound_slice(rect(0,au.target.data.length))) if(au.sel.x!=au.sel.y)au.head=au.sel.x;au.mode='recording' } sound_record=_=>{ initaudio();if(au.record_stream){sound_begin_record();return} function resample(input,after){ // Safari refuses to downsample to or play low sample rates, // so convert to 64khz as an even multiple of 8khz // and discretize from that. messy, but better than nothing: const conv=new offline(1,input.length*64000/input.sampleRate,64000), buff=conv.createBufferSource() conv.oncomplete=event=>{after(event.renderedBuffer.getChannelData(0))} buff.buffer=input,buff.connect(conv.destination),buff.start(0),conv.startRendering() } try{ navigator.mediaDevices.getUserMedia({audio:true}).then(stream=>{ try{ const source=audio.createMediaStreamSource(stream) const scriptNode=audio.createScriptProcessor(4096,1,1) scriptNode.onaudioprocess=event=>{ if(au.mode!='recording')return resample(event.inputBuffer,data=>{ const r=[];for(let z=0;zau.target.data.length){ const a=new Uint8Array(appended), b=new Uint8Array(appended) for(let z=0;z=end){sound_finish()} }) } source.connect(scriptNode),scriptNode.connect(audio.destination) au.record_stream=stream,sound_begin_record() }catch(err){console.log(err),au.norecord=1} }).catch(err=>{console.log(err),au.norecord=1}) }catch(err){console.log(err),au.norecord=1} } // Modal Helpers table_decode=(text,format)=>ms.edit_json?monad.table(dyad.parse(lms('%J'),text)): n_readcsv(count(format)?[text,format]:[text]) transit_enumerate=_=>monad.table(monad.keys(deck.transit)) sounds_enumerate=_=>{ const r=lmt(),iv=[],nv=[],bv=[],sv=[];tab_set(r,'icon',iv),tab_set(r,'name',nv),tab_set(r,'bytes',bv),tab_set(r,'secs',sv) deck.sounds.v.map((sn,i)=>{ iv.push(lmn(ICON.sound)),nv.push(deck.sounds.k[i]) bv.push(dyad.format(lms('%.2fkb'),lmn(ln(ifield(sn,'size'))/1000.0*1.33))) sv.push(dyad.format(lms('%.2fs' ),ifield(sn,'duration'))) });return table_sort_asc(r,'name') } fonts_enumerate=_=>{ // note that this ALSO finds the selected font, if any! const r=lmt(),iv=[],nv=[];tab_set(r,'icon',iv),tab_set(r,'name',nv) deck.fonts.v.map((font,z)=>{iv.push(lmn(ICON.font)),nv.push(deck.fonts.k[z])}) const rt=table_sort_asc(r,'name');let fi=-1 tab_get(rt,'name').map((name,i)=>{ const font=dget(deck.fonts,name) if(uimode=='object'){if(ob.sel.every(x=>ifield(x,'font')==font))fi=i} else if(ms.old_wid.ft){if(ifield(ms.old_wid.ft,'font')==font)fi=i} }) return gridtab(rt,fi) } contraptions_enumerate=_=>{ const r=lmt(),iv=[],nv=[];tab_set(r,'icon',iv),tab_set(r,'name',nv) deck.contraptions.k.map(k=>{iv.push(lmn(ICON.app)),nv.push(k)});return table_sort_asc(r,'name') } let baseline_deck_size=null res_size=x=>{ let kb=-1; if(baseline_deck_size==null){baseline_deck_size=deck_write(deck_read(''),0).length} if(sound_is(x)){kb=sound_write(x).length} if(font_is(x)){kb=font_write(x).length} if(module_is(x)||prototype_is(x)){const d=deck_read('');deck_add(d,x);kb=deck_write(d,0).length-baseline_deck_size} return kb==-1?lms(''): dyad.format(lms('%0.2fkb'),lml([lmn(kb/1000.0)])) } res_enumerate=(source)=>{ const r=lmt(),iv=[],nv=[],vv=[],kb=[];tab_set(r,'icon',iv),tab_set(r,'name',nv),tab_set(r,'value',vv),tab_set(r,'kb',kb) const pat=source.patterns,pp=patterns_write(pat),pa=anims_write(pat),da=dyad.parse(lms('%j'),lms(DEFAULT_ANIMS)) if(!match(pa,da)||pp!=DEFAULT_PATTERNS)iv.push(lmn(ICON.pat)),nv.push(lms('patterns')),vv.push(pat) const fonts=dyad.drop(lms('mono'),dyad.drop(lms('menu'),dyad.drop(lms('body'),source.fonts))) fonts.v.map((font,i)=>{iv.push(lmn(ICON.font)),nv.push(fonts.k[i]),vv.push(font)}) const sounds=source.sounds sounds.v.map((sound,i)=>{iv.push(lmn(ICON.sound)),nv.push(sounds.k[i]),vv.push(sound)}) const modules=source.modules modules.v.map((mod,i)=>{iv.push(lmn(ICON.lil)),nv.push(modules.k[i]),vv.push(mod)}) const defs=source.contraptions defs.v.map((def,i)=>{iv.push(lmn(ICON.app)),nv.push(defs.k[i]),vv.push(def)}) vv.forEach(x=>kb.push(res_size(x))) return table_sort_radix(r,'icon','name') } title_caps=x=>{let w=1;return x.split('').map(c=>{if(w)c=drom_toupper(c);w=c==' '||c=='\n';return c}).join('')} do_transition=(tween,dest,errors)=>{ let f=frame;ms.canvas.image.size=dest.size,ms.canvas.image.pix=dest.pix,ms.canvas.image.pix.fill(0),canvas_clip(ms.canvas) const a=lml([ms.canvas,ms.carda,ms.cardb,lmn(tween)]), p=lmblk();blk_lit(p,ms.trans),blk_lit(p,a),blk_op(p,op.CALL) const e=lmenv();pushstate(e),issue(e,p);let quota=TRANS_QUOTA;while(quota&&running())runop(),quota-- if(running()&&errors){listen_show(ALIGN.right,1,lms(`warning: transition ${ms.trans.n} exceeded quota and was halted.`)),tween=2} popstate(),frame=f,sleep_play=0,sleep_frames=0;return tween } // Modal Dialogues modal_enter=type=>{ ev.md=ev.mu=ev.dclick=0,menus_clear() if(ms.type=='trans')return ms.from_listener=ms.type=='listen' ms.from_keycaps=kc.on ms.type=ms.subtype=type ms.old_wid=wid,wid=wid_state() if(enable_touch){wid.active=({link:1,gridcell:1,listen:1})[type]?0:-1} if(type=='listen'){ if(uimode=='script'){ try{const text=ls(rtext_string(sc.f.table));parse(text),script_save(text)} catch(e){listen_show(ALIGN.right,1,lms('note: this script contains an error.\nexecuting under last saved version!'))} } ms.text=fieldstr(lms('')),li.scroll=RTEXT_END } if(type=='query')ms.grid=gridtab(ms.old_wid.gv.table),ms.text=fieldstr(lms('select from me.value')) if(type=='recording'){ au.head=0,au.sel=rect(),au.mode='stopped',au.hist=[],au.hist_cursor=0 ms.name=fieldstr(dkey(deck.sounds,au.target)||lms('unknown sound')) } if(type=='cards')ms.grid=gridtab(null) if(type=='orderwids')ms.grid=gridtab(null),ms.grid.col=-99 if(type=='sounds')ms.grid=gridtab(sounds_enumerate()) if(type=='contraptions'||type=='pick_contraption')ms.grid=gridtab(contraptions_enumerate()) if(type=='fonts')ms.grid=fonts_enumerate(),ms.grid.scroll=-99 if(type=='resources')ms.message=null,ms.grid=gridtab(lmt()),ms.grid2=gridtab(res_enumerate(deck)) if(type=='link'){ const t=ms.old_wid.fv.table,s=tab_get(rtext_span(t,ms.old_wid.cursor),'arg') let ol=lms('');for(let z=0;z{ const attr_heights={bool:16,number:20,string:20,code:80,rich:80,data:80,note:0} const v=iwrite(w,n), item={type:ls(tab_cell(a,'type',i)), label:ls(tab_cell(a,'label',i)), bval:0, value:fieldstr('')} item.height=attr_heights[item.type] if (item.type=='bool'){item.bval=lb(v)} else if(item.type=='data'){item.value.table=rtext_cast(dyad.format(lms('%J'),monad.list(v)))} else if(item.type=='rich'){item.value.table=rtext_cast(v)} else if(item.type=='note'){item.height=layout_plaintext(item.label,FONT_BODY,ALIGN.left,rect(213,400)).size.y} else {item.value.table=rtext_cast(v)} attrs.push(item) }) } if(type=='prototype_props'){ const c=con() ms.name =fieldstr(ifield(c,'name')) ms.form0=fieldstr(ifield(c,'description')) ms.form1=fieldstr(ifield(c,'template')) ms.form2=fieldstr(ifield(c,'version')) } if(type=='prototype_attrs'){ const a=ifield(con(),'attributes') ms.grid=gridtab(dyad.take(lmn(count(a)),a)) ms.name=fieldstr(lms('')) ms.text=fieldstr(lms('')) } if(type=='pick_card')ms.act_card=ln(ifield(ifield(deck,'card'),'index')),ms.carda=ob.sel.slice(0) if(type=='action'){ sc.target=ob.sel[0],sc.others=[] ms.act_go=1,ms.act_gomode=5,ms.act_trans=0,ms.act_sound=0 ms.verb =lms('') // card name ms.message=lms('') // sound name ms.grid=gridtab(transit_enumerate(),0) ms.canvas=free_canvas(deck),ms.canvas.size=rect(17,13) ms.carda=image_read('%%IMG3ABEADQKEj6nLGu3Ag2ZKeiu1uoUPconYReaJkNi6FA==') ms.cardb=image_read('%%IMG3ABEADQKMj6nLBg+bS0BWdBncWfXzgUs4edqocY/EtkoB') // parse action script, if any: const scr=ifield(sc.target,'script') const p0=dyad.parse(lms('on click do\n play[%q]\nend%m' ),scr) // sound, no go const p1=dyad.parse(lms('on click do\n play[%q]\n go[%q %q]\nend%m'),scr) // sound, go + trans const p2=dyad.parse(lms('on click do\n play[%q]\n go[%q]\nend%m' ),scr) // sound, just go const p3=dyad.parse(lms('on click do\n go[%q %q]\nend%m' ),scr) // no sound, go, trans const p4=dyad.parse(lms('on click do\n go[%q]\nend%m' ),scr) // no sound, just go const fs=lb(monad.last(p0))||lb(monad.last(p1))||lb(monad.last(p2))?ls(monad.first(p0)): null const fg=lb(monad.last(p1))||lb(monad.last(p2))?ls(p1.v[1]): lb(monad.last(p3))||lb(monad.last(p4))?ls(monad.first(p3)): null const ft=lb(monad.last(p1))?ls(p1.v[2]): lb(monad.last(p3))?ls(p3.v[1]): null const fk={First:0,Prev:1,Next:2,Last:3,Back:4} if(fs!=null||fg!=null||ft!=null){ if(fs!=null){ms.act_sound=1,ms.message=lms(fs)} if(ft!=null){ms.grid.scroll=-99;tab_get(ms.grid.table,'value').map((x,i)=>{if(ft==ls(x))ms.act_trans=1,ms.grid.row=i})} ms.act_go=fg!=null;if(fg!=null){if(fk[fg]!=undefined)ms.act_gomode=fk[fg];if(ms.act_gomode==5)ms.verb=lms(fg)} } } const dname=(x,e)=>{x=x||'untitled';return lms(/\.(deck|html)$/.test(x)?x:x+e)} if(type=='card_props')ms.name=fieldstr(ifield(ifield(deck,'card'),'name')) if(type=='link'||type=='gridcell'||type=='query')wid.cursor=rect(0,RTEXT_END) if(type=='alert_lil' )ms.type=type='alert' if(type=='confirm_lil' )ms.type=type='confirm' if(type=='input_lil' )ms.type=type='input' if(type=='confirm_new' )ms.type=type='confirm' if(type=='confirm_script')ms.type=type='confirm' if(type=='multiscript' )ms.type=type='confirm' if(type=='save_deck' )ms.type=type='save',ms.text=fieldstr(dname(deck.name,'.deck')),ms.desc='Save a .deck or .html file.' if(type=='save_locked' )ms.type=type='save',ms.text=fieldstr(dname(deck.name,'.html')),ms.desc='Save locked standalone deck as an .html file.' if(type=='export_image' )ms.type=type='save',ms.text=fieldstr(lms('image.gif' )),ms.desc='Save a .gif image file.' if(type=='save_lil' )ms.type=type='save',ms.text=fieldstr(lms('untitled.txt')),ms.desc='Save a text file.' if(type=='input' )ms.text=fieldstr(lms('')) } modal_exit=value=>{ wid=ms.old_wid if(ms.type=='gridcell'&&value)grid_edit_cell(ms.cell,ms.pending_grid_cell_rich?rtext_cast(ms.text.table):rtext_string(ms.text.table)) if(ms.type=='card_props'){iwrite(ifield(deck,'card'),lms('name'),rtext_string(ms.name.table)),mark_dirty()} if(ms.type=='grid_props'){ const t=table_decode(rtext_string(ms.form0.table),rtext_string(ms.text.table)) if(!match(t,ifield(ob.sel[0],'value')))ob_edit_prop('value',t) } if(ms.type=='canvas_props'){ const c=ob.sel[0], sn=clamp(0.1,ln(rtext_string(ms.text.table)),16),f=frame,img=ifield(c,'copy').f([]) iwrite(c,lms('size'),lmpair(rect(ceil(img.size.x*sn),ceil(img.size.y*sn)))) iwrite(c,lms('scale'),lmn(sn)),ifield(c,'paste').f([img]),frame=f } if(ms.type=='contraption_props'){ const w=ob.sel[0],a=ifield(w.def,'attributes');tab_get(a,'name').map((n,i)=>{ const t=ls(tab_cell(a,'type',i));let v=lmn(attrs[i].bval) if(t=='number')v=lmn(ln(rtext_string(attrs[i].value.table))) if(t=='string')v=rtext_string(attrs[i].value.table) if(t=='code' )v=rtext_string(attrs[i].value.table) if(t=='rich' )v=attrs[i].value.table if(t=='data' )v=dyad.parse(lms('%J'),rtext_string(attrs[i].value.table)) iwrite(w,n,v) }) } if(ms.type=='action'&&value){ let r='on click do\n' if(ms.act_sound)r+=` play[${show(ms.message)}]\n` if(ms.act_go){ r+=' go[' r+=ms.act_gomode==0?'"First"': ms.act_gomode==1?'"Prev"': ms.act_gomode==2?'"Next"': ms.act_gomode==3?'"Last"': ms.act_gomode==4?'"Back"': show(ms.verb) if(ms.act_trans)r+=` ${show(tab_cell(ms.grid.table,'value',ms.grid.row))}` r+=']\n' }r+='end',script_save(lms(r)) } if(ms.type=='recording'){ const name=rtext_string(ms.name.table);rename_sound(deck,au.target,name) au.mode='stopped',modal_enter('sounds') tab_get(ms.grid.table,'name').map((k,i)=>{if(match(name,k))ms.grid.row=i}) return } if(ms.type=='widpattern'&&value!=-1)ob_edit_prop('pattern',lmn(value)) if(ms.subtype=='confirm_new' &&value)load_deck(deck_read('')) if(ms.subtype=='confirm_script'&&value)finish_script() if(ms.subtype=='multiscript' &&value)setscript(ob.sel) if(ms.subtype=='alert_lil' )arg(),ret(lmn(value)) if(ms.subtype=='confirm_lil')arg(),ret(lmn(value)) if(ms.subtype=='input_lil' )arg(),ret(rtext_string(ms.text.table)) if(ms.subtype=='choose_lil' )arg(),ret(ms.verb.v[ms.grid.row]) ms.type=null ev.callback=null if(ms.from_listener)modal_enter('listen') if(enable_touch&&ms.from_keycaps)kc.on=1 if(ms.type==null&&uimode=='interact')msg.next_view=1 } modals=_=>{ ms.in_modal=1 const pal=deck.patterns.pal.pix if(ms.type=='about'){ const b=draw_modalbox(rect(150,70)) if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit)modal_exit(0) draw_text(b,`Decker v${VERSION}`,FONT_MENU,1),b.y+=15 draw_text(b,'by John Earnest',FONT_BODY,1),b.y+=12 draw_text(b,'beyondloom.com/decker',FONT_BODY,1) } else if(ms.type=='listen'){ if(!kc.on){listener(rect(0,0,frame.size.x,frame.size.y));if(ev.eval)listener_eval()} if(ev.exit)modal_exit(0) } else if(ms.type=='cards'){ const b=draw_modalbox(rect(210,frame.size.y-46)), gsize=rect(b.x,b.y+15,b.w,b.h-20-20) const slot=30,ch=slot*count(deck.cards); let m=0,props=0,gutter=-1,curr=ifield(deck,'card') draw_textc(rect(b.x,b.y-5,b.w,20),'Cards',FONT_MENU,1),draw_box(gsize,0,1) const sbar=scrollbar(gsize,max(0,ch-(gsize.h-2)),10,gsize.h,ms.grid.scroll,ch>=gsize.h,0),bb=sbar.size;ms.grid.scroll=sbar.scroll,bb.y++ const oc=frame.clip;frame.clip=bb;for(let z=0;zbb.y+bb.h||c.y+c.hoi?gutter-1:gutter)),m=1,n_go([s],deck) }ms.grid.row=-1 } else if(ev.drag&&ms.grid.row!=-1){const r=rect(ev.pos.x-5,ev.pos.y-5,10,10);draw_rect(r,0),draw_box(r,0,1),uicursor=cursor.drag} else if(ev.dir=='up' &&ev.shift){iwrite(curr,lms('index'),lmn(ln(ifield(curr,'index'))-1)),m=1,n_go([curr],deck)} else if(ev.dir=='down'&&ev.shift){iwrite(curr,lms('index'),lmn(ln(ifield(curr,'index'))+1)),m=1,n_go([curr],deck)} else if(ev.dir=='left' ||ev.dir=='up' ){m=1,n_go([lms('Prev')],deck)} else if(ev.dir=='right'||ev.dir=='down'){m=1,n_go([lms('Next')],deck)} if(m){ curr=ifield(deck,'card');const y=(ln(ifield(curr,'index'))*slot)-ms.grid.scroll if(y<0){ms.grid.scroll+=y}if(y+slot>=bb.h){ms.grid.scroll+=(y+slot)-bb.h} }if(props)modal_enter('card_props') } else if(ms.type=='orderwids'){ const b=draw_modalbox(rect(210,frame.size.y-46)),def=con(),wids=ll(ifield(def,'widgets')) draw_textc(rect(b.x,b.y-5,b.w,20),'Widget Order',FONT_MENU,1) const gsize=rect(b.x,b.y+15,b.w,b.h-20-20), slot=16, ch=slot*wids.length let m=ms.grid.col==-99?-1:0, props=0, gutter=-1; draw_box(gsize,0,1) const sbar=scrollbar(gsize,max(0,ch-(gsize.h-2)),10,gsize.h,ms.grid.scroll,ch>=gsize.h,0),bb=sbar.size;ms.grid.scroll=sbar.scroll,bb.y++ const oc=frame.clip;frame.clip=bb;for(let z=0;zbb.y+bb.h||c.y+c.h=0){ob.sel=ob.sel.filter(x=>x!=wid),props=0}else{ob.sel.push(wid)}} else{object_select(wid),ms.grid.row=z} } const col=ev.drag&&ms.grid.row==z?13:1 draw_text_fit(rect(c.x+3,c.y+2,bb.w-6,font_h(FONT_BODY)),ls(ifield(wid,'name')),FONT_BODY,col) if(ls(ifield(wid,'script')).length){ const i=rect(c.x+c.w-13,c.y+2,12,12) draw_rect(rect(i.x+2,i.y+1,i.w-2,i.h-2),0),draw_icon(i,ICONS[ICON.lil],1) } if(ob.sel.indexOf(wid)>=0)draw_invert(pal,c) if((ev.drag||ev.mu)&&ms.grid.row!=-1){ {const g=rect(c.x,c.y-3 ,c.w,7);if(over(g)){draw_hline(c.x,c.x+c.w,c.y ,13),gutter=z }} {const g=rect(c.x,c.y-3+c.h,c.w,7);if(over(g)){draw_hline(c.x,c.x+c.w,c.y+c.h-1,13),gutter=z+1}} } }frame.clip=oc if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit)modal_exit(0) const c=rect(b.x,b.y+b.h-20) if(ui_button(rect(c.x,c.y,80,20),'Properties...',ob.sel.length==1)||props)if(ob.sel.length)object_properties(ob.sel[0]) if(ev.mu){ if(ms.grid.row!=-1&&gutter!=-1){ const s=wids[ms.grid.row], oi=ln(ifield(s,'index')) iwrite(s,lms('index'),lmn(gutter>oi?gutter-1:gutter)),m=1,object_select(s),mark_dirty() }ms.grid.row=-1 } else if(ev.drag&&ms.grid.row!=-1){const r=rect(ev.pos.x-5,ev.pos.y-5,10,10);draw_rect(r,0),draw_box(r,0,1),uicursor=cursor.drag} else if(ev.shift &&ev.dir=='up' ){ob_move_dn(),m=-1} else if(ev.shift &&ev.dir=='down'){ob_move_up(),m= 1} else if(ob.sel.length&&ev.dir=='up' ){ob_order(),object_select(wids[mod(ln(ifield(ob.sel[0 ],'index'))-1,wids.length)]),m=-1} else if(ob.sel.length&&ev.dir=='down'){ob_order(),object_select(wids[mod(ln(ifield(ob.sel[ob.sel.length-1],'index'))+1,wids.length)]),m= 1} if(m!=0&&ob.sel.length){ ms.grid.col=-1;ob_order();const target=m==-1?ob.sel[0]:ob.sel[ob.sel.length-1] const y=(ln(ifield(target,'index'))*slot)-ms.grid.scroll if(y<0){ms.grid.scroll+=y}if(y+slot>=bb.h){ms.grid.scroll+=(y+slot)-bb.h} } } else if(ms.type=='sounds'){ const b=draw_modalbox(rect(250,frame.size.y-16-30)), gsize=rect(b.x,b.y+15,b.w,b.h-16-(2*25)) draw_textc(rect(b.x,b.y-5,b.w,20),'Sounds',FONT_MENU,1) const s=ms.grid.row>=0?dget(deck.sounds,tab_cell(ms.grid.table,'name',ms.grid.row)):null if(ui_table(gsize,[16,120],'Isss',ms.grid))n_play([s]) if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit){ if(ms.from_action){ if(ms.grid.row>=0)ms.message=tab_cell(ms.grid.table,'name',ms.grid.row) ms.grid=gridtab(transit_enumerate(),ms.act_transno),ms.type='action',ms.from_action=0 }else{modal_exit(1)} } const c=rect(b.x,b.y+b.h-20) if(ui_button(rect(c.x,c.y,60,20),'Edit...',s!=null))au.target=s,modal_enter('recording') if(ui_button(rect(c.x,c.y-25,60,20),'New...',1))au.target=deck_add(deck,lms('sound')),mark_dirty(),modal_enter('recording');c.x+=65 if(ui_button(rect(c.x,c.y,60,20),"Delete",s!=null))deck_remove(deck,s),mark_dirty(),ms.grid=gridtab(sounds_enumerate()) } else if(ms.type=='recording'){ const b=draw_modalbox(rect(frame.size.x-50,130)), samples=max(1,ln(ifield(au.target,'size'))) draw_textc(rect(b.x,b.y-5,b.w,20),'Audio Editor',FONT_MENU,1) au.head=clamp(0,au.head,samples), au.sel.x=clamp(0,au.sel.x,samples), au.sel.y=clamp(0,au.sel.y,samples) const gsize=rect(b.x,b.y+15,b.w,64), lsize=rint(rect(b.x,gsize.y+gsize.h+2,b.w/2,font_h(FONT_BODY))) const sndpos=x=>0|(x*((gsize.w*1.0)/samples)) const possnd=x=>0|max(0,min(samples-1,((x/gsize.w)*samples))) if((ev.mu||ev.drag)&&dover(gsize)&&au.mode=='stopped'){ const x=possnd(ev.dpos.x-gsize.x),y=possnd(ev.pos.x-gsize.x);field_exit() if(ev.mu)au.head=y;if(ev.drag)au.sel=rect(min(x,y),max(x,y)),au.head=au.sel.x } const sel=au.sel, sc=sel.y-sel.x, oc=frame.clip frame.clip=gsize;for(let z=0;z=samples)continue;let s=byte_to_sample(au.target.data[base+d]);if(Math.abs(s)>Math.abs(v))v=s }let vp=0|(v*64);draw_vline(gsize.x+z,gsize.y+32,gsize.y+32+vp,1),draw_vline(gsize.x+z,gsize.y+32-vp,gsize.y+32,1) }frame.clip=oc if(sc)draw_invert(pal,rect(gsize.x+sndpos(sel.x),gsize.y,sndpos(sel.y)-sndpos(sel.x),gsize.h)) draw_invert(pal,rect(gsize.x+sndpos(au.head)-1,gsize.y,3,gsize.h)),draw_box(gsize,0,1) const t=sc==0?ls(dyad.format(lms('%0.2fkb, %0.2fs' ),lml([lmn(samples/1000.0*1.33),ifield(au.target,'duration')]))): ls(dyad.format(lms('%0.2fkb, %0.2fs selected'),lml([lmn(sc /1000.0*1.33),lmn(sc/SFX_RATE )]))) draw_text_fit(lsize,t,FONT_BODY,1) if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit)modal_exit(0) const c=rect(b.x,b.y+b.h-20) if(ui_toggle(rect(c.x,c.y,60,20),'Play',au.mode=='playing',1)){ if(au.mode=='recording')sound_finish() au.head=sc?au.sel.x:0;if(au.mode=='playing'){stop_sound_pump()}else{play_sound_pump(sound_selected())} };c.x+=65 if(ui_toggle(rect(c.x,c.y,60,20),'Record',au.mode=='recording',!au.norecord)){ if(au.mode=='recording'){sound_finish()}else{sound_record()} };c.x+=65 if(ui_button(rect(c.x,c.y,60,20),'Crop',sc&&au.mode=='stopped'))sound_edit(sound_slice(au.sel)),au.sel=rect(),au.head=0 draw_text(rect(b.x+(b.w/2),gsize.y+gsize.h+9,37,20),'Name',FONT_MENU,1) ui_field(rint(rect(b.x+(b.w/2)+37,gsize.y+gsize.h+5,(b.w/2)-37,20)),ms.name) } else if(ms.type=='fonts'){ const b=draw_modalbox(rect(170,170)) draw_textc(rect(b.x,b.y-5,b.w,20),'Fonts',FONT_MENU,1) const gsize=rect(b.x,b.y+15,b.w,b.h-20-50-25) if(ms.grid.scroll==-99){ms.grid.scroll=grid_scrollto(tab_rowcount(ms.grid.table),{size:gsize,font:FONT_BODY,headers:0},-1,ms.grid.row)} const choose=ui_table(gsize,[16],'Is',ms.grid) let psize=rect(b.x,gsize.y+gsize.h+5,b.w,50);draw_box(psize,0,1),psize=inset(psize,2) const font=ms.grid.row<0?null: dget(deck.fonts,tab_cell(ms.grid.table,'name',ms.grid.row)) if(font){const l=layout_plaintext(PANGRAM,font,ALIGN.left,rect(psize.w,psize.h));draw_text_wrap(psize,l,1)} const c=rect(b.x+b.w-60,b.y+b.h-20) if(ui_button(rect(c.x,c.y,60,20),'OK',ms.grid.row>=0)||choose){ const nf=tab_cell(ms.grid.table,'name',ms.grid.row),nested=ms_stack.length>0;modal_pop(1) if(uimode=='object'&&!nested){ob_edit_prop('font',nf)} else if(wid.fv&&wid.cursor.x!=wid.cursor.y){const c=wid.cursor;field_fontspan(nf),wid.cursor=c,mark_dirty()} else if(wid.ft){iwrite(wid.ft,lms('font'),nf),wid.f=unpack_field(ms.old_wid.ft),wid.fv=unpack_field_value(ms.old_wid.ft),mark_dirty()} };c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_pop(0) } else if(ms.type=='contraptions'||ms.type=='pick_contraption'){ const b=draw_modalbox(rect(250,230)) draw_textc(rect(b.x,b.y-5,b.w,20),ms.type=='contraptions'?'Contraption Prototypes':'New Contraption',FONT_MENU,1) const gsize=rect(b.x,b.y+15,b.w,b.h-20-50-25-(ms.type=='contraptions'?25:0)) const choose=ui_table(gsize,[16],'Is',ms.grid) let psize=rect(b.x,gsize.y+gsize.h+5,b.w,50);draw_box(psize,0,1),psize=inset(psize,2) const proto=ms.grid.row<0?null: dget(deck.contraptions,tab_cell(ms.grid.table,'name',ms.grid.row)) if(proto){ const desc=ls(ifield(proto,'description')) const l=layout_plaintext(desc,FONT_BODY,ALIGN.left,rect(psize.w,psize.h));draw_text_wrap(psize,l,1) } const c=rect(b.x+b.w-60,b.y+b.h-20) if(ms.type=='contraptions'){ if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit){modal_exit(0)} if(ui_button(rect(b.x,b.y+b.h-45,60,20),'New...',1)){modal_exit(1),con_set(deck_add(deck,lms('contraption'))),mark_dirty()} if(ui_button(rect(b.x,b.y+b.h-20,60,20),'Edit...',proto)||choose){modal_exit(2),con_set(proto)} if(ui_button(rect(b.x+65,b.y+b.h-45,60,20),'Clone',proto)){ deck_add(deck,proto,lms(ls(ifield(proto,'name'))+'_clone')) ms.grid=gridtab(contraptions_enumerate()) } if(ui_button(rect(b.x+65,b.y+b.h-20,60,20),'Delete',proto)){ deck_remove(deck,proto) ms.grid=gridtab(contraptions_enumerate()) } } if(ms.type=='pick_contraption'){ if(ui_button(rect(c.x,c.y,60,20),'Create',ms.grid.row>=0)||choose){ ob_create([lmd(['type','def'].map(lms),[lms('contraption'),tab_cell(ms.grid.table,'name',ms.grid.row)])]),modal_exit(1) };c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_exit(0) } } else if(ms.type=='contraption_props'){ const b=draw_modalbox(rect(240,240)),contraption=ob.sel[0] draw_textc(rect(b.x,b.y-5,b.w,20),`${title_caps(contraption.def.name)} Properties`,FONT_MENU,1) draw_text(rect(b.x,b.y+22,47,20),'Name',FONT_MENU,1) ui_field (rect(b.x+47 ,b.y+20 ,b.w-47,18),ms.name) iwrite(contraption,lms('name'),rtext_string(ms.name.table)),mark_dirty() widget_attributes(rect(b.x,b.y+42,b.w,b.h-42-25)) const c=rect(b.x,b.y+b.h-20) if(ui_button(rect(c.x,c.y,60,20),'Script...',1))modal_exit(0),setscript(contraption);c.x+=65 if(ui_button(rect(c.x,c.y,80,20),'Prototype...',1))modal_exit(1),con_set(contraption.def) if(ui_button(rect(b.x+b.w-60,c.y,60,20),'OK',1)||ev.exit)modal_exit(1) } else if(ms.type=='prototype_props'){ const b=draw_modalbox(rect(220,230)),def=con(), lw=67 draw_textc(rect(b.x,b.y-5,b.w,20),'Prototype Properties',FONT_MENU,1) draw_text(rect(b.x,b.y+ 22,lw,20),'Name',FONT_MENU,1) draw_text(rect(b.x,b.y+ 42,lw,20),'Version',FONT_MENU,1) draw_text(rect(b.x,b.y+ 62,lw,20),'Description',FONT_MENU,1) draw_text(rect(b.x,b.y+122,lw,20),'Template\nScript',FONT_MENU,1) ui_field (rect(b.x+lw,b.y+20 ,b.w-lw,18),ms.name) ui_field (rect(b.x+lw,b.y+40 ,b.w-lw,18),ms.form2) ui_textedit(rect(b.x+lw,b.y+60 ,b.w-lw,58),1,ms.form0) ui_codeedit(rect(b.x+lw,b.y+120,b.w-lw,78),1,ms.form1) iwrite(def,lms('name' ),rtext_string(ms.name .table)) iwrite(def,lms('version' ),rtext_string(ms.form2.table)) iwrite(def,lms('description'),rtext_string(ms.form0.table)) iwrite(def,lms('template' ),rtext_string(ms.form1.table)),mark_dirty() const c=rect(b.x,b.y+b.h-20) if(ui_button(rect(c.x,c.y,60,20),'Script...',1))modal_exit(1),setscript(def);c.x+=65 if(ui_button(rect(b.x+b.w-60,c.y,60,20),'OK',1)||ev.exit)modal_exit(0) } else if(ms.type=='prototype_attrs'){ const b=draw_modalbox(rect(220,220)),def=con(), lw=42 draw_textc(rect(b.x,b.y-5,b.w,20),`${title_caps(def.name)} Attributes`,FONT_MENU,1) const gsize=rect(b.x,b.y+20,80,b.h-(20+5+20)) const before=ms.grid.row;ui_table(gsize,[gsize.w-18],'s',ms.grid) if(before!=ms.grid.row||ms.name.table==null||ms.text.table==null){ ms.name=fieldstr(ms.grid.row>=0?tab_cell(ms.grid.table,'name' ,ms.grid.row):lms('')) ms.text=fieldstr(ms.grid.row>=0?tab_cell(ms.grid.table,'label',ms.grid.row):lms('')) } const sel=ms.grid.row>=0 draw_text(rect(gsize.x+gsize.w+10,b.y+20+2,lw,20),'Name' ,FONT_MENU,1) draw_text(rect(gsize.x+gsize.w+10,b.y+40+2,lw,20),'Label',FONT_MENU,1) draw_text(rect(gsize.x+gsize.w+10,b.y+80+2,lw,20),'Type' ,FONT_MENU,1) ui_dfield(rect(gsize.x+gsize.w+5+lw,b.y+20,b.w-(lw+5+gsize.w),18),sel,ms.name) ui_dfield(rect(gsize.x+gsize.w+5+lw,b.y+40,b.w-(lw+5+gsize.w),2*18),sel,ms.text) if(sel){ tab_get(ms.grid.table,'name' )[ms.grid.row]=rtext_string(ms.name.table) tab_get(ms.grid.table,'label')[ms.grid.row]=rtext_string(ms.text.table) } const cr=rect(gsize.x+gsize.w+5+lw,b.y+80), c=rect(b.x,b.y+b.h-20) const attr_types=['bool','number','string','code','rich','data','note'] const attr_labels=['Boolean','Number','String','Code','Rich Text','Data','Note'] const t=sel?ls(tab_cell(ms.grid.table,'type',ms.grid.row)):'' for(let z=0;z-1)ms.grid2.row=-1 ui_table(rgrid,[16,rgrid.w-38],'Is',ms.grid2);if(ms.grid2.row>-1)ms.grid .row=-1 draw_vline(lgrid.x+lgrid.w,lgrid.y+lgrid.h+5,b.y+b.h,18) draw_vline(rgrid.x ,rgrid.y+rgrid.h+5,b.y+b.h,18) draw_textc(rect(lgrid.x,lgrid.y+lgrid.h+3,lgrid.w,15),ms.message?ls(ifield(ms.message,'name')):'(Choose a Deck)',FONT_BODY,1) draw_textc(rect(rgrid.x,rgrid.y+rgrid.h+3,rgrid.w,15),ls(ifield(deck,'name')),FONT_BODY,1) const cb=rect(lgrid.x+lgrid.w+5,lgrid.y+5,b.w-(lgrid.w+5+5+rgrid.w),20) const rvalue=(g,k)=>tab_cell(g.table,k,g.row) let sel=(ms.grid.table&&ms.grid.row>-1)?rvalue(ms.grid,'value'): ms.grid2.row>-1?rvalue(ms.grid2,'value'): null let skb=(ms.grid.table&&ms.grid.row>-1)?rvalue(ms.grid,'kb' ): ms.grid2.row>-1?rvalue(ms.grid2,'kb' ): null let copy_message='>> Copy >>',can_copy=1 if(ms.grid.row>-1&&sel&&(module_is(sel)||prototype_is(sel))){ const name=ifield(sel,'name');let sver=ln(ifield(sel,'version')),dver=sver;can_copy=0 if(module_is (sel)){const v=dget(deck.modules ,name);if(v){dver=ln(ifield(v,'version'))}else{can_copy=1}} if(prototype_is(sel)){const v=dget(deck.contraptions,name);if(v){dver=ln(ifield(v,'version'))}else{can_copy=1}} if(sver>dver)can_copy=1,copy_message='>> Upgrade >>' if(sver> Downgrade >>' } if(ui_button(cb,copy_message,can_copy&&ms.grid.row>-1)){ if(patterns_is(sel)){const dst=ifield(deck,'patterns');for(let z=2;z<32+PAL_COLORS;z++)iindex(dst,z,iindex(sel,z))} else if(module_is(sel)||prototype_is(sel)){deck_add(deck,sel)} else{deck_add(deck,sel,rvalue(ms.grid,'name'))} ms.grid2=gridtab(res_enumerate(deck)),mark_dirty();if(module_is(sel))validate_modules() }cb.y+=25 if(ui_button(cb,'Remove',ms.grid2.row>-1)){ deck_remove(deck,rvalue(ms.grid2,'value')) ms.grid2=gridtab(res_enumerate(deck)),mark_dirty(),sel=null }cb.y+=25 if(sel&&module_is(sel)){ if(ui_button(cb,'Script...',1)){modal_exit(0),setscript(sel)} cb.y+=25 } const pre=rect(cb.x,cb.y,cb.w,b.h-(cb.y-b.y)) if(sel&&font_is(sel)){ draw_textc(rect(pre.x,pre.y+pre.h-18,pre.w,18),count(ifield(sel,'glyphs'))+' glyphs '+ls(skb),FONT_BODY,1);pre.h-=20 const l=layout_plaintext(PANGRAM,sel,ALIGN.center,rect(pre.w,pre.h));draw_text_wrap(pre,l,1) } if(sel&&(module_is(sel)||prototype_is(sel))){ draw_textc(rect(pre.x,pre.y+pre.h-18,pre.w,18),ls(dyad.format(lms('version %f '+ls(skb)),ifield(sel,'version'))),FONT_BODY,1);pre.h-=20 const l=layout_plaintext(ls(ifield(sel,'description')),FONT_BODY,ALIGN.center,rect(pre.w,pre.h));draw_text_wrap(pre,l,1) } if(sel&&sound_is(sel)){ if(ui_button(cb,'Play',1))n_play([sel]) draw_textc(rect(pre.x,pre.y+pre.h-18,pre.w,18),ls(skb),FONT_BODY,1) } if(sel&&patterns_is(sel)){ const c=frame.clip,pal=sel.pal.pix;frame.clip=pre; const anim_ants =(x,y)=>(0|((x+y+(0|(frame_count/2)))/3))%2?15:0 const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal_pat(pal,pix,x,y)&1 const draw_color =(pix,x,y)=>pix==ANTS?anim_ants(x,y): pix>=32+PAL_COLORS?0: pix>31?pix-32: draw_pattern(pix,x,y)?15:0 for(let z=0;z<32;z++)for(let y=0;y<16;y++)for(let x=0;x<16;x++){ const h=rect(3+x+pre.x+16*(z%(0|(pre.w/16))), y+pre.y+16*(0|(z/(0|(pre.w/16))))) if(inclip(h))pix(h,32+draw_color(z,x,y)) }frame.clip=c } ui_button(rint(rect(lgrid.x+(lgrid.w-80)/2,b.y+b.h-20,80,20)),'Choose...',1,_=>open_text('.html,.deck',text=>{ ms.message=deck_read(text),ms.grid=gridtab(res_enumerate(ms.message)) })) } else if(ms.type=='query'){ const b=draw_modalbox(rect(frame.size.x-30,frame.size.y-16-30)),t=ms.grid.table const desc=t?`${tab_cols(t).length} column${tab_cols(t).length==1?'':'s'}, ${count(t)} row${count(t)==1?'':'s'}.`:'Executing Query.' let compiles=0,error=' ';try{parse(ls(rtext_string(ms.text.table))),compiles=1}catch(e){error=e.x} const dsize=font_textsize(FONT_BODY,desc);draw_text(b,desc,FONT_BODY,1) const msize=font_textsize(FONT_BODY,error), gsize=rint(rect(b.x,b.y+dsize.y,b.w,(b.h-(2*5)-dsize.y-20)/2)) const esize=rect(b.x,gsize.y+gsize.h+5,b.w,gsize.h-msize.y);ui_codeedit(esize,1,ms.text) if(!compiles)draw_text_fit(rect(b.x,esize.y+esize.h,b.w,msize.y),error,FONT_BODY,1) const c=rect(b.x+b.w-60,b.y+b.h-20) if(ui_button(rect(c.x,c.y,60,20),'Run',compiles)||ev.eval){ try{ const prog=parse(ls(rtext_string(ms.text.table))) blk_opa(prog,op.BUND,1),blk_lit(prog,lmnat(n_post_query)),blk_op(prog,op.SWAP),blk_op(prog,op.CALL) fire_hunk_async(ms.old_wid.gt,prog),ms.grid=gridtab(null) }catch(e){console.log(e)} }c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Apply',ms.grid.table!=null&&ms.grid.table!=ms.old_wid.gv.table&&!ms.old_wid.g.locked)){ const t=ms.grid.table;modal_exit(0),grid_edit(t) listen_show(ALIGN.right,1,lms('applied table query:')),listen_show(ALIGN.left,1,rtext_string(ms.text.table)) }c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Close',1)||ev.exit)modal_exit(0) if(ms.grid.table){widget_grid(null,{size:gsize,font:FONT_MONO,widths:[],format:'',headers:1,scrollbar:1,lines:1,bycell:0,show:'solid',locked:1},ms.grid)} else{draw_box(gsize,0,1),draw_textc(gsize,'Working...',FONT_BODY,1)} } else if(ms.type=='url'){ const b=draw_modalbox(rect(200,90)) draw_textc(rect(b.x,b.y,b.w,20),'Do you wish to open this URL?',FONT_BODY,1) ui_textedit(rect(b.x,b.y+20+5,b.w,40),1,ms.text) const c=rect(b.x+b.w-(b.w-(2*60+5))/2-60,b.y+b.h-20) ui_button(rect(c.x,c.y,60,20),'Open',1,_=>{open_url(ls(rtext_string(ms.text.table)));modal_exit(0)});c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_exit(0) } else if(ms.type=='link'){ const b=draw_modalbox(rect(230,70)) draw_textc(rect(b.x,b.y,b.w,20),kc.heading='Enter a link string for\nthe selected text span:',FONT_BODY,1) ui_field(rect(b.x,b.y+20+5,b.w,20),ms.text) const c=rect(b.x+b.w-60,b.y+b.h-20) if(ui_button(rect(c.x,c.y,60,20),'OK',1))modal_pop(1);c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_pop(0) if(ui_button(rect(b.x,c.y,60,20),'Card...',1))modal_push('pick_card') } else if(ms.type=='gridcell'){ const c=ms.pending_grid_cell draw_rect(inset(c,-2),32),draw_box(inset(c,-2),0,1) if(ms.pending_grid_cell_rich){ui_richedit(c,1,ms.text)}else{ui_field(c,ms.text)} if(ev.click&&!over(c))modal_exit(1) if(ev.exit)modal_exit(0) } else if(ms.type=='save'){ const l=layout_plaintext(ms.desc,FONT_BODY,ALIGN.center,rect(250,100)) const b=draw_modalbox(radd(l.size,rect(0,20+5+20+5+20))), tbox=rect(b.x,b.y+20,b.w,l.size.y) draw_textc(rect(b.x,b.y-5,b.w,20),'Save File',FONT_MENU,1),draw_text_wrap(tbox,l,1) draw_text(rect(b.x ,tbox.y+tbox.h+7, 56,20),'Filename',FONT_MENU,1) ui_field (rect(b.x+56,tbox.y+tbox.h+5,b.w-56,18),ms.text) const c=rect(b.x+b.w-60,b.y+b.h-20),subtype=ms.subtype if(ui_button(rect(c.x,c.y,60,20),'Save',1,_=>{ const name=ls(rtext_string(ms.text.table)) const savedeck=_=>{save_deck(name,deck);dirty=0} const save_image=_=>{ if(bg_has_sel()){const s=rcopy(dr.sel_here);bg_end_selection(),dr.sel_here=s} let i=draw_con(con(),1),off=rect(),f=1,a=0, bg=dr.trans?0:32, frames=[] const anim=deck.patterns.anim const anim_pattern=(pix,x,y,f)=>pix<28||pix>31?pix: anim[pix-28][f%max(1,anim[pix-28].length)] const draw_pattern=(pix,x,y )=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal[(x%8)+(8*(y%8))+(8*8*pix)]&1 if(bg_has_sel()||bg_has_lasso()){const r=rclip(dr.sel_here,con_dim());i=image_copy(i,r),off=rcopy(r)} for(let z=0;z<4&&dr.show_anim;z++){const c=anim[z].length;if(c)f=lcm(f,c)} for(let z=0;z=28&&v<=31)a=1 const c=anim_pattern(v,x,y,z),p=draw_pattern(c,x+off.x,y+off.y) frame.pix[x+(i.size.x*y)]=c>=32?c: p?1:bg }frames.push(bg_has_lasso()?image_mask(frame,dr.mask):frame) }save_bin(name,writegif(a?frames:[frames[0]], a?frames.map(x=>10):[10],new Int32Array(256),0)) } if(subtype=='save_deck' )savedeck() if(subtype=='save_locked' )iwrite(deck,lms('locked'),ONE),savedeck(),iwrite(deck,lms('locked'),ZERO) if(subtype=='export_image' )save_image() if(subtype=='save_lil'){ let x=ms.verb;arg();ret(ONE) let f=image_is(x)?[x]: lid(x)?ll(dget(x,lms('frames'))||lml([])): lil(x)?ll(x): [] let d=image_is(x)?lml([]): lid(x)? dget(x,lms('delays'))||lml([] ): lml([]) let ff=[],fd=[];f.map((v,i)=>{if(image_is(v))ff.push(v),fd.push(0|clamp(1,!lil(d)?ln(d): i>=count(d)?3: ln(d.v[i]),65535))}) let p=new Int32Array(256),pn=0;if(ms.hint){for(let z=0;z<256;z++)p[z]=z>=ms.hint.length?0:ms.hint[z],pn=min(256,ms.hint.length)} if((lil(x)||lid(x)||image_is(x))&&f.length){save_bin(name,writegif(ff,fd,p,pn))} else if(sound_is(x)) {save_bin(name,writewav(x))} else if(array_is(x)) {save_bin(name,writearray(x))} else if(deck_is(x)) {save_deck(name,x)} else {save_text(name,ls(x))} } modal_exit(1) }));c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_exit(0) } else if(ms.type=='alert'){ const b=draw_modal_rtext(rect(0,5+20)) if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit)modal_exit(0) } else if(ms.type=='confirm'){ const b=draw_modal_rtext(rect(0,5+20)),v=ms.verb?ls(ms.verb):'OK' const vs=font_textsize(FONT_MENU,v);vs.x=min(max(60,vs.x+10),200-65);const c=rect(b.x+b.w-vs.x,b.y+b.h-20) if(ui_button(rect(c.x,c.y,vs.x,20),v,1))modal_exit(1);c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_exit(0) } else if(ms.type=='input'){ const b=draw_modal_rtext(rect(0,5+20+5+20)) ui_field(rect(b.x,b.y+b.h-(20+5+20),b.w,20),ms.text) if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',1)||ev.exit)modal_exit(0) } else if(ms.type=='choose_lil'){ const b=draw_modal_rtext(rect(0,5+80+5+20)) const choose=ui_table(rect(b.x,b.y+b.h-(20+5+80),b.w,80),[],'s',ms.grid) if(ui_button(rect(b.x+b.w-60,b.y+b.h-20,60,20),'OK',ms.grid.row>=0)||choose)modal_exit(1) } else if(ms.type=='open_lil'){ const b=draw_modalbox(rect(125,45)) draw_textc(rect(b.x,b.y,b.w,16),'Click to open a file:',FONT_BODY,1) const c=rect(b.x+b.w-60,b.y+b.h-20) ui_button(rect(c.x,c.y,60,20),'Open',1,_=>{ if (ls(ms.verb)=='array' )open_file(ms.filter,file=>{load_array(file, array=>{arg(),ret(array) ,modal_exit(1)})}) else if(ms.filter=='image/*' )open_file(ms.filter,file=>{load_image(file,ms.verb,image=>{arg(),ret(image) ,modal_exit(1)})}) else if(ms.filter=='audio/*' )open_file(ms.filter,file=>{load_sound(file,ms.verb,sound=>{arg(),ret(sound) ,modal_exit(1)})}) else if(ms.filter=='.csv,.txt' )open_text(ms.filter, text =>{arg(),ret(lms(text)) ,modal_exit(1) }) else if(ms.filter=='.html,.deck')open_text(ms.filter, text =>{arg(),ret(deck_read(text)),modal_exit(1) }) else open_text('' , text =>{arg(),ret(lms(text)) ,modal_exit(1) }) }),c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit){arg(),ret(NIL),modal_exit(0)} } else if(ms.type=='fullscreen_lil'){ const b=draw_modalbox(rect(150,45)) draw_textc(rect(b.x,b.y,b.w,16),'Click to enter fullscreen mode:',FONT_BODY,1) const c=rect(b.x+b.w-80,b.y+b.h-20) ui_button(rect(c.x,c.y,80,20),'Fullscreen',1,_=>{toggle_fullscreen(),modal_exit(1)}),c.x-=65 if(ui_button(rect(c.x,c.y,60,20),'Cancel',1)||ev.exit)modal_exit(0) } else if(ms.type=='brush'){ const grid=rect(6,4), ss=25, gs=ss+4, m=5, lh=font_h(FONT_BODY), br=deck.brushes;grid.y+=ceil(count(br)/grid.x) const b=draw_modalbox(rect(m+(grid.x*gs)+m,m+(grid.y*gs)+lh+m)) const lab=dr.brush>=(6*4)?ls(br.k[dr.brush-(6*4)]):'Choose a brush shape.' draw_textc(rect(b.x,b.y+b.h-lh,b.w,lh),lab,FONT_BODY,1) for(let z=0;zms.type=='widpattern'||ms.type=='spanpattern'?ob.pending_pattern :ms.type=='pattern'?dr.pattern :dr.fill const setv=x=>ms.type=='widpattern'||ms.type=='spanpattern'?ob.pending_pattern=x:ms.type=='pattern'?dr.pattern=x:dr.fill=x const b=draw_modalbox(rect(m+(grid.x*gs)+m,m+(grid.y*gs)+lh+m)); let v=getv() draw_textc(rect(b.x,b.y+b.h-lh,b.w,lh), ms.type=='widpattern'||ms.type=='spanpattern'?'Choose a pattern.': `Choose a ${ms.type=='fill'?'fill':'stroke'} pattern.`,FONT_BODY,1) for(let z=0;z=1||do_panic)modal_exit(0) } ms.in_modal=0 } n_open=([type,hint])=>{ modal_enter('open_lil');let t=type?ls(type):'',r=lms('');ms.filter='' if(t=='array')r=array_make(0,'u8',0) if(t=='sound')ms.filter='audio/*',r=(hint&&ls(hint)=='samples')?array_make(0,'i8',0): sound_make(new Uint8Array(0)) if(t=='image')ms.filter='image/*',r=image_make(rect()) if(t=='deck')ms.filter='.html,.deck',r=deck_read('') if(t=='text')ms.filter='.csv,.txt' if(image_is(r)&&hint&&(ls(hint)=='frames'||ls(hint)=='gray_frames'))r=readgif([],ls(hint)) ms.verb=t=='array'?lms(t): hint?ls(hint):'';return r } n_save=([x,s])=>{ modal_enter('save_lil');x=x||NIL;ms.path_suffix=array_is(x)&&s?ls(s):'',ms.hint=null if(array_is(x) )ms.desc='Save a binary file.' ,ms.text=fieldstr(lms('untitled'+ms.path_suffix)) if(sound_is(x) )ms.desc='Save a .wav sound file.' ,ms.text=fieldstr(lms('sound.wav')) if(deck_is(x) )ms.desc='Save a .deck or .html file.',ms.text=fieldstr(lms('untitled.deck')) if(image_is(x)||lid(x)||(lil(x)&&x.v.some(image_is)))ms.desc='Save a .gif image file.' ,ms.text=fieldstr(lms('image.gif')),ms.hint=s?ll(s).map(ln):null ms.verb=x;return NIL } n_alert=([t,p,x,y])=>{ if(ls(p)=='bool'){modal_enter('confirm_lil'),ms.verb=x?lms(ls(x)):null} else if(ls(p)=='string'){modal_enter('input_lil');if(x)ms.text=fieldstr(x)} else if(ls(p)=='choose'){ modal_enter('choose_lil') ms.verb=!x?ld(NIL): lil(x)?dyad.dict(x,x): ld(x); if(count(ms.verb)<1)ms.verb=ld(monad.list(NIL)) ms.grid=gridtab(lt(monad.keys(ms.verb)), y?dvix(ms.verb,y):-1) }else{modal_enter('alert_lil')} ms.message=plain_or_rich(t);return NIL } free_canvas=deck=>{ // make a drawing surface that isn't attached to the parent deck, but is aware of its resources: const d=deck_read('{deck}\n{card:home}\n{widgets}\nc:{"type":"canvas"}') const c=d.cards.v[0], r=c.widgets.v[0] iwrite(r,lms('size'),lmpair(deck.size)),d.fonts=deck.fonts,d.patterns=deck.patterns,r.free=1 container_image(r,1);return r } go_notify=(target_deck,x,t,url,delay)=>{ if(target_deck!=deck)return if(url&&/^(http|https|ftp|gopher|gemini):\/\//.test(url)){modal_enter('url'),ms.text=fieldstr(lms(url));return} const moved=x!=ln(ifield(ifield(target_deck,'card'),'index')) if(moved){con_set(null);if(uimode=='script')close_script()} if(moved&&!target_deck.locked&&x>=0){try{ const name=ls(ifield(target_deck.cards.v[x],'name')) history.replaceState(undefined,undefined,'#'+name) }catch(e){}} const tfun=t==null?null: lion(t)?t: dget(target_deck.transit,t) if(ms.type!='trans'&&x>=0&&tfun){ modal_enter('trans'),ms.time_curr=0,ms.time_end=delay==null?30:0|max(1,ln(delay)),ms.time_start=-1; ms.trans=tfun, ms.canvas=free_canvas(target_deck) ms.carda=draw_con(ifield(target_deck,'card')), ms.cardb=draw_con(ifield(target_deck,'cards').v[x]) } if(moved&&uimode=='interact')msg.pending_loop=1 if(moved||t){ grid_exit(),field_exit(),bg_end_selection(),bg_end_lasso(),ob.sel=[],wid.active=ms.type=='listen'?0:-1,mark_dirty() } if(uimode=='interact')msg.next_view=1 } let field_notify_disable=0 field_notify=(field)=>{ if(field_notify_disable||!wid.infield||wid.ft!=field)return const v=ifield(field,'value');if(rtext_len(v))return wid.fv={table:v,scroll:0} wid.cursor=rect(),wid.field_dirty=0 wid.hist=[],wid.hist_cursor=0 if(enable_touch){field_exit(),wid.active=-1} } // Keycaps KS=(v,l,w)=>({v,l,w}), K=v=>KS(v,v,1), KCOMB=i=>KS(i,'',1), KSP=i=>K(drom_from_ord(i)), KBL=KS(null,'',1) const LCAPS=[ [K('`'),K('1'),K('2'),K('3'),K('4'),K('5'),K('6'),K('7'),K('8'),K('9'),K('0'),K('-'),K('='),KS('Backspace','delete',1.5)], [KS('Tab','tab',1.5),K('q'),K('w'),K('e'),K('r'),K('t'),K('y'),K('u'),K('i'),K('o'),K('p'),K('['),K(']'),K('\\')], [KS('CapsLock','capslock',2),K('a'),K('s'),K('d'),K('f'),K('g'),K('h'),K('j'),K('k'),K('l'),K(';'),K('\''),KS('Enter','return',2)], [KS('Shift','shift',2.5),K('z'),K('x'),K('c'),K('v'),K('b'),K('n'),K('m'),K(','),K('.'),K('/'),KS('Shift','shift',2.5)], [KS('ArrowLeft','',1),KS('ArrowDown','',1),KS('ArrowUp','',1),KS('ArrowRight','',1),KS('alt','alt',1),KS(' ',' ',5),KS('alt','alt',1),KS(-2,'',2),KS(-1,'OK',2)], ] const UCAPS=[ [K('~'),K('!'),K('@'),K('#'),K('$'),K('%'),K('^'),K('&'),K('*'),K('('),K(')'),K('_'),K('+'),KS('Backspace','delete',1.5)], [KS('Tab','tab',1.5),K('Q'),K('W'),K('E'),K('R'),K('T'),K('Y'),K('U'),K('I'),K('O'),K('P'),K('{'),K('}'),K('|')], [KS('CapsLock','capslock',2),K('A'),K('S'),K('D'),K('F'),K('G'),K('H'),K('J'),K('K'),K('L'),K(':'),K('"'),KS('Enter','return',2)], [KS('Shift','shift',2.5),K('Z'),K('X'),K('C'),K('V'),K('B'),K('N'),K('M'),K('<'),K('>'),K('?'),KS('Shift','shift',2.5)], [KS('ArrowLeft','',1),KS('ArrowDown','',1),KS('ArrowUp','',1),KS('ArrowRight','',1),KS('alt','alt',1),KS(' ',' ',5),KS('alt','alt',1),KS(-2,'',2),KS(-1,'OK',2)], ] const ALCAPS=[ [KBL,KSP(0xeb),KBL,KBL,KSP(0xef),KBL,KBL,KBL,KSP(0xf0),KBL,KBL,KCOMB('MACRON'),KSP(0xff),KS(0,'',1.5)], [KS(0,'',1.5),KSP(0xd6),KSP(0xc1),KCOMB('ACUTE'),KBL,KSP(0xbc),KBL,KCOMB('UMLAUT'),KCOMB('CIRCUM'),KSP(0xb6),KBL,KSP(0xed),KSP(0xee),KBL], [KS('CapsLock','capslock',2),KSP(0xa4),KSP(0x9e),KSP(0xaf),KBL,KCOMB('GRAVE'),KCOMB('HUNGRA'),KBL,KCOMB('OGONEK'),KSP(0xce),KSP(0x7f),KSP(0xa5),KS(0,'',2)], [KS('Shift','shift',2.5),KBL,KBL,KSP(0xa6),KCOMB('CARON'),KSP(0xcc),KCOMB('TILDE'),KBL,KCOMB('COMMA'),KSP(0xe3),KSP(0xec),KS('Shift','shift',2.5)], [KBL,KBL,KBL,KBL,KS('alt','alt',1),KS(' ',' ',5),KS('alt','alt',1),KS(0,'',2),KS(0,'',2)], ] const AUCAPS=[ [KBL,KSP(0xeb),KBL,KBL,KSP(0xef),KBL,KBL,KBL,KSP(0xf0),KBL,KBL,KCOMB('MACRON'),KSP(0xff),KS(0,'',1.5)], [KS(0,'',1.5),KSP(0xd5),KSP(0xc0),KCOMB('ACUTE'),KBL,KSP(0x9d),KBL,KCOMB('UMLAUT'),KCOMB('CIRCUM'),KSP(0x97),KBL,KSP(0xed),KSP(0xee),KBL], [KS('CapsLock','capslock',2),KSP(0x85),KSP(0xea),KSP(0x90),KBL,KCOMB('GRAVE'),KCOMB('HUNGRA'),KBL,KCOMB('OGONEK'),KSP(0xcd),KSP(0x7f),KSP(0x86),KS(0,'',2)], [KS('Shift','shift',2.5),KBL,KBL,KSP(0x87),KCOMB('CARON'),KSP(0xcc),KCOMB('TILDE'),KBL,KCOMB('COMMA'),KSP(0xe2),KSP(0xec),KS('Shift','shift',2.5)], [KBL,KBL,KBL,KBL,KS('alt','alt',1),KS(' ',' ',5),KS('alt','alt',1),KS(0,'',2),KS(0,'',2)], ] const ACCENTS={ MACRON:image_read('%%IMG2AAYADQAHAQQAQw=='), ACUTE :image_read('%%IMG0AAYADRAgAAAAAAAAAAAAAAA='), UMLAUT:image_read('%%IMG0AAYADQBIAAAAAAAAAAAAAAA='), CIRCUM:image_read('%%IMG0AAYADTBIAAAAAAAAAAAAAAA='), GRAVE :image_read('%%IMG0AAYADSAQAAAAAAAAAAAAAAA='), HUNGRA:image_read('%%IMG0AAYADQBEiAAAAAAAAAAAAAA='), OGONEK:image_read('%%IMG0AAYADQAAAAAAAAAAAAAYDAA='), CARON :image_read('%%IMG0AAUADVAgAAAAAAAAAAAAAAA='), TILDE :image_read('%%IMG0AAYADTRYAAAAAAAAAAAAAAA='), COMMA :image_read('%%IMG0AAUADQAAAAAAAAAAAAAAIEA='), } const ACCENT={ // abcdefghijklmnopqrstuvwxyz MACRON:'ā ē ī ō ū ', ACUTE :'á ć é í ńó ś ú ýź', UMLAUT:'ä ë ï ö ü ÿ ', CIRCUM:'â ê î ô û ', GRAVE :'à è ì ò ù ', HUNGRA:' ő ű ', OGONEK:'ą ę ', CARON :' š ž', TILDE :'ã ñõ ', COMMA :' șț ', } soft_keyboard=r=>{ let ex=0, el=0, y=r.y, kh=0|(r.h/LCAPS.length), sh=ev.shift^kc.lock^kc.shift, combiner=kc.comb, pal=deck.patterns.pal.pix for(let row=0;rowa+x.w,0), keys=(kc.alt?(sh?AUCAPS:ALCAPS): sh?UCAPS:LCAPS)[row]; let x=0 keys.forEach((k,i,arr)=>{ let b=rint(rect(r.x+x+1,y,i==arr.length-1?(r.w-x):(k.w*(r.w/w)),kh+1));x+=b.w-1 draw_box(b,0,1) const e=k.v==-2&&wid.f&&wid.f.style=='code'&&uimode=='interact' const letter=k.v&&/^[a-zA-Z]$/.test(k.v)?k.v.toLowerCase().charCodeAt(0)-97: -1 const accented=letter!=-1&&ACCENT[combiner]?ACCENT[combiner][letter]: ' ' const temp=ACCENT[combiner]?(sh?drom_toupper(accented):accented): '' const special=k.v=='Shift'||k.v=='CapsLock' if(combiner&&!special){if(accented!=' ')draw_textc(b,temp,FONT_MENU,1)} else if(k.v=='ArrowLeft' )draw_iconc(b,ARROWS[4],1) else if(k.v=='ArrowDown' )draw_iconc(b,ARROWS[1],1) else if(k.v=='ArrowUp' )draw_iconc(b,ARROWS[0],1) else if(k.v=='ArrowRight')draw_iconc(b,ARROWS[5],1) else draw_textc(b,e?'run':k.l,FONT_MENU,1) let kd=keydown[k.v];b=inset(b,2) const a=dover(b)&&over(inset(b,-4))&&ev.down_modal==ms.type&&ev.down_uimode==uimode&&ev.down_caps==1;if(k.v&&a&&(ev.md||ev.drag))kd=1 if(combiner&&!special){if(ev.mu&&a&&accented!=' '){field_input(temp);kc.shift=0,kc.alt=0,kc.comb=0}kd=kd&&accented!=' '&&a} else if(k.v==-1){draw_box(b,0,13);if(ev.mu&&a)ex=1} else if(k.v=='alt'){if(ev.mu&&a)kc.alt^=1;if(kc.alt)kd=1} else if(e){if(ev.mu&a)el=1} else if(k.v=='Shift' ){if(ev.mu&&a)kc.shift^=1;if(kc.shift)kd=1} else if(k.v=='CapsLock'){if(ev.mu&&a)kc.lock^=1;if(kc.lock)kd=1} else if(ACCENT[k.v]){draw_box(b,0,1),draw_iconc(b,ACCENTS[k.v],1);if(ev.mu&&a)kc.comb=k.v,kc.alt=0} else if(ev.mu&&a&&k.v){if(k.v.length==1){field_input(k.v)}else{field_keys(k.v,sh)};kc.shift=0,kc.alt=0} if(kd)draw_invert(pal,b) });y+=kh };return {exit:ex,eval:el} } keycaps=_=>{ if(!wid.fv)kc.on=0;if(!kc.on)return frame.image.pix.fill(32) const mh=3+font_h(FONT_MENU) const r=rect(0,mh,frame.size.x+1,0|((frame.size.y/2)-mh)) if(kc.heading){const s=font_textsize(FONT_MENU,kc.heading);const h=rect(r.x,r.y,r.w,s.y+5);draw_textc(h,kc.heading,FONT_MENU,1),r.h-=h.h,r.y+=h.h} if(ms.type)ms.in_modal=1 if(uimode=='script'){script_editor(r)} else if(ms.type=='listen'){const c=frame.clip;frame.clip=rect(r.x,r.y,r.w,r.h+6);wid.count=0,listener(r),frame.clip=c} else{ wid.count=wid.active widget_field(wid.ft,{size:inset(r,5),font:wid.f.font,show:'solid',scrollbar:1,border:1,style:wid.f.style,align:wid.f.align,locked:0,pattern:wid.f.pattern},wid.fv) ms.in_modal=0 } const modes=soft_keyboard(inset(rect(r.x,r.y+r.h+1,r.w-2,frame.size.y-(r.y+r.h)),5)) if(ms.type=='listen'&&(modes.eval||ev.eval))listener_eval() if(ms.type!='listen'&&(modes.eval||ev.eval))field_keys('Enter',1) if(modes.exit||ev.exit){ field_exit();wid.active=-1 if(uimode=='script')close_script() if(ms.type=='gridcell')modal_exit(1) if(ms.type=='link' )modal_pop(1) if(ms.type=='listen' )modal_exit(0) } } // General Purpose Edit History let doc_hist=[], doc_hist_cursor=0 has_undo=_=>doc_hist_cursor>0 has_redo=_=>doc_hist_cursor{const x=doc_hist[--(doc_hist_cursor)];apply(0,x)} redo=_=>{const x=doc_hist[(doc_hist_cursor)++];apply(1,x)} edit=x=>{doc_hist=doc_hist.slice(0,doc_hist_cursor),doc_hist.push(x),redo()} edit_target=r=>{ const c=con() if(card_is (c))r.card=ln(ifield(c,'index')) if(prototype_is(c))r.def=ifield(c,'name') return r } apply=(fwd,x)=>{ let container=null, t=x.type if(x.def){con_set(container=dget(deck.contraptions,x.def))} else if(x.card!=undefined){ let c=ifield(deck,'card') if(ln(ifield(c,'index'))!=x.card)n_go([lmn(x.card)],deck),c=ifield(deck,'card') con_set(null),container=c } if(!container)return const wids=con_wids() if(t=='ob_create'){fwd=!fwd,t='ob_destroy'} if(t=='bg_block'){ if(uimode!='draw')setmode('draw') const r=x.pos, p=x[fwd?'after':'before'] let bg=container_image(container,1), s=bg.size const cs=getpair(ifield(container,'size')) if(s.x!=cs.x||s.y!=cs.y){bg=image_resize(bg,cs),iwrite(container,lms('image'),bg),s=cs} const cb=x.clr_before, ca=x.clr_after, clip=rect(0,0,s.x,s.y) if(fwd&&ca)image_paste(x.clr_pos,clip,ca,bg,1) image_paste(r,clip,p,bg,1) if(!fwd&&cb)image_paste(x.clr_pos,clip,cb,bg,1) } else if(t=='ob_props'){ if(uimode!='object')setmode('object') const p=x[fwd?'after':'before'] Object.keys(p).map(k=>{const d=p[k], w=dget(wids,lms(k));if(w)Object.keys(d).map(i=>iwrite(w,lms(i),d[i]))}) } else if(t=='ob_destroy'){ if(uimode!='object')setmode('object') ob.sel=[] if(fwd){ const w=x.props.map(z=>dget(wids,dget(z,lms('name')))).filter(x=>x!=null) x.props=con_copy_raw(container,w),w.map(z=>card_remove(container,z)) } else{ const w=con_paste_raw(container,x.props);w.map((z,i)=>{ dset(x.props[i],lms('name'),ifield(z,'name')),ob.sel.push(z) if(!dget(x.props[i],lms('pos')))iwrite(z,lms('pos'),lmpair(rcenter(con_dim(),getpair(ifield(z,'size'))))) }) } } else if(t=='proto_props'){ const v=x[fwd?'after':'before'];x.keys.map((k,i)=>iwrite(container,k,v[i])) }mark_dirty() } // Draw Mode image_overlay=(dst,src,mask,offset)=>{ const sd=dst.size, ss=src.size, d=rect(0,0,sd.x,sd.y);for(let b=0;b{const r=image_copy(src);for(let z=0;zdr.scratch.pix.fill(BG_MASK) bg_scratch_under=_=>{if(dr.scratch&&dr.under){const b=con_image();for(let z=0;z{ const c=con_size();if(!dr.scratch)dr.scratch=image_make(c) const s=dr.scratch.size;if(s.x!=c.x||s.y!=c.y)dr.scratch=image_make(c) bg_scratch_clear() } bg_edit=_=>{ const d=find_occupied(dr.scratch,BG_MASK) const back=con_image(),after=image_copy(back,d);image_overlay(after,image_copy(dr.scratch,d),BG_MASK,rect(0,0)) edit(edit_target({type:'bg_block',pos:d,before:image_copy(back,d),after})) } draw_limbo=(clip,scale)=>{ clip=rnorm(clip);if(!dr.limbo){/*nothing*/} else if(scale&&dr.limbo_dither){draw_rect (clip,21)} else if(scale ){draw_fat_scaled(screen_to_con(clip),dr.limbo,!dr.trans,deck.patterns.pal.pix,frame_count,dr.zoom,con_to_screen(rect(0,0)))} else if( dr.limbo_dither){draw_dithered (clip,dr.limbo,!dr.trans,dr.omask,dr.dither_threshold)} else {draw_scaled (clip,dr.limbo,!dr.trans)} } bg_scaled_limbo=_=>{ const d=dr.sel_here, back=con_image() const r=dr.trans||dr.omask?image_copy(back,d):image_make(rect(d.w,d.h)), s=r.size, t=frame;frame=draw_frame(r) if(dr.trans){const c=dr.sel_start;draw_rect(rect(c.x-d.x,c.y-d.y,c.w,c.h),dr.fill)} draw_limbo(frame.clip,0),frame=t;return r } bg_edit_sel=_=>{ if(!dr.limbo)return const d=rcopy(dr.sel_here), back=con_image() const r={type:'bg_block',pos:d,before:image_copy(back,d),after:bg_scaled_limbo()} dr.limbo=null,dr.limbo_dither=0 if(dr.sel_start.w>0||dr.sel_start.h>0){ const after=image_make(rect(dr.sel_start.w,dr.sel_start.h));for(let z=0;zimage_copy(container_image(con(),1),s) bg_scoop_selection=_=>{if(dr.limbo)return;dr.sel_start=rcopy(dr.sel_here);dr.limbo=bg_copy_selection(dr.sel_start),dr.limbo_dither=0} bg_draw_lasso=(r,o,show_ants,fill)=>{ if(dr.omask){ const s=dr.omask.size for(let a=0;a{ if(!bg_has_lasso())return const d=rsub(ev.pos,ev.dpos), dh=rect(dr.sel_here.x+d.x,dr.sel_here.y+d.y,dr.sel_here.w,dr.sel_here.h) const dd=rsub(ev.pos,dh), insel=dr.mask!=null&&rin(dh,ev.pos)&&dr.mask.pix[dd.x+dd.y*dh.w], r=ev.drag&&insel?dh:dr.sel_here, origin=con_to_screen(rect(0,0)) if(!dr.fatbits){bg_draw_lasso(con_to_screen(r),con_to_screen(dr.sel_start),1,dr.fill);return} const o=dr.sel_start, anim=deck.patterns.anim, pal=deck.patterns.pal.pix const anim_pattern=(pix,x,y)=>pix<28||pix>31?pix: anim[pix-28][(0|(frame_count/4))%max(1,anim[pix-28].length)] const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal_pat(pal,pix,x,y)&1 if(dr.omask)for(let a=0;a=32?c: c==0?c: pat?1:32) } for(let a=0;a=r.w-1||!dr.mask.pix[(b+1)+a*r.w])draw_vline(p.x +dr.zoom-1,p.y,p.y+dr.zoom-1,ANTS) if(a<= 0||!dr.mask.pix[b+(a-1)*r.w])draw_hline(p.x,p.x+dr.zoom-1,p.y ,ANTS) if(a>=r.h-1||!dr.mask.pix[b+(a+1)*r.w])draw_hline(p.x,p.x+dr.zoom-1,p.y +dr.zoom-1,ANTS) } } bg_tools=_=>{ const bg_cancel=_=>{bg_scratch(),dr.poly=[]} if (!dr.fatbits&&ev.mu&&ev.alt){dr.fatbits=1,center_fatbits(ev.pos),bg_cancel();return} else if( dr.fatbits&&ev.mu&&ev.alt){dr.fatbits=0,bg_cancel();return}if(ev.alt)return if(ev.md)pointer.prev=ev.pos if(!dover(con_view_dim()))ev.md=ev.mu=ev.drag=0 if(ev.mdown){dr.pattern=ln(iwrite(con_image(),lmpair(ev.pos)))} // pipette if(dr.tool=='pencil'||dr.tool=='line'||dr.tool=='rect'||dr.tool=='fillrect'||dr.tool=='ellipse'||dr.tool=='fillellipse'){ let clear=0;if(!dr.scratch)bg_scratch() if(ev.md){bg_scratch(),dr.erasing=ev.rdown||ev.shift} else if(ev.mu||ev.drag){ const t=frame;frame=draw_frame(dr.scratch) if(dr.tool=='pencil'||dr.erasing){ draw_line(rpair(pointer.prev,ev.pos),dr.brush,dr.erasing?0:bg_pat(),deck) } else if(dr.tool=='line'){ let b=rcopy(snap(ev.dpos)),t=rcopy(snap(ev.pos));if(ev.shift){ // snap to isometric angles const d=rsub(t,b);t=b if (Math.abs(d.x)*4{ const a=z*(2*Math.PI)/divs;return rint(rect(c.x+(0.5+r.w/2.0)*Math.cos(a),c.y+(0.5+r.h/2.0)*Math.sin(a))) });poly.push(poly[0]) if(dr.tool=='fillellipse')draw_poly(poly,bg_fill());draw_lines(poly,dr.brush,bg_pat(),deck) } bg_scratch_under(),frame=t;if(ev.mu)bg_edit(),clear=1 } if(dr.scratch){ if(dr.fatbits){draw_fat(con_clip(),dr.scratch,deck.patterns.pal.pix,frame_count,BG_MASK,dr.zoom,dr.offset)} else{image_overlay(frame.image,dr.scratch,BG_MASK,con_offset());} } if(clear)bg_scratch_clear() } if(dr.tool=='lasso'){ const d=rect(ev.pos.x-ev.dpos.x,ev.pos.y-ev.dpos.y) const dh=rect(dr.sel_here.x+d.x,dr.sel_here.y+d.y,dr.sel_here.w,dr.sel_here.h) const dd=rect(ev.pos.x-dh.x,ev.pos.y-dh.y), insel=dr.mask!=null&&over(dh)&&dr.mask.pix[dd.x+dd.y*dh.w] if(ev.md&&!insel){bg_lasso_preview(),bg_end_lasso(),dr.poly.push(rcopy(ev.dpos))} else if(ev.drag&&!insel&&dr.poly.length>0){const l=last(dr.poly);if(ev.pos.x!=l.x||ev.pos.y!=l.y)dr.poly.push(rcopy(ev.pos))} else if(ev.mu&&insel){dr.sel_here=radd(dr.sel_here,d)} else if(ev.mu){ const r=poly_bounds(dr.poly);if(r.w>1&&r.h>1){ dr.mask=image_make(rect(r.w,r.h)) const t=frame;frame=draw_frame(dr.mask) for(let a=0;a0)draw_lines(dr.poly.concat(dr.poly[0]).map(p=>rsub(p,rect(r.x,r.y))),0,ANTS,deck) frame=t,dr.sel_here=rcopy(r),dr.sel_start=rcopy(r) dr.omask=image_copy(dr.mask);dr.omask.pix.forEach((v,i)=>dr.omask.pix[i]=v!=0) bg_scoop_selection() }dr.poly=[] } if(dr.mask&&dr.limbo){ if(ev.dir=='left' ){ev.dir=0,dr.sel_here.x--} if(ev.dir=='up' ){ev.dir=0,dr.sel_here.y--} if(ev.dir=='right'){ev.dir=0,dr.sel_here.x++} if(ev.dir=='down' ){ev.dir=0,dr.sel_here.y++} if(ev.exit){dr.limbo=dr.mask=dr.omask=null,bg_end_lasso()} } } if(dr.tool=='poly'){ if(ev.md){dr.poly=[rect(ev.dpos.x,ev.dpos.y)]} else if(ev.drag&&dr.poly.length>0){const l=last(dr.poly);if(ev.pos.x!=l.x||ev.pos.y!=l.y)dr.poly.push(rcopy(ev.pos))} else if(ev.mu){ dr.poly.push(rcopy(ev.dpos)),bg_scratch();const t=frame;frame=draw_frame(dr.scratch) draw_poly(dr.poly,bg_fill()),draw_lines(dr.poly,dr.brush,bg_pat(),deck) bg_scratch_under(),frame=t,bg_edit(),bg_scratch_clear(),dr.poly=[] } } if(dr.tool=='lasso'||dr.tool=='poly'){ const o=rsub(dr.sel_here,dr.sel_start);o.w=o.h=0 draw_lines(dr.poly.map(p=>radd(o,con_to_screen(p))),dr.tool=='lasso'?0:dr.brush,dr.tool=='lasso'?ANTS:bg_pat(),deck) } if(dr.tool=='fill'&&ev.mu){ const bg=container_image(con(),1), t=frame;bg_scratch(),frame=draw_frame(dr.scratch) draw_fill(ev.pos,ev.rup?0:bg_pat(),bg),frame=t,bg_edit(),bg_scratch_clear() } if(!bg_has_sel()&&!bg_has_lasso()){ if(dr.fatbits){ if(ev.dir=='left' )dr.offset.x-=ev.shift?dr.grid_size.x:1 if(ev.dir=='right')dr.offset.x+=ev.shift?dr.grid_size.x:1 if(ev.dir=='up' )dr.offset.y-=ev.shift?dr.grid_size.y:1 if(ev.dir=='down' )dr.offset.y+=ev.shift?dr.grid_size.y:1 clamp_fatbits();if(ev.exit)dr.fatbits=0,ev.exit=0 }else{tracking()} } } bg_end_lasso=_=>{ if(uimode!='draw'||dr.tool!='lasso')return const data=dr.mask&&dr.limbo, diffrect=!requ(dr.sel_here,dr.sel_start);let diffmask=dr.omask==null||(dr.mask&&dr.omask.pix.length!=dr.mask.pix.length); if(dr.omask&&!diffmask)for(let z=0;data&&z0)!=(dr.omask.pix[z]>0)){diffmask=1;break} if(data&&(diffrect||diffmask||dr.lasso_dirty)){ bg_scratch();const t=frame;frame=draw_frame(dr.scratch),bg_draw_lasso(dr.sel_here,dr.sel_start,0,dr.fill),frame=t,bg_edit(),bg_scratch_clear() }dr.poly=[],dr.mask=null,dr.omask=null,dr.limbo=null,dr.lasso_dirty=0,dr.sel_here=rect(),dr.sel_start=rect() } bg_end_selection=_=>{ if(uimode!='draw'||dr.tool!='select')return if(dr.sel_here.w<=0&&dr.sel_here.h<=0)return bg_edit_sel(),dr.sel_start=rcopy(ev.dpos),dr.sel_here=rcopy(ev.dpos) } bg_delete_selection=_=>{ if(bg_has_lasso()){ dr.mask=null,bg_scratch();const t=frame;frame=draw_frame(dr.scratch),bg_draw_lasso(dr.sel_here,dr.sel_start,0,dr.fill),frame=t bg_edit(),bg_scratch_clear(),bg_end_lasso();return } if(!bg_has_sel())return if(dr.limbo!=null&&dr.sel_start.w<=0&&dr.sel_start.h<=0){dr.sel_here=rect(),dr.limbo=null,dr.limbo_dither=0;return} if(dr.sel_start.w<=0&&dr.sel_start.h<=0)dr.sel_start=rcopy(dr.sel_here) dr.sel_here=rect(),dr.limbo=image_make(rect(1,1)),dr.limbo_dither=0,bg_edit_sel(),dr.sel_start=rcopy(ev.dpos),dr.sel_here=rcopy(ev.dpos) } bg_paste=(image,fit)=>{ const clip=con_dim(), f=rect(clip.w*.75,clip.h*.75);let s=image.size if(fit&&(s.x>f.x||s.y>f.y)){const scale=min(f.x/s.x,f.y/s.y);s=rect(s.x*scale,s.y*scale)}if(!s.x)return if(bg_has_sel()){bg_scoop_selection(),dr.limbo=image,dr.limbo_dither=0} else{settool('select'),dr.sel_start=rect(),dr.sel_here=rcenter(con_view_dim(),s),dr.limbo=image,dr.limbo_dither=0} } handle_size=_=>enable_touch?10:5 draw_handles=r=>{ const h=handle_size(), pal=deck.patterns.pal.pix const x0=r.x+1-h, x2=r.x+r.w-1, y0=r.y+1-h, y2=r.y+r.h-1, x1=0|((x2-x0)/2+x0), y1=0|((y2-y0)/2+y0) draw_invert(pal,rclip(rect(x0,y0,h,h),frame.clip)) draw_invert(pal,rclip(rect(x0,y2,h,h),frame.clip)) draw_invert(pal,rclip(rect(x2,y0,h,h),frame.clip)) draw_invert(pal,rclip(rect(x2,y2,h,h),frame.clip)) draw_invert(pal,rclip(rect(x2,y1,h,h),frame.clip)) draw_invert(pal,rclip(rect(x1,y2,h,h),frame.clip)) draw_invert(pal,rclip(rect(x1,y0,h,h),frame.clip)) draw_invert(pal,rclip(rect(x0,y1,h,h),frame.clip)) } in_handle=r=>{ const h=handle_size(), x0=r.x+1-h, x2=r.x+r.w-1, y0=r.y+1-h, y2=r.y+r.h-1, x1=0|((x2-x0)/2+x0), y1=0|((y2-y0)/2+y0) if(over(rect(x0,y0,h,h)))return 4 if(over(rect(x0,y2,h,h)))return 6 if(over(rect(x2,y0,h,h)))return 2 if(over(rect(x2,y2,h,h)))return 0 if(over(rect(x2,y1,h,h)))return 1 if(over(rect(x1,y0,h,h)))return 3 if(over(rect(x0,y1,h,h)))return 5 if(over(rect(x1,y2,h,h)))return 7;return -1 } bg_select=_=>{ if(uimode!='draw'||dr.tool!='select')return rect(0,0,0,0) let s=rcopy(dr.sel_here), has_sel=s.w>0||s.h>0, in_sel=has_sel&&dover(s) const ax=min(ev.dpos.x,ev.pos.x), bx=max(ev.dpos.x,ev.pos.x), ay=min(ev.dpos.y,ev.pos.y), by=max(ev.dpos.y,ev.pos.y), h=handle_size() const x0=s.x+1-h, x2=s.x+s.w-1, y0=s.y+1-h, y2=s.y+s.h-1, x1=0|((x2-x0)/2+x0), y1=0|((y2-y0)/2+y0), dx=ev.pos.x-ev.dpos.x, dy=ev.pos.y-ev.dpos.y const sz=dr.limbo?dr.limbo.size:rect(dr.sel_start.w,dr.sel_start.h) handle=(rw,rh,ox,oy,ow,oh)=>{if(has_sel&&(ev.mu||ev.drag)&&dover(rect(rw,rh,h,h))){ s=rnorm(keep_ratio(rect(s.x+ox,s.y+oy,s.w+ow,s.h+oh),sz)) if(ev.mu)dr.sel_here=snapr(s);bg_scoop_selection(),uicursor=cursor.drag;return 1 };return 0} if(in_layer()){ if (handle(x2,y2, 0, 0, dx, dy)){} // se else if(handle(x0,y2, dx, 0,-dx, dy)){} // sw else if(handle(x2,y0, 0,dy, dx,-dy)){} // ne else if(handle(x0,y0, dx,dy,-dx,-dy)){} // nw else if(handle(x2,y1, 0, 0, dx, 0)){} // e else if(handle(x1,y0, 0,dy, 0,-dy)){} // n else if(handle(x0,y1, dx, 0,-dx, 0)){} // w else if(handle(x1,y2, 0, 0, 0, dy)){} // s else if(ev.md&&in_sel){bg_scoop_selection()} // begin move else if((ev.mu||ev.drag)&&in_sel){s.x+=dx, s.y+=dy;if(ev.mu)dr.sel_here=snap(s)} // move/finish else if(ev.md&&!in_sel){if(has_sel){draw_limbo(con_to_screen(s),dr.fatbits),bg_end_selection(),has_sel=0}s=rcopy(dr.sel_here)} // begin create else if(ev.mu||ev.drag){s=snapr(rect(ax,ay,bx-ax,by-ay));if(ev.mu)dr.sel_here=s} // size/finish } if(has_sel)draw_limbo(con_to_screen(s),dr.fatbits) if(in_layer()){ let nudge=0 if(has_sel&&ev.dir=='left' ){ev.dir=0,bg_scoop_selection(),dr.sel_here.x-=ev.shift?dr.grid_size.x:1,nudge=1} if(has_sel&&ev.dir=='up' ){ev.dir=0,bg_scoop_selection(),dr.sel_here.y-=ev.shift?dr.grid_size.y:1,nudge=1} if(has_sel&&ev.dir=='right'){ev.dir=0,bg_scoop_selection(),dr.sel_here.x+=ev.shift?dr.grid_size.x:1,nudge=1} if(has_sel&&ev.dir=='down' ){ev.dir=0,bg_scoop_selection(),dr.sel_here.y+=ev.shift?dr.grid_size.y:1,nudge=1} if(nudge&&ev.shift)dr.sel_here=snap(dr.sel_here) if(ev.exit){dr.limbo=null,dr.limbo_dither=0,bg_end_selection()} } return s } bg_box_to_lasso=_=>{ const r=dr.sel_here if(dr.tool=='select'){ dr.tool='lasso',bg_scoop_selection();const s=dr.sel_start, l=dr.limbo, t=frame dr.limbo=image_make(rect(r.w,r.h)),frame=draw_frame(dr.limbo) if(dr.limbo_dither){draw_dithered(frame.clip,l,1,dr.omask,dr.dither_threshold),dr.limbo_dither=0}else{draw_scaled(frame.clip,l,1)}frame=t dr.mask=image_make(rect(r.w,r.h)),dr.mask.pix.fill(1) if(s.w>0&&s.h>0){dr.omask=image_make(rect(s.w,s.h)),dr.omask.pix.fill(1)}else{dr.omask=null} } } bg_regenerate_lasso_outline=_=>{ const r=dr.sel_here const set=(p,v)=>dr.mask.pix[p.x+p.y*dr.sel_here.w]=v const get=p=>(p.x<0||p.y<0||p.x>=dr.sel_here.w||p.y>=dr.sel_here.h)?0:dr.mask.pix[p.x+p.y*dr.sel_here.w] for(let a=0;a{ const r=dr.sel_here bg_box_to_lasso() const set=(p,v)=>dr.mask.pix[p.x+p.y*dr.sel_here.w]=v const get=p=>(p.x<0||p.y<0||p.x>=dr.sel_here.w||p.y>=dr.sel_here.h)?0:dr.mask.pix[p.x+p.y*dr.sel_here.w] let changed=1,background=dr.fill;while(changed){changed=0 // erode the mask, iterating to a fixed point for(let a=0;a{ const r=dr.sel_here;bg_box_to_lasso() dr.mask.pix.forEach((v,i)=>dr.mask.pix[i]=v!=0) // strip the ANTS outline const l=image_copy(dr.limbo);for(let a=0;a0 ){const i=(b-1)+(a )*r.w; n|=dr.limbo.pix[i]&&dr.mask.pix[i]} if(b0 ){const i=(b )+(a-1)*r.w; n|=dr.limbo.pix[i]&&dr.mask.pix[i]} if(a{ob.sel.sort((av,bv)=>ln(ifield(av,'index'))-ln(ifield(bv,'index')))} ob_move_up=_=>{if(ob.sel.length<1)return;ob_order();ob.sel.slice(0).reverse().map(o=>iwrite(o,lms('index'),lmn(ln(ifield(o,'index'))+1)))} ob_move_dn=_=>{if(ob.sel.length<1)return;ob_order();ob.sel .map(o=>iwrite(o,lms('index'),lmn(ln(ifield(o,'index'))-1)))} ob_edit_prop=(key,value)=>{ const before={}, after={} ob.sel.map(w=>{const n=ls(ifield(w,'name')),bp={},ap={}; bp[key]=ifield(w,key),ap[key]=value,before[n]=bp,after[n]=ap}) edit(edit_target({type:'ob_props',before,after})) } ob_create=props=>{ edit(edit_target({type:'ob_create',props})) } ob_destroy=_=>{ if(ob.sel.length<1)return const props=ob.sel.map(w=>lmd([lms('name')],[ifield(w,'name')])) edit(edit_target({type:'ob_destroy',props})),ob.sel=[] } can_coalesce=move=>{ if(has_redo()||doc_hist.length==0)return false const prev=doc_hist[doc_hist_cursor-1],c=con();if(prev.type!='ob_props')return false if('def' in prev&&(!prototype_is(c)||ls(prev.def)!=ls(ifield(c,'name' ))))return false if('card' in prev&&(!card_is (c)||prev.card !=ln(ifield(c,'index'))))return false if(Object.keys(prev.after).length!=ob.sel.length)return false for(let z=0;z{ if(ob.sel.length<1)return if(coalesce&&can_coalesce(1)){ const r=doc_hist[doc_hist_cursor-1]; Object.values(r.after).map(z=>z.pos=lmpair(radd(getpair(z.pos),delta))),undo(),redo() }else{ const before={},after={};ob.sel.map(w=>{ const n=ls(ifield(w,'name')),f=ifield(w,'pos'),fv=getpair(f) before[n]={pos:f},after[n]={pos:lmpair(radd(fv,delta))} }),edit(edit_target({type:'ob_props',before,after})) } } ob_resize=(size,coalesce)=>{ if(ob.sel.length!=1)return;const w=ob.sel[0] if(coalesce&&can_coalesce(0)){ const r=doc_hist[doc_hist_cursor-1],after=Object.values(r.after)[0] after.pos =lmpair(rect(size.x,size.y)) after.size=lmpair(rect(size.w,size.h)),undo(),redo() }else{ const bp={pos:ifield(w,'pos') ,size:ifield(w,'size') } const ap={pos:lmpair(rect(size.x,size.y)),size:lmpair(rect(size.w,size.h))} const before={},after={},n=ls(ifield(w,'name'));before[n]=bp,after[n]=ap edit(edit_target({type:'ob_props',before,after})) } } proto_prop=(target,key,value)=>{ edit(edit_target({type:'proto_props',keys:[lms(key)],before:[ifield(target,key)],after:[value]})) } proto_size=(target,size,margin)=>{ const o=image_resize(image_copy(ifield(target,'image')),getpair(ifield(target,'size'))), n=image_make(size), c=rpair(rect(0,0),size) draw_9seg(c,n,o,getrect(ifield(target,'margin')),c,1,null) edit(edit_target({ type:'proto_props',keys:['size','image','margin'].map(lms), before:[ifield(target,'size'),o,ifield(target,'margin')],after:[lmpair(size),n,lmrect(margin)] })) } object_select=x=>{ob.sel=[x]} object_properties=x=>{ ob.sel=[x] if(button_is (x))modal_enter('button_props') if(field_is (x))modal_enter('field_props' ) if(slider_is (x))modal_enter('slider_props') if(canvas_is (x))modal_enter('canvas_props') if(grid_is (x))modal_enter('grid_props' ) if(contraption_is(x))modal_enter('contraption_props') } is_resizable=_=>ob.sel.length==1&&(contraption_is(ob.sel[0])?lb(ifield(ob.sel[0].def,'resizable')):1) object_editor=_=>{ const wids=con_wids(), pal=deck.patterns.pal.pix wids.v.map(wid=>{ const w=unpack_widget(wid), sel=ob.sel.some(x=>x==wid);w.size=con_to_screen(w.size) if(sel){draw_box(inset(w.size,-1),0,ANTS)}else if(ob.show_bounds){draw_boxinv(pal,inset(w.size,-1))} if(sel&&is_resizable())draw_handles(w.size) if(ob.show_bounds){ if(lb(ifield(wid,'volatile'))){ draw_line(rect(w.size.x,w.size.y,w.size.x+w.size.w,w.size.y+w.size.h),0,1,deck) draw_line(rect(w.size.x+w.size.w,w.size.y,w.size.x,w.size.y+w.size.h),0,1,deck) } const badge=rect(w.size.x+w.size.w-10,w.size.y,10,10) if(w.locked )draw_rect(badge,1),draw_icon(rect(badge.x+1,badge.y+1),LOCK,32),badge.y+=10 if(lb(ifield(wid,"animated")))draw_rect(badge,1),draw_icon(rect(badge.x+1,badge.y+1),ANIM,32) } }) if(!in_layer())return if(ob.sel.length>0){ let nudge=0 if(ev.dir=='left' )ob_move(rect(-1*(ev.shift?dr.grid_size.x:1), 0),1),nudge=1 if(ev.dir=='right')ob_move(rect( 1*(ev.shift?dr.grid_size.x:1), 0),1),nudge=1 if(ev.dir=='up' )ob_move(rect( 0,-1*(ev.shift?dr.grid_size.y:1)),1),nudge=1 if(ev.dir=='down' )ob_move(rect( 0, 1*(ev.shift?dr.grid_size.y:1)),1),nudge=1 if(nudge&&ev.shift&&ob.sel.length){ const p=ob.sel.reduce((p,w)=>rmin(p,getpair(ifield(w,'pos'))),rect(RTEXT_END,RTEXT_END)) ob_move(snap_delta(p),1) } } const ish=is_resizable()?in_handle(unpack_widget(ob.sel[0]).size):-1 const isw=wids.v.some(w=>over(unpack_widget(w).size)&&ob.sel.some(x=>x==w)) const a=ev.pos,b=ob.prev,dragged=a.x!=b.x||a.y!=b.y const sr=rnorm(rect(ev.dpos.x,ev.dpos.y,ev.pos.x-ev.dpos.x,ev.pos.y-ev.dpos.y)), box=sr.w>1||sr.h>1; if(isw&&ev.dclick){ for(let z=count(wids)-1;z>=0;z--){ const wid=wids.v[z], w=unpack_widget(wid) if(over(w.size)){object_properties(wid);break} } } else if(isw&&ev.mu&&!box){ for(let z=ob.sel.length-1;z>=0;z--){ const wid=ob.sel[z], w=unpack_widget(wid) if(over(w.size)){if(ev.shift){ob.sel=ob.sel.filter(x=>x!=wid)}else{ob.sel=[wid]};break} } } else if(ish!=-1&&ev.md){ob.resize=1,ob.resize_first=1,ob.handle=ish,ob.prev=rcopy(ev.pos),ob.orig=unpack_widget(ob.sel[0]).size} else if(isw &&ev.md){ob.move =1,ob.move_first =1,ob.prev=rcopy(ev.pos)} else if(ob.resize&&(!ev.drag||!ob.sel.length)){ob.resize=0,ob.resize_first=0} else if(ob.move&&(!ev.drag||!ob.sel.length)){ if(ob.sel.length){ const p=ob.sel.reduce((p,w)=>rmin(p,getpair(ifield(w,'pos'))),rect(RTEXT_END,RTEXT_END)) ob_move(snap_delta(p),!ob.move_first) } ob.move=0,ob.move_first=0 } else if(ob.resize){ let delta=rsub(ev.pos,ev.dpos), r=rcopy(ob.orig) if(ob.handle==0){r.w+=delta.x, r.h+=delta.y} if(ob.handle==2){r.w+=delta.x, r.h-=delta.y, r.y+=delta.y} if(ob.handle==6){r.w-=delta.x, r.h+=delta.y, r.x+=delta.x} if(ob.handle==4){r.w-=delta.x, r.h-=delta.y, r.x+=delta.x, r.y+=delta.y} if(ob.handle==1){r.w+=delta.x} if(ob.handle==3){r.h-=delta.y, r.y+=delta.y} if(ob.handle==5){r.w-=delta.x, r.x+=delta.x} if(ob.handle==7){r.h+=delta.y} r=snapr(r), r.w=max(8,r.w), r.h=max(8,r.h) if(dragged)ob_resize(r,!ob.resize_first),ob.resize_first=0,ob.prev=rcopy(ev.pos) } else if(ob.move){ const delta=rsub(a,b) if(dragged)ob_move(delta,!ob.move_first),ob.move_first=0,ob.prev=rcopy(ev.pos) }else{ if(box&&ev.drag)draw_box(con_to_screen(sr),0,ANTS); if(box&&(ev.drag||ev.mu))ob.sel=[] let f=0;for(let z=count(wids)-1;z>=0;z--){ // backward pass for selection priority(!) const wid=wids.v[z], w=unpack_widget(wid), sel=ob.sel.some(x=>x==wid), overlap=rclip(w.size,sr) const insel=box?overlap.w>=1&&overlap.h>=1: over(w.size)&&dover(w.size), c=ev.mu&insel if(c)f=1; if(!c)continue; if(box){ob.sel.push(wid);continue} if(!sel){if(!ev.shift)ob.sel=[];ob.sel.push(wid);break} }if(ev.mu&&!f)ob.sel=[] } } prototype_size_editor=_=>{ const def=con(),drag=rect(0,0,0,0);if(uimode=='interact'||!prototype_is(def))return 0;let r=0 const delta=rsub(screen_to_con(ev.pos),screen_to_con(ev.dpos)),view=con_clip() let resize=0, s=getpair(ifield(def,'size')), m=getrect(ifield(def,'margin')),om=rcopy(m) if(view.x!=0&&view.y!=0&&view.w!=frame.size.x&&view.h!=frame.size.y&&uimode!='draw'){ // resize handle let sh=rpair(radd(con_to_screen(s),rect(1,1)),rect(8,8));if(over(sh))r=1; if((ev.drag||ev.mu)&&dover(sh)&&(delta.x!=0||delta.y!=0)){ s=snap(rmax(rect(1,1),radd(s,delta))),sh=rpair(radd(con_to_screen(s),rect(1,1)),rect(8,8)) draw_box(con_to_screen(rpair(rect(0,0),s)),0,ANTS),r=1;if(ev.mu)resize=1 } draw_rect(sh,32),draw_box(sh,0,1);if(over(sh))uicursor=cursor.drag draw_text(rect(sh.x+10,sh.y+10,100,20),`${s.x} x ${s.y}`,FONT_MONO,1) // margin handles const h_margin=(i,v,o) =>rpair(rsub(con_to_screen(v),o),i.size) const m_margin=(i,v,o,a,d)=>{const h=h_margin(i,v,o);if(over(h)||dover(h))r=1;if((ev.drag||ev.mu)&&dover(h))m[d]+=a,drag[d]=1;} const i_margin=(h,i) =>{image_paste(h,frame.clip,i,frame.image,0);if(over(h))uicursor=cursor.drag;} const l_label =(h,v) =>{const l=`${v}`,t=font_textsize(FONT_MONO,l);draw_text(rect(h.x-2-t.x ,h.y-3 ,60,20),l,FONT_MONO,1)} const t_label =(h,v) =>{const l=`${v}`,t=font_textsize(FONT_MONO,l);draw_text(rect(h.x+3-(0|(t.x/2)),h.y-t.y,60,20),l,FONT_MONO,1)} const l_margin=(i,v,o,d) =>{const h=h_margin(i,v,o);if(drag[d])l_label(h,m[d]);i_margin(h,i)} const t_margin=(i,v,o,d) =>{const h=h_margin(i,v,o);if(drag[d])t_label(h,m[d]);i_margin(h,i)} if(ob.show_margins){ m_margin(HANDLES[0],rect(m.x ,0 ),rect(3,11), delta.x,'x') m_margin(HANDLES[1],rect(0 ,m.y ),rect(11,3), delta.y,'y') m_margin(HANDLES[0],rect(s.x-m.w,0 ),rect(3,11),-delta.x,'w');if(drag.x&&drag.w)m.w+=delta.x,drag.w=0 m_margin(HANDLES[1],rect(0 ,s.y-m.h),rect(11,3),-delta.y,'h');if(drag.y&&drag.h)m.h+=delta.y,drag.h=0 m=normalize_margin(lmrect(m),getpair(ifield(def,'size'))) t_margin(HANDLES[0],rect(m.x ,0 ),rect(3,11),'x') l_margin(HANDLES[1],rect(0 ,m.y ),rect(11,3),'y') t_margin(HANDLES[0],rect(s.x-m.w,0 ),rect(3,11),'w') l_margin(HANDLES[1],rect(0 ,s.y-m.h),rect(11,3),'h') } } if(ob.show_margins){ const m0=con_to_screen(rect(m.x,0,1,s.y ));draw_vline(m0.x,m0.y,m0.y+m0.h,ANTS) const m1=con_to_screen(rect(0,m.y,s.x,1 ));draw_hline(m1.x,m1.x+m1.w,m1.y,ANTS) const m2=con_to_screen(rect(s.x-m.w,0,1,s.y));draw_vline(m2.x,m2.y,m2.y+m2.h,ANTS) const m3=con_to_screen(rect(0,s.y-m.h,s.x,1));draw_hline(m3.x,m3.x+m3.w,m3.y,ANTS) } const move_margin=m.x!=om.x||m.y!=om.y||m.w!=om.w||m.h!=om.h if(resize){proto_size(def,s,m)} else if(ev.mu&&move_margin){proto_prop(def,'margin',lmrect(m))} return r||resize||move_margin } // Toolbars let toolbars_enable=0, tzoom=1 // should be off by default? const tcellw=22, tcellh=19, tgap=1, toolsize=rect(tcellw*2+1,tcellh*18+tgap+1), tfb=image_make(toolsize), tid=new ImageData(toolsize.x,toolsize.y) const tooltypes=['select','pencil','lasso','line','fill','poly','rect','fillrect','ellipse','fillellipse'] const patorder=[0,1,4,5,8,9,16,17,12,13,18,19,20,21,22,23,24,25,26,27,2,6,3,7,10,11,14,15,28,29,30,31] // pleasing visual ramps for 2 columns toolbars=_=>{ if(!toolbars_enable)return const enable=uimode!='script'&&!deck.locked const enable_left=enable&&!ms.type&&!kc.on const enable_right=enable&&((!ms.type)||(wid.fv!=null&&wid.f.style=='rich')) const toolbar=(enable,element,render,behavior)=>{ if(!enable){element.getContext('2d').clearRect(0,0,element.width,element.height);return} const c=element.getBoundingClientRect() const pos =rint(rect((ev.rawpos .x-c.x)/tzoom,(ev.rawpos .y-c.y)/tzoom)) const dpos=rint(rect((ev.rawdpos.x-c.x)/tzoom,(ev.rawdpos.y-c.y)/tzoom)) tfb.pix.fill(0),frame=draw_frame(tfb),draw_box(rpair(rect(),toolsize),0,1),behavior(pos,dpos) const anim=deck.patterns.anim, pal=deck.patterns.pal.pix const animated=rin(rect(c.x,c.y,c.width,c.height),ev.rawpos)&&dr.show_anim?(0|(frame_count/4)):0 const anim_pattern=(pix,x,y)=>pix<28||pix>31?pix: anim[pix-28][animated%max(1,anim[pix-28].length)] const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal_pat(pal,pix,x,y)&1 const draw_color =(pix,x,y)=>pix>=32+PAL_COLORS?0: pix>31?pix-32: draw_pattern(pix,x,y)?15:0 const data=tid.data;for(let z=0,d=0,y=0;y>16),data[d++]=0xFF&(cv>>8),data[d++]=0xFF&(cv),data[d++]=0xFF } render.getContext('2d').putImageData(tid,0,0) const g=element.getContext('2d');g.imageSmoothingEnabled=tzoom!=(0|tzoom),g.save(),g.scale(tzoom,tzoom),g.drawImage(render,0,0),g.restore() } const toolbtn=(pos,dn,b,icon,active)=>{ const i=rcenter(b,rect(16,16)) draw_box(b,0,1);if(active)draw_rect(b,1);draw_icon(i,TOOLS[icon],active?0:1) if(rin(b,pos))uicursor=cursor.point;return rin(b,pos)&&rin(b,dn)&&ev.mu } const modebtn=(pos,dn,b,text,active)=>{ draw_box(b,0,1);if(active)draw_rect(inset(b,2),1);draw_textc(b,text,FONT_BODY,active?0:1) if(rin(b,pos))uicursor=cursor.point;return rin(b,pos)&&rin(b,dn)&&ev.mu } const scrollbtn=(pos,dn,b,icon)=>{ const i=rcenter(b,rect(12,12)),active=rin(b,dn)&&(ev.mu||ev.drag) draw_box(b,0,1);if(active)draw_rect(b,1);draw_icon(i,ARROWS[icon],active?0:1) if(rin(b,pos))uicursor=cursor.point;return rin(b,pos)&&active&&ev.mu } const brushbtn=(pos,dn,b,brush)=>{ const i=rint(rect(b.x+(b.w/2),b.y+(b.h/2))) draw_box(b,0,1),draw_line(rect(i.x,i.y,i.x,i.y),brush,1,deck) if(dr.brush==brush)draw_box(inset(b,2),0,1) if(!rin(b,pos))return;uicursor=cursor.point;if(!ev.mu||!rin(b,dn))return setmode('draw');if(dr.tool=='select'||dr.tool=='lasso'||dr.tool=='fill')settool('pencil');dr.brush=brush } const cbrushbtn=(pos,dn,b,brush,bt)=>{ const icon=bt.v[brush-24],oc=frame.clip;frame.clip=b image_paste(rcenter(b,icon.size),b,icon,frame.image,1),frame.clip=oc,draw_box(b,0,1) if(dr.brush==brush)draw_box(inset(b,2),0,1) if(!rin(b,pos))return;uicursor=cursor.point;if(!ev.mu||!rin(b,dn))return setmode('draw');if(dr.tool=='select'||dr.tool=='lasso'||dr.tool=='fill')settool('pencil');dr.brush=brush } const palbtn=(pos,dn,b,pattern)=>{ if(wid.fv){ draw_rect(b,pattern) const canpat=(wid.cursor.x!=wid.cursor.y)&&(wid.f.style=='rich') if(rin(b,pos)){if(canpat)uicursor=cursor.point;if(ev.md&&rin(b,dn)){if(canpat)field_patspan(pattern);ev.md=0}} draw_box(b,0,1);return } if(uimode=='object'){ draw_rect(b,pattern) if(ob.sel&&ob.sel.length&&rin(b,pos)){uicursor=cursor.point;if(ev.mu&&rin(b,dn)){ob_edit_prop('pattern',lmn(pattern)),ev.mu=0}} draw_box(b,0,1);return } if((dr.pickfill?dr.fill:dr.pattern)==pattern){draw_rect(inset(b,3),pattern),draw_box(inset(b,3),0,1)}else{draw_rect(b,pattern)} if(rin(b,pos)){uicursor=cursor.point;if(ev.mu&&rin(b,dn)){if(dr.pickfill){dr.fill=pattern}else{dr.pattern=pattern}}}draw_box(b,0,1) } toolbar(enable_left,q('#ltools'),q('#lrender'),(pos,dn)=>{ const bs=deck.brushes,bt=deck.brusht,th=count(bs)?17:tcellh;toolbar_scroll=clamp(0,toolbar_scroll,count(bs)) draw_rect(rect(0,6*tcellh,toolsize.x,tgap),1) if(toolbtn(pos,dn,rect(0 ,0,tcellw+1,tcellh+1),0,uimode=='interact'))setmode('interact'),ev.mu=ev.md=0 if(toolbtn(pos,dn,rect(tcellw,0,tcellw+1,tcellh+1),1,uimode=='object' ))setmode('object' ),ev.mu=ev.md=0 for(let z=0;z<10;z++){ if(toolbtn(pos,dn,rect((z%2)*tcellw,(1+(0|(z/2)))*tcellh,tcellw+1,tcellh+1),z+2,uimode=='draw'&&dr.tool==tooltypes[z])){ settool(tooltypes[z]),ev.mu=ev.md=0 } } let cy=(6*tcellh)+tgap,brow=0;for(let z=0;z<12-toolbar_scroll;z++){ brushbtn(pos,dn,rect(0 ,cy,tcellw+1,th+1),z +toolbar_scroll) brushbtn(pos,dn,rect(tcellw,cy,tcellw+1,th+1),z+12+toolbar_scroll) cy+=th,brow++ } if(count(bs)){ for(let bi=0;brow<12;bi++,cy+=th,brow++)cbrushbtn(pos,dn,rect(0,cy,toolsize.x,th+1),24+bi+max(toolbar_scroll-12,0),bt) draw_rect(rect(0,cy+1,toolsize.x,tgap),1),cy+=tgap if(toolbar_scroll>0 )if(scrollbtn(pos,dn,rect(0 ,cy,tcellw+1,toolsize.y-cy),0))toolbar_scroll-- if(toolbar_scroll{ draw_rect(rect(0,16*tcellh,toolsize.x,tgap),1) if(wid.fv){ const b=rect(1,1,tcellw*2-1,tcellh*2-1) draw_rect(b,1),draw_box(b,0,13);if(rin(b,pos)&&rin(b,dn)&&ev.md)ev.md=0 }else{ if(modebtn(pos,dn,rect(0,0 ,tcellw*2+1,tcellh+1),'Stroke',dr.pickfill==0))dr.pickfill=0 if(modebtn(pos,dn,rect(0,tcellh,tcellw*2+1,tcellh+1),'Fill' ,dr.pickfill==1))dr.pickfill=1 } if(!dr.color){for(let z=0;z<4*8;z++)palbtn(pos,dn,rect((z%2)*tcellw,(2*tcellh)+(0|(z/2))*tcellh+(z>=28?tgap:0),tcellw+1,tcellh+1),patorder[z])} else{ if(PAL_COLORS<=16){for(let z=0;z<16 ;z++)palbtn(pos,dn,rect(0,(2*tcellh)+z*tcellh,2*tcellw+1,tcellh+1),(z>=2?31:0)+z)} else {for(let z=0;z=2?31:0)+(z>=16?1:0)+z)} } }) } // Script Editor setscript=x=>{ if(uimode!='script')sc.prev_mode=uimode;setmode('script') sc.status='', sc.target=lii(x)?x:x[0], sc.others=lii(x)?[]: x.slice(1) let v=ifield(sc.target,'script'), p=0 if(!count(v))v=card_is (sc.target)?(p=1,lms('on view do\n \nend')): button_is(sc.target)?(p=1,lms('on click do\n \nend')): grid_is (sc.target)?(p=1,lms('on click row do\n \nend')): field_is (sc.target)?(p=1,lms('on change val do\n \nend')): slider_is(sc.target)?(p=1,lms('on change val do\n \nend')): canvas_is(sc.target)?(p=1,lms('on click pos do\n \nend\n\non drag pos do\n \nend\n\non release pos do\n \nend')):v if(p&&widget_is(sc.target)&&!contraption_is(sc.target)&&lb(ifield(sc.target,'animated'))){v=lms(ls(v)+'\n\non view do\n \nend')} if(!count(v)&&contraption_is(sc.target)){const t=sc.target.def.template;if(t&&t.length)p=1,v=lms(t)} sc.status=p?'No existing script; populated a template.':'',sc.f=fieldstr(v),wid.active=0 } finish_script=_=>{if(sc.next){setscript(sc.next),sc.next=null}else{setmode(sc.prev_mode)}} close_script=next=>{ sc.next=next,field_exit() try{const text=ls(rtext_string(sc.f.table));parse(text),script_save(lms(text)),finish_script()} catch(e){ modal_enter('confirm_script') ms.message=lms(`The current script contains errors:\n\n${e.x}\n\nDo you wish to discard your changes?`),ms.verb=lms('Discard') } } script_editor=r=>{ const field_position=(x,cursor)=>{ if(x.layout.length<1)return rect(1,1);cursor=max(0,min(cursor,x.layout.length+1)) const e=cursor>=x.layout.length?1:0, i=cursor-e, l=x.layout[i].line, c=i-x.lines[l].range.x return rect(l+1,c+1+e) } const mh=3+font_h(FONT_MENU) let overw=null;if(sc.xray){ const wids=con_wids();let ow=0;for(let z=wids.v.length-1;z>=0;z--){ const wid=wids.v[z],size=con_to_screen(unpack_widget(wid).size), o=ev.alt&&over(size)&&!ow, col=o?(overw=wid,13):44; ow|=o draw_textc(size,ls(ifield(wid,'name')),FONT_BODY,o?-1:col),draw_box(size,0,col) if(count(ifield(wid,'script')))draw_icon(rect(size.x-1,size.y),ICONS[ICON.lil],o?1:col) if(ev.alt&&ev.mu&&over(size)&&dover(size)){close_script(wid),ev.md=ev.mu=0;break} }if(ev.alt&&ev.mu)close_script(con()),ev.md=ev.mu=0 } ui_codeedit(rect(r.x,r.y,r.w,r.h-mh),0,sc.f),draw_hline(r.x,r.x+r.w,r.y+r.h-mh-1,1) if(overw){uicursor=cursor.point;draw_textc(con_to_screen(unpack_widget(overw).size),ls(ifield(overw,'name')),FONT_BODY,-1)} if(sc.status.length){draw_text_fit(rect(r.x+3,r.y+r.h-mh+3,r.w,mh-6),sc.status,FONT_BODY,1);} else{ let stat='';if(in_layer()&&wid.infield){ const x=layout_richtext(deck,sc.f.table,FONT_MONO,ALIGN.left,r.w) let a=min(wid.cursor.x,wid.cursor.y), b=max(wid.cursor.x,wid.cursor.y) if(a!=b&&a{ const r=lmd();if(uimode!='interact')return r con_wids().v.map(wid=>{ if(lb(ifield(wid,'animated'))&&!dget(viewed,wid))dset(r,wid,ZERO) if(contraption_is(wid))ivalue(wid,'widgets').v.map(cwid=>{if(lb(ifield(cwid,'animated'))&&!dget(viewed,cwid))dset(r,cwid,ONE)}) });return r } fire_animate=targets=>{ const root=lmenv(),block=lmblk();primitives(root,deck),constants(root) targets.k.map(w=>{blk_cat(block,event_invoke(w,'view',lml([NIL]),null)),dset(viewed,w,ONE)}) pushstate(root),pending_popstate=1,issue(root,block) } fire_view=target=>{ const root=lmenv();primitives(root,deck),constants(root) const block=event_invoke(target,'view',lml([NIL]),null) ifield(target,'widgets').v.filter(w=>contraption_is(w)&&!dget(viewed,w)).map(w=>{blk_cat(block,event_invoke(w.viewproxy,'view',lml([NIL]),null)),dset(viewed,w,ONE)}) pushstate(root),pending_popstate=1,issue(root,block) } interpret=_=>{ viewed=lmd() if(msg.overshoot&&!running()&&!msg.pending_view&&!msg.next_view)msg.overshoot=0 if(msg.pending_halt){if(running())halt();sleep_frames=0,sleep_play=0,msg.pending_view=0,msg.next_view=0} if(sleep_play&&sfx_any())return 0;sleep_play=0 if(sleep_frames){sleep_frames--;return 0} const nomodal=_=>(ms.type==null||ms.type=='query'||ms.type=='listen') let quota=FRAME_QUOTA;while(1){ while(nomodal()&&running()&&sleep_frames==0&&sleep_play==0&"a>0){runop(),quota--,mark_dirty()}frame=context if(quota<=0&&running())msg.overshoot=1 if(!nomodal()||quota<=0||sleep_frames||sleep_play){if(sleep_frames)sleep_frames--;break} if(!running()&&pending_popstate)popstate(),pending_popstate=0 const a=find_animated() if(msg.pending_halt||pending_popstate){/*suppress other new events until this one finishes*/} else if(msg.pending_view){fire_view(con()),msg.pending_view=0} else if(msg.target_click){ const arg=grid_is(msg.target_click)?lmn(msg.arg_click.y): canvas_is(msg.target_click)?lmpair(msg.arg_click): NIL fire_event_async(msg.target_click,'click',arg),msg.target_click=null } else if(msg.target_drag ){fire_event_async(msg.target_drag ,'drag' ,lmpair(msg.arg_drag )),msg.target_drag =null} else if(msg.target_release ){fire_event_async(msg.target_release ,'release' ,lmpair(msg.arg_release)),msg.target_release =null} else if(msg.target_run ){fire_event_async(msg.target_run ,'run' ,msg.arg_run ),msg.target_run =null} else if(msg.target_link ){fire_event_async(msg.target_link ,'link' ,msg.arg_link ),msg.target_link =null} else if(msg.target_order ){fire_event_async(msg.target_order ,'order' ,msg.arg_order ),msg.target_order =null} else if(msg.target_ccell ){fire_event_async(msg.target_ccell ,'changecell',msg.arg_ccell ),msg.target_ccell =null} else if(msg.target_change ){fire_event_async(msg.target_change ,'change' ,msg.arg_change ),msg.target_change =null} else if(msg.target_navigate){fire_event_async(msg.target_navigate,'navigate' ,msg.arg_navigate ),msg.target_navigate=null} else if(count(a) ){fire_animate(a)} if(!running())break // not running, and no remaining events to process, so we're done for this frame }if(msg.next_view&&ms.type!='listen')msg.pending_view=1,msg.next_view=0 // no more than one view[] event per frame! return FRAME_QUOTA-quota } text_edit_menu=_=>{ const selection=wid.fv!=null&&wid.cursor.x!=wid.cursor.y const rich=wid.fv!=null&&wid.f.style=='rich' if(menu_item('Undo',wid.hist_cursor>0 ,'z'))field_undo() if(menu_item('Redo',wid.hist_cursor{ const blocked=running()||msg.overshoot const canlisten=!blocked&&(ms.type=='listen'||ms.type==null) menu_bar('Decker',canlisten&&!kc.on) if(menu_item('About...',1))modal_enter('about') if(menu_check('Listener',canlisten,ms.type=='listen','l')){if(ms.type!='listen'){modal_enter('listen')}else{modal_exit(0)}} menu_separator() menu_check('Fullscreen',1,is_fullscreen(),null,toggle_fullscreen) if(menu_check('Touch Input',1,enable_touch)){enable_touch^=1,set_touch=1;if(!enable_touch)kc.on=0} if(menu_check('Script Profiler',1,profiler))profiler^=1 if(menu_check('Toolbars',tzoom>0,toolbars_enable))toolbars_enable^=1,resize() if(blocked){ menu_bar('Script',1) if(menu_item('Stop',1)){msg.pending_halt=1;if(ms.type!='query'&&ms.type!='listen'){if(ms.type!=null){modal_exit(0)}setmode('object')}} menu_bar('Edit',(ms.type=='input'||ms.type=='save')&&wid.fv) text_edit_menu() return } menu_bar('File',(ms.type==null||ms.type=='sounds'||ms.type=='recording')&&(!kc.on||uimode=='script')) if(uimode=='script'){ if(menu_item('Close Script',1))close_script() menu_separator() if(menu_item('Go to Deck',!deck_is(sc.target) ))close_script(deck) const container=con() if(menu_item(`Go to ${prototype_is(container)?'Prototype':'Card'}`,sc.target!=container))close_script(container) if(menu_check('X-Ray Specs',!kc.on,sc.xray))sc.xray^=1 } else if(ms.type=='sounds'){ menu_item('Import Sound...',1,0,_=>open_file('audio/*',load_sound)) } else if(ms.type=='recording'){ if(menu_item('Close Sound',1))modal_exit(0) } else{ if(menu_item('New Deck...',1)){ if(dirty){ modal_enter('confirm_new') ms.message=lms('The current deck has unsaved changes.\nAre you sure you want to discard it?') ms.verb=lms('Discard') }else{load_deck(deck_read(''))} } if(menu_item('New Card',1)){const c=deck_add(deck,lms('card')), n=ln(ifield(ifield(deck,'card'),'index'));iwrite(c,lms('index'),lmn(n+1)),n_go([c],deck)} menu_separator() menu_item('Open...',1,0,_=>open_text('.html,.deck',text=>{load_deck(deck_read(text))})) if(menu_item('Save As...',1))modal_enter('save_deck') menu_separator() menu_item("Import Image...",1,0,_=>{open_file('image/*',load_image)}) if(menu_item("Export Image...",1))modal_enter('export_image') menu_separator() if(menu_item('Purge Volatiles',1)){deck_purge(deck),msg.next_view=1} menu_separator() if(menu_item('Cards...' ,1,'C'))modal_enter('cards') if(menu_item('Sounds...' ,1,'S'))modal_enter('sounds') if(menu_item('Prototypes...',1,'T'))modal_enter('contraptions') if(menu_item('Resources...' ,1, ))modal_enter('resources') if(menu_item('Properties...',1, ))modal_enter('deck_props') } if(ms.type=='prototype_attrs'){ menu_bar('Edit',1) if(menu_item('Move Attribute Up',ms.grid.row>0)){ ms.grid.table=table_swap_rows(ms.grid.table,ms.grid.row-1,ms.grid.row),ms.grid.row-- } if(menu_item('Move Attribute Down',ms.grid.row>=0&&ms.grid.row0 ,'z'))grid_undo() if(menu_item('Redo',wid.hist_cursor-2.0))dr.dither_threshold-=.1 if(menu_item('Darken Image',dr.dither_threshold< 2.0))dr.dither_threshold+=.1 } } else if(ms.type==null&&uimode=='object'){ if(menu_item('Undo',has_undo(),'z'))undo() if(menu_item('Redo',has_redo(),'Z'))redo() menu_separator() if(menu_item('Cut Widgets',ob.sel.length,'x',menucut)){} if(menu_item('Copy Widgets',ob.sel.length,'c',menucopy)){} if(menu_item('Copy Image',ob.sel.length==1,0,copywidgetimg)){} if(menu_item('Paste',1,'v',menupaste)){} if(menu_item('Paste as new Canvas',1,0,pasteascanvas)){} if(menu_item('Paste into Canvas',ob.sel.length==1&&canvas_is(ob.sel[0]),0,pasteintocanvas)){} menu_separator() if(menu_item('Select All',1,'a'))ob.sel=con_wids().v.slice(0) if(menu_item('Move to Front',ob.sel.length))ob_order(),ob.sel .map(w=>iwrite(w,lms('index'),lmn(RTEXT_END))),mark_dirty() if(menu_item('Move Up' ,ob.sel.length))ob_move_up() if(menu_item('Move Down' ,ob.sel.length))ob_move_dn() if(menu_item('Move to Back' ,ob.sel.length))ob_order(),ob.sel.slice(0).reverse().map(w=>iwrite(w,lms('index'),ZERO )),mark_dirty() } if(wid.fv&&wid.f){ const selection=wid.fv!=null&&wid.cursor.x!=wid.cursor.y menu_bar('Text',selection&&wid.f.style!='plain') if(wid.f.style=='rich'){ if(menu_item('Heading' ,selection))field_fontspan(lms('menu')) if(menu_item('Body' ,selection))field_fontspan(lms('' )) if(menu_item('Fixed Width',selection))field_fontspan(lms('mono')) if(menu_item('Link...' ,selection))modal_push('link') } else if(wid.f.style=='code'){ if(menu_item('Indent' ,1 ))field_indent(1) if(menu_item('Unindent' ,1 ))field_indent(0) if(menu_item('Toggle Comment',1,'/'))field_comment() } if(wid.f&&wid.f.style!='code'){ if(menu_item('Font...',wid.f.style!='plain'))modal_push('fonts') if(menu_item("Pattern...",wid.f&&wid.f.style!='plain')){ ob.pending_pattern=ln(tab_get(rtext_span(wid.fv.table,wid.cursor),'pat')[0]) modal_push('spanpattern') } } } } else if(ms.type=='recording'&&!wid.fv){ menu_bar('Edit',au.mode=='stopped') if(menu_item('Undo',au.hist_cursor>0 ,'z'))sound_undo() if(menu_item('Redo',au.hist_cursor1))n_go([lms('Back')],deck) menu_separator() if(menu_item('Cut Card',1,0,cutcard)){} if(menu_item('Copy Card',1,0,copycard)){} menu_separator() if(menu_item('Script...' ,1))setscript(con()) if(menu_item('Properties...',1))modal_enter('card_props') } if((uimode=='interact'||uimode=='draw'||uimode=='object')&&prototype_is(con())){ menu_bar('Prototype',ms.type==null&&!kc.on) const def=con(), defs=deck.contraptions if(menu_item('Close',1))con_set(null) if(menu_item('Go to Previous',count(defs)>1)){ev.dir='left' ,tracking(),ev.dir=0} if(menu_item('Go to Next' ,count(defs)>1)){ev.dir='right',tracking(),ev.dir=0} menu_separator() if(menu_item('Script...' ,1))setscript(con()) if(menu_item('Properties...',1))modal_enter('prototype_props') if(menu_item('Attributes...',1))modal_enter('prototype_attrs') if(menu_check('Show Margins',1,ob.show_margins))ob.show_margins^=1 menu_separator() let r=lb(ifield(def,'resizable')) if(menu_check('Resizable',1,r,0)){r^=1,iwrite(def,lms('resizable'),lmn(r)),mark_dirty()} } if(uimode=='interact'||uimode=='draw'||uimode=='object'){ menu_bar('Tool',ms.type==null&&!kc.on) if(menu_check('Interact',1,uimode=='interact',0))setmode('interact') if(menu_check('Widgets' ,1,uimode=='object' ,0))setmode('object') menu_separator() if(menu_check('Select' ,1,uimode=='draw'&&dr.tool=='select' ))settool('select' ) if(menu_check('Lasso' ,1,uimode=='draw'&&dr.tool=='lasso' ))settool('lasso' ) if(menu_check('Pencil' ,1,uimode=='draw'&&dr.tool=='pencil' ))settool('pencil' ) if(menu_check('Line' ,1,uimode=='draw'&&dr.tool=='line' ))settool('line' ) if(menu_check('Flood' ,1,uimode=='draw'&&dr.tool=='fill' ))settool('fill' ) if(menu_check('Box' ,1,uimode=='draw'&&dr.tool=='rect' ))settool('rect' ) if(menu_check('Filled Box' ,1,uimode=='draw'&&dr.tool=='fillrect' ))settool('fillrect' ) if(menu_check('Oval' ,1,uimode=='draw'&&dr.tool=='ellipse' ))settool('ellipse' ) if(menu_check('Filled Oval',1,uimode=='draw'&&dr.tool=='fillellipse'))settool('fillellipse') if(menu_check('Polygon' ,1,uimode=='draw'&&dr.tool=='poly' ))settool('poly' ) } if(uimode=='draw'||uimode=='object'){ menu_bar('View',ms.type==null&&!kc.on) if(menu_check('Show Widgets' ,1,dr.show_widgets))dr.show_widgets^=1 if(menu_check('Show Widget Bounds' ,1,ob.show_bounds ))ob.show_bounds ^=1 if(menu_check('Show Widget Names' ,1,ob.show_names ))ob.show_names ^=1 if(menu_check('Show Cursor Info' ,1,ob.show_cursor ))ob.show_cursor ^=1 if(menu_check('Show Alignment Guides',1,ob.show_guides ))ob.show_guides ^=1 if(menu_check('Show Animation' ,1,dr.show_anim ))dr.show_anim ^=1 menu_separator() if(menu_check('Show Grid Overlay',1,dr.show_grid))dr.show_grid^=1 if(menu_check('Snap to Grid' ,1,dr.snap ))dr.snap ^=1 if(menu_item('Grid and Scale...',1))modal_enter('grid') menu_separator() if(menu_check('Transparency Mask',1,dr.trans_mask))dr.trans_mask^=1 if(menu_check('Fat Bits' ,1,dr.fatbits )){ if(ms.type==null&&uimode!='draw')setmode('draw') dr.fatbits^=1;if(dr.fatbits)center_fatbits(rcenter(bg_has_sel()||bg_has_lasso()?dr.sel_here:con_dim(),rect())) } } if(uimode=='object'){ menu_bar('Widgets',ms.type==null) if(menu_item('New Button',1))ob_create([lmd([lms('type')],[lms('button')])]) if(menu_item('New Field' ,1))ob_create([lmd([lms('type')],[lms('field' )])]) if(menu_item('New Slider',1))ob_create([lmd([lms('type')],[lms('slider')])]) if(menu_item('New Canvas',1))ob_create([lmd([lms('type')],[lms('canvas')])]) if(menu_item('New Grid' ,1))ob_create([lmd([lms('type')],[lms('grid' )])]) if(card_is(con())&&menu_item('New Contraption...',1))modal_enter('pick_contraption') if(menu_item('Order...' ,count(ifield(con(),'widgets'))))modal_enter('orderwids') menu_separator() let al=1,aa=1,av=1,as=1,at=1,ai=1,an=1 ob.sel.map(unpack_widget).map(w=>{al&=w.locked, as&=w.show=='solid', at&=w.show=='transparent', ai&=w.show=='invert', an&=w.show=='none'}) ob.sel.map(w=>aa&=lb(ifield(w,'animated'))) ob.sel.map(w=>av&=lb(ifield(w,'volatile'))) if(menu_check('Locked' ,ob.sel.length,ob.sel.length&&al))ob_edit_prop('locked' ,lmn(!al)) if(menu_check('Animated' ,ob.sel.length,ob.sel.length&&aa))ob_edit_prop('animated',lmn(!aa)) if(menu_check('Volatile' ,ob.sel.length,ob.sel.length&&av))ob_edit_prop('volatile',lmn(!av)) menu_separator() if(menu_check('Show Solid' ,ob.sel.length,ob.sel.length&&as))ob_edit_prop('show',lms('solid' )) if(menu_check('Show Transparent',ob.sel.length,ob.sel.length&&at))ob_edit_prop('show',lms('transparent')) if(menu_check('Show Inverted' ,ob.sel.length,ob.sel.length&&ai))ob_edit_prop('show',lms('invert' )) if(menu_check('Show None' ,ob.sel.length,ob.sel.length&&an))ob_edit_prop('show',lms('none' )) menu_separator() if(menu_item('Font...',ob.sel.length))modal_enter('fonts') if(menu_item('Pattern...',ob.sel.length)){ob.pending_pattern=ln(ifield(ob.sel[0],'pattern')),modal_enter('widpattern')} if(menu_item('Script...',ob.sel.length)){ if(ob.sel.reduce((m,v)=>m&&ob.sel[0].script==v.script,1)){setscript(ob.sel)}else{ modal_enter('multiscript') ms.message=lms('Not all of the selected widgets\nhave the same script.\nEdit them all together anyway?') ms.verb=lms('Edit') } } if(menu_item('Properties...',ob.sel.length==1)||(ob.sel.length==1&&ev.action&&ms.type==null))object_properties(ob.sel[0]) } if(ms.type=='listen'){ menu_bar('Listener',1) if(menu_item('Clear History',1))li.hist=[],li.scroll=0 if(menu_item('Clear Locals' ,1))li.vars=new Map() menu_separator() if(menu_item('Show Locals',1)){ const loc=lmd();for(let k of li.vars.keys())dset(loc,lms(k),li.vars.get(k)) listen_show(ALIGN.right,0,loc) } menu_separator() if(menu_item('Evaluate',rtext_len(ms.text.table)))listener_eval() } if(uimode=='draw'||uimode=='object'){ menu_bar('Style',ms.type==null&&!kc.on) if(uimode=='draw'){ if(menu_item('Stroke...',1))modal_enter('pattern') if(menu_item('Fill...' ,1))modal_enter('fill' ) if(menu_item('Brush...' ,1))modal_enter('brush' ) menu_separator() } if(menu_check('Color' ,1,dr.color))dr.color^=1 if(menu_check('Transparency',1,dr.trans))dr.trans^=1 menu_separator() if(menu_check('Underpaint' ,1,dr.under))dr.under^=1 } menu_bar('Help',1) if(menu_item('Decker Website...' ,1))n_go([lms('http://beyondloom.com/decker/index.html' )],deck) if(menu_item('Decker Community...',1))n_go([lms('https://internet-janitor.itch.io/decker/community')],deck) if(menu_item('Decker Reference...',1))n_go([lms('http://beyondloom.com/decker/decker.html' )],deck) if(menu_item('Lil Reference...' ,1))n_go([lms('http://beyondloom.com/decker/lil.html' )],deck) } main_view=_=>{ if(in_layer()&&uimode=='object'&&ob.sel.length==0)tracking() const back=con_image(), wids=con_wids(), pal=deck.patterns.pal.pix if(ms.type!='trans'){ const cl=con_clip(),s=con_size() if(back.size.x!=s.x||back.size.y!=s.y)image_resize(back,s),mark_dirty() if(dr.fatbits){frame.image.pix.fill(46),draw_rect(cl,dr.trans_mask?45:32),draw_fat(cl,back,pal,frame_count,0,dr.zoom,dr.offset)} else if(s.x==frame.size.x&&s.y==frame.size.y){for(let z=0;z0||dr.sel_start.h>0))draw_rect(con_to_screen(dr.sel_start),dr.fill) bg_lasso_preview() const livesel=bg_select() ev=con_to_ev(ev) if(((uimode=='object'||uimode=='draw')&&dr.show_grid)||ms.type=='grid'){ const c=con_dim() if(dr.fatbits){ for(let x=dr.grid_size.x;xcon_to_screen(unpack_widget(w).size)).reduce(runion) wids.v.filter(w=>!ob.sel.some(x=>x==w)).map(w=>{ const a=con_to_screen(unpack_widget(w).size), u=runion(a,b) if(b.y ==a.y )draw_hline(u.x,u.x+u.w,u.y ,13) // top-top if(b.y+b.h==a.y+a.h)draw_hline(u.x,u.x+u.w,u.y+u.h,13) // bottom-bottom if(b.x ==a.x )draw_vline(u.x ,u.y,u.y+u.h,13) // left-left if(b.x+b.w==a.x+a.w)draw_vline(u.x+u.w,u.y,u.y+u.h,13) // right-right if(b.y ==a.y+a.h)draw_hline(u.x,u.x+u.w,b.y ,13) // top-bottom if(b.y+b.h==a.y )draw_hline(u.x,u.x+u.w,a.y ,13) // bottom-top if(b.x ==a.x+a.w)draw_vline(b.x,u.y,u.y+u.h ,13) // left-right if(b.x+b.w==a.x )draw_vline(a.x,u.y,u.y+u.h ,13) // right-left }) } const eb=ev;if(uimode!='interact')ev=event_state() if(uimode=='interact'||(dr.show_widgets&&!dr.fatbits)){handle_widgets(wids,con_offset())} else if(dr.show_widgets&&dr.fatbits)wids.v.map(w=>{draw_boxinv(pal,con_to_screen(unpack_widget(w).size))}) ev=eb const resizing=prototype_size_editor() if(uimode=='draw'){if(bg_has_sel())draw_handles(con_to_screen(livesel));draw_box(con_to_screen(livesel),0,ANTS)} if(wid.pending_grid_edit){wid.pending_grid_edit=0;modal_enter('gridcell')} if(uimode=='object'&&!resizing)ev=ev_to_con(ev),object_editor(),ev=con_to_ev(ev) if((uimode=='object'&&ob.show_names)||(uimode=='draw'&&dr.show_widgets&&dr.fatbits)||ms.type=='listen'){ wids.v.map(wid=>{ const size=con_to_screen(unpack_widget(wid).size) const n=ls(ifield(wid,'name')),s=font_textsize(FONT_BODY,n) draw_text_outlined(rect(size.x,size.y-s.y,s.x,s.y),n,FONT_BODY) }) } if(ob.show_cursor&&ms.type==null&&({draw:1,object:1})[uimode]){ ev=ev_to_con(ev); const cursor=(x,r)=>{ const t=ls(dyad.format(lms(x),lml([r.x,r.y,r.w,r.h].map(lmn)))),s=font_textsize(FONT_BODY,t) draw_text_outlined(rsub(con_to_screen(rpair(ev.pos,s)),rect(0,s.y)),t,FONT_BODY) } if(uimode=='draw'&&bg_has_sel()) {cursor('(%3i,%3i,%3i,%3i)',livesel)} else if(uimode=='object'&&ev.drag&&is_resizable()){cursor('(%3i,%3i,%3i,%3i)',unpack_widget(ob.sel[0]).size)} else if(ev.drag){const a=ev.dpos,b=ev.pos; cursor('(%3i,%3i,%3i,%3i)',rpair(b,rsub(b,a)))} else {cursor('(%3i,%3i)',ev.pos)} ev=con_to_ev(ev); } if(in_layer()&&ev.exit&&!dr.fatbits&&!card_is(con()))con_set(null),ev.exit=0 } gestures=_=>{ if(!enable_touch||!card_is(con()))return if(!in_layer()||uimode!='interact'||(!ev.drag&&!ev.mu))return // must be in the right state of mind if(ev.drag&&ob.sel.length&&lb(ifield(ob.sel[0],'draggable')))return // must not be dragging a canvas if(con_wids().v.some(x=>dover(unpack_widget(x).size)))return // must touch grass const d=rsub(ev.pos,ev.dpos);if(Math.sqrt(d.x*d.x+d.y*d.y)<50)return // must be emphatic if(Math.abs(d.x)<2*Math.abs(d.y)&&Math.abs(d.y)<2*Math.abs(d.x))return // must be highly directional const dir=Math.abs(d.x)>2*Math.abs(d.y)?(d.x<0?'left':'right'):(d.y<0?'up':'down') image_paste(rect(ev.pos.x-8,ev.pos.y-8,16,16),frame.clip,GESTURES[dir],frame.image,0) if(ev.mu)msg.target_navigate=con(),msg.arg_navigate=lms(dir) } gamepad_poll=_=>{ try{ const bval=(b)=>b&&b.pressed const bset=(k,v)=>{ keypress[k]=!keydown[k]&& v keyup [k]= keydown[k]&&!v keydown [k]=v } navigator.getGamepads().filter(x=>x).forEach(g=>{ bset('GamepadUp',bval(g.buttons[12])) bset('GamepadDn',bval(g.buttons[13])) bset('GamepadLf',bval(g.buttons[14])) bset('GamepadRt',bval(g.buttons[15])) bset('GamepadA1',[1,3,5].some(x=>bval(g.buttons[x]))) bset('GamepadA2',[0,2,4].some(x=>bval(g.buttons[x]))) bset('GamestickLf',g.axes[0]<-.2) bset('GamestickRt',g.axes[0]> .2) bset('GamestickUp',g.axes[1]<-.2) bset('GamestickDn',g.axes[1]> .2) }) }catch(e){} } validate_modules=_=>{ for(let z=0;z{ deck=d, dirty=0, wid.active=-1, wid.hist=[], au.hist=[], doc_hist=[], doc_hist_cursor=0, dr=draw_state(), desired_cursor=null, con_set(null) FONT_BODY=dget(deck.fonts,lms('body')),FONT_MENU=dget(deck.fonts,lms('menu')),FONT_MONO=dget(deck.fonts,lms('mono')) fb=image_make(getpair(ifield(ifield(deck,'card'),'size'))),context=frame=draw_frame(fb),validate_modules(),setmode('interact'),msg.next_view=1 seed=0|(new Date().getTime()/1000),n_play([NIL,lms('loop')]) } tick=_=>{ pointer.up=ev.mu,pointer.down=ev.md,gamepad_poll(),toolbars() pointer_updated=0,msg.pending_halt=0,frame=context,uicursor=0,fb.pix.fill(0) menu_setup(),all_menus(),widget_setup() const ev_stash=ev;kc.heading=null;if(kc.on)ev=event_state() if(uimode=='script'){const mh=3+font_h(FONT_MENU);if(!kc.on)script_editor(rect(0,mh,frame.size.x+1,frame.size.y-mh))}else{main_view()} modals(),gestures() if(kc.on){ev=ev_stash,keycaps()} if(uimode=='script'&&enable_touch&&ms.type==null)wid.active=0 menu_finish() if(uimode=='draw'&&dr.fatbits&&!ev.hidemenu)draw_icon(rect(frame.size.x-14,2),ZOOM,1) if(uimode=='interact'&&ev.drag&&ob.sel.length&&lb(ifield(ob.sel[0],'draggable'))){ const c=ob.sel[0].card, off=(contraption_is(c)||prototype_is(c))?getpair(ifield(c,'pos')):rect(0,0) iwrite(ob.sel[0],lms('pos'),lmpair(rsub(rsub(ev.pos,ob.prev),off))),mark_dirty() } document.title=ls(ifield(deck,'name')) || 'Untitled Deck' const ccolor=ln(ifield(deck,'corners')) if(ccolor)for(let x=0;x<=1;x++)for(let y=0;y<=1;y++)draw_icon(rect(x*(context.size.x-5),y*(context.size.y-5)),CORNERS[x+y*2],ccolor) const used=interpret() if(uimode=='interact'&&profiler){ const r=rect(frame.size.x-60,2,50,12), pal=deck.patterns.pal.pix draw_text(inset(r,2),ls(dyad.format(lms('%0.2f%%'),lmn(100*used/FRAME_QUOTA))),FONT_BODY,1),draw_box(r,0,1) for(let z=0;z{ pick_palette(deck) const anim=deck.patterns.anim, pal=deck.patterns.pal.pix, mask=dr.trans_mask&&uimode=='draw', fc=dr.show_anim?0|(frame_count/4):0 const anim_ants =(x,y)=>(0|((x+y+(0|(frame_count/2)))/3))%2?15:0 const anim_pattern=(pix,x,y)=>pix<28||pix>31?pix: anim[pix-28][fc%max(1,anim[pix-28].length)] const draw_pattern=(pix,x,y)=>pix<2?(pix?1:0): pix>31?(pix==32?0:1): pal_pat(pal,pix,x,y)&1 const draw_color =(pix,x,y)=>pix==ANTS?anim_ants(x,y): pix>=32+PAL_COLORS?0: pix>31?pix-32: draw_pattern(pix,x,y)?15:0 if(!id||id.width!=fb.size.x||id.height!=fb.size.y){id=new ImageData(fb.size.x,fb.size.y);id.data.fill(0xFF)} for(let z=0,d=0,y=0;y>16) id.data[d+1]=0xFF&(cv>> 8) id.data[d+2]=0xFF&(cv ) } const r=q('#render');r.getContext('2d').putImageData(id,0,0) const g=q('#display').getContext('2d');g.imageSmoothingEnabled=zoom!=(0|zoom),g.save(),g.scale(zoom,zoom),g.drawImage(r,0,0),g.restore() } let pointer_updated=0 move=(x,y)=>{if(!pointer_updated)pointer.prev=pointer.pos;pointer.pos=ev.pos=rect(x,y);pointer_updated=1} down=(x,y,alt)=>{ ev.rawdpos=ev.rawpos,ev.down_modal=ms.type,ev.down_uimode=uimode,ev.down_caps=kc.on move(x,y),pointer.held=ev.drag=1;pointer.start=ev.dpos=pointer.pos,ev.md=1,ev.clicktime=12;if(alt)ev.rdown=1;initaudio()} up=(x,y,alt)=>{ move(x,y),pointer.held=ev.drag=0,pointer.end=pointer.pos,ev.mu=1;if(alt)ev.rup=1 if(ev.clicktime)ev.click=1;ev.clicktime=0;if(ev.clicklast)ev.dclick=1;ev.clicklast=DOUBLE_CLICK_DELAY if(ev.callback&&ev.callback_rect&&over(ev.callback_rect)&&(ev.callback_drag||dover(ev.callback_rect)))ev.callback() ev.callback=null,ev.callback_rect=null,ev.callback_drag=0;if(audio)audio.resume() } mouse=(e,f)=>{ ev.rawpos=rect(e.pageX,e.pageY);const c=q('#display').getBoundingClientRect() if(e.button==1){if(f==down)ev.mdown=1;return} f(0|((e.pageX-c.x)/zoom),0|((e.pageY-c.y)/zoom),e.button!=0) } touch=(e,f)=>{const t=e.targetTouches[0]||{}; mouse({pageX:t.clientX, pageY:t.clientY, button:0},f);if(!set_touch)enable_touch=1} let prev_stamp=null, leftover=0 loop=stamp=>{ if(!prev_stamp)prev_stamp=stamp let delta=(stamp-prev_stamp)+leftover, frame=1000/60, tc=0 while(delta>frame){tick(),tc++,delta-=frame;if(tc==5){delta=0;break}};leftover=delta sync(),prev_stamp=stamp,requestAnimationFrame(loop) if(do_panic)setmode('object');do_panic=0 } resize=_=>{ const b=q('body'), screen=rect(b.clientWidth,b.clientHeight), fs=min(screen.x/fb.size.x,screen.y/fb.size.y) zoom=max(1,is_fullscreen()?fs:(0|fs)) tzoom=0|min((screen.x-(zoom*fb.size.x))/(2*toolsize.x),screen.y/toolsize.y) const tz=tzoom*toolbars_enable const c =q('#display');c .width=fb.size .x*zoom,c.height =fb.size .y*zoom const tl=q('#ltools' );tl.width=toolsize.x*tz ,tl.height=toolsize.y*tz const tr=q('#rtools' );tr.width=toolsize.x*tz ,tr.height=toolsize.y*tz const r =q('#render' );r .width=fb.size .x ,r .height=fb.size .y const rl=q('#lrender');rl.width=toolsize.x ,rl.height=toolsize.y const rr=q('#rrender');rr.width=toolsize.x ,rr.height=toolsize.y } window.onresize=_=>{resize(),sync()} q('body').addEventListener('mousedown' ,e=>mouse(e,down)) q('body').addEventListener('mouseup' ,e=>mouse(e,up )) q('body').addEventListener('mousemove' ,e=>mouse(e,move)) q('body').addEventListener('contextmenu',e=>e.preventDefault()) q('body').addEventListener('touchstart' ,e=>{e.preventDefault(),touch(e,down)},{passive:false}) q('body').addEventListener('touchend' ,e=>touch({targetTouches:e.changedTouches},up)) q('body').addEventListener('touchmove' ,e=>touch(e,move)) q('body').onwheel=e=>ev.scroll=e.deltaY<0?-1:e.deltaY>0?1:0 q('body').onkeydown=e=>{ initaudio() keypress[e.key]=!keydown[e.key],keydown[e.key]=1 if(e.shiftKey)ev.shift=1 if(e.key=='ArrowUp' )ev.dir='up' if(e.key=='ArrowDown' )ev.dir='down' if(e.key=='ArrowLeft' )ev.dir='left' if(e.key=='ArrowRight')ev.dir='right' if(e.metaKey||e.ctrlKey)ev.alt=1,ev.shortcuts[e.shiftKey?(e.key.toUpperCase()):e.key]=1 else if(e.key.length==1&&wid.infield)field_input(clchars(e.key)) else if(uimode=='draw'&&ms.type==null){if(e.key=='Backspace'||e.key=='Delete')bg_delete_selection()} else if(uimode=='object'&&ms.type==null){ if(e.key=='[')ob_move_dn() if(e.key==']')ob_move_up() if(e.key=='Backspace'||e.key=='Delete')ob_destroy() } else if(ms.type=='recording'&&!wid.infield&&au.mode=='stopped'){if(e.key=='Backspace'||e.key=='Delete')sound_delete()} if(e.key=='Enter')ev.action=1 if (wid.ingrid )grid_keys (e.key,e.shiftKey) else if(wid.infield)field_keys(e.key,e.shiftKey) if(uimode=='script')sc.status='' if(e.key==' '&&!wid.infield)ev.action=1 if(e.key=='Tab')ev.tab=1 if(e.key=='L'&&ms.type==null&&!wid.ingrid&&!wid.infield)ev.shortcuts['l']=1 if(e.key=='f'&&ms.type==null&&!wid.ingrid&&!wid.infield)toggle_fullscreen if(e.key=='j'&&ms.type==null&&dr.limbo_dither&&dr.dither_threshold>-2.0)dr.dither_threshold-=.1 if(e.key=='k'&&ms.type==null&&dr.limbo_dither&&dr.dither_threshold< 2.0)dr.dither_threshold+=.1 if((e.metaKey||e.ctrlKey)&&({c:1,x:1,v:1})[e.key]){} else{e.preventDefault()} } q('body').onkeyup=e=>{ keydown[e.key]=0,keyup[e.key]=1 if(e.key=='Meta'||e.key=='Control'||e.metaKey||e.ctrlKey)ev.alt=0,keydown={} if(e.key=='Enter'&&e.shiftKey)ev.eval=1 if(e.key=='Shift'||e.shiftKey)ev.shift=0 if(e.key=='m'&&uimode=='draw'&&in_layer())ev.hidemenu^=1 if(e.key=='t'&&uimode=='draw'&&in_layer())dr.trans^=1 if(e.key=='u'&&uimode=='draw'&&in_layer())dr.under^=1 if(e.key=='r'&&uimode=='draw'&&in_layer())dr.trans_mask^=1 const brush_count=24+count(deck.brushes) if(e.key=='9'&&uimode=='draw'&&ms.type==null)dr.brush=max( 0,dr.brush-1) if(e.key=='0'&&uimode=='draw'&&ms.type==null)dr.brush=min(brush_count-1,dr.brush+1) if(e.key=='Escape')ev.exit=1 if(!wid.infield&&!wid.ingrid&&ms.type==null&&uimode=='interact'&&card_is(con())){ if(e.key=='ArrowUp' )msg.target_navigate=ifield(deck,'card'),msg.arg_navigate=lms('up' ) if(e.key=='ArrowDown' )msg.target_navigate=ifield(deck,'card'),msg.arg_navigate=lms('down' ) if(e.key=='ArrowLeft' )msg.target_navigate=ifield(deck,'card'),msg.arg_navigate=lms('left' ) if(e.key=='ArrowRight')msg.target_navigate=ifield(deck,'card'),msg.arg_navigate=lms('right') } e.preventDefault() } q('body').onblur=e=>{ev.alt=0,keydown={}} let local_clipboard='' getclipboard=after=>{ const t=document.createElement('textarea');t.style.top='0',t.style.left='0',t.position='fixed',t.onpaste=e=>{e.stopPropagation()} document.body.appendChild(t),t.focus(),t.select();const success=document.execCommand('paste');const x=t.value;document.body.removeChild(t) if(success){after(x);return} // this approach seems to work fine for Safari try{navigator.clipboard.readText().then(text=>{after(text)},_=>_)} // this works in Chrome, but *explicitly* does not work in Firefox catch(e){after(local_clipboard)} // so Firefox gets to use a baby-jail local clipboard } setclipboard=x=>{ local_clipboard=x const t=document.createElement('textarea');t.value=x,t.style.top='0',t.style.left='0',t.position='fixed',t.oncopy=e=>{e.stopPropagation()} document.body.appendChild(t),t.focus(),t.select();const success=document.execCommand('copy');document.body.removeChild(t);if(success)return; try{navigator.clipboard.writeText(x).then(_=>_,_=>_)}catch(e){console.log(e)} // generally not necessary, but worth having as a last resort } docut=_=>{ if(wid.fv){ const s=rtext_span(wid.fv.table,wid.cursor),i=rtext_is_image(s);field_keys('Delete',0) return i?image_write(i):ls(rtext_string(s)) } else if(ms.type=='recording'&&au.mode=='stopped'){const r=sound_write(sound_selected());sound_delete();return r} else if(ms.type==null&&uimode=='draw'&&(bg_has_sel()||bg_has_lasso())){ const i=bg_has_lasso()?image_mask(dr.limbo,dr.mask): dr.limbo?bg_scaled_limbo():bg_copy_selection(dr.sel_here) bg_scoop_selection(),bg_delete_selection();return image_write(i) } else if(ms.type==null&&uimode=='object'&&ob.sel.length){ob_order();const r=ls(con_copy(con(),lml(ob.sel)));ob_destroy();return r} return null } docopy=_=>{ if(wid.gv){return ls(n_writecsv([wid.gv.table,grid_format()]))} else if(wid.fv){ const s=rtext_span(wid.fv.table,wid.cursor),i=rtext_is_image(s) return i?image_write(i):ls(rtext_string(s)) } else if(ms.type=='recording'&&au.mode=='stopped'){return sound_write(sound_selected())} else if(ms.type==null&&uimode=='draw'&&(bg_has_sel()||bg_has_lasso())){ const i=bg_has_lasso()?image_mask(dr.limbo,dr.mask): dr.limbo?bg_scaled_limbo():bg_copy_selection(dr.sel_here);return image_write(i) } else if(ms.type==null&&uimode=='object'&&ob.sel.length){ob_order();return ls(con_copy(con(),lml(ob.sel)))} return null } dopaste=x=>{ x=clchars(x) if((ms.type==null||(ms.type in{'contraption_props':1,'field_props':1}&&wid.fv))&&/^%%IMG[0123]/.test(x)){ const i=image_read(x);if(i.size.x==0||i.size.y==0)return if(wid.fv){ if(wid.f.style!='rich'){field_input(x)} else{field_edit(lms(''),i,1,'i',wid.cursor)} }else{setmode('draw'),bg_paste(i,0)} } else if(ms.type=='recording'&&au.mode=='stopped'&&/^%%SND0/.test(x)){sound_edit(sound_replace(sound_read(x)))} else if(ms.type==null&&/^%%WGT0/.test(x)){ const v=plove(x,6,x.length-6).value; let defs=dget(v,lms('d')),wids=dget(v,lms('w'));wids=wids?ll(wids):[] let nest=0;if(prototype_is(con())){const t=lms('type'),k=lms('contraption');nest=wids.some(x=>match(k,dget(x,t)))} if(nest){modal_enter('alert');ms.message=rtext_cast(lms('Contraptions cannot be pasted into contraption prototypes.'))} else{merge_fonts(deck,dget(v,lms('f'))),merge_prototypes(deck,defs?ld(defs):lmd(),wids),ob_create(wids)} } else if(ms.type==null&&/^%%CRD0/.test(x)){ const c=deck_paste(deck,lms(x));con_set(null) const card=ifield(deck,'card'), n=ln(ifield(card,'index'));iwrite(c,lms('index'),lmn(n+1)),n_go([c],deck) } else if(/^%%RTX0/.test(x)){ if(wid.fv){ const t=rtext_decode(x) if(wid.f.style=='rich'){field_editr(t,wid.cursor)} else{field_input_raw(ls(rtext_string(t)))} } } else if(wid.gv&&!wid.g.locked&&ms.type==null){grid_edit(n_readcsv([lms(x),lms(wid.g.format)]))} else if(wid.fv){field_input_raw(x)} } cutcard=_=>{const c=ifield(deck,'card');setclipboard(ls(deck_copy(deck,c))),deck_remove(deck,c),mark_dirty()} copycard=_=>{const c=ifield(deck,'card');setclipboard(ls(deck_copy(deck,c)))} copywidgetimg=_=>{setclipboard(image_write(draw_widget(ob.sel[0]))),frame=context} pasteascanvas=_=>getclipboard(t=>{ob_create([lmd(['type','locked','image','border'].map(lms),[lms('canvas'),ONE,lms(t),ZERO])]),frame=context}) pasteintocanvas=_=>getclipboard(t=>{const i=image_read(t),c=ob.sel[0];iwrite(c,lms('lsize'),ifield(i,'size')),c.image=i}) menucut=_=>{const r=docut();if(r)setclipboard(r)} menucopy=_=>{const r=docopy();if(r)setclipboard(r)} menucopyrich=_=>{setclipboard(rtext_encode(rtext_span(wid.fv.table,wid.cursor)))} menupaste=_=>getclipboard(t=>{if(t.length)dopaste(t)}) document.oncut=e=>{const r=docut();if(r)e.clipboardData.setData('text/plain',r),local_clipboard=r;e.preventDefault()} document.oncopy=e=>{const r=docopy();if(r)e.clipboardData.setData('text/plain',r),local_clipboard=r;e.preventDefault()} document.onpaste=e=>{ const src=e.clipboardData||e.originalEvent.clipboardData if(src.items[0]){ const file=src.items[0].getAsFile() if(file&&/^image\//.test(file.type)){load_image(file);return} if(file&&/^audio\//.test(file.type)){load_sound(file);return} } dopaste(src.getData('text/plain')),e.preventDefault() } q('body').ondragover=e=>e.preventDefault() q('body').ondrop=e=>{ e.preventDefault();const file=e.dataTransfer.files.item(0);if((!file)||lb(ifield(deck,'locked')))return if(/\.(psv|csv)$/i.test(file.name)){ file.text().then(t=>{ const data=n_readcsv([lms(t),NIL,lms(file.type=='text/csv'?',':'|')]) setmode('object'),ob_create([lmd([lms('type'),lms('value')],[lms('grid'),monad.cols(data)])]) }) } if(/\.(html|deck)$/i.test(file.name)){ file.text().then(t=>{ modal_enter('resources'),ms.message=deck_read(t),ms.grid=gridtab(res_enumerate(ms.message)) }) } if(/\.hex$/i.test(file.name)){ file.text().then(t=>{ const color_dist=(a,b)=>{ const dr=(0xFF&(a>>16))-(0xFF&(b>>16)) const dg=(0xFF&(a>> 8))-(0xFF&(b>> 8)) const db=(0xFF&(a ))-(0xFF&(b )) return (dr*dr)+(dg*dg)+(db*db) } iwrite(deck.patterns,lmn(32),lmn(0xFFFFFF)),iwrite(deck.patterns,lmn(47),ZERO) let pal=ll(dyad.parse(lms('%h'),lml(t.split('\n').slice(0,-1).map(lms)))) if(pal.length>14){ let a=0,b=0;pal.map((v,i)=>{ if(color_dist(ln(v),0x000000)i!=a&&i!=b) }for(let z=0;z=15?1:0)),pal[z]) }) } if(/^image\//.test(file.type)){load_image(file)} if(/^audio\//.test(file.type)){load_sound(file)} } pushstate(lmenv()),load_deck(deck_read(q('script[language="decker"]').innerText)) const tag=decodeURI((new URL(document.URL)).hash.slice(1)||'');if(tag.length)iwrite(deck,lms('card'),lms(tag)) resize(),requestAnimationFrame(loop)