OWE64YJ27IFNSGLLWIGYBXRNJ4XU55UYUINAHLOWG2LFEYJM73QAC 6ECYOEHY3BHYC6VYMR2AJV4H54NVKSTUKOMFBI3HRDS5V2JZ42JAC ELJNEPW26FUIIFY6D24274J7KZICRLE3TJHCFNRVLR5NZBNNV37AC PV2YA7KSWRCOKDS2WYO45WKE5L3CK56HPYT6DRVQRI3ZIE3B633AC APX2PY6GAMJSUH7SFSMBFOQJBSAWLLOCKH4L4ZQP2VLHNEXJPREAC RRDO6H7H2FIGMGKUBJWIVNVKDT5T2E4ZX3LPFSSAN6Z7OJ34ER5QC 5ITAXPEP2BCWZMUJ6T6ERIDYFNJRCJXY7SZPR6RNPSBTX4ZA7DAQC YRJFJNUDVPX2OAK7OKRYCVYDGMJI4XL4JASN4JJ7QGIPCVQI22TAC O4RRXNOK7GKGZB2AH3FULDJDQLVSQCQFWZLBMCDGNIRA63OSSMCAC TXI6GSQDOUHU4DWQTCMLVVCA2YSIAUZJWSFMH22QPO3W4NGNRF5AC ZWDTEUH7J2WM5VF5R2NCHN6FXXSMGJUZP4JSVMJKFZJTKVEBBSLQC WK6UK5AJS5RPR55LNME5IHQOIHYNEB6WWJHFXVSTBG6VR4ZA72MAC 6XCJX4DZB6UEAXEXXUGVVPBCF5SNDYOJGU3Q6BPB4ZMI5DZH4MYQC 5SM6DRHKPLFWQPCZDTVM4ENVENWBYBQ3Q2KYKSWLWYUTOSEPCRLAC 2QQNDEWTFMD6XE7TU43PNEU2CU4LWWLBOIKGYPLEKMIEVDVPCNKQC NPXYCSEAHZBZN5GY6EGMO5VDJQVHUXJHRSOZPX7YU6HHJHP74UNQC FPY4LO2WZNXUYCZ6MOQBSPGCC3M3PE7YX2TJIFRNEQLJ4ACWUPVAC NVSC4N4KMUHGX5FEOM2DXYA4VHEEQTP3QNY3D7WY4UONVW5LCT2QC 2MGBV7NPY2RVK7EZ7OJRWBYDUMADFZAFSBKLQFX74ERBVZODJLUQC J5NTUS2GD6LSISY3TIG7ZE34WCE2PYZ3TIN2H4CJRTMQYOQGOABAC OMLASW7KT5UWFXNGUFLLIKGDZ6OJETV5JAT53XKSNGMSML4WTOQQC FTB4YNQUDY5NCZYCRF5F46HPUIOPMSKVLGGB3XPAN6DUKDAA32LQC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC X3CQLBTR7ICDAVFZZRLAWWJZI2SNZAMRUWW7O4R25DIKDUHL75CQC 7VGDIPLCFDG3PVE4JH3WDKZ4A7PG5UYW7TLFFFOWN2JEUZYYTFJQC 2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC VN3MTXFKOWZ7J3IZVWM45RT2HFGXTMNITZZRNSZZMY3HLXGOWG5AC Z5WOBP27DT46JXRZGX6SNDHZSTY7G77V4FB6B62QQIGUZ7MT45TAC LNUHQOGHIOFGJXNGA3DZLYEASLYYDGLN2I3EDZY5ANASQAHCG3YQC PP2IIHL6EK4HBFFSYAQNV35BKIK6D4EL2JQOY7NZVJX2DXCLSMGAC UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC WJBZZQE4A4KLYGS2KA254I6VN2DVXDY4XKCNAE76GTMLLQGYCUOQC TGHAJBESCIEGWUE2D3FGLNOIAYT4D2IRGZKRXRMTUFW7QZETC7OAC SPNMXTYRSNPNQJNBTYDZSHYDZVZRPM4LI5QX7GR2TLTC6SPJX4DAC 2CK5QI7WA7M4IVSACFGOJYAIDKRUTZVMMPSFWEJTUNMWTN7AX4NAC 4EGQRXDANFLUYXADP3MNHZWP2LBH2P5VBVKNN5RT6ERGMBVSRI2AC QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC QQBP3G6WZSIOAFPOGHWJCX3E4K66KOPVYNPZRMKUBXPLDTJ32XSAC ERQKFTPVWZO4WJD2WRIV33JWTWZSF4HNTK2GD7QT5I5TIL3SOGKQC OWYI7OJT4JQK5R5ABHIYXJDVFBHQ2BRRLQ657OTND6KIPKGXGLBQC SW7BSBMJZLUDRMHO2HMCHQW6PPGCEBL4JSDTAHVYPCZLBEKY54XQC AF253GHLDMRDMDL5X3OV2UJ5NPKBBMHGH4B3R5IGR35KO6IKYPKQC APYPFFS3G6TDEUMIHQGMDBJNRNDTCNTPKI5M2AFACJ73P725XQRQC OGD5RAQK5BIZTFEYAKMVX33KNLYYJUIN2DW4KI2H5DHCVTZKU2TQC 5UKUADTWMNWPOPBBTXUXY7UNFW64DWANI2RQHKSCSZNWHTQM4GUAC KYNGDE2CKNOKUC2XMAS5MEU6YT2C3IW5SIZLOJE64G3ERT7BSWFAC CNCYMM6ABOXCRI2IP5A4T2OGBO5FQ7GWBXBP2OQYL4YET5BLJCGQC 2WGHUWE6DWRNUD7AJNLGT4I6PZ2EIVDQCZ7V7V7WSYPBRPYUUDUQC H6QZ7GRR2UBK4POXP6PNMYHHW54SJAQYTTFVPQK4YIJZ4FECAPPQC 2Y7YH7UPQWDNYDJN4BYY2MOHA36B2BIRX6DMIAKHJPQC7UP2R6NQC SPSW74Y5OJ54Y7VQ3SJFCJR5CYDKTR4A3TOEVZODDZLUSDDU2GZAC IFTYOERMW7P3I24WISZN35X3GWJ5MSMRYDRBK3L52GCZTPP3CWZQC PTDO2SOTXEI6FROZ2AVRFXSKKNKCRMPPTQSI5LWD45UVGDJPMSGQC 5Y24ZDZISLWYQ5ZILKUGMQJ2DBHIRKYPH2ZINMHSPUQWYASTC5ZQC MTJEVRJR5GLWUSK7HMIM4UXM6GS6O6YCRWJT3DUSU2RYMHCQNOEQC LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC 23MA4T3GWPOLM5S6JCNQJU2SRT7VQGYZ2JZJN26KA5MKI4LOCC4QC 3HVBAZPADERJZTVHKZWOBNGDWYPJKPOEDZCCI6TLAFU4BFEKFOKAC WLJCIXYMSTCNSYCFOEBQNDLBZ5D2Z3WTF4E4WYL5CFGIJ434FKNQC Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC S2MISTTMPEULTO6WRO4Q4NRUO7XC2PTZW3UBR7K7SO6JPZO6HBHAC BH7BT36LM3D7HF3GOHXUPVNKLJ5LFJHOHRLD3KTC5HA627M3II4AC WLWNS6FBT6D3HKOFWDPBKLK7KS73LJJLWLHNWX3YJ723OHJBZGDQC ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC OI4FPFINEROK6GNDEMOBTGSPYIULCLRGGT5W3H7VLM7VFH22GMWQC 5ZA3BRNYWKSGEBJ4JLA4UBC3LJPT5JBWYCU7PQYRSGX6MJMEWDIQC Z3BQO2RKMQO622REOOE3NSF3EMS4FKZOBEEE7JRVYVE4QXDRGQ6AC Z3IQ6A4RVO4W33IHGTJS3SYZAFWCW3Z2NWIOHQNJ7AXBUNISE6RQC GN3C6AGM5KFHXHAJFRQIHC27HFWQFPIMQ4J6TKZVAW6VMWR32CPAC 3MAZEQK5AR3IJJ2ENHHYDPDICIK645NE5QWR54Z52BHGHE6VR5XQC GFXWHTE6POBIOBUMRAWD5QS22JEO52EF4VTLMB4CDK4RLSCK7HCAC 7FPELAZBPC6545IA75OOVI5CJUKCV5OLBHVMWM3KMSKTZO6SOWNAC QJISOCHJGOGK4PTD4IGOBKBU2KMEXJZJCTBL6Y2ZNDVQ2WNR7N4AC MRA2Y3EEDV73ZFMWL3EBUXAWZYQSDK4E5FWISWHHJZ2YXTYYYTBAC PK5U572CFG4M7LEGO5UPJXXIYFWHYIVTLZN6FTQJBUTNFFREJ4SAC FKNXK2OAH4U2V2TXCHWE4C3Z5DROBIIPSXUWFKP7Q3DSNOKFFL5AC 52ZZ5TIEK5Z6VVXO7R3EFV5JTIWDA4IDD3YISXHHTHQMOAKVYJJQC QXVD2RIFQPTO3H6J3IJHRHRDRBW6C6MLUWMCEDQ6Z75SW2TMRBIQC 3OTESDW65UJ2W5RIXA6FNKRSD7TBCZTCCCEAYOQMEVGYZ6RCU34QC KTZQ57HVZU4XGWRPXBA27G4GXZFV74YYKJRXCJCE7UKDS7NGJVBAC C4VTBATAPF4ZE7XE7WOUZTMKED6DEOKXRKTOIN72WAM33EHFBUQAC 6RYLD5ONDIQFWU5CNL4NGHJQ2LNAZZFGTPXQJDNJGLNYAUOTUI7QC YFW4MNNPY452RIUIGW6NO7WAUWPBXXOKOJJBNZGU727PUBZFATSAC 6D5MOJS4KEFIOKCBR37NXQHAJSHFIWUALGQ6DTSKWZOPS4TBIQJAC K6DTOGOQG4YXWTFKHGZ3DIC23G5ZV3WQZQPRRDYIWEGLNQW6QDGQC PJEQCTBL2ZX5Q7NJQ3KCWSHHBV2QWGYS5NVMRDNK5LOOIIRRIPHAC X3F7ECSLGXCH6NBSDIH7LY47I4EG2RR5VFPEMM6ZVDYQIGFID4HQC TGZAJUEFRK3NTCDMPIIG7U2TGLDHK4U3JDNFAYX7NHXTJYBYEZIAC 7JH2ZT3FCW4WX52IJFSLGRVZWQQVYJXULSQM2BM4BJUZWG3IVGKAC BW2IUB3KA4AKD35DYLCUCUM4Z32FMKGZNUBQBAEDIQJJYPA547MAC 5BMR5HRT7GN5L4XB4ISP4JJP3ONZESHEEQBCTQE4EVEDL7MBSDGAC LDFXFRUOUESGMZ7Z6BCZUQFRFFRIAB67GSGN2BR2VLT2ONZPUV3QC 4KC7I3E2DIKLIP7LQRKB5WFA2Z5XZXAU46RFHNFQU5BVEJPDX6UQC function edit.put_cursor_on_first_text_line(State)for i,line in ipairs(State.lines) do
-- return y drawn untilfunction edit.draw(State)State.button_handlers = {}App.color(Text_color)if #State.lines ~= #State.line_cache thenprint(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))assert(false)endif not Text.le1(State.screen_top1, State.cursor1) thenprint(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)assert(false)endState.cursor_x = nilState.cursor_y = nillocal y = State.toplocal screen_bottom1 = {line=nil, pos=nil}--? print('== draw')for line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]--? print('draw:', y, line_index, line)if y + State.line_height > App.screen.height then break endscreen_bottom1.line = line_indexif line.mode == 'text' then--? print('text.draw', y, line_index)local startpos = 1if line_index == State.screen_top1.line thenstartpos = State.screen_top1.posendif line.data == '' then-- button to insert new drawingbutton(State, 'draw', {x=State.left-Margin_left+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 thenState.cursor1.line = State.cursor1.line+1endschedule_save(State)record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})end,})endy, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)--? print('=> y', y)elseif line.mode == 'drawing' theny = y+Drawing_padding_topDrawing.draw(State, line_index, y)y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottomelseprint(line.mode)assert(false)endendState.screen_bottom1 = screen_bottom1if State.search_term thenText.draw_search_bar(State)endreturn yendfunction edit.update(State, dt)Drawing.update(State, dt)if State.next_save and State.next_save < Current_time thensave_to_disk(State)State.next_save = nilendendfunction schedule_save(State)if State.next_save == nil thenState.next_save = Current_time + 3 -- short enough that you're likely to still remember what you didendendfunction edit.quit(State)-- make sure to save before quittingif State.next_save thensave_to_disk(State)-- give some time for the OS to flush everything to disklove.timer.sleep(0.1)endendfunction edit.mouse_press(State, x,y, mouse_button)if State.search_term then return end--? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then-- press on a button and it returned 'true' to short-circuitreturnendif y < State.top thenState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=State.screen_top1.line,pos=State.screen_top1.pos,}returnendfor line_index,line in ipairs(State.lines) do
State.cursor1 = {line=i, pos=1}break
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_release should never look at shift state--? print_and_log(('edit.mouse_press: in line %d'):format(line_index))State.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}returnendelseif line.mode == 'drawing' thenlocal line_cache = State.line_cache[line_index]if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) thenState.lines.current_drawing_index = line_indexState.lines.current_drawing = lineDrawing.before = snapshot(State, line_index)Drawing.mouse_press(State, line_index, x,y, mouse_button)returnendendend-- still here? click is below all screen linesState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=State.screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),}endfunction edit.mouse_release(State, x,y, mouse_button)if State.search_term then return end--? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.lines.current_drawing thenDrawing.mouse_release(State, x,y, mouse_button)schedule_save(State)if Drawing.before thenrecord_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})Drawing.before = nilendelse--? print_and_log('edit.mouse_release: no current drawing')for line_index,line in ipairs(State.lines) doif line.mode == 'text' thenif Text.in_line(State, line_index, x,y) then--? print_and_log(('edit.mouse_release: in line %d'):format(line_index))State.cursor1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.mousepress_shift thenif State.old_selection1.line == nil thenState.selection1 = State.old_cursor1elseState.selection1 = State.old_selection1endendState.old_cursor1, State.old_selection1, State.mousepress_shift = nilif eq(State.cursor1, State.selection1) thenState.selection1 = {}endbreakendendend--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))endendfunction edit.mouse_wheel_move(State, dx,dy)if dy > 0 thenState.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}edit.put_cursor_on_next_text_line(State)for i=1,math.floor(dy) doText.up(State)endelseif dy < 0 thenState.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}edit.put_cursor_on_next_text_line(State)for i=1,math.floor(-dy) doText.down(State)endendendfunction edit.text_input(State, t)--? print('text input', t)if State.search_term thenState.search_term = State.search_term..tText.search_next(State)elseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenlocal before = snapshot(State, State.lines.current_drawing_index)local drawing = State.lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]p.name = p.name..trecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})elselocal drawing_index, drawing = Drawing.current_drawing(State)if drawing_index == nil thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollText.text_input(State, t)endendschedule_save(State)endfunction edit.keychord_press(State, chord, key)if State.selection1.line andnot State.lines.current_drawing and-- printable character created using shift key => delete selection-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)(not App.shift_down() or utf8.len(key) == 1) andchord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) thenText.delete_selection(State, State.left, State.right)endif State.search_term thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollif chord == 'escape' thenState.search_term = nilState.cursor1 = State.search_backup.cursorState.screen_top1 = State.search_backup.screen_topState.search_backup = nilText.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leakselseif chord == 'return' thenState.search_term = nilState.search_backup = nilelseif chord == 'backspace' thenlocal len = utf8.len(State.search_term)local byte_offset = Text.offset(State.search_term, len)State.search_term = string.sub(State.search_term, 1, byte_offset-1)elseif chord == 'down' thenState.cursor1.pos = State.cursor1.pos+1Text.search_next(State)elseif chord == 'up' thenText.search_previous(State)endreturnelseif chord == 'C-f' thenState.search_term = ''State.search_backup = {cursor={line=State.cursor1.line, pos=State.cursor1.pos},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},}-- zoomelseif chord == 'C-=' thenedit.update_font_settings(State, State.font_height+2)Text.redraw_all(State)elseif chord == 'C--' thenif State.font_height > 2 thenedit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)endelseif chord == 'C-0' thenedit.update_font_settings(State, 20)Text.redraw_all(State)-- undoelseif chord == 'C-z' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal event = undo_event(State)if event thenlocal src = event.beforeState.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.after, event.before)patch_placeholders(State.line_cache, event.after, event.before)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)schedule_save(State)endelseif chord == 'C-y' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal event = redo_event(State)if event thenlocal src = event.afterState.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.before, event.after)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)schedule_save(State)end-- clipboardelseif chord == 'C-a' thenState.selection1 = {line=1, pos=1}State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}elseif chord == 'C-c' thenlocal s = Text.selection(State)if s thenApp.set_clipboard(s)endelseif chord == 'C-x' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal s = Text.cut_selection(State, State.left, State.right)if s thenApp.set_clipboard(s)endschedule_save(State)elseif chord == 'C-v' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.local before_line = State.cursor1.linelocal before = snapshot(State, before_line)local clipboard_data = App.get_clipboard()for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenText.insert_return(State)elseText.insert_at_cursor(State, c)endendif Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State, State.left, State.right)endschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})-- dispatch to drawing or textelseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then-- DON'T reset line_cache.starty herelocal drawing_index, drawing = Drawing.current_drawing(State)if drawing_index thenlocal before = snapshot(State, drawing_index)Drawing.keychord_press(State, chord)record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})schedule_save(State)endelseif chord == 'escape' and not App.mouse_down(1) thenfor _,line in ipairs(State.lines) doif line.mode == 'drawing' thenline.show_help = falseend
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenif chord == 'return' thenState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilelselocal before = snapshot(State, State.lines.current_drawing_index)local drawing = State.lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilrecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})elseif chord == 'backspace' thenlocal len = utf8.len(p.name)if len > 0 thenlocal byte_offset = Text.offset(p.name, len-1)if len == 1 then byte_offset = 0 endp.name = string.sub(p.name, 1, byte_offset)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})endendendschedule_save(State)elsefor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollText.keychord_press(State, chord)
endfunction edit.key_release(State, key, scancode)endfunction edit.update_font_settings(State, font_height)State.font_height = font_heightlove.graphics.setFont(love.graphics.newFont(State.font_height))State.line_height = math.floor(font_height*1.3)end--== some methods for tests-- Insulate tests from some key globals so I don't have to change the vast-- majority of tests when they're modified for the real app.Test_margin_left = 25Test_margin_right = 0function edit.initialize_test_state()-- if you change these values, tests will start failingreturn edit.initialize_state(15, -- top marginTest_margin_left,App.screen.width - Test_margin_right,14, -- font height assuming default LÖVE font15) -- line height
State.button_handlers = {}local screen_bottom1 = {line=nil, pos=nil}table.insert(State.line_cache, line_index, {})-- give some time for the OS to flush everything to disklove.timer.sleep(0.1)--? print_and_log(('edit.mouse_press: in line %d'):format(line_index))--? print('text input', t)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll-- undopatch_placeholders(State.line_cache, event.after, event.before)-- return y drawn untilif State.lines[State.cursor1.line].mode == 'text' thenbreakendState.cursor1.line = State.cursor1.line+1State.cursor1.pos = 1endfunction edit.put_cursor_on_next_text_line(State)while true doif State.cursor1.line >= #State.lines thenbreakendedit.draw(State)edit.update(State, 0)App.screen.contents = {}edit.mouse_release(State, x,y, mouse_button)function edit.run_after_mouse_release(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.draw(State)endedit.update(State, 0)App.screen.contents = {}edit.mouse_press(State, x,y, mouse_button)function edit.run_after_mouse_press(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.draw(State)endedit.update(State, 0)App.screen.contents = {}edit.mouse_release(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.mouse_press(State, x,y, mouse_button)function edit.run_after_mouse_click(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.draw(State)endedit.update(State, 0)App.screen.contents = {}edit.keychord_press(State, chord)edit.key_release(State, chord)function edit.run_after_keychord(State, chord)-- not all keys are text_inputendedit.draw(State)edit.update(State, 0)App.screen.contents = {}function edit.run_after_text_input(State, t)edit.keychord_press(State, t)edit.text_input(State, t)edit.key_release(State, t)-- TODO: handle chords of multiple keys-- all text_input events are also keypresses14, -- font height assuming default LÖVE font15) -- line heightendApp.screen.width - Test_margin_right,function edit.initialize_test_state()-- if you change these values, tests will start failingreturn edit.initialize_state(15, -- top marginTest_margin_left,Test_margin_right = 0Test_margin_left = 25-- Insulate tests from some key globals so I don't have to change the vast-- majority of tests when they're modified for the real app.--== some methods for testsendState.line_height = math.floor(font_height*1.3)love.graphics.setFont(love.graphics.newFont(State.font_height))function edit.update_font_settings(State, font_height)State.font_height = font_heightendfunction edit.key_release(State, key, scancode)endendText.keychord_press(State, chord)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelseschedule_save(State)endendif len > 0 thenlocal byte_offset = Text.offset(p.name, len-1)if len == 1 then byte_offset = 0 endp.name = string.sub(p.name, 1, byte_offset)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})endelseif chord == 'backspace' thenlocal len = utf8.len(p.name)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})local p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nillocal drawing = State.lines.current_drawinglocal before = snapshot(State, State.lines.current_drawing_index)elseState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilif chord == 'return' thenelseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenif line.mode == 'drawing' thenline.show_help = falseendendfor _,line in ipairs(State.lines) doendelseif chord == 'escape' and not App.mouse_down(1) thenschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})Drawing.keychord_press(State, chord)local before = snapshot(State, drawing_index)if drawing_index thenlocal drawing_index, drawing = Drawing.current_drawing(State)-- DON'T reset line_cache.starty here-- dispatch to drawing or textelseif App.mouse_down(1) or chord:sub(1,2) == 'C-' thenrecord_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})schedule_save(State)endText.snap_cursor_to_bottom_of_screen(State, State.left, State.right)if Text.cursor_out_of_screen(State) thenendendText.insert_at_cursor(State, c)elseText.insert_return(State)for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenlocal clipboard_data = App.get_clipboard()local before = snapshot(State, before_line)local before_line = State.cursor1.line-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelseif chord == 'C-v' thenschedule_save(State)endApp.set_clipboard(s)if s thenlocal s = Text.cut_selection(State, State.left, State.right)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollendelseif chord == 'C-x' thenApp.set_clipboard(s)if s thenlocal s = Text.selection(State)elseif chord == 'C-c' thenelseif chord == 'C-a' thenState.selection1 = {line=1, pos=1}State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}end-- clipboardschedule_save(State)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)State.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.before, event.after)if event thenlocal src = event.afterlocal event = redo_event(State)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollendelseif chord == 'C-y' thenschedule_save(State)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)State.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.after, event.before)if event thenlocal src = event.beforelocal event = undo_event(State)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelseif chord == 'C-z' thenText.redraw_all(State)edit.update_font_settings(State, 20)elseif chord == 'C-0' thenif State.font_height > 2 thenedit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)endelseif chord == 'C--' thenText.redraw_all(State)edit.update_font_settings(State, State.font_height+2)elseif chord == 'C-=' then-- zoomState.search_backup = {cursor={line=State.cursor1.line, pos=State.cursor1.pos},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},}State.search_term = ''endreturnelseif chord == 'C-f' thenText.search_previous(State)elseif chord == 'up' thenText.search_next(State)State.cursor1.pos = State.cursor1.pos+1elseif chord == 'down' thenlocal len = utf8.len(State.search_term)local byte_offset = Text.offset(State.search_term, len)State.search_term = string.sub(State.search_term, 1, byte_offset-1)elseif chord == 'backspace' thenState.search_backup = nilState.search_term = nilelseif chord == 'return' thenText.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaksState.cursor1 = State.search_backup.cursorState.screen_top1 = State.search_backup.screen_topState.search_backup = nilState.search_term = nilif chord == 'escape' thenif State.search_term thenendText.delete_selection(State, State.left, State.right)chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then-- printable character created using shift key => delete selection-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)(not App.shift_down() or utf8.len(key) == 1) andif State.selection1.line andnot State.lines.current_drawing andfunction edit.keychord_press(State, chord, key)endschedule_save(State)endendText.text_input(State, t)local drawing_index, drawing = Drawing.current_drawing(State)if drawing_index == nil thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelserecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})local p = drawing.points[drawing.pending.target_point]p.name = p.name..tlocal drawing = State.lines.current_drawinglocal before = snapshot(State, State.lines.current_drawing_index)elseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenText.search_next(State)if State.search_term thenState.search_term = State.search_term..tfunction edit.text_input(State, t)endendfor i=1,math.floor(-dy) doText.down(State)endedit.put_cursor_on_next_text_line(State)State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}elseif dy < 0 thenfor i=1,math.floor(dy) doText.up(State)endedit.put_cursor_on_next_text_line(State)endendfunction edit.mouse_wheel_move(State, dx,dy)if dy > 0 thenState.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))endbreakendendendState.old_cursor1, State.old_selection1, State.mousepress_shift = nilif eq(State.cursor1, State.selection1) thenState.selection1 = {}endendState.selection1 = State.old_selection1elseif State.mousepress_shift thenif State.old_selection1.line == nil thenState.selection1 = State.old_cursor1--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))}pos=Text.to_pos_on_line(State, line_index, x, y),line=line_index,State.cursor1 = {--? print_and_log(('edit.mouse_release: in line %d'):format(line_index))if Text.in_line(State, line_index, x,y) thenif line.mode == 'text' thenfor line_index,line in ipairs(State.lines) do--? print_and_log('edit.mouse_release: no current drawing')Drawing.before = nilendelserecord_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})if Drawing.before thenschedule_save(State)Drawing.mouse_release(State, x,y, mouse_button)if State.lines.current_drawing then--? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.search_term then return endfunction edit.mouse_release(State, x,y, mouse_button)end-- still here? click is below all screen linesState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=State.screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),}endendendreturnDrawing.mouse_press(State, line_index, x,y, mouse_button)Drawing.before = snapshot(State, line_index)State.lines.current_drawing_index = line_indexState.lines.current_drawing = linelocal line_cache = State.line_cache[line_index]if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) thenendelseif line.mode == 'drawing' thenreturn}pos=Text.to_pos_on_line(State, line_index, x, y),line=line_index,State.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {-- i.e. mouse_release should never look at shift state-- 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 shiftif Text.in_line(State, line_index, x,y) thenif line.mode == 'text' thenfor line_index,line in ipairs(State.lines) do}returnendline=State.screen_top1.line,pos=State.screen_top1.pos,if y < State.top thenState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {-- press on a button and it returned 'true' to short-circuitreturnendif mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then--? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.search_term then return endfunction edit.mouse_press(State, x,y, mouse_button)endendsave_to_disk(State)if State.next_save then-- make sure to save before quittingfunction edit.quit(State)endendState.next_save = Current_time + 3 -- short enough that you're likely to still remember what you didfunction schedule_save(State)if State.next_save == nil thenendendState.next_save = nilsave_to_disk(State)if State.next_save and State.next_save < Current_time thenDrawing.update(State, dt)function edit.update(State, dt)endreturn yendText.draw_search_bar(State)if State.search_term thenState.screen_bottom1 = screen_bottom1endendprint(line.mode)assert(false)elsey = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottomDrawing.draw(State, line_index, y)y = y+Drawing_padding_topelseif line.mode == 'drawing' then--? print('=> y', y)y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)endend,})if State.cursor1.line >= line_index thenState.cursor1.line = State.cursor1.line+1endschedule_save(State)record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})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={}})button(State, 'draw', {x=State.left-Margin_left+4, y=y+4, w=12,h=12, color={1,1,0},endif line.data == '' then-- button to insert new drawingstartpos = State.screen_top1.posif line_index == State.screen_top1.line thenlocal startpos = 1if line.mode == 'text' then--? print('text.draw', y, line_index)screen_bottom1.line = line_indexif y + State.line_height > App.screen.height then break end--? print('draw:', y, line_index, line)for line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]--? print('== draw')local y = State.topState.cursor_x = nilState.cursor_y = nilif not Text.le1(State.screen_top1, State.cursor1) thenprint(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)assert(false)endif #State.lines ~= #State.line_cache thenprint(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))assert(false)endApp.color(Text_color)function edit.draw(State)
-- all text_input events are also keypresses-- TODO: handle chords of multiple keysfunction edit.run_after_text_input(State, t)edit.keychord_press(State, t)edit.text_input(State, t)edit.key_release(State, t)App.screen.contents = {}edit.update(State, 0)edit.draw(State)end-- not all keys are text_inputfunction edit.run_after_keychord(State, chord)edit.keychord_press(State, chord)edit.key_release(State, chord)App.screen.contents = {}edit.update(State, 0)edit.draw(State)endfunction edit.run_after_mouse_click(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.mouse_press(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.mouse_release(State, x,y, mouse_button)App.screen.contents = {}edit.update(State, 0)edit.draw(State)endfunction edit.run_after_mouse_press(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.mouse_press(State, x,y, mouse_button)App.screen.contents = {}edit.update(State, 0)edit.draw(State)endfunction edit.run_after_mouse_release(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.mouse_release(State, x,y, mouse_button)App.screen.contents = {}edit.update(State, 0)edit.draw(State)end