main.lua is now merely responsible for delegating to the 'pong' app or the 'source' app. The 'source' app has an 'edit' side and a 'log_browser' side. So far only the 'edit' side is tested. I plan to keep the 'edit' side always visible, and just toggle the 'log_browser' side on or off.
FXI74QCLOZ4BS7UVZ3U2PE3LOL7MX3FWGHZCTGH3DYFXGTXVVIRAC 7Q6GKOOL56VGLW3WCFTMXIGBIBNBJ5AD7LB5KJGZFAUUDIEYXE2QC TXDMRA5JEAML2GF5QY4ATU22G3NI7DQWPGO4U5OZNP7NGK4JT6WQC Z7FXYFTUSMTCQFT6H5DTCKRCPZXUWWZWGG343J3LOY5E6SEJGXZQC D442K2YTHS4SYCEVZB4DZFVT3KEIVS7HYQGSN65DX4J2SOW3HUAAC JBS77AXN7VRCG7EWWKCL52Q7GAYIZ4TZDI5HD4DL7BJL7FXX2PFQC PDRWPVUENRDB2Q5LCLZPRGFV7S6NUCIAUVUO2EPY3P7JKYNJ4GEQC QPJMOH5SQSTDF52FEQVOJ5XMUNLTUHLO3ANQJP7Y6UHID3RTROIAC DKRCQI3NCJUNKZZ2J7UZJJF6NNTVJZ7PX56R4LMUYEHSVJI7JCPQC J7EFSYHSNNNDH6YGYPP6LATVYAD62QCPDJMZKHFUPV7PORARGHZAC CAQ22VJHJOVQV4HFYYZPPRQ3JMA4MUYEMBGSQU4O6QSBC6VRT4VQC A4NPMJKEBHDG4M2Z55H6Q54YTJYDN7BBN3OBRXNZVGHRLFHR2UGAC TBGVJYFMLQ2PUDGPZ62XUDEXSVF2NLKZJSOBOCUVMPPOARIIH6SQC 57DL2BXXMP5ETXJK3E4JYOEPOMVFSJWT4525OSOJNUPK3I2FAX6AC N34S6GWNCQQNEOXZRHPWSRZE7BXFPRIJHUYP32OIPQ4436VS25FAC XWF54CWCLIZEO44O7VMTA7T2JYURCJCX4VX6HIQN7T5NXFSL7GHQC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC UH4YWHW5NDKNR7RS664UG4PRJNZIPNWAD5JWBEUB22JHOY2SWZKAC CE4LZV4TNXJT54CVGM3QANCBP42TMLMZWF2DBSMUYKAHILXIZEMQC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC 2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC KEFZWDCOCLPTLSZJKRV4VYAHRITV5T33YKG2VGT332YAUCOBS3EAC 6PUNJS5BSLTYMYMN4JFD7YDEGVQLM5PGAT7PQIG5NIAKLTM5T4PQC 6LJZN727CRPYR34LV75CQF55YZI3E7MGESYZSFSYAE73SNEZE3FAC BLWAYPKV3MLDZ4ALXLUJ25AIR6PCIL4RFYNRYLB26GFVC2KQBYBAC 3QNOKBFMKBGXBVJIRHR2444JRRMBTABHE4674NR3DT67RRM2X6GAC AD34IX2ZSGYGU3LGY2IZOZNKD4HRQOYJVG5UWMWLXJZJSM62FFOAC UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC UOTHQWM74AFOCPGQKKPLZXRI5HQFH3SRGI336AVZRGWPR6P256EAC KTSXR2MUTVMBEU7BGN5CHXL5UP6PPA6TTHN4CON7T67YWGZB72VAC 4I2LMNEXDHZTOTDKBJXHSZRQDHIBDXQOYJDNGQSZYXRNHZGP7YPAC 2Y7YH7UPQWDNYDJN4BYY2MOHA36B2BIRX6DMIAKHJPQC7UP2R6NQC DJGC4ZEFQZAAKF3YWQKHMHAP2KANFVQ2H7KWAC4PU3HPHP6WLSOAC 3QQZ7W4EJ7G4HQM5IYWXICMAHVRGERY4X6AOC6LOV5NSZ4OBICSAC SDRXK4X5R6KBAFZTFWKTC7375HVVVPSTCDJVAYWSNUSHSKD242GQC YGCT2D2ORMLTBHANLGHZV3EBGGHD7ZK55UAM7HF2AVSHDXAAKK5QC YKRF5V3ZZQIQ3UGAFYTQT5PUQVHCP2VHFDX77EY2C3X543HUDYKQC IZZVOCLB7KB4ZNQ35OL466MHWOK3XZMOS7ZPFLHUFQ47LJLQQQ3QC QCQHLMSTHTRNIKC5CJU3CAYMMWPTGITALRJNLFMGQWN2ZYMDSAWAC NEXUNNCF5PJC57XAMQGMSSYNI7MJ4ARWDY3HFGVYMGWG3MPHG7CQC HFI2YR2CWHWTAIQMDM6HIHHBUKQ74WA2QXW72PSKZWKHSVFWLKSQC QGO66DNKCM6NNXLSKKOVDLBIJ43UKN6APMF2BLO2KM4F3J7AH7KQC PGZJ6NATSMW4XH64XEPE5Q2EEYCMAMQIIP2OZXPNJ527234QPKMQC 46ASCE5K5QRO6BZNJPW4CJZCRVVG76S3GENIBGNGB352CP3DLDCQC PESSMQBJCOIA5PYNVKUG4D25VTFIG44QVCAOFRD4PKOJNW2AIHKAC RF5ALVNYB2FMU7LRRD5LMQC7P6OO4BX3NXIGWNZTQ2CD62RBRRFAC AVQ5MC5DWNLI6LUUIPGBLGP4LKRPGWBY4THNY25OBT2FAVHC6MCAC ESETRNLB3MIJ2SID6HJMMP52FEVUBLGK2HLWD75KDQZAKQMKSF2QC YTSPVDZHEN5LLNMGIBUBLPWFWSFM3SOHBRGWYSDEVFKRTH24ARRQC XFODWM5LVER7MERESZNYZBB6SWNRUUYPFYEBOOTVYDWFZFZE7HQQC 4QWICBYVZ3ZQU6L7VUM5SCNYRIWXAEOO4ZTE73KECL53NBBADMGQC 5NQBYGKPCGT7E7PKSYPSZ2XYJLU2C5C2VEHJJ6MQCZLBO4NEIESQC AJB4LFRBMIRBEDWJ3OW7GQIMD2BZBVQ62GH4TE2FISWZKSAHRF4QC R6GUSTBY5ZHR7E46DSIDQDNZDJI6QMZQDC7RPQMQWLGWQKXU6HVQC G3VLJLDHTVRE35JLU3DYRIIHDFE4BAPSMHO3UQZ4W4BDE56LVYVQC 4VKEE43Z7MUPNIAOCK36INVBNHRTSWRRN37TIKRPXPH3DRKGHHAQC 67P4LUQW3F7IOYUQNPOPHADGB5USA3SA4ZAXIQISN6SLSAVTVEYQC JGESC375DNF2CAB7ZOCSC45X5CGZVOXHSVCN643B3YHTRZRG6CIQC XSLCFVFHBXYPJDGOFULVB7UAWQY5CRDY4QKKHDXSZTSVLCHDL54QC W4UVZETRKOSWDPLAM5LGAPCQEJWIVFCXUJDVZQASEIKALYEU34KAC UYRAO73Y4LMTBBSH5RGNSNR532NFLRU5N5CJW3VIG72GZZXC654AC 2KRK3OBVPHQIDGCH2FBTP2AXKPEXS3OEPLBKU7UWCLKQA4MANGSAC LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC SPNMXTYRSNPNQJNBTYDZSHYDZVZRPM4LI5QX7GR2TLTC6SPJX4DAC ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC ERQKFTPVWZO4WJD2WRIV33JWTWZSF4HNTK2GD7QT5I5TIL3SOGKQC BJ5X5O4ACBBJ56LRBBSTCW6IBQP4HAEOOOPNH3SKTA4F66YTOIDAC 5STHSG4UB2SC4EZWOQHPQM43BLC4X2EJTNSSYRF35XEYVMTOID5AC XQOTOGK6PKMYSKRXM6V7WZGDOSSJDQSYJIMKBGGCMUXRFAPI5R2QC W32INMIMRYAHHQZH3RU45JGI4CFHVQHAAW5IMFSBJM4VLIDR3BMQC WQOSZSUESLH4YRMW3PIWGSEC7RS243324PBROJP2KPRFJ3NFSEZQC VO3GEIRWVBJSBMJJHHCIL3UJ3TVGGSX2QT74X276JY7JFGSC4LVAC YMH3YXXIVI7SIHH3PCWA7GQ374VVRLTQ74XTQEV22ZYUO5A4OGHQC HPVT467W763S6XQWS5Q47BAK4GMVY57LDXS7LSTFM23Y5XGKZMMQC SJJTCVWI4FFGF22FAUDMEQAVOZ2IHL6LUYTQBWTEZ3DTOGPT4FCAC MHOUX5JFGBFYMOULX3NZA2JXH6PF2227DT54EEXLBUZQFO7NDI2AC CNCYMM6ABOXCRI2IP5A4T2OGBO5FQ7GWBXBP2OQYL4YET5BLJCGQC 5MR22SGZE5YDU5CAIY53GNJDA6HSWBPYPD6M3FRQ5ZUMCSKTYJRAC AVTNUQYRBW7IX2YQ3KDLVQ23RGW3BAKTAE7P73ASBYNKOHMQMH5AC QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC K464QQR4FTXFUMHFWAGOD5DJ6YHUBUKRHLXF2ORE74DVT7TVQ35QC V7LATJC7BMSIZWVQKQXPS5ZYL24FDBMGPX54GV6FL2KNWIB5UTHQC PX7DDEMOBGPVK3FXKK5XEPG24CJXZSVW67DLG2JZZ5E77NVEAA3AC YT5P6TO64XSMCZGTT4SVNFOWUN5ECNXTWCMFXN3YCDZUNH4H3IFAC Z6HI3K55DX3BK25SGJ5B5KYGNRPV6WH65QLN65BFPWD7L6WB7IHQC 73OCE2MCBJJZZMN2KYPJTBOUCKBZAOQ2QIAMTGCNOOJ2AJAXFT2AC Z4KNS42NJZTQKUQZ7B5NYU2U4VOCUQCBFT2D7423MAXKF7NQ5ZJAC MGOQ5XAVFTWZPBG2O5ZTGSEKU6BRJKQZLDV6CM4737VD2FAEB5JQC 5T2E3PDVSLMZSSIIQRNKIKQVV77XQTHP473OP7XBTTMSZHIQID5AC WJAI2QG3KBQJ3L3KGAIWMKOKMKBLY3LNKKAPIQ4OYMXM6OWMIDRAC EMBBTRXDTL6DYNYUHOIZ2BXRCBS3BM6BPPZ4YLJAONG4K7DRSKNAC TVZWTOE3T54QML767ZPFXBAFACGQ7CI4NIU63YZP5STFCWSAJ7CAC VIU2FBNVHG5FV5AJLVPMGEUO5HCLJEGZTRWNY2C5XC4AKMQZZKVAC 7M7LS7I2QT6AFZ6RVK5KK2CZ6SNJAMQIWD7MX34F7MQ3MZKH72GAC LNUHQOGHIOFGJXNGA3DZLYEASLYYDGLN2I3EDZY5ANASQAHCG3YQC AM42E4Y6RLS7QPWBMESL6H5RPFKG5LQYM6EFNB5UYSRSUASKLISQC S7ZZA3YEKYGLBN6UC2N7WGUS43L6MX2KQQ2LBUZT4FQ7K7V5IQGQC KVHUFUFVOSY6GB4XI2QK4T4WCLIYOV3NZR67TX6AQHAQDWJMEOBQC DWZK32YDFQIUYR4LDUUFLOPJMUVJMFDP2RKW2L3QFAGUHAWPJU2AC EFMLTMZG5TUEGLSYLVKOKDSTGVSVWSKOMS7CJWOUGK5LADSH4YTQC O2UFJ6G3MDBJFSABWAJWTZGP6VAKRMQ6XCMILLQRRSS43C3UF2OQC ZUOL7X6VIPRCMEZURYGNHTDEIP3ZCHZW4PKVKBNXVZL5V4VOE5ZQC XX7G2FFJ4QCGQGD4REAW5QFHVYAKCFUPGZCK7L6DFGS5ISVBYBQQC NX3DDSCZM23ONUBXATHBM2DM3RL7YO7LDPXLI2UA6GQU2G3DKOTQC BYG5CEMVXANDTBI2ORNVMEY6K3EBRIHZHS4QBK27VONJC5537COQC 2ENZW7TVCS47BWCA4AIEVGKGMT4Y2TSM5IJ7O5K2VSWNXIN3SG4QC 6DE7RBZ6RHNEICJ7EUMCTROK43LW4LYINULIF2QEQOKCXWLUYUXAC JCSLDGAH2F6AIY4Z6XM6K4LOMW7EFY3E4NF5YXLMHLTYTX3A4Z3QC CIQN2MDEMWAASJAHOHMUZTI5PF4JV5SZSOBYYDCIIFYO2VHWULKAC HRWN5V6J6VMXS7WNSRGI7WMUSZ2OI52JJ4IK352VVSDZI4EF5HHQC 4C5277X7DTT6YSUD7QXQJ323CIRU4IERYZGOG43JWCPZ67B4N5AQC 5FWVELA5XU72JOLKVJMIIEFUIFPJQD2MEQQZVGLB3PMNVJPJT2PAC G42A6ELTV6MRSMEOXPSXRDTTN7Q464FKWKBHQLVQ5O6JXS3W4TMAC LLKIX6AHIV5UDCHQWKQNY4SK2KCQ2CM2K7BRKH4D3PFVUDFDMZEQC INVLLFPN46D7CXCPILG76LQMPALJHAPFJY3WJSL6IT3AMKJSNYWQC LANVGALXJMMLB2XLWCASW3CUR5KS2WE7ZZFZPRK5BWEDB24I5HCAC source = {}-- delegate most business logic to a layer that can be reused by other projectsrequire 'edit'Editor_state = {}require 'log_browser'-- called both in tests and real runfunction source.initialize_globals()-- tests currently mostly clear their own stateShow = {}Focus = 'spokecone'-- a few text objects we can avoid recomputing unless the font changesText_cache = {}-- blinking cursorCursor_time = 0end-- called only for real runfunction source.initialize(arg)love.keyboard.setTextInput(true) -- bring up keyboard on touch screenlove.keyboard.setKeyRepeat(true)love.graphics.setBackgroundColor(1,1,1)if love.filesystem.getInfo('config') thensource.load_settings()elsesource.initialize_default_settings()endif #arg == 0 thensource.initialize_spokecone(arg)elselocal type = source.detect_filetype(arg[1])if type == 'spokecone' thensource.initialize_spokecone(arg)Show.spokecone = trueelseinitialize_log_browser(arg)Show.log_browser = trueendendendfunction source.detect_filetype(filename)-- assume logs have a unicode box character up top on the first linelocal infile = App.open_for_reading(filename)if infile == nil thenerror("file '"..filename.."' not found")endfor line in infile:lines() doif line:find('\u{250c}') or line:find('\u{2518}') thenreturn 'log_browser'elsereturn 'spokecone'endendreturn 'spokecone'end-- tenon auger side is already open; expand spokecone side as wellfunction source.expand_spokecone()-- temporarily disable resize--? print('disabling resize')love.handlers.resize = function() endRestore_resize_at = App.getTime() + 0.5-- use whole windowApp.screen.height = Display_height-100App.screen.width = Display_width--? App.screen.width = Display_width-100 -- for when I want to see prints on a window belowApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(App.screen.width, 200)App.screen.flags.minheight = math.min(App.screen.width, 200)love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)--log_browser.make_room_for_spokecone()end-- environment for a mutable file of bifolded text-- TODO: some initialization is also happening in load_settings/initialize_default_settings. Clean that up.function source.initialize_spokecone(arg)if #arg > 0 thenEditor_state.filename = arg[1]load_from_disk(Editor_state)Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}elseload_from_disk(Editor_state)Text.redraw_all(Editor_state)endif #arg > 1 thenprint('ignoring commandline args after '..arg[1])end-- We currently start out with side B collapsed.-- Other options:-- * save all expanded state by line-- * expand all if any location is in side Bif Editor_state.cursor1.line > #Editor_state.lines thenEditor_state.cursor1 = {line=1, pos=1}endif Editor_state.screen_top1.line > #Editor_state.lines thenEditor_state.screen_top1 = {line=1, pos=1}endedit.eradicate_locations_after_the_fold(Editor_state)if rawget(_G, 'jit') thenjit.off()jit.flush()endendfunction source.load_settings()local settings = json.decode(love.filesystem.read('config'))love.graphics.setFont(love.graphics.newFont(settings.font_height))-- maximize window to determine maximum allowable dimensionslove.window.setMode(0, 0) -- maximizeDisplay_width, Display_height, App.screen.flags = love.window.getMode()-- set up desired window dimensionsApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(Display_width, 200)App.screen.flags.minheight = math.min(Display_height, 200)App.screen.width, App.screen.height = settings.width, settings.heightlove.window.setMode(App.screen.width, App.screen.height, App.screen.flags)love.window.setPosition(settings.x, settings.y, settings.displayindex)Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right, settings.font_height, math.floor(settings.font_height*1.3))Editor_state.filename = settings.filenameEditor_state.screen_top1 = settings.screen_topEditor_state.cursor1 = settings.cursorendfunction source.initialize_default_settings()local font_height = 20love.graphics.setFont(love.graphics.newFont(font_height))local em = App.newText(love.graphics.getFont(), 'm')source.initialize_window_geometry(App.width(em))Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right)Editor_state.font_height = font_heightEditor_state.line_height = math.floor(font_height*1.3)Editor_state.em = emendfunction source.initialize_window_geometry(em_width)-- maximize windowlove.window.setMode(0, 0) -- maximizeDisplay_width, Display_height, App.screen.flags = love.window.getMode()-- shrink height slightly to account for window decorationApp.screen.height = Display_height-100App.screen.width = 40*em_widthApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(App.screen.width, 200)App.screen.flags.minheight = math.min(App.screen.width, 200)love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)endfunction source.resize(w, h)--? print(("Window resized to width: %d and height: %d."):format(w, h))App.screen.width, App.screen.height = w, hText.redraw_all(Editor_state)Editor_state.selection1 = {} -- no support for shift drag while we're resizingEditor_state.right = App.screen.width-Margin_rightEditor_state.width = Editor_state.right-Editor_state.leftText.tweak_screen_top_and_cursor(Editor_state, Editor_state.left, Editor_state.right)Last_resize_time = App.getTime()--? print('end resize')endfunction source.filedropped(file)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- clear the slate for the new fileEditor_state.filename = file:getFilename()file:open('r')Editor_state.lines = load_from_file(file)file:close()Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}end-- a copy of source.filedropped when given a filenamefunction source.switch_to_file(filename)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- clear the slate for the new fileEditor_state.filename = filenameload_from_disk(Editor_state)Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}endfunction source.draw()if Show.spokecone thenedit.draw(Editor_state)endif Show.log_browser thenlog_browser.draw(Log_browser_state)endif Show.spokecone and Show.log_browser then-- dividerlove.graphics.rectangle('fill', App.screen.width/2-1,0, 3,App.screen.height)endendfunction source.update(dt)Cursor_time = Cursor_time + dt-- restore resize if it was disabledif Restore_resize_at and App.getTime() > Restore_resize_at then--? print('restoring resize')love.handlers.resize = App.resizeRestore_resize_at = nilend-- some hysteresis while resizingif App.getTime() < Last_resize_time + 0.1 thenreturnendif Focus == 'spokecone' thenedit.update(Editor_state, dt)elselog_browser.update(Log_browser_state, dt)endendfunction source.quit()if Focus == 'spokecone' thenedit.quit(Editor_state)elselog_browser.quit(Log_browser_state)end-- save some important settingslocal x,y,displayindex = love.window.getPosition()local filename = Editor_state.filenameif filename:sub(1,1) ~= '/' thenfilename = love.filesystem.getWorkingDirectory()..'/'..filename -- '/' should work even on Windowsendlocal settings = {x=x, y=y, displayindex=displayindex,width=App.screen.width, height=App.screen.height,font_height=Editor_state.font_height,filename=filename,screen_top=Editor_state.screen_top1, cursor=Editor_state.cursor1}love.filesystem.write('config', json.encode(settings))endfunction source.mouse_pressed(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it moves--? print('mouse click', x, y)--? print(Editor_state.left, Editor_state.right)--? print(Log_browser_state.left, Log_browser_state.right)if Show.spokecone and Editor_state.left <= x and x < Editor_state.right then--? print('click on spokecone side')if Focus ~= 'spokecone' thenFocus = 'spokecone'elseedit.mouse_pressed(Editor_state, x,y, mouse_button)endelseif Show.log_browser and Log_browser_state.left <= x and x < Log_browser_state.right then--? print('click on log_browser side')if Focus ~= 'log_browser' thenFocus = 'log_browser'elsereturn log_browser.mouse_pressed(Log_browser_state, x,y, mouse_button)endendendfunction source.mouse_released(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.mouse_released(Editor_state, x,y, mouse_button)elsereturn log_browser.mouse_released(Log_browser_state, x,y, mouse_button)endendfunction source.textinput(t)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.textinput(Editor_state, t)elsereturn log_browser.textinput(Log_browser_state, t)endendfunction source.keychord_pressed(chord, key)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.keychord_pressed(Editor_state, chord, key)elsereturn log_browser.keychord_pressed(Log_browser_state, chord, key)endendfunction source.key_released(key, scancode)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.key_released(Editor_state, key, scancode)elsereturn log_browser.keychord_pressed(Log_browser_state, chordkey, scancode)endend-- use this sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)endreturn Text_cache[s]end
-- Pongpong = {}function pong.initialize_globals()N = 99M = math.floor(N/2)endfunction pong.initialize(arg)love.graphics.setBackgroundColor(1,0,0)love.window.setMode(N*3, N*3) -- the courtpong.new_game()endfunction pong.new_game()Ballp = {x=0, y=0}Ballv = {x=love.math.random()-0.5, y=love.math.random()-0.5}A = {ymin=-5, ymax=5} -- at x=-MB = {ymin=-5, ymax=5} -- at x=+MPw, Pv = 3, 5 -- paddle width, velocityGame_start = falseGame_over = falseendfunction pong.draw()love.graphics.scale(3, 3)love.graphics.translate(M, M)love.graphics.setColor(0,0,0)love.graphics.circle('fill', Ballp.x, Ballp.y, 3)love.graphics.rectangle('fill', -M, A.ymin, Pw, A.ymax-A.ymin)love.graphics.rectangle('fill', M-Pw+1, B.ymin, Pw, B.ymax-B.ymin)endfunction pong.update(dt)if not Game_start then return endif Game_over then return endBallp.x = Ballp.x + Ballv.xBallp.y = Ballp.y + Ballv.yif Ballp.y >= M or Ballp.y <= -M thenBallv.y = -Ballv.yendif Ballp.x <= -M thenif Ballp.y >= A.ymin and Ballp.y <= A.ymax thenBallv.x = -Ballv.xelseGame_over = trueendendif Ballp.x >= M thenif Ballp.y >= B.ymin and Ballp.y <= B.ymax thenBallv.x = -Ballv.xelseGame_over = trueendendendfunction pong.keychord_pressed(key)Game_start = trueif key == 'space' thenpong.new_game()endif key == 'e' thenedit.load()endif Game_over then return endif key == 'down' thenB.ymin, B.ymax = B.ymin+Pv, B.ymax+Pvendif key == 'up' thenB.ymin, B.ymax = B.ymin-Pv, B.ymax-Pvendif key == 'a' thenA.ymin, A.ymax = A.ymin-Pv, A.ymax-Pvendif key == 'z' thenA.ymin, A.ymax = A.ymin+Pv, A.ymax+Pvendend-- vim:noexpandtab
-- delegate most business logic to a layer that can be reused by other projectsrequire 'edit'Editor_state = {}require 'tenonauger'-- called both in tests and real run
Show = {}Focus = 'spokecone'-- a few text objects we can avoid recomputing unless the font changesText_cache = {}-- blinking cursorCursor_time = 0
love.keyboard.setTextInput(true) -- bring up keyboard on touch screenlove.keyboard.setKeyRepeat(true)love.graphics.setBackgroundColor(1,1,1)if love.filesystem.getInfo('config') thenload_settings()
if Current_app == 'pong' thendofile('pong.lua')pong.initialize_globals()pong.initialize(arg)elseif Current_app == 'source' thendofile('source.lua')source.initialize_globals()source.initialize(arg)
if #arg == 0 theninitialize_spokecone(arg)elselocal type = detect_filetype(arg[1])if type == 'spokecone' theninitialize_spokecone(arg)Show.spokecone = trueelseinitialize_tenonauger(arg)Show.tenonauger = trueendendendfunction detect_filetype(filename)-- assume logs have a unicode box character up top on the first linelocal infile = App.open_for_reading(filename)if infile == nil thenerror("file '"..filename.."' not found")endfor line in infile:lines() doif line:find('\u{250c}') or line:find('\u{2518}') thenreturn 'tenonauger'elsereturn 'spokecone'endendreturn 'spokecone'end-- tenon auger side is already open; expand spokecone side as wellfunction expand_spokecone()-- temporarily disable resize--? print('disabling resize')love.handlers.resize = function() endRestore_resize_at = App.getTime() + 0.5-- use whole windowApp.screen.height = Display_height-100App.screen.width = Display_width--? App.screen.width = Display_width-100 -- for when I want to see prints on a window belowApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(App.screen.width, 200)App.screen.flags.minheight = math.min(App.screen.width, 200)love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)--tenonauger.make_room_for_spokecone()
love.window.setTitle('pong.love - '..Current_app)
-- environment for a mutable file of bifolded text-- TODO: some initialization is also happening in load_settings/initialize_default_settings. Clean that up.function initialize_spokecone(arg)if #arg > 0 thenEditor_state.filename = arg[1]load_from_disk(Editor_state)Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}
function App.resize(w,h)if Current_app == 'pong' thenif pong.resize then pong.resize(w,h) endelseif Current_app == 'source' thenif source.resize then source.resize(w,h) end
load_from_disk(Editor_state)Text.redraw_all(Editor_state)endlove.window.setTitle('spokecone.love - '..Editor_state.filename)if #arg > 1 thenprint('ignoring commandline args after '..arg[1])end-- We currently start out with side B collapsed.-- Other options:-- * save all expanded state by line-- * expand all if any location is in side Bif Editor_state.cursor1.line > #Editor_state.lines thenEditor_state.cursor1 = {line=1, pos=1}endif Editor_state.screen_top1.line > #Editor_state.lines thenEditor_state.screen_top1 = {line=1, pos=1}endedit.eradicate_locations_after_the_fold(Editor_state)if rawget(_G, 'jit') thenjit.off()jit.flush()
assert(false, 'unknown app "'..Current_app..'"')
endfunction load_settings()local settings = json.decode(love.filesystem.read('config'))love.graphics.setFont(love.graphics.newFont(settings.font_height))-- maximize window to determine maximum allowable dimensionslove.window.setMode(0, 0) -- maximizeDisplay_width, Display_height, App.screen.flags = love.window.getMode()-- set up desired window dimensionsApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(App.screen.width, 200)App.screen.flags.minheight = math.min(App.screen.width, 200)App.screen.width, App.screen.height = settings.width, settings.heightlove.window.setMode(App.screen.width, App.screen.height, App.screen.flags)love.window.setPosition(settings.x, settings.y, settings.displayindex)Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right, settings.font_height, math.floor(settings.font_height*1.3))Editor_state.filename = settings.filenameEditor_state.screen_top1 = settings.screen_topEditor_state.cursor1 = settings.cursorendfunction initialize_default_settings()local font_height = 20love.graphics.setFont(love.graphics.newFont(font_height))local em = App.newText(love.graphics.getFont(), 'm')initialize_window_geometry(App.width(em))Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right)Editor_state.font_height = font_heightEditor_state.line_height = math.floor(font_height*1.3)Editor_state.em = emendfunction initialize_window_geometry(em_width)-- maximize windowlove.window.setMode(0, 0) -- maximizeDisplay_width, Display_height, App.screen.flags = love.window.getMode()-- shrink height slightly to account for window decorationApp.screen.height = Display_height-100App.screen.width = 40*em_widthApp.screen.flags.resizable = trueApp.screen.flags.minwidth = math.min(App.screen.width, 200)App.screen.flags.minheight = math.min(App.screen.width, 200)love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)endfunction App.resize(w, h)--? print(("Window resized to width: %d and height: %d."):format(w, h))App.screen.width, App.screen.height = w, hText.redraw_all(Editor_state)Editor_state.selection1 = {} -- no support for shift drag while we're resizingEditor_state.right = App.screen.width-Margin_rightEditor_state.width = Editor_state.right-Editor_state.leftText.tweak_screen_top_and_cursor(Editor_state, Editor_state.left, Editor_state.right)
-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)
if Current_app == 'pong' thenif pong.filedropped then pong.filedropped(file) endelseif Current_app == 'source' thenif source.filedropped then source.filedropped(file) endelseassert(false, 'unknown app "'..Current_app..'"')
-- clear the slate for the new fileEditor_state.filename = file:getFilename()file:open('r')Editor_state.lines = load_from_file(file)file:close()Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}love.window.setTitle('spokecone.love - '..Editor_state.filename)
love.window.setTitle('pong.love - '..Current_app)
-- a copy of App.filedropped when given a filenamefunction edit.switch_to_file(filename)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)
function App.focus(in_focus)if in_focus thenLast_focus_time = App.getTime()endif Current_app == 'pong' thenif pong.focus then pong.focus(in_focus) endelseif Current_app == 'source' thenif source.focus then source.focus(in_focus) endelseassert(false, 'unknown app "'..Current_app..'"')
if Show.spokecone thenedit.draw(Editor_state)endif Show.tenonauger thentenonauger.draw(Tenonauger_state)endif Show.spokecone and Show.tenonauger then-- dividerlove.graphics.rectangle('fill', App.screen.width/2-1,0, 3,App.screen.height)
if Current_app == 'pong' thenpong.draw()elseif Current_app == 'source' thensource.draw()elseassert(false, 'unknown app "'..Current_app..'"')
function love.quit()if Focus == 'spokecone' thenedit.quit(Editor_state)elsetenonauger.quit(Tenonauger_state)end-- save some important settingslocal x,y,displayindex = love.window.getPosition()local filename = Editor_state.filenameif filename:sub(1,1) ~= '/' thenfilename = love.filesystem.getWorkingDirectory()..'/'..filename -- '/' should work even on Windows
function App.keychord_pressed(chord, key)-- ignore events for some time after window in focus (mostly alt-tab)if App.getTime() < Last_focus_time + 0.01 thenreturn
local settings = {x=x, y=y, displayindex=displayindex,width=App.screen.width, height=App.screen.height,font_height=Editor_state.font_height,filename=filename,screen_top=Editor_state.screen_top1, cursor=Editor_state.cursor1}love.filesystem.write('config', json.encode(settings))endfunction App.mousepressed(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it moves--? print('mouse click', x, y)--? print(Editor_state.left, Editor_state.right)--? print(Tenonauger_state.left, Tenonauger_state.right)if Show.spokecone and Editor_state.left <= x and x < Editor_state.right then--? print('click on spokecone side')if Focus ~= 'spokecone' thenFocus = 'spokecone'elseedit.mouse_pressed(Editor_state, x,y, mouse_button)endelseif Show.tenonauger and Tenonauger_state.left <= x and x < Tenonauger_state.right then--? print('click on tenonauger side')if Focus ~= 'tenonauger' thenFocus = 'tenonauger'
--if chord == 'C-e' thenlove.quit()local arg = {}if Current_app == 'pong' thenCurrent_app = 'source'table.insert(arg, 'pong.lua')
endfunction App.mousereleased(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.mouse_released(Editor_state, x,y, mouse_button)
if Current_app == 'pong' thenif pong.keychord_pressed then pong.keychord_pressed(chord, key) endelseif Current_app == 'source' thenif source.keychord_pressed then source.keychord_pressed(chord, key) end
return tenonauger.mouse_released(Tenonauger_state, x,y, mouse_button)endendfunction App.focus(in_focus)if in_focus thenLast_focus_time = App.getTime()
assert(false, 'unknown app "'..Current_app..'"')
Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.textinput(Editor_state, t)
--if Current_app == 'pong' thenif pong.textinput then pong.textinput(t) endelseif Current_app == 'source' thenif source.textinput then source.textinput(t) end
Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.keychord_pressed(Editor_state, chord, key)
--if Current_app == 'pong' thenif pong.key_released then pong.key_released(chord, key) endelseif Current_app == 'source' thenif source.key_released then source.key_released(chord, key) end
function App.keyreleased(key, scancode)-- ignore events for some time after window in focusif App.getTime() < Last_focus_time + 0.01 thenreturnendCursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'spokecone' thenreturn edit.key_released(Editor_state, key, scancode)
function App.mousepressed(x,y, mouse_button)if Current_app == 'pong' thenif pong.mouse_pressed then pong.mouse_pressed(x,y, mouse_button) endelseif Current_app == 'source' thenif source.mouse_pressed then source.mouse_pressed(x,y, mouse_button) end
-- use this sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)
function App.mousereleased(x,y, mouse_button)if Current_app == 'pong' thenif pong.mouse_released then pong.mouse_released(x,y, mouse_button) endelseif Current_app == 'source' thenif source.mouse_released then source.mouse_released(x,y, mouse_button) endelseassert(false, 'unknown app "'..Current_app..'"')
-- use this sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)
function love.quit()if Current_app == 'pong' thenif pong.quit then pong.quit() endelseif Current_app == 'source' thenif source.quit then source.quit() endelseassert(false, 'unknown app "'..Current_app..'"')
function initialize_tenonauger(arg)print('switching to tenonauger')Focus = 'tenonauger'Tenonauger_state = edit.initialize_state(Margin_top, Margin_right, App.screen.width-Margin_right, Editor_state.font_height, Editor_state.line_height)Tenonauger_state.filename = arg[1]load_from_disk(Tenonauger_state) -- TODO: pay no attention to Fold
function initialize_log_browser(arg)print('switching to log_browser')Focus = 'log_browser'Log_browser_state = edit.initialize_state(Margin_top, Margin_right, App.screen.width-Margin_right, Editor_state.font_height, Editor_state.line_height)Log_browser_state.filename = arg[1]load_from_disk(Log_browser_state) -- TODO: pay no attention to Fold
tenonauger.parse(Tenonauger_state)Text.redraw_all(Tenonauger_state)Tenonauger_state.screen_top1 = {line=1, pos=1}Tenonauger_state.cursor1 = {line=1, pos=nil}love.window.setTitle('tenonauger.love - '..Tenonauger_state.filename)
log_browser.parse(Log_browser_state)Text.redraw_all(Log_browser_state)Log_browser_state.screen_top1 = {line=1, pos=1}Log_browser_state.cursor1 = {line=1, pos=nil}love.window.setTitle('log_browser.love - '..Log_browser_state.filename)
function tenonauger.mouse_pressed(State, x,y, mouse_button)local line_index = tenonauger.line_index(State, x,y)
function log_browser.mouse_pressed(State, x,y, mouse_button)local line_index = log_browser.line_index(State, x,y)
function tenonauger.make_room_for_spokecone()--? print('resizing tenonauger side')Tenonauger_state.left = App.screen.width/2 + Margin_leftTenonauger_state.right = App.screen.width - Margin_rightText.redraw_all(Tenonauger_state)
function log_browser.make_room_for_spokecone()--? print('resizing log_browser side')Log_browser_state.left = App.screen.width/2 + Margin_leftLog_browser_state.right = App.screen.width - Margin_rightText.redraw_all(Log_browser_state)