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 until
function edit.draw(State)
State.button_handlers = {}
App.color(Text_color)
if #State.lines ~= #State.line_cache then
print(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))
assert(false)
end
if not Text.le1(State.screen_top1, State.cursor1) then
print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
assert(false)
end
State.cursor_x = nil
State.cursor_y = nil
local y = State.top
local screen_bottom1 = {line=nil, pos=nil}
--? print('== draw')
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
--? print('draw:', y, line_index, line)
if y + State.line_height > App.screen.height then break end
screen_bottom1.line = line_index
if line.mode == 'text' then
--? print('text.draw', y, line_index)
local startpos = 1
if line_index == State.screen_top1.line then
startpos = State.screen_top1.pos
end
if line.data == '' then
-- button to insert new drawing
button(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 then
State.cursor1.line = State.cursor1.line+1
end
schedule_save(State)
record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
end,
})
end
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
--? print('=> y', y)
elseif line.mode == 'drawing' then
y = y+Drawing_padding_top
Drawing.draw(State, line_index, y)
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
else
print(line.mode)
assert(false)
end
end
State.screen_bottom1 = screen_bottom1
if State.search_term then
Text.draw_search_bar(State)
end
return y
end
function edit.update(State, dt)
Drawing.update(State, dt)
if State.next_save and State.next_save < Current_time then
save_to_disk(State)
State.next_save = nil
end
end
function schedule_save(State)
if State.next_save == nil then
State.next_save = Current_time + 3 -- short enough that you're likely to still remember what you did
end
end
function edit.quit(State)
-- make sure to save before quitting
if State.next_save then
save_to_disk(State)
-- give some time for the OS to flush everything to disk
love.timer.sleep(0.1)
end
end
function 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-circuit
return
end
if y < State.top then
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=State.screen_top1.line,
pos=State.screen_top1.pos,
}
return
end
for 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.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
return
end
elseif line.mode == 'drawing' then
local line_cache = State.line_cache[line_index]
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index)
Drawing.mouse_press(State, line_index, x,y, mouse_button)
return
end
end
end
-- still here? click is below all screen lines
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end
function 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 then
Drawing.mouse_release(State, x,y, mouse_button)
schedule_save(State)
if Drawing.before then
record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
Drawing.before = nil
end
else
--? print_and_log('edit.mouse_release: no current drawing')
for line_index,line in ipairs(State.lines) do
if line.mode == 'text' then
if Text.in_line(State, line_index, x,y) then
--? print_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 then
if State.old_selection1.line == nil then
State.selection1 = State.old_cursor1
else
State.selection1 = State.old_selection1
end
end
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
if eq(State.cursor1, State.selection1) then
State.selection1 = {}
end
break
end
end
end
--? 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))
end
end
function edit.mouse_wheel_move(State, dx,dy)
if dy > 0 then
State.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) do
Text.up(State)
end
elseif dy < 0 then
State.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) do
Text.down(State)
end
end
end
function edit.text_input(State, t)
--? print('text input', t)
if State.search_term then
State.search_term = State.search_term..t
Text.search_next(State)
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
p.name = p.name..t
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
else
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index == nil then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.text_input(State, t)
end
end
schedule_save(State)
end
function edit.keychord_press(State, chord, key)
if State.selection1.line and
not 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) and
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
Text.delete_selection(State, State.left, State.right)
end
if State.search_term then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
if chord == 'escape' then
State.search_term = nil
State.cursor1 = State.search_backup.cursor
State.screen_top1 = State.search_backup.screen_top
State.search_backup = nil
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
elseif chord == 'return' then
State.search_term = nil
State.search_backup = nil
elseif chord == 'backspace' then
local 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' then
State.cursor1.pos = State.cursor1.pos+1
Text.search_next(State)
elseif chord == 'up' then
Text.search_previous(State)
end
return
elseif chord == 'C-f' then
State.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},
}
-- zoom
elseif chord == 'C-=' then
edit.update_font_settings(State, State.font_height+2)
Text.redraw_all(State)
elseif chord == 'C--' then
if State.font_height > 2 then
edit.update_font_settings(State, State.font_height-2)
Text.redraw_all(State)
end
elseif chord == 'C-0' then
edit.update_font_settings(State, 20)
Text.redraw_all(State)
-- undo
elseif chord == 'C-z' then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
local event = undo_event(State)
if event then
local src = event.before
State.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 lines
State.lines.current_drawing = nil
-- if we're scrolling, reclaim all fragments to avoid memory leaks
Text.redraw_all(State)
schedule_save(State)
end
elseif chord == 'C-y' then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
local event = redo_event(State)
if event then
local src = event.after
State.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 lines
State.lines.current_drawing = nil
-- if we're scrolling, reclaim all fragments to avoid memory leaks
Text.redraw_all(State)
schedule_save(State)
end
-- clipboard
elseif chord == 'C-a' then
State.selection1 = {line=1, pos=1}
State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
elseif chord == 'C-c' then
local s = Text.selection(State)
if s then
App.set_clipboard(s)
end
elseif chord == 'C-x' then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
local s = Text.cut_selection(State, State.left, State.right)
if s then
App.set_clipboard(s)
end
schedule_save(State)
elseif chord == 'C-v' then
for _,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.line
local before = snapshot(State, before_line)
local clipboard_data = App.get_clipboard()
for _,code in utf8.codes(clipboard_data) do
local c = utf8.char(code)
if c == '\n' then
Text.insert_return(State)
else
Text.insert_at_cursor(State, c)
end
end
if Text.cursor_out_of_screen(State) then
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
end
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then
local before = snapshot(State, drawing_index)
Drawing.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
schedule_save(State)
end
elseif chord == 'escape' and not App.mouse_down(1) then
for _,line in ipairs(State.lines) do
if line.mode == 'drawing' then
line.show_help = false
end
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
if chord == 'return' then
State.current_drawing_mode = State.previous_drawing_mode
State.previous_drawing_mode = nil
else
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
if chord == 'escape' then
p.name = nil
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
elseif chord == 'backspace' then
local len = utf8.len(p.name)
if len > 0 then
local byte_offset = Text.offset(p.name, len-1)
if len == 1 then byte_offset = 0 end
p.name = string.sub(p.name, 1, byte_offset)
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
end
end
end
schedule_save(State)
else
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.keychord_press(State, chord)
end
function edit.key_release(State, key, scancode)
end
function edit.update_font_settings(State, font_height)
State.font_height = font_height
love.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 = 25
Test_margin_right = 0
function edit.initialize_test_state()
-- if you change these values, tests will start failing
return edit.initialize_state(
15, -- top margin
Test_margin_left,
App.screen.width - Test_margin_right,
14, -- font height assuming default LÖVE font
15) -- 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 disk
love.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
-- undo
patch_placeholders(State.line_cache, event.after, event.before)
-- return y drawn until
if State.lines[State.cursor1.line].mode == 'text' then
break
end
State.cursor1.line = State.cursor1.line+1
State.cursor1.pos = 1
end
function edit.put_cursor_on_next_text_line(State)
while true do
if State.cursor1.line >= #State.lines then
break
end
edit.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)
end
edit.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)
end
edit.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)
end
edit.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_input
end
edit.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 keypresses
14, -- font height assuming default LÖVE font
15) -- line height
end
App.screen.width - Test_margin_right,
function edit.initialize_test_state()
-- if you change these values, tests will start failing
return edit.initialize_state(
15, -- top margin
Test_margin_left,
Test_margin_right = 0
Test_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 tests
end
State.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_height
end
function edit.key_release(State, key, scancode)
end
end
Text.keychord_press(State, chord)
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
else
schedule_save(State)
end
end
if len > 0 then
local byte_offset = Text.offset(p.name, len-1)
if len == 1 then byte_offset = 0 end
p.name = string.sub(p.name, 1, byte_offset)
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
end
elseif chord == 'backspace' then
local 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' then
p.name = nil
local drawing = State.lines.current_drawing
local before = snapshot(State, State.lines.current_drawing_index)
else
State.current_drawing_mode = State.previous_drawing_mode
State.previous_drawing_mode = nil
if chord == 'return' then
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
if line.mode == 'drawing' then
line.show_help = false
end
end
for _,line in ipairs(State.lines) do
end
elseif chord == 'escape' and not App.mouse_down(1) then
schedule_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 then
local drawing_index, drawing = Drawing.current_drawing(State)
-- DON'T reset line_cache.starty here
-- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
schedule_save(State)
end
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
if Text.cursor_out_of_screen(State) then
end
end
Text.insert_at_cursor(State, c)
else
Text.insert_return(State)
for _,code in utf8.codes(clipboard_data) do
local c = utf8.char(code)
if c == '\n' then
local 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 scroll
elseif chord == 'C-v' then
schedule_save(State)
end
App.set_clipboard(s)
if s then
local 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 scroll
end
elseif chord == 'C-x' then
App.set_clipboard(s)
if s then
local s = Text.selection(State)
elseif chord == 'C-c' then
elseif chord == 'C-a' then
State.selection1 = {line=1, pos=1}
State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
end
-- clipboard
schedule_save(State)
-- invalidate various cached bits of lines
State.lines.current_drawing = nil
-- if we're scrolling, reclaim all fragments to avoid memory leaks
Text.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 then
local src = event.after
local event = redo_event(State)
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
end
elseif chord == 'C-y' then
schedule_save(State)
-- invalidate various cached bits of lines
State.lines.current_drawing = nil
-- if we're scrolling, reclaim all fragments to avoid memory leaks
Text.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 then
local src = event.before
local event = undo_event(State)
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
elseif chord == 'C-z' then
Text.redraw_all(State)
edit.update_font_settings(State, 20)
elseif chord == 'C-0' then
if State.font_height > 2 then
edit.update_font_settings(State, State.font_height-2)
Text.redraw_all(State)
end
elseif chord == 'C--' then
Text.redraw_all(State)
edit.update_font_settings(State, State.font_height+2)
elseif chord == 'C-=' then
-- zoom
State.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 = ''
end
return
elseif chord == 'C-f' then
Text.search_previous(State)
elseif chord == 'up' then
Text.search_next(State)
State.cursor1.pos = State.cursor1.pos+1
elseif chord == 'down' then
local 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' then
State.search_backup = nil
State.search_term = nil
elseif chord == 'return' then
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
State.cursor1 = State.search_backup.cursor
State.screen_top1 = State.search_backup.screen_top
State.search_backup = nil
State.search_term = nil
if chord == 'escape' then
if State.search_term then
end
Text.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) and
if State.selection1.line and
not State.lines.current_drawing and
function edit.keychord_press(State, chord, key)
end
schedule_save(State)
end
end
Text.text_input(State, t)
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index == nil then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
else
record_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..t
local drawing = State.lines.current_drawing
local before = snapshot(State, State.lines.current_drawing_index)
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
Text.search_next(State)
if State.search_term then
State.search_term = State.search_term..t
function edit.text_input(State, t)
end
end
for i=1,math.floor(-dy) do
Text.down(State)
end
edit.put_cursor_on_next_text_line(State)
State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
elseif dy < 0 then
for i=1,math.floor(dy) do
Text.up(State)
end
edit.put_cursor_on_next_text_line(State)
end
end
function edit.mouse_wheel_move(State, dx,dy)
if dy > 0 then
State.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))
end
break
end
end
end
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
if eq(State.cursor1, State.selection1) then
State.selection1 = {}
end
end
State.selection1 = State.old_selection1
else
if State.mousepress_shift then
if State.old_selection1.line == nil then
State.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) then
if line.mode == 'text' then
for line_index,line in ipairs(State.lines) do
--? print_and_log('edit.mouse_release: no current drawing')
Drawing.before = nil
end
else
record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
if Drawing.before then
schedule_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 end
function edit.mouse_release(State, x,y, mouse_button)
end
-- still here? click is below all screen lines
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end
end
end
return
Drawing.mouse_press(State, line_index, x,y, mouse_button)
Drawing.before = snapshot(State, line_index)
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
local line_cache = State.line_cache[line_index]
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
end
elseif line.mode == 'drawing' then
return
}
pos=Text.to_pos_on_line(State, line_index, x, y),
line=line_index,
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.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 shift
if Text.in_line(State, line_index, x,y) then
if line.mode == 'text' then
for line_index,line in ipairs(State.lines) do
}
return
end
line=State.screen_top1.line,
pos=State.screen_top1.pos,
if y < State.top then
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
-- press on a button and it returned 'true' to short-circuit
return
end
if 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 end
function edit.mouse_press(State, x,y, mouse_button)
end
end
save_to_disk(State)
if State.next_save then
-- make sure to save before quitting
function edit.quit(State)
end
end
State.next_save = Current_time + 3 -- short enough that you're likely to still remember what you did
function schedule_save(State)
if State.next_save == nil then
end
end
State.next_save = nil
save_to_disk(State)
if State.next_save and State.next_save < Current_time then
Drawing.update(State, dt)
function edit.update(State, dt)
end
return y
end
Text.draw_search_bar(State)
if State.search_term then
State.screen_bottom1 = screen_bottom1
end
end
print(line.mode)
assert(false)
else
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
Drawing.draw(State, line_index, y)
y = y+Drawing_padding_top
elseif line.mode == 'drawing' then
--? print('=> y', y)
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
end
end,
})
if State.cursor1.line >= line_index then
State.cursor1.line = State.cursor1.line+1
end
schedule_save(State)
record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
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},
end
if line.data == '' then
-- button to insert new drawing
startpos = State.screen_top1.pos
if line_index == State.screen_top1.line then
local startpos = 1
if line.mode == 'text' then
--? print('text.draw', y, line_index)
screen_bottom1.line = line_index
if 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 do
local line = State.lines[line_index]
--? print('== draw')
local y = State.top
State.cursor_x = nil
State.cursor_y = nil
if not Text.le1(State.screen_top1, State.cursor1) then
print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
assert(false)
end
if #State.lines ~= #State.line_cache then
print(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))
assert(false)
end
App.color(Text_color)
function edit.draw(State)
-- all text_input events are also keypresses
-- TODO: handle chords of multiple keys
function 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_input
function 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)
end
function 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)
end
function 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)
end
function 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