pensieve.love/edit.lua

670 lines
25 KiB
Lua
Raw Permalink Normal View History

2022-07-12 06:03:27 +00:00
-- some constants people might like to tweak
Text_color = {r=0, g=0, b=0}
Cursor_color = {r=1, g=0, b=0}
Hyperlink_decoration_color = {r=0.4, g=0.4, b=1}
2022-07-12 06:03:27 +00:00
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
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}
Margin_left = 25
Margin_right = 25
Drawing_padding_top = 10
Drawing_padding_bottom = 10
Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom
2022-07-13 04:38:29 +00:00
Same_point_distance = 4 -- pixel distance at which two points are considered the same
edit = {}
-- run in both tests and a real run
function edit.initialize_state(top, left, right, font, font_height, line_height) -- currently always draws to bottom of screen
local result = {
id='test_id',
-- a line is either text or a drawing
-- a text is a table with:
-- mode = 'text',
-- string data,
-- 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=''}}, -- array of lines
-- Lines can be too long to fit on screen, in which case they _wrap_ into
-- multiple _screen lines_.
-- rendering wrapped text lines needs some additional short-lived data per line:
-- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen
-- starty, the y coord in pixels the line starts rendering from
-- fragments: snippets of the line guaranteed to not straddle screen lines
-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line
2022-07-20 23:34:09 +00:00
line_cache = {},
-- Given wrapping, any potential location for the text cursor can be described in two ways:
-- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units)
-- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line.
--
-- Most of the time we'll only persist positions in schema 1, translating to
-- schema 2 when that's convenient.
--
-- Make sure these coordinates are never aliased, so that changing one causes
-- action at a distance.
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
cursor1 = {line=1, pos=1}, -- position of cursor
screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen
selection1 = {},
-- some extra state to compute selection between mouse press and release
old_cursor1 = nil,
old_selection1 = nil,
mousepress_shift = nil,
2022-07-29 19:27:45 +00:00
editable = true,
-- cursor coordinates in pixels (nil => cursor is outside viewport)
cursor_x = nil,
cursor_y = nil,
current_drawing_mode = 'line', -- one of the available shape modes
previous_drawing_mode = nil, -- extra state for some ephemeral modes like moving/deleting/naming points
font = font,
font_height = font_height,
line_height = line_height,
top = top,
left = math.floor(left),
right = math.floor(right),
width = right-left,
filename = love.filesystem.getSourceBaseDirectory()..'/lines.txt', -- '/' should work even on Windows
next_save = nil,
recent_updated = false, -- on first write, log filename to a 'recent' file
-- undo
history = {},
next_history = 1,
-- search
search_term = nil,
search_backup = nil, -- stuff to restore when cancelling search
}
return result
end -- edit.initialize_state
2023-03-17 16:36:38 +00:00
function edit.check_locs(State)
-- if State is inconsistent (i.e. file changed by some other program),
-- throw away all cursor state entirely
if edit.invalid1(State, State.screen_top1)
or edit.invalid_cursor1(State)
2023-03-17 16:36:38 +00:00
or not edit.cursor_on_text(State)
or not Text.le1(State.screen_top1, State.cursor1) then
State.screen_top1 = {line=1, pos=1}
2023-09-17 05:39:51 +00:00
State.cursor1 = {line=1, pos=1}
2023-09-15 15:46:36 +00:00
edit.put_cursor_on_next_text_line(State)
2023-03-17 16:36:38 +00:00
end
end
function edit.invalid1(State, loc1)
if loc1.line > #State.lines then return true end
local l = State.lines[loc1.line]
if l.mode ~= 'text' then return false end -- pos is irrelevant to validity for a drawing line
return loc1.pos > #State.lines[loc1.line].data
2023-03-17 16:36:38 +00:00
end
-- cursor loc in particular differs from other locs in one way:
-- pos might occur just after end of line
function edit.invalid_cursor1(State)
local cursor1 = State.cursor1
if cursor1.line > #State.lines then return true end
local l = State.lines[cursor1.line]
if l.mode ~= 'text' then return false end -- pos is irrelevant to validity for a drawing line
return cursor1.pos > #State.lines[cursor1.line].data + 1
end
2023-03-17 16:36:38 +00:00
function edit.cursor_on_text(State)
return State.cursor1.line <= #State.lines
and State.lines[State.cursor1.line].mode == 'text'
end
2023-09-15 15:46:36 +00:00
function edit.put_cursor_on_next_text_line(State)
while true do
2023-09-17 05:41:55 +00:00
if State.cursor1.line >= #State.lines then
break
end
if State.lines[State.cursor1.line].mode == 'text' then
break
end
State.cursor1.line = State.cursor1.line+1
State.cursor1.pos = 1
2022-07-24 06:36:04 +00:00
end
end
2022-08-26 23:24:05 +00:00
function edit.final_cursor(State)
for i=#State.lines,1,-1 do
if State.lines[i].mode == 'text' then
return {line=i, pos=utf8.len(State.lines[i].data)+1}
2022-08-26 23:24:05 +00:00
end
end
end
-- return y drawn until
function edit.draw(State)
--? print_and_log(('edit.draw %s %d %d,%d'):format(State.id, State.top, State.left,State.right))
State.button_handlers = {}
love.graphics.setFont(State.font)
2022-07-12 06:03:27 +00:00
App.color(Text_color)
assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
State.cursor_x = nil
State.cursor_y = nil
local y = State.top
local screen_bottom1 = {line=nil, pos=nil}
--? print('== draw')
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
--? print('draw:', y, line_index, line)
snapshot I'm still trying to get those two tests to scroll and keep cursor on screen: typing in text at bottom screen line of screen until it wraps hitting down arrow from bottom screen line of screen (assuming pane extends further down) But this small tweak to keychord_pressed_on_editable_pane isn't the answer. It ensures down arrow makes forward progress, but the first down arrow past bottom still makes the cursor invisible. Unclear why it gets into that state _and_ how it's able to get out of that state now. Things I remember now: - pane.cursor_y tracks the _bottom_ pixel of the cursor - when you press down arrow in an editor and it doesn't use it, only the rest of the surface pans, thanks to ignore_editable_cursor_pane. Another thing I've learned: the bug with down arrow is unrelated to wrapping lines. I can reproduce it in an editor containing all single-char lines. It's almost like snap_cursor_to_bottom_of_screen isn't operating exactly opposite to draw in some situations. Assuming no line wrapping: screen_top = snap_cursor_to_bottom_of_screen(cursor) screen_bottom = edit.draw() assert(cursor.line == screen_bottom.line) This is getting violated somehow?! Even if the screen height isn't an integral multiple of line_height, it seems like straightforward modulo arithmetic. If cursor is at bottom screen line and there's no wrapping: screen_top.line = cursor.line - math.floor(App.screen.height/line_height) screen_bottom.line = screen_top.line + math.floor(App.screen.height/line_height) assert(cursor.line == screen_bottom.line) I'm still juggling tons of state in my head, feeling incredibly stupid.
2022-08-16 22:21:57 +00:00
if y + State.line_height > App.screen.height then
--? print('draw:', line.data, '--', y,'+',State.line_height, '>', App.screen.height, 'stop')
break
end
screen_bottom1.line = line_index
if line.mode == 'text' then
--? print('text.draw', y, line_index)
local startpos = 1
if line_index == State.screen_top1.line then
startpos = State.screen_top1.pos
end
2022-07-29 19:27:45 +00:00
if State.editable and line.data == '' then
-- button to insert new drawing
button(State, 'draw', {x=State.left-Margin_left+4, y=y+4, w=12,h=12, bg={r=1,g=1,b=0},
2022-07-29 19:26:47 +00:00
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={}})
2022-07-20 23:34:09 +00:00
table.insert(State.line_cache, line_index, {})
if State.cursor1.line >= line_index then
State.cursor1.line = State.cursor1.line+1
end
schedule_save(State)
record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
end,
})
end
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
--? print('=> y', y)
elseif line.mode == 'drawing' then
y = y+Drawing_padding_top
2022-07-21 00:06:16 +00:00
Drawing.draw(State, line_index, y)
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
else
assert(false, ('unknown line mode %s'):format(line.mode))
end
end
State.bottom = y
--? print_and_log(('edit.draw screen bottom %s %s,%s'):format(State.id, screen_bottom1.line, screen_bottom1.pos))
State.screen_bottom1 = screen_bottom1
--? print('screen bottom: '..tostring(State.screen_bottom1.pos)..' in '..tostring(State.lines[State.screen_bottom1.line].data))
if State.search_term then
Text.draw_search_bar(State)
end
return y
end
function edit.update(State, dt)
2022-07-12 23:26:00 +00:00
Drawing.update(State, dt)
if State.next_save and State.next_save < Current_time then
log(2, 'edit.update: about to save to disk '..State.id)
2022-07-26 02:56:39 +00:00
save_to_disk(State)
log(2, 'edit.update: done saving to disk '..State.id)
State.next_save = nil
end
end
2022-07-12 23:30:41 +00:00
function schedule_save(State)
if State.next_save == nil then
State.next_save = Current_time + 3 -- short enough that you're likely to still remember what you did
end
end
function edit.quit(State)
-- make sure to save before quitting
log(2, 'edit.quit '..State.id)
if State.next_save then
log(2, 'edit.quit: about to save to disk '..State.id)
2022-07-26 02:56:39 +00:00
save_to_disk(State)
log(2, 'edit.quit: done saving to disk '..State.id)
-- give some time for the OS to flush everything to disk
love.timer.sleep(0.1)
log(2, 'edit.quit: done giving some time to flush to disk '..State.id)
end
end
2022-12-24 03:30:06 +00:00
function edit.mouse_press(State, x,y, mouse_button)
love.keyboard.setTextInput(true) -- bring up keyboard on touch screen
if State.search_term then return end
State.mouse_down = mouse_button
--? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))
2023-12-17 07:37:32 +00:00
if mouse_press_consumed_by_any_button(State, x,y, mouse_button) then
-- press on a button and it returned 'true' to short-circuit
return
end
if y < State.top then
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
2023-06-03 17:43:20 +00:00
line=State.screen_top1.line,
pos=State.screen_top1.pos,
}
return
end
for line_index,line in ipairs(State.lines) do
if line.mode == 'text' then
local line_cache = State.line_cache[line_index]
--? if line_cache and line_cache.starty then
--? log(2, os.date('%Y/%m/%d/%H-%M-%S')..' edit.mouse_pressed c: line index '..tostring(line_index)..' starts at '..line_cache.starty)
--? end
if Text.in_line(State, line_index, x,y) then
--? log(2, os.date('%Y/%m/%d/%H-%M-%S')..' edit.mouse_pressed d: line found '..tostring(line_index))
-- delicate dance between cursor, selection and old cursor/selection
-- scenarios:
-- regular press+release: sets cursor, clears selection
-- shift press+release:
-- sets selection to old cursor if not set otherwise leaves it untouched
-- sets cursor
-- press and hold to start a selection: sets selection on press, cursor on release
-- press and hold, then press shift: ignore shift
-- i.e. mouse_release should never look at shift state
--? print_and_log(('edit.mouse_press: in line %d'):format(line_index))
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? log(2, 'selection is now at '..tostring(State.selection1.line)..','..tostring(State.selection1.pos))
return
end
elseif line.mode == 'drawing' then
2022-07-21 00:06:16 +00:00
local line_cache = State.line_cache[line_index]
2022-07-29 19:27:45 +00:00
if State.editable and 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)
2022-12-24 03:30:06 +00:00
Drawing.mouse_press(State, line_index, x,y, mouse_button)
return
end
end
end
-- still here? mouse press is below all screen lines
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end
function edit.mouse_release(State, x,y, mouse_button)
--? log(2, os.date('%Y/%m/%d/%H-%M-%S')..' edit.mouse_release a '..tostring(x)..','..tostring(y))
if State.search_term then return end
--? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))
State.mouse_down = nil
if State.lines.current_drawing then
Drawing.mouse_release(State, x,y, mouse_button)
2022-07-12 23:30:41 +00:00
schedule_save(State)
if Drawing.before then
record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
Drawing.before = nil
end
else
--? print_and_log('edit.mouse_release: no current drawing')
if y < State.top then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
edit.clean_up_mouse_press(State)
return
end
for line_index,line in ipairs(State.lines) do
if line.mode == 'text' then
if Text.in_line(State, line_index, x,y) then
--? print_and_log(('edit.mouse_release: in line %d'):format(line_index))
State.cursor1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))
edit.clean_up_mouse_press(State)
return
end
end
end
-- still here? mouse release is below all screen lines
State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
edit.clean_up_mouse_press(State)
--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))
end
end
function edit.clean_up_mouse_press(State)
if State.mousepress_shift then
if State.old_selection1.line == nil then
State.selection1 = State.old_cursor1
else
State.selection1 = State.old_selection1
end
end
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
if eq(State.cursor1, State.selection1) then
State.selection1 = {}
end
end
2023-03-24 04:00:09 +00:00
function edit.mouse_wheel_move(State, dx,dy)
if dy > 0 then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
edit.put_cursor_on_next_text_line(State)
2023-03-24 04:00:09 +00:00
for i=1,math.floor(dy) do
Text.up(State)
end
elseif dy < 0 then
2023-03-24 04:00:09 +00:00
State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
edit.put_cursor_on_next_text_line(State)
2023-03-24 04:00:09 +00:00
for i=1,math.floor(-dy) do
Text.down(State)
end
end
end
function edit.text_input(State, t)
2023-03-26 16:36:41 +00:00
--? print('text input', t)
if State.search_term then
State.search_term = State.search_term..t
Text.search_next(State)
2023-03-26 16:36:41 +00:00
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
p.name = p.name..t
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
else
2022-12-13 07:17:02 +00:00
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index == nil then
Text.text_input(State, t)
2022-12-13 07:17:02 +00:00
end
end
2022-07-12 23:30:41 +00:00
schedule_save(State)
end
function edit.keychord_press(State, chord, key)
if State.selection1.line and
not State.lines.current_drawing and
-- printable character created using shift key => delete selection
-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
(not App.shift_down() or utf8.len(key) == 1) and
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then
Text.delete_selection(State, State.left, State.right)
end
if State.search_term then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
if chord == 'escape' then
State.search_term = nil
State.cursor1 = State.search_backup.cursor
State.screen_top1 = State.search_backup.screen_top
State.search_backup = nil
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
elseif chord == 'return' then
State.search_term = nil
State.search_backup = nil
elseif chord == 'backspace' then
local len = utf8.len(State.search_term)
local byte_offset = Text.offset(State.search_term, len)
State.search_term = string.sub(State.search_term, 1, byte_offset-1)
elseif chord == 'down' then
State.cursor1.pos = State.cursor1.pos+1
Text.search_next(State)
elseif chord == 'up' then
Text.search_previous(State)
end
return
elseif chord == 'C-f' then
State.search_term = ''
2022-09-05 18:28:03 +00:00
State.search_backup = {
cursor={line=State.cursor1.line, pos=State.cursor1.pos},
screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},
}
2022-08-18 17:09:20 +00:00
-- zoom
elseif chord == 'C-=' then
2022-07-16 05:15:07 +00:00
edit.update_font_settings(State, State.font_height+2)
Text.redraw_all(State)
elseif chord == 'C--' then
if State.font_height > 2 then
edit.update_font_settings(State, State.font_height-2)
Text.redraw_all(State)
end
elseif chord == 'C-0' then
2022-07-16 05:15:07 +00:00
edit.update_font_settings(State, 20)
Text.redraw_all(State)
2022-08-18 17:09:20 +00:00
-- undo
elseif chord == 'C-z' then
local event = undo_event(State)
if event then
2022-08-31 04:31:55 +00:00
local screen_top_before = State.screen_top1
local src = event.before
State.screen_top1 = deepcopy(src.screen_top)
State.cursor1 = deepcopy(src.cursor)
State.selection1 = deepcopy(src.selection)
patch(State.lines, event.after, event.before)
2022-07-20 23:34:09 +00:00
patch_placeholders(State.line_cache, event.after, event.before)
2022-08-31 04:31:55 +00:00
-- make sure cursor remains in view (particularly so we can continue undoing)
if Text.cursor_out_of_screen(State) then
if Text.lt1(screen_top_before, State.screen_top1) then
bring_cursor_of_cursor_pane_in_view('down')
else
bring_cursor_of_cursor_pane_in_view('up')
end
end
-- invalidate various cached bits of lines
State.lines.current_drawing = nil
-- if we're scrolling, reclaim all fragments to avoid memory leaks
Text.redraw_all(State)
2022-07-12 23:30:41 +00:00
schedule_save(State)
end
elseif chord == 'C-y' then
local event = redo_event(State)
if event then
2022-08-31 04:31:55 +00:00
local screen_top_before = State.screen_top1
local src = event.after
State.screen_top1 = deepcopy(src.screen_top)
State.cursor1 = deepcopy(src.cursor)
State.selection1 = deepcopy(src.selection)
patch(State.lines, event.before, event.after)
patch_placeholders(State.line_cache, event.before, event.after)
2022-08-31 04:31:55 +00:00
-- make sure cursor remains in view (particularly so we can continue undoing)
if Text.cursor_out_of_screen(State) then
if Text.lt1(screen_top_before, State.screen_top1) then
bring_cursor_of_cursor_pane_in_view('down')
else
bring_cursor_of_cursor_pane_in_view('up')
end
end
-- invalidate various cached bits of lines
State.lines.current_drawing = nil
-- if we're scrolling, reclaim all fragments to avoid memory leaks
Text.redraw_all(State)
2022-07-12 23:30:41 +00:00
schedule_save(State)
end
-- clipboard
2022-11-19 08:11:39 +00:00
elseif chord == 'C-a' then
State.selection1 = {line=1, pos=1}
State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
elseif chord == 'C-c' then
local s = Text.selection(State)
if s then
App.set_clipboard(s)
end
elseif chord == 'C-x' then
local s = Text.cut_selection(State, State.left, State.right)
if s then
App.set_clipboard(s)
end
2022-07-12 23:30:41 +00:00
schedule_save(State)
elseif chord == 'C-v' then
-- We don't have a good sense of when to scroll, so we'll be conservative
-- and sometimes scroll when we didn't quite need to.
local before_line = State.cursor1.line
local before = snapshot(State, before_line)
local clipboard_data = App.get_clipboard()
for _,code in utf8.codes(clipboard_data) do
local c = utf8.char(code)
if c == '\n' then
Text.insert_return(State)
else
Text.insert_at_cursor(State, c)
end
end
if Text.cursor_out_of_screen(State) then
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
end
2022-07-12 23:30:41 +00:00
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then
local before = snapshot(State, drawing_index)
Drawing.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
2022-07-12 23:30:41 +00:00
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
2023-03-26 16:36:41 +00:00
elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
if chord == 'return' then
State.current_drawing_mode = State.previous_drawing_mode
State.previous_drawing_mode = nil
else
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
if chord == 'escape' then
p.name = nil
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
elseif chord == 'backspace' then
local len = utf8.len(p.name)
2023-03-26 15:17:31 +00:00
if len > 0 then
local byte_offset = Text.offset(p.name, len-1)
if len == 1 then byte_offset = 0 end
p.name = string.sub(p.name, 1, byte_offset)
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
end
end
end
2022-07-12 23:30:41 +00:00
schedule_save(State)
else
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.keychord_press(State, chord)
end
end
function edit.key_release(State, key, scancode)
end
2022-07-16 05:15:07 +00:00
function edit.update_font_settings(State, font_height)
State.font_height = font_height
State.font = love.graphics.newFont(State.font_height)
2022-07-16 05:15:07 +00:00
State.line_height = math.floor(font_height*1.3)
end
--== some methods for tests
2022-09-07 17:16:24 +00:00
-- Insulate tests from some key globals so I don't have to change the vast
-- majority of tests when they're modified for the real app.
Test_margin_left = 25
2022-09-07 17:16:24 +00:00
Test_margin_right = 0
function edit.initialize_test_state()
-- if you change these values, tests will start failing
return edit.initialize_state(
15, -- top margin
Test_margin_left,
2022-09-07 17:16:24 +00:00
App.screen.width - Test_margin_right,
love.graphics.getFont(),
14,
15) -- line height
end
-- all text_input events are also keypresses
-- TODO: handle chords of multiple keys
function edit.run_after_text_input(State, t)
edit.keychord_press(State, t)
edit.text_input(State, t)
edit.key_release(State, t)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
-- not all keys are text_input
function edit.run_after_keychord(State, chord, key)
edit.keychord_press(State, chord, key)
edit.key_release(State, key)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
function edit.run_after_mouse_click(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button)
2022-12-24 03:30:06 +00:00
edit.mouse_press(State, x,y, mouse_button)
App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
function edit.run_after_mouse_press(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button)
2022-12-24 03:30:06 +00:00
edit.mouse_press(State, x,y, mouse_button)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
function edit.run_after_mouse_release(State, x,y, mouse_button)
App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end