write docs and make implementation match the docs

added ability to enable/disable all
added showing current widget configuration
added moving widget back to default coordinates
develop
myk002 2022-11-09 14:08:38 -08:00
parent ffc09ff25d
commit 9bdc995f20
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
2 changed files with 138 additions and 35 deletions

@ -2,19 +2,66 @@ overlay
======= =======
.. dfhack-tool:: .. dfhack-tool::
:summary: Provide an on-screen clickable DFHack launcher button. :summary: Manage on-screen overlay widgets.
:tags: dfhack interface :tags: dfhack interface
This tool places a small button in the lower left corner of the screen that you The overlay framework manages the on-screen widgets that other tools (including
can click to run DFHack commands with `gui/launcher`. 3rd party plugins and scripts) can register for display. If you are a developer
who wants to write an overlay widget, please see the `overlay-widget-guide`.
If you would rather always run `gui/launcher` with the hotkeys, or just don't
want the DFHack button on-screen, just disable the plugin with
``disable overlay``.
Usage Usage
----- -----
:: ``enable overlay``
Display enabled widgets.
``overlay enable|disable all|<name or list number> [<name or list number> ...]``
Enable/disable all or specified widgets. Widgets can be specified by either
their name or their number, as returned by ``overlay list``.
``overlay list``
Show a list of all the widgets that are registered with the overlay
framework.
``overlay position <name or list number> [default|<x> <y>]``
Display configuration information for the given widget or change the
position where it is rendered. See the `Widget position`_ section below for
details.
``overlay trigger <name or list number>``
Intended to be used by keybindings for manually triggering a widget. For
example, you could use an ``overlay trigger`` keybinding to show a menu that
normally appears when you hover the mouse over a screen hotspot.
Examples
--------
``overlay enable all``
Enable all widgets. Note that they will only be displayed on the screens
that they are associated with. You can see which screens a widget will be
displayed on, along with whether the widget is a hotspot, by calling
``overlay position``.
``overlay position hotkeys.menu``
Show the current configuration of the `hotkeys` menu widget.
``overlay position dwarfmonitor.cursor -2 -3``
Display the `dwarfmonitor` cursor position reporting widget in the lower
right corner of the screen, 2 tiles from the left and 3 tiles from the
bottom.
``overlay position dwarfmonitor.cursor default``
Reset the `dwarfmonitor` cursor position to its default.
``overlay trigger hotkeys.menu``
Trigger the `hotkeys` menu widget so that it shows its popup menu. This is
what is run when you hit :kbd:`Ctrl`:kbd:`Shift`:kbd:`C`.
Widget position
---------------
Widgets can be positioned at any (``x``, ``y``) position on the screen, and can
be specified relative to any edge. Coordinates are 1-based, which means that
``1`` is the far left column (for ``x``) or the top row (for ``y``). Negative
numbers are measured from the right of the screen to the right edge of the
widget or from the bottom of the screen to the bottom of the widget,
respectively.
For easy reference, the corners can be found at the following coordinates:
enable overlay :(1, 1): top left corner
:(-1, 1): top right corner
:(1, -1): lower left corner
:(-1, -1): lower right corner

@ -14,6 +14,7 @@ local DEFAULT_X_POS, DEFAULT_Y_POS = -2, -2
-- state and config -- -- state and config --
-- ---------------- -- -- ---------------- --
local active_triggered_widget = nil
local active_triggered_screen = nil -- if non-nil, hotspots will not get updates local active_triggered_screen = nil -- if non-nil, hotspots will not get updates
local widget_db = {} -- map of widget name to ephermeral state local widget_db = {} -- map of widget name to ephermeral state
local widget_index = {} -- ordered list of widget names local widget_index = {} -- ordered list of widget names
@ -25,6 +26,7 @@ local function reset()
if active_triggered_screen then if active_triggered_screen then
active_triggered_screen:dismiss() active_triggered_screen:dismiss()
end end
active_triggered_widget = nil
active_triggered_screen = nil active_triggered_screen = nil
widget_db = {} widget_db = {}
@ -47,6 +49,7 @@ end
local function triggered_screen_has_lock() local function triggered_screen_has_lock()
if not active_triggered_screen then return false end if not active_triggered_screen then return false end
if active_triggered_screen:isActive() then return true end if active_triggered_screen:isActive() then return true end
active_triggered_widget = nil
active_triggered_screen = nil active_triggered_screen = nil
return false return false
end end
@ -108,7 +111,12 @@ local function get_name(name_or_number)
end end
local function do_by_names_or_numbers(args, fn) local function do_by_names_or_numbers(args, fn)
for _,name_or_number in ipairs(normalize_list(args)) do local arglist = normalize_list(args)
if #arglist == 0 then
dfhack.printerr('please specify a widget name or list number')
return
end
for _,name_or_number in ipairs(arglist) do
local name = get_name(name_or_number) local name = get_name(name_or_number)
local db_entry = widget_db[name] local db_entry = widget_db[name]
if not db_entry then if not db_entry then
@ -120,7 +128,7 @@ local function do_by_names_or_numbers(args, fn)
end end
local function do_enable(args, quiet, skip_save) local function do_enable(args, quiet, skip_save)
do_by_names_or_numbers(args, function(name, db_entry) local enable_fn = function(name, db_entry)
overlay_config[name].enabled = true overlay_config[name].enabled = true
if db_entry.widget.hotspot then if db_entry.widget.hotspot then
active_hotspot_widgets[name] = db_entry active_hotspot_widgets[name] = db_entry
@ -132,14 +140,23 @@ local function do_enable(args, quiet, skip_save)
if not quiet then if not quiet then
print(('enabled widget %s'):format(name)) print(('enabled widget %s'):format(name))
end end
end) end
if args[1] == 'all' then
for name,db_entry in pairs(widget_db) do
if not overlay_config[name].enabled then
enable_fn(name, db_entry)
end
end
else
do_by_names_or_numbers(args, enable_fn)
end
if not skip_save then if not skip_save then
save_config() save_config()
end end
end end
local function do_disable(args) local function do_disable(args)
do_by_names_or_numbers(args, function(name, db_entry) local disable_fn = function(name, db_entry)
overlay_config[name].enabled = false overlay_config[name].enabled = false
if db_entry.widget.hotspot then if db_entry.widget.hotspot then
active_hotspot_widgets[name] = nil active_hotspot_widgets[name] = nil
@ -152,7 +169,16 @@ local function do_disable(args)
end end
end end
print(('disabled widget %s'):format(name)) print(('disabled widget %s'):format(name))
end) end
if args[1] == 'all' then
for name,db_entry in pairs(widget_db) do
if overlay_config[name].enabled then
disable_fn(name, db_entry)
end
end
else
do_by_names_or_numbers(args, disable_fn)
end
save_config() save_config()
end end
@ -175,11 +201,10 @@ local function do_list(args)
end end
local db_entry = widget_db[name] local db_entry = widget_db[name]
local enabled = overlay_config[name].enabled local enabled = overlay_config[name].enabled
dfhack.color(enabled and COLOR_YELLOW or COLOR_LIGHTGREEN) dfhack.color(enabled and COLOR_LIGHTGREEN or COLOR_YELLOW)
dfhack.print(enabled and '[enabled] ' or '[disabled]') dfhack.print(enabled and '[enabled] ' or '[disabled]')
dfhack.color() dfhack.color()
print((' %d) %s%s'):format(i, name, print((' %d) %s'):format(i, name))
db_entry.widget.overlay_trigger and ' (can trigger)' or ''))
::continue:: ::continue::
end end
if num_filtered > 0 then if num_filtered > 0 then
@ -225,7 +250,7 @@ local function load_widgets(env_prefix, provider, env_fn)
end end
end end
-- also called directly from cpp on init -- called directly from cpp on init
function reload() function reload()
reset() reset()
@ -256,18 +281,44 @@ function reload()
reposition_widgets() reposition_widgets()
end end
local function do_reload() local function dump_widget_config(name, widget)
reload() local pos = overlay_config[name].pos
print('reloaded overlay configuration') print(('widget %s is positioned at x=%d, y=%d'):format(name, pos.x, pos.y))
if #widget.viewscreens > 0 then
print(' it will be attached to the following viewscreens:')
for _,vs in ipairs(widget.viewscreens) do
print((' %s'):format(vs))
end
end
if widget.hotspot then
print(' on all screens it will act as a hotspot')
end
end end
local function do_reposition(args) local function do_position(args)
local name_or_number, x, y = table.unpack(args) local name_or_number, x, y = table.unpack(args)
local name = get_name(name_or_number) local name = get_name(name_or_number)
-- TODO: check existence of widget, validate numbers, warn if offscreen if not widget_db[name] then
local pos = sanitize_pos{x=tonumber(x), y=tonumber(y)} if not name_or_number then
overlay_config[name].pos = pos dfhack.printerr('please specify a widget name or list number')
else
dfhack.printerr(('widget not found: "%s"'):format(name))
end
return
end
local widget = widget_db[name].widget local widget = widget_db[name].widget
local pos
if x == 'default' then
pos = sanitize_pos(widget.default_pos)
else
x, y = tonumber(x), tonumber(y)
if not x or not y then
dump_widget_config(name, widget)
return
end
pos = sanitize_pos{x=x, y=y}
end
overlay_config[name].pos = pos
widget.frame = make_frame(pos, widget.frame) widget.frame = make_frame(pos, widget.frame)
widget:updateLayout(get_screen_rect()) widget:updateLayout(get_screen_rect())
save_config() save_config()
@ -277,8 +328,8 @@ end
-- note that the widget does not have to be enabled to be triggered -- note that the widget does not have to be enabled to be triggered
local function do_trigger(args) local function do_trigger(args)
if triggered_screen_has_lock() then if triggered_screen_has_lock() then
dfhack.printerr( dfhack.printerr(('cannot trigger widget; widget "%s" is already active')
'cannot trigger widget; another widget is already active') :format(active_triggered_widget))
return return
end end
local target = args[1] local target = args[1]
@ -286,6 +337,9 @@ local function do_trigger(args)
local widget = db_entry.widget local widget = db_entry.widget
if widget.overlay_trigger then if widget.overlay_trigger then
active_triggered_screen = widget:overlay_trigger() active_triggered_screen = widget:overlay_trigger()
if active_triggered_screen then
active_triggered_widget = name
end
print(('triggered widget %s'):format(name)) print(('triggered widget %s'):format(name))
end end
end) end)
@ -295,8 +349,7 @@ local command_fns = {
enable=do_enable, enable=do_enable,
disable=do_disable, disable=do_disable,
list=do_list, list=do_list,
reload=do_reload, position=do_position,
reposition=do_reposition,
trigger=do_trigger, trigger=do_trigger,
} }
@ -325,7 +378,7 @@ end
-- reduces the next call by a small random amount to introduce jitter into the -- reduces the next call by a small random amount to introduce jitter into the
-- widget processing timings -- widget processing timings
local function do_update(db_entry, now_ms, vs) local function do_update(name, db_entry, now_ms, vs)
if db_entry.next_update_ms > now_ms then return end if db_entry.next_update_ms > now_ms then return end
local w = db_entry.widget local w = db_entry.widget
local freq_ms = w.overlay_onupdate_max_freq_seconds * 1000 local freq_ms = w.overlay_onupdate_max_freq_seconds * 1000
@ -334,15 +387,18 @@ local function do_update(db_entry, now_ms, vs)
if detect_frame_change(w, if detect_frame_change(w,
function() return w:overlay_onupdate(vs) end) then function() return w:overlay_onupdate(vs) end) then
active_triggered_screen = w:overlay_trigger() active_triggered_screen = w:overlay_trigger()
if active_triggered_screen then return true end if active_triggered_screen then
active_triggered_widget = name
return true
end
end end
end end
function update_hotspot_widgets() function update_hotspot_widgets()
if triggered_screen_has_lock() then return end if triggered_screen_has_lock() then return end
local now_ms = dfhack.getTickCount() local now_ms = dfhack.getTickCount()
for _,db_entry in pairs(active_hotspot_widgets) do for name,db_entry in pairs(active_hotspot_widgets) do
if do_update(db_entry, now_ms) then return end if do_update(name, db_entry, now_ms) then return end
end end
end end
@ -350,8 +406,8 @@ function update_viewscreen_widgets(vs_name, vs)
local vs_widgets = active_viewscreen_widgets[vs_name] local vs_widgets = active_viewscreen_widgets[vs_name]
if not vs_widgets then return end if not vs_widgets then return end
local now_ms = dfhack.getTickCount() local now_ms = dfhack.getTickCount()
for _,db_entry in pairs(vs_widgets) do for name,db_entry in pairs(vs_widgets) do
if do_update(db_entry, now_ms, vs) then return end if do_update(name, db_entry, now_ms, vs) then return end
end end
end end