Implement support for lua-backed viewscreens.

develop
Alexander Gavrilov 2012-08-19 14:27:44 +04:00
parent b8ee52131b
commit 30f71ff510
9 changed files with 734 additions and 24 deletions

@ -1204,6 +1204,124 @@ Constructions module
Returns *true, was_only_planned* if removed; or *false* if none found.
Screen API
----------
The screen module implements support for drawing to the tiled screen of the game.
Note that drawing only has any effect when done from callbacks, so it can only
be feasibly used in the core context.
* ``dfhack.screen.getWindowSize()``
Returns *width, height* of the screen.
* ``dfhack.screen.getMousePos()``
Returns *x,y* of the tile the mouse is over.
* ``dfhack.screen.paintTile(pen,x,y[,char,tile])``
Paints a tile using given parameters. Pen is a table with following possible fields:
``ch``
Provides the ordinary tile character. Can be overridden with the ``char`` function parameter.
``fg``
Foreground color for the ordinary tile. Defaults to 7.
``bg``
Background color for the ordinary tile. Defaults to 0.
``bold``
Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is reset to 7 bits.
Otherwise should be *true/false*.
``tile``
Graphical tile id. Ignored unless [GRAPHICS:YES] in init.txt.
``tile_color = true``
Specifies that the tile should be shaded with *fg/bg*.
``tile_fg, tile_bg``
If specified, overrides *tile_color* and supplies shading colors directly.
Returns *false* if coordinates out of bounds, or other error.
* ``dfhack.screen.paintString(pen,x,y,text)``
Paints the string starting at *x,y*. Uses the string characters
in sequence to override the ``ch`` field of pen.
Returns *true* if painting at least one character succeeded.
* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2)``
Fills the rectangle specified by the coordinates with the given pen.
Returns *true* if painting at least one character succeeded.
* ``dfhack.screen.clear()``
Fills the screen with blank background.
* ``dfhack.screen.invalidate()``
Requests repaint of the screen by setting a flag. Unlike other
functions in this section, this may be used at any time.
In order to actually be able to paint to the screen, it is necessary
to create and register a viewscreen (basically a modal dialog) with
the game. Screens are managed with the following functions:
* ``dfhack.screen.show(screen[,below])``
Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns *true* if success.
* ``dfhack.screen.dismiss(screen)``
Marks the screen to be removed when the game enters its event loop.
* ``dfhack.screen.isDismissed(screen)``
Checks if the screen is already marked for removal.
Apart from a native viewscreen object, these functions accept a table
as a screen. In this case, ``show`` creates a new native viewscreen
that delegates all processing to methods stored in that table.
**NOTE**: Lua-implemented screens are only supported in the core context.
Supported callbacks and fields are:
* ``screen._native``
Initialized by ``show`` with a reference to the backing viewscreen
object, and removed again when the object is deleted.
* ``function screen:onDestroy()``
Called from the destructor when the viewscreen is deleted.
* ``function screen:onRender()``
Called when the viewscreen should paint itself. This is the only context
where the above painting functions work correctly.
If omitted, the screen is cleared; otherwise it should do that itself.
In order to make a see-through dialog, call ``self._native.parent:render()``.
* ``function screen:onIdle()``
Called every frame when the screen is on top of the stack.
* ``function screen:onHelp()``
Called when the help keybinding is activated (usually '?').
* ``function screen:onInput(keys)``
Called when keyboard or mouse events are available.
If any keys are pressed, the keys argument is a table mapping them to *true*.
Note that this refers to logical keybingings computed from real keys via
options; if multiple interpretations exist, the table will contain multiple keys.
If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key.
Internal API
------------

@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" />
<title>DFHack Lua API</title>
<style type="text/css">
@ -351,27 +351,28 @@ ul.auto-toc {
<li><a class="reference internal" href="#burrows-module" id="id23">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id24">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id25">Constructions module</a></li>
<li><a class="reference internal" href="#internal-api" id="id26">Internal API</a></li>
<li><a class="reference internal" href="#screen-api" id="id26">Screen API</a></li>
<li><a class="reference internal" href="#internal-api" id="id27">Internal API</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id27">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id28">Event type</a></li>
<li><a class="reference internal" href="#core-interpreter-context" id="id28">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id29">Event type</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#lua-modules" id="id29">Lua Modules</a><ul>
<li><a class="reference internal" href="#global-environment" id="id30">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id31">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id32">dumper</a></li>
<li><a class="reference internal" href="#lua-modules" id="id30">Lua Modules</a><ul>
<li><a class="reference internal" href="#global-environment" id="id31">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id32">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id33">dumper</a></li>
</ul>
</li>
<li><a class="reference internal" href="#plugins" id="id33">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id34">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id35">sort</a></li>
<li><a class="reference internal" href="#plugins" id="id34">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id35">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id36">sort</a></li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id36">Scripts</a></li>
<li><a class="reference internal" href="#scripts" id="id37">Scripts</a></li>
</ul>
</div>
<p>The current version of DFHack has extensive support for
@ -1385,8 +1386,114 @@ Returns <em>true, was_only_planned</em> if removed; or <em>false</em> if none fo
</li>
</ul>
</div>
<div class="section" id="screen-api">
<h3><a class="toc-backref" href="#id26">Screen API</a></h3>
<p>The screen module implements support for drawing to the tiled screen of the game.
Note that drawing only has any effect when done from callbacks, so it can only
be feasibly used in the core context.</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.screen.getWindowSize()</tt></p>
<p>Returns <em>width, height</em> of the screen.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.getMousePos()</tt></p>
<p>Returns <em>x,y</em> of the tile the mouse is over.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.screen.paintTile(pen,x,y[,char,tile])</span></tt></p>
<p>Paints a tile using given parameters. Pen is a table with following possible fields:</p>
<dl class="docutils">
<dt><tt class="docutils literal">ch</tt></dt>
<dd><p class="first last">Provides the ordinary tile character. Can be overridden with the <tt class="docutils literal">char</tt> function parameter.</p>
</dd>
<dt><tt class="docutils literal">fg</tt></dt>
<dd><p class="first last">Foreground color for the ordinary tile. Defaults to 7.</p>
</dd>
<dt><tt class="docutils literal">bg</tt></dt>
<dd><p class="first last">Background color for the ordinary tile. Defaults to 0.</p>
</dd>
<dt><tt class="docutils literal">bold</tt></dt>
<dd><p class="first last">Bright/bold text flag. If <em>nil</em>, computed based on (fg &amp; 8); fg is reset to 7 bits.
Otherwise should be <em>true/false</em>.</p>
</dd>
<dt><tt class="docutils literal">tile</tt></dt>
<dd><p class="first last">Graphical tile id. Ignored unless [GRAPHICS:YES] in init.txt.</p>
</dd>
<dt><tt class="docutils literal">tile_color = true</tt></dt>
<dd><p class="first last">Specifies that the tile should be shaded with <em>fg/bg</em>.</p>
</dd>
<dt><tt class="docutils literal">tile_fg, tile_bg</tt></dt>
<dd><p class="first last">If specified, overrides <em>tile_color</em> and supplies shading colors directly.</p>
</dd>
</dl>
<p>Returns <em>false</em> if coordinates out of bounds, or other error.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.paintString(pen,x,y,text)</tt></p>
<p>Paints the string starting at <em>x,y</em>. Uses the string characters
in sequence to override the <tt class="docutils literal">ch</tt> field of pen.</p>
<p>Returns <em>true</em> if painting at least one character succeeded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.fillRect(pen,x1,y1,x2,y2)</tt></p>
<p>Fills the rectangle specified by the coordinates with the given pen.
Returns <em>true</em> if painting at least one character succeeded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.clear()</tt></p>
<p>Fills the screen with blank background.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.invalidate()</tt></p>
<p>Requests repaint of the screen by setting a flag. Unlike other
functions in this section, this may be used at any time.</p>
</li>
</ul>
<p>In order to actually be able to paint to the screen, it is necessary
to create and register a viewscreen (basically a modal dialog) with
the game. Screens are managed with the following functions:</p>
<ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.screen.show(screen[,below])</span></tt></p>
<p>Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns <em>true</em> if success.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.dismiss(screen)</tt></p>
<p>Marks the screen to be removed when the game enters its event loop.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.isDismissed(screen)</tt></p>
<p>Checks if the screen is already marked for removal.</p>
</li>
</ul>
<p>Apart from a native viewscreen object, these functions accept a table
as a screen. In this case, <tt class="docutils literal">show</tt> creates a new native viewscreen
that delegates all processing to methods stored in that table.</p>
<p><strong>NOTE</strong>: Lua-implemented screens are only supported in the core context.</p>
<p>Supported callbacks and fields are:</p>
<ul>
<li><p class="first"><tt class="docutils literal">screen._native</tt></p>
<p>Initialized by <tt class="docutils literal">show</tt> with a reference to the backing viewscreen
object, and removed again when the object is deleted.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onDestroy()</tt></p>
<p>Called from the destructor when the viewscreen is deleted.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onRender()</tt></p>
<p>Called when the viewscreen should paint itself. This is the only context
where the above painting functions work correctly.</p>
<p>If omitted, the screen is cleared; otherwise it should do that itself.
In order to make a see-through dialog, call <tt class="docutils literal">self._native.parent:render()</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onIdle()</tt></p>
<p>Called every frame when the screen is on top of the stack.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onHelp()</tt></p>
<p>Called when the help keybinding is activated (usually '?').</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onInput(keys)</tt></p>
<p>Called when keyboard or mouse events are available.
If any keys are pressed, the keys argument is a table mapping them to <em>true</em>.
Note that this refers to logical keybingings computed from real keys via
options; if multiple interpretations exist, the table will contain multiple keys.</p>
<p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p>
</li>
</ul>
</div>
<div class="section" id="internal-api">
<h3><a class="toc-backref" href="#id26">Internal API</a></h3>
<h3><a class="toc-backref" href="#id27">Internal API</a></h3>
<p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p>
<ul>
@ -1434,7 +1541,7 @@ Returns: <em>found_index</em>, or <em>nil</em> if end reached.</p>
</div>
</div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id27">Core interpreter context</a></h2>
<h2><a class="toc-backref" href="#id28">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p>
@ -1465,7 +1572,7 @@ Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id28">Event type</a></h3>
<h3><a class="toc-backref" href="#id29">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
@ -1491,7 +1598,7 @@ order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</div>
</div>
<div class="section" id="lua-modules">
<h1><a class="toc-backref" href="#id29">Lua Modules</a></h1>
<h1><a class="toc-backref" href="#id30">Lua Modules</a></h1>
<p>DFHack sets up the lua interpreter so that the built-in <tt class="docutils literal">require</tt>
function can be used to load shared lua code from hack/lua/.
The <tt class="docutils literal">dfhack</tt> namespace reference itself may be obtained via
@ -1520,7 +1627,7 @@ in this document.</p>
</li>
</ul>
<div class="section" id="global-environment">
<h2><a class="toc-backref" href="#id30">Global environment</a></h2>
<h2><a class="toc-backref" href="#id31">Global environment</a></h2>
<p>A number of variables and functions are provided in the base global
environment by the mandatory init file dfhack.lua:</p>
<ul>
@ -1561,7 +1668,7 @@ Returns <em>nil</em> if any of obj or indices is <em>nil</em>, or a numeric inde
</ul>
</div>
<div class="section" id="utils">
<h2><a class="toc-backref" href="#id31">utils</a></h2>
<h2><a class="toc-backref" href="#id32">utils</a></h2>
<ul>
<li><p class="first"><tt class="docutils literal">utils.compare(a,b)</tt></p>
<p>Comparator function; returns <em>-1</em> if a&lt;b, <em>1</em> if a&gt;b, <em>0</em> otherwise.</p>
@ -1674,7 +1781,7 @@ throws an error.</p>
</ul>
</div>
<div class="section" id="dumper">
<h2><a class="toc-backref" href="#id32">dumper</a></h2>
<h2><a class="toc-backref" href="#id33">dumper</a></h2>
<p>A third-party lua table dumper module from
<a class="reference external" href="http://lua-users.org/wiki/DataDumper">http://lua-users.org/wiki/DataDumper</a>. Defines one
function:</p>
@ -1688,14 +1795,14 @@ the other arguments see the original documentation link above.</p>
</div>
</div>
<div class="section" id="plugins">
<h1><a class="toc-backref" href="#id33">Plugins</a></h1>
<h1><a class="toc-backref" href="#id34">Plugins</a></h1>
<p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p>
<div class="section" id="burrows">
<h2><a class="toc-backref" href="#id34">burrows</a></h2>
<h2><a class="toc-backref" href="#id35">burrows</a></h2>
<p>Implements extended burrow manipulations.</p>
<p>Events:</p>
<ul>
@ -1733,13 +1840,13 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div>
<div class="section" id="sort">
<h2><a class="toc-backref" href="#id35">sort</a></h2>
<h2><a class="toc-backref" href="#id36">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p>
</div>
</div>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id36">Scripts</a></h1>
<h1><a class="toc-backref" href="#id37">Scripts</a></h1>
<p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans

@ -39,6 +39,7 @@ distribution.
#include "modules/World.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Job.h"
#include "modules/Translation.h"
#include "modules/Units.h"
@ -84,6 +85,8 @@ distribution.
using namespace DFHack;
using namespace DFHack::LuaWrapper;
using Screen::Pen;
void dfhack_printerr(lua_State *S, const std::string &str);
void Lua::Push(lua_State *state, const Units::NoblePosition &pos)
@ -179,6 +182,48 @@ static df::coord CheckCoordXYZ(lua_State *state, int base, bool vararg = false)
return p;
}
template<class T>
static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int defval)
{
lua_getfield(L, idx, name);
bool nil = lua_isnil(L, -1);
if (nil) *pf = T(defval);
else if (lua_isnumber(L, -1)) *pf = T(lua_tointeger(L, -1));
else luaL_error(L, "Field %s is not a number.", name);
lua_pop(L, 1);
return !nil;
}
static void decode_pen(lua_State *L, Pen &pen, int idx)
{
get_int_field(L, &pen.ch, idx, "ch", 0);
get_int_field(L, &pen.fg, idx, "fg", 7);
get_int_field(L, &pen.bg, idx, "bg", 0);
lua_getfield(L, idx, "bold");
if (lua_isnil(L, -1))
{
pen.bold = (pen.fg & 8) != 0;
pen.fg &= 7;
}
else pen.bold = lua_toboolean(L, -1);
lua_pop(L, 1);
get_int_field(L, &pen.tile, idx, "tile", 0);
bool tcolor = get_int_field(L, &pen.tile_fg, idx, "tile_fg", 7);
tcolor = get_int_field(L, &pen.tile_bg, idx, "tile_bg", 0) || tcolor;
if (tcolor)
pen.tile_mode = Pen::TileColor;
else
{
lua_getfield(L, idx, "tile_color");
pen.tile_mode = (lua_toboolean(L, -1) ? Pen::CharColor : Pen::AsIs);
lua_pop(L, 1);
}
}
/**************************************************
* Per-world persistent configuration storage API *
**************************************************/
@ -1019,6 +1064,108 @@ static const luaL_Reg dfhack_constructions_funcs[] = {
{ NULL, NULL }
};
/***** Screen module *****/
static const LuaWrapper::FunctionReg dfhack_screen_module[] = {
WRAPM(Screen, clear),
WRAPM(Screen, invalidate),
{ NULL, NULL }
};
static int screen_getMousePos(lua_State *L)
{
auto pos = Screen::getMousePos();
lua_pushinteger(L, pos.x);
lua_pushinteger(L, pos.y);
return 2;
}
static int screen_getWindowSize(lua_State *L)
{
auto pos = Screen::getWindowSize();
lua_pushinteger(L, pos.x);
lua_pushinteger(L, pos.y);
return 2;
}
static int screen_paintTile(lua_State *L)
{
Pen pen;
decode_pen(L, pen, 1);
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
if (lua_gettop(L) >= 4 && !lua_isnil(L, 4))
pen.ch = luaL_checkint(L, 4);
if (lua_gettop(L) >= 5 && !lua_isnil(L, 5))
pen.tile = luaL_checkint(L, 5);
lua_pushboolean(L, Screen::paintTile(pen, x, y));
return 1;
}
static int screen_paintString(lua_State *L)
{
Pen pen;
decode_pen(L, pen, 1);
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
const char *text = luaL_checkstring(L, 4);
lua_pushboolean(L, Screen::paintString(pen, x, y, text));
return 1;
}
static int screen_fillRect(lua_State *L)
{
Pen pen;
decode_pen(L, pen, 1);
int x1 = luaL_checkint(L, 2);
int y1 = luaL_checkint(L, 3);
int x2 = luaL_checkint(L, 4);
int y2 = luaL_checkint(L, 5);
lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2));
return 1;
}
namespace {
int screen_show(lua_State *L)
{
df::viewscreen *before = NULL;
if (lua_gettop(L) >= 2)
before = Lua::CheckDFObject<df::viewscreen>(L, 2);
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, true);
lua_pushboolean(L, Screen::show(screen, before));
return 1;
}
static int screen_dismiss(lua_State *L)
{
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false);
Screen::dismiss(screen);
return 0;
}
static int screen_isDismissed(lua_State *L)
{
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false);
lua_pushboolean(L, Screen::isDismissed(screen));
return 1;
}
}
static const luaL_Reg dfhack_screen_funcs[] = {
{ "getMousePos", screen_getMousePos },
{ "getWindowSize", screen_getWindowSize },
{ "paintTile", screen_paintTile },
{ "paintString", screen_paintString },
{ "fillRect", screen_fillRect },
{ "show", &Lua::CallWithCatchWrapper<screen_show> },
{ "dismiss", screen_dismiss },
{ "isDismissed", screen_isDismissed },
{ NULL, NULL }
};
/***** Internal module *****/
static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
@ -1252,5 +1399,6 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs);
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
OpenModule(state, "constructions", dfhack_constructions_module);
OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
}

@ -37,6 +37,7 @@ distribution.
#include "DataDefs.h"
#include "DataIdentity.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
#include "DataFuncs.h"
#include "MiscUtils.h"
@ -1067,6 +1068,27 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
return 1;
}
int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *context)
{
if (!context)
context = "native code";
try {
return fn(state);
}
catch (Error::NullPointer &e) {
const char *vn = e.varname();
return luaL_error(state, "%s: NULL pointer: %s", context, vn ? vn : "?");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?");
}
catch (std::exception &e) {
return luaL_error(state, "%s: C++ exception: %s", context, e.what());
}
}
/**
* Push a closure invoking the given function.
*/

@ -467,7 +467,7 @@ int Plugin::lua_cmd_wrapper(lua_State *state)
luaL_error(state, "plugin command %s() has been unloaded",
(cmd->owner->name+"."+cmd->name).c_str());
return cmd->command(state);
return Lua::CallWithCatch(state, cmd->command, cmd->name.c_str());
}
int Plugin::lua_fun_wrapper(lua_State *state)

@ -191,6 +191,16 @@ namespace DFHack {namespace Lua {
return cb(state, rv, ctx);
}
/**
* Call through to the function with try/catch for C++ exceptions.
*/
DFHACK_EXPORT int CallWithCatch(lua_State *, int (*fn)(lua_State*), const char *context = NULL);
template<int (*cb)(lua_State*)>
int CallWithCatchWrapper(lua_State *state) {
return CallWithCatch(state, cb);
}
/**
* Invoke lua function via pcall. Returns true if success.
* If an error is signalled, and perr is true, it is printed and popped from the stack.

@ -95,6 +95,12 @@ namespace DFHack
/// Fills a rectangle with one pen. Possibly more efficient than a loop over paintTile.
DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2);
/// Wipes the screen to full black
DFHACK_EXPORT bool clear();
/// Requests repaint
DFHACK_EXPORT bool invalidate();
/// Find a loaded graphics tile from graphics raws.
DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL);
@ -111,6 +117,37 @@ namespace DFHack
static bool is_instance(df::viewscreen *screen);
virtual bool is_lua_screen() { return false; }
virtual std::string getFocusString() = 0;
};
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
std::string focus;
void update_focus(lua_State *L, int idx);
bool safe_call_lua(int (*pf)(lua_State *), int args, int rvs);
static dfhack_lua_viewscreen *get_self(lua_State *L);
static int do_destroy(lua_State *L);
static int do_render(lua_State *L);
static int do_notify(lua_State *L);
static int do_input(lua_State *L);
public:
dfhack_lua_viewscreen(lua_State *L, int table_idx);
virtual ~dfhack_lua_viewscreen();
static df::viewscreen *get_pointer(lua_State *L, int idx, bool make);
virtual bool is_lua_screen() { return true; }
virtual std::string getFocusString() { return focus; }
virtual void render();
virtual void logic();
virtual void help();
virtual void resize(int w, int h);
virtual void feed(std::set<df::interface_key> *keys);
virtual bool key_conflict(df::interface_key key);
};
}

@ -188,6 +188,12 @@ function dfhack.buildings.getSize(bld)
return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y
end
dfhack.screen.__index = dfhack.screen
function dfhack.screen:__tostring()
return "<lua viewscreen: "..tostring(self._native)..">"
end
-- Interactive
local print_banner = true

@ -38,7 +38,10 @@ using namespace std;
#include "ModuleFactory.h"
#include "Core.h"
#include "PluginManager.h"
#include "LuaTools.h"
#include "MiscUtils.h"
using namespace DFHack;
#include "DataDefs.h"
@ -46,15 +49,21 @@ using namespace DFHack;
#include "df/texture_handler.h"
#include "df/tile_page.h"
#include "df/interfacest.h"
#include "df/enabler.h"
using namespace df::enums;
using df::global::init;
using df::global::gps;
using df::global::texture;
using df::global::gview;
using df::global::enabler;
using Screen::Pen;
/*
* Screen painting API.
*/
df::coord2d Screen::getMousePos()
{
if (!gps) return df::coord2d();
@ -136,6 +145,21 @@ bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2)
return true;
}
bool Screen::clear()
{
if (!gps) return false;
return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1);
}
bool Screen::invalidate()
{
if (!enabler) return false;
enabler->flag.bits.render = true;
return true;
}
bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs)
{
if (!gps || !texture || x < 0 || y < 0) return false;
@ -197,6 +221,10 @@ bool Screen::isDismissed(df::viewscreen *screen)
return screen->breakdown_level != interface_breakdown_types::NONE;
}
/*
* Base DFHack viewscreen.
*/
static std::set<df::viewscreen*> dfhack_screens;
dfhack_viewscreen::dfhack_viewscreen()
@ -213,3 +241,237 @@ bool dfhack_viewscreen::is_instance(df::viewscreen *screen)
{
return dfhack_screens.count(screen) != 0;
}
/*
* Lua-backed viewscreen.
*/
static int DFHACK_LUA_VS_TOKEN = 0;
df::viewscreen *dfhack_lua_viewscreen::get_pointer(lua_State *L, int idx, bool make)
{
df::viewscreen *screen;
if (lua_istable(L, idx))
{
if (!Lua::IsCoreContext(L))
luaL_error(L, "only the core context can create lua screens");
lua_rawgetp(L, idx, &DFHACK_LUA_VS_TOKEN);
if (!lua_isnil(L, -1))
{
if (make)
luaL_error(L, "this screen is already on display");
screen = (df::viewscreen*)lua_touserdata(L, -1);
}
else
{
if (!make)
luaL_error(L, "this screen is not on display");
screen = new dfhack_lua_viewscreen(L, idx);
}
lua_pop(L, 1);
}
else
screen = Lua::CheckDFObject<df::viewscreen>(L, idx);
return screen;
}
bool dfhack_lua_viewscreen::safe_call_lua(int (*pf)(lua_State *), int args, int rvs)
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
auto L = Lua::Core::State;
lua_pushcfunction(L, pf);
if (args > 0) lua_insert(L, -args-1);
lua_pushlightuserdata(L, this);
if (args > 0) lua_insert(L, -args-1);
return Lua::Core::SafeCall(out, args+1, rvs);
}
dfhack_lua_viewscreen *dfhack_lua_viewscreen::get_self(lua_State *L)
{
auto self = (dfhack_lua_viewscreen*)lua_touserdata(L, 1);
lua_rawgetp(L, LUA_REGISTRYINDEX, self);
if (!lua_istable(L, -1)) return NULL;
return self;
}
int dfhack_lua_viewscreen::do_destroy(lua_State *L)
{
auto self = get_self(L);
if (!self) return 0;
lua_pushnil(L);
lua_rawsetp(L, LUA_REGISTRYINDEX, self);
lua_pushnil(L);
lua_rawsetp(L, -2, &DFHACK_LUA_VS_TOKEN);
lua_pushnil(L);
lua_setfield(L, -2, "_native");
lua_getfield(L, -1, "onDestroy");
lua_pushvalue(L, -2);
lua_call(L, 1, 0);
return 0;
}
void dfhack_lua_viewscreen::update_focus(lua_State *L, int idx)
{
lua_getfield(L, idx, "focus_path");
auto str = lua_tostring(L, -1);
if (!str) str = "";
focus = str;
lua_pop(L, 1);
if (focus.empty())
focus = "lua";
else
focus = "lua/"+focus;
}
int dfhack_lua_viewscreen::do_render(lua_State *L)
{
auto self = get_self(L);
if (!self) return 0;
lua_getfield(L, -1, "onRender");
if (lua_isnil(L, -1))
{
Screen::clear();
return 0;
}
lua_pushvalue(L, -2);
lua_call(L, 1, 0);
return 0;
}
int dfhack_lua_viewscreen::do_notify(lua_State *L)
{
int args = lua_gettop(L);
auto self = get_self(L);
if (!self) return 0;
lua_pushvalue(L, 2);
lua_gettable(L, -2);
if (lua_isnil(L, -1))
return 0;
// self field args table fn -> table fn table args
lua_replace(L, 1);
lua_copy(L, -1, 2);
lua_insert(L, 1);
lua_call(L, args-1, 1);
self->update_focus(L, 1);
return 1;
}
int dfhack_lua_viewscreen::do_input(lua_State *L)
{
auto self = get_self(L);
if (!self) return 0;
auto keys = (std::set<df::interface_key>*)lua_touserdata(L, 2);
lua_getfield(L, -1, "onInput");
if (lua_isnil(L, -1))
{
if (keys->count(interface_key::LEAVESCREEN))
Screen::dismiss(self);
return 0;
}
lua_pushvalue(L, -2);
if (keys->empty())
lua_pushnil(L);
else
{
lua_createtable(L, 0, keys->size());
for (auto it = keys->begin(); it != keys->end(); ++it)
{
if (auto name = enum_item_raw_key(*it))
lua_pushstring(L, name);
else
lua_pushinteger(L, *it);
lua_pushboolean(L, true);
lua_rawset(L, -3);
}
}
lua_call(L, 2, 0);
self->update_focus(L, -1);
return 0;
}
dfhack_lua_viewscreen::dfhack_lua_viewscreen(lua_State *L, int table_idx)
{
assert(Lua::IsCoreContext(L));
Lua::PushDFObject(L, (df::viewscreen*)this);
lua_setfield(L, table_idx, "_native");
lua_pushlightuserdata(L, this);
lua_rawsetp(L, table_idx, &DFHACK_LUA_VS_TOKEN);
lua_pushvalue(L, table_idx);
lua_rawsetp(L, LUA_REGISTRYINDEX, this);
update_focus(L, table_idx);
}
dfhack_lua_viewscreen::~dfhack_lua_viewscreen()
{
safe_call_lua(do_destroy, 0, 0);
}
void dfhack_lua_viewscreen::render()
{
safe_call_lua(do_render, 0, 0);
}
void dfhack_lua_viewscreen::logic()
{
lua_pushstring(Lua::Core::State, "onIdle");
safe_call_lua(do_notify, 1, 0);
}
void dfhack_lua_viewscreen::help()
{
lua_pushstring(Lua::Core::State, "onHelp");
safe_call_lua(do_notify, 1, 0);
}
void dfhack_lua_viewscreen::resize(int w, int h)
{
auto L = Lua::Core::State;
lua_pushstring(L, "onResize");
lua_pushinteger(L, w);
lua_pushinteger(L, h);
safe_call_lua(do_notify, 3, 0);
}
void dfhack_lua_viewscreen::feed(std::set<df::interface_key> *keys)
{
lua_pushlightuserdata(Lua::Core::State, keys);
safe_call_lua(do_input, 1, 0);
}
bool dfhack_lua_viewscreen::key_conflict(df::interface_key key)
{
return key == interface_key::MOVIES || key == interface_key::OPTIONS;
}