From 651563f793796fd8128b7875c04f504e7a6c9f72 Mon Sep 17 00:00:00 2001 From: Vince Date: Mon, 27 Aug 2012 02:05:13 +0200 Subject: [PATCH] FrameSheet model object migration Migration to a Domain object (currently a FrameSheetModel, feel free to change its name). The model is being used by the slideshow (drawing each tiles), animation preview (drawing animation) and drawing (update model and redraw current tile). Now the rendering information are not stored in a canvas element that you paste from canvas to canvas but centralize in this model. The frame is described as an array of array: that will allow different rendering using the dpi constants and more flexibility (e.g. drawing a grid, serializing the data). Some minor modifications: - cleaning markup - adding background image to highlight transparent area --- css/style.css | 30 +++- img/transparent_background.png | Bin 0 -> 48266 bytes index.html | 16 +- js/frameSheetModel.js | 76 ++++++++ js/piskel.js | 313 ++++++++++++++++++++++----------- 5 files changed, 326 insertions(+), 109 deletions(-) create mode 100644 img/transparent_background.png create mode 100644 js/frameSheetModel.js diff --git a/css/style.css b/css/style.css index 7008f476..9136c5f0 100644 --- a/css/style.css +++ b/css/style.css @@ -76,13 +76,19 @@ ul, li { list-style-type: none; } -#preview-list .preview-tile { +.preview-tile { padding : 10px; overflow: hidden; + background-color: gray; +} + +.preview-tile .canvas-container { + float: left; } .preview-tile .tile-view { float: left; + border: blue 1px solid; } .preview-tile .tile-action { @@ -99,9 +105,29 @@ ul, li { } #preview-list .preview-tile.selected { - background-color: gray; + background-color: lightyellow; } +.canvas-container { + position: relative; + display: block; +} + +.canvas-container .canvas-background { + background: url(../img/transparent_background.png) repeat; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.canvas { + position: relative; + z-index: 1; +} + +/* Force apparition of scrollbars on leopard */ ::-webkit-scrollbar { -webkit-appearance: none; width: 7px; diff --git a/img/transparent_background.png b/img/transparent_background.png new file mode 100644 index 0000000000000000000000000000000000000000..4a1f7a8e591c8d0cffd8b628b21c0a52a230ce58 GIT binary patch literal 48266 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�jE{3%&IsST3LlrXxownQ;_7_*S-5&?kDXh?eGXJ2=upf-O&)E;S!1m!dhA!P@u?_{?|bx*o}6 z-$~H0fg9BwN|Y2p8^N8j?4%ObHM!c|J7N3&7LEUg5cWx8>FB>$Bq~nUe4UP&rtt!3 z#U7y@Bz_Md%2F(O$&H#6So%}kKr8Hsv3$a_ID&XgR;q(%E7MS|VIe+(WbsQ!xR zL7v`qLfi$iGt+*tM|A_R2v;yTZIGxef0&88oDb6dc%^H-#=KHV>xosnMeAdHk(+&m zof9}HJfB~H&^hV7Cf~z%L|MSY+*XnEfkMwyimC~E$#uAm0r`fF7o(KS6@-!(QkY2S zthAGE737+3kfT+L3Gx2zB8^)(080Mv+O1I%bYFK=w(k;z1uJM~6G+-ghAc{p3BE3L zEv$fw-=tWeacZHCTm^-;aDBq-p<<+5Bi7u014!RfRP^Oi@v?!k^`eNjh=nLC*XAmE z89xy;Y_qx@A^Gqi$4VkmR*V6V?VTrPMELaUoANuM)Wl8sEVN0 zCy_D~ZzOFqhPV4UqPe`dr=RGX$q{y1QFali*Ax`~)5>IejQ~<(Z;pbg^jWi}+0>h* zu(`jQCm8%Nm!mKL9`Xqbs8>X!09=!0scbTl)B%IsC8YC9;8XZJ6V^qInpBKLT-}8w zi^02q!0m+O2P=Bduq=oK21ZR&Q+%^p|0&Ig<&O!nSn3MIxJ$)#5du0!`U=q2vO@3ty>0t3=k!EX(AV&0F<3({8{#sYTp z7&EpQLpQ(>~xGPbj<10lOKGK5lZN3S1Cx3P$UGM`d5+%g#Duc5+kRyM>L=Z*jC=bV-@J^im&QlVtW1eRb4>I4G`WDvhyqr2((quq z1+D{UmL-pvgS`1mf>9u8W2DoQyt=={YM`fu?R#ty1WF-7eb(% z61s$7IN@s|mIf&0$Etm;ctjELNGwu)WpzduVeW!5UO!Km!xEPRk{}>p`CUXx6B^gm zOE&JnQ#7GsN7aG&q%-RVfy{B3%**5;=1%+Qc8{=dMDFQ@t(-tw57yxgUP^S3mY1&5 z@WMmX;+1Iei10PMAsORgFhRjsB3GI5l)?Y(9&@d#Pczd2OM4(?_95K#Me{iY2=O6M z%`=82Pyh|_(MPtl%2oRN{&~lTPBm6yCp z`%H+_a?92X=tD$bcPOD}ZdK`Vq~}q(S~Elj6#90Hzu2RooURlC$duV9xVlAhNfYR~ z6gWAMIJ;e`Wtg_XcKE>6-W3+l+sv3AqrUFU~&p7lze z_M?-`BGK4X3#S~1wuXtLbVUZ^CRs%VXATfUD`&(nTlyg^u}`)oE}>b^Cl{ zy|xP7f(-q~^((h_1W|v140jIEYcSE3f9zLcl>N7^CjZZdxbLs$W1SVaUf4M8(_gmT z?1U5nz+twvM9>9Kp!*g6D=C-3IRS3CM$}}_sVPJ-U)=aDi#DA8lM1J^KU_}*RU&HFRf(jMcdAo1D!@VT%Qj-)}i@KYt7~9 z&u@oXg+_eMwh_y?*7}7Me_(w(ntwFBzA6ot_0F<1rWu@|{@sm40XVZYa=-m+E~aK( zD-D_u0Ni^jrkN9S3h2sz%iQXoGrOWZ9plg{KAVqwIJlv`2fIh-7+%sHe0rCmdZ6rcW?0xY4_WBXbhErmPX%i3#qj2&&k6n?u&6~>MDN23ew=%)^k5BgUNMY zqKu!ThkcSfcxOqY5%Q5d^Nj=l-de!>6}|A69W=nZsL zTe|euT_C^s;x8k=9{@w$+a+wbM|vVEwp0j&N~js^(>+YWgbiBSulO*$gyR6DBc3=eRUeh!4zdTjU%! zs1BzX^wA27xd0qi+2`)48ts~136CEJPl=A& zE>uD3W*4~&6}b1FKHVD8nSk@QJNLSppG0&B-*M-16ctR}#PMra(0uWp6?SQ9W2k7; z$i{1hSvNv##z_(`y3ofmYEI9HL0^$cR*HS8)LXq08k$S!ymv&QF5FZ!$>K}oR<9ET znel&uL*OTF{jqOq8DGz8WU-f2UODjZI^zGp^XpNf%s^Afy-z)tyQ}O$DT*kXPi8-F z-pL^viDmLj6IhMI=?v@Hdu$4-$}<(0xYHueQSE$Ob04?z{nQ;?9;wN*0W?G}_b3HD zYns0fYQlt@kDopN2T?Y~K z(IJpvRQDo}j3ycWJnI&eL)`zVfRV8lcfUN3Q1uJJ_KHB%ys6FXG_%AywOZ5bS>vtE zN!Gp;z~}LX!w38!8}=+LWWb@f%>GdSkaJ%j*(gM_(Bk5Q+5_Wl`<5v-?IX_8fA_gT z#=TESh(CM?A&@XrWHS?dj>hZ0IJ{)BLBGgpeB3CvyjAQrw<@*j|Fe1&z2`PA6y#*i z!H27dz~$C@M_Xqs@m*#ch4 z<)TiWlq}e+g{Hx|Ibn+?OcaKGCcHsL|$*4j1 zjTSSP(B5g`L1b4@hZyO__q@8qu3N>g2~aFtg;DdCz1^IedepFS@$Otf-hLA+N8 zy<6tM^JpX#YZMN$bmuBxXuS;i=YNML$QvIQ*S0?1@_wX{lH3;UXA(bzD;SCoAkP-& zjbV*{vbPL&P`C7^>Fezw#;1#^iK3%eCN3S{>VPJBxP?%J$y>)% zZI=~~m7mK#mXsL}<#C{WdLUeI*#^rnYWMiYv4cx6aHZ?E=*uY3a1!pFXI`nRBL5oZ zuxaaL?fJW7aQ9^2uoUo0CW_L`(q?zZ6q@+?ZEIhTX)A$>DmgbTJvRQq+iVtYirP| znI7vCNzqS24S?}xUTekBEoZVLaJH65vCJ>sDwm6Jgx?66y?K+lX5MT`IZHjZPBi6| z&R@TGCuz=?x-_MB`KNb&L$@jGhZ{OkoNV`ul)0urO$0@4%cZ}ea-Qy@)GxW?u3##H+pts9~*%4plP8Y21OJKc?;JtUl(tO@-$Jkt73RIY(EF+HuTWRwi zVa1H37JKAO`AQv5lbEyWEskrFlOaN8Tz}66S1SFpH5*IYNAq=N<)-MTymXTNo^YaAcr zZz2Ed-U89vA=i&h0hBtJ{gbarIQ+Bb!QF$oJIlR2xT5WN3gP3$_i3Wo-_IOJ+9&Sl zyWRjGy~-KW8@@NBkZ)Bcv=*R0ivC&dE|^i_cf4~E3HNSgH%Z^Kz}J>(?RWfuVFwOr zHWc*y)^iZ@NJ1$$#Ix-eW1d#8V3Q)?(FY$!&F@ro)cM_L{l{}Oz#zK@%|n>omtNcBdZ}-1|dTZ z-x+X;;@=P@xk6jvnOF(#w*x|sh!LPGYwPBqp>^L}XN&qnCnk4BiESwG9&Yrgi7imw zPJdWz#g7J+dpjim>La;4xHwEax}OCB^>HJ?Z|pv_0qrw3DY_HO9>4GB^Y3mdqefzo z%SU3unCe;wLoNJToP|eR_=;UOr@yYE2xn`yPB(pKiM^d4k3)q7iR+n)(7Q{Vr+Z#D zWr9|P1cJ|Uavx7JKW|Q=|E?A6&7w3TgHMgoz}k4Q8bA`g>3?t3#S7?YOs|0cx$vt8 z>jMxWxtiY#k`*LC-ftGFj33KO&O3||%P9$Kb&-dV?w}u73=Lfq0O8NmkBCh+-v)YJ z!c-uQzNZpKKWOdG{4HGGmiMYr<_qD)gSkLU!iIT_T`{!+90H#20N7NsN7xtNwiPxN z4xc-SH^TzLw`EM9=kKmvoxCvYTKTURq^Z@&SHzUKmid=sT(@GXelX>7R8k^k?Soqd zt-=5;7CW+tbM*HPW2g0%3I;6T3cNs@KNzG7ohcc|di4yt5kAJ&4Ls`Y zR88BG_Bv-_OcFn}J8YmLqBDt?MMHJe89gX`Lv-F_H&;=xIg&!A1tWxCnwtg862%Pr^P#vhC2K3VDI56cV&k;rdp9lv2qS$-6`>}&3N{6+AloJ zTQNuY%4R*(L9)tdY{dB+Kvq=c)5I(+qYU0*AlK0+^XUr&GR@VNiEX7^8XwOTLZ%bn z$c%o92RBF0k;Tq|?hI@Jo#b9Q?L~>x3^5JU!;qHwpcEENUS>aky=a($5px8_6;PpO zGOmrdgvL*!u;PkPijF>9?$*fu;Y8B7g5b<_yUsfklin!-U4TAslhuiKU-Q-H*q~%MH4*jgzMCRiHl*i zWZu(Xsogr07=xtYBZ+q{BL zwF9f$SH+S*!*J}01veVCH~HC8JDj~2o$xCjz050VRCJi0!&G&eq>)d8*bEj9__&-U zK8nAJ_S^`P1Xkl@JSG`>QTt_JiMg5v2M<9B=$8>e-$VZn2a;!*JaUuzxEaKJYh!63 ze+e38?I3?)rA5326gw+H>EW?qwWAkLm)zz!PCzJ#2hdn`DF;XdR0w9VOb!Z@5jW66 z)n{WoidP3-oE^SGINARmU(yE2o}qbjhf@hRJ@o;twigDMApTT z5^A=zrijQ(2{$6>{u@eKaA>NVL4@m5=FX-#M&lBu_1^kfRB?~n!yA!fiPlXilA|+( z7PiwzQi>-0PRAGy9dKB5cCabuN0>cw4B(VVq|-~X)?1haOR|j!gcUdEDIr)OgVQ~1 zVeM*-SHo(^J+zX_!G%5>8-I<1!Z{lNjW2qU92m|WI$JB6{z!dc!mW)Jv3A2}E<8=L%U!SBCHd8acA6#V+WDFGxt=Z0Y>}B9iwJuRhnV z>gG7y(#%HavWQS$O!9y^od7C{tO@X*yzDMRQ`gIZX4(#QZ=F|W{wcn&11Vd_S{l5Z zj+tSjSR(It%1FgU(8^=YQciF7(a;up(JLwFHsV90@=&XX%GZcTV-rDyBBdgSQ$J}h zjaxrIE!nQYPI^^|*|DbxDZr}rm{Mh7^f8t4C>gC~KCgCAqz*$_B{#F@=8GXEB9lTY zwOk32`;2b(8MZXthMhgCTyeCkdR0oI5pGOSdCjeHoaiF<^hS=*!cbqG{bvmJWQsm$ z3`J+67ZXmzws@9iM41j{y0^;2(6QDf>Q(UIlpuaUe0;@*b!KERu|EQy#fNC%hKyo- zIH_)dKP|4@8Z#7wOp`~2t$N`55>I(Gy9oB+ytv z1Gs*$!gZ7La&KVz!Gg&Spgv2nv?TrFzI17or;Pk2*@F5KV@dMz3q?2F@2b;UymIis zEpMzA8a$%5Y(76R-i%s0p;VXvlv0eCWrzvxIv7Q-8RZ?E;eyrx2$8fdT>tB65^hI( z@Y50|I>AKkKs)R1puW#G^RO2&V4;&)TmZc&ilNRy@0$gz7)y5T% zOU?N==s_(AdPfL)(=NskeXLkNpQ5itsX8Pd#78&a0cS>_1ldOPzherfL@5`P7ZR1p z2YBu+3S*2g`_kq-ZC2IREJ&#>9iyY<@J>@@6*NICe!)OHi;FW~Z%`xE{w-rSaTZ@} zXn6DE!I|JbBx)h*Dtrm1=*UE!ZG+B64&F`R2>)Jmr?(^X z#7wR;9*#7ff*@LD$cm69g^+`W!dc=)&0)2=XpdPzIonTec&3B+MUhRqTE;r;9cRIQ z87GmyUPY3eH;QBy`?x4YX|D{nSXNRV?MdCws-sd)B^bamJq;P?r~BGYwfe^Bhk}N> z?YzZ+#hM7m8Kpyc2Ksspw?NEAWH?lVU()UC9>=bP6wu=eUjZFu5pheaDcm*I`P-L| zqzEr5qc9S*(IykzNx3M}*{}=u*S?8wv3s|23RKdo3*f8T=An_n-M<>uwV-Kg><~*Y zp|?E0J_&9RiAWW>Mj7(Yp7Rfk;FeYKn0%W(-~khM~Fv4=#&4B&8%Yh-46e`?i2}o=rz+(3la8&=e!`MBQkx zRWHFcyH7m0=#AQJw!{Nn^+IoOQh_sdClwe-(mHz1ha`Kv-+0;y(|)|0{vNC5o19}7 zKZC+p%nn#s%J<1*2OD-3)SmjgA3I;kS^DMDk*l zL}Xi-ZiodYQ9D^9^tBM1$#x8LBy6c&dmXq77F9;mo~;{fN2oXsc-a2|UEFS?Vtd@z zBd9>}zX12L5lPweW@LT-S&=yX2VzJ`qv%+Xm@7Lj-O08yM(*`*h|a}bHZsAR&LP)M zg27Xv{{vXSNEyuTkiyP8svsOpxBHs2-c26yMhRrc%e}h8Hr?McMt{PpJ&GXRo2>kM z^g{1Zv6YfC*wzP#+r}58Ya+}+?EeZ2Uj}YgWk8cee9$7L-Tonde_)plgIulp)KD&h zYxVaaK=HU8%}1i-h#=k>ot{+D^T!}8mceui&7o>KUL>2*!{kmf?u;4X2pTXBt+W`{ z;1XjdnZlH93Y)@-@*rXU4FsE@lC+Bc0fP96ZKuPz+=sW^lC}RPbWQ2AT3>_?MJq2L zLJtlVhHBj}p{^@)mY$Nm0!s_BK;TG(?_j3zHAj-ku804~D3Q>y?k3!d)Rrxw**ZZD z<7J9dqbkaZ-U)`$jNwG3n7`mU*%vR&wJOYF zos+YkV_@0q`nk&1G=$3ZA3d7MS~O@`$h@2lSX|M&z!8PGnfPprO?O*?J*0zl{TL$p zwH|`}ah336YkpCWIgHpMt`%t1_i2@~=<$Fvk&6Z1yuEB0K z)ySfl@-fb^Jp6?MQEdm?zFsJ?cB#m|Z;GDjd;Q`4#8t-A7sG#Js1HoFTL(7)g_@Q4X zUesf=SY1(GQM)V%IaP&>tiWo{-!-h6$xBbV?m4*q@i4&FCjXQ4-pFE}9vxM3aEzYz z%lw%GksTHzl~eM@VpLdEm)eW=mia{Y&sH(-tOqbdA5G@&^^IQ)UN6dqFh6fo`l%)00HVbr#PdS{QKOQjDhcINgBey{63MRyQUVy@jBUqq)lThh%y} zt+|P>zKNBvH+_uDVaT>zI2)sB5^*!DJo>HvtHC{Cm+t%>gGqi{+s0dq%x3;j=sOzv zVUH%0{!8X9lGJ7DTbx{`d)9B#Wu(~sy( z(fX6q-q|_+CEd?-fA{77McH!xKPa2bAh&oUK~Z!57w{9}aKblhGm8jH|C9`Ot*L3g-f7n(-KlARH@DbEXWah|?qEdyW4?f$ z#5I`2Kj2Q{3ky;X|2J@#fitp_9^JTyK6Z;p!6~r2Dv_eb9^e~KYyvafQ>=z zNC)E@jGLNoQk~`UGqR5<@ zi6a$+42>=5f)I_{K~fJn$;&Db({3<3`4=oThO96>89~jXUC@d3%Z%R>Y>MhyxzL@o z_m0|S2=$W6oltS`oNksHZdg>v>IJ(MU3lPhTt_uZSg+1C((Z|;c22_o-pc0 zXETXn6-zCxkwBh8JSotz|6{RJG?>ziU1Q}y*IRT;Tor=lBg@_0j+Gjt&Oy$vF;X#) z^+#6w&&ig`+39sTG(~mkM|55?6ytJX#5|N#`we#02(D%hr6yb;mZ1xr>nK^MvC~LV z!oA{isY7QEC^>eMfL<^#pxJGQh-fK*Wy`vCg`@ z^O(B_f$NzP)7Z1#Xg11IkU>Aj8!ue0wo0OynJ0zU-7 zUqzTHo>2CSsErh$c36BOGl*fP0GHa&=*aFuvx(CKuIYHrM@K-an7UphZ*m$>ZdnRWg+8rp=80La%NC2t>RYpm$e z=LW;bIUA{;Ke(0_r%-D8FutxK4GC-n)dXu@ksB3Zm=|g;uZgp3G1fS-ZU-}rHn{ zSs4spq^)_S#`9G7+6?RCZl15Oi<--&8(B>42}TZMnNcXF@E|rhT(Wp)Uv%?5ZtRkL zPs)5to$$M*{>5qiZ(f+|`a1W3ntdA^i`b*GekIJng@w4nC1li8miHv;1r%2Gr`1y^&Oa_mCoiR80vTUXpP_P3O?K+bGWlU0eH zik+%H!8ngm1an!i8Sk`<#PXDlUJs@hM2S4*oP`WG)464L9NRDC7WmF~2y^xr!QVlb zne!Lfx*1Pf2hrB?Katz;`O92ixv!jCj%Y(?UcYP>alV)k`ihx`o+Y`-x+S_6WY6m`l>?)e1scYH@DqUoV+CH(}z zHReyP)RKU+58zbD?=x51XQ2>(GiJB^G!xO;nTw8f4-C4jb^C#U*N| z)%$Ub58qSI_ZH6Dp8cvy-o!lcvx5g-sM#x^DHP-qTL54;8i{jw)qqwd@czK-iAj$5MlKd{dHcyF>ORHIU*s*rrKr(lnfrY{E zJ%`*yjG!~mLTR1$HC0@V%gi@QA%;HnF{ca4FX3S>I5*+fwUQplFprsw_&47$n;EP} zv6SP#r+<}g2Z*Aj?aU}daf^qjA-#E}oT|)kKIYl}fWPIRb^?V~(4`V9{M3OT%8me^ z1oK3Te%z>BsUW3Qr;3LKI??mvM2I!DM`x+-;p0RbBi+vy+?jxQXUChyQRDs##&X-PJ1ZLKzNQV;>T$!=cQ|cY+WTo{i&FYLw7GKb?2$;?A(6a z{D7(0?Kufa&y0f`*DXTelhp{{{|a3of%4&?6`E_bk$DGfKs`ZX38*31Bz|8J99lr?;N(2INQvoz z;so##B^eRMY1ZaT4A8mt36&QgxC84IM)Y!(0V8DX1lbWorq=Lz*Ko&&zV&X=#>wb1dJFZ6VDHiPKzBWR*8PURzWob7+xkp@xc?*+BQCvZN; z0oa{eLzF*5zzmyA@Wqi;ke4zTNCKt1nFT7Z8)zBgoPk)B!V@?DZG?3RTQK&`M0<@W zokEa~>?4msV$fLG#YyoarnZeoTU;gX1Vcqe(gS@5IEA#d;jmab?>mD$zE;*sNCuif zhfL5cgfB3jnqG$#ZxMb&CfGY{@7cK;0R6jj@Id|7!E>vuq`y*l`Lyn+g7+TT(<9QDP#i#G;>ixI4MJK8^v)*Jt#&gjyIbLC1X!$Fm)aH!PSs;bq zBHu9Mymz2P_Qns*W6s|pKz~TFF)NCp^<_5>JiZ-L0 zv@$&^jE7PGfxG!Topb7;<>v3&TCPM2`atkcZ+w;~$uCiDgw3EN6>p^FTySh|#nw=q z!;&Em)sGBboMK4*b|U>!(Yn84ARwSzUnBYXHvJ_p_0zEXA8AYfqw$xlbgmB;oCO6! z_&D$27b#y*)`&r4Ek$|WQQZR%)3s-=`spvg@$;{aOh3zNppRS+`-*>eseb0Ukp2SG z)MH-FY1+}-%*um|GvL_zt)%Vq9?)Q)x!s*p1ax}4E(O~CXH`>kk@eJKUv~aqTW|p8 zb%3w`t%Gf$_tMwP4W2iqvj08I8~Fd>z*~Glqp5BEseAoqzeKs7|LYX`a}WI=M*4qs z|2l=45w_>dD3H36cv2)ZVM_;X<4*#{4#tYjq|sna~I>@q2X>qNGjRbS25k z(lq~6fR*Rx=g;$VYw2Ix!SzIGvhQf8`YNh_xH}gx3@QEzX{heBGqLE)oy=J zPo7od^M{(M2jJJg2QW#yR(c5)0WIHWc7Pdzf`WeQAkLt+Mb&iCZhfx5mJ(htr@J&W zT_?F48s8dR<#FeQiq#bb$62JEizC%7)quwz2>^XpnHw=;V8l#oIFnQM?n+&5#*~;Q(Vel)_&vRLY2MFlO?=6YpWR>GAvI^c0xvHwZ(_rJJxZTPWXXJm zv%YoC?`1zpeB`PTPH=UmCFni-3MN_3=eE}~922~peCAmm0WbJ`b7iGpYA-Y{DMx;e zD?YOUer}P~JZzT}nggqEq=|`_Uu}@S5t$wUsICPfmtTZ_O{LqP9>3ZJT-F&ronK@< zbop@M#9+P(W&(sp;vZB~DJJ+TuF>KOt3KkGZf&2Ma^?M*PWGi@o7SGq?v<86BhK zg}~WI&A(U39E_uu`? zce?vKO*Vio2cc{IjujC19!teVB_ZGg?@^Ye8K#*T&P*Qg)N8zPv_bFZ;>EMYW(=j= zw`o)gM%h|MIS36H#5@IFqm%Du0>W%qw7 zqO-&&Rb$Cvj;Xnyl^_{S$v+W2l1 z_-^DhZsh-XsO<ZZPY`@bZnX*YUk7fcf>KGx_1?{3qC zyO=^e=s_MiKSAIlS`-(2w$qDxtWL@d#77M5iuQ!nS!M*)TXNVI3=oHZH|-Ao4{n$L-gP4jcywFQBK`i?J@E@EvV#Ubuw-o7z&GrvuAnmqqWx-&yLAAZTZTn~P#{^U1`B_=L zANgpBABO+ywgEuiT^;U1!3Ds;EeZH<=$1!G1(x*GfmcK?)W{xe%3 z@b-Ln9n8}egt(T3)reZ-T_Ti~W)Thf8Tp!5-8drC=&Eb;EA2b)jGt+cOEM@+j8>t3x5E&+@=WPT5RqpSU_uM!aj03aG3u*W@v zx;fj1clt)PmGKfON^~;r3o4@XSCF`)Ho z^KNwT$s-Abhr0FjIg5HaSkSP5MH()EXeZXrUna-}txskDjD^J;n|MgSd6st}))4eY zrdXL~_}BjXlbi2EOLe0RkoQc#ZMh51f*|O-ajFU=@Po}fW*?Z>5ju`Oa4FOM;B!Rp z{~>6aXDgg{N~SD0RO*AfcblxtC^G;~rI&}gO$LvxKgGcKX<7!+ds25SS0_6q=nPa4 z!&Xnzf;3vm`1ySogQF@gwU^@LFA6oXc}|;oIFD0W&8&e|v(QeYnhU3~$sqT2DT6~h zY=^|^S|1n2nyf_l@tQ^GnD)9ZH7d5I5>Qn3N$HGg}ZZ?$uDVRoj%i z!0m`RNZ~FT1FI`TN@V@I+;3y}*EEq3gJ-P*C(d1ZrCTC6#kenof(DoK`z&_{N({hO zZlM96^w0;3R0Z>HUz7TNO;+u-Y&pT0%ZHlEZC_9RK4;Q1@oz>N-!>@A?aoV6nw4_8 zdz~Qz8V}Vz?9L(4giyT_00-uSOyx}4Mbsq69peM2Z*pYdH(bO0ikZ+Ms&-q(&%n#= zZzh9&pI6K*|LWcSId|Fo^IDpsh^VK&?}GVa6g_bKo;suck6{UJBf#C}+!l=)Wz3;O z=zq)dm>=!@K)>jGJA3uI;KL%*iNP()?h|RuxYF4Uuo9e)2<5{{-b)c>dO%-CL_fJ7O zkh5TZpQFBcQt~NH!_l}B_AYgOgQSEn3F2^J1O(sW;EmDLoh0(BJJh>N`$y{q)+ViS zM>#%N3$poCr9)F}m2I;VrD5;TRc8Oz%|`=WF}L5syq-IJ)#LiDO6jr8fQB+IqzH#N zGaCM_;(|YfelqY0AO=3zVyfu&nP*+SyUIp96|B;Rq=(*URGeyCdqbEAlT7i;s)w4} zuB&Y=RjTjC@}W;pK6mE9zXY6QU5?BB6wKehd<(n~ivrozpZlDTT8+jsZHoAOR{gnjbzXghUiRD- zgP)*u{&PA^hVi9{`7xq(2_tB#hcX{KIfBt&{>C`0nPRH{0!=ZHy8ozq%^nky$So7K zS>PGCf5VyeSr}cu;*1H&-z=Xjux&G%FbtHs;@9vStO$G~igvjBkDkD509RG>7=K=e z+!ct-j#Lg9Bb_jr5~NWuvD;1?IvIJJkNp%Wv+K31?^kJ6F1bE=nDPFfn0+w{Q&G5sc$=5@Dzrup*Uh`L$)2tyCP*__Y9MT}#Nx;v5RYcBtP- zRV&h&@8-Nv5Ri}WzUKvf;nN~D^WNcdk|&$-(&W2)aYmQ(F5UgE0oa_3-ciz5%3#ZA zmiK_7GtR2Re7|d0K!^5iA6F8WS)Q8`1Fyj#!dtA3Xa1Kh6I z9PIpR+fEOEO<9Pv6nevMj)?Sd4%82NZ_Dayb|2WMaVZ_6BAY{UJ4(7h#? zc1vn=m+>|utP8TxL+EV{)HHeGU?zq&pPp2P>n(+SX!Oayt-5X*-vm2x^T{*KBiE9| z>Ia{wyPRJJQ=bM^bdiEw_WUyhY0sv=k~pbu6YZ66d+QBNqD{@(KR0N!k*)2v=rhP& zx3e_BeG?DU%lC0o{@SmPdJbG2(G$55CDcpc7j=W*NKcXYznYI+ce~|KusoYH#BO{u zIDK5~mfcV_>ZhZO;x6R$B4#I3H6cUTRcQU8U@KX9ZMINY*KJmPRDI^QB<=KA+AmP` z;{3*Yu-qCMyKI8`>OTA>PYAoQ0K_Dnn0pqWf6#Kb%0GiZ;N?k5uQupIw8-d+f2FcIwSqpq#uTT2iMK@=sNj#i= z*j5Sx*+MFt(sa?9h1>WZ<40@uNvj8KuvbP2#=+)e)mHoSRn5g`b)0$M=Fcu$$9+r} zF{1xD$T#iZ4{1%oD-Mi{$8Y1klDjyqOVv!vAHyc+!(iAha%M@;niENBWu3x$z&EUY z@1yEk>Ul{iIQ6ApSo3oi>ZF$%C;W8Kh8Ssy^%AX<58G7O`CbJ@QVpQzQmp03?gK&9 zaz9FiUoEt}G`B*TMfC0}T`&s?3#FjITBjzL+$J2hEw@icmB&B+#EqS}=Y6yW z9vxWQ)MBWT_Cy}>;g>o3rxtj~t(n(#l+)Inkj?mt*3KU_7rj2w3raYt)Tw%P@szW( zrCXtjvI5z?Yz`%CUMdKFYl70gh6L9-0Fe*u`1Mv0*JJ+^~7g!1Rq$1EEqeK0sK+*2JcG9HK8wp z-bgeLPnwrI({lz)qrEWw(nr-7-3Ke~-|KVXZWC!;55**Xq29$TE zV4G%jJuneRXocQDPJ$$~)TUNAKTDhT-aKcobFXXb!?aWhC&C1Nb;jsQ>bJy=VjSYl!K%~X z^ovR{d0zb0-aNb8y*J>w?@U>2apDI)0HCVmjIYGGFWqU|1F6iTK-ng;Wy|^jaAy@& zbm{GfyA8jsAu9ZK%X=dxyPTP*%jh#9CXY{_aqd>te}XYZ6wyd0?F-;vIwr~gu@?Cd zWPJ^9jmfVpKi5IAH!tk>k8Q3Ien78Vk6s(Sr!PE=5L!hr!7PAQKPs&S@YhmNoX%&4 z*Wkav8~LlOwj`W__ihnYx=0`dgWHZKT~FVa%I$~U04t4I!O}>e4JOezFbV@FyMlYv z_@8J&Ii4!-?d~o1{tgN-f<#UVme*%Hiw@eOXUA6?Y0{UT(%Tz>Vh ze>QQ8JyN|048jn=ux7{*qWL4b-d^Gp%JAc}Uq{v5mVI3;*Ngiy&0Ju6m($8~pMa~m zs5?gT=y|%;x@a{7-mP@Glm6bp`LKYOdGU2Z1x9S4I&*iwz}_Vv(7r@h=Kx$AQ@p!b zU#~5^20C>_p-?{fvoe?cq(>}O--CWFk5NOen%_uN9BD2WYD!9BDli^-NWZMylfv1c zT~r~L+@DABqC~oEML+P*NU)o9FN=6+n^c*Gg-#=S8DD-z#KmjX8%~_xEgH=&q;&EqOMF{9P5vbubJ*% zRE(mLnvz`tY_eTP+p@u)%5;-RiMDd&Z)m{os#oYJ`al%hi9e2NFI(GmT}wZY=>wEb z0HIrzxa)5UF7Ypz@nFhBrHm{?1g8c~y`j`8C%;!#eSK!~FIOd&vSgP0zTA&`;Tc)X zOZy)HPrX|#tVfOi1#zo`|L@@SznA+T5O-OOSV{MP`i!aKoSf@(aYCdrVpz?v>?@c* zcMx4n@c0K-^4+ZmZIs2olkeLuAO^h*10^wG7~0wN5(Ba}7jI8D`(3*Kffs+nm%40G z`r^Nd`w^iX;H~S^Omu5|Q{tQp8?Z`QpCmvg7`ZS%_YQr6aRlC7?nBmH*1J&1^W8Dv z-H&UU#_Ds9*SwRvw9pg{lu6YRxOU0qHqATF__LAuT&)H8-DDn`SK=0kM&3e1t~4>+ zt?9(c;sT!l(D&%!C)p6RBh975w>~i9J-;0@U^(}5$Wjv*YTRIC-5+ZYk&*A!Kjoo* z%K}N&Gd+}1F^7Nc@w+ih1$nnIFUMZR@0vRed?v=|cx|CQ8kkHc48A!rm73ckI1CNf zILPjt{+dGkxU%XhoR*&ZNAZsw+g$W@hZ z#Ms`kJwEuatxDc$=5<$lCj24~30^o$3XJm0hkJDm@N{M%F;VCXY7_Kjy#=`cXQDbQ z%IBVlkY>kWpCu2ReD@MCX?lyR4s|JxH%USbcX2KA9#T@o$W8a!t?@_qo-hBlC;UE4M@ED^)y^RubXPmUI|%|_R~F3Yw< zt1!4;cAXntPJqOq0Mqy>p5Wo2DQ|N)=AGoBFVtn+_TBa2rJ-r+TKyqVwrL-i=ct?` zd(*`YsE~>5P6Gt3%gBNd1_eEn>tD~=xRF>pe?~Cuo%$&N@bz78U2R9)<=`?6^lokc z;brVHVcTFEtv7loy&HVEqxo$#`%(w}4RYI$_C7LJ1sKZ(l>oR=KG(;)jk8YEb1A4G zDdl0yUDb$;ZvLQ{j#97PGvB{z{%b!O8THWpe8m4qT9(E~xkxFQZd()S>yW3G-AqkE zXZ7k`aYUU|z#eGhEA4kp&m!g+q@t)JiZ7Bp~G+dgp9b8Wh?D zGx~c&*5;>}f0%UvK5D)Y-iz7CKn+HKB^Z70)60@lO$IuS;}?ipOTJ0^F>k|-w1q(; zVhq&i92OY7(Q5vR7yN*0@)GE35;8W@>Ni<)pTj#fvM!|V9RlFs-eN^|M@cFH-Z69PTYi71B=r6`ZCA12m5 z%z+H}dnQw~n0c@72yg%alKM_V?pMNjNCuZ3%WJ0k0`yz|BfwqP*Q6B96S%)GJU*b6 zbpgbTh_r6?=RyBMwxjZpe`sb0UrbluLkHj`N$V;<^2z+gPVbqrEd+BMz&VqI+nfr7 zFghZ9q_9!aLCUM0M@vdWWyxlD986bvp-o5rZy8*7l)E#KMAh4>M_3i}-zEbl0v1Q^ zN%LSth#ch%U<$v$K4`hJ8thhGn*G_!$%ZB?O{SZtdk(!CR8ci?c>b*_IN##vUn5qN zW9-$?oy&PDdZX*@R?!bT`a55F?Kc?-eZRv$y&HSLQ1(8 zMB|X`0h4sK>(#lsGsmT?ckQy(eT}>8R|Tp&Fma~66Xg8&?va)>pbVKrfpf3mJ{9zl zxiQB4HSo~Mv&Pcts9!;5$E_Hni*86zOS>Tl5lGbmppT`Te4Ajrp;!Lxt^ziE$Ktb127zl|RuKoT48G7N03Rf-gN6kooWH(tzOK-h)a>rSG5WmYdj{DQi2Z(J)NL(BBABV*=h&acA~S5q@1MyAouR_d z#H;In(d6rcz`@NXl24Wa-e~8U^G^+bT?~`t@_iZV<$gCBvN31ET z$-W;b$$6^_-dDav3IzfdY>t^4FjMfR*8q@vVisIK7%TvG8Pq^$E*6a5TLWFRI0h1I zm1t28{R%xlnU7u>S$BG(5+`zfLgC{+BwjVFy`hA=Hw_zf@gW)c4@y7!YXdU>RK*blvW1JnDr%KxWL{13~>*TFNOMV%E) zhR1|{JwM4Wt8B7W(7m&wI(oWBzV5DSfce_!ApqQ}WnV8jhqfiYJ3MNpZWBvSjAxVC zN;Qz(%IqMW)UH5iqn>>#+E{YCOw2p?@r@-q`UK}l>pnbbF&|Y8%t%V{o}_ucw!#lh zoKx=*A$K6KF0ken0Oi8&gl`MPvo06_s%D9A+KPi?#m~XD+OXOx!zzeUt61hCkb+DD zup2BXA^lD&3%0zDF62J)#5!vmNcGc@TcZQ*+qR<>(20a5n+Ofmmcgj%L6!#x+rF79 z#?~Zw3n~zlD{>q(Ti@Nh3FByGTyZwlY)81SF__Vf!|Uy%gQH$Fp9C zx92K7k_<+=9jfDXE0{hU=OTIMpzU7oq+Je(l~LWNQgL(bYHXOIx(l_6a|yB?MbkQw z75fUCyzFyZwXcVG{&~FBoC}@C0g2h`3~lbGs|uIuRfZ6^pTV1Z#_jY0Nq%u1MOEh$ ze$NOk9}N4;b(gMPwqK^KN&Y$Ufkl+phJeJgDk%L&JpA>$(-A6F-!6_WK15si3SzLn zhuuA|gcS8*VZEmLuK;%T^gcwz{_lmU|L0Qo0c;Rw%Mtthb*c*2!BX?Df zbIwh$smoE5>dA(0p6n+3KHsTDYc(eTPWbry%6qP2^90;#(a;tTlf30M@K-Z=M}7{Z z0>wqf3>g{ctiv+Bx8i9mwVujRA?r`DyBXxer`9ny8*r zT@QTYrk`UJaP46ui}q4=`-s!kFk#-jvGlixX^yug8zF)eVvq{PiX>`a6cyDypbG_U zYOK!5YeP2Yk1q~O%hJ8nH{bzDi3s80S3L4RHAcP=(K<-b5<28+^mI6diZ75Mp*XKg zy+ZW{0|m;W7GFtD!ze;?%Nbfsl{PIPsT^RU-#TwzspwnssoUZX$+X{a@Opl>OSAcU zHAgtzVh|-WI!a&mcaR3e*hP0lHY7@^r3v$XAE5rk;Ty^o!TZ8hNSewClGVO@h~!S6j zODIAnQ>k~JeTB^*Y;$uy2EOHz+G)uM+EBV6)wVaCa(QK}WigGyjuAo88ay`ao{-w4 z`3)d1Gjy@T3Q@|ZLGp=+ZiZGeiM&%f%ThKzjeOwa&5;8Z1#r>t$oeEcN_gn3z5)8V zO6fHI;GUicaJ4t0wqy?@tEZ9;q6rt4)TGwK^Z<>>x z&Tk0qGQsH7sNI!~g{YLahx_v4ca2+wQAG2_IAVI0<*M|g3jN*< z_MZ;P)QI+Wp$Z*1un@oM2ZcmX#stY?CmMSqEuPs2&$Di}VTulCRDB0N=t4cro4&pf z!ONAh-t?4=R59N}y`IQ-qsUf|$4NzxWAjZS>Iq2qMhX%+B_9Ui<9SrlDv@t9XtOh_ zzO6ig|1?<5jTkC>)XGmZc8BIvO}Z=FvoeThGL?KcP?QL>B#7~SnOCYf4QXB@BhUXq zY4Gtggz@=N+2J&~tmAkR!ri&X>1AlykQqdLG6Lo=uME!rt}RV93%we_-_0jn(<^gU(ydfXKyMC?5FbjKK9@UANaEL?HcyMa;Jp!fO}dvU66MLWjTi4Cd zk=pZ+GoqHjPJt}WJ=BJYK=bwU<28Wc~3{$2lTzNMm+MJAw@7+ybSBJ$hd@X z9o_ZI(4lZA8LO{vL-)~+=*x394HAnetAQWNf~~I-qB>a0IRJYd;Fk}uJ(%d%331JZ zr77!<4e3GhIxO4{FWi*1xTmZiqhCO6Dtig~Heb#E;s&P)GKwBd?TinL&8mxMKMe(c zdHRKmu3~S;{I#>BN?7mR&>L#`q1q^hc}vIc;G4;lS}4g_o^g00uZKl}ZOdCKnl}Uj zyIlUbGyF?5o}qLSsSavGf1s1S4+|%b>4&ok$aah3lsW#=&jvge9d$V!8+5iRyD8pl zKHba+kafS(`YJ;{`Qc%KvXQaP}7_LGxj)dtre&QwLjVPY9@nn9&bpQHpRl~mGn+-mCR z`&*Sd8F#mD;0E+#j7NDwO3=tK`j>GeHtfub*{|QF;*%*gRN}V2~Xx*HOvl z!98DueW?-YJL{w}4epe~Nph7c)x?33qs^*Qn`@5{yGkHWU z(e@4$-j(+SHR3I0>z}wCzlV4B{0u|Duk_l@DVtj|jpXfl!V@e}5&-TRcQ1X?cKE0LicBFEw?%xI|=#Kh4l z9vKnDwZ1tU9jd~fadSI8S`c-m3k9@<567>mRy<6G556KDE`1p)Nw(DiU_@zGxYH(x z)Vpfoihk^{$c}2+H0iu+_nFdfxxCLb#jj zdHS1hUnD!=w*+OAZI9kzV)in4jyqrR zldt5eu1I4Fs15JG1b9d|=`j9gSH{QG7_Iv)DZ52*`tVV3X!s+pp_^VYhL_xftQnQqt-Bcj5yFA@A6c_CYFw+uL9^|*D7FOA9@4ocpJz+zwlX*QjR_Fw$z*4hJO&3 zvSgk_Lp3BON3Ca~{Miyu!Qr1nz>NE%X}_Di?sqd57wU5~k7LLyJb#?6tH)an z%y#hSn4R=+ttzY-H*&^9FIG|ZhzInydXn-ug{h9u{gn=VA&C#`Lx04n=fcO%iMRr_ zY=}Qx&40gk2uoqh)o8o?G@i~|`0|ZLM(giKD!6ix&ua*6BIz(MZYJLMf3@Wl#nXz) zW^Ct<^p8(BUKtv}s!T!(mMmsVinoQ6H7o7i%gDy2&wkx_y%67?mtXLA4Ii(V_jW9) zbd)9vCr@Ie8fh^++5`C&P_1+cd*Y%Zf6h=4L7eXY?c<-%iTup~Q>Ux#ijyf*>vjN1 z$GN)`3nk#*9vze-pYYnE$a3EFxIaes1Zzdn{&bdKH~wB_VrxtlP*@(FYWu8c+i)4z zTc<#ujV>zsQGk+ljT1j?4Ld%72xrx&3m;bvGbfqMAX>)yG6jnEy|Y}p>7U{&APP;) zcV4}Yy*Os2xLfiXloj?+*n6TdSL^icj7dy|HhkOK<_y+4ZOBO9Is zOTHam!R#> znlxX??>-1hmGe@8>l7A$r+7GtgEeP+2>*KWOr7b3s7y*UnpiWgSz`Ej)EMq0;&tqO zowy2;6iocvC_Rik@nP66^@(?8+(T4jZtjw@@QRBCT~*PO>OYE`O2W&^11@;5Jc+7a z6yjbud*(a?P;MM1SbaXL`82K|Qhy;`Gx~QO?-h;Q4m z9Q~!lr~t_lg>?_luL^9)Y5|{ylx^POPWVkWTg=bVlL?Wc{peS{{mSF~^YhJ4K_`V~ z_EpZuGo62QQGCP-{@8J#V>N>)LDaWlz3}S@bxE@aTWB+?U<1pV&cqOG3+!im-nhyt z54>J%LP_|)TsUYzR8jB409-MPQ1@`@la<6MgCG$Fd2)S`<#w!OrFEJzlM}&ArvM>l zQ*q2@dnIJ$osjeXlLboQcYi!0e8N`Dh<_^*vy9CNDi(8=cj0zp4{fq~?Jz%I-K;rh~+=%6tf-vBpui|)GR=`f2zz=M#) zDYSOltMk%v$KHx-q06vhaG0-?{#!^?i1KmuPldf_GnYsFj}IQF5-dMX!tB% zHdzd;+%)|#3Q6qojWyBn^v=PaF*AC1ylP>>jy=jaY?Q6_CNt>}bMl=3OU_!+gF?H+ z4S3#>-$OpQE>^M{^ROT3gf^}g*?2zrF^i7upITO-YKsDKavU~+5$ybdxQ(W_R26SP zVg@5OZ{6nx9to9EX1?~zM}1-f1LMXz{ygJli6!x^S)E>y91_sEm*?*|?0c&9`Atnx z@bM2jSlZlIy~GPMf>H;(OOp1SlI>FVov!a(B{2m8w9!Zi2Ur0gF2TN-ESW{GEhy88 z7TV;vdlhp#`$^=4MqUgXW%B(AQ$3bz<5kYcQMt9$LEEwJek*x$*uv2g=O25I(*f7` zh!%qNS5mA`Jp%**@kIm`ff^4vTtz89WzXUOPqy~~vlhjlcN8fb^&JU-snpnoou@Gd zSp_&fMoTi1UN?Ch#SSjG94z{q!xOqW&Q+E=oxYdp0meNZ2HyO|72?9N6y_DrD)?0Q zX|H-|zfd&IWs5G!eEfkK9Ygue4c6=sSqKka7kawVV(ivlrd=LrED>vR0cDgRS zAti3>*2a>4;T3e9GUN*W`r0?))Ad|z+sSs;Hp^#q&iY%~b|dCwLUc2@D%gHw#xQ-g zf{He!uwmvUBS82|#&@TOo(Enrrq82aCCVnkS0g1zh3v$Z!&|-iUXh9Gb)EreB>RM#-5{o`-de5MMnU zDn6)zjjw43NITexX7|vj?^gHt7--+RB2Bzu6KRv0yqG4Q!Rw{7)<>5K8iLIeDyllC zBaUBGGhgJoF_4|7Gsc?fui(|&g?8}!c-}H@ee#|6-)(Ff@rD^K@ z757%s?=-6zW>kNS8vy%sixyJ={MRrnVg5O3?5$1IZN2yYPBU z?M1>9D|0_$CK#~5flKIwNO?RHHT_9ZhN~CM}Sy5eMbzkIu=||cuz|rp)RG_>SGqq#ocKsXkKDbp3Jp9!8PLUu)A=uJ7;>t z1W-tO*-VHq%RKWONP>5ZTd}8zQERP6nl3fv6hVB9AOFDiairY`OE$(^Wz0HfL82~Q zCRNM#60B~CRi6D^i(ps0tB$O`?~^m~?f7$b*f!Ff9qTrVZgP zr`a1I)w@imxW`X^?|ZHd2b3seHRg7XD^%cEe(XG14Y(-D<vL_=-{XB)npTU5*lBg5LzjhW{s7oFQQA%c5mQ?ZwWu# zC`;Gt-vF7h-9`3ueWq0eFKTJqsSz`-FU}Ys%W*Ws*sXqY+ zou8q}IL9;sj|?Xak=cnND0{jrcU=Byekuo{o4e0!1|v_AHsK}`E7nzO9q{KX!L3^2 zIlD9b$Lk46^^dNZqF6-}#k318T&=G_XRxTf>loqZ;w~OP)tCIT2(MJ}xTlxBz7%^= zi~n80m$&j2Rf+zP-8H|!`qP}OtU8Zb;k?KZo7Ps1^f`u4D7F^Z?57LIDG3ThyDiqQ zIZT}%VK3f|f2#73z_w{)Y}L>oDCNsp#CrW$Ww&KCbV~nBc(qO_uO}SGJc8O37p0vdl99eEBpV zA}D?u>i=5kbFr@E`t^nFcbp1aTQ~e8)E&h?xwW)M70!|~aX&wGbTgl=l2aPCC9MNf zjpP}o>;?ieee0T4E@VkWmJR(s>oYk<_-Ir15>wD`28H>#keZrQbDJLx-f?$jfUk!8JfhnZDV|mn3Mov7RpPy8U zam)BCTuY)e-G4&U7IGNs^MUm^l7o2mSNNi{tg8zw0a|CeIvyZc;oT%bbVo`Hx@(MD z>o-iSwPK6!UJ?RGly1VSlD0BFqDZ{yg*)j+W|V2N z6tB#=o6%@M1Uxb-c`xJ7Mj?8u9UbaMksHyP*ws1`e~iPTx~0Q&&a05tj{jkP#TKLu z8LlYX-QT`oG9lU`Kl<3x46s^Rrs4OiPv7-YUlti^6f-g~73;q-bB+A%qkTyIrVC2K zN&t(GuH>h-ai`SD!R=N3j5vc%y(CvIrkhmlcIaz0q;`?czcpy-rC&Xde>FDU}o>qe{5#NE9ZPK7ey)0|?0 zY~lo+O|lfYy8k_67_?iIj>8#iCWPSEU%czt=Z0Zb#Rw`rqa>7M*)>+rOn#yV7x)9OBfSW)oAC`*$x_yFuEU#%jkl)2Q}}e}`(QSP~x)EuUb|suro7WwD`zZ{#%SBi#q?%f|$#)=O zcJ~OQ^=wLC?G$n&Y8ir(VCC;}){Gk5SKGH3d&A2W(X5#+XURKfz@OF^TFuqIGeOkR z%^AQOox+8Mh)mdpByKqx6%-YR$1=k zMowp6)t;?Tp^zw=H;zuqeWe!CNklYu1n1O!W%A1Af4w5>A%@OIN9C1Dh91u^_4XTI zQ2DFVv5rZMttvaWV1vDmd9D-lkCJazH)tyHo@-Z5!k6G_Zs z@wV9yU5Iavaoiq$-Mj~BNTsqOP0J>A&o+A$7H4eMi#J8B^(xNuQF7+FnR>ND6=4^VdgK@0doy@3YC>qK z%1c8yR%)m`+rE^Hj!Jku?aylYg7`Ivoojs>xPoc+JAPB(#-8Jc|JC@aqmJI~m(z!;c-`_AbUw=wz7CiMSvwFbbP)7bng%Tu1XgDsNARbUOT-DaSnZv zHA#Sd{Hc+ll>@I+Djd=^v2qq|ZGOt%M8$yo=DN2BhX%pR`S~wbl~_{N4GqFWU8#!Q zo;)+nTJcD{kMFpQ`(C|fr7wI~D;v(B*kN##I)+Cew`;Gz8Pn zX3!8wLM2%2-l$bT@{F-{G_B%BDa@4jbL0}ne%Y)C8#1HDrgq-!9?+f~?osMnI?!q1R-<7{s-Qv&&A7tn6!Y;^P5I|yzH0_S+(^W> z1#CNKu*Q+mq}W(I%n~6WaGR+}pfI*P!kBt=B2mTVqVYeEjU=_h{d}Y2;Qg ziTWo35)UkD$P=wMzU@(1p_%f$TW6Z{HA~(5q%(RL&J_FR-fOQU)1=F+ns1=Fy%W{Mp^QA zPP$a+k>7v{t!9eUPCj2?P7)MIPbqj=GEPn~Zj8B>CzJqM@u$2nTGV{Wsp<>=Jf12I z^Z2^^t_2-3Yx^^1nD{~P`e$Puf3_&vjvV|UWvYCZYVVY+pe30SfPSzmQ?&>9J0Kt9 zP`@Szhp6_u4>k@APCCeDzC8_;G4nn3XaS&1=1xCyDbnrjT#ct<|BA1 zrAb;wj+hlu?%hCrh(Z+qJZ)6RkJ+TYXOuS=lX?}%h(#%8ev}>S`dI6NKPqzjBtjqE zZqX;0&);r( z*e@iy`Ix>`iY^MG$NZpb_bVrXKoDG?XxZlWYBZuJ(bBUnt=IFHvwU2SuF61|w)U;y z%pvtS#_$2eBf|`xhS(CJEfG%Ur1T)!V6SNV8cWYSh1t7}D=nTuLw1xe29lWcy%XZ! z2oQbDH>7W%v{KaNLWdhq*V$YhC+JTa7y2cHj{sOhT}9q&DORRabf8@O!1+s=+PYL2%I*mEsO!V~{&xm>AJD4wQx zj6VHg(aeHm;R~=N=^gGlebwltsIMT8O<7_TBDX(bB+|~ckOW2eqKMdyUPae7EEn4a&eCV}hPsd*ZX9swFx;Y$@oN-z{1@C4FP~?;+m&#_|0wHyI zJ8Uty{Yf8h#M3Kz%hZK&-Ic2hs1YzNv84pmxLx-lt58d*jBL-Z5+Os`! zuNH6YP*NPO;0Tr=-rl2ZE65U8c3L?~^A$)fx#b3UJj`^rP=4X(tydrFY9XmZMut-k zy(X&d|a-;gq7y5HSG}wkias4f82hg298Te>8t5QMnQf9*@oRr7+MwIOt&Y?AX!G7>UW)k@SvE~lWUrUHQAPx= zun?>Xzql2cN+F7NY;qi4rsIw~%LIHgp4=Pn#++&e7g1=A_HV-j3COZGK_wS9xfMtrSCDILeIwf9a{{2;!)v2ZRH1I#@=<(ucs#+ zv#ON*)%!)KywOf?Oo0g7v~(xvpZFR2Md~g!u8h@h;6!x>2PFZ{A$4*Z-$^>VGdfuSj&JsES&5LTQLX9f|h1b#=lu&Ug8%WjTv-XEivn1V7-06 z=xBLK8=@D6$)Ba?6-$YvN4Mz1dWBMszpxf2rmjH(yA)7qGlJ~{@ElvygO=~XE{9;9j-B_1GyUUj z{(0H?{C@Af&wewrf8=>P``IO^orc{U71vpR=dt(09gpM@+1gWrXXW+V?@}(@2~W|W z^Lvowu#WvNpXUZ2pe}baxyY7)FU}XtO`Y4nxDcz8-pQmrS64!k4q?y9O z{JVlUsdV|(m=#l67mOHN(WIH=seRRA4E-^2RZ~lbo&OHMqX*=ZTvOK1*?A-0S6Ocp z{@W}nyDTM`5$!D8$4Og}rtcF~-(WFiE zOei^#?$sz|Oga4IaPuKe*bPBj!;84OL9Myrj#6VHIk=+BFk#_HExz!vo?(`LH?C;h zVaepeG2HSFA97G$iWT|=#8{-3tPM>Y<+OcGJhvm+$A0LZmb?iAbXPLTe^fm1%vtq2Gf=?hnjn+;dVLQ>l+RnNm7fb3_@pf75uRf}JU3{&H*Su+qg-E%LkwNM;MFC9$kJ}y{{zj z53kP~lBlZHC)UOc;U^S#uCZQLZeLJS=3{;FblA!4Bc1vS)koGDYaY9l)<$N!%n zM65ho=lUeBXmU7FlxMrAG-HwJ>+sBn)e)*cQDIG0@-?l zgJvwwxY8DVt9{Iwm#47S>-(>EoV2)NFq#q??H9FQ6Bf6n=g0s5hhtWn%C4na$g&`Tcv+;_UBdA6u;l7+x`BeAM2JoyS88w_kI~Z?r^ns5Q3le3 zP?0=_clU%yXy9zbmsAsoGmFg>vt8JT3&z46om^dA9S{y1W20;iigB2jEAGg_*=(fm zVt826Bd%H!jtBGo`lh45@fb@fN`<4SR;y)c9a*GWiem2W?%i!T941XLHL*%cq+=>I zlY5)I>BA>9Vzo>~$w(#A-B+}PjHY-DM)yShSKq!As{VU*Jg$^%J>EIjsK%AGKrjrEnk1@)D4(QZ3$ z+3I-|-5Y)9{(o7Ni2Eg~qSf-AXC)D%gq%wL(OB$P^+efvS{J zE;?}Yz3b~;HJueYrf7do-z>fV`F@bWfcFp5zPBly1V}E_1$6-#0D@tH;zV1jBSeT|fqaV3;nb3&;Qv z4ATX50T}>-VY;9$AOk=!Oc&GzWB>?$Z@Mf8PJR$d`nZRdKG(6149KESg#^#wdkdx~ zKB@M$G(#--6&SwJ=$Y<%Y_w$a!D+8+tlPXsG95#<@h=|P`ejjEO@7n-8ar#ZSDw0j xWt1^R_}r8?VSHyqQi8R07q{Hg+&p38#N4G5 - +
-
-
+
+
+
- +
+
+
+ diff --git a/js/frameSheetModel.js b/js/frameSheetModel.js new file mode 100644 index 00000000..5ea52a0d --- /dev/null +++ b/js/frameSheetModel.js @@ -0,0 +1,76 @@ + +var FrameSheetModel = (function() { + + var inst; + var frames = []; + var width; + var height; + + var createEmptyFrame_ = function() { + var emptyFrame = new Array(width); + for (var columnIndex=0; columnIndex < width; columnIndex++) { + emptyFrame[columnIndex] = new Array(height); + } + return emptyFrame; + }; + + return { + validate: function() { + return true; // I'm always right dude + }, + + // Could be use to pass around model using long GET param (good enough for simple models) and + // do some temporary locastorage + serialize: function() { + throw "FrameSheet.serialize Not implemented" + }, + + addEmptyFrame: function() { + frames.push(createEmptyFrame_()); + }, + + getFrameCount: function() { + return frames.length; + }, + + getFrameByIndex: function(index) { + if (isNaN(index)) { + throw "Bad argument value for getFrameByIndex method: <" + index + ">" + } else if (index < 0 || index > frames.length) { + throw "Out of bound index for frameSheet object." + } + + return frames[index]; + }, + + removeFrameByIndex: function(index) { + if(index < 0 || index > inst.getFrameCount()) { + throw "Bad index value for removeFrameByIndex."; + } + frames.splice(index, 1); + }, + + duplicateFrameByIndex: function(frameToDuplicateIndex) { + var frame = inst.getFrameByIndex(frameToDuplicateIndex); + var clonedFrame = []; + for(var i=0, l=frame.length; i 1) { + if(tileNumber > 0 || frameSheet.getFrameCount() > 1) { var canvasPreviewDeleteAction = document.createElement("button"); canvasPreviewDeleteAction.className = "tile-action" canvasPreviewDeleteAction.innerHTML = "del" - canvasPreviewDeleteAction.setAttribute('onclick', 'piskel.removeFrame('+ tileNumber +')'); - preview.appendChild(canvasPreviewDeleteAction); + canvasPreviewDeleteAction.addEventListener('click', function(evt) { + frameSheet.removeFrameByIndex(tileNumber); + animIndex = 0; + piskel.createPreviews(); + }); + previewTileRoot.appendChild(canvasPreviewDeleteAction); } - return preview; + return previewTileRoot; }, refreshAnimatedPreview : function () { - var context = $('animated-preview').getContext('2d'); - // erase canvas, verify proper way - context.fillStyle = "white"; - context.fillRect(0, 0, 256, 256); - - context.drawImage(frames[animIndex++], 0, 0, 320, 320, 0, 0 , 256, 256); - if (animIndex == frames.length) { + piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi); + animIndex++; + if (animIndex == frameSheet.getFrameCount()) { animIndex = 0; } }, - setFrame : function (frameIndex) { - index = frameIndex; - $('canvas-container').innerHTML = ""; - $('canvas-container').appendChild(this.getCurrentCanvas()); - this.createPreviews(); - }, + removeFrame: function(frameIndex) { + frameSheet.removeFrameByIndex(frameIndex); - removeFrame: function(frameIndex) { - index = frameIndex - 1 < 0 ? 0 : frameIndex - 1; - animIndex = 0; - frames.splice(frameIndex, 1); - $('canvas-container').innerHTML = ""; - $('canvas-container').appendChild(this.getCurrentCanvas()); - this.createPreviews(); + this.setActiveFrameAndRedraw(frameIndex - 1); }, duplicateFrame: function(frameIndex) { - index = frameIndex + 1; - animIndex = 0; - var duplicateCanvas = frames[frameIndex].cloneNode(true); - // Copy canvas content: - var context = duplicateCanvas.getContext('2d'); - context.drawImage(frames[frameIndex], 0, 0); + frameSheet.duplicateFrameByIndex(frameIndex); - // Insert cloned node into frame collection: - frames.splice(frameIndex + 1, 0, duplicateCanvas); - $('canvas-container').innerHTML = ""; - $('canvas-container').appendChild(this.getCurrentCanvas()); - this.createPreviews(); + this.setActiveFrameAndRedraw(frameIndex + 1); }, updateCursorInfo : function (event) { @@ -155,26 +243,73 @@ onCanvasMousedown : function (event) { isClicked = true; - button = event.button; var coords = this.getRelativeCoordinates(event.clientX, event.clientY); - this.drawAt(coords.x, coords.y); + if(event.button == 0) { + this.drawAt(coords.x, coords.y, penColor); + } else { + // Right click used to delete. + isRightClicked = true; + this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR); + } + }, + + onCanvasMousemove : function (event) { + //this.updateCursorInfo(event); + if (isClicked) { + var coords = this.getRelativeCoordinates(event.clientX, event.clientY); + if(isRightClicked) { + this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR); + } else { + this.drawAt(coords.x, coords.y, penColor); + } + } }, onCanvasMouseup : function (event) { + if(isClicked || isRightClicked) { + // A mouse button was clicked on the drawing canvas before this mouseup event, + // the user was probably drawing on the canvas. + // Note: The mousemove movement (and the mouseup) may end up outside + // of the drawing canvas. + this.createPreviews(); + } isClicked = false; + isRightClicked = false; }, - drawAt : function (x, y) { - if (x < 0 || y < 0 || x > 320 || y > 320) return; - var context = this.getCurrentCanvas().getContext('2d'); - if (button == 0) { - context.fillStyle = "black"; - } else { - context.fillStyle = "white"; - } + drawAt : function (x, y, color) { + var pixelWidthIndex = (x - x%drawingCanvasDpi) / 10; + var pixelHeightIndex = (y - y%drawingCanvasDpi) / 10; + + // Update model: + var currentFrame = frameSheet.getFrameByIndex(this.getActiveFrameIndex()); + + // TODO: make a better accessor for pixel state update: + // TODO: Make pen color dynamic: + currentFrame[pixelWidthIndex][pixelHeightIndex] = color; + + // Update view: + // TODO: Create a per pixel update function for perf ? + this.drawFrameToCanvas(currentFrame, drawingAreaCanvas, drawingCanvasDpi); + }, - context.fillRect(x - x%brushSize, y - y%brushSize, brushSize, brushSize); - this.createPreviews(); + // TODO: move that to a FrameRenderer (/w cache) ? + drawFrameToCanvas: function(frame, canvasElement, dpi) { + var pixelColor, context = canvasElement.getContext('2d'); + for(var col = 0, num_col = frame.length; col < num_col; col++) { + for(var row = 0, num_row = frame[col].length; row < num_row; row++) { + pixelColor = frame[col][row]; + + if(pixelColor == undefined || pixelColor == TRANSPARENT_COLOR) { + context.clearRect(col * dpi, row * dpi, dpi, dpi); + } else { + context.fillStyle = pixelColor; + context.fillRect(col * dpi, row * dpi, dpi, dpi); + } + + + } + } }, onCanvasContextMenu : function (event) { @@ -183,35 +318,13 @@ event.cancelBubble = true; return false; }, + getRelativeCoordinates : function (x, y) { - var canvas = this.getCurrentCanvas(); - var canvasRect = canvas.getBoundingClientRect(); + var canvasRect = drawingAreaCanvas.getBoundingClientRect(); return { x : x - canvasRect.left, y : y - canvasRect.top } - }, - - addFrame : function () { - var canvas = document.createElement("canvas"); - canvas.setAttribute('width', '320'); - canvas.setAttribute('height', '320'); - canvas.setAttribute('onmousemove', 'piskel.onCanvasMousemove(arguments[0])'); - canvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(arguments[0])'); - //canvas.setAttribute('onclick', 'piskel.onCanvasClick(arguments[0])'); - var context = canvas.getContext('2d'); - - context.fillStyle = "white"; - context.fillRect(0, 0, 320, 320); - - if(frames[index]) { //is a valid canvas - context.drawImage(frames[index], 0, 0, 320, 320, 0, 0 , 320, 320); - } - - // TODO: We should probably store some metadata or enhance a domain object instead - // of the rendered view ? It will allow to decouple view and model and clean a bunch of code above. - frames.push(canvas); - this.setFrame(frames.length - 1); } };