diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index b0cb2f0c2..90852f9fe 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -1,3 +1,83 @@ local _ENV = mkmodule('plugins.overlay') +local widgets = require('gui.widgets') + +local widget_db = {} -- map of widget name to state +local active_hotspot_widgets = {} -- map of widget names to the db entry +local active_viewscreen_widgets = {} -- map of vs_name to map of w.names -> db +local active_triggered_screen = nil + +function reload() + widget_db = {} + active_hotspot_widgets = {} + active_viewscreen_widgets = {} + active_triggered_screen = nil +end + +-- reduces the next call by a small random amount to introduce jitter into the +-- widget processing timings +local function do_update(db_entry, now_ms, vs) + if db_entry.next_update_ms > now_ms then return end + local w = db_entry.widget + local freq_ms = w.overlay_onupdate_max_freq_seconds * 1000 + local jitter = math.rand(0, freq_ms // 8) -- up to ~12% jitter + db_entry.next_update_ms = now_ms + freq_ms - jitter + if w:overlay_onupdate(vs) then + active_triggered_screen = w:overlay_trigger() + if active_triggered_screen then return true end + end +end + +function update_hotspot_widgets() + if active_triggered_screen then + if active_triggered_screen:isActive() then return end + active_triggered_screen = nil + end + local now_ms = dfhack.getTickCount() + for _,db_entry in pairs(active_hotspot_widgets) do + if do_update(db_entry, now_ms) then return end + end +end + +function update_viewscreen_widgets(vs_name, vs) + local vs_widgets = active_viewscreen_widgets[vs_name] + if not vs_widgets then return end + local now_ms = dfhack.getTickCount() + for _,db_entry in pairs(vs_widgets) do + if do_update(db_entry, now_ms, vs) then return end + end +end + +function feed_viewscreen_widgets(vs_name, keys) + local vs_widgets = active_viewscreen_widgets[vs_name] + if not vs_widgets then return false end + for _,db_entry in pairs(vs_widgets) do + if db_entry.widget:onInput(keys) then return true end + end + return false +end + +function render_viewscreen_widgets(vs_name) + local vs_widgets = active_viewscreen_widgets[vs_name] + if not vs_widgets then return false end + local dc = Painter.new() + for _,db_entry in pairs(vs_widgets) do + db_entry.widget:render(dc) + end +end + +-- called when the DF window is resized +function reposition_widgets() + local w, h = dscreen.getWindowSize() + local vr = ViewRect{rect=mkdims_wh(0, 0, w, h)} + for _,db_entry in pairs(widget_db) do + db_entry.widget:updateLayout(vr) + end +end + +OverlayWidget = defclass(OverlayWidget, widgets.Widget) +OverlayWidget.ATTRS{ + overlay_onupdate_max_freq_seconds=5, +} + return _ENV diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 0c63e53e8..ad7a6ab53 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -88,6 +88,8 @@ #include "PluginManager.h" #include "VTableInterpose.h" +#include "modules/Screen.h" + using namespace DFHack; DFHACK_PLUGIN("overlay"); @@ -98,18 +100,76 @@ namespace DFHack { DBG_DECLARE(overlay, event, DebugCategory::LINFO); } +static df::coord2d screenSize; + +template +static void call_overlay_lua(const char *fn_name, int nargs, int nres, + FA && args_lambda, + FR && res_lambda) { + DEBUG(event).print("calling overlay lua function: '%s'\n", fn_name); + + CoreSuspender guard; + + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + color_ostream &out = Core::getInstance().getConsole(); + + if (!lua_checkstack(L, 1 + nargs) || + !Lua::PushModulePublic( + out, L, "plugins.overlay", fn_name)) { + out.printerr("Failed to load overlay Lua code\n"); + return; + } + + std::forward(args_lambda)(L); + + if (!Lua::SafeCall(out, L, nargs, nres)) + out.printerr("Failed Lua call to '%s'\n", fn_name); + + std::forward(res_lambda)(L); +} + +static auto DEFAULT_LAMBDA = [](lua_State *){}; +template +static void call_overlay_lua(const char *fn_name, int nargs, int nres, + FA && args_lambda) { + call_overlay_lua(fn_name, nargs, nres, args_lambda, DEFAULT_LAMBDA); +} + +static void call_overlay_lua(const char *fn_name) { + call_overlay_lua(fn_name, 0, 0, DEFAULT_LAMBDA, DEFAULT_LAMBDA); +} + template struct viewscreen_overlay : T { typedef T interpose_base; DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { INTERPOSE_NEXT(logic)(); + call_overlay_lua("update_viewscreen_widgets", 2, 0, [&](lua_State *L) { + Lua::Push(L, T::_identity.getName()); + Lua::Push(L, this); + }); } DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { - INTERPOSE_NEXT(feed)(input); + bool input_is_handled = false; + call_overlay_lua("feed_viewscreen_widgets", 2, 1, + [&](lua_State *L) { + Lua::Push(L, T::_identity.getName()); + Lua::PushInterfaceKeys(L, *input); + }, [&](lua_State *L) { + input_is_handled = lua_toboolean(L, -1); + }); + if (!input_is_handled) + INTERPOSE_NEXT(feed)(input); } DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); + call_overlay_lua("render_viewscreen_widgets", 2, 0, [&](lua_State *L) { + Lua::Push(L, T::_identity.getName()); + Lua::Push(L, this); + }); } }; @@ -320,6 +380,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector