diff --git a/Lua API.html b/Lua API.html index a52347104..5dff4b3e4 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1555,37 +1555,12 @@ be feasibly used in the core context.
Checks if [GRAPHICS:YES] was specified in init.
dfhack.screen.paintTile(pen,x,y[,char,tile])
-Paints a tile using given parameters. Pen is a table with following possible fields:
-Provides the ordinary tile character, as either a 1-character string or a number. -Can be overridden with the char function parameter.
-Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).
-Background color for the ordinary tile. Defaults to COLOR_BLACK (0).
-Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. -Otherwise should be true/false.
-Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.
-Specifies that the tile should be shaded with fg/bg.
-If specified, overrides tile_color and supplies shading colors directly.
-Paints a tile using given parameters. See below for a description of pen.
Returns false if coordinates out of bounds, or other error.
dfhack.screen.readTile(x,y)
Retrieves the contents of the specified tile from the screen buffers. -Returns a pen, or nil if invalid or TrueType.
+Returns a pen object, or nil if invalid or TrueType.dfhack.screen.paintString(pen,x,y,text)
Paints the string starting at x,y. Uses the string characters @@ -1610,6 +1585,59 @@ The values can then be used for the tile field of pen structur functions in this section, this may be used at any time.
The "pen" argument used by functions above may be represented by +a table with the following possible fields:
++++
+- ch
+- Provides the ordinary tile character, as either a 1-character string or a number. +Can be overridden with the char function parameter.
+- fg
+- Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).
+- bg
+- Background color for the ordinary tile. Defaults to COLOR_BLACK (0).
+- bold
+- Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. +Otherwise should be true/false.
+- tile
+- Graphical tile id. Ignored unless [GRAPHICS:YES] was 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.
+
Alternatively, it may be a pre-parsed native object with the following API:
+dfhack.pen.make(base[,pen_or_fg,bg,bold])
+Creates a new pre-parsed pen by combining its arguments according to the +following rules:
+This function always returns a new pre-parsed pen, or nil.
+dfhack.pen.parse(base[,pen_or_fg,bg,bold])
+Exactly like the above function, but returns base or pen_or_fg +directly if they are already a pre-parsed native object.
+pen.property, pen.property = value, pairs(pen)
+Pre-parsed pens support reading and setting their properties, +but don't behave exactly like a simple table would; for instance, +assigning to pen.tile_color also resets pen.tile_fg and +pen.tile_bg to nil.
+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.
diff --git a/Lua API.rst b/Lua API.rst index bd712d301..ec48938db 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1403,31 +1403,14 @@ Basic painting functions: * ``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, as either a 1-character string or a number. - Can be overridden with the ``char`` function parameter. - ``fg`` - Foreground color for the ordinary tile. Defaults to COLOR_GREY (7). - ``bg`` - Background color for the ordinary tile. Defaults to COLOR_BLACK (0). - ``bold`` - Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is masked to 3 bits. - Otherwise should be *true/false*. - ``tile`` - Graphical tile id. Ignored unless [GRAPHICS:YES] was 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. + Paints a tile using given parameters. See below for a description of pen. Returns *false* if coordinates out of bounds, or other error. * ``dfhack.screen.readTile(x,y)`` Retrieves the contents of the specified tile from the screen buffers. - Returns a pen, or *nil* if invalid or TrueType. + Returns a pen object, or *nil* if invalid or TrueType. * ``dfhack.screen.paintString(pen,x,y,text)`` @@ -1458,6 +1441,61 @@ Basic painting functions: Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time. +The "pen" argument used by functions above may be represented by +a table with the following possible fields: + + ``ch`` + Provides the ordinary tile character, as either a 1-character string or a number. + Can be overridden with the ``char`` function parameter. + ``fg`` + Foreground color for the ordinary tile. Defaults to COLOR_GREY (7). + ``bg`` + Background color for the ordinary tile. Defaults to COLOR_BLACK (0). + ``bold`` + Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is masked to 3 bits. + Otherwise should be *true/false*. + ``tile`` + Graphical tile id. Ignored unless [GRAPHICS:YES] was 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. + +Alternatively, it may be a pre-parsed native object with the following API: + +* ``dfhack.pen.make(base[,pen_or_fg,bg,bold])`` + + Creates a new pre-parsed pen by combining its arguments according to the + following rules: + + 1. The ``base`` argument may be a pen object, a pen table as specified above, + or a single color value. In the single value case, it is split into + ``fg`` and ``bold`` properties, and others are initialized to 0. + This argument will be converted to a pre-parsed object and returned + if there are no other arguments. + + 2. If the ``pen_or_fg`` argument is specified as a table or object, it + completely replaces the base, and is returned instead of it. + + 3. Otherwise, the non-nil subset of the optional arguments is used + to update the ``fg``, ``bg`` and ``bold`` properties of the base. + If the ``bold`` flag is *nil*, but *pen_or_fg* is a number, ``bold`` + is deduced from it like in the simple base case. + + This function always returns a new pre-parsed pen, or *nil*. + +* ``dfhack.pen.parse(base[,pen_or_fg,bg,bold])`` + + Exactly like the above function, but returns ``base`` or ``pen_or_fg`` + directly if they are already a pre-parsed native object. + +* ``pen.property``, ``pen.property = value``, ``pairs(pen)`` + + Pre-parsed pens support reading and setting their properties, + but don't behave exactly like a simple table would; for instance, + assigning to ``pen.tile_color`` also resets ``pen.tile_fg`` and + ``pen.tile_bg`` to *nil*. + 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. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index e7424ad50..0151ed404 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -725,6 +725,316 @@ static void OpenMatinfo(lua_State *state) lua_pop(state, 1); } +/************** + * Pen object * + **************/ + +static int DFHACK_PEN_TOKEN = 0; + +void Lua::Push(lua_State *L, const Screen::Pen &info) +{ + if (!info.valid()) + { + lua_pushnil(L); + return; + } + + void *pdata = lua_newuserdata(L, sizeof(Pen)); + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + lua_setmetatable(L, -2); + + new (pdata) Pen(info); +} + +static Pen *check_pen_native(lua_State *L, int index) +{ + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + + if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2)) + luaL_argerror(L, index, "not a pen object"); + + lua_pop(L, 2); + + return (Pen*)lua_touserdata(L, index); +} + +void Lua::CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil, bool allow_color) +{ + index = lua_absindex(L, index); + + luaL_checkany(L, index); + + if (lua_isnil(L, index)) + { + if (!allow_nil) + luaL_argerror(L, index, "nil pen not allowed"); + + *pen = Pen(0,0,0,-1); + } + else if (lua_isuserdata(L, index)) + { + *pen = *check_pen_native(L, index); + } + else if (allow_color && lua_isnumber(L, index)) + { + *pen = Pen(0, lua_tointeger(L, index)&15, 0); + } + else + { + luaL_checktype(L, index, LUA_TTABLE); + decode_pen(L, *pen, index); + } +} + +static int adjust_pen(lua_State *L, bool no_copy) +{ + lua_settop(L, 4); + + Pen pen; + int iidx = 1; + Lua::CheckPen(L, &pen, 1, true, true); + + if (!lua_isnil(L, 2) || !lua_isnil(L, 3) || !lua_isnil(L, 4)) + { + if (lua_isnumber(L, 2) || lua_isnil(L, 2)) + { + if (!pen.valid()) + pen = Pen(); + + iidx = -1; + + pen.fg = luaL_optint(L, 2, pen.fg) & 15; + pen.bg = luaL_optint(L, 3, pen.bg); + + if (!lua_isnil(L, 4)) + pen.bold = lua_toboolean(L, 4); + else if (!lua_isnil(L, 2)) + { + pen.bold = !!(pen.fg & 8); + pen.fg &= 7; + } + } + else + { + iidx = 2; + Lua::CheckPen(L, &pen, 2, false, false); + } + } + + if (no_copy && iidx > 0 && lua_isuserdata(L, iidx)) + lua_pushvalue(L, iidx); + else + Lua::Push(L, pen); + + return 1; +} + +static int dfhack_pen_parse(lua_State *L) +{ + return adjust_pen(L, true); +} + +static int dfhack_pen_make(lua_State *L) +{ + return adjust_pen(L, false); +} + +static void make_pen_table(lua_State *L, Pen &pen) +{ + if (!pen.valid()) + luaL_error(L, "invalid pen state"); + else + { + lua_newtable(L); + lua_pushinteger(L, (unsigned char)pen.ch); lua_setfield(L, -2, "ch"); + lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); + lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); + lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); + + if (pen.tile) + { + lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); + } + + switch (pen.tile_mode) { + case Pen::CharColor: + lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); + break; + case Pen::TileColor: + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); + break; + default: + lua_pushboolean(L, false); lua_setfield(L, -2, "tile_color"); + break; + } + } +} + +static void get_pen_mirror(lua_State *L, int idx) +{ + lua_getuservalue(L, idx); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + + Pen pen; + Lua::CheckPen(L, &pen, idx, false, false); + make_pen_table(L, pen); + + lua_dup(L); + lua_setuservalue(L, idx); + } +} + +static int dfhack_pen_index(lua_State *L) +{ + lua_settop(L, 2); + luaL_checktype(L, 1, LUA_TUSERDATA); + + // check metatable + if (!lua_getmetatable(L, 1)) + luaL_argerror(L, 1, "must be a pen"); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) + return 1; + + // otherwise read from the mirror table, creating it if necessary + lua_settop(L, 2); + get_pen_mirror(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + return 1; +} + +static int pen_pnext(lua_State *L) +{ + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, lua_upvalueindex(1))) + return 2; + lua_pushnil(L); + return 1; +} + +static int dfhack_pen_pairs(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + get_pen_mirror(L, 1); + lua_pushcclosure(L, pen_pnext, 1); + lua_pushnil(L); + lua_pushnil(L); + return 3; +} + +const char *const pen_fields[] = { + "ch", "fg", "bold", "bg", "tile", "tile_color", "tile_fg", "tile_bg", NULL +}; + +static int dfhack_pen_newindex(lua_State *L) +{ + lua_settop(L, 3); + luaL_checktype(L, 1, LUA_TUSERDATA); + int id = luaL_checkoption(L, 2, NULL, pen_fields); + int arg = 0; + Pen &pen = *check_pen_native(L, 1); + bool wipe_tile = false, wipe_tc = false; + + switch (id) { + case 0: + if (lua_type(L, 3) != LUA_TNUMBER) + arg = (unsigned char)*luaL_checkstring(L, 3); + else + arg = luaL_checkint(L, 3); + pen.ch = arg; + lua_pushinteger(L, (unsigned char)pen.ch); + break; + case 1: + pen.fg = luaL_checkint(L, 3) & 15; + lua_pushinteger(L, pen.fg); + break; + case 2: + pen.bold = lua_toboolean(L, 3); + lua_pushboolean(L, pen.bold); + break; + case 3: + pen.bg = luaL_checkint(L, 3) & 15; + lua_pushinteger(L, pen.bg); + break; + case 4: + arg = lua_isnil(L, 3) ? 0 : luaL_checkint(L, 3); + if (arg < 0) + luaL_argerror(L, 3, "invalid tile index"); + pen.tile = arg; + if (pen.tile) + lua_pushinteger(L, pen.tile); + else + lua_pushnil(L); + break; + case 5: + wipe_tile = (pen.tile_mode == Pen::TileColor); + pen.tile_mode = lua_toboolean(L, 3) ? Pen::CharColor : Pen::AsIs; + lua_pushboolean(L, pen.tile_mode == Pen::CharColor); + break; + case 6: + if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_bg = 0; } + pen.tile_fg = luaL_checkint(L, 3) & 15; + pen.tile_mode = Pen::TileColor; + lua_pushinteger(L, pen.tile_fg); + break; + case 7: + if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_fg = 7; } + pen.tile_bg = luaL_checkint(L, 3) & 15; + pen.tile_mode = Pen::TileColor; + lua_pushinteger(L, pen.tile_bg); + break; + } + + lua_getuservalue(L, 1); + + if (!lua_isnil(L, -1)) + { + lua_remove(L, 3); + lua_insert(L, 2); + lua_rawset(L, 2); + + if (wipe_tc) { + lua_pushnil(L); lua_setfield(L, 2, "tile_color"); + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, 2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, 2, "tile_bg"); + } + if (wipe_tile) { + lua_pushnil(L); lua_setfield(L, 2, "tile_fg"); + lua_pushnil(L); lua_setfield(L, 2, "tile_bg"); + } + } + + return 0; +} + +static const luaL_Reg dfhack_pen_funcs[] = { + { "parse", dfhack_pen_parse }, + { "make", dfhack_pen_make }, + { "__index", dfhack_pen_index }, + { "__pairs", dfhack_pen_pairs }, + { "__newindex", dfhack_pen_newindex }, + { NULL, NULL } +}; + +static void OpenPen(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "pen"); + + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + + luaL_setfuncs(state, dfhack_pen_funcs, 0); + + lua_pop(state, 1); +} + /************************ * Wrappers for C++ API * ************************/ @@ -1251,7 +1561,7 @@ static int screen_getWindowSize(lua_State *L) static int screen_paintTile(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) @@ -1272,44 +1582,14 @@ static int screen_readTile(lua_State *L) int x = luaL_checkint(L, 1); int y = luaL_checkint(L, 2); Pen pen = Screen::readTile(x, y); - - if (!pen.valid()) - { - lua_pushnil(L); - } - else - { - lua_newtable(L); - lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch"); - lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); - lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); - lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); - - if (pen.tile) - { - lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); - - switch (pen.tile_mode) { - case Pen::CharColor: - lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); - break; - case Pen::TileColor: - lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); - lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); - break; - default: - break; - } - } - } - + Lua::Push(L, pen); return 1; } static int screen_paintString(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); const char *text = luaL_checkstring(L, 4); @@ -1320,7 +1600,7 @@ static int screen_paintString(lua_State *L) static int screen_fillRect(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x1 = luaL_checkint(L, 2); int y1 = luaL_checkint(L, 3); int x2 = luaL_checkint(L, 4); @@ -1720,6 +2000,7 @@ void OpenDFHackApi(lua_State *state) { OpenPersistent(state); OpenMatinfo(state); + OpenPen(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index ec4917972..655d069d3 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -41,6 +41,9 @@ namespace DFHack { namespace Units { struct NoblePosition; } + namespace Screen { + struct Pen; + }; } namespace DFHack {namespace Lua { @@ -285,6 +288,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); void Push(lua_State *state, const Units::NoblePosition &pos); DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info); + DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info); template