Merge lines.love
[?]
Sep 16, 2023, 6:44 AM
OWE64YJ27IFNSGLLWIGYBXRNJ4XU55UYUINAHLOWG2LFEYJM73QACDependencies
- [2]
6ECYOEHYbugfix: obsolete location for attribute - [3]
ELJNEPW2simplify cursor-on-screen check - [4]
PV2YA7KSsubsection headings in a long switch - [5]
APX2PY6Gstop tracking wallclock time - [6]
RRDO6H7Hbugfix - [7]
5ITAXPEPwait a little to flush disk before quitting - [8]
YRJFJNUDbugfix - [9]
O4RRXNOKbugfix: disallow font size of 0 - [10]
TXI6GSQDsome minor cleanup - [11]
ZWDTEUH7clean up some absolute coordinates - [12]
WK6UK5AJenhance bugfix of commit a9aa3436f (Dec 2024) - [13]
6XCJX4DZbugfix: inscript's bug - [14]
5SM6DRHKport inscript's bugfix to source editor - [15]
2QQNDEWTMerge lines.love - [16]
NPXYCSEAMerge lines.love - [17]
FPY4LO2Wmake a few names consistent with snake_case - [18]
NVSC4N4Kchange a helper slightly - [19]
2MGBV7NPbugfix: crash when using mouse wheel - [20]
J5NTUS2Gsource: show file being edited in window title bar - [21]
MRA2Y3EEidea: set recent_mouse on mouse events - [22]
QQBP3G6Wreturn height of editor widget after drawing - [23]
TGZAJUEFbring back a set of constants - [24]
5BMR5HRTclick to the left of a line - [25]
SPNMXTYRhave file API operate on state object - [26]
UH4YWHW5button framework is at the app level - [27]
FKNXK2OAswitch to line index in a function - [28]
4KC7I3E2make colors easier to edit - [29]
PTDO2SOTadd state arg to schedule_save - [30]
3MAZEQK5add state arg to Text.textinput - [31]
52ZZ5TIEswitch to line index in a function - [32]
LF7BWEG4group all editor globals - [33]
KKMFQDR4editing source code from within the app - [34]
UHB4GARJleft/right margin -> left/right coordinates - [35]
X3CQLBTRset window title within each app - [36]
X3F7ECSLadd state arg to some functions - [37]
OGD5RAQKbugfix: naming points in drawings - [38]
C4VTBATA. - [39]
Z3BQO2RKtypo - [40]
GNKUD23Iget rid of recent_mouse - [41]
2Y7YH7UPinfrastructure for caching LÖVE text objects - [42]
6RYLD5ONchange how we handle clicks above top margin - [43]
2WGHUWE6self-documenting 0 Test_right_margin - [44]
ILOA5BYFseparate data structure for each line's cache data - [45]
PP2IIHL6stop putting button state in a global - [46]
7JH2ZT3Fadd state arg to Drawing.draw - [47]
KYNGDE2Cconsistent names in a few more places - [48]
AU424KN7Merge lines.love - [49]
7VGDIPLCmore robust state validation - [50]
GN3C6AGMbugfix in changing shape mid-stroke - [51]
H6QZ7GRRmore precise name - [52]
QXVD2RIFadd state arg to Drawing.mouse_released - [53]
2L5MEZV3experiment: new edit namespace - [54]
PK5U572Cdrop some extra args - [55]
BH7BT36Lctrl+a: select entire buffer - [56]
6D5MOJS4allow buttons to interrupt events - [57]
GZ5WULJVswitch source side to new screen-line-based render - [58]
YFW4MNNPhandle wrapping lines - [59]
LDFXFRUObring a few things in sync between run and source - [60]
OMLASW7Kexperiment at avoiding some merge conflicts - [61]
TGHAJBESuse line cache for drawings as well - [62]
PJEQCTBLadd state arg to Drawing.update - [63]
MTJEVRJRadd state arg to a few functions - [64]
SPSW74Y5add state arg to Text.keychord_pressed - [65]
IFTYOERMline.y -> line_cache.starty in a few more places - [66]
PWDBOOWJcopying to clipboard can never scroll - [67]
3QQZ7W4Ebring couple more globals back to the app level - [68]
APYPFFS3call edit rather than App callbacks in tests - [69]
DLQAEAC7add state arg to Drawing.mouse_pressed - [70]
BW2IUB3Kkeep all text cache writes inside text.lua - [71]
WJBZZQE4fold together two largely similar cases - [72]
WLJCIXYMadd state arg to a few functions - [73]
ERQKFTPVextract method - [74]
OWYI7OJTMerge upstream - [75]
2CK5QI7Wmake love event names consistent - [76]
WLWNS6FBa bug I've never run into - [77]
KTZQ57HVreplace globals with args in a few functions - [78]
CNCYMM6Amake test initializations a little more obvious - [79]
Z3IQ6A4Rbugfix - [80]
Z5WOBP27Merge lines.love - [81]
FTB4YNQUMerge lines.love - [82]
QJISOCHJsome temporary logging to catch a bug - [83]
7FPELAZBah, I see the problem - [84]
3OTESDW6move drawing.starty into line cache - [85]
4GYPLUDYstreamline the interface for Text.draw - [86]
S2MISTTMadd state arg to a few functions - [87]
OI4FPFINsupport drawings in the source editor - [88]
VN3MTXFKMerge lines.love - [89]
ISOFHXB2App.width can no longer take a Text - [90]
Z5HLXU4Padd state arg to a few functions - [91]
K6DTOGOQflip return value of button handlers - [92]
LNUHQOGHstart passing in Editor_state explicitly - [93]
GFXWHTE6mouse wheel support - [94]
5UKUADTWdistinguish consistently between mouse buttons and other buttons - [95]
4EGQRXDAbugfix: naming points - [96]
SW7BSBMJseveral bugfixes in saving/loading cursor position - [97]
QCPXQ2E3add state arg to a few functions - [98]
3HVBAZPAadd state arg to a few functions - [99]
5Y24ZDZIbugfix - [100]
23MA4T3Gadd state arg to Drawing.keychord_pressed - [101]
5ZA3BRNYadd state arg to a few functions - [102]
AF253GHLbugfix
Change contents
- edit in source.lua at line 77
love.window.setTitle('capture.love - source')love.window.setTitle('lines.love - source - '..Editor_state.filename) - resurrect zombie in source.lua at line 77[21.60]→[21.105:108](∅→∅),[20.85]→[21.105:108](∅→∅),[21.57]→[21.105:108](∅→∅),[21.57]→[21.105:108](∅→∅)
- edit in source.lua at line 77
love.window.setTitle('capture.love - source - '..Editor_state.filename) - edit in edit.lua at line 109
edit.put_cursor_on_next_text_line(State) - replacement in edit.lua at line 118
edit.put_cursor_on_first_text_line(State)edit.put_cursor_on_next_text_line(State) - edit in edit.lua at line 142
endfunction edit.put_cursor_on_next_text_line(State)while true doif State.cursor1.line >= #State.lines thenbreakendif State.lines[State.cursor1.line].mode == 'text' thenbreakendState.cursor1.line = State.cursor1.line+1State.cursor1.pos = 1end - replacement in edit.lua at line 157
function edit.put_cursor_on_first_text_line(State)for i,line in ipairs(State.lines) do-- return y drawn untilfunction edit.draw(State)State.button_handlers = {}App.color(Text_color)if #State.lines ~= #State.line_cache thenprint(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))assert(false)endif not Text.le1(State.screen_top1, State.cursor1) thenprint(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)assert(false)endState.cursor_x = nilState.cursor_y = nillocal y = State.toplocal screen_bottom1 = {line=nil, pos=nil}--? print('== draw')for line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]--? print('draw:', y, line_index, line)if y + State.line_height > App.screen.height then break endscreen_bottom1.line = line_indexif line.mode == 'text' then--? print('text.draw', y, line_index)local startpos = 1if line_index == State.screen_top1.line thenstartpos = State.screen_top1.posendif line.data == '' then-- button to insert new drawingbutton(State, 'draw', {x=State.left-Margin_left+4, y=y+4, w=12,h=12, color={1,1,0},icon = icon.insert_drawing,onpress1 = function()Drawing.before = snapshot(State, line_index-1, line_index)table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})table.insert(State.line_cache, line_index, {})if State.cursor1.line >= line_index thenState.cursor1.line = State.cursor1.line+1endschedule_save(State)record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})end,})endy, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)--? print('=> y', y)elseif line.mode == 'drawing' theny = y+Drawing_padding_topDrawing.draw(State, line_index, y)y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottomelseprint(line.mode)assert(false)endendState.screen_bottom1 = screen_bottom1if State.search_term thenText.draw_search_bar(State)endreturn yendfunction edit.update(State, dt)Drawing.update(State, dt)if State.next_save and State.next_save < Current_time thensave_to_disk(State)State.next_save = nilendendfunction schedule_save(State)if State.next_save == nil thenState.next_save = Current_time + 3 -- short enough that you're likely to still remember what you didendendfunction edit.quit(State)-- make sure to save before quittingif State.next_save thensave_to_disk(State)-- give some time for the OS to flush everything to disklove.timer.sleep(0.1)endendfunction edit.mouse_press(State, x,y, mouse_button)if State.search_term then return end--? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then-- press on a button and it returned 'true' to short-circuitreturnendif y < State.top thenState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=State.screen_top1.line,pos=State.screen_top1.pos,}returnendfor line_index,line in ipairs(State.lines) do - replacement in edit.lua at line 263
State.cursor1 = {line=i, pos=1}breakif Text.in_line(State, line_index, x,y) then-- delicate dance between cursor, selection and old cursor/selection-- scenarios:-- regular press+release: sets cursor, clears selection-- shift press+release:-- sets selection to old cursor if not set otherwise leaves it untouched-- sets cursor-- press and hold to start a selection: sets selection on press, cursor on release-- press and hold, then press shift: ignore shift-- i.e. mouse_release should never look at shift state--? print_and_log(('edit.mouse_press: in line %d'):format(line_index))State.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}returnendelseif line.mode == 'drawing' thenlocal line_cache = State.line_cache[line_index]if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) thenState.lines.current_drawing_index = line_indexState.lines.current_drawing = lineDrawing.before = snapshot(State, line_index)Drawing.mouse_press(State, line_index, x,y, mouse_button)returnendendend-- still here? click is below all screen linesState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=State.screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),}endfunction edit.mouse_release(State, x,y, mouse_button)if State.search_term then return end--? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.lines.current_drawing thenDrawing.mouse_release(State, x,y, mouse_button)schedule_save(State)if Drawing.before thenrecord_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})Drawing.before = nilendelse--? print_and_log('edit.mouse_release: no current drawing')for line_index,line in ipairs(State.lines) doif line.mode == 'text' thenif Text.in_line(State, line_index, x,y) then--? print_and_log(('edit.mouse_release: in line %d'):format(line_index))State.cursor1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.mousepress_shift thenif State.old_selection1.line == nil thenState.selection1 = State.old_cursor1elseState.selection1 = State.old_selection1endendState.old_cursor1, State.old_selection1, State.mousepress_shift = nilif eq(State.cursor1, State.selection1) thenState.selection1 = {}endbreakendendend--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))endendfunction edit.mouse_wheel_move(State, dx,dy)if dy > 0 thenState.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}edit.put_cursor_on_next_text_line(State)for i=1,math.floor(dy) doText.up(State)endelseif dy < 0 thenState.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}edit.put_cursor_on_next_text_line(State)for i=1,math.floor(-dy) doText.down(State)endendendfunction edit.text_input(State, t)--? print('text input', t)if State.search_term thenState.search_term = State.search_term..tText.search_next(State)elseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenlocal before = snapshot(State, State.lines.current_drawing_index)local drawing = State.lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]p.name = p.name..trecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})elselocal drawing_index, drawing = Drawing.current_drawing(State)if drawing_index == nil thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollText.text_input(State, t)endendschedule_save(State)endfunction edit.keychord_press(State, chord, key)if State.selection1.line andnot State.lines.current_drawing and-- printable character created using shift key => delete selection-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)(not App.shift_down() or utf8.len(key) == 1) andchord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) thenText.delete_selection(State, State.left, State.right)endif State.search_term thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollif chord == 'escape' thenState.search_term = nilState.cursor1 = State.search_backup.cursorState.screen_top1 = State.search_backup.screen_topState.search_backup = nilText.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leakselseif chord == 'return' thenState.search_term = nilState.search_backup = nilelseif chord == 'backspace' thenlocal len = utf8.len(State.search_term)local byte_offset = Text.offset(State.search_term, len)State.search_term = string.sub(State.search_term, 1, byte_offset-1)elseif chord == 'down' thenState.cursor1.pos = State.cursor1.pos+1Text.search_next(State)elseif chord == 'up' thenText.search_previous(State)endreturnelseif chord == 'C-f' thenState.search_term = ''State.search_backup = {cursor={line=State.cursor1.line, pos=State.cursor1.pos},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},}-- zoomelseif chord == 'C-=' thenedit.update_font_settings(State, State.font_height+2)Text.redraw_all(State)elseif chord == 'C--' thenif State.font_height > 2 thenedit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)endelseif chord == 'C-0' thenedit.update_font_settings(State, 20)Text.redraw_all(State)-- undoelseif chord == 'C-z' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal event = undo_event(State)if event thenlocal src = event.beforeState.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.after, event.before)patch_placeholders(State.line_cache, event.after, event.before)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)schedule_save(State)endelseif chord == 'C-y' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal event = redo_event(State)if event thenlocal src = event.afterState.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.before, event.after)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)schedule_save(State)end-- clipboardelseif chord == 'C-a' thenState.selection1 = {line=1, pos=1}State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}elseif chord == 'C-c' thenlocal s = Text.selection(State)if s thenApp.set_clipboard(s)endelseif chord == 'C-x' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal s = Text.cut_selection(State, State.left, State.right)if s thenApp.set_clipboard(s)endschedule_save(State)elseif chord == 'C-v' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.local before_line = State.cursor1.linelocal before = snapshot(State, before_line)local clipboard_data = App.get_clipboard()for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenText.insert_return(State)elseText.insert_at_cursor(State, c)endendif Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State, State.left, State.right)endschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})-- dispatch to drawing or textelseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then-- DON'T reset line_cache.starty herelocal drawing_index, drawing = Drawing.current_drawing(State)if drawing_index thenlocal before = snapshot(State, drawing_index)Drawing.keychord_press(State, chord)record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})schedule_save(State)endelseif chord == 'escape' and not App.mouse_down(1) thenfor _,line in ipairs(State.lines) doif line.mode == 'drawing' thenline.show_help = falseend - edit in edit.lua at line 515
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenif chord == 'return' thenState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilelselocal before = snapshot(State, State.lines.current_drawing_index)local drawing = State.lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilrecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})elseif chord == 'backspace' thenlocal len = utf8.len(p.name)if len > 0 thenlocal byte_offset = Text.offset(p.name, len-1)if len == 1 then byte_offset = 0 endp.name = string.sub(p.name, 1, byte_offset)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})endendendschedule_save(State)elsefor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollText.keychord_press(State, chord) - edit in edit.lua at line 541
endfunction edit.key_release(State, key, scancode)endfunction edit.update_font_settings(State, font_height)State.font_height = font_heightlove.graphics.setFont(love.graphics.newFont(State.font_height))State.line_height = math.floor(font_height*1.3)end--== some methods for tests-- Insulate tests from some key globals so I don't have to change the vast-- majority of tests when they're modified for the real app.Test_margin_left = 25Test_margin_right = 0function edit.initialize_test_state()-- if you change these values, tests will start failingreturn edit.initialize_state(15, -- top marginTest_margin_left,App.screen.width - Test_margin_right,14, -- font height assuming default LÖVE font15) -- line height - replacement in edit.lua at line 569[21.838]→[15.29:29](∅→∅),[21.2727]→[21.15:44](∅→∅),[21.22445]→[13.1951:1996](∅→∅),[21.609]→[21.1896:1966](∅→∅),[21.470]→[7.26:113](∅→∅),[21.5449]→[14.6169:6248](∅→∅),[21.5593]→[21.170:199](∅→∅),[21.6985]→[8.134:241](∅→∅),[21.6870]→[4.26:36](∅→∅),[21.8139]→[21.1967:2037](∅→∅),[15.29]→[21.28:52](∅→∅),[21.366]→[21.28:52](∅→∅),[21.361]→[18.513:660](∅→∅),[21.361]→[18.513:660](∅→∅),[21.795]→[18.391:512](∅→∅),[21.795]→[18.391:512](∅→∅),[21.795]→[18.391:512](∅→∅),[16.498]→[21.364:368](∅→∅),[21.258]→[21.364:368](∅→∅),[21.7283]→[16.498:498](∅→∅),[21.258]→[21.7264:7283](∅→∅),[21.7264]→[21.7264:7283](∅→∅),[21.7264]→[21.234:258](∅→∅),[21.567]→[21.7237:7264](∅→∅),[21.6186]→[21.7237:7264](∅→∅),[21.7237]→[21.7237:7264](∅→∅),[21.519]→[21.6139:6186](∅→∅),[21.7099]→[21.411:519](∅→∅),[21.233]→[21.7075:7099](∅→∅),[21.7075]→[21.7075:7099](∅→∅),[21.7075]→[21.209:233](∅→∅),[21.410]→[21.7048:7075](∅→∅),[21.880]→[21.7048:7075](∅→∅),[21.7048]→[21.7048:7075](∅→∅),[21.363]→[21.835:880](∅→∅),[21.6915]→[21.259:363](∅→∅),[21.208]→[21.6891:6915](∅→∅),[21.6891]→[21.6891:6915](∅→∅),[21.6891]→[21.184:208](∅→∅),[21.258]→[21.6864:6891](∅→∅),[21.6138]→[21.6864:6891](∅→∅),[21.6864]→[21.6864:6891](∅→∅),[21.210]→[21.6091:6138](∅→∅),[21.834]→[21.166:210](∅→∅),[21.166]→[21.166:210](∅→∅),[21.119]→[21.789:834](∅→∅),[21.6651]→[21.15:119](∅→∅),[21.183]→[21.6627:6651](∅→∅),[21.6627]→[21.6627:6651](∅→∅),[21.6627]→[21.159:183](∅→∅),[21.6090]→[21.6600:6627](∅→∅),[21.6600]→[21.6600:6627](∅→∅),[21.6528]→[21.6021:6090](∅→∅),[21.6020]→[21.6481:6528](∅→∅),[21.6481]→[21.6481:6528](∅→∅),[21.6451]→[21.5989:6020](∅→∅),[21.16047]→[21.6450:6451](∅→∅),[21.6449]→[21.16043:16047](∅→∅),[21.9642]→[21.16043:16047](∅→∅),[21.16043]→[21.16043:16047](∅→∅),[21.158]→[21.6430:6449](∅→∅),[21.6430]→[21.6430:6449](∅→∅),[21.6430]→[21.134:158](∅→∅),[21.5988]→[21.6403:6430](∅→∅),[21.6403]→[21.6403:6430](∅→∅),[21.6268]→[21.5854:5988](∅→∅),[21.5853]→[21.6228:6268](∅→∅),[21.6228]→[21.6228:6268](∅→∅),[21.6184]→[21.5808:5853](∅→∅),[21.5729]→[21.6183:6184](∅→∅),[21.6183]→[21.6183:6184](∅→∅),[21.1119]→[21.5645:5729](∅→∅),[21.5645]→[21.5645:5729](∅→∅),[21.5600]→[21.1075:1119](∅→∅),[21.1074]→[21.5422:5600](∅→∅),[21.5422]→[21.5422:5600](∅→∅),[21.5422]→[21.1052:1074](∅→∅),[21.1051]→[21.5400:5422](∅→∅),[21.5400]→[21.5400:5422](∅→∅),[21.5400]→[21.915:1051](∅→∅),[21.447]→[21.5371:5400](∅→∅),[21.6146]→[21.5371:5400](∅→∅),[21.329]→[21.442:447](∅→∅),[21.442]→[21.442:447](∅→∅),[6.92]→[21.337:387](∅→∅),[21.337]→[21.337:387](∅→∅),[21.264]→[6.26:92](∅→∅),[21.6146]→[21.175:264](∅→∅),[21.5807]→[21.6141:6146](∅→∅),[21.9642]→[21.6141:6146](∅→∅),[21.16001]→[21.5759:5807](∅→∅),[21.5758]→[21.15990:16001](∅→∅),[21.7139]→[21.15990:16001](∅→∅),[21.15990]→[21.15990:16001](∅→∅),[21.1400]→[21.5720:5758](∅→∅),[21.15878]→[21.1293:1400](∅→∅),[21.631]→[21.15871:15878](∅→∅),[21.15871]→[21.15871:15878](∅→∅),[21.15851]→[21.606:631](∅→∅),[21.330]→[21.15833:15851](∅→∅),[21.3243]→[21.15833:15851](∅→∅),[21.9506]→[21.15833:15851](∅→∅),[21.105844]→[21.15833:15851](∅→∅),[21.15833]→[21.15833:15851](∅→∅),[21.15593]→[21.26:330](∅→∅),[21.3134]→[21.15517:15593](∅→∅),[21.9411]→[21.15517:15593](∅→∅),[21.105742]→[21.15517:15593](∅→∅),[21.15517]→[21.15517:15593](∅→∅),[21.15429]→[21.3026:3134](∅→∅),[21.9316]→[21.15315:15429](∅→∅),[21.105640]→[21.15315:15429](∅→∅),[21.15315]→[21.15315:15429](∅→∅),[21.3025]→[21.9266:9316](∅→∅),[21.9266]→[21.9266:9316](∅→∅),[21.15212]→[21.2953:3025](∅→∅),[21.9200]→[21.15203:15212](∅→∅),[21.105510]→[21.15203:15212](∅→∅),[21.15203]→[21.15203:15212](∅→∅),[21.15118]→[21.9097:9200](∅→∅),[21.367]→[21.15088:15118](∅→∅),[21.9096]→[21.15088:15118](∅→∅),[21.105385]→[21.15088:15118](∅→∅),[21.15088]→[21.15088:15118](∅→∅),[21.15043]→[21.284:367](∅→∅),[21.9044]→[21.14957:15043](∅→∅),[21.105326]→[21.14957:15043](∅→∅),[21.14957]→[21.14957:15043](∅→∅),[21.14922]→[21.9003:9044](∅→∅),[21.605]→[21.14856:14922](∅→∅),[21.14856]→[21.14856:14922](∅→∅),[21.2952]→[21.578:605](∅→∅),[21.14834]→[21.578:605](∅→∅),[21.5719]→[21.2866:2952](∅→∅),[21.60]→[21.2866:2952](∅→∅),[21.2865]→[21.5676:5719](∅→∅),[21.14679]→[21.2813:2865](∅→∅),[21.81]→[21.14653:14679](∅→∅),[21.14653]→[21.14653:14679](∅→∅),[21.1292]→[21.15:81](∅→∅),[21.14592]→[21.15:81](∅→∅),[21.14561]→[21.1250:1292](∅→∅),[21.2812]→[21.14470:14561](∅→∅),[21.9002]→[21.14470:14561](∅→∅),[21.105277]→[21.14470:14561](∅→∅),[21.14470]→[21.14470:14561](∅→∅),[21.577]→[21.2710:2812](∅→∅),[21.14368]→[21.552:577](∅→∅),[21.1438]→[21.14360:14368](∅→∅),[21.8913]→[21.14360:14368](∅→∅),[21.23116]→[21.14360:14368](∅→∅),[21.105181]→[21.14360:14368](∅→∅),[21.14360]→[21.14360:14368](∅→∅),[3.360]→[21.23041:23116](∅→∅),[21.1331]→[21.23041:23116](∅→∅),[21.14227]→[3.315:360](∅→∅),[21.8706]→[21.14209:14227](∅→∅),[21.14209]→[21.14209:14227](∅→∅),[21.14176]→[21.8666:8706](∅→∅),[21.8665]→[21.14165:14176](∅→∅),[21.14165]→[21.14165:14176](∅→∅),[21.14136]→[21.8631:8665](∅→∅),[17.454]→[21.14032:14136](∅→∅),[21.14032]→[21.14032:14136](∅→∅),[21.2709]→[17.407:454](∅→∅),[21.8812]→[21.2661:2709](∅→∅),[21.13904]→[21.8769:8812](∅→∅),[2.230]→[21.13768:13904](∅→∅),[21.1249]→[21.13768:13904](∅→∅),[21.8768]→[21.13768:13904](∅→∅),[21.105015]→[21.13768:13904](∅→∅),[21.13768]→[21.13768:13904](∅→∅),[21.13689]→[2.123:230](∅→∅),[21.551]→[21.13660:13689](∅→∅),[21.13660]→[21.13660:13689](∅→∅),[21.13640]→[21.526:551](∅→∅),[17.406]→[21.13632:13640](∅→∅),[21.13632]→[21.13632:13640](∅→∅),[21.13602]→[17.379:406](∅→∅),[21.3962]→[21.13588:13602](∅→∅),[21.8682]→[21.13588:13602](∅→∅),[21.23040]→[21.13588:13602](∅→∅),[21.104922]→[21.13588:13602](∅→∅),[21.13588]→[21.13588:13602](∅→∅),[21.1158]→[21.22975:23040](∅→∅),[21.8593]→[21.22975:23040](∅→∅),[21.13432]→[21.1051:1158](∅→∅),[17.378]→[21.13395:13432](∅→∅),[21.13395]→[21.13395:13432](∅→∅),[21.13365]→[17.351:378](∅→∅),[21.3865]→[21.13351:13365](∅→∅),[21.13351]→[21.13351:13365](∅→∅),[21.1050]→[21.3829:3865](∅→∅),[21.8507]→[21.3829:3865](∅→∅),[21.657]→[21.13212:13241](∅→∅),[21.13212]→[21.13212:13241](∅→∅),[21.13212]→[21.501:657](∅→∅),[21.525]→[21.13189:13212](∅→∅),[21.13189]→[21.13189:13212](∅→∅),[21.398]→[21.498:525](∅→∅),[21.7066]→[21.498:525](∅→∅),[21.13167]→[21.498:525](∅→∅),[21.8421]→[21.207:398](∅→∅),[21.12904]→[21.8226:8421](∅→∅),[21.2660]→[21.12856:12904](∅→∅),[21.12856]→[21.12856:12904](∅→∅),[21.942]→[21.2624:2660](∅→∅),[21.8225]→[21.2624:2660](∅→∅),[21.12746]→[21.835:942](∅→∅),[21.497]→[21.12709:12746](∅→∅),[21.12709]→[21.12709:12746](∅→∅),[21.206]→[21.470:497](∅→∅),[21.6968]→[21.470:497](∅→∅),[21.12687]→[21.470:497](∅→∅),[21.2037]→[21.15:206](∅→∅),[21.8570]→[21.15:206](∅→∅),[21.8139]→[21.15:206](∅→∅),[21.12424]→[21.7944:8139](∅→∅),[21.2623]→[21.12375:12424](∅→∅),[21.12375]→[21.12375:12424](∅→∅),[2.122]→[21.2587:2623](∅→∅),[21.834]→[21.2587:2623](∅→∅),[21.7943]→[21.2587:2623](∅→∅),[21.12265]→[2.15:122](∅→∅),[4.36]→[21.12236:12265](∅→∅),[21.6870]→[21.12236:12265](∅→∅),[21.12236]→[21.12236:12265](∅→∅),[21.174]→[21.6843:6870](∅→∅),[21.12214]→[21.6843:6870](∅→∅),[21.12181]→[21.133:174](∅→∅),[9.289]→[21.12152:12181](∅→∅),[21.6842]→[21.12152:12181](∅→∅),[21.12152]→[21.12152:12181](∅→∅),[21.12086]→[9.158:289](∅→∅),[21.6814]→[21.12057:12086](∅→∅),[21.12057]→[21.12057:12086](∅→∅),[21.73]→[21.6787:6814](∅→∅),[21.7806]→[21.6787:6814](∅→∅),[21.11991]→[21.15:73](∅→∅),[4.25]→[21.11962:11991](∅→∅),[21.7755]→[21.11962:11991](∅→∅),[21.103883]→[21.11962:11991](∅→∅),[21.11962]→[21.11962:11991](∅→∅),[21.7755]→[4.15:25](∅→∅),[21.7565]→[21.25240:25412](∅→∅),[21.11787]→[21.7538:7565](∅→∅),[21.2183]→[21.11739:11787](∅→∅),[21.11739]→[21.11739:11787](∅→∅),[21.11710]→[21.2149:2183](∅→∅),[21.2148]→[21.11680:11710](∅→∅),[21.11680]→[21.11680:11710](∅→∅),[21.7537]→[21.2118:2148](∅→∅),[21.11621]→[21.7491:7537](∅→∅),[21.7490]→[21.11589:11621](∅→∅),[21.103555]→[21.11589:11621](∅→∅),[21.11589]→[21.11589:11621](∅→∅),[21.11407]→[21.7278:7460](∅→∅),[21.7277]→[21.11370:11407](∅→∅),[21.103307]→[21.11370:11407](∅→∅),[21.11370]→[21.11370:11407](∅→∅),[21.7245]→[21.7245:7277](∅→∅),[21.11296]→[21.7185:7215](∅→∅),[21.6786]→[21.11262:11296](∅→∅),[21.11262]→[21.11262:11296](∅→∅),[21.7184]→[21.6689:6786](∅→∅),[21.7046]→[21.7046:7184](∅→∅),[21.11014]→[21.6986:7016](∅→∅),[8.241]→[21.10984:11014](∅→∅),[21.6985]→[21.10984:11014](∅→∅),[21.102945]→[21.10984:11014](∅→∅),[21.10984]→[21.10984:11014](∅→∅),[21.10962]→[21.6957:6985](∅→∅),[21.3828]→[21.10956:10962](∅→∅),[21.6956]→[21.10956:10962](∅→∅),[21.22974]→[21.10956:10962](∅→∅),[21.102909]→[21.10956:10962](∅→∅),[21.10956]→[21.10956:10962](∅→∅),[21.325]→[21.22916:22974](∅→∅),[21.363]→[21.22916:22974](∅→∅),[12.401]→[21.22916:22974](∅→∅),[21.500]→[21.22916:22974](∅→∅),[21.10886]→[21.22916:22974](∅→∅),[21.10752]→[12.214:401](∅→∅),[21.6873]→[21.10522:10752](∅→∅),[21.102812]→[21.10522:10752](∅→∅),[21.10522]→[21.10522:10752](∅→∅),[21.5675]→[21.6800:6873](∅→∅),[21.6800]→[21.6800:6873](∅→∅),[21.10343]→[21.5627:5675](∅→∅),[21.469]→[21.10338:10343](∅→∅),[21.10338]→[21.10338:10343](∅→∅),[21.10320]→[21.446:469](∅→∅),[21.273]→[21.10314:10320](∅→∅),[21.806]→[21.10314:10320](∅→∅),[21.10314]→[21.10314:10320](∅→∅),[21.5626]→[21.265:273](∅→∅),[21.265]→[21.265:273](∅→∅),[21.234]→[21.5594:5626](∅→∅),[21.10292]→[21.26:234](∅→∅),[21.2586]→[21.10285:10292](∅→∅),[21.6749]→[21.10285:10292](∅→∅),[21.102724]→[21.10285:10292](∅→∅),[21.10285]→[21.10285:10292](∅→∅),[21.10201]→[21.2482:2586](∅→∅),[21.6658]→[21.10119:10201](∅→∅),[21.102626]→[21.10119:10201](∅→∅),[21.10119]→[21.10119:10201](∅→∅),[21.2481]→[21.6610:6658](∅→∅),[21.6610]→[21.6610:6658](∅→∅),[21.283]→[21.2411:2481](∅→∅),[21.6547]→[21.2411:2481](∅→∅),[21.2117]→[21.200:283](∅→∅),[21.6495]→[21.2089:2117](∅→∅),[21.199]→[21.6394:6467](∅→∅),[21.743]→[21.6394:6467](∅→∅),[21.5593]→[21.6394:6467](∅→∅),[21.6394]→[21.6394:6467](∅→∅),[21.9696]→[21.5558:5593](∅→∅),[21.2225]→[21.9685:9696](∅→∅),[21.6276]→[21.9685:9696](∅→∅),[21.102222]→[21.9685:9696](∅→∅),[21.9685]→[21.9685:9696](∅→∅),[19.212]→[21.2163:2225](∅→∅),[21.2163]→[21.2163:2225](∅→∅),[21.2163]→[19.167:212](∅→∅),[10.182]→[21.2080:2163](∅→∅),[21.2080]→[21.2080:2163](∅→∅),[21.2073]→[10.161:182](∅→∅),[19.166]→[21.2014:2073](∅→∅),[21.2014]→[21.2014:2073](∅→∅),[21.2014]→[19.121:166](∅→∅),[21.936]→[21.1864:2014](∅→∅),[21.1241]→[21.1864:2014](∅→∅),[13.3165]→[21.1864:2014](∅→∅),[21.6276]→[21.1864:2014](∅→∅),[21.9624]→[13.2970:3165](∅→∅),[21.6202]→[21.9564:9624](∅→∅),[21.102134]→[21.9564:9624](∅→∅),[21.9564]→[21.9564:9624](∅→∅),[21.9432]→[21.6034:6202](∅→∅),[21.6033]→[21.9402:9432](∅→∅),[21.101923]→[21.9402:9432](∅→∅),[21.9402]→[21.9402:9432](∅→∅),[21.9360]→[21.5979:6033](∅→∅),[21.5978]→[21.9343:9360](∅→∅),[21.101854]→[21.9343:9360](∅→∅),[21.9343]→[21.9343:9360](∅→∅),[21.354]→[21.5833:5978](∅→∅),[21.744]→[21.5833:5978](∅→∅),[21.1049]→[21.5833:5978](∅→∅),[13.2969]→[21.5833:5978](∅→∅),[21.5833]→[21.5833:5978](∅→∅),[21.9165]→[13.2853:2969](∅→∅),[21.224]→[21.9153:9165](∅→∅),[21.421]→[21.9153:9165](∅→∅),[21.5763]→[21.9153:9165](∅→∅),[21.6688]→[21.9153:9165](∅→∅),[21.22915]→[21.9153:9165](∅→∅),[21.101597]→[21.9153:9165](∅→∅),[21.9153]→[21.9153:9165](∅→∅),[21.9058]→[21.357:421](∅→∅),[21.5655]→[21.9027:9058](∅→∅),[21.101475]→[21.9027:9058](∅→∅),[21.9027]→[21.9027:9058](∅→∅),[21.631]→[21.5627:5655](∅→∅),[21.936]→[21.5627:5655](∅→∅),[13.2852]→[21.5627:5655](∅→∅),[21.9005]→[21.5627:5655](∅→∅),[21.258]→[13.2769:2852](∅→∅),[21.8882]→[21.205:258](∅→∅),[21.5529]→[21.8848:8882](∅→∅),[21.101328]→[21.8848:8882](∅→∅),[21.8848]→[21.8848:8882](∅→∅),[21.856]→[21.5479:5529](∅→∅),[13.2768]→[21.5479:5529](∅→∅),[21.8804]→[21.5479:5529](∅→∅),[21.8804]→[13.2704:2768](∅→∅),[21.2410]→[21.8762:8804](∅→∅),[21.5478]→[21.8762:8804](∅→∅),[21.101270]→[21.8762:8804](∅→∅),[21.8762]→[21.8762:8804](∅→∅),[21.8668]→[21.2296:2410](∅→∅),[21.445]→[21.8641:8668](∅→∅),[21.8641]→[21.8641:8668](∅→∅),[21.5557]→[21.420:445](∅→∅),[21.68]→[21.420:445](∅→∅),[21.5377]→[21.5505:5557](∅→∅),[21.169]→[21.5339:5377](∅→∅),[21.551]→[21.5339:5377](∅→∅),[21.795]→[21.5339:5377](∅→∅),[13.2703]→[21.5339:5377](∅→∅),[21.8468]→[21.5339:5377](∅→∅),[21.5338]→[13.2596:2703](∅→∅),[21.5504]→[21.5299:5338](∅→∅),[21.5299]→[21.5299:5338](∅→∅),[21.8364]→[21.5450:5504](∅→∅),[13.2595]→[21.8359:8364](∅→∅),[21.8359]→[21.8359:8364](∅→∅),[21.8359]→[13.2287:2595](∅→∅),[13.2286]→[21.8335:8359](∅→∅),[21.8335]→[21.8335:8359](∅→∅),[21.788]→[13.2271:2286](∅→∅),[21.2295]→[21.722:788](∅→∅),[21.5243]→[21.2242:2295](∅→∅),[21.192]→[21.5145:5243](∅→∅),[21.754]→[21.5145:5243](∅→∅),[21.8134]→[21.5145:5243](∅→∅),[21.8089]→[21.57:192](∅→∅),[13.2270]→[21.8040:8089](∅→∅),[21.8040]→[21.8040:8089](∅→∅),[21.7962]→[13.2255:2270](∅→∅),[21.117]→[21.7952:7962](∅→∅),[21.356]→[21.7952:7962](∅→∅),[21.5067]→[21.7952:7962](∅→∅),[21.6469]→[21.7952:7962](∅→∅),[21.22758]→[21.7952:7962](∅→∅),[21.100865]→[21.7952:7962](∅→∅),[21.7952]→[21.7952:7962](∅→∅),[21.7859]→[21.294:356](∅→∅),[21.4961]→[21.7830:7859](∅→∅),[21.100745]→[21.7830:7859](∅→∅),[21.7830]→[21.7830:7859](∅→∅),[21.82]→[21.4792:4961](∅→∅),[21.329]→[21.4792:4961](∅→∅),[21.573]→[21.4792:4961](∅→∅),[21.5449]→[21.4792:4961](∅→∅),[14.6248]→[21.4792:4961](∅→∅),[21.7697]→[21.4792:4961](∅→∅),[21.7631]→[21.5383:5449](∅→∅),[21.60]→[21.7173:7631](∅→∅),[21.204]→[21.7173:7631](∅→∅),[21.4791]→[21.7173:7631](∅→∅),[21.6356]→[21.7173:7631](∅→∅),[21.22676]→[21.7173:7631](∅→∅),[21.100533]→[21.7173:7631](∅→∅),[21.7173]→[21.7173:7631](∅→∅),[21.7091]→[21.153:204](∅→∅),[21.4696]→[21.7059:7091](∅→∅),[21.100424]→[21.7059:7091](∅→∅),[21.7059]→[21.7059:7091](∅→∅),[21.1281]→[21.4648:4696](∅→∅),[21.7017]→[21.4648:4696](∅→∅),[21.865]→[21.1257:1281](∅→∅),[21.1257]→[21.1257:1281](∅→∅),[21.1226]→[21.793:865](∅→∅),[21.7017]→[21.1049:1226](∅→∅),[21.254]→[21.7016:7017](∅→∅),[21.7016]→[21.7016:7017](∅→∅),[21.94]→[21.172:254](∅→∅),[21.179]→[21.172:254](∅→∅),[21.172]→[21.172:254](∅→∅),[21.125]→[21.15:94](∅→∅),[21.253]→[21.15:94](∅→∅),[21.497]→[21.15:94](∅→∅),[13.2254]→[21.15:94](∅→∅),[21.4647]→[21.15:94](∅→∅),[21.4581]→[13.2149:2254](∅→∅),[21.721]→[21.4542:4581](∅→∅),[21.4542]→[21.4542:4581](∅→∅),[21.6757]→[21.669:721](∅→∅),[7.113]→[21.6746:6757](∅→∅),[21.470]→[21.6746:6757](∅→∅),[21.4487]→[21.6746:6757](∅→∅),[21.100241]→[21.6746:6757](∅→∅),[21.6746]→[21.6746:6757](∅→∅),[21.4441]→[21.446:470](∅→∅),[21.6692]→[21.4415:4441](∅→∅),[21.4414]→[21.6653:6692](∅→∅),[21.6653]→[21.6653:6692](∅→∅),[21.6632]→[21.4388:4414](∅→∅),[21.419]→[21.6621:6632](∅→∅),[5.895]→[21.6621:6632](∅→∅),[21.100147]→[21.6621:6632](∅→∅),[21.6621]→[21.6621:6632](∅→∅),[21.312]→[5.789:895](∅→∅),[21.6468]→[21.249:312](∅→∅),[21.4387]→[21.6457:6468](∅→∅),[21.99992]→[21.6457:6468](∅→∅),[21.6457]→[21.6457:6468](∅→∅),[21.445]→[21.4361:4387](∅→∅),[21.4361]→[21.4361:4387](∅→∅),[5.788]→[21.421:445](∅→∅),[21.4315]→[21.421:445](∅→∅),[21.43]→[5.727:788](∅→∅),[21.4252]→[21.15:43](∅→∅),[21.6095]→[21.4220:4252](∅→∅),[21.64]→[21.6090:6095](∅→∅),[21.6090]→[21.6090:6095](∅→∅),[21.6090]→[21.53:64](∅→∅),[21.2101]→[21.6084:6090](∅→∅),[21.6084]→[21.6084:6090](∅→∅),[21.4219]→[21.2069:2101](∅→∅),[13.2148]→[21.4191:4219](∅→∅),[21.4191]→[21.4191:4219](∅→∅),[21.5923]→[13.2108:2148](∅→∅),[21.4060]→[21.5917:5923](∅→∅),[21.99635]→[21.5917:5923](∅→∅),[21.5917]→[21.5917:5923](∅→∅),[21.1110]→[21.5845:5853](∅→∅),[21.5845]→[21.5845:5853](∅→∅),[21.5492]→[21.1067:1110](∅→∅),[21.633]→[21.5483:5492](∅→∅),[21.684]→[21.5483:5492](∅→∅),[21.3725]→[21.5483:5492](∅→∅),[21.99244]→[21.5483:5492](∅→∅),[21.5483]→[21.5483:5492](∅→∅),[21.56]→[21.558:633](∅→∅),[21.47]→[21.558:633](∅→∅),[21.557]→[21.15:56](∅→∅),[21.5347]→[21.525:557](∅→∅),[21.1066]→[21.5308:5347](∅→∅),[21.3617]→[21.5308:5347](∅→∅),[21.99122]→[21.5308:5347](∅→∅),[21.5308]→[21.5308:5347](∅→∅),[13.2107]→[21.1039:1066](∅→∅),[21.3617]→[21.1039:1066](∅→∅),[21.5239]→[13.2035:2107](∅→∅),[21.972]→[21.5229:5239](∅→∅),[21.5229]→[21.5229:5239](∅→∅),[21.113]→[21.935:972](∅→∅),[21.935]→[21.935:972](∅→∅),[21.1966]→[21.609:935](∅→∅),[21.8494]→[21.609:935](∅→∅),[21.609]→[21.609:935](∅→∅),[21.113]→[21.330:609](∅→∅),[11.195]→[21.330:609](∅→∅),[21.330]→[21.330:609](∅→∅),[21.269]→[11.103:195](∅→∅),[21.340]→[21.189:269](∅→∅),[21.189]→[21.189:269](∅→∅),[21.143]→[21.299:340](∅→∅),[21.298]→[21.92:143](∅→∅),[21.4442]→[21.92:143](∅→∅),[21.91]→[21.273:298](∅→∅),[13.2034]→[21.15:91](∅→∅),[21.25239]→[21.15:91](∅→∅),[21.3138]→[21.15:91](∅→∅),[21.3095]→[13.1997:2034](∅→∅),[21.4249]→[21.3031:3095](∅→∅),[21.3030]→[21.4205:4249](∅→∅),[21.98451]→[21.4205:4249](∅→∅),[21.4205]→[21.4205:4249](∅→∅),[21.4124]→[21.2931:3030](∅→∅),[13.1996]→[21.4101:4124](∅→∅),[21.2930]→[21.4101:4124](∅→∅),[21.22445]→[21.4101:4124](∅→∅),[21.98330]→[21.4101:4124](∅→∅),[21.4101]→[21.4101:4124](∅→∅),[3.314]→[21.22423:22445](∅→∅),[21.2901]→[21.22423:22445](∅→∅),[21.1165]→[3.268:314](∅→∅),[21.816]→[21.988:1165](∅→∅),[21.1895]→[21.988:1165](∅→∅),[21.1023]→[21.642:816](∅→∅),[21.44]→[21.999:1023](∅→∅),[21.2727]→[21.999:1023](∅→∅),[21.3913]→[21.999:1023](∅→∅),[21.52]→[21.2701:2727](∅→∅),[21.366]→[21.2701:2727](∅→∅),[21.838]→[21.2701:2727](∅→∅),[21.3868]→[21.2701:2727](∅→∅)
State.button_handlers = {}local screen_bottom1 = {line=nil, pos=nil}table.insert(State.line_cache, line_index, {})-- give some time for the OS to flush everything to disklove.timer.sleep(0.1)--? print_and_log(('edit.mouse_press: in line %d'):format(line_index))--? print('text input', t)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll-- undopatch_placeholders(State.line_cache, event.after, event.before)-- return y drawn untilif State.lines[State.cursor1.line].mode == 'text' thenbreakendState.cursor1.line = State.cursor1.line+1State.cursor1.pos = 1endfunction edit.put_cursor_on_next_text_line(State)while true doif State.cursor1.line >= #State.lines thenbreakendedit.draw(State)edit.update(State, 0)App.screen.contents = {}edit.mouse_release(State, x,y, mouse_button)function edit.run_after_mouse_release(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.draw(State)endedit.update(State, 0)App.screen.contents = {}edit.mouse_press(State, x,y, mouse_button)function edit.run_after_mouse_press(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.draw(State)endedit.update(State, 0)App.screen.contents = {}edit.mouse_release(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.mouse_press(State, x,y, mouse_button)function edit.run_after_mouse_click(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.draw(State)endedit.update(State, 0)App.screen.contents = {}edit.keychord_press(State, chord)edit.key_release(State, chord)function edit.run_after_keychord(State, chord)-- not all keys are text_inputendedit.draw(State)edit.update(State, 0)App.screen.contents = {}function edit.run_after_text_input(State, t)edit.keychord_press(State, t)edit.text_input(State, t)edit.key_release(State, t)-- TODO: handle chords of multiple keys-- all text_input events are also keypresses14, -- font height assuming default LÖVE font15) -- line heightendApp.screen.width - Test_margin_right,function edit.initialize_test_state()-- if you change these values, tests will start failingreturn edit.initialize_state(15, -- top marginTest_margin_left,Test_margin_right = 0Test_margin_left = 25-- Insulate tests from some key globals so I don't have to change the vast-- majority of tests when they're modified for the real app.--== some methods for testsendState.line_height = math.floor(font_height*1.3)love.graphics.setFont(love.graphics.newFont(State.font_height))function edit.update_font_settings(State, font_height)State.font_height = font_heightendfunction edit.key_release(State, key, scancode)endendText.keychord_press(State, chord)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelseschedule_save(State)endendif len > 0 thenlocal byte_offset = Text.offset(p.name, len-1)if len == 1 then byte_offset = 0 endp.name = string.sub(p.name, 1, byte_offset)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})endelseif chord == 'backspace' thenlocal len = utf8.len(p.name)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})local p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nillocal drawing = State.lines.current_drawinglocal before = snapshot(State, State.lines.current_drawing_index)elseState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilif chord == 'return' thenelseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenif line.mode == 'drawing' thenline.show_help = falseendendfor _,line in ipairs(State.lines) doendelseif chord == 'escape' and not App.mouse_down(1) thenschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})Drawing.keychord_press(State, chord)local before = snapshot(State, drawing_index)if drawing_index thenlocal drawing_index, drawing = Drawing.current_drawing(State)-- DON'T reset line_cache.starty here-- dispatch to drawing or textelseif App.mouse_down(1) or chord:sub(1,2) == 'C-' thenrecord_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})schedule_save(State)endText.snap_cursor_to_bottom_of_screen(State, State.left, State.right)if Text.cursor_out_of_screen(State) thenendendText.insert_at_cursor(State, c)elseText.insert_return(State)for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenlocal clipboard_data = App.get_clipboard()local before = snapshot(State, before_line)local before_line = State.cursor1.line-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelseif chord == 'C-v' thenschedule_save(State)endApp.set_clipboard(s)if s thenlocal s = Text.cut_selection(State, State.left, State.right)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollendelseif chord == 'C-x' thenApp.set_clipboard(s)if s thenlocal s = Text.selection(State)elseif chord == 'C-c' thenelseif chord == 'C-a' thenState.selection1 = {line=1, pos=1}State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}end-- clipboardschedule_save(State)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)State.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.before, event.after)if event thenlocal src = event.afterlocal event = redo_event(State)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollendelseif chord == 'C-y' thenschedule_save(State)-- invalidate various cached bits of linesState.lines.current_drawing = nil-- if we're scrolling, reclaim all fragments to avoid memory leaksText.redraw_all(State)State.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor)State.selection1 = deepcopy(src.selection)patch(State.lines, event.after, event.before)if event thenlocal src = event.beforelocal event = undo_event(State)for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelseif chord == 'C-z' thenText.redraw_all(State)edit.update_font_settings(State, 20)elseif chord == 'C-0' thenif State.font_height > 2 thenedit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)endelseif chord == 'C--' thenText.redraw_all(State)edit.update_font_settings(State, State.font_height+2)elseif chord == 'C-=' then-- zoomState.search_backup = {cursor={line=State.cursor1.line, pos=State.cursor1.pos},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},}State.search_term = ''endreturnelseif chord == 'C-f' thenText.search_previous(State)elseif chord == 'up' thenText.search_next(State)State.cursor1.pos = State.cursor1.pos+1elseif chord == 'down' thenlocal len = utf8.len(State.search_term)local byte_offset = Text.offset(State.search_term, len)State.search_term = string.sub(State.search_term, 1, byte_offset-1)elseif chord == 'backspace' thenState.search_backup = nilState.search_term = nilelseif chord == 'return' thenText.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaksState.cursor1 = State.search_backup.cursorState.screen_top1 = State.search_backup.screen_topState.search_backup = nilState.search_term = nilif chord == 'escape' thenif State.search_term thenendText.delete_selection(State, State.left, State.right)chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then-- printable character created using shift key => delete selection-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)(not App.shift_down() or utf8.len(key) == 1) andif State.selection1.line andnot State.lines.current_drawing andfunction edit.keychord_press(State, chord, key)endschedule_save(State)endendText.text_input(State, t)local drawing_index, drawing = Drawing.current_drawing(State)if drawing_index == nil thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrollelserecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})local p = drawing.points[drawing.pending.target_point]p.name = p.name..tlocal drawing = State.lines.current_drawinglocal before = snapshot(State, State.lines.current_drawing_index)elseif State.lines.current_drawing and State.current_drawing_mode == 'name' thenText.search_next(State)if State.search_term thenState.search_term = State.search_term..tfunction edit.text_input(State, t)endendfor i=1,math.floor(-dy) doText.down(State)endedit.put_cursor_on_next_text_line(State)State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}elseif dy < 0 thenfor i=1,math.floor(dy) doText.up(State)endedit.put_cursor_on_next_text_line(State)endendfunction edit.mouse_wheel_move(State, dx,dy)if dy > 0 thenState.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))endbreakendendendState.old_cursor1, State.old_selection1, State.mousepress_shift = nilif eq(State.cursor1, State.selection1) thenState.selection1 = {}endendState.selection1 = State.old_selection1elseif State.mousepress_shift thenif State.old_selection1.line == nil thenState.selection1 = State.old_cursor1--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))}pos=Text.to_pos_on_line(State, line_index, x, y),line=line_index,State.cursor1 = {--? print_and_log(('edit.mouse_release: in line %d'):format(line_index))if Text.in_line(State, line_index, x,y) thenif line.mode == 'text' thenfor line_index,line in ipairs(State.lines) do--? print_and_log('edit.mouse_release: no current drawing')Drawing.before = nilendelserecord_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})if Drawing.before thenschedule_save(State)Drawing.mouse_release(State, x,y, mouse_button)if State.lines.current_drawing then--? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.search_term then return endfunction edit.mouse_release(State, x,y, mouse_button)end-- still here? click is below all screen linesState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {line=State.screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),}endendendreturnDrawing.mouse_press(State, line_index, x,y, mouse_button)Drawing.before = snapshot(State, line_index)State.lines.current_drawing_index = line_indexState.lines.current_drawing = linelocal line_cache = State.line_cache[line_index]if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) thenendelseif line.mode == 'drawing' thenreturn}pos=Text.to_pos_on_line(State, line_index, x, y),line=line_index,State.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {-- i.e. mouse_release should never look at shift state-- delicate dance between cursor, selection and old cursor/selection-- scenarios:-- regular press+release: sets cursor, clears selection-- shift press+release:-- sets selection to old cursor if not set otherwise leaves it untouched-- sets cursor-- press and hold to start a selection: sets selection on press, cursor on release-- press and hold, then press shift: ignore shiftif Text.in_line(State, line_index, x,y) thenif line.mode == 'text' thenfor line_index,line in ipairs(State.lines) do}returnendline=State.screen_top1.line,pos=State.screen_top1.pos,if y < State.top thenState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()State.selection1 = {-- press on a button and it returned 'true' to short-circuitreturnendif mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then--? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))if State.search_term then return endfunction edit.mouse_press(State, x,y, mouse_button)endendsave_to_disk(State)if State.next_save then-- make sure to save before quittingfunction edit.quit(State)endendState.next_save = Current_time + 3 -- short enough that you're likely to still remember what you didfunction schedule_save(State)if State.next_save == nil thenendendState.next_save = nilsave_to_disk(State)if State.next_save and State.next_save < Current_time thenDrawing.update(State, dt)function edit.update(State, dt)endreturn yendText.draw_search_bar(State)if State.search_term thenState.screen_bottom1 = screen_bottom1endendprint(line.mode)assert(false)elsey = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottomDrawing.draw(State, line_index, y)y = y+Drawing_padding_topelseif line.mode == 'drawing' then--? print('=> y', y)y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)endend,})if State.cursor1.line >= line_index thenState.cursor1.line = State.cursor1.line+1endschedule_save(State)record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})icon = icon.insert_drawing,onpress1 = function()Drawing.before = snapshot(State, line_index-1, line_index)table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})button(State, 'draw', {x=State.left-Margin_left+4, y=y+4, w=12,h=12, color={1,1,0},endif line.data == '' then-- button to insert new drawingstartpos = State.screen_top1.posif line_index == State.screen_top1.line thenlocal startpos = 1if line.mode == 'text' then--? print('text.draw', y, line_index)screen_bottom1.line = line_indexif y + State.line_height > App.screen.height then break end--? print('draw:', y, line_index, line)for line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]--? print('== draw')local y = State.topState.cursor_x = nilState.cursor_y = nilif not Text.le1(State.screen_top1, State.cursor1) thenprint(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)assert(false)endif #State.lines ~= #State.line_cache thenprint(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))assert(false)endApp.color(Text_color)function edit.draw(State)[21.838]-- all text_input events are also keypresses-- TODO: handle chords of multiple keysfunction edit.run_after_text_input(State, t)edit.keychord_press(State, t)edit.text_input(State, t)edit.key_release(State, t)App.screen.contents = {}edit.update(State, 0)edit.draw(State)end-- not all keys are text_inputfunction edit.run_after_keychord(State, chord)edit.keychord_press(State, chord)edit.key_release(State, chord)App.screen.contents = {}edit.update(State, 0)edit.draw(State)endfunction edit.run_after_mouse_click(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.mouse_press(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.mouse_release(State, x,y, mouse_button)App.screen.contents = {}edit.update(State, 0)edit.draw(State)endfunction edit.run_after_mouse_press(State, x,y, mouse_button)App.fake_mouse_press(x,y, mouse_button)edit.mouse_press(State, x,y, mouse_button)App.screen.contents = {}edit.update(State, 0)edit.draw(State)endfunction edit.run_after_mouse_release(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.mouse_release(State, x,y, mouse_button)App.screen.contents = {}edit.update(State, 0)edit.draw(State)end