1731 lines
60 KiB
Lua
1731 lines
60 KiB
Lua
Column_header_color = {r=0.7, g=0.7, b=0.7}
|
|
Pane_title_color = {r=0.5, g=0.5, b=0.5}
|
|
Pane_title_background_color = {r=0, g=0, b=0, a=0.1}
|
|
Pane_background_color = {r=0.7, g=0.7, b=0.7, a=0.1}
|
|
Grab_background_color = {r=0.7, g=0.7, b=0.7}
|
|
Cursor_pane_background_color = {r=0.7, g=0.7, b=0, a=0.1}
|
|
Menu_background_color = {r=0.6, g=0.8, b=0.6}
|
|
Menu_border_color = {r=0.6, g=0.7, b=0.6}
|
|
Menu_command_color = {r=0.2, g=0.2, b=0.2}
|
|
Command_palette_background_color = Menu_background_color
|
|
Command_palette_border_color = Menu_border_color
|
|
Command_palette_command_color = Menu_command_color
|
|
Command_palette_alternatives_background_color = Menu_background_color
|
|
Command_palette_highlighted_alternative_background_color = {r=0.5, g=0.7, b=0.3}
|
|
Command_palette_alternatives_color = {r=0.3, g=0.5, b=0.3}
|
|
Crosslink_color={r=0, g=0.7, b=0.7}
|
|
Crosslink_background_color={r=0, g=0, b=0, a=0.1}
|
|
|
|
run = {}
|
|
|
|
profile = require 'profile'
|
|
|
|
-- The note-taking app has a few differences with the baseline editor it's
|
|
-- forked from:
|
|
-- - most notes are read-only
|
|
-- - the editor operates entirely in viewport-relative coordinates; 0,0 is
|
|
-- the top-left corner of the window. However the note-taking app in
|
|
-- read-only mode largely operates in absolute coordinates; a potentially
|
|
-- large 2D space that the window is just a peephole into.
|
|
--
|
|
-- We'll use the rendering logic in the editor, but only use its event loop
|
|
-- when a window is being edited (there can only be one all over the entire
|
|
-- surface)
|
|
--
|
|
-- Most of the time the viewport affects each pane's top and screen_top. An
|
|
-- exception is when you're editing a pane and you scroll the cursor inside
|
|
-- it. In that case we want to affect the viewport (for all panes) based on
|
|
-- the editable pane's screen_top.
|
|
|
|
Editor_state = {}
|
|
|
|
-- called both in tests and real run
|
|
function run.initialize_globals()
|
|
-- stuff we paginate over is organized as follows:
|
|
-- - there are multiple columns
|
|
-- - each column contains panes
|
|
-- - each pane contains editor state as in lines.love
|
|
Surface = {}
|
|
|
|
-- The surface may show the same file in multiple panes. Share links between
|
|
-- files (which will never go in Surface).
|
|
Links = {}
|
|
|
|
-- LÖVE renders N frames per second like any game engine, but we don't
|
|
-- really need that. The only thing that animates in this app is the cursor.
|
|
--
|
|
-- Until I fix that, the architecture of this app will be to plan what to
|
|
-- draw only when something changes. That way we minimize the amount of
|
|
-- computation/power wasted on each of those frames.
|
|
Panes_to_draw = {} -- array of panes from surface
|
|
Column_headers_to_draw = {} -- strings with x coordinates
|
|
|
|
Display_settings = {
|
|
mode='normal',
|
|
-- valid modes:
|
|
-- normal (show full surface)
|
|
-- maximize (show just a single note; focus mode)
|
|
-- search (notes currently on surface)
|
|
-- search_all (notes in directory)
|
|
-- searching_all (search in progress)
|
|
x=0, y=0, -- <==== Top-left corner of the viewport into the surface
|
|
column_width=400,
|
|
show_debug=false,
|
|
palette=nil, -- {command='', alternative_index=1, candidates=nil}
|
|
search_term='',
|
|
search_backup_x=nil, search_backup_y=nil, search_backup_cursor_pane=nil,
|
|
search_all_query=nil, search_all_terms=nil,
|
|
search_all_progress=nil,
|
|
search_all_progress_indicator=nil,
|
|
search_all_pane=nil,
|
|
}
|
|
-- display settings that are constants
|
|
Font_height = 20
|
|
Line_height = math.floor(Font_height*1.3)
|
|
|
|
-- space saved for headers
|
|
-- this is only on the screen, not used on the surface itself
|
|
Menu_status_bar_height = 5 + Line_height + 5
|
|
--? print('menu height', Menu_status_bar_height)
|
|
Column_header_height = 5 + Line_height + 5
|
|
--? print('column header height', Column_header_height)
|
|
Header_height = Menu_status_bar_height + Column_header_height
|
|
|
|
-- padding is the space between panes on the surface
|
|
Padding_vertical = 20 -- space between panes
|
|
Padding_horizontal = 20
|
|
|
|
-- margins are extra space inside the borders of panes on the surface
|
|
Margin_above = 10
|
|
Margin_below = 10
|
|
|
|
Pan_step = 10
|
|
Pan = {}
|
|
|
|
Cursor_pane = {col=0, row=1} -- surface column and row index, along with some cached data
|
|
-- occasional secondary cursor
|
|
Grab_pane = nil
|
|
|
|
-- where we store our notes (pane id is also a relative path under there)
|
|
Directory = nil
|
|
Directory_error = nil -- Any error encountered while determining Directory. If this is ever set, the app will do nothing but display it. (We can't rely on 'error' for that because we don't want to open up the sources in that situation.)
|
|
|
|
-- blinking cursor
|
|
Cursor_time = 0
|
|
|
|
-- a read-only buffer for errors
|
|
Error_log = edit.initialize_state(0, 0, Display_settings.column_width, Font_height, Line_height)
|
|
Error_log.id = 'errors'
|
|
Error_log.filename = nil -- Will live within Directory. Don't forget to set this once Directory is initialized.
|
|
Error_log.editable = false
|
|
Text.redraw_all(Error_log)
|
|
Current_error = nil
|
|
Current_error_time = nil
|
|
end
|
|
|
|
-- called only for real run
|
|
function run.initialize(arg)
|
|
log_new('run')
|
|
if Settings then
|
|
print('loading settings')
|
|
run.load_settings()
|
|
Directory = Settings.data_directory
|
|
else
|
|
print('no saved settings; initializing to defaults')
|
|
run.initialize_default_settings()
|
|
end
|
|
|
|
if #arg > 0 then
|
|
if not is_absolute_path(arg[1]) then
|
|
Directory_error = 'Please use an unambiguous absolute path for the notes directory.'
|
|
return
|
|
end
|
|
Directory = arg[1]
|
|
print('setting Directory from commandline: '..arg[1])
|
|
if Directory:sub(#Directory,#Directory) ~= '/' then
|
|
Directory = Directory..'/'
|
|
end
|
|
print('Directory is now '..Directory)
|
|
end
|
|
|
|
if Directory == nil then
|
|
print('setting Directory_error')
|
|
if Settings == nil then
|
|
Directory_error =
|
|
"Please run pensieve.love once from a terminal window and specify the\n"..
|
|
"location of your notes. The location will be remembered in future.\n"..
|
|
"Thank you! If all goes well, you won't see this message ever again."
|
|
else
|
|
Directory_error =
|
|
"Please perform a one-time migration for your notes:\n"..
|
|
"\n"..
|
|
"* (optional) Move any existing notes in "..App.save_dir.."data\n"..
|
|
" or similar locations to your preferred location.\n"..
|
|
"\n"..
|
|
"* (optional) Move any existing "..App.save_dir.."config\n"..
|
|
" file _inside_ your notes directory to preserve any prior state\n"..
|
|
" of your note-taking surface (open columns, etc.).\n"..
|
|
"\n"..
|
|
"* Please run pensieve.love once from a terminal window and specify the\n"..
|
|
" location of your notes as an absolute path. This location will be\n"..
|
|
" remembered in future.\n"..
|
|
"\n"..
|
|
"Thank you! If all goes well, you won't see this message ever again."
|
|
end
|
|
return
|
|
end
|
|
|
|
assert(Directory, 'no directory to browse notes in')
|
|
print('Directory initialized to '..Directory)
|
|
Error_log.filename = Directory..'errors'
|
|
|
|
run.load_more_settings_from_notes_directory()
|
|
|
|
Editor_state = nil -- not used outside editor tests
|
|
|
|
|
|
|
|
-- keep a few blank lines around: https://merveilles.town/@akkartik/110084833821965708
|
|
love.window.setTitle('pensieve.love')
|
|
|
|
|
|
|
|
if #arg > 1 then
|
|
print('ignoring commandline args after '..arg[1])
|
|
end
|
|
|
|
print_and_log('reading notes from '..Directory)
|
|
print_and_log('put any notes there (and make frequent backups)')
|
|
|
|
if Display_settings.column_width > App.screen.width - Padding_horizontal - Margin_left - Margin_right - Padding_horizontal then
|
|
Display_settings.column_width = math.max(200, App.screen.width - Padding_horizontal - Margin_left - Margin_right - Padding_horizontal)
|
|
end
|
|
|
|
Cursor_pane.col = math.min(Cursor_pane.col, #Surface)
|
|
if Cursor_pane.col >= 1 then
|
|
Cursor_pane.row = math.min(Cursor_pane.row, #Surface[Cursor_pane.col])
|
|
end
|
|
|
|
plan_draw()
|
|
|
|
if rawget(_G, 'jit') then
|
|
jit.off()
|
|
jit.flush()
|
|
end
|
|
end
|
|
|
|
function print_and_log(s)
|
|
print(s)
|
|
log(3, s)
|
|
end
|
|
|
|
function run.load_settings()
|
|
Display_settings.font = love.graphics.newFont(Settings.font_height)
|
|
-- set up desired window dimensions and make window resizable
|
|
_, _, App.screen.flags = App.screen.size()
|
|
App.screen.flags.resizable = true
|
|
App.screen.width, App.screen.height = Settings.width, Settings.height
|
|
App.screen.resize(App.screen.width, App.screen.height, App.screen.flags)
|
|
run.set_window_position_from_settings(Settings)
|
|
Font_height = Settings.font_height
|
|
Line_height = math.floor(Font_height*1.3)
|
|
love.graphics.setFont(Display_settings.font)
|
|
end
|
|
|
|
function run.load_more_settings_from_notes_directory()
|
|
local f = App.open_for_reading(Directory..'config')
|
|
if f then
|
|
local directory_settings = json.decode(f:read())
|
|
f:close()
|
|
Display_settings.column_width = directory_settings.column_width
|
|
for _,column_name in ipairs(directory_settings.columns) do
|
|
create_column(column_name)
|
|
end
|
|
Cursor_pane.col = directory_settings.cursor_col
|
|
Cursor_pane.row = directory_settings.cursor_row
|
|
Display_settings.x = directory_settings.surface_x
|
|
Display_settings.y = directory_settings.surface_y
|
|
else
|
|
-- initialize surface with a single column
|
|
command.recently_modified()
|
|
end
|
|
end
|
|
|
|
function run.set_window_position_from_settings(settings)
|
|
local os = love.system.getOS()
|
|
if os == 'Linux' then
|
|
-- love.window.setPosition doesn't quite seem to do what is asked of it on Linux.
|
|
App.screen.move(settings.x, settings.y-37, settings.displayindex)
|
|
else
|
|
App.screen.move(settings.x, settings.y, settings.displayindex)
|
|
end
|
|
end
|
|
|
|
function run.initialize_default_settings()
|
|
Display_settings.font = love.graphics.newFont(Font_height)
|
|
love.graphics.setFont(Display_settings.font)
|
|
run.initialize_window_geometry()
|
|
Display_settings.column_width = 40*Display_settings.font:getWidth('m')
|
|
end
|
|
|
|
function run.initialize_window_geometry()
|
|
-- Initialize window width/height and make window resizable.
|
|
--
|
|
-- I get tempted to have opinions about window dimensions here, but they're
|
|
-- non-portable:
|
|
-- - maximizing doesn't work on mobile and messes things up
|
|
-- - maximizing keeps the title bar on screen in Linux, but off screen on
|
|
-- Windows. And there's no way to get the height of the title bar.
|
|
-- It seems more robust to just follow LÖVE's default window size until
|
|
-- someone overrides it.
|
|
App.screen.width, App.screen.height, App.screen.flags = App.screen.size()
|
|
App.screen.flags.resizable = true
|
|
App.screen.resize(App.screen.width, App.screen.height, App.screen.flags)
|
|
end
|
|
|
|
function run.resize(w, h)
|
|
App.screen.width, App.screen.height = w, h
|
|
if Directory_error then return end
|
|
--? print('resize:', App.screen.width, App.screen.height)
|
|
plan_draw()
|
|
end
|
|
|
|
function load_pane(id)
|
|
--? print('load pane from file', id)
|
|
local result = edit.initialize_state(0, 0, math.min(Display_settings.column_width, App.screen.width-Margin_right), love.graphics.getFont(), Font_height, Line_height)
|
|
result.id = id
|
|
result.filename = Directory..id
|
|
load_from_disk(result)
|
|
-- links are shared across all instances of an id, so never clobber them once loaded
|
|
if Links[id] == nil then
|
|
Links[id] = load_links(id)
|
|
end
|
|
result.font_height = Font_height
|
|
result.line_height = Line_height
|
|
result.editable = false
|
|
edit.check_locs(result)
|
|
Text.redraw_all(result)
|
|
return result
|
|
end
|
|
|
|
function height(pane)
|
|
if pane._height == nil then
|
|
refresh_pane_height(pane)
|
|
end
|
|
return pane._height
|
|
end
|
|
|
|
-- keep the structure of this function sync'd with plan_draw
|
|
function refresh_pane_height(pane)
|
|
--? print('refresh pane height')
|
|
local y = 0
|
|
if pane.title then
|
|
y = y + 5+Line_height+5
|
|
end
|
|
rehydrate_pane(pane)
|
|
for i=1,#pane.lines do
|
|
local line = pane.lines[i]
|
|
if line.mode == 'text' then
|
|
Text.populate_screen_line_starting_pos(pane, i)
|
|
y = y + Line_height*#pane.line_cache[i].screen_line_starting_pos
|
|
Text.clear_screen_line_cache(pane, i)
|
|
elseif line.mode == 'drawing' then
|
|
-- nothing
|
|
y = y + Drawing.pixels(line.h, Display_settings.column_width) + Drawing_padding_height
|
|
else
|
|
assert(false, ('unknown line mode %s'):format(line.mode))
|
|
end
|
|
end
|
|
if Links[pane.id] and not empty(Links[pane.id]) then
|
|
y = y + 5+Line_height+5 -- for crosslinks
|
|
end
|
|
pane._height = y
|
|
end
|
|
|
|
-- titles are optional and so affect the height of the pane
|
|
function add_title(pane, title)
|
|
pane.title = title
|
|
pane._height = nil
|
|
end
|
|
|
|
-- keep the structure of this function sync'd with refresh_pane_height
|
|
function plan_draw()
|
|
--? print_and_log(('plan_draw %d,%d'):format(Display_settings.x, Display_settings.y))
|
|
Panes_to_draw = {}
|
|
Column_headers_to_draw = {}
|
|
local sx = Padding_horizontal + Margin_left
|
|
if Grab_pane then rehydrate_pane(Grab_pane) end
|
|
for column_index, column in ipairs(Surface) do
|
|
if should_show_column(sx) then
|
|
table.insert(Column_headers_to_draw, {name=('%d. %s'):format(column_index, column.name), x = sx-Display_settings.x})
|
|
local sy = Padding_vertical
|
|
for pane_index, pane in ipairs(column) do
|
|
if sy > Display_settings.y + App.screen.height - Header_height then
|
|
break
|
|
end
|
|
--? print_and_log(('plan_draw bounds %d,%d %s %d,%d'):format(column_index, pane_index, pane.id, sx,sy))
|
|
if should_show_pane(pane, sy) then
|
|
if not App.mouse_down(1) then
|
|
for _,line_cache in ipairs(pane.line_cache) do line_cache.starty = nil end -- just in case we scrolled at some point without sending information to any of the panes currently on screen
|
|
end
|
|
table.insert(Panes_to_draw, pane)
|
|
-- stash some short-lived variables
|
|
pane.column_index = column_index
|
|
pane.pane_index = pane_index
|
|
local y_offset = 0
|
|
local body_sy = sy
|
|
if column[pane_index].title then
|
|
body_sy = body_sy + 5+Line_height+5
|
|
end
|
|
if body_sy < Display_settings.y then
|
|
pane.screen_top1, y_offset = schema1_of_y(pane, Display_settings.y - body_sy)
|
|
pane.top = 0
|
|
else
|
|
pane.screen_top1 = {line=1, pos=1}
|
|
pane.top = body_sy - Display_settings.y
|
|
end
|
|
pane.top = Header_height + Margin_above + pane.top - y_offset
|
|
--? print('bounds: =>', pane.top)
|
|
pane.left = sx - Display_settings.x
|
|
pane.right = pane.left + Display_settings.column_width
|
|
pane.width = pane.right - pane.left
|
|
else
|
|
-- clear bounds to catch issues early
|
|
pane.top = nil
|
|
--? print('bounds: =>', pane.top)
|
|
end
|
|
sy = sy + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
end
|
|
else
|
|
-- clear bounds to catch issues early
|
|
for _, pane in ipairs(column) do
|
|
pane.top = nil
|
|
end
|
|
end
|
|
sx = sx + Margin_right + Display_settings.column_width + Padding_horizontal + Margin_left
|
|
end
|
|
end
|
|
|
|
function run.draw()
|
|
if Directory_error then
|
|
love.graphics.print(Directory_error, 50,50)
|
|
return
|
|
end
|
|
--? print('draw', Display_settings.y)
|
|
if Display_settings.mode == 'normal' then
|
|
draw_normal_mode()
|
|
elseif Display_settings.mode == 'search' then
|
|
draw_normal_mode()
|
|
-- hack: pass in an unexpected object and pun some attributes
|
|
Text.draw_search_bar(Display_settings, --[[force show cursor]] true)
|
|
elseif Display_settings.mode == 'search_all' then
|
|
draw_normal_mode()
|
|
-- only difference is in command palette below
|
|
elseif Display_settings.mode == 'searching_all' then
|
|
draw_normal_mode()
|
|
-- only difference is in command palette below
|
|
elseif Display_settings.mode == 'maximize' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
pane.top = Header_height + Margin_above
|
|
pane.left = App.screen.width/2 - 20*pane.font:getWidth('m')
|
|
pane.right = App.screen.width/2 + 20*pane.font:getWidth('m')
|
|
pane.width = pane.right - pane.left
|
|
edit.draw(pane)
|
|
end
|
|
end
|
|
else
|
|
assert(false, ('pensieve is in an unknown mode %s'):format(Display_settings.mode))
|
|
end
|
|
if Grab_pane then
|
|
local old_top, old_left, old_right = Grab_pane.top, Grab_pane.left, Grab_pane.right
|
|
local old_screen_top = Grab_pane.screen_top1
|
|
Grab_pane.screen_top1 = {line=1, pos=1}
|
|
Grab_pane.top = App.screen.height - 10*Line_height
|
|
Grab_pane.left = App.screen.width - Display_settings.column_width - Margin_right - Padding_horizontal
|
|
Grab_pane.right = Grab_pane.left + Display_settings.column_width
|
|
Grab_pane.width = Grab_pane.right - Grab_pane.left
|
|
App.color(Grab_background_color)
|
|
love.graphics.rectangle('fill', Grab_pane.left-Margin_left,Grab_pane.top-Margin_above, Grab_pane.width+Margin_left+Margin_right, App.screen.height-Grab_pane.top+Margin_above)
|
|
edit.draw(Grab_pane)
|
|
Grab_pane.top, Grab_pane.left, Grab_pane.right = old_top, old_left, old_right
|
|
Grab_pane.screen_top1 = old_screen_top
|
|
end
|
|
draw_menu_bar()
|
|
if Display_settings.mode == 'search_all' or Display_settings.mode == 'searching_all' then
|
|
draw_command_palette_for_search_all()
|
|
else
|
|
if Display_settings.palette then
|
|
draw_command_palette()
|
|
else
|
|
show_error()
|
|
end
|
|
end
|
|
draw_debug()
|
|
end
|
|
|
|
function draw_normal_mode()
|
|
assert(Cursor_pane.col, 'no current column')
|
|
assert(Cursor_pane.row, 'no current row')
|
|
--? print('draw', Display_settings.x, Display_settings.y)
|
|
for _,pane in ipairs(Panes_to_draw) do
|
|
assert(pane.top, "pane has no top coordinate; there's likely a problem in plan_draw")
|
|
--? if pane.column_index == 1 and pane.pane_index == 1 then
|
|
--? print('draw', pane.id, 'from y', pane.top, 'down to screen height', App.screen.height)
|
|
--? print('screen top', pane.screen_top1.line, pane.screen_top1.pos)
|
|
--? print('cursor', pane.cursor1.line, pane.cursor1.pos)
|
|
--? end
|
|
if pane.title and eq(pane.screen_top1, {line=1, pos=1}) then
|
|
draw_title(pane)
|
|
end
|
|
edit.draw(pane)
|
|
if pane_drew_to_bottom(pane) then
|
|
draw_links(pane)
|
|
end
|
|
if pane.column_index == Cursor_pane.col and pane.pane_index == Cursor_pane.row then
|
|
App.color(Cursor_pane_background_color)
|
|
if pane.editable and Surface.cursor_on_screen_check then
|
|
assert(pane.cursor_y, 'cursor went off screen; this should never happen')
|
|
Surface.cursor_on_screen_check = false
|
|
end
|
|
else
|
|
App.color(Pane_background_color)
|
|
end
|
|
love.graphics.rectangle('fill', pane.left-Margin_left,pane.top-Margin_above, pane.width+Margin_left+Margin_right, pane.bottom-pane.top+Margin_above+Margin_below)
|
|
end
|
|
for _,header in ipairs(Column_headers_to_draw) do
|
|
-- column header
|
|
App.color(Column_header_color)
|
|
love.graphics.rectangle('fill', header.x - Margin_left, Menu_status_bar_height, Margin_left + Display_settings.column_width + Margin_right, Column_header_height)
|
|
App.color(Text_color)
|
|
love.graphics.print(header.name, header.x, Menu_status_bar_height+5)
|
|
end
|
|
end
|
|
|
|
function pane_drew_to_bottom(pane)
|
|
return pane.bottom < App.screen.height - Line_height
|
|
end
|
|
|
|
function should_show_column(sx)
|
|
return overlap(sx-Margin_left, sx+Display_settings.column_width+Margin_right, Display_settings.x, Display_settings.x + App.screen.width)
|
|
end
|
|
|
|
function should_show_pane(pane, sy)
|
|
return overlap(sy, sy + Margin_above + height(pane) + Margin_below, Display_settings.y, Display_settings.y + App.screen.height - Header_height)
|
|
end
|
|
|
|
function draw_title(pane)
|
|
assert(pane.title, 'pane has no title')
|
|
App.color(Pane_title_color)
|
|
App.screen.print(pane.title, pane.left, pane.top-Margin_above -5-Line_height)
|
|
App.color(Pane_title_background_color)
|
|
love.graphics.rectangle('fill', pane.left-Margin_left, pane.top-Margin_above-5-Line_height-5, Margin_left+Display_settings.column_width+Margin_right, 5+Line_height+5)
|
|
end
|
|
|
|
function draw_links(pane)
|
|
local links = Links[pane.id]
|
|
if links == nil then return end
|
|
if empty(links) then return end
|
|
local x = pane.left
|
|
for _,label in ipairs(Edge_list) do
|
|
if links[label] then
|
|
draw_link(pane.font, label, x, pane.bottom)
|
|
end
|
|
x = x + pane.font:getWidth(label) + 10 + 10
|
|
end
|
|
-- links we don't know about, just in case
|
|
for link,_ in pairs(links) do
|
|
if not Opposite[link] then
|
|
draw_link(pane.font, link, x, pane.bottom)
|
|
x = x + pane.font:getWidth(link) + 10 + 10
|
|
end
|
|
end
|
|
pane.bottom = pane.bottom + 5+Line_height+5
|
|
end
|
|
|
|
function draw_link(font, label, x,y)
|
|
App.color(Crosslink_color)
|
|
love.graphics.print(label, x, y+5)
|
|
App.color(Crosslink_background_color)
|
|
love.graphics.rectangle('fill', x-5, y+3, font:getWidth(label)+10, 2+Line_height+2)
|
|
end
|
|
|
|
-- assumes intervals are half-open: [lo, hi)
|
|
-- https://en.wikipedia.org/wiki/Interval_(mathematics)
|
|
function overlap(lo1,hi1, lo2,hi2)
|
|
-- lo2 hi2
|
|
-- | |
|
|
-- | |
|
|
-- | |
|
|
if lo1 <= lo2 and hi1 > lo2 then
|
|
return true
|
|
end
|
|
-- lo2 hi2
|
|
-- | |
|
|
-- | |
|
|
if lo1 < hi2 and hi1 >= hi2 then
|
|
return true
|
|
end
|
|
-- lo2 hi2
|
|
-- | |
|
|
-- | |
|
|
return lo1 >= lo2 and hi1 <= hi2
|
|
end
|
|
|
|
function run.update(dt)
|
|
if Directory_error then return end
|
|
update_footprint()
|
|
Cursor_time = Cursor_time + dt
|
|
if App.mouse_y() < Header_height then
|
|
-- column header
|
|
love.mouse.setCursor(love.mouse.getSystemCursor('arrow'))
|
|
elseif in_pane(App.mouse_x(), App.mouse_y()) then
|
|
love.mouse.setCursor(love.mouse.getSystemCursor('arrow'))
|
|
else
|
|
love.mouse.setCursor(love.mouse.getSystemCursor('hand'))
|
|
end
|
|
if Pan.x then
|
|
Display_settings.x = math.max(Pan.x-App.mouse_x(), 0)
|
|
Display_settings.y = math.max(Pan.y-(App.mouse_y()-Header_height), 0)
|
|
end
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane and pane.editable then
|
|
edit.update(pane, dt)
|
|
end
|
|
end
|
|
if not Display_settings.palette and (Display_settings.mode == 'normal' or Display_settings.mode == 'search') and App.mouse_down(1) then
|
|
-- pan the surface by dragging
|
|
plan_draw()
|
|
end
|
|
if Display_settings.mode == 'searching_all' then
|
|
resume_search_all()
|
|
end
|
|
if Current_error then
|
|
if App.get_time() - Current_error_time > 3 then
|
|
Current_error = nil
|
|
Current_error_time = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function in_pane(x,y)
|
|
-- duplicate some logic from App.draw
|
|
local sx,sy = to_surface(x,y)
|
|
local x = Padding_horizontal
|
|
for column_idx, column in ipairs(Surface) do
|
|
if sx < x then
|
|
return false
|
|
end
|
|
if sx < x + Margin_left + Display_settings.column_width + Margin_right then
|
|
local y = Padding_vertical
|
|
for pane_idx, pane in ipairs(column) do
|
|
if sy < y then
|
|
return false
|
|
end
|
|
if sy < y + Margin_above + height(pane) + Margin_below then
|
|
return true
|
|
end
|
|
y = y + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
end
|
|
end
|
|
x = x + Margin_left + Display_settings.column_width + Margin_right + Padding_horizontal
|
|
end
|
|
return false
|
|
end
|
|
|
|
function to_pane(sx,sy)
|
|
-- duplicate some logic from App.draw
|
|
local x = Padding_horizontal
|
|
for column_idx, column in ipairs(Surface) do
|
|
if sx < x then
|
|
return nil
|
|
end
|
|
if sx < x + Margin_left + Display_settings.column_width + Margin_right then
|
|
local y = Padding_vertical
|
|
for pane_idx, pane in ipairs(column) do
|
|
if sy < y then
|
|
return nil
|
|
end
|
|
if sy < y + Margin_above + height(pane) + Margin_below then
|
|
return {col=column_idx, row=pane_idx}
|
|
end
|
|
y = y + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
end
|
|
end
|
|
x = x + Margin_left + Display_settings.column_width + Margin_right + Padding_horizontal
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function to_surface(x, y)
|
|
return x+Display_settings.x, y+Display_settings.y-Header_height
|
|
end
|
|
|
|
function run.quit()
|
|
if Directory_error then return end
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane and pane.editable then
|
|
stop_editing(pane)
|
|
end
|
|
end
|
|
end
|
|
|
|
function run.settings()
|
|
-- avoid an infinite loop if handle_error ever tries to save settings
|
|
assert(Directory_error == nil,
|
|
"tried to save settings when we couldn't determine the directory to browse notes in")
|
|
-- side effect: save notes-related settings inside Directory
|
|
local column_names = {}
|
|
for _,column in ipairs(Surface) do
|
|
table.insert(column_names, column.name)
|
|
end
|
|
local status = App.write_file(Directory..'config',
|
|
json.encode({
|
|
columns=column_names,
|
|
column_width=Display_settings.column_width,
|
|
cursor_col=Cursor_pane.col,
|
|
cursor_row=Cursor_pane.row,
|
|
surface_x=Display_settings.x,
|
|
surface_y=Display_settings.y,
|
|
}))
|
|
assert(status, 'failed to write settings')
|
|
|
|
if Settings == nil then Settings = {} end
|
|
Settings.x, Settings.y, Settings.displayindex = App.screen.position()
|
|
return {
|
|
x=Settings.x, y=Settings.y, displayindex=Settings.displayindex,
|
|
width=App.screen.width, height=App.screen.height,
|
|
font_height=Font_height,
|
|
data_directory=Directory,
|
|
}
|
|
end
|
|
|
|
function run.mouse_press(x,y, mouse_button)
|
|
if Directory_error then return end
|
|
--? print('app mouse pressed', x,y)
|
|
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
|
|
clear_selections()
|
|
if Display_settings.mode == 'normal' or Display_settings.mode == 'search' or Display_settings.mode == 'search_all' or Display_settings.mode == 'searching_all' then
|
|
mouse_press_in_normal_mode(x,y, mouse_button)
|
|
elseif Display_settings.mode == 'maximize' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
edit.mouse_press(pane, x,y, mouse_button)
|
|
end
|
|
end
|
|
else
|
|
assert(false, ('pensieve is in an unknown mode %s'):format(Display_settings.mode))
|
|
end
|
|
end
|
|
|
|
function clear_selections()
|
|
for _,column in ipairs(Surface) do
|
|
for _,pane in ipairs(column) do
|
|
pane.selection1 = {}
|
|
end
|
|
end
|
|
end
|
|
|
|
function mouse_press_in_normal_mode(x,y, mouse_button)
|
|
Pan = {}
|
|
if y < Header_height then
|
|
-- column headers currently not interactable
|
|
return
|
|
end
|
|
local sx,sy = to_surface(x,y)
|
|
if in_pane(x,y) then
|
|
Cursor_pane = to_pane(sx,sy)
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
edit.mouse_press(pane, x,y, mouse_button)
|
|
pane._height = nil
|
|
end
|
|
end
|
|
else
|
|
Pan = {x=sx, y=sy}
|
|
end
|
|
end
|
|
|
|
function run.mouse_release(x,y, mouse_button)
|
|
if Directory_error then return end
|
|
--? print('app mouse released')
|
|
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
|
|
if in_pane(x,y) or Display_settings.mode == 'maximize' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
edit.mouse_release(pane, x,y, mouse_button)
|
|
end
|
|
end
|
|
end
|
|
Pan = {}
|
|
end
|
|
|
|
function run.mouse_wheel_move(dx,dy)
|
|
if Directory_error then return end
|
|
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
if pane.editable then
|
|
if pane.cursor_x then
|
|
edit.mouse_wheel_move(pane, dx,dy)
|
|
plan_draw()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- shift+scroll wheel doesn't set dx for me; manually do so if necessary
|
|
if App.shift_down() then
|
|
if dx == 0 then
|
|
dx,dy = dy,dx
|
|
end
|
|
end
|
|
if dy > 0 then
|
|
for i=1,math.floor(dy) do
|
|
pan_up()
|
|
end
|
|
plan_draw()
|
|
elseif dy < 0 then
|
|
for i=1,math.floor(-dy) do
|
|
pan_down()
|
|
end
|
|
plan_draw()
|
|
end
|
|
if dx > 0 then
|
|
for i=1,math.floor(dx) do
|
|
pan_left()
|
|
end
|
|
plan_draw()
|
|
elseif dx < 0 then
|
|
for i=1,math.floor(-dx) do
|
|
pan_right()
|
|
end
|
|
plan_draw()
|
|
end
|
|
end
|
|
|
|
function run.text_input(t)
|
|
if Directory_error then return end
|
|
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
|
|
--? print('textinput', t)
|
|
-- hotkeys operating on the cursor pane
|
|
if Display_settings.palette then
|
|
Display_settings.palette.command = Display_settings.palette.command..t
|
|
Display_settings.palette.alternative_index = 1
|
|
Display_settings.palette.candidates = candidates()
|
|
elseif Display_settings.mode == 'normal' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
if not pane.editable then
|
|
-- global hotkeys for normal mode
|
|
if t == 'X' then
|
|
command.wider_columns()
|
|
return
|
|
elseif t == 'x' then
|
|
command.narrower_columns()
|
|
return
|
|
end
|
|
-- send keys to the current pane
|
|
else
|
|
if pane.cursor_x and pane.cursor_x >= 0 and pane.cursor_x < App.screen.width then
|
|
if pane.cursor_y and pane.cursor_y >= Header_height and pane.cursor_y < App.screen.height then
|
|
--? print(('%s typed in editor pane'):format(t))
|
|
edit.text_input(pane, t)
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
pane._height = nil
|
|
plan_draw()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif Display_settings.mode == 'search' then
|
|
--? print('insert', t)
|
|
Display_settings.search_term = Display_settings.search_term..t
|
|
-- reset search state
|
|
clear_selections()
|
|
Display_settings.x = Display_settings.search_backup_x
|
|
Display_settings.y = Display_settings.search_backup_y
|
|
Cursor_pane = Display_settings.search_backup_cursor_pane
|
|
-- search again
|
|
search_next()
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
Surface.cursor_on_screen_check = true
|
|
plan_draw()
|
|
elseif Display_settings.mode == 'search_all' then
|
|
Display_settings.search_all_query = Display_settings.search_all_query..t
|
|
elseif Display_settings.mode == 'searching_all' then
|
|
Display_settings.mode = 'normal'
|
|
elseif Display_settings.mode == 'maximize' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
if pane.editable then
|
|
edit.text_input(pane, t)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
assert(false, ('pensieve is in an unknown mode %s'):format(Display_settings.mode))
|
|
end
|
|
end
|
|
|
|
function run.keychord_press(chord, key)
|
|
if Directory_error then return end
|
|
if App.run_tests == nil then
|
|
log(2, os.date('%Y/%m/%d/%H-%M-%S')..' app.keychord_press '..chord..' '..key)
|
|
end
|
|
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
|
|
-- global hotkeys
|
|
if chord == 'C-=' then
|
|
update_font_settings(Font_height+2)
|
|
elseif chord == 'C--' then
|
|
update_font_settings(Font_height-2)
|
|
elseif chord == 'C-0' then
|
|
update_font_settings(20)
|
|
-- mode-specific hotkeys
|
|
elseif Display_settings.palette then
|
|
keychord_press_on_command_palette(chord, key)
|
|
elseif Display_settings.mode == 'normal' then
|
|
if chord == 'C-return' then
|
|
Display_settings.palette = {command='', alternative_index=1, candidates = initial_candidates()}
|
|
elseif chord == 'C-f' then
|
|
command.commence_find_on_surface()
|
|
elseif Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
if pane.editable then
|
|
if chord == 'C-e' then
|
|
--? profile.stop()
|
|
--? print(profile.report())
|
|
command.exit_editing()
|
|
elseif pane.cursor_x == nil then
|
|
-- ignore if cursor is not visible on screen
|
|
assert(pane.cursor_y == nil, 'cursor x is not set but y is set')
|
|
panning_keychord_press(chord, key)
|
|
plan_draw()
|
|
else
|
|
local old_top = {line=pane.screen_top1.line, pos=pane.screen_top1.pos}
|
|
edit.keychord_press(pane, chord, key)
|
|
--? print_and_log(('run.keychord_press A %s %d,%d'):format(chord, Display_settings.x, Display_settings.y))
|
|
if chord == 'pagedown' or chord == 'S-pagedown' then
|
|
pan_surface_to_screen_top_of_cursor_pane()
|
|
elseif chord == 'backspace' or chord == 'up' or chord == 'left' or chord == 'pageup' then
|
|
bring_cursor_of_cursor_pane_in_view('up')
|
|
else
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
end
|
|
pane._height = nil
|
|
--? print_and_log(('run.keychord_press Z %s %d,%d'):format(chord, Display_settings.x, Display_settings.y))
|
|
plan_draw()
|
|
end
|
|
else
|
|
keychord_press_in_normal_mode_with_immutable_pane(pane, chord, key)
|
|
plan_draw()
|
|
end
|
|
end
|
|
end
|
|
elseif Display_settings.mode == 'search' then
|
|
keychord_press_in_search_mode(chord, key)
|
|
elseif Display_settings.mode == 'search_all' then
|
|
keychord_press_in_search_all_mode(chord, key)
|
|
elseif Display_settings.mode == 'searching_all' then
|
|
interrupt_search_all()
|
|
elseif Display_settings.mode == 'maximize' then
|
|
if chord == 'C-return' then
|
|
Display_settings.palette = {command='', alternative_index=1, candidates = initial_candidates()}
|
|
else
|
|
keychord_press_in_maximize_mode(chord, key)
|
|
end
|
|
else
|
|
assert(false, ('pensieve is in an unknown mode %s'):format(Display_settings.mode))
|
|
end
|
|
end
|
|
|
|
function update_font_settings(font_height)
|
|
local column_width_in_ems = Display_settings.column_width / Display_settings.font:getWidth('m')
|
|
Font_height = font_height
|
|
Display_settings.font = love.graphics.newFont(Font_height)
|
|
Line_height = math.floor(font_height*1.3)
|
|
Display_settings.column_width = column_width_in_ems*Display_settings.font:getWidth('m')
|
|
for _,column in ipairs(Surface) do
|
|
for _,pane in ipairs(column) do
|
|
pane.font = Display_settings.font
|
|
pane.font_height = Font_height
|
|
pane.line_height = Line_height
|
|
pane.left = 0
|
|
pane.right = Display_settings.column_width
|
|
end
|
|
end
|
|
clear_all_pane_heights()
|
|
plan_draw()
|
|
end
|
|
|
|
-- Scan all panes, while delegating as much work as possible to lines.love search.
|
|
-- * Text.search_next in lines.love scans from cursor while wrapping around
|
|
-- within the pane, so we need to work around that.
|
|
-- * Each pane's search_term field influences whether the search term at
|
|
-- cursor is highlighted, so we need to manage that as well. At any moment
|
|
-- we want the search_term to be set for at most a single pane.
|
|
--
|
|
-- Side-effect: we perturb the cursor of panes as we scan them.
|
|
function search_next()
|
|
if Cursor_pane.col < 1 then return end
|
|
clear_all_search_terms()
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane == nil then
|
|
return
|
|
end
|
|
--? print('search next', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
local old_cursor_in_cursor_pane = {line=pane.cursor1.line, pos=pane.cursor1.pos}
|
|
-- scan current pane down from cursor
|
|
if search_next_in_pane(pane) then
|
|
--? print('found in same pane', pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
pane.cursor1 = old_cursor_in_cursor_pane
|
|
-- scan current column down from current pane
|
|
for current_pane_index=Cursor_pane.row+1,#Surface[Cursor_pane.col] do
|
|
local pane = Surface[Cursor_pane.col][current_pane_index]
|
|
pane.cursor1 = {line=1, pos=1}
|
|
edit.check_locs(pane)
|
|
pane.screen_top1 = {line=1, pos=1}
|
|
if search_next_in_pane(pane) then
|
|
Cursor_pane.row = current_pane_index
|
|
--? print('found in same column', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
end
|
|
local current_column_index = 1 + Cursor_pane.col%#Surface -- (i+1)%#Surface in the presence of 1-indexing
|
|
-- scan columns past current, looping around
|
|
while true do
|
|
for current_pane_index,pane in ipairs(Surface[current_column_index]) do
|
|
pane.cursor1 = {line=1, pos=1}
|
|
edit.check_locs(pane)
|
|
pane.screen_top1 = {line=1, pos=1}
|
|
if search_next_in_pane(pane) then
|
|
Cursor_pane = {col=current_column_index, row=current_pane_index}
|
|
--? print('found', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
end
|
|
-- loop update
|
|
current_column_index = 1 + current_column_index%#Surface -- i = (i+1)%#Surface in the presence of 1-indexing
|
|
-- termination check
|
|
if current_column_index == Cursor_pane.col then
|
|
break
|
|
end
|
|
end
|
|
-- scan current column until current pane
|
|
for current_pane_index=1,Cursor_pane.row-1 do
|
|
local pane = Surface[Cursor_pane.col][current_pane_index]
|
|
if search_next_in_pane(pane) then
|
|
Cursor_pane.row = current_pane_index
|
|
--? print('found in same column', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
end
|
|
-- finally, scan the cursor pane until the cursor
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
local old_cursor = pane.cursor1
|
|
pane.cursor1 = {line=1, pos=1}
|
|
edit.check_locs(pane)
|
|
pane.screen_top1 = {line=1, pos=1}
|
|
if search_next_in_pane(pane) then
|
|
if Text.lt1(pane.cursor1, old_cursor) then
|
|
return
|
|
end
|
|
end
|
|
-- nothing found
|
|
pane.cursor1 = old_cursor_in_cursor_pane
|
|
end
|
|
|
|
-- returns whether it found an occurrence
|
|
function search_next_in_pane(pane)
|
|
pane.search_term = Display_settings.search_term
|
|
pane.search_backup = {cursor={line=pane.cursor1.line, pos=pane.cursor1.pos}, screen_top={line=pane.screen_top1.line, pos=pane.screen_top1.pos}}
|
|
for i=1,#pane.lines do
|
|
if pane.line_cache[i] == nil then
|
|
pane.line_cache[i] = {}
|
|
end
|
|
end
|
|
if Text.search_next(pane) then
|
|
if Text.le1(pane.search_backup.cursor, pane.cursor1) then
|
|
-- select this occurrence
|
|
return true
|
|
end
|
|
-- Otherwise cursor wrapped around. Skip this pane.
|
|
end
|
|
-- Clean up this pane before moving on to the next one.
|
|
pane.search_term = nil
|
|
pane.cursor1.line = pane.search_backup.cursor.line
|
|
pane.cursor1.pos = pane.search_backup.cursor.pos
|
|
pane.screen_top1.line = pane.search_backup.screen_top.line
|
|
pane.screen_top1.pos = pane.search_backup.screen_top.pos
|
|
pane.search_backup = nil
|
|
end
|
|
|
|
-- Scan all panes, while delegating as much work as possible to lines.love search.
|
|
function search_previous()
|
|
if Cursor_pane.col < 1 then return end
|
|
clear_all_search_terms()
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane == nil then
|
|
return
|
|
end
|
|
--? print('search previous', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
local old_cursor_in_cursor_pane = {line=pane.cursor1.line, pos=pane.cursor1.pos}
|
|
-- scan current pane up from cursor
|
|
if search_previous_in_pane(pane) then
|
|
--? print('found in same pane', pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
pane.cursor1 = old_cursor_in_cursor_pane
|
|
-- scan current column down from current pane
|
|
for current_pane_index=Cursor_pane.row-1,1,-1 do
|
|
local pane = Surface[Cursor_pane.col][current_pane_index]
|
|
pane.cursor1 = edit.final_cursor(pane)
|
|
if search_previous_in_pane(pane) then
|
|
Cursor_pane.row = current_pane_index
|
|
--? print('found in same column', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
end
|
|
local current_column_index = 1 + (Cursor_pane.col-2)%#Surface -- (i-1)%#Surface in the presence of 1-indexing
|
|
-- scan columns past current, looping around
|
|
while true do
|
|
for current_pane_index = #Surface[current_column_index],1,-1 do
|
|
local pane = Surface[current_column_index][current_pane_index]
|
|
pane.cursor1 = edit.final_cursor(pane)
|
|
if search_previous_in_pane(pane) then
|
|
Cursor_pane = {col=current_column_index, row=current_pane_index}
|
|
--? print('found', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
end
|
|
-- loop update
|
|
current_column_index = 1 + (current_column_index-2)%#Surface -- i = (i-1)%#Surface in the presence of 1-indexing
|
|
-- termination check
|
|
if current_column_index == Cursor_pane.col then
|
|
break
|
|
end
|
|
end
|
|
-- scan current column from bottom current pane
|
|
for current_pane_index=#Surface[Cursor_pane.col],Cursor_pane.row+1,-1 do
|
|
--? print('same column', current_pane_index)
|
|
local pane = Surface[Cursor_pane.col][current_pane_index]
|
|
if search_previous_in_pane(pane) then
|
|
Cursor_pane.row = current_pane_index
|
|
--? print('found in same column', Cursor_pane.col, Cursor_pane.row, pane.cursor1.line, pane.cursor1.pos)
|
|
return
|
|
end
|
|
end
|
|
-- finally, scan the cursor pane from bottom until cursor
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
local old_cursor = pane.cursor1
|
|
pane.cursor1 = edit.final_cursor(pane)
|
|
if search_previous_in_pane(pane) then
|
|
if Text.lt1(old_cursor, pane.cursor1) then
|
|
return
|
|
end
|
|
end
|
|
-- nothing found
|
|
pane.cursor1 = old_cursor_in_cursor_pane
|
|
end
|
|
|
|
-- returns whether it found an occurrence
|
|
function search_previous_in_pane(pane)
|
|
pane.search_term = Display_settings.search_term
|
|
pane.search_backup = {cursor={line=pane.cursor1.line, pos=pane.cursor1.pos}, screen_top={line=pane.screen_top1.line, pos=pane.screen_top1.pos}}
|
|
for i=1,#pane.lines do
|
|
if pane.line_cache[i] == nil then
|
|
pane.line_cache[i] = {}
|
|
end
|
|
end
|
|
if Text.search_previous(pane) then
|
|
if Text.lt1(pane.cursor1, pane.search_backup.cursor) then
|
|
-- select this occurrence
|
|
return true
|
|
end
|
|
-- Otherwise cursor wrapped around. Skip this pane.
|
|
end
|
|
-- Clean up this pane before moving on to the previous one.
|
|
pane.search_term = nil
|
|
pane.cursor1.line = pane.search_backup.cursor.line
|
|
pane.cursor1.pos = pane.search_backup.cursor.pos
|
|
pane.screen_top1.line = pane.search_backup.screen_top.line
|
|
pane.screen_top1.pos = pane.search_backup.screen_top.pos
|
|
pane.search_backup = nil
|
|
end
|
|
|
|
function pan_surface_to_screen_top_of_cursor_pane()
|
|
if Cursor_pane.col < 1 then
|
|
return
|
|
end
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane == nil then
|
|
return
|
|
end
|
|
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) + y_of_schema1(pane, pane.screen_top1)
|
|
end
|
|
|
|
function bring_cursor_of_cursor_pane_in_view(dir)
|
|
if Cursor_pane.col < 1 then
|
|
return
|
|
end
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane == nil then
|
|
return
|
|
end
|
|
local left_edge_sx = left_edge_sx(Cursor_pane.col)
|
|
local cursor_sx = left_edge_sx + Text.x_of_schema1(pane, pane.cursor1)
|
|
local vertically_ok = cursor_sx > Display_settings.x and cursor_sx < Display_settings.x + App.screen.width - pane.font:getWidth('m')
|
|
local cursor_sy = up_edge_sy(Cursor_pane.col, Cursor_pane.row) + y_of_schema1(pane, pane.cursor1)
|
|
local horizontally_ok = cursor_sy > Display_settings.y and cursor_sy < Display_settings.y + App.screen.height - Header_height - 2*Line_height -- account for search bar along the bottom
|
|
if vertically_ok and horizontally_ok then
|
|
return
|
|
end
|
|
if dir == 'up' then
|
|
if not vertically_ok then
|
|
Display_settings.x = left_edge_sx - Margin_left - Padding_horizontal
|
|
end
|
|
if not horizontally_ok then
|
|
Display_settings.y = cursor_sy - 3*Line_height
|
|
end
|
|
elseif dir == 'down' then
|
|
if not vertically_ok then
|
|
Display_settings.x = left_edge_sx + Display_settings.column_width + Margin_right + Padding_horizontal - App.screen.width
|
|
end
|
|
if not horizontally_ok then
|
|
--? print('subtract', App.screen.height, App.screen.height-Header_height)
|
|
Display_settings.y = cursor_sy + Text.search_bar_height(pane) - (App.screen.height - Header_height)
|
|
-- Bah, temporarily giving up on debugging.
|
|
Display_settings.y = Display_settings.y + Line_height
|
|
assert(App.screen.height - (cursor_sy-Display_settings.y) > 1.5*Line_height, 'ugh, ancient bug is back: panning the viewport when cursor falls off')
|
|
end
|
|
else
|
|
assert(false, ('unknown dir %s'):format(dir))
|
|
end
|
|
Display_settings.x = math.max(Display_settings.x, 0)
|
|
Display_settings.y = math.max(Display_settings.y, 0)
|
|
end
|
|
|
|
function clear_all_search_terms()
|
|
for col,column in ipairs(Surface) do
|
|
for row,pane in ipairs(column) do
|
|
pane.search_term = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function keychord_press_in_maximize_mode(chord, key)
|
|
if Cursor_pane.col < 1 then
|
|
print_and_log('keychord_press (maximized): no current note to edit')
|
|
return
|
|
end
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane == nil then
|
|
print_and_log('keychord_press (maximized): no current note to edit')
|
|
return
|
|
end
|
|
if pane.editable then
|
|
if chord == 'C-e' then
|
|
command.exit_editing()
|
|
else
|
|
edit.keychord_press(pane, chord, key)
|
|
end
|
|
else
|
|
if chord == 'C-e' then
|
|
command.edit_note()
|
|
elseif chord == 'C-c' then
|
|
edit.keychord_press(pane, chord, key)
|
|
end
|
|
end
|
|
end
|
|
|
|
function keychord_press_in_normal_mode_with_immutable_pane(pane, chord, key)
|
|
-- return if no part of cursor pane is visible
|
|
local left_sx = left_edge_sx(Cursor_pane.col)
|
|
if not should_show_column(left_sx) then
|
|
panning_keychord_press(chord, key)
|
|
return
|
|
end
|
|
local up_sy = up_edge_sy(Cursor_pane.col, Cursor_pane.row)
|
|
if not should_show_pane(pane, up_sy) then
|
|
panning_keychord_press(chord, key)
|
|
return
|
|
end
|
|
if chord == 'C-e' then
|
|
command.edit_note()
|
|
profile.start()
|
|
elseif chord == 'C-c' then
|
|
edit.keychord_press(pane, chord, key)
|
|
else
|
|
panning_keychord_press(chord, key)
|
|
end
|
|
end
|
|
|
|
-- y offset of a given (line, pos)
|
|
function y_of_schema1(pane, loc)
|
|
--? print(('updating viewport y; cursor pane starts at %d; screen top is at %d,%d'):format(result, loc.line, loc.pos))
|
|
local result = 0
|
|
if pane.title then
|
|
result = result + 5+Line_height+5
|
|
end
|
|
result = result + Margin_above
|
|
if loc.line == 1 and loc.pos == 1 then
|
|
return result
|
|
end
|
|
for i=1,loc.line-1 do
|
|
--? print('', 'd', i, result)
|
|
Text.populate_screen_line_starting_pos(pane, i)
|
|
--? print('', '', #pane.line_cache[i].screen_line_starting_pos, pane.left, pane.right)
|
|
result = result + line_height(pane, i, pane.left, pane.right)
|
|
end
|
|
if pane.lines[loc.line].mode == 'text' then
|
|
Text.populate_screen_line_starting_pos(pane, loc.line)
|
|
for i,screen_line_starting_pos in ipairs(pane.line_cache[loc.line].screen_line_starting_pos) do
|
|
if screen_line_starting_pos >= loc.pos then
|
|
break
|
|
end
|
|
result = result + Line_height
|
|
end
|
|
end
|
|
--? print(('viewport at %d'):format(result))
|
|
return result
|
|
end
|
|
|
|
function keychord_press_in_search_mode(chord, key)
|
|
if chord == 'escape' then
|
|
Display_settings.mode = 'normal'
|
|
clear_all_search_terms()
|
|
dehydrate_all_panes()
|
|
-- go back to old viewport
|
|
--? print('esc; exiting search mode')
|
|
Display_settings.x = Display_settings.search_backup_x
|
|
Display_settings.y = Display_settings.search_backup_y
|
|
Cursor_pane = Display_settings.search_backup_cursor_pane
|
|
-- don't forget search text
|
|
elseif chord == 'return' then
|
|
Display_settings.mode = 'normal'
|
|
clear_all_search_terms()
|
|
dehydrate_all_panes()
|
|
-- forget old viewport
|
|
--? print('return; exiting search mode')
|
|
Display_settings.search_backup_x = nil
|
|
Display_settings.search_backup_y = nil
|
|
Display_settings.search_backup_cursor_pane = nil
|
|
-- don't forget search text
|
|
elseif chord == 'backspace' then
|
|
local len = utf8.len(Display_settings.search_term)
|
|
local byte_offset = Text.offset(Display_settings.search_term, len)
|
|
Display_settings.search_term = string.sub(Display_settings.search_term, 1, byte_offset-1)
|
|
-- reset search state
|
|
clear_selections()
|
|
Display_settings.x = Display_settings.search_backup_x
|
|
Display_settings.y = Display_settings.search_backup_y
|
|
Cursor_pane = Display_settings.search_backup_cursor_pane
|
|
-- search again
|
|
search_next()
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
Surface.cursor_on_screen_check = true
|
|
plan_draw()
|
|
--? print('backspace; search term is now', Display_settings.search_term)
|
|
elseif chord == 'C-v' then
|
|
Display_settings.search_term = Display_settings.search_term..App.get_clipboard()
|
|
-- reset search state
|
|
clear_selections()
|
|
Display_settings.x = Display_settings.search_backup_x
|
|
Display_settings.y = Display_settings.search_backup_y
|
|
Cursor_pane = Display_settings.search_backup_cursor_pane
|
|
-- search again
|
|
search_next()
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
Surface.cursor_on_screen_check = true
|
|
plan_draw()
|
|
--? print('paste; search term is now', Display_settings.search_term)
|
|
elseif chord == 'up' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
search_previous()
|
|
bring_cursor_of_cursor_pane_in_view('up')
|
|
Surface.cursor_on_screen_check = true
|
|
plan_draw()
|
|
end
|
|
end
|
|
elseif chord == 'down' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
pane.cursor1.pos = pane.cursor1.pos+1
|
|
search_next()
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
Surface.cursor_on_screen_check = true
|
|
plan_draw()
|
|
end
|
|
end
|
|
-- things from normal mode we still want
|
|
elseif chord == 'C-c' then
|
|
if Cursor_pane.col >= 1 then
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane then
|
|
edit.keychord_press(pane, chord, key)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function keychord_press_in_search_all_mode(chord, key)
|
|
if chord == 'escape' then
|
|
Display_settings.mode = 'normal'
|
|
-- don't forget search text
|
|
Display_settings.search_all_progress = nil
|
|
elseif chord == 'return' then
|
|
finalize_search_all_pane()
|
|
add_search_all_pane_to_right_of_cursor()
|
|
Display_settings.mode = 'searching_all'
|
|
plan_draw()
|
|
elseif chord == 'backspace' then
|
|
local len = utf8.len(Display_settings.search_all_query)
|
|
local byte_offset = Text.offset(Display_settings.search_all_query, len)
|
|
Display_settings.search_all_query = string.sub(Display_settings.search_all_query, 1, byte_offset-1)
|
|
--? print('backspace; search_all term is now', Display_settings.search_all_query)
|
|
elseif chord == 'C-v' then
|
|
Display_settings.search_all_query = Display_settings.search_all_query..App.get_clipboard()
|
|
--? print('paste; search_all term is now', Display_settings.search_all_query)
|
|
end
|
|
end
|
|
|
|
-- return (line, pos) of the screen line starting near a given y offset, and
|
|
-- y_offset remaining after the calculation
|
|
-- invariants:
|
|
-- - 0 <= y_offset <= Line_height if line is text
|
|
-- - let loc, y_offset = schema1_of_y(pane, y)
|
|
-- y - y_offset == y_of_schema1(pane, loc)
|
|
function schema1_of_y(pane, y)
|
|
assert(y >= 0, 'something is at negative y on the surface')
|
|
local y_offset = y
|
|
for i=1,#pane.lines do
|
|
--? print('--', y_offset)
|
|
Text.populate_screen_line_starting_pos(pane, i)
|
|
local height = line_height(pane, i, pane.left, pane.right)
|
|
if y_offset < height then
|
|
local line = pane.lines[i]
|
|
if line.mode ~= 'text' then
|
|
return {line=i, pos=1}, y_offset
|
|
else
|
|
local nlines = math.floor(y_offset/pane.line_height)
|
|
--? print(y_offset, pane.line_height, nlines)
|
|
assert(nlines >= 0 and nlines < #pane.line_cache[i].screen_line_starting_pos, 'error in mapping y coordinate to schema-1')
|
|
local pos = pane.line_cache[i].screen_line_starting_pos[nlines+1] -- switch to 1-indexing
|
|
y_offset = y_offset - nlines*pane.line_height
|
|
return {line=i, pos=pos}, y_offset
|
|
end
|
|
end
|
|
y_offset = y_offset - height
|
|
end
|
|
-- y is below the pane
|
|
return {line=#pane.lines+1, pos=1}, y_offset
|
|
end
|
|
|
|
function line_height(pane, line_index, left, right)
|
|
local line = pane.lines[line_index]
|
|
local line_cache = pane.line_cache[line_index]
|
|
if line.mode == 'text' then
|
|
return pane.line_height*#line_cache.screen_line_starting_pos
|
|
else
|
|
return Drawing.pixels(line.h, right-left) + Drawing_padding_height
|
|
end
|
|
end
|
|
|
|
function stop_editing_all()
|
|
local edit_count = 0
|
|
for _,column in ipairs(Surface) do
|
|
for _,pane in ipairs(column) do
|
|
if pane.editable then
|
|
stop_editing(pane)
|
|
edit_count = edit_count+1
|
|
end
|
|
end
|
|
end
|
|
assert(edit_count <= 1, 'multiple panes were editable')
|
|
end
|
|
|
|
function stop_editing(pane)
|
|
log(2, 'stop_editing: '..pane.id)
|
|
edit.quit(pane)
|
|
-- save symmetric links
|
|
for rel,x in pairs(Links[pane.id]) do
|
|
process_all_links(x, function(target_id)
|
|
log(2, 'stop_editing '..pane.id..': saving links for '..target_id)
|
|
if Links[target_id] then
|
|
save_links(target_id)
|
|
end
|
|
end)
|
|
end
|
|
if Display_settings.mode ~= 'maximize' then
|
|
refresh_panes(pane)
|
|
end
|
|
pane.editable = false
|
|
end
|
|
|
|
function panning_keychord_press(chord, key)
|
|
if chord == 'up' then
|
|
pan_up()
|
|
elseif chord == 'down' then
|
|
pan_down()
|
|
elseif chord == 'left' then
|
|
pan_left()
|
|
elseif chord == 'right' then
|
|
pan_right()
|
|
elseif chord == 'pageup' or chord == 'S-up' then
|
|
Display_settings.y = math.max(Display_settings.y - App.screen.height + Line_height*2, 0)
|
|
local up_sy = up_edge_sy(Cursor_pane.col, Cursor_pane.row)
|
|
local up_py = up_sy - Display_settings.y
|
|
if up_py > 2/3*App.screen.height then
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], row(Cursor_pane.col, Display_settings.y + App.screen.height/3))
|
|
end
|
|
elseif chord == 'pagedown' or chord == 'S-down' then
|
|
--? print('pagedown')
|
|
local visible_column_max_y = most(column_height, visible_columns())
|
|
if visible_column_max_y - Display_settings.y > App.screen.height then
|
|
--? print('updating viewport')
|
|
Display_settings.y = Display_settings.y + App.screen.height - Line_height*2
|
|
end
|
|
local down_sx = down_edge_sx(Cursor_pane.col, Cursor_pane.row)
|
|
local down_px = down_sx - Display_settings.y
|
|
if down_px < App.screen.height/3 then
|
|
--? print('updating row')
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], row(Cursor_pane.col, Display_settings.y + App.screen.height/3))
|
|
--? print('=>', Cursor_pane.row)
|
|
end
|
|
elseif chord == 'S-left' then
|
|
Display_settings.x = math.max(Display_settings.x - Margin_left - Display_settings.column_width - Margin_right - Padding_horizontal, 0)
|
|
local left_sx = left_edge_sx(Cursor_pane.col)
|
|
local left_px = left_sx - Display_settings.x
|
|
if left_px > App.screen.width - Margin_right - Display_settings.column_width/2 then
|
|
Cursor_pane.col = math.min(#Surface, col(Display_settings.x + App.screen.width - Margin_right - Display_settings.column_width/2))
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], Cursor_pane.row)
|
|
end
|
|
elseif chord == 'S-right' then
|
|
if Display_settings.x < (#Surface-1) * (Padding_horizontal + Margin_left + Display_settings.column_width + Margin_right) then
|
|
Display_settings.x = Display_settings.x + Margin_left + Display_settings.column_width + Margin_right + Padding_horizontal
|
|
local right_sx = left_edge_sx(Cursor_pane.col) + Display_settings.column_width
|
|
local right_px = right_sx - Display_settings.x
|
|
if right_px < Margin_left + Display_settings.column_width/2 then
|
|
Cursor_pane.col = math.min(#Surface, col(Display_settings.x + Margin_left + Display_settings.column_width/2))
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], Cursor_pane.row)
|
|
end
|
|
end
|
|
elseif chord == 'C-down' then
|
|
command.down_one_pane()
|
|
elseif chord == 'C-up' then
|
|
command.up_one_pane()
|
|
elseif chord == 'C-end' then
|
|
command.bottom_pane_of_column()
|
|
elseif chord == 'C-home' then
|
|
command.top_pane_of_column()
|
|
end
|
|
--? print('after', Cursor_pane.col, Cursor_pane.row)
|
|
end
|
|
|
|
function pan_up()
|
|
Display_settings.y = math.max(Display_settings.y - Pan_step, 0)
|
|
local up_sy = up_edge_sy(Cursor_pane.col, Cursor_pane.row)
|
|
local up_py = up_sy - Display_settings.y
|
|
if up_py > 2/3*App.screen.height then
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], row(Cursor_pane.col, Display_settings.y + App.screen.height/3))
|
|
end
|
|
end
|
|
|
|
function pan_down()
|
|
local visible_column_max_y = most(column_height, visible_columns())
|
|
if visible_column_max_y - Display_settings.y > App.screen.height/2 then
|
|
Display_settings.y = Display_settings.y + Pan_step
|
|
end
|
|
local down_sx = down_edge_sx(Cursor_pane.col, Cursor_pane.row)
|
|
local down_px = down_sx - Display_settings.y
|
|
if down_px < App.screen.height/3 then
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], row(Cursor_pane.col, Display_settings.y + App.screen.height/3))
|
|
end
|
|
end
|
|
|
|
function pan_left()
|
|
Display_settings.x = math.max(Display_settings.x - Pan_step, 0)
|
|
local left_sx = left_edge_sx(Cursor_pane.col)
|
|
local left_px = left_sx - Display_settings.x
|
|
if left_px > App.screen.width - Margin_right - Display_settings.column_width/2 then
|
|
Cursor_pane.col = math.min(#Surface, col(Display_settings.x + App.screen.width - Margin_right - Display_settings.column_width/2))
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], Cursor_pane.row)
|
|
end
|
|
end
|
|
|
|
function pan_right()
|
|
if Display_settings.x < (#Surface-1) * (Padding_horizontal + Margin_left + Display_settings.column_width + Margin_right) then
|
|
Display_settings.x = Display_settings.x + Pan_step
|
|
end
|
|
local right_sx = left_edge_sx(Cursor_pane.col) + Display_settings.column_width
|
|
local right_px = right_sx - Display_settings.x
|
|
if right_px < Margin_left + Display_settings.column_width/2 then
|
|
Cursor_pane.col = math.min(#Surface, col(Display_settings.x + Margin_left + Display_settings.column_width/2))
|
|
Cursor_pane.row = math.min(#Surface[Cursor_pane.col], Cursor_pane.row)
|
|
end
|
|
end
|
|
|
|
function visible_columns()
|
|
local result = {}
|
|
local col = col(Display_settings.x)
|
|
local x = left_edge_sx(col) - Display_settings.x
|
|
while col <= #Surface do
|
|
x = x + Padding_horizontal
|
|
table.insert(result, col)
|
|
x = x + Margin_left + Display_settings.column_width + Margin_right + Padding_horizontal
|
|
if x > App.screen.width then
|
|
break
|
|
end
|
|
col = col+1
|
|
end
|
|
return result
|
|
end
|
|
|
|
function refresh_panes(pane)
|
|
--? print('refreshing')
|
|
for x,col in ipairs(Surface) do
|
|
for y,p in ipairs(col) do
|
|
if p.id == pane.id then
|
|
if p ~= pane then
|
|
Surface[x][y] = load_pane(pane.id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
plan_draw()
|
|
end
|
|
|
|
-- The basic text editor buffer (editor State) in edit.lua and text.lua
|
|
-- assumes there's always an element in line_cache for every line. However,
|
|
-- this can be a lot of overhead when we have hundreds of buffers. So pensieve
|
|
-- needs to violate this assumption while hiding it from buffer helpers.
|
|
function dehydrate_all_panes()
|
|
for x,col in ipairs(Surface) do
|
|
for y,p in ipairs(col) do
|
|
p._height = nil
|
|
p.line_cache = {}
|
|
end
|
|
end
|
|
plan_draw()
|
|
end
|
|
|
|
function rehydrate_pane(pane)
|
|
for i=1,#pane.lines do
|
|
-- don't clobber starty/startpos if they exist
|
|
if pane.line_cache[i] == nil then
|
|
pane.line_cache[i] = {}
|
|
end
|
|
Text.clear_screen_line_cache(pane, i)
|
|
end
|
|
end
|
|
|
|
function run.key_release(key, scancode)
|
|
if Directory_error then return end
|
|
--? print('key release', key)
|
|
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
|
|
if Cursor_pane.col < 1 then
|
|
return
|
|
end
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
if pane and pane.editable then
|
|
edit.key_release(pane, key, scancode)
|
|
end
|
|
end
|
|
|
|
function clear_all_pane_heights()
|
|
for _,column in ipairs(Surface) do
|
|
for _,pane in ipairs(column) do
|
|
pane._height = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- convert x surface pixel coordinate into column index
|
|
function col(x)
|
|
return 1 + math.floor(x / (Padding_horizontal + Margin_left + Display_settings.column_width + Margin_right))
|
|
end
|
|
|
|
-- col is 1-indexed
|
|
-- returns x surface pixel coordinate of left edge of column col
|
|
function left_edge_sx(col)
|
|
return (col-1)*(Padding_horizontal + Margin_left + Display_settings.column_width + Margin_right) + Padding_horizontal + Margin_left
|
|
end
|
|
|
|
function row(col, y)
|
|
local sy = Padding_vertical
|
|
for i,pane in ipairs(Surface[col]) do
|
|
--? print('', i, y, sy, next_sy)
|
|
local next_sy = sy + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
if next_sy > y then
|
|
return i
|
|
end
|
|
sy = next_sy
|
|
end
|
|
return #Surface[col]
|
|
end
|
|
|
|
function up_edge_sy(col, row)
|
|
local result = Padding_vertical
|
|
for i=1,row-1 do
|
|
local pane = Surface[col][i]
|
|
result = result + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
end
|
|
return result
|
|
end
|
|
|
|
function down_edge_sx(col, row)
|
|
local result = Padding_vertical
|
|
for i=1,row do
|
|
local pane = Surface[col][i]
|
|
result = result + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
end
|
|
return result - Padding_vertical
|
|
end
|
|
|
|
function column_height(col)
|
|
local result = Padding_vertical
|
|
for pane_index, pane in ipairs(Surface[col]) do
|
|
result = result + Margin_above + height(pane) + Margin_below + Padding_vertical
|
|
end
|
|
return result
|
|
end
|
|
|
|
function most(f, arr)
|
|
local result = nil
|
|
for _,x in ipairs(arr) do
|
|
local curr = f(x)
|
|
if result == nil or result < curr then
|
|
result = curr
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function width(s)
|
|
return love.graphics.getFont():getWidth(s)
|
|
end
|
|
|
|
function num_panes()
|
|
local result = 0
|
|
for _,column in ipairs(Surface) do
|
|
result = result+#column
|
|
end
|
|
return result
|
|
end
|