From bd3d3061ae72973fa0ddf5349bf6a9f59b662e28 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 18:01:28 +0400 Subject: [PATCH] Add a module that wraps the dialogs as "blocking" coroutine functions. --- library/lua/gui/script.lua | 151 +++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 library/lua/gui/script.lua diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua new file mode 100644 index 000000000..021a4fa52 --- /dev/null +++ b/library/lua/gui/script.lua @@ -0,0 +1,151 @@ +-- Support for scripted interaction sequences via coroutines. + +local _ENV = mkmodule('gui.script') + +local dlg = require('gui.dialogs') + +--[[ + Example: + + start(function() + sleep(100, 'frames') + print(showYesNoPrompt('test', 'print true?')) + end) +]] + +-- Table of running background scripts. +if not scripts then + scripts = {} + setmetatable(scripts, { __mode = 'k' }) +end + +local function do_resume(inst, ...) + inst.gen = inst.gen + 1 + return (dfhack.saferesume(inst.coro, ...)) +end + +-- Starts a new background script by calling the function. +function start(fn,...) + local coro = coroutine.create(fn) + local inst = { + coro = coro, + gen = 0, + } + scripts[coro] = inst + return do_resume(inst, ...) +end + +-- Checks if called from a background script +function in_script() + return scripts[coroutine.running()] ~= nil +end + +local function getinst() + local inst = scripts[coroutine.running()] + if not inst then + error('Not in a gui script coroutine.') + end + return inst +end + +local function invoke_resume(inst,gen,quiet,...) + local state = coroutine.status(inst.coro) + if state ~= 'suspended' then + if state ~= 'dead' then + dfhack.printerr(debug.traceback('resuming a non-waiting continuation')) + end + elseif inst.gen > gen then + if not quiet then + dfhack.printerr(debug.traceback('resuming an expired continuation')) + end + else + do_resume(inst, ...) + end +end + +-- Returns a callback that resumes the script from wait with given return values +function mkresume(...) + local inst = getinst() + return curry(invoke_resume, inst, inst.gen, false, ...) +end + +-- Like mkresume, but does not complain if already resumed from this wait +function qresume(...) + local inst = getinst() + return curry(invoke_resume, inst, inst.gen, true, ...) +end + +-- Wait until a mkresume callback is called, then return its arguments. +-- Once it returns, all mkresume callbacks created before are invalidated. +function wait() + getinst() -- check that the context is right + return coroutine.yield() +end + +-- Wraps dfhack.timeout for coroutines. +function sleep(time, quantity) + if dfhack.timeout(time, quantity, mkresume()) then + wait() + return true + else + return false + end +end + +-- Some dialog wrappers: + +function showMessage(title, text, tcolor) + dlg.MessageBox{ + frame_title = title, + text = text, + text_pen = tcolor, + on_close = qresume(nil) + }:show() + + return wait() +end + +function showYesNoPrompt(title, text, tcolor) + dlg.MessageBox{ + frame_title = title, + text = text, + text_pen = tcolor, + on_accept = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +function showInputPrompt(title, text, tcolor, input, min_width) + dlg.InputBox{ + frame_title = title, + text = text, + text_pen = tcolor, + input = input, + frame_width = min_width, + on_input = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +function showListPrompt(title, text, tcolor, choices, min_width) + dlg.ListBox{ + frame_title = title, + text = text, + text_pen = tcolor, + choices = choices, + frame_width = min_width, + on_select = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +return _ENV