2022-05-18 04:29:08 +01:00
-- primitives for editing text
Text = { }
2022-05-18 06:36:10 +01:00
local utf8 = require ' utf8 '
2022-05-21 06:18:39 +01:00
local Debug_new_render = false
2022-05-21 02:16:13 +01:00
2022-05-22 04:52:44 +01:00
-- return values:
-- y coordinate drawn until in px
-- position of start of final screen line drawn
2022-05-20 14:09:37 +01:00
function Text . draw ( line , line_width , line_index )
2022-05-23 19:02:37 +01:00
--? print('text.draw')
2022-05-18 04:29:08 +01:00
love.graphics . setColor ( 0 , 0 , 0 )
2022-05-20 01:29:14 +01:00
-- wrap long lines
local x = 25
local y = line.y
local pos = 1
2022-05-22 04:52:44 +01:00
local screen_line_starting_pos = 1
2022-05-20 01:29:14 +01:00
if line.fragments == nil then
Text.compute_fragments ( line , line_width )
2022-05-18 04:29:08 +01:00
end
2022-05-22 00:41:34 +01:00
line.screen_line_starting_pos = nil -- TODO: avoid recomputing on every repaint
2022-05-21 06:16:51 +01:00
if Debug_new_render then print ( ' -- ' ) end
2022-05-20 01:29:14 +01:00
for _ , f in ipairs ( line.fragments ) do
local frag , frag_text = f.data , f.text
-- render fragment
2022-05-23 07:17:06 +01:00
local frag_width = math.floor ( App.width ( frag_text ) * Zoom )
2022-05-23 23:34:05 +01:00
local s = tostring
print ( ' ( ' .. s ( x ) .. ' , ' .. s ( y ) .. ' ) ' .. frag .. ' ( ' .. s ( frag_width ) .. ' vs ' .. s ( line_width ) .. ' ) ' .. s ( line_index ) .. ' vs ' .. s ( Screen_top1.line ) .. ' ; ' .. s ( pos ) .. ' vs ' .. s ( Screen_top1.pos ) )
2022-05-20 01:29:14 +01:00
if x + frag_width > line_width then
assert ( x > 25 ) -- no overfull lines
2022-05-23 23:34:05 +01:00
-- don't update y above screen top
2022-05-22 00:24:07 +01:00
if line_index > Screen_top1.line or pos > Screen_top1.pos then
2022-05-20 14:58:17 +01:00
y = y + math.floor ( 15 * Zoom )
2022-05-23 16:13:58 +01:00
if New_foo then print ( ' text: new screen line ' , y , App.screen . height , screen_line_starting_pos ) end
2022-05-22 04:52:44 +01:00
screen_line_starting_pos = pos
2022-05-21 06:16:51 +01:00
if Debug_new_render then print ( ' y ' , y ) end
2022-05-20 14:58:17 +01:00
end
2022-05-20 01:29:14 +01:00
x = 25
2022-05-20 06:07:12 +01:00
if line.screen_line_starting_pos == nil then
line.screen_line_starting_pos = { 1 , pos }
else
table.insert ( line.screen_line_starting_pos , pos )
end
2022-05-23 23:34:05 +01:00
-- if we updated y, check if we're done with the screen
2022-05-22 06:08:13 +01:00
if line_index > Screen_top1.line or pos > Screen_top1.pos then
2022-05-23 23:34:05 +01:00
print ( ' a ' )
if y + math.floor ( 15 * Zoom ) > App.screen . height then
2022-05-23 23:22:16 +01:00
print ( ' b ' , y , App.screen . height )
2022-05-22 06:08:13 +01:00
return y , screen_line_starting_pos
end
end
2022-05-20 01:29:14 +01:00
end
2022-05-22 00:24:07 +01:00
if Debug_new_render then print ( ' checking to draw ' , pos , Screen_top1.pos ) end
2022-05-23 23:34:05 +01:00
-- don't draw text above screen top
2022-05-22 00:24:07 +01:00
if line_index > Screen_top1.line or pos >= Screen_top1.pos then
2022-05-21 06:16:51 +01:00
if Debug_new_render then print ( ' drawing ' .. frag ) end
2022-05-23 07:17:06 +01:00
App.screen . draw ( frag_text , x , y , 0 , Zoom )
2022-05-20 14:58:17 +01:00
end
2022-05-20 01:29:14 +01:00
-- render cursor if necessary
local frag_len = utf8.len ( frag )
2022-05-22 00:24:07 +01:00
if line_index == Cursor1.line then
if pos <= Cursor1.pos and pos + frag_len > Cursor1.pos then
Text.draw_cursor ( x + Text.cursor_x2 ( frag , Cursor1.pos - pos + 1 ) , y )
2022-05-20 01:29:14 +01:00
end
end
x = x + frag_width
pos = pos + frag_len
end
2022-05-22 00:24:07 +01:00
if line_index == Cursor1.line and Cursor1.pos == pos then
2022-05-20 13:40:42 +01:00
Text.draw_cursor ( x , y )
2022-05-20 06:35:26 +01:00
end
2022-05-21 06:16:51 +01:00
Debug_new_render = false
2022-05-22 04:52:44 +01:00
return y , screen_line_starting_pos
2022-05-18 04:29:08 +01:00
end
2022-05-20 01:29:14 +01:00
-- manual tests:
-- draw with small line_width of 100
-- short words break on spaces
-- long words break when they must
2022-05-18 04:29:08 +01:00
2022-05-20 13:40:42 +01:00
function Text . draw_cursor ( x , y )
love.graphics . setColor ( 1 , 0 , 0 )
love.graphics . circle ( ' fill ' , x , y + math.floor ( 15 * Zoom ) , 2 )
love.graphics . setColor ( 0 , 0 , 0 )
2022-05-20 13:41:25 +01:00
Cursor_x = x
Cursor_y = y + math.floor ( 15 * Zoom )
2022-05-20 13:40:42 +01:00
end
2022-05-23 07:17:06 +01:00
function test_draw_text ( )
2022-05-23 16:13:58 +01:00
print ( ' test_draw_text ' )
2022-05-23 07:17:06 +01:00
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 07:17:06 +01:00
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_draw_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_draw_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_draw_text/screen:3 ' )
end
2022-05-23 16:13:58 +01:00
function test_pagedown ( )
print ( ' test_pagedown ' )
App.screen . init { width = 120 , height = 45 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 16:13:58 +01:00
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
-- initially the first two lines are displayed
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_pagedown/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_pagedown/baseline/screen:2 ' )
-- after pagedown the bottom line becomes the top
App.run_after_keychord ( ' pagedown ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 2 , ' F - test_pagedown/screen_top ' )
check_eq ( Cursor1.line , 2 , ' F - test_pagedown/cursor ' )
2022-05-23 16:13:58 +01:00
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_pagedown/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_pagedown/screen:2 ' )
end
2022-05-23 18:56:39 +01:00
function test_pagedown_skips_drawings ( )
print ( ' test_pagedown_skips_drawings ' )
2022-05-23 16:13:58 +01:00
-- some lines of text with a drawing intermixed
2022-05-23 16:52:13 +01:00
App.screen . init { width = 50 , height = 80 }
Lines = load_array { ' abc ' , -- height 15
' ```lines ' , ' ``` ' , -- height 25
' def ' , -- height 15
' ghi ' } -- height 15
2022-05-23 18:56:39 +01:00
check_eq ( Lines [ 2 ] . mode , ' drawing ' , ' F - test_pagedown_skips_drawings/baseline/lines ' )
2022-05-23 16:13:58 +01:00
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
2022-05-23 16:52:13 +01:00
local text height = 15
local drawing_height = 20 + App.screen . 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
2022-05-23 16:13:58 +01:00
App.draw ( )
local y = screen_top_margin
2022-05-23 18:56:39 +01:00
App.screen . check ( y , ' abc ' , ' F - test_pagedown_skips_drawings/baseline/screen:1 ' )
2022-05-23 23:22:16 +01:00
-- after pagedown the screen draws the drawing up top
2022-05-23 16:52:13 +01:00
-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
2022-05-23 16:13:58 +01:00
App.run_after_keychord ( ' pagedown ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 2 , ' F - test_pagedown_skips_drawings/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_pagedown_skips_drawings/cursor ' )
2022-05-23 16:13:58 +01:00
y = screen_top_margin + drawing_height
2022-05-23 18:56:39 +01:00
App.screen . check ( y , ' def ' , ' F - test_pagedown_skips_drawings/screen:1 ' )
end
function test_down_arrow_moves_cursor ( )
print ( ' test_down_arrow_moves_cursor ' )
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 18:56:39 +01:00
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
-- initially the first three lines are displayed
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_moves_cursor/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_moves_cursor/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_moves_cursor/baseline/screen:3 ' )
2022-05-23 23:22:16 +01:00
-- after hitting the down arrow, the cursor moves down by 1 line
2022-05-23 18:56:39 +01:00
App.run_after_keychord ( ' down ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_moves_cursor/screen_top ' )
check_eq ( Cursor1.line , 2 , ' F - test_down_arrow_moves_cursor/cursor ' )
-- the screen is unchanged
2022-05-23 18:56:39 +01:00
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_moves_cursor/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_moves_cursor/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_moves_cursor/screen:3 ' )
end
function test_down_arrow_scrolls_down_by_one_line ( )
print ( ' test_down_arrow_scrolls_down_by_one_line ' )
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 18:56:39 +01:00
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:3 ' )
-- after hitting the down arrow the screen scrolls down by one line
App.run_after_keychord ( ' down ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 2 , ' F - test_down_arrow_scrolls_down_by_one_line/screen_top ' )
check_eq ( Cursor1.line , 4 , ' F - test_down_arrow_scrolls_down_by_one_line/cursor ' )
2022-05-23 18:56:39 +01:00
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_down_arrow_scrolls_down_by_one_line/screen:3 ' )
2022-05-23 23:22:16 +01:00
end
function test_down_arrow_scrolls_down_by_one_screen_line ( )
print ( ' test_down_arrow_scrolls_down_by_one_screen_line ' )
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:3 ' ) -- line wrapping includes trailing whitespace
-- after hitting the down arrow the screen scrolls down by one line
App.run_after_keychord ( ' down ' )
check_eq ( Screen_top1.line , 2 , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 5 , ' F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen:2 ' )
y = y + line_height
-- HERE
App.screen . check ( y , ' jkl ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen:3 ' )
end
function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word ( )
print ( ' test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word ' )
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghijkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghijk ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:3 ' )
-- after hitting the down arrow the screen scrolls down by one line
App.run_after_keychord ( ' down ' )
check_eq ( Screen_top1.line , 2 , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:line ' )
check_eq ( Cursor1.pos , 6 , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghijk ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' l ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:3 ' )
2022-05-23 19:02:37 +01:00
end
function test_up_arrow_moves_cursor ( )
print ( ' test_up_arrow_moves_cursor ' )
-- display the first 3 lines with the cursor on the bottom line
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = 120
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_moves_cursor/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_moves_cursor/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_moves_cursor/baseline/screen:3 ' )
2022-05-23 23:22:16 +01:00
-- after hitting the up arrow the cursor moves up by 1 line
2022-05-23 19:02:37 +01:00
App.run_after_keychord ( ' up ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_moves_cursor/screen_top ' )
check_eq ( Cursor1.line , 2 , ' F - test_up_arrow_moves_cursor/cursor ' )
-- the screen is unchanged
2022-05-23 19:02:37 +01:00
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_moves_cursor/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_moves_cursor/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_moves_cursor/screen:3 ' )
end
function test_up_arrow_scrolls_up_by_one_line ( )
print ( ' test_up_arrow_scrolls_up_by_one_line ' )
-- display the lines 2/3/4 with the cursor on line 2
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = 120
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3 ' )
-- after hitting the up arrow the screen scrolls up by one line
App.run_after_keychord ( ' up ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_scrolls_up_by_one_line/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_up_arrow_scrolls_up_by_one_line/cursor ' )
2022-05-23 19:02:37 +01:00
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_scrolls_up_by_one_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_by_one_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_line/screen:3 ' )
2022-05-23 23:22:16 +01:00
end
function test_up_arrow_scrolls_up_by_one_screen_line ( )
print ( ' test_up_arrow_scrolls_up_by_one_screen_line ' )
-- display the lines 2/3/4 with the cursor on line 2
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:3 ' )
-- after hitting the up arrow the screen scrolls up by one line
App.run_after_keychord ( ' up ' )
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3 ' )
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_up_arrow_scrolls_up_by_one_screen_line/cursor ' )
2022-05-23 16:13:58 +01:00
end
2022-05-20 06:58:04 +01:00
function Text . compute_fragments ( line , line_width )
2022-05-23 23:22:16 +01:00
--? print('compute_fragments')
2022-05-20 06:58:04 +01:00
line.fragments = { }
local x = 25
-- try to wrap at word boundaries
for frag in line.data : gmatch ( ' %S*%s* ' ) do
2022-05-23 07:17:06 +01:00
local frag_text = App.newText ( love.graphics . getFont ( ) , frag )
local frag_width = math.floor ( App.width ( frag_text ) * Zoom )
2022-05-20 06:58:04 +01:00
--? print('x: '..tostring(x)..'; '..tostring(line_width-x)..'px to go')
--? print('frag: ^'..frag..'$ is '..tostring(frag_width)..'px wide')
if x + frag_width > line_width then
2022-05-23 23:22:16 +01:00
assert ( 25 + frag_width > line_width ) -- avoid infinite loop when window is too narrow
2022-05-20 06:58:04 +01:00
while x + frag_width > line_width do
if x < 0.8 * line_width then
2022-05-23 23:22:16 +01:00
print ( frag , x , frag_width , line_width )
2022-05-20 06:58:04 +01:00
-- long word; chop it at some letter
-- We're not going to reimplement TeX here.
2022-05-23 23:22:16 +01:00
local b = Text.nearest_pos_less_than ( frag , line_width - x )
2022-05-20 06:58:04 +01:00
--? print('space for '..tostring(b)..' graphemes')
local frag1 = string.sub ( frag , 1 , b )
2022-05-23 07:17:06 +01:00
local frag1_text = App.newText ( love.graphics . getFont ( ) , frag1 )
2022-05-23 23:22:16 +01:00
local frag1_width = math.floor ( App.width ( frag1_text ) * Zoom )
print ( frag , x , frag1_width , line_width )
assert ( x + frag1_width <= line_width )
2022-05-20 06:58:04 +01:00
--? print('inserting '..frag1..' of width '..tostring(frag1_width)..'px')
table.insert ( line.fragments , { data = frag1 , text = frag1_text } )
frag = string.sub ( frag , b + 1 )
2022-05-23 07:17:06 +01:00
frag_text = App.newText ( love.graphics . getFont ( ) , frag )
frag_width = math.floor ( App.width ( frag_text ) * Zoom )
2022-05-20 06:58:04 +01:00
end
x = 25 -- new line
end
end
if # frag > 0 then
--? print('inserting '..frag..' of width '..tostring(frag_width)..'px')
table.insert ( line.fragments , { data = frag , text = frag_text } )
end
end
end
2022-05-21 22:03:06 +01:00
function Text . textinput ( t )
2022-05-18 06:36:10 +01:00
if love.mouse . isDown ( ' 1 ' ) then return end
2022-05-19 04:22:57 +01:00
Text.insert_at_cursor ( t )
end
function Text . insert_at_cursor ( t )
2022-05-18 06:36:10 +01:00
local byte_offset
2022-05-22 00:24:07 +01:00
if Cursor1.pos > 1 then
byte_offset = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos - 1 )
2022-05-18 06:36:10 +01:00
else
byte_offset = 0
end
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_offset ) .. t .. string.sub ( Lines [ Cursor1.line ] . data , byte_offset + 1 )
Lines [ Cursor1.line ] . fragments = nil
Cursor1.pos = Cursor1.pos + 1
2022-05-18 06:36:10 +01:00
end
-- Don't handle any keys here that would trigger love.textinput above.
function Text . keychord_pressed ( chord )
2022-05-21 06:18:39 +01:00
--? Debug_new_render = true
2022-05-18 06:36:10 +01:00
if chord == ' return ' then
2022-05-22 00:24:07 +01:00
local byte_offset = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos )
table.insert ( Lines , Cursor1.line + 1 , { mode = ' text ' , data = string.sub ( Lines [ Cursor1.line ] . data , byte_offset ) } )
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_offset - 1 )
Lines [ Cursor1.line ] . fragments = nil
Cursor1.line = Cursor1.line + 1
Cursor1.pos = 1
2022-05-18 06:36:10 +01:00
save_to_disk ( Lines , Filename )
2022-05-19 04:22:57 +01:00
elseif chord == ' tab ' then
Text.insert_at_cursor ( ' \t ' )
save_to_disk ( Lines , Filename )
2022-05-18 06:36:10 +01:00
elseif chord == ' left ' then
2022-05-22 00:24:07 +01:00
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
if Cursor1.pos > 1 then
Cursor1.pos = Cursor1.pos - 1
2022-05-18 06:36:10 +01:00
else
2022-05-22 00:24:07 +01:00
local new_cursor_line = Cursor1.line
2022-05-18 06:36:10 +01:00
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line - 1
if Lines [ new_cursor_line ] . mode == ' text ' then
2022-05-22 00:24:07 +01:00
Cursor1.line = new_cursor_line
Cursor1.pos = utf8.len ( Lines [ Cursor1.line ] . data ) + 1
2022-05-18 06:36:10 +01:00
break
end
end
2022-05-22 00:24:07 +01:00
if Cursor1.line < Screen_top1.line then
Screen_top1.line = Cursor1.line
2022-05-19 01:22:04 +01:00
end
2022-05-18 06:36:10 +01:00
end
elseif chord == ' right ' then
2022-05-22 00:24:07 +01:00
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
if Cursor1.pos <= utf8.len ( Lines [ Cursor1.line ] . data ) then
Cursor1.pos = Cursor1.pos + 1
2022-05-18 06:36:10 +01:00
else
2022-05-22 00:24:07 +01:00
local new_cursor_line = Cursor1.line
2022-05-18 06:36:10 +01:00
while new_cursor_line <= # Lines - 1 do
new_cursor_line = new_cursor_line + 1
if Lines [ new_cursor_line ] . mode == ' text ' then
2022-05-22 00:24:07 +01:00
Cursor1.line = new_cursor_line
Cursor1.pos = 1
2022-05-18 06:36:10 +01:00
break
end
end
2022-05-22 00:24:07 +01:00
if Cursor1.line > Screen_bottom1.line then
Screen_top1.line = Cursor1.line
2022-05-19 01:22:04 +01:00
end
2022-05-18 06:36:10 +01:00
end
elseif chord == ' home ' then
2022-05-22 00:24:07 +01:00
Cursor1.pos = 1
2022-05-18 06:36:10 +01:00
elseif chord == ' end ' then
2022-05-22 00:24:07 +01:00
Cursor1.pos = utf8.len ( Lines [ Cursor1.line ] . data ) + 1
2022-05-18 06:36:10 +01:00
elseif chord == ' backspace ' then
2022-05-22 00:24:07 +01:00
if Cursor1.pos > 1 then
local byte_start = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos - 1 )
local byte_end = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos )
2022-05-18 06:36:10 +01:00
if byte_start then
if byte_end then
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 ) .. string.sub ( Lines [ Cursor1.line ] . data , byte_end )
2022-05-18 06:36:10 +01:00
else
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 )
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . fragments = nil
Cursor1.pos = Cursor1.pos - 1
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
elseif Cursor1.line > 1 then
if Lines [ Cursor1.line - 1 ] . mode == ' drawing ' then
table.remove ( Lines , Cursor1.line - 1 )
2022-05-18 06:36:10 +01:00
else
-- join lines
2022-05-22 00:24:07 +01:00
Cursor1.pos = utf8.len ( Lines [ Cursor1.line - 1 ] . data ) + 1
Lines [ Cursor1.line - 1 ] . data = Lines [ Cursor1.line - 1 ] . data .. Lines [ Cursor1.line ] . data
Lines [ Cursor1.line - 1 ] . fragments = nil
table.remove ( Lines , Cursor1.line )
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
Cursor1.line = Cursor1.line - 1
2022-05-18 06:36:10 +01:00
end
save_to_disk ( Lines , Filename )
elseif chord == ' delete ' then
2022-05-22 00:24:07 +01:00
if Cursor1.pos <= utf8.len ( Lines [ Cursor1.line ] . data ) then
local byte_start = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos )
local byte_end = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos + 1 )
2022-05-18 06:36:10 +01:00
if byte_start then
if byte_end then
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 ) .. string.sub ( Lines [ Cursor1.line ] . data , byte_end )
2022-05-18 06:36:10 +01:00
else
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 )
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . fragments = nil
-- no change to Cursor1.pos
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
elseif Cursor1.line < # Lines then
if Lines [ Cursor1.line + 1 ] . mode == ' drawing ' then
table.remove ( Lines , Cursor1.line + 1 )
2022-05-18 06:36:10 +01:00
else
-- join lines
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = Lines [ Cursor1.line ] . data .. Lines [ Cursor1.line + 1 ] . data
Lines [ Cursor1.line ] . fragments = nil
table.remove ( Lines , Cursor1.line + 1 )
2022-05-18 06:36:10 +01:00
end
end
save_to_disk ( Lines , Filename )
elseif chord == ' up ' then
2022-05-22 00:24:07 +01:00
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
--? print('up', Cursor1.pos, Screen_top1.pos)
2022-05-21 00:41:21 +01:00
local screen_line_index , screen_line_starting_pos = Text.pos_at_start_of_cursor_screen_line ( )
if screen_line_starting_pos == 1 then
2022-05-21 06:46:32 +01:00
--? print('cursor is at first screen line of its line')
2022-05-21 04:32:00 +01:00
-- line is done; skip to previous text line
2022-05-22 00:24:07 +01:00
local new_cursor_line = Cursor1.line
2022-05-20 16:55:45 +01:00
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line - 1
if Lines [ new_cursor_line ] . mode == ' text ' then
2022-05-22 00:24:07 +01:00
Cursor1.line = new_cursor_line
if Lines [ Cursor1.line ] . screen_line_starting_pos == nil then
Cursor1.pos = Text.nearest_cursor_pos ( Lines [ Cursor1.line ] . data , Cursor_x )
2022-05-20 23:19:43 +01:00
break
end
2022-05-21 04:32:00 +01:00
-- previous text line found, pick its final screen line
2022-05-22 00:24:07 +01:00
local screen_line_starting_pos = Lines [ Cursor1.line ] . screen_line_starting_pos
2022-05-20 23:19:43 +01:00
screen_line_starting_pos = screen_line_starting_pos [ # screen_line_starting_pos ]
2022-05-21 06:46:32 +01:00
--? print('previous screen line starts at pos '..tostring(screen_line_starting_pos)..' of its line')
2022-05-22 00:24:07 +01:00
if Screen_top1.line == Cursor1.line and Screen_top1.pos == screen_line_starting_pos then
Screen_top1.pos = screen_line_starting_pos
--? print('pos of top of screen is also '..tostring(Screen_top1.pos)..' of the same line')
2022-05-21 00:41:21 +01:00
end
2022-05-22 00:24:07 +01:00
local s = string.sub ( Lines [ Cursor1.line ] . data , screen_line_starting_pos )
Cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos ( s , Cursor_x ) - 1
2022-05-20 16:55:45 +01:00
break
end
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
if Cursor1.line < Screen_top1.line then
Screen_top1.line = Cursor1.line
2022-05-20 16:55:45 +01:00
end
else
2022-05-21 04:32:00 +01:00
-- move up one screen line in current line
2022-05-21 06:46:32 +01:00
--? print('cursor is NOT at first screen line of its line')
2022-05-20 16:55:45 +01:00
assert ( screen_line_index > 1 )
2022-05-22 00:24:07 +01:00
new_screen_line_starting_pos = Lines [ Cursor1.line ] . screen_line_starting_pos [ screen_line_index - 1 ]
2022-05-21 06:46:32 +01:00
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
2022-05-22 00:24:07 +01:00
if Screen_top1.line == Cursor1.line and Screen_top1.pos == screen_line_starting_pos then
Screen_top1.pos = new_screen_line_starting_pos
--? print('also setting pos of top of screen to '..tostring(Screen_top1.pos))
2022-05-21 00:41:21 +01:00
end
2022-05-22 00:24:07 +01:00
local s = string.sub ( Lines [ Cursor1.line ] . data , new_screen_line_starting_pos )
Cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos ( s , Cursor_x ) - 1
--? print('cursor pos is now '..tostring(Cursor1.pos))
2022-05-19 01:22:04 +01:00
end
2022-05-18 06:36:10 +01:00
elseif chord == ' down ' then
2022-05-22 00:24:07 +01:00
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
2022-05-22 06:08:13 +01:00
print ( ' down ' , Cursor1.line , Cursor1.pos , Screen_top1.line , Screen_top1.pos , Screen_bottom1.line , Screen_bottom1.pos )
2022-05-21 04:33:30 +01:00
if Text.cursor_at_final_screen_line ( ) then
-- line is done, skip to next text line
2022-05-22 06:08:13 +01:00
print ( ' cursor at final screen line of its line ' )
--? os.exit(1)
2022-05-22 00:24:07 +01:00
local new_cursor_line = Cursor1.line
2022-05-21 04:33:30 +01:00
while new_cursor_line < # Lines do
new_cursor_line = new_cursor_line + 1
if Lines [ new_cursor_line ] . mode == ' text ' then
2022-05-22 00:24:07 +01:00
Cursor1.line = new_cursor_line
Cursor1.pos = Text.nearest_cursor_pos ( Lines [ Cursor1.line ] . data , Cursor_x )
2022-05-22 06:08:13 +01:00
print ( Cursor1.pos )
2022-05-21 04:33:30 +01:00
break
end
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
if Cursor1.line > Screen_bottom1.line then
2022-05-22 06:08:13 +01:00
print ( ' screen top before: ' , Screen_top1.line , Screen_top1.pos )
2022-05-22 00:24:07 +01:00
Screen_top1.line = Cursor1.line
2022-05-22 06:08:13 +01:00
print ( ' scroll up preserving cursor ' )
2022-05-21 04:33:30 +01:00
Text.scroll_up_while_cursor_on_screen ( )
2022-05-22 06:08:13 +01:00
print ( ' screen top after: ' , Screen_top1.line , Screen_top1.pos )
2022-05-21 04:33:30 +01:00
end
else
-- move down one screen line in current line
2022-05-22 06:08:13 +01:00
print ( ' cursor is NOT at final screen line of its line ' )
2022-05-21 04:33:30 +01:00
local screen_line_index , screen_line_starting_pos = Text.pos_at_start_of_cursor_screen_line ( )
2022-05-22 00:24:07 +01:00
new_screen_line_starting_pos = Lines [ Cursor1.line ] . screen_line_starting_pos [ screen_line_index + 1 ]
2022-05-22 06:08:13 +01:00
print ( ' switching pos of screen line at cursor from ' .. tostring ( screen_line_starting_pos ) .. ' to ' .. tostring ( new_screen_line_starting_pos ) )
2022-05-22 00:24:07 +01:00
local s = string.sub ( Lines [ Cursor1.line ] . data , new_screen_line_starting_pos )
Cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos ( s , Cursor_x ) - 1
2022-05-22 07:02:04 +01:00
print ( ' cursor pos is now ' , Cursor1.line , Cursor1.pos )
2022-05-22 00:24:07 +01:00
Screen_top1.line = Cursor1.line
2022-05-22 06:08:13 +01:00
print ( ' scroll up preserving cursor ' )
2022-05-21 04:33:30 +01:00
Text.scroll_up_while_cursor_on_screen ( )
2022-05-22 06:08:13 +01:00
print ( ' screen top after: ' , Screen_top1.line , Screen_top1.pos )
2022-05-19 01:22:04 +01:00
end
2022-05-22 06:08:13 +01:00
print ( ' => ' , Cursor1.line , Cursor1.pos , Screen_top1.line , Screen_top1.pos , Screen_bottom1.line , Screen_bottom1.pos )
2022-05-18 06:36:10 +01:00
end
end
2022-05-21 00:41:21 +01:00
function Text . pos_at_start_of_cursor_screen_line ( )
2022-05-22 00:24:07 +01:00
if Lines [ Cursor1.line ] . screen_line_starting_pos == nil then
2022-05-21 00:41:21 +01:00
return 1 , 1
end
2022-05-22 00:24:07 +01:00
for i =# Lines [ Cursor1.line ] . screen_line_starting_pos , 1 , - 1 do
local spos = Lines [ Cursor1.line ] . screen_line_starting_pos [ i ]
if spos <= Cursor1.pos then
2022-05-21 00:41:21 +01:00
return i , spos
end
end
assert ( false )
end
2022-05-21 04:33:30 +01:00
function Text . cursor_at_final_screen_line ( )
2022-05-22 00:24:07 +01:00
if Lines [ Cursor1.line ] . screen_line_starting_pos == nil then
2022-05-21 04:33:30 +01:00
return true
end
2022-05-22 05:08:21 +01:00
screen_lines = Lines [ Cursor1.line ] . screen_line_starting_pos
2022-05-22 06:08:13 +01:00
print ( screen_lines [ # screen_lines ] , Cursor1.pos )
2022-05-22 05:08:21 +01:00
return screen_lines [ # screen_lines ] <= Cursor1.pos
2022-05-21 04:33:30 +01:00
end
2022-05-19 02:19:27 +01:00
function Text . move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary ( )
2022-05-23 16:52:13 +01:00
local y = 15 -- top margin
2022-05-22 00:24:07 +01:00
while Cursor1.line <= # Lines do
if Lines [ Cursor1.line ] . mode == ' text ' then
2022-05-19 02:19:27 +01:00
break
end
2022-05-23 16:52:13 +01:00
print ( ' cursor skips ' , Cursor1.line )
y = y + 20 + Drawing.pixels ( Lines [ Cursor1.line ] . h )
2022-05-22 00:24:07 +01:00
Cursor1.line = Cursor1.line + 1
2022-05-19 02:19:27 +01:00
end
-- hack: insert a text line at bottom of file if necessary
2022-05-22 00:24:07 +01:00
if Cursor1.line > # Lines then
assert ( Cursor1.line == # Lines + 1 )
2022-05-19 02:19:27 +01:00
table.insert ( Lines , { mode = ' text ' , data = ' ' } )
end
2022-05-23 16:52:13 +01:00
--? print(y, App.screen.height, App.screen.height-math.floor(15*Zoom))
if y > App.screen . height - math.floor ( 15 * Zoom ) then
--? if Cursor1.line > Screen_bottom1.line then
2022-05-23 16:13:58 +01:00
print ( ' scroll up ' )
2022-05-22 00:24:07 +01:00
Screen_top1.line = Cursor1.line
2022-05-19 02:19:27 +01:00
Text.scroll_up_while_cursor_on_screen ( )
end
end
function Text . scroll_up_while_cursor_on_screen ( )
2022-05-22 07:02:04 +01:00
local top2 = Text.to2 ( Cursor1 )
--? print('cursor pos '..tostring(Cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
2022-05-23 16:13:58 +01:00
local y = App.screen . height - math.floor ( 15 * Zoom )
2022-05-21 06:43:09 +01:00
-- duplicate some logic from love.draw
2022-05-19 02:19:27 +01:00
while true do
2022-05-22 07:02:04 +01:00
--? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos)
if top2.line == 1 and top2.screen_line == 1 then break end
if top2.screen_line > 1 or Lines [ top2.line - 1 ] . mode == ' text ' then
local h = math.floor ( 15 * Zoom )
if y - h < 15 then -- top margin = 15
break
end
y = y - h
2022-05-21 06:43:09 +01:00
else
2022-05-22 07:02:04 +01:00
assert ( top2.line > 1 )
assert ( Lines [ top2.line - 1 ] . mode == ' drawing ' )
-- We currently can't draw partial drawings, so either skip it entirely
-- or not at all.
2022-05-22 17:15:56 +01:00
local h = 20 + Drawing.pixels ( Lines [ top2.line - 1 ] . h )
2022-05-22 07:02:04 +01:00
if y - h < 15 then
break
end
--? print('skipping drawing of height', h)
y = y - h
2022-05-19 02:19:27 +01:00
end
2022-05-22 07:02:04 +01:00
top2 = Text.previous_screen_line ( top2 )
2022-05-19 02:19:27 +01:00
end
2022-05-22 07:02:04 +01:00
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
Screen_top1 = Text.to1 ( top2 )
--? print('top1 finally:', Screen_top1.line, Screen_top1.pos)
2022-05-19 02:19:27 +01:00
end
2022-05-18 06:53:09 +01:00
function Text . in_line ( line , x , y )
2022-05-19 01:22:04 +01:00
if line.y == nil then return false end -- outside current page
2022-05-20 06:07:12 +01:00
if x < 16 then return false end
if y < line.y then return false end
if line.screen_line_starting_pos == nil then return y < line.y + math.floor ( 15 * Zoom ) end
return y < line.y + # line.screen_line_starting_pos * math.floor ( 15 * Zoom )
2022-05-18 06:53:09 +01:00
end
2022-05-20 06:07:12 +01:00
function Text . move_cursor ( line_index , line , mx , my )
2022-05-22 00:24:07 +01:00
Cursor1.line = line_index
2022-05-20 06:07:12 +01:00
if line.screen_line_starting_pos == nil then
2022-05-22 00:24:07 +01:00
Cursor1.pos = Text.nearest_cursor_pos ( line.data , mx )
2022-05-20 06:07:12 +01:00
return
end
assert ( line.fragments )
assert ( my >= line.y )
-- duplicate some logic from Text.draw
local y = line.y
2022-05-20 06:26:15 +01:00
for screen_line_index , screen_line_starting_pos in ipairs ( line.screen_line_starting_pos ) do
2022-05-20 06:07:12 +01:00
local nexty = y + math.floor ( 15 * Zoom )
if my < nexty then
2022-05-20 06:26:15 +01:00
-- On all wrapped screen lines but the final one, clicks past end of
-- line position cursor on final character of screen line.
-- (The final screen line positions past end of screen line as always.)
if mx > Line_width and screen_line_index < # line.screen_line_starting_pos then
2022-05-22 00:24:07 +01:00
Cursor1.pos = line.screen_line_starting_pos [ screen_line_index + 1 ]
2022-05-20 06:26:15 +01:00
return
end
2022-05-20 06:07:12 +01:00
local s = string.sub ( line.data , screen_line_starting_pos )
2022-05-22 00:24:07 +01:00
Cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos ( s , mx ) - 1
2022-05-20 06:07:12 +01:00
return
end
y = nexty
end
assert ( false )
2022-05-18 06:53:09 +01:00
end
2022-05-20 06:07:12 +01:00
-- manual test:
-- line: abc
-- def
-- gh
-- fragments: abc, def, gh
-- click inside e
-- line_starting_pos = 1 + 3 = 4
-- nearest_cursor_pos('defgh', mx) = 2
2022-05-22 00:24:07 +01:00
-- Cursor1.pos = 4 + 2 - 1 = 5
2022-05-20 06:07:12 +01:00
-- manual test:
-- click inside h
-- line_starting_pos = 1 + 3 + 3 = 7
-- nearest_cursor_pos('gh', mx) = 2
2022-05-22 00:24:07 +01:00
-- Cursor1.pos = 7 + 2 - 1 = 8
2022-05-18 06:53:09 +01:00
2022-05-23 23:22:16 +01:00
function Text . nearest_cursor_pos ( line , x ) -- x includes left margin
2022-05-18 04:29:08 +01:00
if x == 0 then
return 1
end
2022-05-20 01:46:02 +01:00
local len = utf8.len ( line )
local max_x = Text.cursor_x ( line , len + 1 )
2022-05-18 04:29:08 +01:00
if x > max_x then
2022-05-20 01:46:02 +01:00
return len + 1
2022-05-18 04:29:08 +01:00
end
2022-05-20 01:46:02 +01:00
local left , right = 1 , len + 1
2022-05-20 05:52:38 +01:00
--? print('--')
while true do
2022-05-18 04:29:08 +01:00
local curr = math.floor ( ( left + right ) / 2 )
local currxmin = Text.cursor_x ( line , curr )
local currxmax = Text.cursor_x ( line , curr + 1 )
2022-05-20 05:52:38 +01:00
--? print(x, left, right, curr, currxmin, currxmax)
2022-05-18 04:29:08 +01:00
if currxmin <= x and x < currxmax then
return curr
end
2022-05-20 05:52:38 +01:00
if left >= right - 1 then
return right
end
2022-05-18 04:29:08 +01:00
if currxmin > x then
right = curr
else
left = curr
end
end
2022-05-20 05:52:38 +01:00
assert ( false )
2022-05-18 04:29:08 +01:00
end
2022-05-23 23:22:16 +01:00
function Text . nearest_pos_less_than ( line , x ) -- x DOES NOT include left margin
if x == 0 then
return 1
end
local len = utf8.len ( line )
local max_x = Text.cursor_x2 ( line , len + 1 )
if x > max_x then
return len + 1
end
local left , right = 1 , len + 1
--? print('--')
while true do
local curr = math.floor ( ( left + right ) / 2 )
local currxmin = Text.cursor_x2 ( line , curr + 1 )
local currxmax = Text.cursor_x2 ( line , curr + 2 )
--? print(x, left, right, curr, currxmin, currxmax)
if currxmin <= x and x < currxmax then
return curr
end
if left >= right - 1 then
return left
end
if currxmin > x then
right = curr
else
left = curr
end
end
assert ( false )
end
2022-05-18 04:29:08 +01:00
function Text . cursor_x ( line_data , cursor_pos )
local line_before_cursor = line_data : sub ( 1 , cursor_pos - 1 )
2022-05-23 07:17:06 +01:00
local text_before_cursor = App.newText ( love.graphics . getFont ( ) , line_before_cursor )
return 25 + math.floor ( App.width ( text_before_cursor ) * Zoom )
2022-05-18 04:29:08 +01:00
end
2022-05-20 01:29:14 +01:00
function Text . cursor_x2 ( s , cursor_pos )
local s_before_cursor = s : sub ( 1 , cursor_pos - 1 )
2022-05-23 07:17:06 +01:00
local text_before_cursor = App.newText ( love.graphics . getFont ( ) , s_before_cursor )
return math.floor ( App.width ( text_before_cursor ) * Zoom )
2022-05-20 01:29:14 +01:00
end
2022-05-22 00:24:07 +01:00
function Text . to2 ( pos1 )
2022-05-22 07:02:04 +01:00
assert ( Lines [ pos1.line ] . mode == ' text ' )
2022-05-22 00:24:07 +01:00
local result = { line = pos1.line , screen_line = 1 }
2022-05-22 06:08:13 +01:00
if Lines [ pos1.line ] . screen_line_starting_pos == nil then
2022-05-22 00:24:07 +01:00
result.screen_pos = pos1.pos
else
for i =# Lines [ pos1.line ] . screen_line_starting_pos , 1 , - 1 do
local spos = Lines [ pos1.line ] . screen_line_starting_pos [ i ]
2022-05-22 07:02:04 +01:00
if spos <= pos1.pos then
2022-05-22 00:24:07 +01:00
result.screen_line = i
2022-05-22 07:02:04 +01:00
result.screen_pos = pos1.pos - spos + 1
2022-05-22 00:24:07 +01:00
break
end
end
end
assert ( result.screen_pos )
return result
end
function Text . to1 ( pos2 )
local result = { line = pos2.line , pos = pos2.screen_pos }
if pos2.screen_line > 1 then
2022-05-22 07:02:04 +01:00
result.pos = Lines [ pos2.line ] . screen_line_starting_pos [ pos2.screen_line ] + pos2.screen_pos - 1
2022-05-22 00:24:07 +01:00
end
return result
end
2022-05-22 07:02:04 +01:00
function Text . previous_screen_line ( pos2 )
if pos2.screen_line > 1 then
return { line = pos2.line , screen_line = pos2.screen_line - 1 , screen_pos = 1 }
elseif pos2.line == 1 then
return pos2
elseif Lines [ pos2.line - 1 ] . mode == ' drawing ' then
return { line = pos2.line - 1 , screen_line = 1 , screen_pos = 1 }
else
local l = Lines [ pos2.line - 1 ]
if l.screen_line_starting_pos == nil then
return { line = pos2.line - 1 , screen_line = 1 , screen_pos = 1 }
else
return { line = pos2.line - 1 , screen_line =# Lines [ pos2.line - 1 ] . screen_line_starting_pos , screen_pos = 1 }
end
end
end
2022-05-18 04:29:08 +01:00
return Text