crosstable.love/main.lua

272 lines
7.9 KiB
Lua
Raw Normal View History

utf8 = require 'utf8'
json = require 'json'
require 'app'
require 'test'
2022-11-27 22:06:11 +00:00
require 'live'
require 'keychord'
require 'button'
-- delegate most business logic to a layer that can be reused by other projects
require 'edit'
Editor_state = {}
2022-06-07 20:35:56 +00:00
-- called both in tests and real run
function App.initialize_globals()
2023-12-07 08:51:29 +00:00
Supported_versions = {'11.5', '11.4', '11.3', '11.2', '11.1', '11.0', '12.0'} -- put the recommended version first
-- Available modes: run, error
if Mode == nil then -- might have already been initialized elsewhere
Mode = 'run'
end
2023-12-07 08:51:29 +00:00
Error_count = 0
-- tests currently mostly clear their own state
-- blinking cursor
Cursor_time = 0
2022-07-01 05:46:45 +00:00
-- for hysteresis in a few places
Current_time = 0
Last_focus_time = 0 -- https://love2d.org/forums/viewtopic.php?p=249700
Last_resize_time = 0
2022-07-01 05:46:45 +00:00
end
2022-05-02 04:55:57 +00:00
-- called only for real run
function App.initialize(arg)
love.keyboard.setTextInput(true) -- bring up keyboard on touch screen
love.keyboard.setKeyRepeat(true)
2022-06-07 20:24:43 +00:00
2022-11-27 22:06:11 +00:00
Editor_state = nil -- not used outside editor tests
love.graphics.setBackgroundColor(1,1,1)
if love.filesystem.getInfo('config') then
load_settings()
else
initialize_default_settings()
end
2023-03-25 18:41:52 +00:00
-- TODO: app-specific stuff goes here
2023-03-25 18:41:52 +00:00
-- keep a few blank lines around: https://merveilles.town/@akkartik/110084833821965708
love.window.setTitle('TODO')
2023-03-25 18:41:52 +00:00
if on.initialize then on.initialize(arg) end
if rawget(_G, 'jit') then
jit.off()
jit.flush()
end
check_love_version()
end
function check_love_version()
2023-12-07 08:51:29 +00:00
-- we'll reuse error mode on load for an initial version check
if array.find(Supported_versions, Version) == nil then
2023-12-07 08:51:29 +00:00
Mode = 'error'
if Error_message == nil then Error_message = '' end
Error_message = ("This app hasn't been tested with LÖVE version %s; please use version %s if you run into errors. Press a key to try recovering.\n\n%s"):format(Version, Supported_versions[1], Error_message)
print(Error_message)
-- continue initializing everything; hopefully we won't have errors during initialization
end
end
2023-06-04 22:25:46 +00:00
function print_and_log(s)
print(s)
log(3, s)
end
2023-01-02 01:52:06 +00:00
function love.quit()
if on.quit then on.quit() end
love.filesystem.write('config', json.encode(settings()))
end
function restart()
if on.quit then on.quit() end
love.filesystem.write('config', json.encode(settings()))
load_settings()
if on.initialize then on.initialize() end
end
function settings()
local x, y, displayindex = App.screen.position()
return {
x=x, y=y, displayindex=displayindex,
width=App.screen.width, height=App.screen.height,
2023-01-02 02:22:52 +00:00
app = on.save_settings and on.save_settings(),
}
2023-01-02 01:52:06 +00:00
end
2023-01-02 01:50:41 +00:00
function load_settings()
local settings = json.decode(love.filesystem.read('config'))
2023-07-11 01:41:23 +00:00
-- 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
2023-03-19 07:04:53 +00:00
App.screen.resize(App.screen.width, App.screen.height, App.screen.flags)
2023-07-11 01:41:23 +00:00
set_window_position_from_settings(settings)
2023-07-11 02:07:02 +00:00
if on.load_settings then on.load_settings(settings.app) end
end
2023-07-11 01:41:23 +00:00
function 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)
2023-01-02 01:51:35 +00:00
else
2023-07-11 01:41:23 +00:00
App.screen.move(settings.x, settings.y, settings.displayindex)
2023-01-02 01:51:35 +00:00
end
end
function initialize_default_settings()
local font_height = 20
love.graphics.setFont(love.graphics.newFont(font_height))
initialize_window_geometry()
end
2023-07-11 18:11:48 +00:00
function initialize_window_geometry()
2023-07-11 01:41:23 +00:00
-- 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.
2023-03-19 07:04:53 +00:00
App.screen.width, App.screen.height, App.screen.flags = App.screen.size()
App.screen.flags.resizable = true
2023-03-19 07:04:53 +00:00
App.screen.resize(App.screen.width, App.screen.height, App.screen.flags)
end
function App.resize(w, h)
--? print(("Window resized to width: %d and height: %d."):format(w, h))
App.screen.width, App.screen.height = w, h
2022-11-04 04:46:20 +00:00
Last_resize_time = Current_time
2022-11-27 22:06:11 +00:00
if on.resize then on.resize(w,h) end
end
function App.filedropped(file)
2023-01-20 22:18:04 +00:00
if on.file_drop then on.file_drop(file) end
end
function App.draw()
if Mode == 'error' then
love.graphics.setColor(0,0,1)
love.graphics.rectangle('fill', 0,0, App.screen.width, App.screen.height)
love.graphics.setColor(1,1,1)
love.graphics.printf(Error_message, 40,40, 600)
2023-04-20 02:45:17 +00:00
return
end
2022-11-27 22:06:11 +00:00
if on.draw then on.draw() end
2022-05-02 04:55:57 +00:00
end
function App.update(dt)
Current_time = Current_time + dt
-- some hysteresis while resizing
if Current_time < Last_resize_time + 0.1 then
2022-07-25 22:23:01 +00:00
return
end
Cursor_time = Cursor_time + dt
-- listen for commands in both 'error' and 'run' modes
live.update(dt)
if Mode == 'run' then
if on.update then on.update(dt) end
end
end
function App.mousepressed(x,y, mouse_button)
if Mode == 'error' then return end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
2022-12-24 04:30:35 +00:00
if on.mouse_press then on.mouse_press(x,y, mouse_button) end
end
function App.mousereleased(x,y, mouse_button)
if Mode == 'error' then return end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
2022-12-24 04:30:35 +00:00
if on.mouse_release then on.mouse_release(x,y, mouse_button) end
end
2023-12-26 16:28:23 +00:00
function App.mousemoved(x,y, dx,dy, istouch)
if on.mouse_move then on.mouse_move(x,y, dx,dy, istouch) end
end
2023-03-24 04:48:27 +00:00
function App.wheelmoved(dx,dy)
if Mode == 'error' then return end
2023-03-24 04:48:27 +00:00
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
2023-03-24 04:48:42 +00:00
if on.mouse_wheel_move then on.mouse_wheel_move(dx,dy) end
2023-03-24 04:48:27 +00:00
end
function App.focus(in_focus)
if Mode == 'error' then return end
if in_focus then
2022-11-04 04:46:20 +00:00
Last_focus_time = Current_time
end
if in_focus then
love.graphics.setBackgroundColor(1,1,1)
else
love.graphics.setBackgroundColor(0.8,0.8,0.8)
end
2022-11-27 22:06:11 +00:00
if on.focus then on.focus(in_focus) end
end
2022-11-27 22:06:11 +00:00
-- App.keypressed is defined in keychord.lua
function App.keychord_press(chord, key)
if Mode == 'error' then
if chord == 'C-c' then
love.system.setClipboardText(Error_message)
end
return
end
-- ignore events for some time after window in focus (mostly alt-tab)
if Current_time < Last_focus_time + 0.01 then
return
end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
2022-12-24 04:17:16 +00:00
if on.keychord_press then on.keychord_press(chord, key) end
end
function App.textinput(t)
if Mode == 'error' then return end
-- ignore events for some time after window in focus (mostly alt-tab)
if Current_time < Last_focus_time + 0.01 then
return
end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
2022-12-24 04:17:16 +00:00
if on.text_input then on.text_input(t) end
end
function App.keyreleased(key, scancode)
if Mode == 'error' then
fix initialization errors using driver.love Changes inside on.initialize are minefields. Until now, if you made a mistake when modifying on.initialize, you could end up in a situation where the app would fail irrecoverably on the next startup. You'd have to go dig up a text editor to fix it. After this commit, errors in on.initialize wait for commands from driver.love just like any other error. Recovering from errors during initialization is a little different than normal. I don't know how much of initialization completed successfully, so I redo all of it. I think this should be safe; the sorts of things we want to do on startup tend to be idempotent just like the sorts of things we do within an event loop with our existing error handling. Things are still not ideal. Initialization by definition happens only when the app starts up. When you make changes to it, you won't find out about errors until you restart the app[1], which can be much later and a big context switch. But at least you'll be able to fix it in the usual way. Slightly more seamless[2]. One glitch to note: at least on Linux, an app with an initialization error feels "sticky". I can't seem to switch focus away from it using Alt-tab. Hitting F4 on the driver also jarringly brings the client app back in focus when there was an initialization error. But the mouse does work consistently. This feels similar to the issues I find when an app goes unresponsive sometimes. The window manager really wants me to respond to the dialog that it's unresponsive. Still, feels like an improvement. [1] I really need to provide that driver command to restart the app! But there's no room in the menus! I really need a first-class command palette like pensieve.love has! [2] https://lobste.rs/s/idi1wt/open_source_vs_ux
2023-11-17 16:31:41 +00:00
if Redo_initialization then
Redo_initialization = nil
love.run() -- won't actually replace the event loop;
-- we're just running it for its initialization side-effects
else
Mode = 'run'
end
2023-04-20 02:45:17 +00:00
return
end
-- ignore events for some time after window in focus (mostly alt-tab)
if Current_time < Last_focus_time + 0.01 then
return
end
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
2023-01-17 06:46:40 +00:00
if on.key_release then on.key_release(key, scancode) end
end
2023-11-22 20:31:37 +00:00
-- plumb all other handlers through to on.*
for handler_name in pairs(love.handlers) do
if App[handler_name] == nil then
App[handler_name] = function(...)
if on[handler_name] then on[handler_name](...) end
end
end
end