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 projects
require 'edit'
Editor_state = {}
require 'log_browser'
-- called both in tests and real run
function source.initialize_globals()
-- tests currently mostly clear their own state
Show = {}
Focus = 'spokecone'
-- a few text objects we can avoid recomputing unless the font changes
Text_cache = {}
-- blinking cursor
Cursor_time = 0
end
-- called only for real run
function source.initialize(arg)
love.keyboard.setTextInput(true) -- bring up keyboard on touch screen
love.keyboard.setKeyRepeat(true)
love.graphics.setBackgroundColor(1,1,1)
if love.filesystem.getInfo('config') then
source.load_settings()
else
source.initialize_default_settings()
end
if #arg == 0 then
source.initialize_spokecone(arg)
else
local type = source.detect_filetype(arg[1])
if type == 'spokecone' then
source.initialize_spokecone(arg)
Show.spokecone = true
else
initialize_log_browser(arg)
Show.log_browser = true
end
end
end
function source.detect_filetype(filename)
-- assume logs have a unicode box character up top on the first line
local infile = App.open_for_reading(filename)
if infile == nil then
error("file '"..filename.."' not found")
end
for line in infile:lines() do
if line:find('\u{250c}') or line:find('\u{2518}') then
return 'log_browser'
else
return 'spokecone'
end
end
return 'spokecone'
end
-- tenon auger side is already open; expand spokecone side as well
function source.expand_spokecone()
-- temporarily disable resize
--? print('disabling resize')
love.handlers.resize = function() end
Restore_resize_at = App.getTime() + 0.5
-- use whole window
App.screen.height = Display_height-100
App.screen.width = Display_width
--? App.screen.width = Display_width-100 -- for when I want to see prints on a window below
App.screen.flags.resizable = true
App.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 then
Editor_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}
else
load_from_disk(Editor_state)
Text.redraw_all(Editor_state)
end
if #arg > 1 then
print('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 B
if Editor_state.cursor1.line > #Editor_state.lines then
Editor_state.cursor1 = {line=1, pos=1}
end
if Editor_state.screen_top1.line > #Editor_state.lines then
Editor_state.screen_top1 = {line=1, pos=1}
end
edit.eradicate_locations_after_the_fold(Editor_state)
if rawget(_G, 'jit') then
jit.off()
jit.flush()
end
end
function 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 dimensions
love.window.setMode(0, 0) -- maximize
Display_width, Display_height, App.screen.flags = love.window.getMode()
-- set up desired window dimensions
App.screen.flags.resizable = true
App.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.height
love.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.filename
Editor_state.screen_top1 = settings.screen_top
Editor_state.cursor1 = settings.cursor
end
function source.initialize_default_settings()
local font_height = 20
love.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_height
Editor_state.line_height = math.floor(font_height*1.3)
Editor_state.em = em
end
function source.initialize_window_geometry(em_width)
-- maximize window
love.window.setMode(0, 0) -- maximize
Display_width, Display_height, App.screen.flags = love.window.getMode()
-- shrink height slightly to account for window decoration
App.screen.height = Display_height-100
App.screen.width = 40*em_width
App.screen.flags.resizable = true
App.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)
end
function source.resize(w, h)
--? print(("Window resized to width: %d and height: %d."):format(w, h))
App.screen.width, App.screen.height = w, h
Text.redraw_all(Editor_state)
Editor_state.selection1 = {} -- no support for shift drag while we're resizing
Editor_state.right = App.screen.width-Margin_right
Editor_state.width = Editor_state.right-Editor_state.left
Text.tweak_screen_top_and_cursor(Editor_state, Editor_state.left, Editor_state.right)
Last_resize_time = App.getTime()
--? print('end resize')
end
function source.filedropped(file)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
end
-- clear the slate for the new file
Editor_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 filename
function source.switch_to_file(filename)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
end
-- clear the slate for the new file
Editor_state.filename = filename
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}
end
function source.draw()
if Show.spokecone then
edit.draw(Editor_state)
end
if Show.log_browser then
log_browser.draw(Log_browser_state)
end
if Show.spokecone and Show.log_browser then
-- divider
love.graphics.rectangle('fill', App.screen.width/2-1,0, 3,App.screen.height)
end
end
function source.update(dt)
Cursor_time = Cursor_time + dt
-- restore resize if it was disabled
if Restore_resize_at and App.getTime() > Restore_resize_at then
--? print('restoring resize')
love.handlers.resize = App.resize
Restore_resize_at = nil
end
-- some hysteresis while resizing
if App.getTime() < Last_resize_time + 0.1 then
return
end
if Focus == 'spokecone' then
edit.update(Editor_state, dt)
else
log_browser.update(Log_browser_state, dt)
end
end
function source.quit()
if Focus == 'spokecone' then
edit.quit(Editor_state)
else
log_browser.quit(Log_browser_state)
end
-- save some important settings
local x,y,displayindex = love.window.getPosition()
local filename = Editor_state.filename
if filename:sub(1,1) ~= '/' then
filename = love.filesystem.getWorkingDirectory()..'/'..filename -- '/' should work even on Windows
end
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))
end
function 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' then
Focus = 'spokecone'
else
edit.mouse_pressed(Editor_state, x,y, mouse_button)
end
elseif 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' then
Focus = 'log_browser'
else
return log_browser.mouse_pressed(Log_browser_state, x,y, mouse_button)
end
end
end
function source.mouse_released(x,y, mouse_button)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.mouse_released(Editor_state, x,y, mouse_button)
else
return log_browser.mouse_released(Log_browser_state, x,y, mouse_button)
end
end
function source.textinput(t)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.textinput(Editor_state, t)
else
return log_browser.textinput(Log_browser_state, t)
end
end
function source.keychord_pressed(chord, key)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.keychord_pressed(Editor_state, chord, key)
else
return log_browser.keychord_pressed(Log_browser_state, chord, key)
end
end
function source.key_released(key, scancode)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.key_released(Editor_state, key, scancode)
else
return log_browser.keychord_pressed(Log_browser_state, chordkey, scancode)
end
end
-- use this sparingly
function to_text(s)
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
end
return Text_cache[s]
end
-- Pong
pong = {}
function pong.initialize_globals()
N = 99
M = math.floor(N/2)
end
function pong.initialize(arg)
love.graphics.setBackgroundColor(1,0,0)
love.window.setMode(N*3, N*3) -- the court
pong.new_game()
end
function 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=-M
B = {ymin=-5, ymax=5} -- at x=+M
Pw, Pv = 3, 5 -- paddle width, velocity
Game_start = false
Game_over = false
end
function 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)
end
function pong.update(dt)
if not Game_start then return end
if Game_over then return end
Ballp.x = Ballp.x + Ballv.x
Ballp.y = Ballp.y + Ballv.y
if Ballp.y >= M or Ballp.y <= -M then
Ballv.y = -Ballv.y
end
if Ballp.x <= -M then
if Ballp.y >= A.ymin and Ballp.y <= A.ymax then
Ballv.x = -Ballv.x
else
Game_over = true
end
end
if Ballp.x >= M then
if Ballp.y >= B.ymin and Ballp.y <= B.ymax then
Ballv.x = -Ballv.x
else
Game_over = true
end
end
end
function pong.keychord_pressed(key)
Game_start = true
if key == 'space' then
pong.new_game()
end
if key == 'e' then
edit.load()
end
if Game_over then return end
if key == 'down' then
B.ymin, B.ymax = B.ymin+Pv, B.ymax+Pv
end
if key == 'up' then
B.ymin, B.ymax = B.ymin-Pv, B.ymax-Pv
end
if key == 'a' then
A.ymin, A.ymax = A.ymin-Pv, A.ymax-Pv
end
if key == 'z' then
A.ymin, A.ymax = A.ymin+Pv, A.ymax+Pv
end
end
-- vim:noexpandtab
-- delegate most business logic to a layer that can be reused by other projects
require '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 changes
Text_cache = {}
-- blinking cursor
Cursor_time = 0
love.keyboard.setTextInput(true) -- bring up keyboard on touch screen
love.keyboard.setKeyRepeat(true)
love.graphics.setBackgroundColor(1,1,1)
if love.filesystem.getInfo('config') then
load_settings()
if Current_app == 'pong' then
dofile('pong.lua')
pong.initialize_globals()
pong.initialize(arg)
elseif Current_app == 'source' then
dofile('source.lua')
source.initialize_globals()
source.initialize(arg)
if #arg == 0 then
initialize_spokecone(arg)
else
local type = detect_filetype(arg[1])
if type == 'spokecone' then
initialize_spokecone(arg)
Show.spokecone = true
else
initialize_tenonauger(arg)
Show.tenonauger = true
end
end
end
function detect_filetype(filename)
-- assume logs have a unicode box character up top on the first line
local infile = App.open_for_reading(filename)
if infile == nil then
error("file '"..filename.."' not found")
end
for line in infile:lines() do
if line:find('\u{250c}') or line:find('\u{2518}') then
return 'tenonauger'
else
return 'spokecone'
end
end
return 'spokecone'
end
-- tenon auger side is already open; expand spokecone side as well
function expand_spokecone()
-- temporarily disable resize
--? print('disabling resize')
love.handlers.resize = function() end
Restore_resize_at = App.getTime() + 0.5
-- use whole window
App.screen.height = Display_height-100
App.screen.width = Display_width
--? App.screen.width = Display_width-100 -- for when I want to see prints on a window below
App.screen.flags.resizable = true
App.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 then
Editor_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' then
if pong.resize then pong.resize(w,h) end
elseif Current_app == 'source' then
if source.resize then source.resize(w,h) end
load_from_disk(Editor_state)
Text.redraw_all(Editor_state)
end
love.window.setTitle('spokecone.love - '..Editor_state.filename)
if #arg > 1 then
print('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 B
if Editor_state.cursor1.line > #Editor_state.lines then
Editor_state.cursor1 = {line=1, pos=1}
end
if Editor_state.screen_top1.line > #Editor_state.lines then
Editor_state.screen_top1 = {line=1, pos=1}
end
edit.eradicate_locations_after_the_fold(Editor_state)
if rawget(_G, 'jit') then
jit.off()
jit.flush()
assert(false, 'unknown app "'..Current_app..'"')
end
function 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 dimensions
love.window.setMode(0, 0) -- maximize
Display_width, Display_height, App.screen.flags = love.window.getMode()
-- set up desired window dimensions
App.screen.flags.resizable = true
App.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.height
love.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.filename
Editor_state.screen_top1 = settings.screen_top
Editor_state.cursor1 = settings.cursor
end
function initialize_default_settings()
local font_height = 20
love.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_height
Editor_state.line_height = math.floor(font_height*1.3)
Editor_state.em = em
end
function initialize_window_geometry(em_width)
-- maximize window
love.window.setMode(0, 0) -- maximize
Display_width, Display_height, App.screen.flags = love.window.getMode()
-- shrink height slightly to account for window decoration
App.screen.height = Display_height-100
App.screen.width = 40*em_width
App.screen.flags.resizable = true
App.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)
end
function App.resize(w, h)
--? print(("Window resized to width: %d and height: %d."):format(w, h))
App.screen.width, App.screen.height = w, h
Text.redraw_all(Editor_state)
Editor_state.selection1 = {} -- no support for shift drag while we're resizing
Editor_state.right = App.screen.width-Margin_right
Editor_state.width = Editor_state.right-Editor_state.left
Text.tweak_screen_top_and_cursor(Editor_state, Editor_state.left, Editor_state.right)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
if Current_app == 'pong' then
if pong.filedropped then pong.filedropped(file) end
elseif Current_app == 'source' then
if source.filedropped then source.filedropped(file) end
else
assert(false, 'unknown app "'..Current_app..'"')
-- clear the slate for the new file
Editor_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 filename
function edit.switch_to_file(filename)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
function App.focus(in_focus)
if in_focus then
Last_focus_time = App.getTime()
end
if Current_app == 'pong' then
if pong.focus then pong.focus(in_focus) end
elseif Current_app == 'source' then
if source.focus then source.focus(in_focus) end
else
assert(false, 'unknown app "'..Current_app..'"')
if Show.spokecone then
edit.draw(Editor_state)
end
if Show.tenonauger then
tenonauger.draw(Tenonauger_state)
end
if Show.spokecone and Show.tenonauger then
-- divider
love.graphics.rectangle('fill', App.screen.width/2-1,0, 3,App.screen.height)
if Current_app == 'pong' then
pong.draw()
elseif Current_app == 'source' then
source.draw()
else
assert(false, 'unknown app "'..Current_app..'"')
function love.quit()
if Focus == 'spokecone' then
edit.quit(Editor_state)
else
tenonauger.quit(Tenonauger_state)
end
-- save some important settings
local x,y,displayindex = love.window.getPosition()
local filename = Editor_state.filename
if filename:sub(1,1) ~= '/' then
filename = 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 then
return
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))
end
function 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' then
Focus = 'spokecone'
else
edit.mouse_pressed(Editor_state, x,y, mouse_button)
end
elseif Show.tenonauger and Tenonauger_state.left <= x and x < Tenonauger_state.right then
--? print('click on tenonauger side')
if Focus ~= 'tenonauger' then
Focus = 'tenonauger'
--
if chord == 'C-e' then
love.quit()
local arg = {}
if Current_app == 'pong' then
Current_app = 'source'
table.insert(arg, 'pong.lua')
end
function App.mousereleased(x,y, mouse_button)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.mouse_released(Editor_state, x,y, mouse_button)
if Current_app == 'pong' then
if pong.keychord_pressed then pong.keychord_pressed(chord, key) end
elseif Current_app == 'source' then
if source.keychord_pressed then source.keychord_pressed(chord, key) end
return tenonauger.mouse_released(Tenonauger_state, x,y, mouse_button)
end
end
function App.focus(in_focus)
if in_focus then
Last_focus_time = App.getTime()
assert(false, 'unknown app "'..Current_app..'"')
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.textinput(Editor_state, t)
--
if Current_app == 'pong' then
if pong.textinput then pong.textinput(t) end
elseif Current_app == 'source' then
if source.textinput then source.textinput(t) end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.keychord_pressed(Editor_state, chord, key)
--
if Current_app == 'pong' then
if pong.key_released then pong.key_released(chord, key) end
elseif Current_app == 'source' then
if source.key_released then source.key_released(chord, key) end
function App.keyreleased(key, scancode)
-- ignore events for some time after window in focus
if App.getTime() < Last_focus_time + 0.01 then
return
end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'spokecone' then
return edit.key_released(Editor_state, key, scancode)
function App.mousepressed(x,y, mouse_button)
if Current_app == 'pong' then
if pong.mouse_pressed then pong.mouse_pressed(x,y, mouse_button) end
elseif Current_app == 'source' then
if source.mouse_pressed then source.mouse_pressed(x,y, mouse_button) end
-- use this sparingly
function to_text(s)
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
function App.mousereleased(x,y, mouse_button)
if Current_app == 'pong' then
if pong.mouse_released then pong.mouse_released(x,y, mouse_button) end
elseif Current_app == 'source' then
if source.mouse_released then source.mouse_released(x,y, mouse_button) end
else
assert(false, 'unknown app "'..Current_app..'"')
-- use this sparingly
function to_text(s)
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
function love.quit()
if Current_app == 'pong' then
if pong.quit then pong.quit() end
elseif Current_app == 'source' then
if source.quit then source.quit() end
else
assert(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_left
Tenonauger_state.right = App.screen.width - Margin_right
Text.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_left
Log_browser_state.right = App.screen.width - Margin_right
Text.redraw_all(Log_browser_state)