reserved texpos range

develop
shevernitskiy 2023-09-01 18:12:59 +03:00
parent 2b11baa35f
commit 6f26650255
7 changed files with 222 additions and 119 deletions

@ -2595,10 +2595,12 @@ The ``textures`` module solves this problem by providing a stable handle instead
raw ``texpos``. When we need to draw a particular tile, we can look up the current raw ``texpos``. When we need to draw a particular tile, we can look up the current
``texpos`` value via the handle. ``texpos`` value via the handle.
* ``loadTileset(file, tile_px_w, tile_px_h)`` * ``loadTileset(file, tile_px_w, tile_px_h, reserved?)``
Loads a tileset from the image ``file`` with give tile dimensions in pixels. The Loads a tileset from the image ``file`` with give tile dimensions in pixels. The
image will be sliced in row major order. Returns an array of ``TexposHandle``. image will be sliced in row major order. Returns an array of ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
Example usage:: Example usage::
@ -2611,18 +2613,22 @@ raw ``texpos``. When we need to draw a particular tile, we can look up the curre
get the ``texpos`` for your texture. ``texpos`` can change when game textures are get the ``texpos`` for your texture. ``texpos`` can change when game textures are
reset, but the handle will be the same. reset, but the handle will be the same.
* ``createTile(pixels, tile_px_w, tile_px_h)`` * ``createTile(pixels, tile_px_w, tile_px_h, reserved?)``
Create and register a new texture with the given tile dimensions and an array of Create and register a new texture with the given tile dimensions and an array of
``pixels`` in row major order. Each pixel is an integer representing color in packed ``pixels`` in row major order. Each pixel is an integer representing color in packed
RBGA format (for example, #0022FF11). Returns a ``TexposHandle``. RBGA format (for example, #0022FF11). Returns a ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
* ``createTileset(pixels, texture_px_w, texture_px_h, tile_px_w, tile_px_h)`` * ``createTileset(pixels, texture_px_w, texture_px_h, tile_px_w, tile_px_h, reserved?)``
Create and register a new texture with the given texture dimensions and an array of Create and register a new texture with the given texture dimensions and an array of
``pixels`` in row major order. Then slice it into tiles with the given tile ``pixels`` in row major order. Then slice it into tiles with the given tile
dimensions. Each pixel is an integer representing color in packed RBGA format (for dimensions. Each pixel is an integer representing color in packed RBGA format (for
example #0022FF11). Returns an array of ``TexposHandle``. example #0022FF11). Returns an array of ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
* ``deleteHandle(handle)`` * ``deleteHandle(handle)``

@ -1760,7 +1760,8 @@ static int textures_loadTileset(lua_State *state)
std::string file = luaL_checkstring(state, 1); std::string file = luaL_checkstring(state, 1);
auto tile_w = luaL_checkint(state, 2); auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3); auto tile_h = luaL_checkint(state, 3);
auto handles = Textures::loadTileset(file, tile_w, tile_h); bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handles = Textures::loadTileset(file, tile_w, tile_h, reserved);
Lua::PushVector(state, handles); Lua::PushVector(state, handles);
return 1; return 1;
} }
@ -1798,7 +1799,8 @@ static int textures_createTile(lua_State *state)
Lua::GetVector(state, pixels); Lua::GetVector(state, pixels);
auto tile_w = luaL_checkint(state, 2); auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3); auto tile_h = luaL_checkint(state, 3);
auto handle = Textures::createTile(pixels, tile_w, tile_h); bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handle = Textures::createTile(pixels, tile_w, tile_h, reserved);
Lua::Push(state, handle); Lua::Push(state, handle);
return 1; return 1;
} }
@ -1811,7 +1813,8 @@ static int textures_createTileset(lua_State *state)
auto texture_h = luaL_checkint(state, 3); auto texture_h = luaL_checkint(state, 3);
auto tile_w = luaL_checkint(state, 4); auto tile_w = luaL_checkint(state, 4);
auto tile_h = luaL_checkint(state, 5); auto tile_h = luaL_checkint(state, 5);
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h); bool reserved = lua_isboolean(state, 6) ? lua_toboolean(state, 6) : false;
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h, reserved);
Lua::PushVector(state, handles); Lua::PushVector(state, handles);
return 1; return 1;
} }

@ -26,7 +26,7 @@ const uint32_t TILE_HEIGHT_PX = 12;
* Load texture and get handle. * Load texture and get handle.
* Keep it to obtain valid texpos. * Keep it to obtain valid texpos.
*/ */
DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface); DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface, bool reserved = false);
/** /**
* Load tileset from image file. * Load tileset from image file.
@ -34,7 +34,8 @@ DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface);
*/ */
DFHACK_EXPORT std::vector<TexposHandle> loadTileset(const std::string& file, DFHACK_EXPORT std::vector<TexposHandle> loadTileset(const std::string& file,
int tile_px_w = TILE_WIDTH_PX, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX); int tile_px_h = TILE_HEIGHT_PX,
bool reserved = false);
/** /**
* Get texpos by handle. * Get texpos by handle.
@ -53,7 +54,7 @@ DFHACK_EXPORT void deleteHandle(TexposHandle handle);
* Register this texture and return TexposHandle. * Register this texture and return TexposHandle.
*/ */
DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px_w = TILE_WIDTH_PX, DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX); int tile_px_h = TILE_HEIGHT_PX, bool reserved = false);
/** /**
* Create new textures as tileset with RGBA32 format and pixels as data in row major order. * Create new textures as tileset with RGBA32 format and pixels as data in row major order.
@ -62,7 +63,8 @@ DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px
DFHACK_EXPORT std::vector<TexposHandle> createTileset(std::vector<uint32_t>& pixels, DFHACK_EXPORT std::vector<TexposHandle> createTileset(std::vector<uint32_t>& pixels,
int texture_px_w, int texture_px_h, int texture_px_w, int texture_px_h,
int tile_px_w = TILE_WIDTH_PX, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX); int tile_px_h = TILE_HEIGHT_PX,
bool reserved = false);
/** /**
* Call this on DFHack init just once to setup interposed handlers and * Call this on DFHack init just once to setup interposed handlers and

@ -6,18 +6,18 @@ local _ENV = mkmodule('gui.textures')
-- Preloaded DFHack Assets. -- Preloaded DFHack Assets.
-- Use this handles if you need to get dfhack standard textures. -- Use this handles if you need to get dfhack standard textures.
---@type table<string, TexposHandle> ---@type table<string, TexposHandle[]>
local texpos_handles = { local texpos_handles = {
green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12), green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12, true),
red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12), red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12, true),
icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12), icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12, true),
on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12), on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12, true),
control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12), control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12, true),
border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12), border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12, true),
border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12), border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12, true),
border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12), border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12, true),
border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12), border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12, true),
border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12), border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12, true),
} }
-- Get valid texpos for preloaded texture in tileset -- Get valid texpos for preloaded texture in tileset

@ -1,3 +1,4 @@
#include <atomic>
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <unordered_map> #include <unordered_map>
@ -29,8 +30,30 @@ DBG_DECLARE(core, textures, DebugCategory::LINFO);
} }
static std::unordered_map<TexposHandle, long> g_handle_to_texpos; static std::unordered_map<TexposHandle, long> g_handle_to_texpos;
static std::unordered_map<TexposHandle, long> g_handle_to_reserved_texpos;
static std::unordered_map<TexposHandle, SDL_Surface*> g_handle_to_surface; static std::unordered_map<TexposHandle, SDL_Surface*> g_handle_to_surface;
static std::unordered_map<std::string, std::vector<TexposHandle>> g_tileset_to_handles;
static std::mutex g_adding_mutex; static std::mutex g_adding_mutex;
static std::atomic<bool> loading_state = false;
struct Reserved {
static void init(int32_t start) {
reserved_range.start = start;
reserved_range.end = start + Reserved::size;
reserved_range.current = start;
}
static long get_new_texpos() {
if (reserved_range.current == reserved_range.end)
return -1;
current = reserved_range.current;
reserved_range.current++;
return current;
}
static const int32_t size = 10000; // size of reserved texpos buffer
inline static int32_t start = -1;
inline static int32_t end = -1;
inline static long current = -1;
} reserved_range;
// Converts an arbitrary Surface to something like the display format // Converts an arbitrary Surface to something like the display format
// (32-bit RGBA), and converts magenta to transparency if convert_magenta is set // (32-bit RGBA), and converts magenta to transparency if convert_magenta is set
@ -71,6 +94,12 @@ static long add_texture(SDL_Surface* surface) {
return texpos; return texpos;
} }
// register surface in texture raws to specific texpos, returns a texpos
static void insert_texture(SDL_Surface* surface, long texpos) {
std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex);
enabler->textures.raws[texpos] = surface;
}
// delete surface from texture raws // delete surface from texture raws
static void delete_texture(long texpos) { static void delete_texture(long texpos) {
std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex); std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex);
@ -94,7 +123,8 @@ SDL_Surface* create_texture(std::vector<uint32_t>& pixels, int texture_px_w, int
// convert single surface into tiles according w/h // convert single surface into tiles according w/h
// register tiles in texture raws and return handles // register tiles in texture raws and return handles
std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int tile_px_h) { std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int tile_px_h,
bool reserved) {
std::vector<TexposHandle> handles{}; std::vector<TexposHandle> handles{};
if (!surface) if (!surface)
return handles; return handles;
@ -109,7 +139,7 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
surface->format->Bmask, surface->format->Amask); surface->format->Bmask, surface->format->Amask);
SDL_Rect vp{tile_px_w * x, tile_px_h * y, tile_px_w, tile_px_h}; SDL_Rect vp{tile_px_w * x, tile_px_h * y, tile_px_w, tile_px_h};
DFSDL_UpperBlit(surface, &vp, tile, NULL); DFSDL_UpperBlit(surface, &vp, tile, NULL);
auto handle = Textures::loadTexture(tile); auto handle = Textures::loadTexture(tile, reserved);
handles.push_back(handle); handles.push_back(handle);
} }
} }
@ -118,22 +148,38 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
return handles; return handles;
} }
TexposHandle Textures::loadTexture(SDL_Surface* surface) { TexposHandle Textures::loadTexture(SDL_Surface* surface, bool reserved) {
if (!surface || !enabler) if (!surface || !enabler)
return 0; // should be some error, i guess return 0; // should be some error, i guess
if (loading_state) {
ERR(textures).printerr("unable to load texture during game loading\n");
return 0;
}
auto handle = reinterpret_cast<uintptr_t>(surface); auto handle = reinterpret_cast<uintptr_t>(surface);
g_handle_to_surface.emplace(handle, surface); g_handle_to_surface.emplace(handle, surface);
surface->refcount++; // prevent destruct on next FreeSurface by game surface->refcount++; // prevent destruct on next FreeSurface by game
auto texpos = add_texture(surface); if (reserved) {
g_handle_to_texpos.emplace(handle, texpos); auto texpos = reserved_range.get_new_texpos();
if (texpos == -1) {
ERR(textures).printerr("reserved range limit has been reached, use dynamic range\n");
return 0;
}
insert_texture(surface, texpos);
g_handle_to_reserved_texpos.emplace(handle, texpos);
} else {
auto texpos = add_texture(surface);
g_handle_to_texpos.emplace(handle, texpos);
}
return handle; return handle;
} }
std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int tile_px_w, std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int tile_px_w,
int tile_px_h) { int tile_px_h, bool reserved) {
if (!enabler) if (!enabler)
return std::vector<TexposHandle>{}; return std::vector<TexposHandle>{};
if (g_tileset_to_handles.contains(file))
return g_tileset_to_handles[file];
SDL_Surface* surface = DFIMG_Load(file.c_str()); SDL_Surface* surface = DFIMG_Load(file.c_str());
if (!surface) { if (!surface) {
@ -142,10 +188,12 @@ std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int til
} }
surface = canonicalize_format(surface); surface = canonicalize_format(surface);
auto handles = slice_tileset(surface, tile_px_w, tile_px_h); auto handles = slice_tileset(surface, tile_px_w, tile_px_h, reserved);
DEBUG(textures).print("loaded %zd textures from '%s'\n", handles.size(), file.c_str()); DEBUG(textures).print("loaded %zd textures from '%s' to %s range\n", handles.size(),
file.c_str(), reserved ? "reserved" : "dynamic");
g_tileset_to_handles[file] = handles;
return handles; return handles;
} }
@ -153,10 +201,15 @@ long Textures::getTexposByHandle(TexposHandle handle) {
if (!handle || !enabler) if (!handle || !enabler)
return -1; return -1;
if (g_handle_to_reserved_texpos.contains(handle))
return g_handle_to_reserved_texpos[handle];
if (g_handle_to_texpos.contains(handle)) if (g_handle_to_texpos.contains(handle))
return g_handle_to_texpos[handle]; return g_handle_to_texpos[handle];
if (g_handle_to_surface.contains(handle)) { if (g_handle_to_surface.contains(handle)) {
if (loading_state) {
ERR(textures).printerr("unable reinit texture from dynamic range during loading\n");
return -1;
}
g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game
auto texpos = add_texture(g_handle_to_surface[handle]); auto texpos = add_texture(g_handle_to_surface[handle]);
g_handle_to_texpos.emplace(handle, texpos); g_handle_to_texpos.emplace(handle, texpos);
@ -166,22 +219,24 @@ long Textures::getTexposByHandle(TexposHandle handle) {
return -1; return -1;
} }
TexposHandle Textures::createTile(std::vector<uint32_t>& pixels, int tile_px_w, int tile_px_h) { TexposHandle Textures::createTile(std::vector<uint32_t>& pixels, int tile_px_w, int tile_px_h,
bool reserved) {
if (!enabler) if (!enabler)
return 0; return 0;
auto texture = create_texture(pixels, tile_px_w, tile_px_h); auto texture = create_texture(pixels, tile_px_w, tile_px_h);
auto handle = Textures::loadTexture(texture); auto handle = Textures::loadTexture(texture, reserved);
return handle; return handle;
} }
std::vector<TexposHandle> Textures::createTileset(std::vector<uint32_t>& pixels, int texture_px_w, std::vector<TexposHandle> Textures::createTileset(std::vector<uint32_t>& pixels, int texture_px_w,
int texture_px_h, int tile_px_w, int tile_px_h) { int texture_px_h, int tile_px_w, int tile_px_h,
bool reserved) {
if (!enabler) if (!enabler)
return std::vector<TexposHandle>{}; return std::vector<TexposHandle>{};
auto texture = create_texture(pixels, texture_px_w, texture_px_h); auto texture = create_texture(pixels, texture_px_w, texture_px_h);
auto handles = slice_tileset(texture, tile_px_w, tile_px_h); auto handles = slice_tileset(texture, tile_px_w, tile_px_h, reserved);
return handles; return handles;
} }
@ -192,6 +247,8 @@ void Textures::deleteHandle(TexposHandle handle) {
auto texpos = Textures::getTexposByHandle(handle); auto texpos = Textures::getTexposByHandle(handle);
if (texpos > 0) if (texpos > 0)
delete_texture(texpos); delete_texture(texpos);
if (g_handle_to_reserved_texpos.contains(handle))
g_handle_to_reserved_texpos.erase(handle);
if (g_handle_to_texpos.contains(handle)) if (g_handle_to_texpos.contains(handle))
g_handle_to_texpos.erase(handle); g_handle_to_texpos.erase(handle);
if (g_handle_to_surface.contains(handle)) { if (g_handle_to_surface.contains(handle)) {
@ -207,7 +264,18 @@ static void reset_texpos() {
g_handle_to_texpos.clear(); g_handle_to_texpos.clear();
} }
static void reset_reserved_texpos() {
DEBUG(textures).print("resetting reserved texture mappings\n");
g_handle_to_reserved_texpos.clear();
}
static void reset_tilesets() {
DEBUG(textures).print("resetting tileset to handle mappings\n");
g_tileset_to_handles.clear();
}
static void reset_surface() { static void reset_surface() {
DEBUG(textures).print("deleting cached surfaces\n");
for (auto& entry : g_handle_to_surface) { for (auto& entry : g_handle_to_surface) {
DFSDL_FreeSurface(entry.second); DFSDL_FreeSurface(entry.second);
} }
@ -220,7 +288,9 @@ struct tracking_stage_new_region : df::viewscreen_new_regionst {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_raw_load_stage != this->raw_load_stage) { if (this->m_raw_load_stage != this->raw_load_stage) {
TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage, this->raw_load_stage); TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage,
this->raw_load_stage);
loading_state = this->raw_load_stage >= 0 && this->raw_load_stage < 3 ? true : false;
this->m_raw_load_stage = this->raw_load_stage; this->m_raw_load_stage = this->raw_load_stage;
if (this->m_raw_load_stage == 1) if (this->m_raw_load_stage == 1)
reset_texpos(); reset_texpos();
@ -240,6 +310,7 @@ struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) { if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
this->m_cur_step = this->cur_step; this->m_cur_step = this->cur_step;
if (this->m_cur_step == 1) if (this->m_cur_step == 1)
reset_texpos(); reset_texpos();
@ -259,6 +330,7 @@ struct tracking_stage_load_region : df::viewscreen_loadgamest {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) { if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
this->m_cur_step = this->cur_step; this->m_cur_step = this->cur_step;
if (this->m_cur_step == 1) if (this->m_cur_step == 1)
reset_texpos(); reset_texpos();
@ -278,6 +350,7 @@ struct tracking_stage_new_arena : df::viewscreen_new_arenast {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) { if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
this->m_cur_step = this->cur_step; this->m_cur_step = this->cur_step;
if (this->m_cur_step == 0) if (this->m_cur_step == 0)
reset_texpos(); reset_texpos();
@ -304,12 +377,25 @@ static void uninstall_reset_point() {
INTERPOSE_HOOK(tracking_stage_new_arena, logic).remove(); INTERPOSE_HOOK(tracking_stage_new_arena, logic).remove();
} }
static void reserve_static_range() {
reserved_range.init(enabler->textures.init_texture_size);
auto dummy_surface =
DFSDL_CreateRGBSurfaceWithFormat(0, 0, 0, 32, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32);
for (int32_t i = 0; i < Reserved::size; i++) {
add_texture(dummy_surface);
}
enabler->textures.init_texture_size += Reserved::size;
}
void Textures::init(color_ostream& out) { void Textures::init(color_ostream& out) {
if (!enabler) if (!enabler)
return; return;
reserve_static_range();
install_reset_point(); install_reset_point();
DEBUG(textures, out).print("dynamic texture loading ready"); DEBUG(textures, out)
.print("dynamic texture loading ready, reserved range %d-%d\n", reserved_range.start,
reserved_range.end);
} }
void Textures::cleanup() { void Textures::cleanup() {
@ -317,6 +403,8 @@ void Textures::cleanup() {
return; return;
reset_texpos(); reset_texpos();
reset_reserved_texpos();
reset_tilesets();
reset_surface(); reset_surface();
uninstall_reset_point(); uninstall_reset_point();
} }

@ -5,8 +5,8 @@ local helpdb = require('helpdb')
local overlay = require('plugins.overlay') local overlay = require('plugins.overlay')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12) local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12, true)
local logo_hovered_textures = dfhack.textures.loadTileset('hack/data/art/logo_hovered.png', 8, 12) local logo_hovered_textures = dfhack.textures.loadTileset('hack/data/art/logo_hovered.png', 8, 12, true)
local function get_command(cmdline) local function get_command(cmdline)
local first_word = cmdline:trim():split(' +')[1] local first_word = cmdline:trim():split(' +')[1]
@ -17,8 +17,8 @@ end
function should_hide_armok(cmdline) function should_hide_armok(cmdline)
local command = get_command(cmdline) local command = get_command(cmdline)
return dfhack.getHideArmokTools() and return dfhack.getHideArmokTools() and
helpdb.is_entry(command) and helpdb.is_entry(command) and
helpdb.get_entry_tags(command).armok helpdb.get_entry_tags(command).armok
end end
-- ----------------- -- -- ----------------- --
@ -26,11 +26,11 @@ end
-- ----------------- -- -- ----------------- --
HotspotMenuWidget = defclass(HotspotMenuWidget, overlay.OverlayWidget) HotspotMenuWidget = defclass(HotspotMenuWidget, overlay.OverlayWidget)
HotspotMenuWidget.ATTRS{ HotspotMenuWidget.ATTRS {
default_pos={x=5,y=1}, default_pos = { x = 5, y = 1 },
default_enabled=true, default_enabled = true,
version=2, version = 2,
viewscreens={ viewscreens = {
'adopt_region', 'adopt_region',
'choose_game_type', 'choose_game_type',
-- 'choose_start_site', -- conflicts with vanilla panel layouts -- 'choose_start_site', -- conflicts with vanilla panel layouts
@ -48,51 +48,51 @@ HotspotMenuWidget.ATTRS{
'update_region', 'update_region',
'world' 'world'
}, },
frame={w=4, h=3} frame = { w = 4, h = 3 }
} }
function HotspotMenuWidget:init() function HotspotMenuWidget:init()
local to_pen = dfhack.pen.parse local to_pen = dfhack.pen.parse
local function tp(idx, ch) local function tp(idx, ch)
return to_pen{ return to_pen {
tile=function() return dfhack.textures.getTexposByHandle(logo_textures[idx]) end, tile = function() return dfhack.textures.getTexposByHandle(logo_textures[idx]) end,
ch=ch, ch = ch,
fg=COLOR_GREY, fg = COLOR_GREY,
} }
end end
local function tph(idx, ch) local function tph(idx, ch)
return to_pen{ return to_pen {
tile=function() return dfhack.textures.getTexposByHandle(logo_hovered_textures[idx]) end, tile = function() return dfhack.textures.getTexposByHandle(logo_hovered_textures[idx]) end,
ch=ch, ch = ch,
fg=COLOR_WHITE, fg = COLOR_WHITE,
} }
end end
local function get_tile_token(idx, ch) local function get_tile_token(idx, ch)
return { return {
tile=tp(idx, ch), tile = tp(idx, ch),
htile=tph(idx, ch), htile = tph(idx, ch),
width=1, width = 1,
} }
end end
self:addviews{ self:addviews {
widgets.Label{ widgets.Label {
text={ text = {
get_tile_token(1, '!'), get_tile_token(2, 'D'), get_tile_token(3, 'F'), get_tile_token(4, '!'), NEWLINE, get_tile_token(1, '!'), get_tile_token(2, 'D'), get_tile_token(3, 'F'), get_tile_token(4, '!'), NEWLINE,
get_tile_token(5, '!'), get_tile_token(6, 'H'), get_tile_token(7, 'a'), get_tile_token(8, '!'), NEWLINE, get_tile_token(5, '!'), get_tile_token(6, 'H'), get_tile_token(7, 'a'), get_tile_token(8, '!'), NEWLINE,
get_tile_token(9, '!'), get_tile_token(10, 'c'), get_tile_token(11, 'k'), get_tile_token(12, '!'), get_tile_token(9, '!'), get_tile_token(10, 'c'), get_tile_token(11, 'k'), get_tile_token(12, '!'),
}, },
on_click=function() dfhack.run_command('hotkeys') end, on_click = function() dfhack.run_command('hotkeys') end,
}, },
} }
end end
function HotspotMenuWidget:overlay_trigger() function HotspotMenuWidget:overlay_trigger()
return MenuScreen{hotspot=self}:show() return MenuScreen { hotspot = self }:show()
end end
-- register the menu hotspot with the overlay -- register the menu hotspot with the overlay
OVERLAY_WIDGETS = {menu=HotspotMenuWidget} OVERLAY_WIDGETS = { menu = HotspotMenuWidget }
-- ---- -- -- ---- --
-- Menu -- -- Menu --
@ -103,15 +103,15 @@ local MAX_LIST_WIDTH = 45
local MAX_LIST_HEIGHT = 15 local MAX_LIST_HEIGHT = 15
Menu = defclass(Menu, widgets.Panel) Menu = defclass(Menu, widgets.Panel)
Menu.ATTRS{ Menu.ATTRS {
hotspot=DEFAULT_NIL, hotspot = DEFAULT_NIL,
} }
-- get a map from the binding string to a list of hotkey strings that all -- get a map from the binding string to a list of hotkey strings that all
-- point to that binding -- point to that binding
local function get_bindings_to_hotkeys(hotkeys, bindings) local function get_bindings_to_hotkeys(hotkeys, bindings)
local bindings_to_hotkeys = {} local bindings_to_hotkeys = {}
for _,hotkey in ipairs(hotkeys) do for _, hotkey in ipairs(hotkeys) do
local binding = bindings[hotkey] local binding = bindings[hotkey]
table.insert(ensure_key(bindings_to_hotkeys, binding), hotkey) table.insert(ensure_key(bindings_to_hotkeys, binding), hotkey)
end end
@ -126,17 +126,17 @@ local function get_choices(hotkeys, bindings, is_inverted)
local bindings_to_hotkeys = get_bindings_to_hotkeys(hotkeys, bindings) local bindings_to_hotkeys = get_bindings_to_hotkeys(hotkeys, bindings)
-- build list choices -- build list choices
for _,hotkey in ipairs(hotkeys) do for _, hotkey in ipairs(hotkeys) do
local command = bindings[hotkey] local command = bindings[hotkey]
if seen[command] then goto continue end if seen[command] then goto continue end
seen[command] = true seen[command] = true
local hk_width, tokens = 0, {} local hk_width, tokens = 0, {}
for _,hk in ipairs(bindings_to_hotkeys[command]) do for _, hk in ipairs(bindings_to_hotkeys[command]) do
if hk_width ~= 0 then if hk_width ~= 0 then
table.insert(tokens, ', ') table.insert(tokens, ', ')
hk_width = hk_width + 2 hk_width = hk_width + 2
end end
table.insert(tokens, {text=hk, pen=COLOR_LIGHTGREEN}) table.insert(tokens, { text = hk, pen = COLOR_LIGHTGREEN })
hk_width = hk_width + #hk hk_width = hk_width + #hk
end end
local command_str = command local command_str = command
@ -144,16 +144,20 @@ local function get_choices(hotkeys, bindings, is_inverted)
local max_command_len = MAX_LIST_WIDTH - hk_width - LIST_BUFFER local max_command_len = MAX_LIST_WIDTH - hk_width - LIST_BUFFER
command_str = command:sub(1, max_command_len - 3) .. '...' command_str = command:sub(1, max_command_len - 3) .. '...'
end end
table.insert(tokens, 1, {text=command_str}) table.insert(tokens, 1, { text = command_str })
local choice = {icon=ARROW, command=command, text=tokens, local choice = {
hk_width=hk_width} icon = ARROW,
command = command,
text = tokens,
hk_width = hk_width
}
max_width = math.max(max_width, hk_width + #command_str + LIST_BUFFER) max_width = math.max(max_width, hk_width + #command_str + LIST_BUFFER)
table.insert(choices, is_inverted and 1 or #choices + 1, choice) table.insert(choices, is_inverted and 1 or #choices + 1, choice)
::continue:: ::continue::
end end
-- adjust width of command fields so the hotkey tokens are right justified -- adjust width of command fields so the hotkey tokens are right justified
for _,choice in ipairs(choices) do for _, choice in ipairs(choices) do
local command_token = choice.text[1] local command_token = choice.text[1]
command_token.width = max_width - choice.hk_width - (LIST_BUFFER - 1) command_token.width = max_width - choice.hk_width - (LIST_BUFFER - 1)
end end
@ -164,17 +168,17 @@ end
function Menu:init() function Menu:init()
local hotkeys, bindings = getHotkeys() local hotkeys, bindings = getHotkeys()
if #hotkeys == 0 then if #hotkeys == 0 then
hotkeys = {''} hotkeys = { '' }
bindings = {['']='gui/launcher'} bindings = { [''] = 'gui/launcher' }
end end
local is_inverted = not not self.hotspot.frame.b local is_inverted = not not self.hotspot.frame.b
local choices,list_width = get_choices(hotkeys, bindings, is_inverted) local choices, list_width = get_choices(hotkeys, bindings, is_inverted)
list_width = math.max(35, list_width) list_width = math.max(35, list_width)
local list_frame = copyall(self.hotspot.frame) local list_frame = copyall(self.hotspot.frame)
local list_widget_frame = {h=math.min(#choices, MAX_LIST_HEIGHT)} local list_widget_frame = { h = math.min(#choices, MAX_LIST_HEIGHT) }
local quickstart_frame = {} local quickstart_frame = {}
list_frame.w = list_width + 2 list_frame.w = list_width + 2
list_frame.h = list_widget_frame.h + 4 list_frame.h = list_widget_frame.h + 4
@ -193,51 +197,51 @@ function Menu:init()
list_frame.r = math.max(0, list_frame.r + 5) list_frame.r = math.max(0, list_frame.r + 5)
end end
local help_frame = {w=list_frame.w, l=list_frame.l, r=list_frame.r} local help_frame = { w = list_frame.w, l = list_frame.l, r = list_frame.r }
if list_frame.t then if list_frame.t then
help_frame.t = list_frame.t + list_frame.h help_frame.t = list_frame.t + list_frame.h
else else
help_frame.b = list_frame.b + list_frame.h help_frame.b = list_frame.b + list_frame.h
end end
self:addviews{ self:addviews {
widgets.Panel{ widgets.Panel {
view_id='list_panel', view_id = 'list_panel',
frame=list_frame, frame = list_frame,
frame_style=gui.PANEL_FRAME, frame_style = gui.PANEL_FRAME,
frame_background=gui.CLEAR_PEN, frame_background = gui.CLEAR_PEN,
subviews={ subviews = {
widgets.List{ widgets.List {
view_id='list', view_id = 'list',
frame=list_widget_frame, frame = list_widget_frame,
choices=choices, choices = choices,
icon_width=2, icon_width = 2,
on_select=self:callback('onSelect'), on_select = self:callback('onSelect'),
on_submit=self:callback('onSubmit'), on_submit = self:callback('onSubmit'),
on_submit2=self:callback('onSubmit2'), on_submit2 = self:callback('onSubmit2'),
}, },
widgets.Panel{frame={h=1}}, widgets.Panel { frame = { h = 1 } },
widgets.HotkeyLabel{ widgets.HotkeyLabel {
frame=quickstart_frame, frame = quickstart_frame,
label='Quickstart guide', label = 'Quickstart guide',
key='STRING_A063', key = 'STRING_A063',
on_activate=function() on_activate = function()
self:onSubmit(nil, {command='quickstart-guide'}) self:onSubmit(nil, { command = 'quickstart-guide' })
end, end,
}, },
}, },
}, },
widgets.ResizingPanel{ widgets.ResizingPanel {
view_id='help_panel', view_id = 'help_panel',
autoarrange_subviews=true, autoarrange_subviews = true,
frame=help_frame, frame = help_frame,
frame_style=gui.PANEL_FRAME, frame_style = gui.PANEL_FRAME,
frame_background=gui.CLEAR_PEN, frame_background = gui.CLEAR_PEN,
subviews={ subviews = {
widgets.WrappedLabel{ widgets.WrappedLabel {
view_id='help', view_id = 'help',
text_to_wrap='', text_to_wrap = '',
scroll_keys={}, scroll_keys = {},
}, },
}, },
}, },
@ -252,7 +256,7 @@ function Menu:onSelect(_, choice)
if not choice or #self.subviews == 0 then return end if not choice or #self.subviews == 0 then return end
local command = get_command(choice.command) local command = get_command(choice.command)
self.subviews.help.text_to_wrap = helpdb.is_entry(command) and self.subviews.help.text_to_wrap = helpdb.is_entry(command) and
helpdb.get_entry_short_help(command) or 'Command not found' helpdb.get_entry_short_help(command) or 'Command not found'
self.subviews.help_panel:updateLayout() self.subviews.help_panel:updateLayout()
end end
@ -302,7 +306,7 @@ end
function Menu:getMouseFramePos() function Menu:getMouseFramePos()
return self.subviews.list_panel:getMouseFramePos() or return self.subviews.list_panel:getMouseFramePos() or
self.subviews.help_panel:getMouseFramePos() self.subviews.help_panel:getMouseFramePos()
end end
function Menu:onRenderBody(dc) function Menu:onRenderBody(dc)
@ -324,14 +328,14 @@ end
MenuScreen = defclass(MenuScreen, gui.ZScreen) MenuScreen = defclass(MenuScreen, gui.ZScreen)
MenuScreen.ATTRS { MenuScreen.ATTRS {
focus_path='hotkeys/menu', focus_path = 'hotkeys/menu',
initial_pause=false, initial_pause = false,
hotspot=DEFAULT_NIL, hotspot = DEFAULT_NIL,
} }
function MenuScreen:init() function MenuScreen:init()
self:addviews{ self:addviews {
Menu{hotspot=self.hotspot}, Menu { hotspot = self.hotspot },
} }
end end

@ -36,7 +36,7 @@ namespace DFHack {
static std::vector<TexposHandle> textures; static std::vector<TexposHandle> textures;
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32); textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32, true);
return CR_OK; return CR_OK;
} }