support drawings in the source editor
This commit is contained in:
parent
9f94470f9d
commit
528c64d690
|
@ -28,7 +28,7 @@ function source.draw_menu_bar()
|
||||||
else
|
else
|
||||||
add_hotkey_to_menu('ctrl+b: expand debug prints')
|
add_hotkey_to_menu('ctrl+b: expand debug prints')
|
||||||
end
|
end
|
||||||
add_hotkey_to_menu('ctrl+d: create/edit debug print')
|
add_hotkey_to_menu('ctrl+i: create/edit debug print')
|
||||||
add_hotkey_to_menu('ctrl+f: find in file')
|
add_hotkey_to_menu('ctrl+f: find in file')
|
||||||
add_hotkey_to_menu('alt+left alt+right: prev/next word')
|
add_hotkey_to_menu('alt+left alt+right: prev/next word')
|
||||||
elseif Focus == 'log_browser' then
|
elseif Focus == 'log_browser' then
|
||||||
|
|
8
edit.lua
8
edit.lua
|
@ -134,7 +134,7 @@ function edit.draw(State)
|
||||||
local line = State.lines[line_index]
|
local line = State.lines[line_index]
|
||||||
--? print('draw:', y, line_index, line)
|
--? print('draw:', y, line_index, line)
|
||||||
if y + State.line_height > App.screen.height then break end
|
if y + State.line_height > App.screen.height then break end
|
||||||
State.screen_bottom1.line = line_index
|
State.screen_bottom1 = {line=line_index, pos=nil}
|
||||||
if line.mode == 'text' then
|
if line.mode == 'text' then
|
||||||
--? print('text.draw', y, line_index)
|
--? print('text.draw', y, line_index)
|
||||||
local startpos = 1
|
local startpos = 1
|
||||||
|
@ -169,7 +169,6 @@ function edit.draw(State)
|
||||||
assert(false)
|
assert(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
--? print('screen bottom: '..tostring(State.screen_bottom1.pos)..' in '..tostring(State.lines[State.screen_bottom1.line].data))
|
|
||||||
if State.search_term then
|
if State.search_term then
|
||||||
Text.draw_search_bar(State)
|
Text.draw_search_bar(State)
|
||||||
end
|
end
|
||||||
|
@ -331,7 +330,10 @@ function edit.keychord_pressed(State, chord, key)
|
||||||
return
|
return
|
||||||
elseif chord == 'C-f' then
|
elseif chord == 'C-f' then
|
||||||
State.search_term = ''
|
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}}
|
State.search_backup = {
|
||||||
|
cursor={line=State.cursor1.line, pos=State.cursor1.pos},
|
||||||
|
screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},
|
||||||
|
}
|
||||||
assert(State.search_text == nil)
|
assert(State.search_text == nil)
|
||||||
-- zoom
|
-- zoom
|
||||||
elseif chord == 'C-=' then
|
elseif chord == 'C-=' then
|
||||||
|
|
3
file.lua
3
file.lua
|
@ -44,7 +44,8 @@ function save_to_disk(State)
|
||||||
if line.mode == 'drawing' then
|
if line.mode == 'drawing' then
|
||||||
store_drawing(outfile, line)
|
store_drawing(outfile, line)
|
||||||
else
|
else
|
||||||
outfile:write(line.data, '\n')
|
outfile:write(line.data)
|
||||||
|
outfile:write('\n')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
outfile:close()
|
outfile:close()
|
||||||
|
|
12
main.lua
12
main.lua
|
@ -24,6 +24,13 @@ load_file_from_source_or_save_directory('button.lua')
|
||||||
-- both sides require (different parts of) the logging framework
|
-- both sides require (different parts of) the logging framework
|
||||||
load_file_from_source_or_save_directory('log.lua')
|
load_file_from_source_or_save_directory('log.lua')
|
||||||
|
|
||||||
|
-- both sides use drawings
|
||||||
|
load_file_from_source_or_save_directory('icons.lua')
|
||||||
|
load_file_from_source_or_save_directory('drawing.lua')
|
||||||
|
load_file_from_source_or_save_directory('geom.lua')
|
||||||
|
load_file_from_source_or_save_directory('help.lua')
|
||||||
|
load_file_from_source_or_save_directory('drawing_tests.lua')
|
||||||
|
|
||||||
-- but some files we want to only load sometimes
|
-- but some files we want to only load sometimes
|
||||||
function App.load()
|
function App.load()
|
||||||
if love.filesystem.getInfo('config') then
|
if love.filesystem.getInfo('config') then
|
||||||
|
@ -43,13 +50,8 @@ function App.load()
|
||||||
load_file_from_source_or_save_directory('search.lua')
|
load_file_from_source_or_save_directory('search.lua')
|
||||||
load_file_from_source_or_save_directory('select.lua')
|
load_file_from_source_or_save_directory('select.lua')
|
||||||
load_file_from_source_or_save_directory('undo.lua')
|
load_file_from_source_or_save_directory('undo.lua')
|
||||||
load_file_from_source_or_save_directory('icons.lua')
|
|
||||||
load_file_from_source_or_save_directory('text_tests.lua')
|
load_file_from_source_or_save_directory('text_tests.lua')
|
||||||
load_file_from_source_or_save_directory('run_tests.lua')
|
load_file_from_source_or_save_directory('run_tests.lua')
|
||||||
load_file_from_source_or_save_directory('drawing.lua')
|
|
||||||
load_file_from_source_or_save_directory('geom.lua')
|
|
||||||
load_file_from_source_or_save_directory('help.lua')
|
|
||||||
load_file_from_source_or_save_directory('drawing_tests.lua')
|
|
||||||
else
|
else
|
||||||
load_file_from_source_or_save_directory('source_file.lua')
|
load_file_from_source_or_save_directory('source_file.lua')
|
||||||
load_file_from_source_or_save_directory('source.lua')
|
load_file_from_source_or_save_directory('source.lua')
|
||||||
|
|
|
@ -277,7 +277,7 @@ function source.mouse_pressed(x,y, mouse_button)
|
||||||
--? print('mouse click', x, y)
|
--? print('mouse click', x, y)
|
||||||
--? print(Editor_state.left, Editor_state.right)
|
--? print(Editor_state.left, Editor_state.right)
|
||||||
--? print(Log_browser_state.left, Log_browser_state.right)
|
--? print(Log_browser_state.left, Log_browser_state.right)
|
||||||
if Editor_state.left <= x and x < Editor_state.right then
|
if x < Editor_state.right + Margin_right then
|
||||||
--? print('click on edit side')
|
--? print('click on edit side')
|
||||||
if Focus ~= 'edit' then
|
if Focus ~= 'edit' then
|
||||||
Focus = 'edit'
|
Focus = 'edit'
|
||||||
|
|
178
source_edit.lua
178
source_edit.lua
|
@ -1,8 +1,14 @@
|
||||||
-- some constants people might like to tweak
|
-- some constants people might like to tweak
|
||||||
Text_color = {r=0, g=0, b=0}
|
Text_color = {r=0, g=0, b=0}
|
||||||
Cursor_color = {r=1, g=0, b=0}
|
Cursor_color = {r=1, g=0, b=0}
|
||||||
|
Stroke_color = {r=0, g=0, b=0}
|
||||||
|
Current_stroke_color = {r=0.7, g=0.7, b=0.7} -- in process of being drawn
|
||||||
|
Current_name_background_color = {r=1, g=0, b=0, a=0.1} -- name currently being edited
|
||||||
Focus_stroke_color = {r=1, g=0, b=0} -- what mouse is hovering over
|
Focus_stroke_color = {r=1, g=0, b=0} -- what mouse is hovering over
|
||||||
Highlight_color = {r=0.7, g=0.7, b=0.9} -- selected text
|
Highlight_color = {r=0.7, g=0.7, b=0.9} -- selected text
|
||||||
|
Icon_color = {r=0.7, g=0.7, b=0.7} -- color of current mode icon in drawings
|
||||||
|
Help_color = {r=0, g=0.5, b=0}
|
||||||
|
Help_background_color = {r=0, g=0.5, b=0, a=0.1}
|
||||||
Fold_color = {r=0, g=0.6, b=0}
|
Fold_color = {r=0, g=0.6, b=0}
|
||||||
Fold_background_color = {r=0, g=0.7, b=0}
|
Fold_background_color = {r=0, g=0.7, b=0}
|
||||||
|
|
||||||
|
@ -10,14 +16,40 @@ Margin_top = 15
|
||||||
Margin_left = 25
|
Margin_left = 25
|
||||||
Margin_right = 25
|
Margin_right = 25
|
||||||
|
|
||||||
|
Drawing_padding_top = 10
|
||||||
|
Drawing_padding_bottom = 10
|
||||||
|
Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom
|
||||||
|
|
||||||
|
Same_point_distance = 4 -- pixel distance at which two points are considered the same
|
||||||
|
|
||||||
edit = {}
|
edit = {}
|
||||||
|
|
||||||
-- run in both tests and a real run
|
-- run in both tests and a real run
|
||||||
function edit.initialize_state(top, left, right, font_height, line_height) -- currently always draws to bottom of screen
|
function edit.initialize_state(top, left, right, font_height, line_height) -- currently always draws to bottom of screen
|
||||||
local result = {
|
local result = {
|
||||||
-- a line of bifold text consists of an A side and an optional B side, each of which is a string
|
-- a line is either bifold text or a drawing
|
||||||
-- expanded: whether to show B side
|
-- a line of bifold text consists of an A side and an optional B side
|
||||||
lines = {{data='', dataB=nil, expanded=nil}}, -- array of lines
|
-- mode = 'text',
|
||||||
|
-- string data,
|
||||||
|
-- string dataB,
|
||||||
|
-- expanded: whether to show B side
|
||||||
|
-- a drawing is a table with:
|
||||||
|
-- mode = 'drawing'
|
||||||
|
-- a (y) coord in pixels (updated while painting screen),
|
||||||
|
-- a (h)eight,
|
||||||
|
-- an array of points, and
|
||||||
|
-- an array of shapes
|
||||||
|
-- a shape is a table containing:
|
||||||
|
-- a mode
|
||||||
|
-- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)
|
||||||
|
-- an array vertices for mode 'polygon', 'rectangle', 'square'
|
||||||
|
-- p1, p2 for mode 'line'
|
||||||
|
-- center, radius for mode 'circle'
|
||||||
|
-- center, radius, start_angle, end_angle for mode 'arc'
|
||||||
|
-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide
|
||||||
|
-- The field names are carefully chosen so that switching modes in midstream
|
||||||
|
-- remembers previously entered points where that makes sense.
|
||||||
|
lines = {{mode='text', data='', dataB=nil, expanded=nil}}, -- array of lines
|
||||||
|
|
||||||
-- Lines can be too long to fit on screen, in which case they _wrap_ into
|
-- Lines can be too long to fit on screen, in which case they _wrap_ into
|
||||||
-- multiple _screen lines_.
|
-- multiple _screen lines_.
|
||||||
|
@ -47,6 +79,9 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
|
||||||
cursor_x = 0,
|
cursor_x = 0,
|
||||||
cursor_y = 0,
|
cursor_y = 0,
|
||||||
|
|
||||||
|
current_drawing_mode = 'line',
|
||||||
|
previous_drawing_mode = nil, -- extra state for some ephemeral modes like moving/deleting/naming points
|
||||||
|
|
||||||
font_height = font_height,
|
font_height = font_height,
|
||||||
line_height = line_height,
|
line_height = line_height,
|
||||||
em = App.newText(love.graphics.getFont(), 'm'), -- widest possible character width
|
em = App.newText(love.graphics.getFont(), 'm'), -- widest possible character width
|
||||||
|
@ -71,6 +106,15 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
|
||||||
return result
|
return result
|
||||||
end -- App.initialize_state
|
end -- App.initialize_state
|
||||||
|
|
||||||
|
function edit.fixup_cursor(State)
|
||||||
|
for i,line in ipairs(State.lines) do
|
||||||
|
if line.mode == 'text' then
|
||||||
|
State.cursor1.line = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function edit.draw(State)
|
function edit.draw(State)
|
||||||
State.button_handlers = {}
|
State.button_handlers = {}
|
||||||
App.color(Text_color)
|
App.color(Text_color)
|
||||||
|
@ -85,21 +129,46 @@ function edit.draw(State)
|
||||||
--? print('== draw')
|
--? print('== draw')
|
||||||
for line_index = State.screen_top1.line,#State.lines do
|
for line_index = State.screen_top1.line,#State.lines do
|
||||||
local line = State.lines[line_index]
|
local line = State.lines[line_index]
|
||||||
--? print('draw:', y, line_index, line)
|
--? print('draw:', y, line_index, line, line.mode)
|
||||||
if y + State.line_height > App.screen.height then break end
|
if y + State.line_height > App.screen.height then break end
|
||||||
State.screen_bottom1 = {line=line_index, pos=nil, posB=nil}
|
State.screen_bottom1 = {line=line_index, pos=nil, posB=nil}
|
||||||
|
if line.mode == 'text' then
|
||||||
--? print('text.draw', y, line_index)
|
--? print('text.draw', y, line_index)
|
||||||
local startpos, startposB = 1, nil
|
local startpos, startposB = 1, nil
|
||||||
if line_index == State.screen_top1.line then
|
if line_index == State.screen_top1.line then
|
||||||
if State.screen_top1.pos then
|
if State.screen_top1.pos then
|
||||||
startpos = State.screen_top1.pos
|
startpos = State.screen_top1.pos
|
||||||
else
|
else
|
||||||
startpos, startposB = nil, State.screen_top1.posB
|
startpos, startposB = nil, State.screen_top1.posB
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
if line.data == '' then
|
||||||
|
-- button to insert new drawing
|
||||||
|
button(State, 'draw', {x=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, State.screen_bottom1.pos, State.screen_bottom1.posB = Text.draw(State, line_index, y, startpos, startposB)
|
||||||
|
y = y + State.line_height
|
||||||
|
--? 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
|
||||||
y, State.screen_bottom1.pos, State.screen_bottom1.posB = Text.draw(State, line_index, y, startpos, startposB)
|
|
||||||
y = y + State.line_height
|
|
||||||
--? print('=> y', y)
|
|
||||||
end
|
end
|
||||||
if State.search_term then
|
if State.search_term then
|
||||||
Text.draw_search_bar(State)
|
Text.draw_search_bar(State)
|
||||||
|
@ -107,6 +176,7 @@ function edit.draw(State)
|
||||||
end
|
end
|
||||||
|
|
||||||
function edit.update(State, dt)
|
function edit.update(State, dt)
|
||||||
|
Drawing.update(State, dt)
|
||||||
if State.next_save and State.next_save < App.getTime() then
|
if State.next_save and State.next_save < App.getTime() then
|
||||||
save_to_disk(State)
|
save_to_disk(State)
|
||||||
State.next_save = nil
|
State.next_save = nil
|
||||||
|
@ -128,23 +198,44 @@ end
|
||||||
|
|
||||||
function edit.mouse_pressed(State, x,y, mouse_button)
|
function edit.mouse_pressed(State, x,y, mouse_button)
|
||||||
if State.search_term then return end
|
if State.search_term then return end
|
||||||
--? print('press', State.selection1.line, State.selection1.pos)
|
--? print('press')
|
||||||
if mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then
|
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
|
-- press on a button and it returned 'true' to short-circuit
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
for line_index,line in ipairs(State.lines) do
|
for line_index,line in ipairs(State.lines) do
|
||||||
if Text.in_line(State, line_index, x,y) then
|
if line.mode == 'text' then
|
||||||
local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
|
if Text.in_line(State, line_index, x,y) then
|
||||||
--? print(x,y, 'setting cursor:', line_index, pos, posB)
|
local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
|
||||||
State.cursor1 = {line=line_index, pos=pos, posB=posB}
|
--? print(x,y, 'setting cursor:', line_index, pos, posB)
|
||||||
break
|
State.cursor1 = {line=line_index, pos=pos, posB=posB}
|
||||||
|
break
|
||||||
|
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_pressed(State, line_index, x,y, mouse_button)
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function edit.mouse_released(State, x,y, mouse_button)
|
function edit.mouse_released(State, x,y, mouse_button)
|
||||||
|
if State.search_term then return end
|
||||||
|
--? print('release')
|
||||||
|
if State.lines.current_drawing then
|
||||||
|
Drawing.mouse_released(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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function edit.textinput(State, t)
|
function edit.textinput(State, t)
|
||||||
|
@ -153,6 +244,12 @@ function edit.textinput(State, t)
|
||||||
State.search_term = State.search_term..t
|
State.search_term = State.search_term..t
|
||||||
State.search_text = nil
|
State.search_text = nil
|
||||||
Text.search_next(State)
|
Text.search_next(State)
|
||||||
|
elseif 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
|
else
|
||||||
Text.textinput(State, t)
|
Text.textinput(State, t)
|
||||||
end
|
end
|
||||||
|
@ -205,7 +302,7 @@ function edit.keychord_pressed(State, chord, key)
|
||||||
end
|
end
|
||||||
edit.eradicate_locations_after_the_fold(State)
|
edit.eradicate_locations_after_the_fold(State)
|
||||||
end
|
end
|
||||||
elseif chord == 'C-d' then
|
elseif chord == 'C-i' then
|
||||||
if State.cursor1.posB == nil then
|
if State.cursor1.posB == nil then
|
||||||
local before = snapshot(State, State.cursor1.line)
|
local before = snapshot(State, State.cursor1.line)
|
||||||
if State.lines[State.cursor1.line].dataB == nil then
|
if State.lines[State.cursor1.line].dataB == nil then
|
||||||
|
@ -240,6 +337,8 @@ function edit.keychord_pressed(State, chord, key)
|
||||||
State.cursor1 = deepcopy(src.cursor)
|
State.cursor1 = deepcopy(src.cursor)
|
||||||
patch(State.lines, event.after, event.before)
|
patch(State.lines, event.after, event.before)
|
||||||
patch_placeholders(State.line_cache, 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
|
-- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||||
Text.redraw_all(State)
|
Text.redraw_all(State)
|
||||||
schedule_save(State)
|
schedule_save(State)
|
||||||
|
@ -252,6 +351,8 @@ function edit.keychord_pressed(State, chord, key)
|
||||||
State.screen_top1 = deepcopy(src.screen_top)
|
State.screen_top1 = deepcopy(src.screen_top)
|
||||||
State.cursor1 = deepcopy(src.cursor)
|
State.cursor1 = deepcopy(src.cursor)
|
||||||
patch(State.lines, event.before, event.after)
|
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
|
-- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||||
Text.redraw_all(State)
|
Text.redraw_all(State)
|
||||||
schedule_save(State)
|
schedule_save(State)
|
||||||
|
@ -289,7 +390,42 @@ function edit.keychord_pressed(State, chord, key)
|
||||||
end
|
end
|
||||||
schedule_save(State)
|
schedule_save(State)
|
||||||
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
|
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
|
||||||
-- dispatch to text
|
-- 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_pressed(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
|
||||||
|
end
|
||||||
|
elseif 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)
|
||||||
|
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
|
||||||
|
schedule_save(State)
|
||||||
else
|
else
|
||||||
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
|
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
|
||||||
Text.keychord_pressed(State, chord)
|
Text.keychord_pressed(State, chord)
|
||||||
|
|
156
source_file.lua
156
source_file.lua
|
@ -25,17 +25,21 @@ function load_from_file(infile)
|
||||||
while true do
|
while true do
|
||||||
local line = infile_next_line()
|
local line = infile_next_line()
|
||||||
if line == nil then break end
|
if line == nil then break end
|
||||||
local line_info = {}
|
if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
|
||||||
if line:find(Fold) then
|
table.insert(result, load_drawing(infile_next_line))
|
||||||
_, _, line_info.data, line_info.dataB = line:find('([^'..Fold..']*)'..Fold..'([^'..Fold..']*)')
|
|
||||||
else
|
else
|
||||||
line_info.data = line
|
local line_info = {mode='text'}
|
||||||
|
if line:find(Fold) then
|
||||||
|
_, _, line_info.data, line_info.dataB = line:find('([^'..Fold..']*)'..Fold..'([^'..Fold..']*)')
|
||||||
|
else
|
||||||
|
line_info.data = line
|
||||||
|
end
|
||||||
|
table.insert(result, line_info)
|
||||||
end
|
end
|
||||||
table.insert(result, line_info)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #result == 0 then
|
if #result == 0 then
|
||||||
table.insert(result, {data=''})
|
table.insert(result, {mode='text', data=''})
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
@ -46,16 +50,86 @@ function save_to_disk(State)
|
||||||
error('failed to write to "'..State.filename..'"')
|
error('failed to write to "'..State.filename..'"')
|
||||||
end
|
end
|
||||||
for _,line in ipairs(State.lines) do
|
for _,line in ipairs(State.lines) do
|
||||||
outfile:write(line.data)
|
if line.mode == 'drawing' then
|
||||||
if line.dataB and #line.dataB > 0 then
|
store_drawing(outfile, line)
|
||||||
outfile:write(Fold)
|
else
|
||||||
outfile:write(line.dataB)
|
outfile:write(line.data)
|
||||||
|
if line.dataB and #line.dataB > 0 then
|
||||||
|
outfile:write(Fold)
|
||||||
|
outfile:write(line.dataB)
|
||||||
|
end
|
||||||
|
outfile:write('\n')
|
||||||
end
|
end
|
||||||
outfile:write('\n')
|
|
||||||
end
|
end
|
||||||
outfile:close()
|
outfile:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function load_drawing(infile_next_line)
|
||||||
|
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
|
||||||
|
while true do
|
||||||
|
local line = infile_next_line()
|
||||||
|
assert(line)
|
||||||
|
if line == '```' then break end
|
||||||
|
local shape = json.decode(line)
|
||||||
|
if shape.mode == 'freehand' then
|
||||||
|
-- no changes needed
|
||||||
|
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||||
|
local name = shape.p1.name
|
||||||
|
shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.p1].name = name
|
||||||
|
name = shape.p2.name
|
||||||
|
shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.p2].name = name
|
||||||
|
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||||
|
for i,p in ipairs(shape.vertices) do
|
||||||
|
local name = p.name
|
||||||
|
shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.vertices[i]].name = name
|
||||||
|
end
|
||||||
|
elseif shape.mode == 'circle' or shape.mode == 'arc' then
|
||||||
|
local name = shape.center.name
|
||||||
|
shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.center].name = name
|
||||||
|
elseif shape.mode == 'deleted' then
|
||||||
|
-- ignore
|
||||||
|
else
|
||||||
|
print(shape.mode)
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
|
table.insert(drawing.shapes, shape)
|
||||||
|
end
|
||||||
|
return drawing
|
||||||
|
end
|
||||||
|
|
||||||
|
function store_drawing(outfile, drawing)
|
||||||
|
outfile:write('```lines\n')
|
||||||
|
for _,shape in ipairs(drawing.shapes) do
|
||||||
|
if shape.mode == 'freehand' then
|
||||||
|
outfile:write(json.encode(shape), '\n')
|
||||||
|
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||||
|
local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
|
||||||
|
outfile:write(line, '\n')
|
||||||
|
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||||
|
local obj = {mode=shape.mode, vertices={}}
|
||||||
|
for _,p in ipairs(shape.vertices) do
|
||||||
|
table.insert(obj.vertices, drawing.points[p])
|
||||||
|
end
|
||||||
|
local line = json.encode(obj)
|
||||||
|
outfile:write(line, '\n')
|
||||||
|
elseif shape.mode == 'circle' then
|
||||||
|
outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}), '\n')
|
||||||
|
elseif shape.mode == 'arc' then
|
||||||
|
outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle}), '\n')
|
||||||
|
elseif shape.mode == 'deleted' then
|
||||||
|
-- ignore
|
||||||
|
else
|
||||||
|
print(shape.mode)
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
outfile:write('```\n')
|
||||||
|
end
|
||||||
|
|
||||||
-- for tests
|
-- for tests
|
||||||
function load_array(a)
|
function load_array(a)
|
||||||
local result = {}
|
local result = {}
|
||||||
|
@ -64,16 +138,64 @@ function load_array(a)
|
||||||
while true do
|
while true do
|
||||||
i,line = next_line(a, i)
|
i,line = next_line(a, i)
|
||||||
if i == nil then break end
|
if i == nil then break end
|
||||||
local line_info = {}
|
--? print(line)
|
||||||
if line:find(Fold) then
|
if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
|
||||||
_, _, line_info.data, line_info.dataB = line:find('([^'..Fold..']*)'..Fold..'([^'..Fold..']*)')
|
--? print('inserting drawing')
|
||||||
|
i, drawing = load_drawing_from_array(next_line, a, i)
|
||||||
|
--? print('i now', i)
|
||||||
|
table.insert(result, drawing)
|
||||||
else
|
else
|
||||||
line_info.data = line
|
--? print('inserting text')
|
||||||
|
local line_info = {mode='text'}
|
||||||
|
if line:find(Fold) then
|
||||||
|
_, _, line_info.data, line_info.dataB = line:find('([^'..Fold..']*)'..Fold..'([^'..Fold..']*)')
|
||||||
|
else
|
||||||
|
line_info.data = line
|
||||||
|
end
|
||||||
|
table.insert(result, line_info)
|
||||||
end
|
end
|
||||||
table.insert(result, line_info)
|
|
||||||
end
|
end
|
||||||
if #result == 0 then
|
if #result == 0 then
|
||||||
table.insert(result, {data=''})
|
table.insert(result, {mode='text', data=''})
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function load_drawing_from_array(iter, a, i)
|
||||||
|
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
|
||||||
|
local line
|
||||||
|
while true do
|
||||||
|
i, line = iter(a, i)
|
||||||
|
assert(i)
|
||||||
|
--? print(i)
|
||||||
|
if line == '```' then break end
|
||||||
|
local shape = json.decode(line)
|
||||||
|
if shape.mode == 'freehand' then
|
||||||
|
-- no changes needed
|
||||||
|
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||||
|
local name = shape.p1.name
|
||||||
|
shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.p1].name = name
|
||||||
|
name = shape.p2.name
|
||||||
|
shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.p2].name = name
|
||||||
|
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||||
|
for i,p in ipairs(shape.vertices) do
|
||||||
|
local name = p.name
|
||||||
|
shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.vertices[i]].name = name
|
||||||
|
end
|
||||||
|
elseif shape.mode == 'circle' or shape.mode == 'arc' then
|
||||||
|
local name = shape.center.name
|
||||||
|
shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
|
||||||
|
drawing.points[shape.center].name = name
|
||||||
|
elseif shape.mode == 'deleted' then
|
||||||
|
-- ignore
|
||||||
|
else
|
||||||
|
print(shape.mode)
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
|
table.insert(drawing.shapes, shape)
|
||||||
|
end
|
||||||
|
return i, drawing
|
||||||
|
end
|
||||||
|
|
197
source_text.lua
197
source_text.lua
|
@ -53,9 +53,6 @@ function Text.draw(State, line_index, y, startpos, startposB)
|
||||||
-- draw B side
|
-- draw B side
|
||||||
--? if line_index == 8 then print('drawing B side') end
|
--? if line_index == 8 then print('drawing B side') end
|
||||||
App.color(Fold_color)
|
App.color(Fold_color)
|
||||||
--? if Foo then
|
|
||||||
--? print('draw:', State.lines[line_index].data, "=====", State.lines[line_index].dataB, 'starting from x', x+AB_padding)
|
|
||||||
--? end
|
|
||||||
if startposB then
|
if startposB then
|
||||||
overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x,y, startposB)
|
overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x,y, startposB)
|
||||||
else
|
else
|
||||||
|
@ -196,6 +193,7 @@ end
|
||||||
|
|
||||||
function Text.populate_screen_line_starting_pos(State, line_index)
|
function Text.populate_screen_line_starting_pos(State, line_index)
|
||||||
local line = State.lines[line_index]
|
local line = State.lines[line_index]
|
||||||
|
if line.mode ~= 'text' then return end
|
||||||
local line_cache = State.line_cache[line_index]
|
local line_cache = State.line_cache[line_index]
|
||||||
if line_cache.screen_line_starting_pos then
|
if line_cache.screen_line_starting_pos then
|
||||||
return
|
return
|
||||||
|
@ -222,6 +220,7 @@ end
|
||||||
function Text.compute_fragments(State, line_index)
|
function Text.compute_fragments(State, line_index)
|
||||||
--? print('compute_fragments', line_index, 'between', State.left, State.right)
|
--? print('compute_fragments', line_index, 'between', State.left, State.right)
|
||||||
local line = State.lines[line_index]
|
local line = State.lines[line_index]
|
||||||
|
if line.mode ~= 'text' then return end
|
||||||
local line_cache = State.line_cache[line_index]
|
local line_cache = State.line_cache[line_index]
|
||||||
if line_cache.fragments then
|
if line_cache.fragments then
|
||||||
return
|
return
|
||||||
|
@ -416,11 +415,16 @@ function Text.keychord_pressed(State, chord)
|
||||||
end
|
end
|
||||||
elseif State.cursor1.line > 1 then
|
elseif State.cursor1.line > 1 then
|
||||||
before = snapshot(State, State.cursor1.line-1, State.cursor1.line)
|
before = snapshot(State, State.cursor1.line-1, State.cursor1.line)
|
||||||
-- join lines
|
if State.lines[State.cursor1.line-1].mode == 'drawing' then
|
||||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
|
table.remove(State.lines, State.cursor1.line-1)
|
||||||
State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
|
table.remove(State.line_cache, State.cursor1.line-1)
|
||||||
table.remove(State.lines, State.cursor1.line)
|
else
|
||||||
table.remove(State.line_cache, State.cursor1.line)
|
-- join lines
|
||||||
|
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
|
||||||
|
State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
|
||||||
|
table.remove(State.lines, State.cursor1.line)
|
||||||
|
table.remove(State.line_cache, State.cursor1.line)
|
||||||
|
end
|
||||||
State.cursor1.line = State.cursor1.line-1
|
State.cursor1.line = State.cursor1.line-1
|
||||||
end
|
end
|
||||||
if State.screen_top1.line > #State.lines then
|
if State.screen_top1.line > #State.lines then
|
||||||
|
@ -471,10 +475,12 @@ function Text.keychord_pressed(State, chord)
|
||||||
-- refuse to delete past end of side B
|
-- refuse to delete past end of side B
|
||||||
end
|
end
|
||||||
elseif State.cursor1.line < #State.lines then
|
elseif State.cursor1.line < #State.lines then
|
||||||
-- join lines
|
if State.lines[State.cursor1.line+1].mode == 'text' then
|
||||||
State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
|
-- join lines
|
||||||
-- delete side B on first line
|
State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
|
||||||
State.lines[State.cursor1.line].dataB = State.lines[State.cursor1.line+1].dataB
|
-- delete side B on first line
|
||||||
|
State.lines[State.cursor1.line].dataB = State.lines[State.cursor1.line+1].dataB
|
||||||
|
end
|
||||||
table.remove(State.lines, State.cursor1.line+1)
|
table.remove(State.lines, State.cursor1.line+1)
|
||||||
table.remove(State.line_cache, State.cursor1.line+1)
|
table.remove(State.line_cache, State.cursor1.line+1)
|
||||||
end
|
end
|
||||||
|
@ -530,7 +536,7 @@ function Text.insert_return(State)
|
||||||
if State.cursor1.pos then
|
if State.cursor1.pos then
|
||||||
-- when inserting a newline, move any B side to the new line
|
-- when inserting a newline, move any B side to the new line
|
||||||
local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
|
local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
|
||||||
table.insert(State.lines, State.cursor1.line+1, {data=string.sub(State.lines[State.cursor1.line].data, byte_offset), dataB=State.lines[State.cursor1.line].dataB})
|
table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset), dataB=State.lines[State.cursor1.line].dataB})
|
||||||
table.insert(State.line_cache, State.cursor1.line+1, {})
|
table.insert(State.line_cache, State.cursor1.line+1, {})
|
||||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
|
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
|
||||||
State.lines[State.cursor1.line].dataB = nil
|
State.lines[State.cursor1.line].dataB = nil
|
||||||
|
@ -550,7 +556,11 @@ function Text.pageup(State)
|
||||||
while y >= State.top do
|
while y >= State.top do
|
||||||
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
|
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
|
||||||
if State.screen_top1.line == 1 and State.screen_top1.pos and State.screen_top1.pos == 1 then break end
|
if State.screen_top1.line == 1 and State.screen_top1.pos and State.screen_top1.pos == 1 then break end
|
||||||
y = y - State.line_height
|
if State.lines[State.screen_top1.line].mode == 'text' then
|
||||||
|
y = y - State.line_height
|
||||||
|
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
|
||||||
|
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
|
||||||
|
end
|
||||||
top2 = Text.previous_screen_line(State, top2)
|
top2 = Text.previous_screen_line(State, top2)
|
||||||
end
|
end
|
||||||
State.screen_top1 = Text.to1(State, top2)
|
State.screen_top1 = Text.to1(State, top2)
|
||||||
|
@ -567,7 +577,7 @@ function Text.pagedown(State)
|
||||||
if Text.lt1(State.screen_top1, new_top1) then
|
if Text.lt1(State.screen_top1, new_top1) then
|
||||||
State.screen_top1 = new_top1
|
State.screen_top1 = new_top1
|
||||||
else
|
else
|
||||||
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
|
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos, posB=State.screen_bottom1.posB}
|
||||||
end
|
end
|
||||||
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
|
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
|
||||||
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}
|
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}
|
||||||
|
@ -578,6 +588,7 @@ function Text.pagedown(State)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.up(State)
|
function Text.up(State)
|
||||||
|
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||||
if State.cursor1.pos then
|
if State.cursor1.pos then
|
||||||
Text.upA(State)
|
Text.upA(State)
|
||||||
else
|
else
|
||||||
|
@ -591,18 +602,23 @@ function Text.upA(State)
|
||||||
if screen_line_starting_pos == 1 then
|
if screen_line_starting_pos == 1 then
|
||||||
--? print('cursor is at first screen line of its line')
|
--? print('cursor is at first screen line of its line')
|
||||||
-- line is done; skip to previous text line
|
-- line is done; skip to previous text line
|
||||||
if State.cursor1.line > 1 then
|
local new_cursor_line = State.cursor1.line
|
||||||
--? print('found previous text line')
|
while new_cursor_line > 1 do
|
||||||
State.cursor1 = {line=State.cursor1.line-1, pos=nil}
|
new_cursor_line = new_cursor_line-1
|
||||||
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
-- previous text line found, pick its final screen line
|
--? print('found previous text line')
|
||||||
--? print('has multiple screen lines')
|
State.cursor1 = {line=State.cursor1.line-1, pos=nil}
|
||||||
local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
|
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||||
--? print(#screen_line_starting_pos)
|
-- previous text line found, pick its final screen line
|
||||||
screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
|
--? print('has multiple screen lines')
|
||||||
local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
|
local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
|
||||||
local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
|
--? print(#screen_line_starting_pos)
|
||||||
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
|
||||||
|
local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
|
||||||
|
local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
|
||||||
|
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- move up one screen line in current line
|
-- move up one screen line in current line
|
||||||
|
@ -626,15 +642,19 @@ function Text.upB(State)
|
||||||
assert(screen_line_indexB >= 1)
|
assert(screen_line_indexB >= 1)
|
||||||
if screen_line_indexB == 1 then
|
if screen_line_indexB == 1 then
|
||||||
-- move to A side of previous line
|
-- move to A side of previous line
|
||||||
if State.cursor1.line > 1 then
|
local new_cursor_line = State.cursor1.line
|
||||||
State.cursor1.line = State.cursor1.line-1
|
while new_cursor_line > 1 do
|
||||||
State.cursor1.posB = nil
|
new_cursor_line = new_cursor_line-1
|
||||||
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
local prev_line_cache = State.line_cache[State.cursor1.line]
|
State.cursor1 = {line=State.cursor1.line-1, posB=nil}
|
||||||
local prev_screen_line_starting_pos = prev_line_cache.screen_line_starting_pos[#prev_line_cache.screen_line_starting_pos]
|
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||||
local prev_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, prev_screen_line_starting_pos)
|
local prev_line_cache = State.line_cache[State.cursor1.line]
|
||||||
local s = string.sub(State.lines[State.cursor1.line].data, prev_screen_line_starting_byte_offset)
|
local prev_screen_line_starting_pos = prev_line_cache.screen_line_starting_pos[#prev_line_cache.screen_line_starting_pos]
|
||||||
State.cursor1.pos = prev_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
local prev_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, prev_screen_line_starting_pos)
|
||||||
|
local s = string.sub(State.lines[State.cursor1.line].data, prev_screen_line_starting_byte_offset)
|
||||||
|
State.cursor1.pos = prev_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elseif screen_line_indexB == 2 then
|
elseif screen_line_indexB == 2 then
|
||||||
-- all-B screen-line to potentially A+B screen-line
|
-- all-B screen-line to potentially A+B screen-line
|
||||||
|
@ -673,16 +693,22 @@ end
|
||||||
-- cursor on A side => move down one screen line (A side) in current line
|
-- cursor on A side => move down one screen line (A side) in current line
|
||||||
-- cursor on B side => move down one screen line (B side) in current line
|
-- cursor on B side => move down one screen line (B side) in current line
|
||||||
function Text.down(State)
|
function Text.down(State)
|
||||||
|
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||||
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
||||||
if Text.cursor_at_final_screen_line(State) then
|
if Text.cursor_at_final_screen_line(State) then
|
||||||
-- line is done, skip to next text line
|
-- line is done, skip to next text line
|
||||||
--? print('cursor at final screen line of its line')
|
--? print('cursor at final screen line of its line')
|
||||||
if State.cursor1.line < #State.lines then
|
local new_cursor_line = State.cursor1.line
|
||||||
State.cursor1 = {
|
while new_cursor_line < #State.lines do
|
||||||
line = State.cursor1.line+1,
|
new_cursor_line = new_cursor_line+1
|
||||||
pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line+1].data, State.cursor_x, State.left)
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
}
|
State.cursor1 = {
|
||||||
--? print(State.cursor1.pos)
|
line = new_cursor_line,
|
||||||
|
pos = Text.nearest_cursor_pos(State.lines[new_cursor_line].data, State.cursor_x, State.left),
|
||||||
|
}
|
||||||
|
--? print(State.cursor1.pos)
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if State.cursor1.line > State.screen_bottom1.line then
|
if State.cursor1.line > State.screen_bottom1.line then
|
||||||
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
|
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
|
||||||
|
@ -737,7 +763,7 @@ function Text.start_of_line(State)
|
||||||
State.cursor1.posB = 1
|
State.cursor1.posB = 1
|
||||||
end
|
end
|
||||||
if Text.lt1(State.cursor1, State.screen_top1) then
|
if Text.lt1(State.cursor1, State.screen_top1) then
|
||||||
State.screen_top1 = {line=State.cursor1.line, pos=1} -- copy
|
State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} -- copy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -919,11 +945,18 @@ end
|
||||||
function Text.leftA(State)
|
function Text.leftA(State)
|
||||||
if State.cursor1.pos > 1 then
|
if State.cursor1.pos > 1 then
|
||||||
State.cursor1.pos = State.cursor1.pos-1
|
State.cursor1.pos = State.cursor1.pos-1
|
||||||
elseif State.cursor1.line > 1 then
|
else
|
||||||
State.cursor1 = {
|
local new_cursor_line = State.cursor1.line
|
||||||
line = State.cursor1.line-1,
|
while new_cursor_line > 1 do
|
||||||
pos = utf8.len(State.lines[State.cursor1.line-1].data) + 1,
|
new_cursor_line = new_cursor_line-1
|
||||||
}
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
|
State.cursor1 = {
|
||||||
|
line = new_cursor_line,
|
||||||
|
pos = utf8.len(State.lines[new_cursor_line].data) + 1,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if Text.lt1(State.cursor1, State.screen_top1) then
|
if Text.lt1(State.cursor1, State.screen_top1) then
|
||||||
local top2 = Text.to2(State, State.screen_top1)
|
local top2 = Text.to2(State, State.screen_top1)
|
||||||
|
@ -955,6 +988,7 @@ function Text.right(State)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.right_without_scroll(State)
|
function Text.right_without_scroll(State)
|
||||||
|
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||||
if State.cursor1.pos then
|
if State.cursor1.pos then
|
||||||
Text.right_without_scrollA(State)
|
Text.right_without_scrollA(State)
|
||||||
else
|
else
|
||||||
|
@ -965,17 +999,31 @@ end
|
||||||
function Text.right_without_scrollA(State)
|
function Text.right_without_scrollA(State)
|
||||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
|
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
|
||||||
State.cursor1.pos = State.cursor1.pos+1
|
State.cursor1.pos = State.cursor1.pos+1
|
||||||
elseif State.cursor1.line <= #State.lines-1 then
|
else
|
||||||
State.cursor1 = {line=State.cursor1.line+1, pos=1}
|
local new_cursor_line = State.cursor1.line
|
||||||
|
while new_cursor_line <= #State.lines-1 do
|
||||||
|
new_cursor_line = new_cursor_line+1
|
||||||
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
|
State.cursor1 = {line=new_cursor_line, pos=1}
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.right_without_scrollB(State)
|
function Text.right_without_scrollB(State)
|
||||||
if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then
|
if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then
|
||||||
State.cursor1.posB = State.cursor1.posB+1
|
State.cursor1.posB = State.cursor1.posB+1
|
||||||
elseif State.cursor1.line <= #State.lines-1 then
|
else
|
||||||
-- overflow back into A side
|
-- overflow back into A side
|
||||||
State.cursor1 = {line=State.cursor1.line+1, pos=1}
|
local new_cursor_line = State.cursor1.line
|
||||||
|
while new_cursor_line <= #State.lines-1 do
|
||||||
|
new_cursor_line = new_cursor_line+1
|
||||||
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
|
State.cursor1 = {line=new_cursor_line, pos=1}
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1027,7 +1075,23 @@ function Text.cursor_at_final_screen_line(State)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
||||||
if State.top > App.screen.height - State.line_height then
|
local y = State.top
|
||||||
|
while State.cursor1.line <= #State.lines do
|
||||||
|
if State.lines[State.cursor1.line].mode == 'text' then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
--? print('cursor skips', State.cursor1.line)
|
||||||
|
y = y + Drawing_padding_height + Drawing.pixels(State.lines[State.cursor1.line].h, State.width)
|
||||||
|
State.cursor1.line = State.cursor1.line + 1
|
||||||
|
end
|
||||||
|
-- hack: insert a text line at bottom of file if necessary
|
||||||
|
if State.cursor1.line > #State.lines then
|
||||||
|
assert(State.cursor1.line == #State.lines+1)
|
||||||
|
table.insert(State.lines, {mode='text', data=''})
|
||||||
|
table.insert(State.line_cache, {})
|
||||||
|
end
|
||||||
|
--? print(y, App.screen.height, App.screen.height-State.line_height)
|
||||||
|
if y > App.screen.height - State.line_height then
|
||||||
--? print('scroll up')
|
--? print('scroll up')
|
||||||
Text.snap_cursor_to_bottom_of_screen(State)
|
Text.snap_cursor_to_bottom_of_screen(State)
|
||||||
end
|
end
|
||||||
|
@ -1052,11 +1116,24 @@ function Text.snap_cursor_to_bottom_of_screen(State)
|
||||||
while true do
|
while true do
|
||||||
--? print(y, 'top2:', State.lines[top2.line].data, top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB)
|
--? print(y, 'top2:', State.lines[top2.line].data, top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB)
|
||||||
if top2.line == 1 and top2.screen_line == 1 then break end
|
if top2.line == 1 and top2.screen_line == 1 then break end
|
||||||
local h = State.line_height
|
if top2.screen_line > 1 or State.lines[top2.line-1].mode == 'text' then
|
||||||
if y - h < State.top then
|
local h = State.line_height
|
||||||
break
|
if y - h < State.top then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
y = y - h
|
||||||
|
else
|
||||||
|
assert(top2.line > 1)
|
||||||
|
assert(State.lines[top2.line-1].mode == 'drawing')
|
||||||
|
-- We currently can't draw partial drawings, so either skip it entirely
|
||||||
|
-- or not at all.
|
||||||
|
local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
|
||||||
|
if y - h < State.top then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
--? print('skipping drawing of height', h)
|
||||||
|
y = y - h
|
||||||
end
|
end
|
||||||
y = y - h
|
|
||||||
top2 = Text.previous_screen_line(State, top2)
|
top2 = Text.previous_screen_line(State, top2)
|
||||||
end
|
end
|
||||||
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
|
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
|
||||||
|
@ -1064,7 +1141,6 @@ function Text.snap_cursor_to_bottom_of_screen(State)
|
||||||
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
|
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
|
||||||
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB, State.screen_bottom1.line, State.screen_bottom1.pos, State.screen_bottom1.posB)
|
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB, State.screen_bottom1.line, State.screen_bottom1.pos, State.screen_bottom1.posB)
|
||||||
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||||
Foo = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.in_line(State, line_index, x,y)
|
function Text.in_line(State, line_index, x,y)
|
||||||
|
@ -1338,6 +1414,9 @@ function Text.x(s, pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.to2(State, loc1)
|
function Text.to2(State, loc1)
|
||||||
|
if State.lines[loc1.line].mode == 'drawing' then
|
||||||
|
return {line=loc1.line, screen_line=1, screen_pos=1}
|
||||||
|
end
|
||||||
if loc1.pos then
|
if loc1.pos then
|
||||||
return Text.to2A(State, loc1)
|
return Text.to2A(State, loc1)
|
||||||
else
|
else
|
||||||
|
@ -1448,10 +1527,8 @@ end
|
||||||
|
|
||||||
function Text.previous_screen_lineA(State, loc2)
|
function Text.previous_screen_lineA(State, loc2)
|
||||||
if loc2.screen_line > 1 then
|
if loc2.screen_line > 1 then
|
||||||
--? print('a')
|
|
||||||
return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1}
|
return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1}
|
||||||
elseif loc2.line == 1 then
|
elseif loc2.line == 1 then
|
||||||
--? print('b')
|
|
||||||
return loc2
|
return loc2
|
||||||
else
|
else
|
||||||
Text.populate_screen_line_starting_pos(State, loc2.line-1)
|
Text.populate_screen_line_starting_pos(State, loc2.line-1)
|
||||||
|
|
|
@ -14,6 +14,34 @@ function test_initial_state()
|
||||||
check_eq(Editor_state.screen_top1.pos, 1, 'F - test_initial_state/screen_top:pos')
|
check_eq(Editor_state.screen_top1.pos, 1, 'F - test_initial_state/screen_top:pos')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function test_click_to_create_drawing()
|
||||||
|
io.write('\ntest_click_to_create_drawing')
|
||||||
|
App.screen.init{width=120, height=60}
|
||||||
|
Editor_state = edit.initialize_test_state()
|
||||||
|
Editor_state.lines = load_array{}
|
||||||
|
Text.redraw_all(Editor_state)
|
||||||
|
edit.draw(Editor_state)
|
||||||
|
edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
|
||||||
|
-- cursor skips drawing to always remain on text
|
||||||
|
check_eq(#Editor_state.lines, 2, 'F - test_click_to_create_drawing/#lines')
|
||||||
|
check_eq(Editor_state.cursor1.line, 2, 'F - test_click_to_create_drawing/cursor')
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_backspace_to_delete_drawing()
|
||||||
|
io.write('\ntest_backspace_to_delete_drawing')
|
||||||
|
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||||
|
App.screen.init{width=120, height=60}
|
||||||
|
Editor_state = edit.initialize_test_state()
|
||||||
|
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||||
|
Text.redraw_all(Editor_state)
|
||||||
|
-- cursor is on text as always (outside tests this will get initialized correctly)
|
||||||
|
Editor_state.cursor1.line = 2
|
||||||
|
-- backspacing deletes the drawing
|
||||||
|
edit.run_after_keychord(Editor_state, 'backspace')
|
||||||
|
check_eq(#Editor_state.lines, 1, 'F - test_backspace_to_delete_drawing/#lines')
|
||||||
|
check_eq(Editor_state.cursor1.line, 1, 'F - test_backspace_to_delete_drawing/cursor')
|
||||||
|
end
|
||||||
|
|
||||||
function test_backspace_from_start_of_final_line()
|
function test_backspace_from_start_of_final_line()
|
||||||
io.write('\ntest_backspace_from_start_of_final_line')
|
io.write('\ntest_backspace_from_start_of_final_line')
|
||||||
-- display final line of text with cursor at start of it
|
-- display final line of text with cursor at start of it
|
||||||
|
@ -695,6 +723,36 @@ function test_pagedown()
|
||||||
App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
|
App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function test_pagedown_skips_drawings()
|
||||||
|
io.write('\ntest_pagedown_skips_drawings')
|
||||||
|
-- some lines of text with a drawing intermixed
|
||||||
|
local drawing_width = 50
|
||||||
|
App.screen.init{width=Editor_state.left+drawing_width, height=80}
|
||||||
|
Editor_state = edit.initialize_test_state()
|
||||||
|
Editor_state.lines = load_array{'abc', -- height 15
|
||||||
|
'```lines', '```', -- height 25
|
||||||
|
'def', -- height 15
|
||||||
|
'ghi'} -- height 15
|
||||||
|
Text.redraw_all(Editor_state)
|
||||||
|
check_eq(Editor_state.lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')
|
||||||
|
Editor_state.cursor1 = {line=1, pos=1}
|
||||||
|
Editor_state.screen_top1 = {line=1, pos=1}
|
||||||
|
Editor_state.screen_bottom1 = {}
|
||||||
|
local drawing_height = Drawing_padding_height + drawing_width/2 -- default
|
||||||
|
-- initially the screen displays the first line and the drawing
|
||||||
|
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
|
||||||
|
edit.draw(Editor_state)
|
||||||
|
local y = Editor_state.top
|
||||||
|
App.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')
|
||||||
|
-- after pagedown the screen draws the drawing up top
|
||||||
|
-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
|
||||||
|
edit.run_after_keychord(Editor_state, 'pagedown')
|
||||||
|
check_eq(Editor_state.screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')
|
||||||
|
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')
|
||||||
|
y = Editor_state.top + drawing_height
|
||||||
|
App.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')
|
||||||
|
end
|
||||||
|
|
||||||
function test_pagedown_can_start_from_middle_of_long_wrapping_line()
|
function test_pagedown_can_start_from_middle_of_long_wrapping_line()
|
||||||
io.write('\ntest_pagedown_can_start_from_middle_of_long_wrapping_line')
|
io.write('\ntest_pagedown_can_start_from_middle_of_long_wrapping_line')
|
||||||
-- draw a few lines starting from a very long wrapping line
|
-- draw a few lines starting from a very long wrapping line
|
||||||
|
@ -1527,7 +1585,7 @@ function test_search()
|
||||||
io.write('\ntest_search')
|
io.write('\ntest_search')
|
||||||
App.screen.init{width=120, height=60}
|
App.screen.init{width=120, height=60}
|
||||||
Editor_state = edit.initialize_test_state()
|
Editor_state = edit.initialize_test_state()
|
||||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'deg'}
|
Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', 'deg'}
|
||||||
Text.redraw_all(Editor_state)
|
Text.redraw_all(Editor_state)
|
||||||
Editor_state.cursor1 = {line=1, pos=1}
|
Editor_state.cursor1 = {line=1, pos=1}
|
||||||
Editor_state.screen_top1 = {line=1, pos=1}
|
Editor_state.screen_top1 = {line=1, pos=1}
|
||||||
|
|
|
@ -50,6 +50,8 @@ function snapshot(State, s,e)
|
||||||
screen_top=deepcopy(State.screen_top1),
|
screen_top=deepcopy(State.screen_top1),
|
||||||
selection=deepcopy(State.selection1),
|
selection=deepcopy(State.selection1),
|
||||||
cursor=deepcopy(State.cursor1),
|
cursor=deepcopy(State.cursor1),
|
||||||
|
current_drawing_mode=Drawing_mode,
|
||||||
|
previous_drawing_mode=State.previous_drawing_mode,
|
||||||
lines={},
|
lines={},
|
||||||
start_line=s,
|
start_line=s,
|
||||||
end_line=e,
|
end_line=e,
|
||||||
|
@ -58,7 +60,19 @@ function snapshot(State, s,e)
|
||||||
-- deep copy lines without cached stuff like text fragments
|
-- deep copy lines without cached stuff like text fragments
|
||||||
for i=s,e do
|
for i=s,e do
|
||||||
local line = State.lines[i]
|
local line = State.lines[i]
|
||||||
table.insert(event.lines, {data=line.data, dataB=line.dataB})
|
if line.mode == 'text' then
|
||||||
|
table.insert(event.lines, {mode='text', data=line.data, dataB=line.dataB})
|
||||||
|
elseif line.mode == 'drawing' then
|
||||||
|
local points=deepcopy(line.points)
|
||||||
|
--? print('copying', line.points, 'with', #line.points, 'points into', points)
|
||||||
|
local shapes=deepcopy(line.shapes)
|
||||||
|
--? print('copying', line.shapes, 'with', #line.shapes, 'shapes into', shapes)
|
||||||
|
table.insert(event.lines, {mode='drawing', h=line.h, points=points, shapes=shapes, pending={}})
|
||||||
|
--? table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
|
||||||
|
else
|
||||||
|
print(line.mode)
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return event
|
return event
|
||||||
end
|
end
|
||||||
|
|
39
text.lua
39
text.lua
|
@ -115,7 +115,7 @@ function Text.compute_fragments(State, line_index)
|
||||||
for frag in line.data:gmatch('%S*%s*') do
|
for frag in line.data:gmatch('%S*%s*') do
|
||||||
local frag_text = App.newText(love.graphics.getFont(), frag)
|
local frag_text = App.newText(love.graphics.getFont(), frag)
|
||||||
local frag_width = App.width(frag_text)
|
local frag_width = App.width(frag_text)
|
||||||
--? print('x: '..tostring(x)..'; '..tostring(State.right-x)..'px to go')
|
--? print('x: '..tostring(x)..'; frag_width: '..tostring(frag_width)..'; '..tostring(State.right-x)..'px to go')
|
||||||
while x + frag_width > State.right do
|
while x + frag_width > State.right do
|
||||||
--? print(('checking whether to split fragment ^%s$ of width %d when rendering from %d'):format(frag, frag_width, x))
|
--? print(('checking whether to split fragment ^%s$ of width %d when rendering from %d'):format(frag, frag_width, x))
|
||||||
if (x-State.left) < 0.8 * (State.right-State.left) then
|
if (x-State.left) < 0.8 * (State.right-State.left) then
|
||||||
|
@ -365,8 +365,7 @@ function Text.insert_return(State)
|
||||||
table.insert(State.line_cache, State.cursor1.line+1, {})
|
table.insert(State.line_cache, State.cursor1.line+1, {})
|
||||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
|
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
|
||||||
Text.clear_screen_line_cache(State, State.cursor1.line)
|
Text.clear_screen_line_cache(State, State.cursor1.line)
|
||||||
State.cursor1.line = State.cursor1.line+1
|
State.cursor1 = {line=State.cursor1.line+1, pos=1}
|
||||||
State.cursor1.pos = 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Text.pageup(State)
|
function Text.pageup(State)
|
||||||
|
@ -386,8 +385,7 @@ function Text.pageup(State)
|
||||||
top2 = Text.previous_screen_line(State, top2)
|
top2 = Text.previous_screen_line(State, top2)
|
||||||
end
|
end
|
||||||
State.screen_top1 = Text.to1(State, top2)
|
State.screen_top1 = Text.to1(State, top2)
|
||||||
State.cursor1.line = State.screen_top1.line
|
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
|
||||||
State.cursor1.pos = State.screen_top1.pos
|
|
||||||
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
||||||
--? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
|
--? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
|
||||||
--? print('pageup end')
|
--? print('pageup end')
|
||||||
|
@ -406,12 +404,10 @@ function Text.pagedown(State)
|
||||||
if Text.lt1(State.screen_top1, new_top1) then
|
if Text.lt1(State.screen_top1, new_top1) then
|
||||||
State.screen_top1 = new_top1
|
State.screen_top1 = new_top1
|
||||||
else
|
else
|
||||||
State.screen_top1.line = State.screen_bottom1.line
|
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
|
||||||
State.screen_top1.pos = State.screen_bottom1.pos
|
|
||||||
end
|
end
|
||||||
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
|
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
|
||||||
State.cursor1.line = State.screen_top1.line
|
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
|
||||||
State.cursor1.pos = State.screen_top1.pos
|
|
||||||
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
||||||
--? print('top now', State.screen_top1.line)
|
--? print('top now', State.screen_top1.line)
|
||||||
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||||
|
@ -430,7 +426,7 @@ function Text.up(State)
|
||||||
new_cursor_line = new_cursor_line-1
|
new_cursor_line = new_cursor_line-1
|
||||||
if State.lines[new_cursor_line].mode == 'text' then
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
--? print('found previous text line')
|
--? print('found previous text line')
|
||||||
State.cursor1.line = new_cursor_line
|
State.cursor1 = {line=State.cursor1.line-1, pos=nil}
|
||||||
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||||
-- previous text line found, pick its final screen line
|
-- previous text line found, pick its final screen line
|
||||||
--? print('has multiple screen lines')
|
--? print('has multiple screen lines')
|
||||||
|
@ -469,8 +465,10 @@ function Text.down(State)
|
||||||
while new_cursor_line < #State.lines do
|
while new_cursor_line < #State.lines do
|
||||||
new_cursor_line = new_cursor_line+1
|
new_cursor_line = new_cursor_line+1
|
||||||
if State.lines[new_cursor_line].mode == 'text' then
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
State.cursor1.line = new_cursor_line
|
State.cursor1 = {
|
||||||
State.cursor1.pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line].data, State.cursor_x, State.left)
|
line = new_cursor_line,
|
||||||
|
pos = Text.nearest_cursor_pos(State.lines[new_cursor_line].data, State.cursor_x, State.left),
|
||||||
|
}
|
||||||
--? print(State.cursor1.pos)
|
--? print(State.cursor1.pos)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
@ -486,6 +484,7 @@ function Text.down(State)
|
||||||
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
|
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
|
||||||
--? print('cursor is NOT at final screen line of its line')
|
--? print('cursor is NOT at final screen line of its line')
|
||||||
local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
|
local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
|
||||||
|
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||||
local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index+1]
|
local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index+1]
|
||||||
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
|
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
|
||||||
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
|
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
|
||||||
|
@ -582,8 +581,10 @@ function Text.left(State)
|
||||||
while new_cursor_line > 1 do
|
while new_cursor_line > 1 do
|
||||||
new_cursor_line = new_cursor_line-1
|
new_cursor_line = new_cursor_line-1
|
||||||
if State.lines[new_cursor_line].mode == 'text' then
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
State.cursor1.line = new_cursor_line
|
State.cursor1 = {
|
||||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
|
line = new_cursor_line,
|
||||||
|
pos = utf8.len(State.lines[new_cursor_line].data) + 1,
|
||||||
|
}
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -611,8 +612,7 @@ function Text.right_without_scroll(State)
|
||||||
while new_cursor_line <= #State.lines-1 do
|
while new_cursor_line <= #State.lines-1 do
|
||||||
new_cursor_line = new_cursor_line+1
|
new_cursor_line = new_cursor_line+1
|
||||||
if State.lines[new_cursor_line].mode == 'text' then
|
if State.lines[new_cursor_line].mode == 'text' then
|
||||||
State.cursor1.line = new_cursor_line
|
State.cursor1 = {line=new_cursor_line, pos=1}
|
||||||
State.cursor1.pos = 1
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -663,8 +663,12 @@ end
|
||||||
|
|
||||||
-- should never modify State.cursor1
|
-- should never modify State.cursor1
|
||||||
function Text.snap_cursor_to_bottom_of_screen(State)
|
function Text.snap_cursor_to_bottom_of_screen(State)
|
||||||
|
--? print('to2:', State.cursor1.line, State.cursor1.pos)
|
||||||
local top2 = Text.to2(State, State.cursor1)
|
local top2 = Text.to2(State, State.cursor1)
|
||||||
|
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
|
||||||
|
-- slide to start of screen line
|
||||||
top2.screen_pos = 1 -- start of screen line
|
top2.screen_pos = 1 -- start of screen line
|
||||||
|
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
||||||
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
|
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
|
||||||
local y = App.screen.height - State.line_height
|
local y = App.screen.height - State.line_height
|
||||||
-- duplicate some logic from love.draw
|
-- duplicate some logic from love.draw
|
||||||
|
@ -694,6 +698,7 @@ function Text.snap_cursor_to_bottom_of_screen(State)
|
||||||
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
|
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
|
||||||
State.screen_top1 = Text.to1(State, top2)
|
State.screen_top1 = Text.to1(State, top2)
|
||||||
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
|
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
|
||||||
|
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
||||||
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -853,7 +858,7 @@ function Text.to2(State, loc1)
|
||||||
if State.lines[loc1.line].mode == 'drawing' then
|
if State.lines[loc1.line].mode == 'drawing' then
|
||||||
return {line=loc1.line, screen_line=1, screen_pos=1}
|
return {line=loc1.line, screen_line=1, screen_pos=1}
|
||||||
end
|
end
|
||||||
local result = {line=loc1.line, screen_line=1}
|
local result = {line=loc1.line}
|
||||||
local line_cache = State.line_cache[loc1.line]
|
local line_cache = State.line_cache[loc1.line]
|
||||||
Text.populate_screen_line_starting_pos(State, loc1.line)
|
Text.populate_screen_line_starting_pos(State, loc1.line)
|
||||||
for i=#line_cache.screen_line_starting_pos,1,-1 do
|
for i=#line_cache.screen_line_starting_pos,1,-1 do
|
||||||
|
|
Loading…
Reference in New Issue