MD3W5IRAC6UQALQE4LJC52VQNDO3I3HXF3XE2XHDABXBYJBUVAXQC
WI7R44TDJJBG3EOC675KWQ6L4TDARNSDO55BNL2ZHZJK4TXYDAWAC
2WGRQI5E4VI4CLRNJTSW57AUE7ZLA7IVOYDATXJXQ4HNNMFACI7QC
JZKEIKO6ZVYTDPQ4SDKIVHJTW3ENPNVJOQGCU4F5WSBU5IYLXE5AC
E5FYDACSQNKJG4USM52I6C4KTN3U4Z47C4TK4QYC6RF2FFCZCYCAC
UN7L3DNN4Y3BCIA7SM6GYYWQG3OKVCH7ADPKAVF33E6QXY2HEJMQC
WIPDCP4UL22KMTX4P54WLTBFJU442DUXPMC36V6QDMXJY5ZYXPIQC
F4QQIBEH24YBBOCBWZ3L7MR7AG7QNTYWQ76W5RDHKDALDKE757VQC
SBS2F7GRG4VYLB7DP2W6LPN5UEX6TK5DZFGCM2P4IVDUIUJRKJ7QC
C4VTBATAPF4ZE7XE7WOUZTMKED6DEOKXRKTOIN72WAM33EHFBUQAC
FM7UDV2GUXXDVRE5WPVPRO6XQ3PYABYIL7IN77YOJW2QIBPCO6JAC
225JKBBO6VPCBBVN67LB3STET4JE7TMAJTFLGFESUD4SRDT4Q5BAC
3OTESDW65UJ2W5RIXA6FNKRSD7TBCZTCCCEAYOQMEVGYZ6RCU34QC
FNJF2FMQJPQDVLHDZTVMUX5R3LGRKLGSNUPTHUT2WZY3RKVSNRIQC
SPNMXTYRSNPNQJNBTYDZSHYDZVZRPM4LI5QX7GR2TLTC6SPJX4DAC
IFTYOERMW7P3I24WISZN35X3GWJ5MSMRYDRBK3L52GCZTPP3CWZQC
KJQ5FEYVVQJTQ5YKHK77CQ7QWVUPW5LO3K7PK2CPJ447WD5UYUVAC
QJB4UHNV45ASBOVBUH4JDMFZVMGS7LTKSQHICRD77SRZMLFVKNTAC
JIK7ZRYIWGJRXEHVI2O3HF2P7IZGJTWE6E2J5YHQQTMI72MFXNTAC
EAEGCJV5JOW46KCZKKPBFKZ4Z3SDB3X4R7TLNXFWCIQN5UCNSXFQC
7QQXO4YY7RNTRZIPNTPNLLUWYEVBKDPSGHKPB2R6K42BWQOZLL7QC
7PZ4CQFVYUMSJKVCNM75VKK5JCUYU6ICHWPZXXIC3S63YJVFCP5QC
AMOPICKVRHMQERJLFPMAAEBV7TL5QACGGSBJWRCMV5R5O3KDVETAC
5STHSG4UB2SC4EZWOQHPQM43BLC4X2EJTNSSYRF35XEYVMTOID5AC
PNBKVYZ4ANUAZNQN6KEWYNDF7552ROZPNAPRJE7Q6O7ZZJMJ3S3QC
6YWPSNUKPQGSG6NYXTUCYFDUS2WBFLWVLS2ZZQYKF45J5HLZHTGAC
R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC
K2X6G75Z6XBC4DVIRWC5HC7XA3A2SKOM3MWSQTCFEYWIJL7LME2QC
7CLGG7J277QZGMNOVFEXBX6DRETCVK7GH66HJ7BYOBMPHTJCDFMAC
LNUHQOGHIOFGJXNGA3DZLYEASLYYDGLN2I3EDZY5ANASQAHCG3YQC
LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC
2JLVAYHBQGIYFYLPYP5MC7V3DGTSUKLKTFSAIDG4XZFWVDU33SNQC
TGZAJUEFRK3NTCDMPIIG7U2TGLDHK4U3JDNFAYX7NHXTJYBYEZIAC
UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC
APYPFFS3G6TDEUMIHQGMDBJNRNDTCNTPKI5M2AFACJ73P725XQRQC
OWK3U6VDHPZM6Q6MB7PVFZPDD2KD5NJ3WHVMGTQ2XGUNYDNWSPWAC
CRYGI3LRKVHD4D76Q2VTNSX4B2JP5FPDEFJU6DBO7QWFHER2E4OQC
42LVB4DEK3ILS3O5DHFMTJO5HNMJFDYA2WRCLUIOYFPA46MJFZTAC
CNCYMM6ABOXCRI2IP5A4T2OGBO5FQ7GWBXBP2OQYL4YET5BLJCGQC
ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC
L6XA5EY2FVDQFRGCBFSCOM6O4GYN74K3YTZKGFUOPXIUYA3YT5JQC
KAUD3YIKPOXHIUZVR3VCIQHQGBPWTBHXEXC3VQIPAN2BAV6SEMIQC
T7SJSJIH3FUWK2TK6DNVLCNYL7ROJGFVMOOCIY3L46EX5T7M7VNAC
WLWNS6FBT6D3HKOFWDPBKLK7KS73LJJLWLHNWX3YJ723OHJBZGDQC
7DYUAOI6WHAP75T2KEGN6CPSASO7B6L6BDE6FQ5ELND7T6X3WO4AC
TO6Y2G3UB3IQARSSQF5LBBFGKNALBWK74EJ2EQP2UCSYOM2HLBFQC
4AXV2HG4NMAHAWGWD35V5PXULNORCBEKY65UQT37XNYXCSB7TWEAC
PX7DDEMOBGPVK3FXKK5XEPG24CJXZSVW67DLG2JZZ5E77NVEAA3AC
Y4VYNEGFA3RYG5TR75B3FTVU6DITIS3TQDCOOSYSIUG5NBFEYM6QC
LYN3L74WRXZI4KNNIMNLPRFQ36RAGPWNE2O5AMB42H3CSTI6QM6QC
FFBIY74NM7CMGTVYDMEFOTE2PY5E5TMACASQRK5OXRBGN4B4GTCAC
BLWAYPKV3MLDZ4ALXLUJ25AIR6PCIL4RFYNRYLB26GFVC2KQBYBAC
WPW3AVFS3WQVHNSCYVKX7ABWTQUUGVO4SXYCJZT2WOL6TOITX7EQC
VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC
NHA7RUFIUMXUBYOYXWSZPJQHXZWR7RJNDVZXBE3ZR2UYSOB75VPQC
7JH2ZT3FCW4WX52IJFSLGRVZWQQVYJXULSQM2BM4BJUZWG3IVGKAC
LUNH47XXUUITDLE6NBI3J7GJQWQ45OQAGXY2HJI4HRPOR2GUULPAC
C45WCXJ26DEQSZ2WXUT2XKNZGK3C25B3EUIUYXA3S5QJJGNKP2NAC
LAW2O3NWVFTPBSKIMIXPAGYBDOCHYJNKCAVWKNKH62G42DIKZCYQC
M6TH7VSZQGKDB7SFNN5K52WWAX5VTVNT6GOKNKTXPVZBT6NEYDOQC
5DOTWNVMOGN75GJMXXB4A56UAJYSNYC5WXPRT7QFMAAV27NWPP3AC
P4376EXKQXGN6KUOKIZDSGN65J5C33XVX3RDXHYVOOTG7P2ISZWQC
XRLJDW3W4FS7T4OPG2QGSAUSNKDHN6IIFUUUTFB7TGSRAXXFIUIQC
P5QNVXSNGPUYFDBHK6KJMFDVG6U6S5Q3KRQICGK7AZ7FKZE7OJJQC
R3XGABERHYD7X42VXYENLVRSNFUXO5JAO4HCX3N4CMHZCHWEIW6AC
4KC7I3E2DIKLIP7LQRKB5WFA2Z5XZXAU46RFHNFQU5BVEJPDX6UQC
KTZQ57HVZU4XGWRPXBA27G4GXZFV74YYKJRXCJCE7UKDS7NGJVBAC
AH744RFRNNEQ7THYLBD52BKUGPJJL36G5YLQY6NVU442UICAXUXQC
2Y5GGGJ4YNCFSRTQRMW53RMS4EAAL3FPHA5KLOIM53JM3JHLFGWQC
W2CQ7YNGXMAXYCGOYBMIH2F6QIL2IOLUKKDAY5PZI62WELVZMUTQC
BYG5CEMVXANDTBI2ORNVMEY6K3EBRIHZHS4QBK27VONJC5537COQC
HOSPP2ANSW654DYRTC6CQUQA2GUKV6T2FI7QBKXD2DZS3R32IMGAC
H22OAXWESRK7IIK3G54V77MOGRYTX7ZM6UA4NBAZ6NA3GLYXJWIAC
2L4DL7PG5Q3IOZ2NTSULXDTDDA7AHNE43P7GH5TSVG2DZ4XC62NQC
AJB4LFRBMIRBEDWJ3OW7GQIMD2BZBVQ62GH4TE2FISWZKSAHRF4QC
GSPXUEQOX5Z2KXWJFAAYHKE5R3PUGWHXEMDTKWVNOFMHXQIOONCQC
XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC
NYQ7HD4D5L44UORK52TH7CAEXYN5CE4ZUVLCWMY6XXPYHXVBTGHAC
FZCKGO2IA5ZMX7TD4HL3WBUR6NPOYYMT4RGVWF7KFQBFV2V3RZBAC
WTDKUACNTWB4KD34TZZNPILNX4FQ6MR64XYBAA5GOMICF73WLIAAC
65HNIAOSEEJPYMO5XJBDMLU27X5XD3UJIDGSOS2IZ7CIYQRBOZ2QC
PTT4K4EURCVSZDFDNBEKMJORH5WQYZHQFHU4MILFZUGETC7XIEBQC
JFFUF5ALUWPDM7IEDEZVAYG2SVXO334STONRGKVB3QKY2TT5QGBQC
F63Q4OV73CQC6WTWDCURANYMF2PM4TJCXWF3FZ25X4QY7ZTEZ6QAC
HYEAFRZ2UEKDYTAE2GDQLHEJBPQASP2NDLMXB7F6MTVK2BKOXKEAC
DLQMM2656JHXX3ONOEM6UIOXKFJFT5QT7RHWK7YS2W77PVZWHRSAC
UZVWYRTY7MVTSJXL3E5YISYK7YEUPATCBPATT57KHTBMQQH5M7IQC
DLQAEAC76KLM3KZXQ2C5DASP4IBS64GR6L7QYEP67CNXJ6LRL7LQC
7SFHSB47KC6YH737DJPYYVONCFGEHC2Z37RCVPJO6I5PHEOLK74AC
6DE7RBZ6RHNEICJ7EUMCTROK43LW4LYINULIF2QEQOKCXWLUYUXAC
RT6EV6OPUYCXYZOX2PHFXJ7KT77KHNEVINEGQXIQLHQVKPGTN6VQC
PJEQCTBL2ZX5Q7NJQ3KCWSHHBV2QWGYS5NVMRDNK5LOOIIRRIPHAC
EHSUSZMKVRSJ5HPJZQCKWFY3BRNLAIBDATSHDQ7AMJZ6SX2I6Z2AC
DRFE3B3ZKRG4RY2R5Q3SDFD3LH4EXUX3CZCDFBNAXVI2SLDS57PAC
BXJMGTV2FMXDI5ML3OATRJ6O35L3T64S4TW4UYM37V3K3DICSC4AC
BXYVMS4A4FLFWTMBI3UHGL6NY6LDAQVBYYQVLWZJ4IMALK6UKPOQC
AVFRVNFRGQ3KY6CPC2QFPYVO6OAGZVYV7IVX5PSKGCNWO24Q2MTAC
QXVD2RIFQPTO3H6J3IJHRHRDRBW6C6MLUWMCEDQ6Z75SW2TMRBIQC
SRVDX4I5QKWAH3Y5DX25PG34U7NY55H46ZYG2APH47BUZT3EJ2HAC
KEPVDTCGTROAN32TWDUVG7E6HRXTUIES6FD4H3T3I5N2M5VDP7TQC
R3JZDBI2EJYSEQJPA3EH4IISB6CTZ6TRMRQEAPZPOVQTCSGNGPLAC
BJ2C6F2BGCBFUP57FGE7NXVURXFDOXUAWF3EQSJT2MR6U4F4MPJAC
U2TKUOIDFGFVQW7FLFSQ5KDIRYWCYIHWD67F5ZTXKR5EEVSGTZZQC
73OCE2MCBJJZZMN2KYPJTBOUCKBZAOQ2QIAMTGCNOOJ2AJAXFT2AC
23MA4T3GWPOLM5S6JCNQJU2SRT7VQGYZ2JZJN26KA5MKI4LOCC4QC
NVSWVPW5IGHXNRE2OMV4OEWRFJA25DP37CRFPZEVWAZINWGCCVXAC
3HVBAZPADERJZTVHKZWOBNGDWYPJKPOEDZCCI6TLAFU4BFEKFOKAC
YCDYGEZUKVVFRLV5TKF2VYPDXWIHDSZD32KINFARWAJX4X3JPSFAC
6VQIWTQUH43LLCBSGL25WJMZWG6T5SMVU6YJC7PSTTZTO5XPOCSQC
7OUJM7DLEE7MRPJ6C3RGVSRVCS6SQLTPO3NY7CYIE6FEALW5VHLAC
2MA33THZH2DRPD7TYP7YXIUKR5E6BAH2BF452TPWYKXMS5I5JRUQC
JAXPXLEBWHBLNLMZ5VCSO63X6Z5WT4KZKWBOVACCAVX4QJAWWNYQC
3RGHOJ25MCTQ5CRQADFIVJ53QZWRSIQOFGXLFGKQ765QABJ5S54AC
7DPPMI2U5UKNTMCGRJWKDNJFCTCQF3ZSGNHL3IIJ52RKPFWGSWVAC
6LIPEQ5IIBGMMYTTQ7U36ZQUOMPLJBSILG5M5RDHI2K3KTHMS5NAC
QFC3WRDZEZJM3UICG2R7YZL35JJNWKAML4KLHCDX2ZDKREWV44GQC
YJGADSGKT43NZWJL4BKCN6LCBVDNNHED6XGN76PWF54R3LNM46LAC
6J3NXBYGADKVHD53QKHUZNRO2B52DC66Y6GQT5KEH6YKVYNCCRTAC
DXT4QTAH5G6J7ZB3SMOOXVECKWYUPZVE2ODMUFTPPNHLTOSZLQSAC
NQH7DEEWVVC6S4DIMKVHRPEWA63UUTSH4VHX67GEPZVEI2DMOD2QC
C6QTJYA4K533XYXW3XG5HMDML5NVT4ST3ADFBP72CW6RL7AX6Y7QC
NUCZBE77AITZNMPHNNHX36HDTO2IVN6YKPZBZHQWKVRGA5BFDTJAC
BERHYBXMPLRJKHXKCHGIMURTVGGE2FLF5JZPWR5WBVEF44DISIHQC
4CTZOJPCTWYUSHLIZZJ2M5W7S4JZFZVT5MUU5XNSOIBS5L4UY5UQC
B4YZWV6SQI6HK4PZAUKB5L6TISNECL72GFZZET34Q673OVN7IBRAC
VJ77YABHVJZWJKLHAGIPC562GYM73AUGRLCP4JLKP5JPWPT2RIHAC
MTJEVRJR5GLWUSK7HMIM4UXM6GS6O6YCRWJT3DUSU2RYMHCQNOEQC
DSLD74DK3P6J2VAFCYF5BGTHZ637QTW3PDHOUHFACDZU66YNM3IAC
LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC
FYS7TCDWKNRNOJSGRD2JMU4B2LHX5S63ZISM7YF7KZYEYLVCIKIAC
VG75U7IM2ZQTGM2QETDT6QQ4CSLQPB4APK436POAAQJWOMINPIJAC
CIQN2MDEMWAASJAHOHMUZTI5PF4JV5SZSOBYYDCIIFYO2VHWULKAC
4WAFGF4ZMUQOLBWRZ2SI6RWEBKMFNFZQJMPECT25C2VPYHNDK2JQC
KMRJOSLYYHHPGMYXBSLUQTICP6F4LXRCGYSP55YTZQSX4SZISDEAC
4J2L6JMR7NZBGCNX63CL2E3AIB7P7QTCC7QQBPNAEPQ7ISQXL7EQC
ZZ2B5RPQKANSIWAZA4ATDXVBK3XLYIORJ7I4IH2WQOG5JAPJFZ4AC
S2YQBEYCOBS4ADO5VX4YLAWY6CJEQOOZM3THYTDOTXM7ADID6PGQC
FZBXBUFFNRE5ZJO5DLRU375HOXT2B7FO35XD7BTHHUXSARVWDFLQC
EETIR4GXZBA5DEXUQKI6XMC22SFHZIYTZRR4U6BW6BT3RZA32FHQC
HALS7E5UGKCP3DFY456F7Z3Y6WNGIABOCV2SHT34D5ZAGNCPV5PQC
BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC
PFT5Y2ZYGQA6XXOZ5HH75WVUGA4B3KTDRHSFOZRAUKTPSFOPMNRAC
QYIFOHW3WDDQMK4ATY6IOSQRFHJOQ5QCPDKRC4GVGWLQEH4HGWVQC
Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC
LSYLEVBDBZBGLSCXTRBW46WT4TUMMSPCH7M6HSNYI5SIH2WNPYEAC
SPSW74Y5OJ54Y7VQ3SJFCJR5CYDKTR4A3TOEVZODDZLUSDDU2GZAC
TGHAJBESCIEGWUE2D3FGLNOIAYT4D2IRGZKRXRMTUFW7QZETC7OAC
2RXZ3PGOTTZ6M4R372JXIKPLBQKPVBMAXNPIEO2HZDN4EMYW4GNAC
DHI6IJCNSTHGED67T6H5X6Y636C7PIDGIJD32HBEKLT5WIMRS5MAC
KOYAJWE4NJ2J4X3SHEAVMRXYZPZGOMTI7OX3PTUQIDIZ2GQI6UKAC
MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC
ZPUQSPQPQFVRUIHGLAWW3IDBYODIWDHO62HAC3WWF5TM3CIJGHNQC
ODLKHO7BO2AODYO2OEQ6D4NSNBT5GR3CKLUXWMDLRYXL7DJOI7BAC
2H67P75X7FSSNZMV4TU5I46XLIYAJ5VT7H6MS7GYLUSNTA4IRDFQC
UV4EWOLYCQ27TL6IGGLKNQX3UUOF7HJ5EJVCZYW345X6BK4J7YQAC
PR4KIAZDOBQMEUOV2G7ZEZUW3E4L5ZCHYSS7PTYWGXPSNVRAGHCAC
T57DTBX6J7E7FVEX4LQWXNKR7YXIHJW4HBCHUOYA5PTJUOEYHTEAC
V5MJRFOZRVVDCPOWTLXPHS2HZBZKOOCPPKFMRP6MWZN6N62QLFAAC
SVJZZDC3K6AKAXHGRNAZKRE2ZXEKJANNLG7LSSUZJARFBL5F7C4AC
V3EABA35RWCOOU5OMIYRWXAKZOLHO4XPGTPOKY24RR2LOAD7ZQAQC
F65ADDGLR2PNXVSM2XBHM3OSLQC2OTRR3GQBI7DJWIKPJCJ5CSOAC
62PZGSUCEXJOCVWEOOENSDJITJFR27BGW7BPGFYVD3E5M6446RQQC
KECEMMMRW2VVBZ567HJQPGLC57LTSBKWH7UFP32IW43D23X6WTEQC
HTWAM4NZFOY463TNSKYIM2EWB7QNBGDRRTTGHF5N3Z4TGC7Q3SFAC
QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC
242L3OQXTU2TCAINRJXQEEDSXQXM7Y7USUPBK37ZNM3A7V5TUDSAC
MDXGMZU2MBEDMTB755D3RRYEFKF54GTTYTI5XJYKKKN5ZFQWZXTAC
3OKKTUT4Q7W44JHILOFV5BVUA7ZOBIHBCEXGZ65CPXV4PRLI2W4QC
YPHKZVWM2FS7U3VNVDXFRJTBF4RLQ6K7ZWISLHOQJPYSKBELHFEAC
TRNWIQN6RPLDLYWULLKG5L255E7E3DPNGLCSLAF6IJWYQRCCLARQC
MYC7XR5QOT2AXHF6UNGSNFFD5VL6UHGUZQBP7PWWLZ5NNXE7UMTAC
MP2TBKU6CNDMZKENYMBV62F5KQ27ZWEVPVRFS2RESVDQQT2IRR4AC
CPZGQT72EBP3SEDBPDWQRK5IUGA664PHXNP2GOHJLP43PKPWF25AC
4VKEE43Z7MUPNIAOCK36INVBNHRTSWRRN37TIKRPXPH3DRKGHHAQC
5L7K4GBDEAFH44LMLNKVFMHLWDNXXBKRPEI347VE5ZLXVFSMD2FAC
IWYLK45KJSPRXKW55OD4GEPMLTYMMTXNFJJU26JTZN3RE35DWSCQC
EGH7XDBKE3R74VXLNTCAP5LJTRBPFUEMPS647MJARDGCMUHJG2QQC
HIKLULFQG7Q7L4C5KXR3DV3TBZ2RGWXBJJXIGSE5YQWF37AJOYZAC
S2MISTTMPEULTO6WRO4Q4NRUO7XC2PTZW3UBR7K7SO6JPZO6HBHAC
52ZZ5TIEK5Z6VVXO7R3EFV5JTIWDA4IDD3YISXHHTHQMOAKVYJJQC
FKNXK2OAH4U2V2TXCHWE4C3Z5DROBIIPSXUWFKP7Q3DSNOKFFL5AC
K4OBZSHEBIZBAKPH3F7ASDGCPLB7D5W5QLFJQYSM5XOYDPB4BUHAC
O7QH4N4WVIN644DOEQ3OKCVGKPRAV3QUBAAO3L7XDXTK6TIRGCCQC
MSOQI3A5BC5PY2MZXZQAQ4EQDT4KICQJPN3YUZVDYTWXSPZWBLIAC
5ZA3BRNYWKSGEBJ4JLA4UBC3LJPT5JBWYCU7PQYRSGX6MJMEWDIQC
LLAOOMULEBXFMIGRBY6LRVEK4RXQGPNTFVWMCZNUEJZHWC7UGUEAC
CE4LZV4TNXJT54CVGM3QANCBP42TMLMZWF2DBSMUYKAHILXIZEMQC
YT5P6TO64XSMCZGTT4SVNFOWUN5ECNXTWCMFXN3YCDZUNH4H3IFAC
TVCPXAAU4P3K5MFYINH2MWDK3KGTQ2GE74TUNERYOONG2G5EYKMQC
OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC
ERQKFTPVWZO4WJD2WRIV33JWTWZSF4HNTK2GD7QT5I5TIL3SOGKQC
5T2E3PDVSLMZSSIIQRNKIKQVV77XQTHP473OP7XBTTMSZHIQID5AC
2XLZCWZCOFZGNAPSD7XNCYITKVY5WZW3OD7IXMC7WJQ5SBI7YHNQC
VDJSUX2QF5MBLRYACBPEEHQF27C7YE3BO44PZ7UZFY422H34CDVAC
2K2YDMFHUUROCRDVKRYNXA75MTR3C2DXHYAAJRVPNTQW6LUARJDAC
YTSPVDZHEN5LLNMGIBUBLPWFWSFM3SOHBRGWYSDEVFKRTH24ARRQC
2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC
WJBZZQE4A4KLYGS2KA254I6VN2DVXDY4XKCNAE76GTMLLQGYCUOQC
BW2IUB3KA4AKD35DYLCUCUM4Z32FMKGZNUBQBAEDIQJJYPA547MAC
PK5U572CFG4M7LEGO5UPJXXIYFWHYIVTLZN6FTQJBUTNFFREJ4SAC
PTDO2SOTXEI6FROZ2AVRFXSKKNKCRMPPTQSI5LWD45UVGDJPMSGQC
FS2ITYYHBLFT66YUC3ENPFYI2HOYHOVEPQIN7NQR6KF5MEK4NKZAC
ZTMRQZSWUL6FJRI4C4H37MR2IMV22DB6KRGEOUNYRWW5CTAVQFKAC
D4FEFHQCSILZFQ5VLWNXAIRZNUMCDNGJSM4UJ6T6FDMMIWYRYILQC
CZRMAMSBRVX26IXKHNPG6M3YSWMOZTM73X3XHAMBDSNETTFVRCUQC
KQWIMWJ5VRAXM7SFNWDSBZMQ6ZE3CZQTKZHVM5ZQCW4RHPTI64MQC
MLG2OGU7OBWWPX5TDJQWTDTHSTM75WIMAW57546C4XLEVZQOYJ7AC
34TC5SYKYVUCVIQM3GNVYURQAMIXX64IOSJ4TYBPSRDS65QLTHWAC
IEHG6OROGLZINLGZACEOD2PHQOM522TDUUF2UT6APHT42GUH2OXAC
-- major tests for drawings
-- We minimize assumptions about specific pixels, and try to test at the level
-- of specific shapes. In particular, no tests of freehand drawings.
function test_draw_line()
io.write('\ntest_draw_line')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(#Editor_state.lines, 2, 'F - test_draw_line/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_line/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_line/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_line/baseline/#shapes')
-- draw a line
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_draw_line/#shapes')
check_eq(#drawing.points, 2, 'F - test_draw_line/#points')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_draw_line/shape:1')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_draw_line/p1:x')
check_eq(p1.y, 6, 'F - test_draw_line/p1:y')
check_eq(p2.x, 35, 'F - test_draw_line/p2:x')
check_eq(p2.y, 36, 'F - test_draw_line/p2:y')
end
function test_draw_arc()
io.write('\ntest_draw_arc')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(#Editor_state.lines, 2, 'F - test_draw_arc/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_arc/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_arc/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_arc/baseline/#shapes')
-- draw an arc
local drawing = Editor_state.lines[1]
edit.run_after_keychord(Editor_state, 'a') -- arc mode
edit.run_after_mouse_release(Editor_state, Editor_state.left+35+50, Editor_state.top+Drawing_padding_top+36+50, 1) -- 45°
check_eq(#drawing.shapes, 1, 'F - test_draw_arc/#shapes')
check_eq(drawing.shapes[1].mode, 'arc', 'F - test_draw_horizontal_line/shape_mode')
local arc = drawing.shapes[1]
check_eq(arc.radius, 30, 'F - test_draw_arc/radius')
local center = drawing.points[arc.center]
check_eq(center.x, 35, 'F - test_draw_arc/center:x')
check_eq(center.y, 36, 'F - test_draw_arc/center:y')
check_eq(arc.start_angle, 0, 'F - test_draw_arc/start:angle')
check_eq(arc.end_angle, math.pi/4, 'F - test_draw_arc/end:angle')
end
function test_draw_polygon()
io.write('\ntest_draw_polygon')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_polygon/baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'F - test_draw_polygon/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_polygon/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_polygon/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_polygon/baseline/#shapes')
-- first point
edit.run_after_keychord(Editor_state, 'g') -- polygon mode
-- second point
edit.run_after_keychord(Editor_state, 'p') -- add point
-- final point
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_draw_polygon/#shapes')
check_eq(#drawing.points, 3, 'F - test_draw_polygon/vertices')
local shape = drawing.shapes[1]
check_eq(shape.mode, 'polygon', 'F - test_draw_polygon/shape_mode')
check_eq(#shape.vertices, 3, 'F - test_draw_polygon/vertices')
local p = drawing.points[shape.vertices[1]]
check_eq(p.x, 5, 'F - test_draw_polygon/p1:x')
check_eq(p.y, 6, 'F - test_draw_polygon/p1:y')
local p = drawing.points[shape.vertices[2]]
check_eq(p.x, 65, 'F - test_draw_polygon/p2:x')
check_eq(p.y, 36, 'F - test_draw_polygon/p2:y')
local p = drawing.points[shape.vertices[3]]
check_eq(p.x, 35, 'F - test_draw_polygon/p3:x')
check_eq(p.y, 26, 'F - test_draw_polygon/p3:y')
end
function test_draw_rectangle()
io.write('\ntest_draw_rectangle')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_rectangle/baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'F - test_draw_rectangle/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_rectangle/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_rectangle/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_rectangle/baseline/#shapes')
-- first point
edit.run_after_keychord(Editor_state, 'r') -- rectangle mode
-- second point/first edge
edit.run_after_keychord(Editor_state, 'p')
-- override second point/first edge
edit.run_after_keychord(Editor_state, 'p')
-- release (decides 'thickness' of rectangle perpendicular to first edge)
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_draw_rectangle/#shapes')
check_eq(#drawing.points, 5, 'F - test_draw_rectangle/#points') -- currently includes every point added
local shape = drawing.shapes[1]
check_eq(shape.mode, 'rectangle', 'F - test_draw_rectangle/shape_mode')
check_eq(#shape.vertices, 4, 'F - test_draw_rectangle/vertices')
local p = drawing.points[shape.vertices[1]]
check_eq(p.x, 35, 'F - test_draw_rectangle/p1:x')
check_eq(p.y, 36, 'F - test_draw_rectangle/p1:y')
local p = drawing.points[shape.vertices[2]]
check_eq(p.x, 75, 'F - test_draw_rectangle/p2:x')
check_eq(p.y, 76, 'F - test_draw_rectangle/p2:y')
local p = drawing.points[shape.vertices[3]]
check_eq(p.x, 70, 'F - test_draw_rectangle/p3:x')
check_eq(p.y, 81, 'F - test_draw_rectangle/p3:y')
local p = drawing.points[shape.vertices[4]]
check_eq(p.x, 30, 'F - test_draw_rectangle/p4:x')
check_eq(p.y, 41, 'F - test_draw_rectangle/p4:y')
end
function test_draw_rectangle_intermediate()
io.write('\ntest_draw_rectangle_intermediate')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_rectangle_intermediate/baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'F - test_draw_rectangle_intermediate/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_rectangle_intermediate/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_rectangle_intermediate/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_rectangle_intermediate/baseline/#shapes')
-- first point
edit.run_after_keychord(Editor_state, 'r') -- rectangle mode
-- second point/first edge
edit.run_after_keychord(Editor_state, 'p')
-- override second point/first edge
local drawing = Editor_state.lines[1]
edit.run_after_keychord(Editor_state, 'p')
check_eq(#drawing.points, 3, 'F - test_draw_rectangle_intermediate/#points') -- currently includes every point added
local pending = drawing.pending
check_eq(pending.mode, 'rectangle', 'F - test_draw_rectangle_intermediate/shape_mode')
check_eq(#pending.vertices, 2, 'F - test_draw_rectangle_intermediate/vertices')
local p = drawing.points[pending.vertices[1]]
check_eq(p.x, 35, 'F - test_draw_rectangle_intermediate/p1:x')
check_eq(p.y, 36, 'F - test_draw_rectangle_intermediate/p1:y')
local p = drawing.points[pending.vertices[2]]
check_eq(p.x, 75, 'F - test_draw_rectangle_intermediate/p2:x')
check_eq(p.y, 76, 'F - test_draw_rectangle_intermediate/p2:y')
-- outline of rectangle is drawn based on where the mouse is, but we can't check that so far
end
function test_draw_square()
io.write('\ntest_draw_square')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_square/baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'F - test_draw_square/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_square/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_square/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_square/baseline/#shapes')
-- first point
edit.run_after_keychord(Editor_state, 's') -- square mode
-- second point/first edge
edit.run_after_keychord(Editor_state, 'p')
-- override second point/first edge
edit.run_after_keychord(Editor_state, 'p')
-- release (decides which side of first edge to draw square on)
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_draw_square/#shapes')
check_eq(#drawing.points, 5, 'F - test_draw_square/#points') -- currently includes every point added
check_eq(drawing.shapes[1].mode, 'square', 'F - test_draw_square/shape_mode')
check_eq(#drawing.shapes[1].vertices, 4, 'F - test_draw_square/vertices')
local p = drawing.points[drawing.shapes[1].vertices[1]]
check_eq(p.x, 35, 'F - test_draw_square/p1:x')
check_eq(p.y, 36, 'F - test_draw_square/p1:y')
local p = drawing.points[drawing.shapes[1].vertices[2]]
check_eq(p.x, 65, 'F - test_draw_square/p2:x')
check_eq(p.y, 66, 'F - test_draw_square/p2:y')
local p = drawing.points[drawing.shapes[1].vertices[3]]
check_eq(p.x, 35, 'F - test_draw_square/p3:x')
check_eq(p.y, 96, 'F - test_draw_square/p3:y')
local p = drawing.points[drawing.shapes[1].vertices[4]]
check_eq(p.x, 5, 'F - test_draw_square/p4:x')
check_eq(p.y, 66, 'F - test_draw_square/p4:y')
end
function test_name_point()
io.write('\ntest_name_point')
-- create a drawing with a line
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
-- draw a line
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_name_point/baseline/#shapes')
check_eq(#drawing.points, 2, 'F - test_name_point/baseline/#points')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_name_point/baseline/shape:1')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_name_point/baseline/p1:x')
check_eq(p1.y, 6, 'F - test_name_point/baseline/p1:y')
check_eq(p2.x, 35, 'F - test_name_point/baseline/p2:x')
check_eq(p2.y, 36, 'F - test_name_point/baseline/p2:y')
check_nil(p2.name, 'F - test_name_point/baseline/p2:name')
-- enter 'name' mode without moving the mouse
check_eq(Editor_state.current_drawing_mode, 'name', 'F - test_name_point/mode:1')
check_eq(p2.name, 'A', 'F - test_name_point')
-- still in 'name' mode
-- exit 'name' mode
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_name_point/mode:3')
check_eq(p2.name, 'A', 'F - test_name_point')
end
function test_delete_point_from_polygon()
io.write('\ntest_delete_point_from_polygon')
-- create a drawing with two lines connected at a point
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
-- first point
edit.run_after_keychord(Editor_state, 'g') -- polygon mode
-- second point
edit.run_after_keychord(Editor_state, 'p') -- add point
-- third point
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_delete_point_from_polygon/baseline/#shapes')
check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/baseline/mode')
check_eq(#drawing.shapes[1].vertices, 3, 'F - test_delete_point_from_polygon/baseline/vertices')
-- hover on a point and delete
edit.run_after_keychord(Editor_state, 'C-d')
-- there's < 3 points left, so the whole polygon is deleted
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_point_from_polygon')
end
function test_undo_name_point()
io.write('\ntest_undo_name_point')
-- create a drawing with a line
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
-- draw a line
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_undo_name_point/baseline/#shapes')
check_eq(#drawing.points, 2, 'F - test_undo_name_point/baseline/#points')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_name_point/baseline/shape:1')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_undo_name_point/baseline/p1:x')
check_eq(p1.y, 6, 'F - test_undo_name_point/baseline/p1:y')
check_eq(p2.x, 35, 'F - test_undo_name_point/baseline/p2:x')
check_eq(p2.y, 36, 'F - test_undo_name_point/baseline/p2:y')
check_nil(p2.name, 'F - test_undo_name_point/baseline/p2:name')
-- enter 'name' mode without moving the mouse
check_eq(p2.name, 'A', 'F - test_undo_name_point/baseline')
-- undo
local drawing = Editor_state.lines[1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p2.name, '', 'F - test_undo_name_point') -- not quite what it was before, but close enough
check_eq(p2.name, '', 'F - test_undo_name_point/save')
end
function test_undo_move_point()
io.write('\ntest_undo_move_point')
-- create a drawing with a line
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_undo_move_point/baseline/#shapes')
check_eq(#drawing.points, 2, 'F - test_undo_move_point/baseline/#points')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_move_point/baseline/shape:1')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_undo_move_point/baseline/p1:x')
check_eq(p1.y, 6, 'F - test_undo_move_point/baseline/p1:y')
check_eq(p2.x, 35, 'F - test_undo_move_point/baseline/p2:x')
check_eq(p2.y, 36, 'F - test_undo_move_point/baseline/p2:y')
check_nil(p2.name, 'F - test_undo_move_point/baseline/p2:name')
-- move p2
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p2.x, 26, 'F - test_undo_move_point/x')
check_eq(p2.y, 44, 'F - test_undo_move_point/y')
-- exit 'move' mode
check_eq(Editor_state.next_history, 4, 'F - test_undo_move_point/next_history')
-- undo
local drawing = Editor_state.lines[1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p2.x, 35, 'F - test_undo_move_point/x')
check_eq(p2.y, 36, 'F - test_undo_move_point/y')
check_eq(p2.x, 35, 'F - test_undo_move_point/save/x')
check_eq(p2.y, 36, 'F - test_undo_move_point/save/y')
end
end
function test_undo_delete_point()
io.write('\ntest_undo_delete_point')
-- create a drawing with two lines connected at a point
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 2, 'F - test_undo_delete_point/baseline/#shapes')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_delete_point/baseline/shape:1')
check_eq(drawing.shapes[2].mode, 'line', 'F - test_undo_delete_point/baseline/shape:2')
-- hover on the common point and delete
edit.run_after_keychord(Editor_state, 'C-d')
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_undo_delete_point/shape:1')
check_eq(drawing.shapes[2].mode, 'deleted', 'F - test_undo_delete_point/shape:2')
-- undo
local drawing = Editor_state.lines[1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_delete_point/shape:1')
check_eq(drawing.shapes[2].mode, 'line', 'F - test_undo_delete_point/shape:2')
-- undo is saved
Text.redraw_all(Editor_state)
check_eq(#Editor_state.lines[1].shapes, 2, 'F - test_undo_delete_point/save')
load_from_disk(Editor_state)
-- wait until save
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
check_eq(Editor_state.next_history, 3, 'F - test_undo_move_point/next_history')
edit.run_after_keychord(Editor_state, 'C-z')
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+36)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+55, Editor_state.top+Drawing_padding_top+26, 1)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
-- wait until save
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
-- undo is saved
Text.redraw_all(Editor_state)
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
load_from_disk(Editor_state)
check_eq(Editor_state.next_history, 2, 'F - test_undo_move_point/next_history')
edit.run_after_keychord(Editor_state, 'C-z')
edit.run_after_keychord(Editor_state, 'C-z') -- bug: need to undo twice
edit.run_after_mouse_click(Editor_state, Editor_state.left+26, Editor_state.top+Drawing_padding_top+44, 1)
edit.run_after_keychord(Editor_state, 'C-u')
edit.update(Editor_state, 0.05)
App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
-- wait until save
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
-- undo is saved
Text.redraw_all(Editor_state)
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
load_from_disk(Editor_state)
check_eq(Editor_state.next_history, 3, 'F - test_undo_name_point/next_history')
edit.run_after_keychord(Editor_state, 'C-z')
check_eq(#Editor_state.history, 3, 'F - test_undo_name_point/baseline/history:2')
check_eq(Editor_state.next_history, 4, 'F - test_undo_name_point/baseline/next_history')
--? print('b', Editor_state.lines.current_drawing)
edit.run_after_keychord(Editor_state, 'C-n')
edit.run_after_textinput(Editor_state, 'A')
edit.run_after_keychord(Editor_state, 'return')
check_eq(#Editor_state.history, 1, 'F - test_undo_name_point/baseline/history:1')
--? print('a', Editor_state.lines.current_drawing)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
edit.run_after_mouse_release(Editor_state, Editor_state.left+14, Editor_state.top+Drawing_padding_top+16, 1)
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
-- wait until save
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
-- change is saved
Text.redraw_all(Editor_state)
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
check_eq(p2.name, 'A', 'F - test_name_point/save')
load_from_disk(Editor_state)
end
function test_move_point()
io.write('\ntest_move_point')
-- create a drawing with a line
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_move_point/baseline/#shapes')
check_eq(#drawing.points, 2, 'F - test_move_point/baseline/#points')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_move_point/baseline/shape:1')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_move_point/baseline/p1:x')
check_eq(p1.y, 6, 'F - test_move_point/baseline/p1:y')
check_eq(p2.x, 35, 'F - test_move_point/baseline/p2:x')
check_eq(p2.y, 36, 'F - test_move_point/baseline/p2:y')
-- wait until save
App.wait_fake_time(3.1)
-- line is saved to disk
Text.redraw_all(Editor_state)
local drawing = Editor_state.lines[1]
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
check_eq(p2.x, 35, 'F - test_move_point/save/x')
check_eq(p2.y, 36, 'F - test_move_point/save/y')
-- enter 'move' mode without moving the mouse
check_eq(Editor_state.current_drawing_mode, 'move', 'F - test_move_point/mode:1')
-- point is lifted
check_eq(drawing.pending.mode, 'move', 'F - test_move_point/mode:2')
check_eq(drawing.pending.target_point, p2, 'F - test_move_point/target')
-- move point
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p2.x, 26, 'F - test_move_point/x')
check_eq(p2.y, 44, 'F - test_move_point/y')
-- exit 'move' mode
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_move_point/mode:3')
check_eq(drawing.pending, {}, 'F - test_move_point/pending')
-- wait until save
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
-- change is saved
Text.redraw_all(Editor_state)
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
check_eq(p2.x, 26, 'F - test_move_point/save/x')
check_eq(p2.y, 44, 'F - test_move_point/save/y')
load_from_disk(Editor_state)
end
function test_delete_lines_at_point()
io.write('\ntest_delete_lines_at_point')
-- create a drawing with two lines connected at a point
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 2, 'F - test_delete_lines_at_point/baseline/#shapes')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_delete_lines_at_point/baseline/shape:1')
check_eq(drawing.shapes[2].mode, 'line', 'F - test_delete_lines_at_point/baseline/shape:2')
-- hover on the common point and delete
edit.run_after_keychord(Editor_state, 'C-d')
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_lines_at_point/shape:1')
check_eq(drawing.shapes[2].mode, 'deleted', 'F - test_delete_lines_at_point/shape:2')
end
function test_delete_line_under_mouse_pointer()
io.write('\ntest_delete_line_under_mouse_pointer')
-- create a drawing with two lines connected at a point
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 2, 'F - test_delete_line_under_mouse_pointer/baseline/#shapes')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_delete_line_under_mouse_pointer/baseline/shape:1')
check_eq(drawing.shapes[2].mode, 'line', 'F - test_delete_line_under_mouse_pointer/baseline/shape:2')
-- hover on one of the lines and delete
edit.run_after_keychord(Editor_state, 'C-d')
-- only that line is deleted
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_line_under_mouse_pointer/shape:1')
check_eq(drawing.shapes[2].mode, 'line', 'F - test_delete_line_under_mouse_pointer/shape:2')
end
function test_delete_point_from_polygon()
io.write('\ntest_delete_point_from_polygon')
-- create a drawing with two lines connected at a point
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
-- first point
edit.run_after_keychord(Editor_state, 'g') -- polygon mode
-- second point
edit.run_after_keychord(Editor_state, 'p') -- add point
-- third point
edit.run_after_keychord(Editor_state, 'p') -- add point
-- fourth point
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_delete_point_from_polygon/baseline/#shapes')
check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/baseline/mode')
check_eq(#drawing.shapes[1].vertices, 4, 'F - test_delete_point_from_polygon/baseline/vertices')
-- hover on a point and delete
edit.run_after_keychord(Editor_state, 'C-d')
-- just the one point is deleted
check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/shape')
check_eq(#drawing.shapes[1].vertices, 3, 'F - test_delete_point_from_polygon/vertices')
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)
edit.run_after_mouse_release(Editor_state, Editor_state.left+14, Editor_state.top+Drawing_padding_top+16, 1)
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
App.mouse_move(Editor_state.left+25, Editor_state.top+Drawing_padding_top+26)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+55, Editor_state.top+Drawing_padding_top+26, 1)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
-- wait for some time
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
-- deleted points disappear after file is reloaded
Text.redraw_all(Editor_state)
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_delete_lines_at_point/save')
load_from_disk(Editor_state)
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+36)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+55, Editor_state.top+Drawing_padding_top+26, 1)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
function test_move_point_on_manhattan_line()
io.write('\ntest_move_point_on_manhattan_line')
-- create a drawing with a manhattan line
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_move_point_on_manhattan_line/baseline/#shapes')
check_eq(#drawing.points, 2, 'F - test_move_point_on_manhattan_line/baseline/#points')
check_eq(drawing.shapes[1].mode, 'manhattan', 'F - test_move_point_on_manhattan_line/baseline/shape:1')
-- enter 'move' mode
check_eq(Editor_state.current_drawing_mode, 'move', 'F - test_move_point_on_manhattan_line/mode:1')
-- move point
-- line is no longer manhattan
check_eq(drawing.shapes[1].mode, 'line', 'F - test_move_point_on_manhattan_line/baseline/shape:1')
end
edit.update(Editor_state, 0.05)
App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
edit.run_after_keychord(Editor_state, 'C-u')
edit.draw(Editor_state)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'manhattan'
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+46, 1)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
edit.run_after_mouse_click(Editor_state, Editor_state.left+26, Editor_state.top+Drawing_padding_top+44, 1)
edit.update(Editor_state, 0.05)
App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
edit.run_after_keychord(Editor_state, 'C-u')
edit.draw(Editor_state)
load_from_disk(Editor_state)
edit.update(Editor_state, 0)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
edit.run_after_keychord(Editor_state, 'return')
check_eq(Editor_state.current_drawing_mode, 'name', 'F - test_name_point/mode:2')
edit.run_after_textinput(Editor_state, 'A')
edit.run_after_keychord(Editor_state, 'C-n')
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+15, Editor_state.top+Drawing_padding_top+26, 1)
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+66)
App.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_square/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
App.mouse_move(Editor_state.left+75, Editor_state.top+Drawing_padding_top+76)
App.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_rectangle_intermediate/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
edit.run_after_mouse_release(Editor_state, Editor_state.left+15, Editor_state.top+Drawing_padding_top+26, 1)
App.mouse_move(Editor_state.left+75, Editor_state.top+Drawing_padding_top+76)
App.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_rectangle/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+26, 1)
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_polygon/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
check_eq(#drawing.points, 1, 'F - test_draw_arc/#points')
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
App.mouse_move(Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_arc/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'circle'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
-- wait until save
App.wait_fake_time(3.1)
edit.update(Editor_state, 0)
-- The format on disk isn't perfectly stable. Table fields can be reordered.
-- So just reload from disk to verify.
Text.redraw_all(Editor_state)
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_draw_line/save/#shapes')
check_eq(#drawing.points, 2, 'F - test_draw_line/save/#points')
check_eq(drawing.shapes[1].mode, 'line', 'F - test_draw_line/save/shape:1')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_draw_line/save/p1:x')
check_eq(p1.y, 6, 'F - test_draw_line/save/p1:y')
check_eq(p2.x, 35, 'F - test_draw_line/save/p2:x')
check_eq(p2.y, 36, 'F - test_draw_line/save/p2:y')
load_from_disk(Editor_state)
end
function test_draw_horizontal_line()
io.write('\ntest_draw_horizontal_line')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(#Editor_state.lines, 2, 'F - test_draw_horizontal_line/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_horizontal_line/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_horizontal_line/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_horizontal_line/baseline/#shapes')
-- draw a line that is more horizontal than vertical
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 1, 'F - test_draw_horizontal_line/#shapes')
check_eq(#drawing.points, 2, 'F - test_draw_horizontal_line/#points')
check_eq(drawing.shapes[1].mode, 'manhattan', 'F - test_draw_horizontal_line/shape_mode')
local p1 = drawing.points[drawing.shapes[1].p1]
local p2 = drawing.points[drawing.shapes[1].p2]
check_eq(p1.x, 5, 'F - test_draw_horizontal_line/p1:x')
check_eq(p1.y, 6, 'F - test_draw_horizontal_line/p1:y')
check_eq(p2.x, 35, 'F - test_draw_horizontal_line/p2:x')
check_eq(p2.y, p1.y, 'F - test_draw_horizontal_line/p2:y')
end
function test_draw_circle()
io.write('\ntest_draw_circle')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(#Editor_state.lines, 2, 'F - test_draw_circle/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_circle/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_circle/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_circle/baseline/#shapes')
-- draw a circle
local drawing = Editor_state.lines[1]
edit.run_after_keychord(Editor_state, 'C-o')
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(#drawing.shapes, 1, 'F - test_draw_circle/#shapes')
check_eq(#drawing.points, 1, 'F - test_draw_circle/#points')
check_eq(drawing.shapes[1].mode, 'circle', 'F - test_draw_horizontal_line/shape_mode')
check_eq(drawing.shapes[1].radius, 30, 'F - test_draw_circle/radius')
local center = drawing.points[drawing.shapes[1].center]
check_eq(center.x, 35, 'F - test_draw_circle/center:x')
check_eq(center.y, 36, 'F - test_draw_circle/center:y')
end
function test_keys_do_not_affect_shape_when_mouse_up()
io.write('\ntest_keys_do_not_affect_shape_when_mouse_up')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
-- hover over drawing and press 'o' without holding mouse
edit.run_after_keychord(Editor_state, 'o')
-- no change to drawing mode
-- no change to text either because we didn't run the textinput event
end
function test_draw_circle_mid_stroke()
io.write('\ntest_draw_circle_mid_stroke')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(#Editor_state.lines, 2, 'F - test_draw_circle_mid_stroke/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_circle_mid_stroke/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_circle_mid_stroke/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_circle_mid_stroke/baseline/#shapes')
-- draw a circle
local drawing = Editor_state.lines[1]
edit.run_after_keychord(Editor_state, 'o')
edit.run_after_mouse_release(Editor_state, Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(#drawing.shapes, 1, 'F - test_draw_circle_mid_stroke/#shapes')
check_eq(#drawing.points, 1, 'F - test_draw_circle_mid_stroke/#points')
check_eq(drawing.shapes[1].mode, 'circle', 'F - test_draw_horizontal_line/shape_mode')
check_eq(drawing.shapes[1].radius, 30, 'F - test_draw_circle_mid_stroke/radius')
local center = drawing.points[drawing.shapes[1].center]
check_eq(center.x, 35, 'F - test_draw_circle_mid_stroke/center:x')
check_eq(center.y, 36, 'F - test_draw_circle_mid_stroke/center:y')
App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_circle_mid_stroke/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_keys_do_not_affect_shape_when_mouse_up/drawing_mode')
App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
end
function test_cancel_stroke()
io.write('\ntest_cancel_stroke')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
check_eq(#Editor_state.lines, 2, 'F - test_cancel_stroke/baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_cancel_stroke/baseline/mode')
check_eq(Editor_state.lines[1].h, 128, 'F - test_cancel_stroke/baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_cancel_stroke/baseline/#shapes')
-- start drawing a line
-- cancel
local drawing = Editor_state.lines[1]
check_eq(#drawing.shapes, 0, 'F - test_cancel_stroke/#shapes')
edit.run_after_keychord(Editor_state, 'escape')
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_cancel_stroke/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_circle/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+26, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_horizontal_line/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'manhattan'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_line/baseline/y')
Editor_state.lines = load_array{'```lines', '```', ''}
Editor_state.current_drawing_mode = 'line'
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
function test_creating_drawing_saves()
io.write('\ntest_creating_drawing_saves')
App.screen.init{width=120, height=60}
-- click on button to create drawing
-- file not immediately saved
check_nil(App.filesystem['foo'], 'F - test_creating_drawing_saves/early')
-- wait until save
App.wait_fake_time(3.1)
-- filesystem contains drawing and an empty line of text
check_eq(App.filesystem['foo'], '```lines\n```\n\n', 'F - test_creating_drawing_saves')
end
edit.update(Editor_state, 0)
edit.update(Editor_state, 0.01)
edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
Editor_state.filename = 'foo'
Editor_state.lines = load_array{}
edit.draw(Editor_state)
Text.redraw_all(Editor_state)
Editor_state = edit.initialize_test_state()
-- primitives for editing drawings
Drawing = {}
require 'drawing_tests'
else
end
return
end
end
if line.show_help then
return
end
for _,shape in ipairs(line.shapes) do
assert(shape)
else
end
end
if p.deleted == nil then
else
end
if p.name then
love.graphics.print(p.name, x,y)
if State.current_drawing_mode == 'name' and i == line.pending.target_point then
-- create a faint red box for the name
local name_text
if p.name == '' then
else
end
end
love.graphics.rectangle('fill', x,y, App.width(name_text), State.line_height)
name_text = App.newText(love.graphics.getFont(), p.name)
name_text = State.em
-- TODO: avoid computing name width on every repaint
App.color(Current_name_background_color)
-- TODO: clip
local x,y = px(p.x)+5, py(p.y)+5
end
end
end
end
end
if shape.mode == 'freehand' then
local prev = nil
for _,point in ipairs(shape.points) do
if prev then
end
prev = point
end
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
local p1 = drawing.points[shape.p1]
local p2 = drawing.points[shape.p2]
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
local prev = nil
for _,point in ipairs(shape.vertices) do
local curr = drawing.points[point]
if prev then
end
prev = curr
end
-- close the loop
local curr = drawing.points[shape.vertices[1]]
elseif shape.mode == 'circle' then
local center = drawing.points[shape.center]
elseif shape.mode == 'arc' then
local center = drawing.points[shape.center]
elseif shape.mode == 'deleted' then
-- ignore
else
print(shape.mode)
assert(false)
end
end
local shape = drawing.pending
elseif shape.mode == 'line' then
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
return
end
local p1 = drawing.points[shape.p1]
elseif shape.mode == 'manhattan' then
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
return
end
if math.abs(mx-p1.x) > math.abs(my-p1.y) then
else
end
elseif shape.mode == 'polygon' then
-- don't close the loop on a pending polygon
local prev = nil
for _,point in ipairs(shape.vertices) do
local curr = drawing.points[point]
if prev then
end
prev = curr
end
elseif shape.mode == 'rectangle' then
local first = drawing.points[shape.vertices[1]]
if #shape.vertices == 1 then
return
end
local second = drawing.points[shape.vertices[2]]
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
elseif shape.mode == 'square' then
local first = drawing.points[shape.vertices[1]]
if #shape.vertices == 1 then
return
end
local second = drawing.points[shape.vertices[2]]
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
elseif shape.mode == 'circle' then
local center = drawing.points[shape.center]
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
return
end
elseif shape.mode == 'arc' then
local center = drawing.points[shape.center]
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
return
end
shape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle)
love.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
elseif shape.mode == 'move' then
-- nothing pending; changes are immediately committed
elseif shape.mode == 'name' then
-- nothing pending; changes are immediately committed
else
print(shape.mode)
assert(false)
end
end
local width = right-left
return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
end
else
assert(false)
end
end
-- a couple of operations on drawings need to constantly check the state of the mouse
assert(drawing.mode == 'drawing')
if App.mouse_down(1) then
if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'move' then
drawing.pending.target_point.x = mx
drawing.pending.target_point.y = my
Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
end
end
drawing.pending.target_point.x = mx
drawing.pending.target_point.y = my
Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
end
else
-- do nothing
end
end
function Drawing.relax_constraints(drawing, p)
for _,shape in ipairs(drawing.shapes) do
if shape.mode == 'manhattan' then
if shape.p1 == p then
shape.mode = 'line'
elseif shape.p2 == p then
shape.mode = 'line'
end
elseif shape.mode == 'rectangle' or shape.mode == 'square' then
for _,v in ipairs(shape.vertices) do
if v == p then
shape.mode = 'polygon'
end
end
end
end
end
end
elseif State.lines.current_drawing then
local drawing = State.lines.current_drawing
local line_cache = State.line_cache[State.lines.current_drawing_index]
-- the last point added during update is good enough
end
if math.abs(mx-p1.x) > math.abs(my-p1.y) then
else
end
end
end
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
end
else
-- too few points; draw nothing
end
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
end
end
elseif drawing.pending.mode == 'circle' then
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local center = drawing.points[drawing.pending.center]
table.insert(drawing.shapes, drawing.pending)
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
table.insert(drawing.shapes, drawing.pending)
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))
elseif drawing.pending.mode == 'square' then
assert(#drawing.pending.vertices <= 2)
if #drawing.pending.vertices == 2 then
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local first = drawing.points[drawing.pending.vertices[1]]
local second = drawing.points[drawing.pending.vertices[2]]
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
table.insert(drawing.shapes, drawing.pending)
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))
end
end
elseif drawing.pending.mode == 'name' then
-- drop it
else
assert(false)
print(drawing.pending.mode)
end
end
end
end
if chord == 'C-p' and not App.mouse_down(1) then
State.current_drawing_mode = 'freehand'
elseif App.mouse_down(1) and chord == 'l' then
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
drawing.pending.p1 = drawing.pending.vertices[1]
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
drawing.pending.p1 = drawing.pending.center
end
drawing.pending.mode = 'line'
elseif chord == 'C-l' and not App.mouse_down(1) then
elseif App.mouse_down(1) and chord == 'm' then
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'line' then
-- do nothing
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
drawing.pending.p1 = drawing.pending.vertices[1]
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
drawing.pending.p1 = drawing.pending.center
end
drawing.pending.mode = 'manhattan'
elseif chord == 'C-m' and not App.mouse_down(1) then
elseif chord == 'C-g' and not App.mouse_down(1) then
elseif App.mouse_down(1) and chord == 'g' then
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
if drawing.pending.vertices == nil then
drawing.pending.vertices = {drawing.pending.p1}
end
-- reuse existing vertices
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
drawing.pending.vertices = {drawing.pending.center}
end
drawing.pending.mode = 'polygon'
while #drawing.pending.vertices >= 2 do
table.remove(drawing.pending.vertices)
end
table.insert(drawing.pending.vertices, j)
drawing.pending.mode = 'arc'
local center = drawing.points[drawing.pending.center]
drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
drawing.pending.center = drawing.pending.p1
drawing.pending.center = drawing.pending.vertices[1]
end
drawing.pending.mode = 'circle'
if drawing then
end
elseif chord == 'C-n' and not App.mouse_down(1) then
local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State)
if drawing then
p.name = ''
end
if drawing then
for _,shape in ipairs(drawing.shapes) do
if Drawing.contains_point(shape, i) then
if shape.mode == 'polygon' then
local idx = table.find(shape.vertices, i)
assert(idx)
table.remove(shape.vertices, idx)
if #shape.vertices < 3 then
shape.mode = 'deleted'
end
else
shape.mode = 'deleted'
end
end
end
drawing.points[i].deleted = true
end
if drawing then
shape.mode = 'deleted'
end
if drawing then
drawing.show_help = true
end
end
end
if drawing.mode == 'drawing' then
end
end
end
return nil
end
if drawing.mode == 'drawing' then
for i,shape in ipairs(drawing.shapes) do
assert(shape)
if geom.on_shape(mx,my, drawing, shape) then
end
end
end
end
end
end
if drawing.mode == 'drawing' then
for i,point in ipairs(drawing.points) do
assert(point)
end
end
end
end
end
end
if drawing.mode == 'drawing' then
return drawing
end
end
end
end
function Drawing.contains_point(shape, p)
if shape.mode == 'freehand' then
-- not supported
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
return shape.p1 == p or shape.p2 == p
return table.find(shape.vertices, p)
elseif shape.mode == 'circle' then
return shape.center == p
elseif shape.mode == 'arc' then
return shape.center == p
-- ugh, how to support angles
elseif shape.mode == 'deleted' then
-- already done
else
print(shape.mode)
assert(false)
end
end
function Drawing.insert_point(points, x,y)
for i,point in ipairs(points) do
return i
end
end
table.insert(points, {x=x, y=y})
return #points
end
end
end
end
function table.find(h, x)
for k,v in pairs(h) do
if v == x then
return k
end
end
function Drawing.coord(n, width) -- pixels to parts
return math.floor(n*256/width)
function Drawing.pixels(n, width) -- parts to pixels
return math.floor(n*width/256)
return (cx-px)*(cx-px) + (cy-py)*(cy-py) < Same_point_distance*Same_point_distance
function Drawing.near(point, x,y, width)
local px,py = Drawing.pixels(x, width),Drawing.pixels(y, width)
local cx,cy = Drawing.pixels(point.x, width), Drawing.pixels(point.y, width)
if Drawing.near(point, x,y, width) then
table.insert(points, {x=x, y=y})
return #points
end
-- check if UI would snap the two points together
function Drawing.find_or_insert_point(points, x,y, width)
function round(num)
return math.floor(num+.5)
end
end
end
function Drawing.smoothen(shape)
assert(shape.mode == 'freehand')
for _=1,7 do
for i=2,#shape.points-1 do
local a = shape.points[i-1]
local b = shape.points[i]
local c = shape.points[i+1]
end
b.x = round((a.x + b.x + c.x)/3)
b.y = round((a.y + b.y + c.y)/3)
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
local x, y = App.mouse_x(), App.mouse_y()
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
function Drawing.select_drawing_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do
if Drawing.near(point, mx,my, State.width) then
return drawing_index,drawing,line_cache,i,point
local x, y = App.mouse_x(), App.mouse_y()
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
function Drawing.select_point_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do
return drawing,line_cache,i,shape
local x, y = App.mouse_x(), App.mouse_y()
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
function Drawing.select_shape_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
return drawing_index,drawing,line_cache
local x, y = App.mouse_x(), App.mouse_y()
for drawing_index,drawing in ipairs(State.lines) do
function Drawing.current_drawing(State)
local fourthx = firstx+deltay
local fourthy = firsty-deltax
return thirdx,thirdy, fourthx,fourthy
end
end
function Drawing.complete_rectangle(firstx,firsty, secondx,secondy, x,y)
if firstx == secondx then
end
if firsty == secondy then
end
local first_slope = (secondy-firsty)/(secondx-firstx)
-- slope of second edge:
-- -1/first_slope
-- equation of line containing the second edge:
-- y-secondy = -1/first_slope*(x-secondx)
-- => 1/first_slope*x + y + (- secondy - secondx/first_slope) = 0
-- now we want to find the point on this line that's closest to the mouse pointer.
-- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equation
local a = 1/first_slope
local c = -secondy - secondx/first_slope
-- slope of third edge = first_slope
-- equation of line containing third edge:
-- y - thirdy = first_slope*(x-thirdx)
-- => -first_slope*x + y + (-thirdy + thirdx*first_slope) = 0
-- now we want to find the point on this line that's closest to the first point
local a = -first_slope
local c = -thirdy + thirdx*first_slope
return thirdx,thirdy, fourthx,fourthy
end
function Drawing.complete_square(firstx,firsty, secondx,secondy, x,y)
-- use x,y only to decide which side of the first edge to complete the square on
local deltax = secondx-firstx
local deltay = secondy-firsty
local thirdx = secondx+deltay
local thirdy = secondy-deltax
if not geom.same_side(firstx,firsty, secondx,secondy, thirdx,thirdy, x,y) then
deltax = -deltax
deltay = -deltay
thirdx = secondx+deltay
thirdy = secondy-deltax
local fourthx = round(((firstx-a*firsty) - a*c) / (a*a + 1))
local fourthy = round((a*(-firstx + a*firsty) - c) / (a*a + 1))
local thirdx = round(((x-a*y) - a*c) / (a*a + 1))
local thirdy = round((a*(-x + a*y) - c) / (a*a + 1))
return secondx,y, firstx,y
return x,secondy, x,firsty
elseif chord == 'escape' and App.mouse_down(1) then
drawing.pending = {}
local _,drawing = Drawing.current_drawing(State)
elseif chord == 'C-h' and not App.mouse_down(1) then
local drawing = Drawing.select_drawing_at_mouse(State)
local drawing,_,_,shape = Drawing.select_shape_at_mouse(State)
elseif chord == 'C-d' and not App.mouse_down(1) then
local _,drawing,_,i,p = Drawing.select_point_at_mouse(State)
drawing.pending = {mode=State.current_drawing_mode, target_point=point_index}
State.lines.current_drawing_index = drawing_index
State.lines.current_drawing = drawing
-- don't clobber
end
State.current_drawing_mode = 'name'
State.previous_drawing_mode = State.current_drawing_mode
if State.previous_drawing_mode == nil then
end
State.current_drawing_mode = 'move'
drawing.pending = {mode=State.current_drawing_mode, target_point=p, target_point_index=i}
State.lines.current_drawing_index = drawing_index
State.lines.current_drawing = drawing
if State.previous_drawing_mode == nil then
State.previous_drawing_mode = State.current_drawing_mode
elseif chord == 'C-u' and not App.mouse_down(1) then
local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State)
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
drawing.pending.center = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
elseif App.mouse_down(1) and chord == 'o' then
State.current_drawing_mode = 'circle'
local _,drawing = Drawing.current_drawing(State)
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
elseif chord == 'C-o' and not App.mouse_down(1) then
State.current_drawing_mode = 'circle'
elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then
local _,drawing,line_cache = Drawing.current_drawing(State)
elseif App.mouse_down(1) and chord == 'r' then
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
if drawing.pending.vertices == nil then
drawing.pending.vertices = {drawing.pending.p1}
end
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
drawing.pending.vertices = {drawing.pending.center}
end
drawing.pending.mode = 'rectangle'
elseif App.mouse_down(1) and chord == 's' then
if drawing.pending.mode == 'freehand' then
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
if drawing.pending.vertices == nil then
drawing.pending.vertices = {drawing.pending.p1}
end
elseif drawing.pending.mode == 'polygon' then
while #drawing.pending.vertices > 2 do
table.remove(drawing.pending.vertices)
end
elseif drawing.pending.mode == 'rectangle' then
-- reuse existing (1-2) vertices
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
drawing.pending.vertices = {drawing.pending.center}
end
drawing.pending.mode = 'square'
table.insert(drawing.pending.vertices, j)
elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
local _,drawing,line_cache = Drawing.current_drawing(State)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
local _,drawing,line_cache = Drawing.current_drawing(State)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
State.current_drawing_mode = 'square'
local _,drawing = Drawing.current_drawing(State)
elseif chord == 'C-s' and not App.mouse_down(1) then
State.current_drawing_mode = 'square'
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'square' then
-- reuse existing (1-2) vertices
drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
State.current_drawing_mode = 'rectangle'
local _,drawing = Drawing.current_drawing(State)
elseif chord == 'C-r' and not App.mouse_down(1) then
State.current_drawing_mode = 'rectangle'
elseif drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
State.current_drawing_mode = 'polygon'
local _,drawing = Drawing.current_drawing(State)
State.current_drawing_mode = 'polygon'
State.current_drawing_mode = 'manhattan'
drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
State.current_drawing_mode = 'manhattan'
local drawing = Drawing.select_drawing_at_mouse(State)
State.current_drawing_mode = 'line'
drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
State.current_drawing_mode = 'line'
local _,drawing = Drawing.current_drawing(State)
function Drawing.keychord_pressed(State, chord)
State.lines.current_drawing.pending = {}
State.lines.current_drawing = nil
elseif drawing.pending.mode == 'arc' then
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local center = drawing.points[drawing.pending.center]
drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle)
table.insert(drawing.shapes, drawing.pending)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
elseif drawing.pending.mode == 'rectangle' then
assert(#drawing.pending.vertices <= 2)
if #drawing.pending.vertices == 2 then
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local first = drawing.points[drawing.pending.vertices[1]]
local second = drawing.points[drawing.pending.vertices[2]]
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
elseif drawing.pending.mode == 'polygon' then
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
table.insert(drawing.shapes, drawing.pending)
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
local p2 = drawing.points[drawing.pending.p2]
table.insert(drawing.shapes, drawing.pending)
App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width))
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)
elseif drawing.pending.mode == 'manhattan' then
local p1 = drawing.points[drawing.pending.p1]
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
Drawing.smoothen(drawing.pending)
table.insert(drawing.shapes, drawing.pending)
elseif drawing.pending.mode == 'line' then
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
table.insert(drawing.shapes, drawing.pending)
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
if drawing.pending then
if drawing.pending.mode == nil then
-- nothing pending
elseif drawing.pending.mode == 'freehand' then
function Drawing.mouse_released(State, x,y, button)
if State.current_drawing_mode == 'move' then
State.current_drawing_mode = State.previous_drawing_mode
State.previous_drawing_mode = nil
if State.lines.current_drawing then
State.lines.current_drawing.pending = {}
State.lines.current_drawing = nil
elseif State.current_drawing_mode == 'move' then
if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then
table.insert(drawing.pending.points, {x=mx, y=my})
local pmx, pmy = App.mouse_x(), App.mouse_y()
local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-line_cache.starty, State.width)
function Drawing.update(State)
if State.lines.current_drawing == nil then return end
local drawing = State.lines.current_drawing
local line_cache = State.line_cache[State.lines.current_drawing_index]
print(State.current_drawing_mode)
local cx = Drawing.coord(x-State.left, State.width)
if State.current_drawing_mode == 'freehand' then
elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
drawing.pending = {mode=State.current_drawing_mode, p1=j}
elseif State.current_drawing_mode == 'polygon' or State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square' then
drawing.pending = {mode=State.current_drawing_mode, vertices={j}}
elseif State.current_drawing_mode == 'circle' then
drawing.pending = {mode=State.current_drawing_mode, center=j}
elseif State.current_drawing_mode == 'move' then
local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}
local cy = Drawing.coord(y-line_cache.starty, State.width)
-- all the action is in mouse_released
-- nothing
elseif State.current_drawing_mode == 'name' then
function Drawing.mouse_pressed(State, drawing_index, x,y, button)
local drawing = State.lines[drawing_index]
local line_cache = State.line_cache[drawing_index]
function Drawing.in_drawing(drawing, line_cache, x,y, left,right)
if line_cache.starty == nil then return false end -- outside current page
local cx,cy = px(center.x), py(center.y)
love.graphics.circle('line', cx,cy, geom.dist(cx,cy, App.mouse_x(),App.mouse_y()))
local cx,cy = px(center.x), py(center.y)
love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
love.graphics.line(px(first.x),py(first.y), pmx,pmy)
love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
love.graphics.line(px(first.x),py(first.y), pmx,pmy)
love.graphics.line(px(prev.x),py(prev.y), pmx,pmy)
love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
love.graphics.line(px(p1.x),py(p1.y), px(p1.x),pmy)
love.graphics.line(px(p1.x),py(p1.y), pmx, py(p1.y))
local p1 = drawing.points[shape.p1]
love.graphics.line(px(p1.x),py(p1.y), pmx,pmy)
if shape.mode == nil then
-- nothing pending
elseif shape.mode == 'freehand' then
local shape_copy = deepcopy(shape)
Drawing.smoothen(shape_copy)
Drawing.draw_shape(drawing, shape_copy, top, left,right)
function Drawing.draw_pending_shape(drawing, top, left,right)
local width = right-left
local pmx,pmy = App.mouse_x(), App.mouse_y()
local mx = Drawing.coord(pmx-left, width)
local my = Drawing.coord(pmy-top, width)
-- recreate pixels from coords to precisely mimic how the drawing will look
-- after mouse_released
pmx,pmy = px(mx), py(my)
local function px(x) return Drawing.pixels(x, width)+left end
local function py(y) return Drawing.pixels(y, width)+top end
love.graphics.arc('line', 'open', px(center.x),py(center.y), Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
love.graphics.circle('line', px(center.x),py(center.y), Drawing.pixels(shape.radius, width))
-- TODO: clip
love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
love.graphics.line(px(p1.x),py(p1.y), px(p2.x),py(p2.y))
love.graphics.line(px(prev.x),py(prev.y), px(point.x),py(point.y))
function Drawing.draw_shape(drawing, shape, top, left,right)
local width = right-left
local function px(x) return Drawing.pixels(x, width)+left end
local function py(y) return Drawing.pixels(y, width)+top end
App.color(Current_stroke_color)
Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)
App.color(Stroke_color)
love.graphics.circle('fill', px(p.x),py(p.y), 2)
App.color(Focus_stroke_color)
love.graphics.circle('line', px(p.x),py(p.y), Same_point_distance)
if Drawing.near(p, mx,my, State.width) then
local function px(x) return Drawing.pixels(x, State.width)+State.left end
local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end
for i,p in ipairs(line.points) do
Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)
App.color(Stroke_color)
if geom.on_shape(mx,my, line, shape) then
App.color(Focus_stroke_color)
local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-line_cache.starty, State.width)
draw_help_without_mouse_pressed(State, line_index)
if App.mouse_down(1) and love.keyboard.isDown('h') then
draw_help_with_mouse_pressed(State, line_index)
icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)
-- All drawings span 100% of some conceptual 'page width' and divide it up
-- into 256 parts.
local pmx,pmy = App.mouse_x(), App.mouse_y()
App.color(Icon_color)
if icon[State.current_drawing_mode] then
icon[State.current_drawing_mode](State.right-22, line_cache.starty+4)
love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))
if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
function Drawing.draw(State, line_index, y)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
line_cache.starty = y
icon = {}
function icon.insert_drawing(x, y)
love.graphics.rectangle('line', x,y, 12,12)
love.graphics.line(4,y+6, 16,y+6)
love.graphics.line(10,y, 10,y+12)
end
function icon.freehand(x, y)
love.graphics.line(x+4,y+7,x+5,y+5)
love.graphics.line(x+5,y+5,x+7,y+4)
love.graphics.line(x+7,y+4,x+9,y+3)
love.graphics.line(x+9,y+3,x+10,y+5)
love.graphics.line(x+10,y+5,x+12,y+6)
love.graphics.line(x+12,y+6,x+13,y+8)
love.graphics.line(x+13,y+8,x+13,y+10)
love.graphics.line(x+13,y+10,x+14,y+12)
love.graphics.line(x+14,y+12,x+15,y+14)
love.graphics.line(x+15,y+14,x+15,y+16)
end
function icon.line(x, y)
love.graphics.line(x+4,y+2, x+16,y+18)
end
function icon.manhattan(x, y)
love.graphics.line(x+4,y+20, x+4,y+2)
love.graphics.line(x+4,y+2, x+10,y+2)
love.graphics.line(x+10,y+2, x+10,y+10)
love.graphics.line(x+10,y+10, x+18,y+10)
end
function icon.polygon(x, y)
love.graphics.line(x+8,y+2, x+14,y+2)
love.graphics.line(x+14,y+2, x+18,y+10)
love.graphics.line(x+18,y+10, x+10,y+18)
love.graphics.line(x+10,y+18, x+4,y+12)
love.graphics.line(x+4,y+12, x+8,y+2)
end
function icon.circle(x, y)
love.graphics.circle('line', x+10,y+10, 8)
end
function icon.square(x, y)
love.graphics.line(x+6,y+6, x+6,y+16)
love.graphics.line(x+6,y+16, x+16,y+16)
love.graphics.line(x+16,y+16, x+16,y+6)
love.graphics.line(x+16,y+6, x+6,y+6)
end
end
function icon.rectangle(x, y)
love.graphics.line(x+4,y+8, x+4,y+16)
love.graphics.line(x+4,y+16, x+16,y+16)
love.graphics.line(x+16,y+16, x+16,y+8)
love.graphics.line(x+16,y+8, x+4,y+8)
App.color(Icon_color)
love.graphics.print("Things you can do:", State.left+30,y)
y = y + State.line_height
y = y + State.line_height
y = y + State.line_height
y = y + State.line_height
y = y + State.line_height
y = y + State.line_height
if State.current_drawing_mode ~= 'freehand' then
y = y + State.line_height
end
end
end
end
end
end
end
end
love.graphics.print("You're currently drawing a "..current_shape(State, drawing.pending), State.left+30,y)
y = y + State.line_height
y = y + State.line_height
if State.current_drawing_mode == 'freehand' then
y = y + State.line_height
elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
y = y + State.line_height
elseif State.current_drawing_mode == 'circle' then
if drawing.pending.mode == 'circle' then
else
end
end
end
end
end
end
end
end
end
return 'freehand stroke'
return 'straight line'
return 'horizontal/vertical line'
return 'arc'
else
end
end
_bullet_indent = nil
function bullet_indent()
if _bullet_indent == nil then
local text = love.graphics.newText(love.graphics.getFont(), '* ')
_bullet_indent = text:getWidth()
end
return _bullet_indent
end
return State.current_drawing_mode
elseif State.current_drawing_mode == 'circle' and shape and shape.start_angle then
elseif State.current_drawing_mode == 'manhattan' then
elseif State.current_drawing_mode == 'line' then
function current_shape(State, shape)
if State.current_drawing_mode == 'freehand' then
App.color(Help_background_color)
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
if State.current_drawing_mode ~= 'square' then
y = y + State.line_height
love.graphics.print("* Press 's' to switch to drawing squares", State.left+30,y)
if State.current_drawing_mode ~= 'rectangle' then
y = y + State.line_height
love.graphics.print("* Press 'r' to switch to drawing rectangles", State.left+30,y)
if State.current_drawing_mode ~= 'polygon' then
y = y + State.line_height
love.graphics.print("* Press 'g' to switch to drawing polygons", State.left+30,y)
if State.current_drawing_mode ~= 'circle' then
y = y + State.line_height
love.graphics.print("* Press 'o' to switch to drawing circles/arcs", State.left+30,y)
if State.current_drawing_mode ~= 'manhattan' then
y = y + State.line_height
love.graphics.print("* Press 'm' to switch to drawing horizontal/vertical lines", State.left+30,y)
y = y + State.line_height
y = y + State.line_height
if State.current_drawing_mode ~= 'line' then
y = y + State.line_height
love.graphics.print("* Press 'l' to switch to drawing lines", State.left+30,y)
love.graphics.print("* Press 'esc' then release the mouse button to cancel the current shape", State.left+30,y)
if #drawing.pending.vertices < 2 then
y = y + State.line_height
else
y = y + State.line_height
y = y + State.line_height
love.graphics.print("* Press 'p' to replace the second vertex of the rectangle", State.left+30,y)
end
if #drawing.pending.vertices < 2 then
y = y + State.line_height
else
y = y + State.line_height
y = y + State.line_height
love.graphics.print("* Press 'p' to replace the second vertex of the square", State.left+30,y)
end
love.graphics.print('* Release the mouse button to finish drawing the square', State.left+30,y)
love.graphics.print("* Press 'p' to add a vertex to the square", State.left+30,y)
elseif State.current_drawing_mode == 'square' then
love.graphics.print('* Release the mouse button to finish drawing the rectangle', State.left+30,y)
love.graphics.print("* Press 'p' to add a vertex to the rectangle", State.left+30,y)
y = y + State.line_height
elseif State.current_drawing_mode == 'polygon' then
y = y + State.line_height
y = y + State.line_height
elseif State.current_drawing_mode == 'rectangle' then
love.graphics.print("* Press 'p' to add a vertex to the polygon", State.left+30,y)
love.graphics.print('* Release the mouse button to finish drawing the polygon', State.left+30,y)
love.graphics.print('* Release the mouse button to finish drawing the arc', State.left+30,y)
y = y + State.line_height
love.graphics.print("* Press 'a' to draw just an arc of a circle", State.left+30,y)
love.graphics.print('* Release the mouse button to finish drawing the circle', State.left+30,y)
love.graphics.print('* Release the mouse button to finish drawing the line', State.left+30,y)
love.graphics.print('* Release the mouse button to finish drawing the stroke', State.left+30,y)
love.graphics.print('Things you can do now:', State.left+30,y)
App.color(Help_color)
local y = line_cache.starty+10
function draw_help_with_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index]
local line_cache = State.line_cache[drawing_index]
y = y + State.line_height
y = y + State.line_height
love.graphics.print("Press 'esc' now to hide this message", State.left+30,y)
App.color(Help_background_color)
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
love.graphics.print("* Press 'ctrl+=' or 'ctrl+-' to zoom in or out, ctrl+0 to reset zoom", State.left+30,y)
if State.current_drawing_mode ~= 'square' then
y = y + State.line_height
love.graphics.print("* Press 'ctrl+s' to switch to drawing squares", State.left+30,y)
if State.current_drawing_mode ~= 'rectangle' then
y = y + State.line_height
love.graphics.print("* Press 'ctrl+r' to switch to drawing rectangles", State.left+30,y)
if State.current_drawing_mode ~= 'polygon' then
y = y + State.line_height
love.graphics.print("* Press 'ctrl+g' to switch to drawing polygons", State.left+30,y)
if State.current_drawing_mode ~= 'circle' then
y = y + State.line_height
love.graphics.print("* Press 'ctrl+o' to switch to drawing circles/arcs", State.left+30,y)
if State.current_drawing_mode ~= 'manhattan' then
y = y + State.line_height
love.graphics.print("* Press 'ctrl+m' to switch to drawing horizontal/vertical lines", State.left+30,y)
if State.current_drawing_mode ~= 'line' then
y = y + State.line_height
love.graphics.print("* Press 'ctrl+l' to switch to drawing lines", State.left+30,y)
love.graphics.print("* Press 'ctrl+p' to switch to drawing freehand strokes", State.left+30,y)
love.graphics.print("* Hover on a point or shape and press 'ctrl+d' to delete it", State.left+30,y)
love.graphics.print("* Hover on a point and press 'ctrl+n', type a name, then press 'enter'", State.left+30,y)
love.graphics.print("then press the mouse button to drop it", State.left+30+bullet_indent(),y)
love.graphics.print("* Hover on a point and press 'ctrl+u' to pick it up and start moving it,", State.left+30,y)
love.graphics.print("* Press the mouse button to start drawing a "..current_shape(State), State.left+30,y)
App.color(Help_color)
local y = line_cache.starty+10
function draw_help_without_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index]
local line_cache = State.line_cache[drawing_index]
function geom.on_shape(x,y, drawing, shape)
if shape.mode == 'freehand' then
return geom.on_freehand(x,y, drawing, shape)
elseif shape.mode == 'line' then
return geom.on_line(x,y, drawing, shape)
elseif shape.mode == 'manhattan' then
return geom.on_polygon(x,y, drawing, shape)
elseif shape.mode == 'circle' then
local center = drawing.points[shape.center]
elseif shape.mode == 'arc' then
local center = drawing.points[shape.center]
if dist < shape.radius*0.95 or dist > shape.radius*1.05 then
return false
end
return geom.angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)
elseif shape.mode == 'deleted' then
else
print(shape.mode)
assert(false)
end
end
function geom.on_freehand(x,y, drawing, shape)
local prev
for _,p in ipairs(shape.points) do
if prev then
if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
return true
end
end
prev = p
end
return false
end
function geom.on_line(x,y, drawing, shape)
local p1,p2
if type(shape.p1) == 'number' then
p1 = drawing.points[shape.p1]
p2 = drawing.points[shape.p2]
else
p1 = shape.p1
p2 = shape.p2
end
if p1.x == p2.x then
return false
end
local y1,y2 = p1.y,p2.y
if y1 > y2 then
y1,y2 = y2,y1
end
end
-- has the right slope and intercept
local m = (p2.y - p1.y) / (p2.x - p1.x)
local yp = p1.y + m*(x-p1.x)
return false
end
-- between endpoints
local k = (x-p1.x) / (p2.x-p1.x)
end
function geom.on_polygon(x,y, drawing, shape)
local prev
for _,p in ipairs(shape.vertices) do
if prev then
if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
return true
end
end
prev = p
end
return geom.on_line(x,y, drawing, {p1=shape.vertices[1], p2=shape.vertices[#shape.vertices]})
end
function geom.angle_with_hint(x1, y1, x2, y2, hint)
local result = geom.angle(x1,y1, x2,y2)
if hint then
-- Smooth the discontinuity where angle goes from positive to negative.
-- The hint is a memory of which way we drew it last time.
while result > hint+math.pi/10 do
result = result-math.pi*2
end
while result < hint-math.pi/10 do
result = result+math.pi*2
end
end
return result
end
-- result is from -π/2 to 3π/2, approximately adding math.atan2 from Lua 5.3
-- (LÖVE is Lua 5.1)
function geom.angle(x1,y1, x2,y2)
local result = math.atan((y2-y1)/(x2-x1))
if x2 < x1 then
result = result+math.pi
end
return result
end
-- is the line between x,y and cx,cy at an angle between s and e?
function geom.angle_between(ox,oy, x,y, s,e)
if s > e then
s,e = e,s
end
-- I'm not sure this is right or ideal..
angle = angle-math.pi*2
if s <= angle and angle <= e then
return true
end
angle = angle+math.pi*2
if s <= angle and angle <= e then
return true
end
angle = angle+math.pi*2
return s <= angle and angle <= e
end
function geom.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 end
local angle = geom.angle(ox,oy, x,y)
end
-- are (x3,y3) and (x4,y4) on the same side of the line between (x1,y1) and (x2,y2)
function geom.same_side(x1,y1, x2,y2, x3,y3, x4,y4)
if x1 == x2 then
return math.sign(x3-x1) == math.sign(x4-x1)
end
if y1 == y2 then
return math.sign(y3-y1) == math.sign(y4-y1)
end
local m = (y2-y1)/(x2-x1)
return math.sign(m*(x3-x1) + y1-y3) == math.sign(m*(x4-x1) + y1-y4)
end
function math.sign(x)
if x > 0 then
return 1
elseif x == 0 then
return 0
elseif x < 0 then
return -1
end
return k > -0.005 and k < 1.005
if yp < y-2 or yp > y+2 then
return y >= y1-2 and y <= y2+2
if math.abs(p1.x-x) > 2 then
local dist = geom.dist(center.x,center.y, x,y)
local dist = geom.dist(center.x,center.y, x,y)
return dist > shape.radius*0.95 and dist < shape.radius*1.05
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
local p1 = drawing.points[shape.p1]
local p2 = drawing.points[shape.p2]
if p1.x == p2.x then
if x ~= p1.x then return false end
local y1,y2 = p1.y, p2.y
if y1 > y2 then
y1,y2 = y2,y1
end
elseif p1.y == p2.y then
if y ~= p1.y then return false end
local x1,x2 = p1.x, p2.x
if x1 > x2 then
x1,x2 = x2,x1
end
end
return x >= x1-2 and x <= x2+2
return y >= y1-2 and y <= y2+2
geom = {}
if line.mode == 'text' then
table.insert(event.lines, {mode='text', data=line.data})
elseif line.mode == 'drawing' then
local points=deepcopy(line.points)
--? print('copying', line.points, 'with', #line.points, 'points into', points)
local shapes=deepcopy(line.shapes)
--? print('copying', line.shapes, 'with', #line.shapes, 'shapes into', shapes)
table.insert(event.lines, {mode='drawing', h=line.h, points=points, shapes=shapes, pending={}})
--? table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
else
print(line.mode)
assert(false)
end
table.insert(event.lines, State.lines[i])
end
function test_click_to_create_drawing()
io.write('\ntest_click_to_create_drawing')
App.screen.init{width=120, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{}
Text.redraw_all(Editor_state)
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
-- cursor skips drawing to always remain on text
check_eq(#Editor_state.lines, 2, 'F - test_click_to_create_drawing/#lines')
check_eq(Editor_state.cursor1.line, 2, 'F - test_click_to_create_drawing/cursor')
end
function test_backspace_to_delete_drawing()
io.write('\ntest_backspace_to_delete_drawing')
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
App.screen.init{width=120, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'```lines', '```', ''}
Text.redraw_all(Editor_state)
-- cursor is on text as always (outside tests this will get initialized correctly)
Editor_state.cursor1.line = 2
-- backspacing deletes the drawing
edit.run_after_keychord(Editor_state, 'backspace')
check_eq(#Editor_state.lines, 1, 'F - test_backspace_to_delete_drawing/#lines')
check_eq(Editor_state.cursor1.line, 1, 'F - test_backspace_to_delete_drawing/cursor')
check_eq(Editor_state.lines[1].data, '', 'F - test_insert_newline_at_start_of_line/data:1')
check_eq(Editor_state.lines[2].data, 'abc', 'F - test_insert_newline_at_start_of_line/data:2')
check_eq(Editor_state.lines[1], '', 'F - test_insert_newline_at_start_of_line/data:1')
check_eq(Editor_state.lines[2], 'abc', 'F - test_insert_newline_at_start_of_line/data:2')
end
function test_pagedown_skips_drawings()
io.write('\ntest_pagedown_skips_drawings')
-- some lines of text with a drawing intermixed
local drawing_width = 50
App.screen.init{width=Editor_state.left+drawing_width, height=80}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'abc', -- height 15
'```lines', '```', -- height 25
'def', -- height 15
'ghi'} -- height 15
Text.redraw_all(Editor_state)
check_eq(Editor_state.lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
local drawing_height = Drawing_padding_height + drawing_width/2 -- default
-- initially the screen displays the first line and the drawing
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')
-- after pagedown the screen draws the drawing up top
-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
edit.run_after_keychord(Editor_state, 'pagedown')
check_eq(Editor_state.screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')
y = Editor_state.top + drawing_height
App.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')
check_eq(Editor_state.lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
check_eq(Editor_state.lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
check_eq(Editor_state.lines[1], 'akl', "F - test_backspace_over_multiple_lines/data:1")
check_eq(Editor_state.lines[2], 'mno', "F - test_backspace_over_multiple_lines/data:2")
check_eq(Editor_state.lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
check_eq(Editor_state.lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
check_eq(Editor_state.lines[1], 'a', "F - test_backspace_to_start_of_line/data:1")
check_eq(Editor_state.lines[2], 'def', "F - test_backspace_to_start_of_line/data:2")
check_eq(Editor_state.lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
check_eq(Editor_state.lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
check_eq(Editor_state.lines[1], 'abc', "F - test_backspace_to_start_of_line/data:1")
check_eq(Editor_state.lines[2], 'f', "F - test_backspace_to_start_of_line/data:2")
if State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then
if State.lines[State.cursor1.line]:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then
local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)
local byte_offset = Text.offset(State.lines[State.cursor1.line], State.cursor1.pos)
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line], byte_offset)
local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos-1)
local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
local byte_start = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos-1)
local byte_end = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos)
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_start-1)..string.sub(State.lines[State.cursor1.line], byte_end)
if State.lines[State.cursor1.line-1].mode == 'drawing' then
table.remove(State.lines, State.cursor1.line-1)
table.remove(State.line_cache, State.cursor1.line-1)
else
-- join lines
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
table.remove(State.lines, State.cursor1.line)
table.remove(State.line_cache, State.cursor1.line)
end
-- join lines
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1])+1
State.lines[State.cursor1.line-1] = State.lines[State.cursor1.line-1]..State.lines[State.cursor1.line]
table.remove(State.lines, State.cursor1.line)
table.remove(State.line_cache, State.cursor1.line)
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos+1)
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line]) then
local byte_start = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos)
local byte_end = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos+1)
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_start-1)..string.sub(State.lines[State.cursor1.line], byte_end)
if State.lines[State.cursor1.line+1].mode == 'text' then
-- join lines
State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
end
-- join lines
State.lines[State.cursor1.line] = State.lines[State.cursor1.line]..State.lines[State.cursor1.line+1]
local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})
local byte_offset = Text.offset(State.lines[State.cursor1.line], State.cursor1.pos)
table.insert(State.lines, State.cursor1.line+1, string.sub(State.lines[State.cursor1.line], byte_offset))
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_offset-1)
if State.lines[State.screen_top1.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
end
y = y - State.line_height
local new_cursor_line = State.cursor1.line
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line-1
if State.lines[new_cursor_line].mode == 'text' then
--? print('found previous text line')
State.cursor1.line = new_cursor_line
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
-- previous text line found, pick its final screen line
--? print('has multiple screen lines')
local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
--? print(#screen_line_starting_pos)
screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
--? print('previous screen line starts at pos '..tostring(screen_line_starting_pos)..' of its line')
if State.screen_top1.line > State.cursor1.line then
State.screen_top1.line = State.cursor1.line
State.screen_top1.pos = screen_line_starting_pos
--? print('pos of top of screen is also '..tostring(State.screen_top1.pos)..' of the same line')
end
local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
break
if State.cursor1.line > 1 then
local new_cursor_line = State.cursor1.line-1
--? print('found previous text line')
State.cursor1.line = new_cursor_line
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
-- previous text line found, pick its final screen line
--? print('has multiple screen lines')
local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
--? print(#screen_line_starting_pos)
screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
--? print('previous screen line starts at pos '..tostring(screen_line_starting_pos)..' of its line')
if State.screen_top1.line > State.cursor1.line then
State.screen_top1.line = State.cursor1.line
State.screen_top1.pos = screen_line_starting_pos
--? print('pos of top of screen is also '..tostring(State.screen_top1.pos)..' of the same line')
local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line], screen_line_starting_pos)
local s = string.sub(State.lines[State.cursor1.line], screen_line_starting_byte_offset)
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line], new_screen_line_starting_pos)
local s = string.sub(State.lines[State.cursor1.line], new_screen_line_starting_byte_offset)
local new_cursor_line = State.cursor1.line
while new_cursor_line < #State.lines do
new_cursor_line = new_cursor_line+1
if State.lines[new_cursor_line].mode == 'text' then
State.cursor1.line = new_cursor_line
State.cursor1.pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line].data, State.cursor_x, State.left)
--? print(State.cursor1.pos)
break
end
if State.cursor1.line < #State.lines then
local new_cursor_line = State.cursor1.line+1
State.cursor1.line = new_cursor_line
State.cursor1.pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line], State.cursor_x, State.left)
--? print(State.cursor1.pos)
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line], new_screen_line_starting_pos)
local s = string.sub(State.lines[State.cursor1.line], new_screen_line_starting_byte_offset)
else
local new_cursor_line = State.cursor1.line
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line-1
if State.lines[new_cursor_line].mode == 'text' then
State.cursor1.line = new_cursor_line
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
break
end
end
elseif State.cursor1.line > 1 then
State.cursor1.line = State.cursor1.line-1
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line]) + 1
else
local new_cursor_line = State.cursor1.line
while new_cursor_line <= #State.lines-1 do
new_cursor_line = new_cursor_line+1
if State.lines[new_cursor_line].mode == 'text' then
State.cursor1.line = new_cursor_line
State.cursor1.pos = 1
break
end
end
elseif State.cursor1.line <= #State.lines-1 then
State.cursor1.line = State.cursor1.line+1
State.cursor1.pos = 1
local y = State.top
while State.cursor1.line <= #State.lines do
if State.lines[State.cursor1.line].mode == 'text' then
break
end
--? print('cursor skips', State.cursor1.line)
y = y + Drawing_padding_height + Drawing.pixels(State.lines[State.cursor1.line].h, State.width)
State.cursor1.line = State.cursor1.line + 1
end
-- hack: insert a text line at bottom of file if necessary
if State.cursor1.line > #State.lines then
assert(State.cursor1.line == #State.lines+1)
table.insert(State.lines, {mode='text', data=''})
table.insert(State.line_cache, {})
end
--? print(y, App.screen.height, App.screen.height-State.line_height)
if y > App.screen.height - State.line_height then
if State.top > App.screen.height - State.line_height then
if top2.screen_line > 1 or State.lines[top2.line-1].mode == 'text' then
local h = State.line_height
if y - h < State.top then
break
end
y = y - h
else
assert(top2.line > 1)
assert(State.lines[top2.line-1].mode == 'drawing')
-- We currently can't draw partial drawings, so either skip it entirely
-- or not at all.
local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
if y - h < State.top then
break
end
--? print('skipping drawing of height', h)
y = y - h
local h = State.line_height
if y - h < State.top then
break
local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos)
--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset))
local screen_line_starting_byte_offset = Text.offset(line, screen_line_starting_pos)
--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line, screen_line_starting_byte_offset))
local past_end_offset = Text.offset(line.data, past_end_pos)
screen_line = string.sub(line.data, start_offset, past_end_offset-1)
local past_end_offset = Text.offset(line, past_end_pos)
screen_line = string.sub(line, start_offset, past_end_offset-1)
local lo_offset = Text.offset(line.data, lo)
local hi_offset = Text.offset(line.data, hi)
local pos_offset = Text.offset(line.data, pos)
local lo_offset = Text.offset(line, lo)
local hi_offset = Text.offset(line, hi)
local pos_offset = Text.offset(line, pos)
if line.mode == 'text' then
if Text.in_line(State, line_index, x,y) then
return line_index, Text.to_pos_on_line(State, line_index, x,y)
end
if Text.in_line(State, line_index, x,y) then
return line_index, Text.to_pos_on_line(State, line_index, x,y)
local min_offset = Text.offset(State.lines[minl].data, minp)
local max_offset = Text.offset(State.lines[maxl].data, maxp)
local min_offset = Text.offset(State.lines[minl], minp)
local max_offset = Text.offset(State.lines[maxl], maxp)
if State.lines[i].mode == 'text' then
table.insert(result, State.lines[i].data)
end
table.insert(result, State.lines[i])
check_eq(Editor_state.lines[1].data, 'abc', 'F - test_drop_file/lines:1')
check_eq(Editor_state.lines[2].data, 'def', 'F - test_drop_file/lines:2')
check_eq(Editor_state.lines[3].data, 'ghi', 'F - test_drop_file/lines:3')
check_eq(Editor_state.lines[1], 'abc', 'F - test_drop_file/lines:1')
check_eq(Editor_state.lines[2], 'def', 'F - test_drop_file/lines:2')
check_eq(Editor_state.lines[3], 'ghi', 'F - test_drop_file/lines:3')
if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
table.insert(result, load_drawing(infile_next_line))
else
table.insert(result, {mode='text', data=line})
end
table.insert(result, line)
json = require 'json'
function load_drawing(infile_next_line)
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
while true do
local line = infile_next_line()
assert(line)
if line == '```' then break end
local shape = json.decode(line)
if shape.mode == 'freehand' then
-- no changes needed
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
local name = shape.p1.name
shape.p1 = Drawing.insert_point(drawing.points, shape.p1.x, shape.p1.y)
drawing.points[shape.p1].name = name
name = shape.p2.name
shape.p2 = Drawing.insert_point(drawing.points, shape.p2.x, shape.p2.y)
drawing.points[shape.p2].name = name
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
for i,p in ipairs(shape.vertices) do
local name = p.name
shape.vertices[i] = Drawing.insert_point(drawing.points, p.x,p.y)
drawing.points[shape.vertices[i]].name = name
end
elseif shape.mode == 'circle' or shape.mode == 'arc' then
local name = shape.center.name
shape.center = Drawing.insert_point(drawing.points, shape.center.x,shape.center.y)
drawing.points[shape.center].name = name
elseif shape.mode == 'deleted' then
-- ignore
else
print(shape.mode)
assert(false)
end
table.insert(drawing.shapes, shape)
end
return drawing
end
function store_drawing(outfile, drawing)
outfile:write('```lines\n')
for _,shape in ipairs(drawing.shapes) do
if shape.mode == 'freehand' then
outfile:write(json.encode(shape), '\n')
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
outfile:write(line, '\n')
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
local obj = {mode=shape.mode, vertices={}}
for _,p in ipairs(shape.vertices) do
table.insert(obj.vertices, drawing.points[p])
end
local line = json.encode(obj)
outfile:write(line, '\n')
elseif shape.mode == 'circle' then
outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}), '\n')
elseif shape.mode == 'arc' then
outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle}), '\n')
elseif shape.mode == 'deleted' then
-- ignore
else
print(shape.mode)
assert(false)
end
end
outfile:write('```\n')
end
--? print(line)
if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
--? print('inserting drawing')
i, drawing = load_drawing_from_array(next_line, a, i)
--? print('i now', i)
table.insert(result, drawing)
else
--? print('inserting text')
table.insert(result, {mode='text', data=line})
end
table.insert(result, line)
end
function load_drawing_from_array(iter, a, i)
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
local line
while true do
i, line = iter(a, i)
assert(i)
--? print(i)
if line == '```' then break end
local shape = json.decode(line)
if shape.mode == 'freehand' then
-- no changes needed
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
local name = shape.p1.name
shape.p1 = Drawing.insert_point(drawing.points, shape.p1.x, shape.p1.y)
drawing.points[shape.p1].name = name
name = shape.p2.name
shape.p2 = Drawing.insert_point(drawing.points, shape.p2.x, shape.p2.y)
drawing.points[shape.p2].name = name
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
for i,p in ipairs(shape.vertices) do
local name = p.name
shape.vertices[i] = Drawing.insert_point(drawing.points, p.x,p.y)
drawing.points[shape.vertices[i]].name = name
end
elseif shape.mode == 'circle' or shape.mode == 'arc' then
local name = shape.center.name
shape.center = Drawing.insert_point(drawing.points, shape.center.x,shape.center.y)
drawing.points[shape.center].name = name
elseif shape.mode == 'deleted' then
-- ignore
else
print(shape.mode)
assert(false)
end
table.insert(drawing.shapes, shape)
end
return i, drawing
Drawing_padding_top = 10
Drawing_padding_bottom = 10
Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom
Same_point_distance = 4 -- pixel distance at which two points are considered the same
-- a line is either text or a drawing
-- a text is a table with:
-- mode = 'text',
-- string data,
-- a drawing is a table with:
-- mode = 'drawing'
-- a (y) coord in pixels (updated while painting screen),
-- a (h)eight,
-- an array of points, and
-- an array of shapes
-- a shape is a table containing:
-- a mode
-- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)
-- an array vertices for mode 'polygon', 'rectangle', 'square'
-- p1, p2 for mode 'line'
-- center, radius for mode 'circle'
-- center, radius, start_angle, end_angle for mode 'arc'
-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide
-- The field names are carefully chosen so that switching modes in midstream
-- remembers previously entered points where that makes sense.
lines = {{mode='text', data=''}}, -- array of lines
lines = {''}, -- array of strings
if line.mode == 'text' then
--? print('text.draw', y, line_index)
local startpos = 1
if line_index == State.screen_top1.line then
startpos = State.screen_top1.pos
end
if line.data == '' then
-- button to insert new drawing
button('draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},
icon = icon.insert_drawing,
onpress1 = function()
Drawing.before = snapshot(State, line_index-1, line_index)
table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})
table.insert(State.line_cache, line_index, {})
if State.cursor1.line >= line_index then
State.cursor1.line = State.cursor1.line+1
end
schedule_save(State)
record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
end,
})
end
y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
y = y + State.line_height
--? print('=> y', y)
elseif line.mode == 'drawing' then
y = y+Drawing_padding_top
Drawing.draw(State, line_index, y)
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
else
print(line.mode)
assert(false)
--? print('text.draw', y, line_index)
local startpos = 1
if line_index == State.screen_top1.line then
startpos = State.screen_top1.pos
if line.mode == 'text' then
if Text.in_line(State, line_index, x,y) then
-- delicate dance between cursor, selection and old cursor/selection
-- scenarios:
-- regular press+release: sets cursor, clears selection
-- shift press+release:
-- sets selection to old cursor if not set otherwise leaves it untouched
-- sets cursor
-- press and hold to start a selection: sets selection on press, cursor on release
-- press and hold, then press shift: ignore shift
-- i.e. mouse_released should never look at shift state
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? print('selection', State.selection1.line, State.selection1.pos)
break
end
elseif line.mode == 'drawing' then
local line_cache = State.line_cache[line_index]
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index)
Drawing.mouse_pressed(State, line_index, x,y, mouse_button)
break
end
if Text.in_line(State, line_index, x,y) then
-- delicate dance between cursor, selection and old cursor/selection
-- scenarios:
-- regular press+release: sets cursor, clears selection
-- shift press+release:
-- sets selection to old cursor if not set otherwise leaves it untouched
-- sets cursor
-- press and hold to start a selection: sets selection on press, cursor on release
-- press and hold, then press shift: ignore shift
-- i.e. mouse_released should never look at shift state
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? print('selection', State.selection1.line, State.selection1.pos)
break
if State.lines.current_drawing then
Drawing.mouse_released(State, x,y, mouse_button)
schedule_save(State)
if Drawing.before then
record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
Drawing.before = nil
end
else
for line_index,line in ipairs(State.lines) do
if line.mode == 'text' then
if Text.in_line(State, line_index, x,y) then
--? print('reset selection')
State.cursor1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? print('cursor', State.cursor1.line, State.cursor1.pos)
if State.mousepress_shift then
if State.old_selection1.line == nil then
State.selection1 = State.old_cursor1
else
State.selection1 = State.old_selection1
end
end
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
if eq(State.cursor1, State.selection1) then
State.selection1 = {}
end
break
for line_index,line in ipairs(State.lines) do
if Text.in_line(State, line_index, x,y) then
--? print('reset selection')
State.cursor1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? print('cursor', State.cursor1.line, State.cursor1.pos)
if State.mousepress_shift then
if State.old_selection1.line == nil then
State.selection1 = State.old_cursor1
else
State.selection1 = State.old_selection1
elseif State.current_drawing_mode == 'name' then
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
p.name = p.name..t
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
-- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then
local before = snapshot(State, drawing_index)
Drawing.keychord_pressed(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
schedule_save(State)
end
elseif chord == 'escape' and not App.mouse_down(1) then
for _,line in ipairs(State.lines) do
if line.mode == 'drawing' then
line.show_help = false
end
end
elseif State.current_drawing_mode == 'name' then
if chord == 'return' then
State.current_drawing_mode = State.previous_drawing_mode
State.previous_drawing_mode = nil
else
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
if chord == 'escape' then
p.name = nil
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
elseif chord == 'backspace' then
local len = utf8.len(p.name)
local byte_offset = Text.offset(p.name, len-1)
if len == 1 then byte_offset = 0 end
p.name = string.sub(p.name, 1, byte_offset)
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
end
end
schedule_save(State)
-- dispatch to text
Updates to lines.love can be downloaded from the following mirrors in addition
to the website above:
* https://github.com/akkartik/lines.love
* https://repo.or.cz/lines.love.git
* https://codeberg.org/akkartik/lines.love
* https://tildegit.org/akkartik/lines.love
* https://git.tilde.institute/akkartik/lines.love
* https://git.sr.ht/~akkartik/lines.love
* https://notabug.org/akkartik/lines.love
* https://pagure.io/lines.love
This repo is a fork of lines.love at [http://akkartik.name/lines.html](http://akkartik.name/lines.html).
Updates to it can be downloaded from the following mirrors:
Forks of lines.love are encouraged. If you show me your fork, I'll link to it
here.
* https://github.com/akkartik/lines-polygon-experiment -- an experiment that
uses separate shortcuts for regular polygons. `ctrl+3` for triangles,
`ctrl+4` for squares, etc.
## Associated tools
* https://codeberg.org/akkartik/text.love
* https://repo.or.cz/text.love.git
* https://tildegit.org/akkartik/text.love
* https://git.tilde.institute/akkartik/text.love
* https://git.sr.ht/~akkartik/text.love
* https://notabug.org/akkartik/text.love
* https://github.com/akkartik/text.love
* https://pagure.io/text.love