clean up docs and code

develop
myk002 2022-11-11 10:36:58 -08:00
parent e992e302a7
commit 19289bf3c8
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
6 changed files with 348 additions and 224 deletions

@ -3898,6 +3898,8 @@ gui.widgets
This module implements some basic widgets based on the View infrastructure. This module implements some basic widgets based on the View infrastructure.
.. _widget:
Widget class Widget class
------------ ------------

@ -8,6 +8,7 @@ These pages are detailed guides covering DFHack tools.
:maxdepth: 1 :maxdepth: 1
/docs/guides/examples-guide /docs/guides/examples-guide
/docs/guides/overlay-dev-guide
/docs/guides/modding-guide /docs/guides/modding-guide
/docs/guides/quickfort-library-guide /docs/guides/quickfort-library-guide
/docs/guides/quickfort-user-guide /docs/guides/quickfort-user-guide

@ -0,0 +1,302 @@
.. _overlay-dev-guide:
DFHack overlay dev guide
=========================
.. highlight:: lua
This guide walks you through how to build overlay widgets and register them with
the `overlay` framework for injection into Dwarf Fortress viewscreens.
Why would I want to create an overlay widget?
---------------------------------------------
There are both C++ and Lua APIs for creating viewscreens and drawing to the
screen. If you need very specific low-level control, those APIs might be the
right choice for you. However, here are some reasons you might want to implement
an overlay widget instead:
1. You can draw directly to an existing viewscreen instead of creating an
entirely new screen on the viewscreen stack. This allows the original
viewscreen to continue processing uninterrupted and keybindings bound to
that viewscreen will continue to function. This was previously only
achievable by C++ plugins.
1. You'll get a free UI for enabling/disabling your widget and repositioning it
on the screen. Widget state is saved for you and is automatically restored
when the game is restarted.
1. You don't have to manage the C++ interposing logic yourself and can focus on
the business logic, writing purely in Lua if desired.
In general, if you are writing a plugin or script and have anything you'd like
to add to an existing screen (including live updates of map tiles while the game
is unpaused), an overlay widget is probably your easiest path to get it done. If
your plugin or script doesn't otherwise need to be enabled to function, using
the overlay allows you to avoid writing any of the enable management code that
would normally be required for you to show info in the UI.
Overlay widget API
------------------
Overlay widgets are Lua classes that inherit from ``overlay.OverlayWidget``
(which itself inherits from `widgets.Widget <widget>`). The regular
``onInput(keys)``, ``onRenderFrame(dc, frame_rect)``, and ``onRenderBody(dc)``
functions work as normal, and they are called when the viewscreen that the
widget is associated with does its usual input and render processing. The widget
gets first dibs on input processing. If a widget returns ``true`` from its
``onInput()`` function, the viewscreen will not receive the input.
Overlay widgets can contain other Widgets and be as simple or complex as you
need them to be, just like you're building a regular UI element.
There are a few extra capabilities that overlay widgets have that take them
beyond your everyday ``Widget``:
- If an ``overlay_onupdate(viewscreen)`` function is defined, it will be called
just after the associated viewscreen's ``logic()`` function is called (i.e.
a "tick" or a (non-graphical) "frame"). For hotspot widgets, this function
will also get called after the top viewscreen's ``logic()`` function is
called, regardless of whether the widget is associated with that viewscreen.
If this function returns ``true``, then the widget's ``overlay_trigger()``
function is immediately called. Note that the ``viewscreen`` parameter will
be ``nil`` for hotspot widgets that are not also associated with the current
viewscreen.
- If an ``overlay_trigger()`` function is defined, will be called when the
widget's ``overlay_onupdate`` callback returns true or when the player uses
the CLI (or a keybinding calling the CLI) to trigger the widget. The
function must return either ``nil`` or the ``gui.Screen`` object that the
widget code has allocated, shown, and now owns. Hotspot widgets will receive
no callbacks from unassociated viewscreens until the returned screen is
dismissed. Unbound hotspot widgets **must** allocate a Screen with this
function if they want to react to the ``onInput()`` feed or be rendered. The
widgets owned by the overlay framework must not be attached to that new
screen, but the returned screen can instantiate and configure any new views
that it wants to.
If the widget can take up a variable amount of space on the screen, and you want
the widget to adjust its position according to the size of its contents, you can
modify ``self.frame.w`` and ``self.frame.h`` at any time -- in ``init()`` or in
any of the callbacks -- to indicate a new size. The overlay framework will
detect the size change and adjust the widget position and layout.
If you don't need to dynamically resize, just set ``self.frame.w`` and
``self.frame.h`` once in ``init()``.
Widget attributes
*****************
The ``overlay.OverlayWidget`` superclass defines the following class attributes:
- ``name``
This will be filled in with the display name of your widget, in case you
have multiple widgets with the same implementation but different
configurations.
- ``default_pos`` (default: ``{x=-2, y=-2}``)
Override this attribute with your desired default widget position. See
the `overlay` docs for information on what positive and negative numbers
mean for the position. Players can change the widget position at any time
via the `overlay position <overlay>` command, so don't assume that your
widget will always be at the default position.
- ``viewscreens`` (default: ``{}``)
The list of viewscreens that this widget should be associated with. When
one of these viewscreens is on top of the viewscreen stack, your widget's
callback functions for update, input, and render will be interposed into the
viewscreen's call path. The name of the viewscreen is the name of the DFHack
class that represents the viewscreen, minus the ``viewscreen_`` prefix and
``st`` suffix. For example, the fort mode main map viewscreen would be
``dwarfmode`` and the adventure mode map viewscreen would be
``dungeonmode``. If there is only one viewscreen that this widget is
associated with, it can be specified as a string instead of a list of
strings with a single element.
- ``hotspot`` (default: ``false``)
If set to ``true``, your widget's ``overlay_onupdate`` function will be
called whenever the `overlay` plugin's ``plugin_onupdate()`` function is
called (which corresponds to one call per call to the current top
viewscreen's ``logic()`` function). This call to ``overlay_onupdate`` is in
addition to any calls initiated from associated interposed viewscreens and
will come after calls from associated viewscreens.
- ``overlay_onupdate_max_freq_seconds`` (default: ``5``)
This throttles how often a widget's ``overlay_onupdate`` function can be
called (from any source). Set this to the largest amount of time (in
seconds) that your widget can take to react to changes in information and
not annoy the player. Set to 0 to be called at the maximum rate. Be aware
that running more often than you really need to will impact game FPS,
especially if your widget can run while the game is unpaused.
Registering a widget with the overlay framework
***********************************************
Anywhere in your code after the widget classes are declared, define a table
named ``OVERLAY_WIDGETS``. The keys are the display names for your widgets and
the values are the widget classes. For example, the `dwarfmonitor` widgets are
declared like this::
OVERLAY_WIDGETS = {
cursor=CursorWidget,
date=DateWidget,
misery=MiseryWidget,
weather=WeatherWidget,
}
When the `overlay` plugin is enabled, it scans all plugins and scripts for
this table and registers the widgets on your behalf. The widget is enabled if it
was enabled the last time the `overlay` plugin was loaded and the widget's
position is restored according to the state saved in the
:file:`dfhack-config/overlay.json` file.
The overlay framework will instantiate widgets from the named classes and own
the resulting objects. The instantiated widgets must not be added as subviews to
any other View, including the Screen views that can be returned from the
``overlay_trigger()`` function.
Widget example 1: adding text to a DF screen
--------------------------------------------
This is a simple widget that displays a message at its position. The message
text is retrieved from the host script or plugin every ~20 seconds or when
the :kbd:`Alt`:kbd:`Z` hotkey is hit::
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
MessageWidget = defclass(MessageWidget, overlay.OverlayWidget)
MessageWidget.ATTRS{
default_pos={x=5,y=-2},
viewscreens={'dwarfmode', 'dungeonmode'},
overlay_onupdate_max_freq_seconds=20,
}
function MessageWidget:init()
self.label = widgets.Label{text=''}
self:addviews{self.label}
end
function MessageWidget:overlay_onupdate()
local text = getImportantMessage() -- defined in the host script/plugin
self.label:setText(text)
self.frame.w = #text
end
function MessageWidget:onInput(keys)
if keys.CUSTOM_ALT_Z then
self:overlay_onupdate()
return true
end
end
OVERLAY_WIDGETS = {message=MessageWidget}
Widget example 2: highlighting artifacts on the live game map
-------------------------------------------------------------
This widget is not rendered at its "position" at all, but instead monitors the
map and overlays information about where artifacts are located. Scanning for
which artifacts are visible on the map can slow, so that is only done every 10
seconds to avoid slowing down the entire game on every frame.
::
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
ArtifactRadarWidget = defclass(ArtifactRadarWidget, overlay.OverlayWidget)
ArtifactRadarWidget.ATTRS{
viewscreens={'dwarfmode', 'dungeonmode'},
overlay_onupdate_max_freq_seconds=10,
}
function ArtifactRadarWidget:overlay_onupdate()
self.visible_artifacts_coords = getVisibleArtifactCoords()
end
function ArtifactRadarWidget:onRenderFrame()
for _,pos in ipairs(self.visible_artifacts_coords) do
-- highlight tile at given coordinates
end
end
OVERLAY_WIDGETS = {radar=ArtifactRadarWidget}
Widget example 3: corner hotspot
--------------------------------
This hotspot reacts to mouseover events and launches a screen that can react to
input events. The hotspot area is a 2x2 block near the lower right corner of the
screen (by default, but the player can move it wherever).
::
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
HotspotMenuWidget = defclass(HotspotMenuWidget, overlay.OverlayWidget)
HotspotMenuWidget.ATTRS{
default_pos={x=-3,y=-3},
frame={w=2, h=2},
hotspot=true,
viewscreens='dwarfmode',
overlay_onupdate_max_freq_seconds=0, -- check for mouseover every tick
}
function HotspotMenuWidget:init()
-- note this label only gets rendered on the associated viewscreen
-- (dwarfmode), but the hotspot is active on all screens
self:addviews{widgets.Label{text={'!!', NEWLINE, '!!'}}}
self.mouseover = false
end
function HotspotMenuWidget:overlay_onupdate()
local hasMouse = self:getMousePos()
if hasMouse and not self.mouseover then -- only trigger on mouse entry
self.mouseover = true
return true
end
self.mouseover = hasMouse
end
function HotspotMenuWidget:overlay_trigger()
return MenuScreen{hotspot_frame=self.frame}:show()
end
OVERLAY_WIDGETS = {menu=HotspotMenuWidget}
MenuScreen = defclass(MenuScreen, gui.Screen)
MenuScreen.ATTRS{
focus_path='hotspot/menu',
hotspot_frame=DEFAULT_NIL,
}
function MenuScreen:init()
self.mouseover = false
-- derrive the menu frame from the hotspot frame so it
-- can appear in a nearby location
local frame = copyall(self.hotspot_frame)
-- ...
self:addviews{
widgets.ResizingPanel{
autoarrange_subviews=true,
frame=frame,
frame_style=gui.GREY_LINE_FRAME,
frame_background=gui.CLEAR_PEN,
subviews={
-- ...
},
},
},
}
end
function MenuScreen:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
return true
end
return self:inputToSubviews(keys)
end
function MenuScreen:onRenderFrame(dc, rect)
self:renderParent()
end

@ -1,194 +0,0 @@
.. _overlay-widget-guide:
.. highlight:: lua
DFHack overlay widget dev guide
===============================
This guide walks you through how to build overlay widgets and register them with
the `overlay` framework for injection into Dwarf Fortress viewscreens.
Why would I want to create an overlay widget?
---------------------------------------------
There are both C++ and Lua APIs for creating viewscreens and drawing to the
screen. If you need very specific low-level control, those APIs might be the
right choice for you. Here are some reasons you might want to implement an
overlay widget instead:
1. You can draw directly to an existing viewscreen instead of creating an
entirely new screen on the viewscreen stack. This allows the original
viewscreen to continue processing uninterrupted and keybindings bound to
that viewscreen will continue to function. This was previously only
achievable by C++ plugins.
1. Your widget will be listed along with other widgets, making it more
discoverable for players who don't already have it enabled.
1. You don't have to manage the C++ interposing logic yourself and can focus on
the business logic, writing purely in Lua if desired.
1. You get the state of whether your widget is enabled and its (configurable)
position managed for you for free.
In general, if you are writing a plugin or script and have anything you'd like
to add to an existing screen (including overlaying map tiles), an overlay widget
is probably your easiest path to get it done. If your plugin or script doesn't
otherwise need to be enabled to function, using the overlay allows you to avoid
writing any of the enable management code that would normally be required for
you to show info in the UI.
What is an overlay widget?
--------------------------
Overlay widgets are Lua classes that inherit from ``overlay.OverlayWidget``
(which itself inherits from ``widgets.Widget``). The regular ``onInput(keys)``,
``onRenderFrame(dc, frame_rect)``, and ``onRenderBody(dc)`` functions work as
normal, and they are called when the viewscreen that the widget is associated
with does its usual input and render processing.
Overlay widgets can contain other Widgets, just like you're building a regular
UI element.
There are a few extra capabilities that overlay widgets have that take them
beyond your everyday ``Widget``:
bool = overlay_onupdate(viewscreen) if defined, will be called on every viewscreen logic() execution, but no more frequently than what is specified in the overlay_onupdate_max_freq_seconds class attribute. Widgets that need to update their state according to game changes can do it here. The viewscreen parameter is the viewscreen that this widget is attached to at the moment. For hotspot widgets, viewscreen will be nil. Returns whether overlay should subsequently call the widget's overlay_trigger() function.
screen = overlay_trigger() if defined, will be called when the overlay_onupdate callback returns true or when the player uses the CLI (or a keybinding calling the CLI) to trigger the widget. must return either nil or the Screen object that the widget code has allocated, shown, and now owns. Overlay widgets will receive no callbacks until the returned screen is dismissed. Unbound hotspot widgets must allocate a Screen if they want to react to the onInput() feed or be rendered. The widgets owned by the overlay must not be attached to that new screen, but the returned screen can instantiate and configure new views.
overlay_onupdate() will always get called for hotspots. Un-hotspotted widgets bound to particular viewscreens only get callbacks called when the relevant functions of the viewscreen are called (that is, the widget will be rendered when that viewscreen's render() function is run; the widget will get its onInput(keys) function called when the viewscreen's feed() function is run; the overlay_onupdate(viewscreen) function is called when that viewscren's logic() function is run).
How do I register a widget with the overlay framework?
------------------------------------------------------
Anywhere in your code after the widget classes are declared, define a table
named ``OVERLAY_WIDGETS``. The keys are the display names for your widgets and
the values are the widget classes. For example, the `dwarfmonitor` widgets are
declared like this::
OVERLAY_WIDGETS = {
cursor=CursorWidget,
date=DateWidget,
misery=MiseryWidget,
weather=WeatherWidget,
}
When the `overlay` plugin is enabled, it scans all plugins and scripts for
this table and registers the widgets on your behalf. The widget is enabled if it
was enabled the last time the `overlay` plugin was loaded and the widget's
position is restored according to the state saved in the
:file:`dfhack-config/overlay.json` file.
Widget example 1: adding text, hotkeys, or functionality to a DF screen
-----------------------------------------------------------------------
Widget example 2: highlighting artifacts on the live game map
-------------------------------------------------------------
Widget example 3: corner hotspot
--------------------------------
Here is a fully functional widget that displays a message on the screen::
local overlay = require('plugins.overlay')
MessageWidget = defclass(MessageWidget, overlay.OverlayWidget)
MessageWidget.ATTRS{
default_pos={x=-16,y=4}, -- default position near the upper right corner
viewscreens={'dungeonmode', 'dwarfmode'}, -- only display on main maps
}
function MessageWidget:init()
self.message = ''
self:addviews{widgets.}
self:overlay_onupdate()
end
function MessageWidget:overlay_onupdate()
-- getMessage() can be implemented elsewhere in the lua file or even
-- in a host plugin (e.g. exported with DFHACK_PLUGIN_LUA_COMMANDS)
local message = getMessage()
self.frame.w = #message
self.message = message
end
-- onRenderBody will be called whenever the associated viewscreen is
-- visible, even if it is not currently the top viewscreen
function MessageWidget:onRenderBody(dc)
dc:string(self.message, COLOR_GREY)
end
function MessageWidget:onInput(keys)
end
-- register our widgets with the overlay
OVERLAY_WIDGETS = {
cursor=CursorWidget,
date=DateWidget,
misery=MiseryWidget,
weather=WeatherWidget,
}
Widget lifecycle
----------------
Overlay will instantiate and own the widgets. The instantiated widgets must not be added as subviews to any other View.
The overlay widget can modify self.frame.w and self.frame.h at any time (in init() or in any of the callbacks) to indicate a new size.
Widget state
------------
whether the widget is enabled
the screen position of the widget (relative to any edge)
Widget architecture
-------------------
bool = overlay_onupdate(viewscreen) if defined, will be called on every viewscreen logic() execution, but no more frequently than what is specified in the overlay_onupdate_max_freq_seconds class attribute. Widgets that need to update their state according to game changes can do it here. The viewscreen parameter is the viewscreen that this widget is attached to at the moment. For hotspot widgets, viewscreen will be nil. Returns whether overlay should subsequently call the widget's overlay_trigger() function.
screen = overlay_trigger() if defined, will be called when the overlay_onupdate callback returns true or when the player uses the CLI (or a keybinding calling the CLI) to trigger the widget. must return either nil or the Screen object that the widget code has allocated, shown, and now owns. Overlay widgets will receive no callbacks until the returned screen is dismissed. Unbound hotspot widgets must allocate a Screen if they want to react to the onInput() feed or be rendered. The widgets owned by the overlay must not be attached to that new screen, but the returned screen can instantiate and configure new views.
overlay_onupdate() will always get called for hotspots. Un-hotspotted widgets bound to particular viewscreens only get callbacks called when the relevant functions of the viewscreen are called (that is, the widget will be rendered when that viewscreen's render() function is run; the widget will get its onInput(keys) function called when the viewscreen's feed() function is run; the overlay_onupdate(viewscreen) function is called when that viewscren's logic() function is run).
Widget attributes
-----------------
Your widget must inherit from ``overlay.OverlayWidget``, which defines the
following class properties:
* ``name``
This will be filled in with the display name of your widget, in case you
have multiple widgets with the same implementation but different
configurations.
* ``default_pos`` (default: ``{x=-2, y=-2}``)
Override this attribute with your desired default widget position. See
the `overlay` docs for information on what positive and negative numbers
mean for the position.
* ``viewscreens`` (default: ``{}``)
The list of viewscreens that this widget should be associated with. When
one of these viewscreens is on top, your widget's callback functions for
update, input, and render will be interposed into the viewscreen's call
path.
* ``hotspot`` (default: ``false``)
If set to ``true``, your widget's ``overlay_onupdate`` function will be
called whenever the `overlay` plugin's ``plugin_onupdate()`` function is
called (which corresponds to one call to the current top viewscreen's
``logic()`` function). This is in addition to any calls to
``overlay_onupdate`` initiated from associated interposed viewscreens.
* ``overlay_onupdate_max_freq_seconds`` (default: ``5``)
This throttles how often a widget's ``overlay_onupdate`` function can be
called. Set this to the largest amount of time (in seconds) that your
widget can take to react to changes in information and not annoy the player.
Set to 0 to be called at the maximum rate. Be aware that running more often
than you really need to will impact game FPS, especially if your widget is
bound to the main map screen.

@ -7,7 +7,7 @@ overlay
The overlay framework manages the on-screen widgets that other tools (including The overlay framework manages the on-screen widgets that other tools (including
3rd party plugins and scripts) can register for display. If you are a developer 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`. who wants to write an overlay widget, please see the `overlay-dev-guide`.
Usage Usage
----- -----
@ -17,9 +17,9 @@ Usage
``overlay enable|disable all|<name or list number> [<name or list number> ...]`` ``overlay enable|disable all|<name or list number> [<name or list number> ...]``
Enable/disable all or specified widgets. Widgets can be specified by either Enable/disable all or specified widgets. Widgets can be specified by either
their name or their number, as returned by ``overlay list``. their name or their number, as returned by ``overlay list``.
``overlay list`` ``overlay list [<filter>]``
Show a list of all the widgets that are registered with the overlay Show a list of all the widgets that are registered with the overlay
framework. framework, optionally filtered by the given filter string.
``overlay position <name or list number> [default|<x> <y>]`` ``overlay position <name or list number> [default|<x> <y>]``
Display configuration information for the given widget or change the Display configuration information for the given widget or change the
position where it is rendered. See the `Widget position`_ section below for position where it is rendered. See the `Widget position`_ section below for

@ -63,12 +63,19 @@ local function normalize_list(element_or_list)
return {element_or_list} return {element_or_list}
end end
-- allow "short form" to be specified, but use "long form" -- normalize "short form" viewscreen names to "long form"
local function normalize_viewscreen_name(vs_name) local function normalize_viewscreen_name(vs_name)
if vs_name:match('viewscreen_.*st') then return vs_name end if vs_name:match('viewscreen_.*st') then return vs_name end
return 'viewscreen_' .. vs_name .. 'st' return 'viewscreen_' .. vs_name .. 'st'
end end
-- reduce "long form" viewscreen names to "short form"
local function simplify_viewscreen_name(vs_name)
_,_,short_name = vs_name:find('^viewscreen_(.*)st$')
if short_name then return short_name end
return vs_name
end
local function is_empty(tbl) local function is_empty(tbl)
for _ in pairs(tbl) do for _ in pairs(tbl) do
return false return false
@ -79,7 +86,7 @@ end
local function sanitize_pos(pos) local function sanitize_pos(pos)
local x = math.floor(tonumber(pos.x) or DEFAULT_X_POS) local x = math.floor(tonumber(pos.x) or DEFAULT_X_POS)
local y = math.floor(tonumber(pos.y) or DEFAULT_Y_POS) local y = math.floor(tonumber(pos.y) or DEFAULT_Y_POS)
-- if someone accidentally uses 1-based instead of 0-based indexing, fix it -- if someone accidentally uses 0-based instead of 1-based indexing, fix it
if x == 0 then x = 1 end if x == 0 then x = 1 end
if y == 0 then y = 1 end if y == 0 then y = 1 end
return {x=x, y=y} return {x=x, y=y}
@ -220,9 +227,7 @@ local function load_widget(name, widget_class)
} }
if not overlay_config[name] then overlay_config[name] = {} end if not overlay_config[name] then overlay_config[name] = {} end
local config = overlay_config[name] local config = overlay_config[name]
if not config.pos then config.pos = sanitize_pos(config.pos or widget.default_pos)
config.pos = sanitize_pos(widget.default_pos)
end
widget.frame = make_frame(config.pos, widget.frame) widget.frame = make_frame(config.pos, widget.frame)
if config.enabled then if config.enabled then
do_enable(name, true, true) do_enable(name, true, true)
@ -250,7 +255,7 @@ local function load_widgets(env_prefix, provider, env_fn)
end end
end end
-- called directly from cpp on init -- called directly from cpp on plugin enable
function reload() function reload()
reset() reset()
@ -284,14 +289,15 @@ end
local function dump_widget_config(name, widget) local function dump_widget_config(name, widget)
local pos = overlay_config[name].pos local pos = overlay_config[name].pos
print(('widget %s is positioned at x=%d, y=%d'):format(name, pos.x, pos.y)) print(('widget %s is positioned at x=%d, y=%d'):format(name, pos.x, pos.y))
if #widget.viewscreens > 0 then local viewscreens = normalize_list(widget.viewscreens)
if #viewscreens > 0 then
print(' it will be attached to the following viewscreens:') print(' it will be attached to the following viewscreens:')
for _,vs in ipairs(widget.viewscreens) do for _,vs in ipairs(viewscreens) do
print((' %s'):format(vs)) print((' %s'):format(simplify_viewscreen_name(vs)))
end end
end end
if widget.hotspot then if widget.hotspot then
print(' on all screens it will act as a hotspot') print(' it will act as a hotspot on all screens')
end end
end end
@ -332,8 +338,7 @@ local function do_trigger(args)
:format(active_triggered_widget)) :format(active_triggered_widget))
return return
end end
local target = args[1] do_by_names_or_numbers(args[1], function(name, db_entry)
do_by_names_or_numbers(target, function(name, db_entry)
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()
@ -376,16 +381,23 @@ local function detect_frame_change(widget, fn)
return ret return ret
end end
local function get_next_onupdate_timestamp(now_ms, widget)
local freq_s = widget.overlay_onupdate_max_freq_seconds
if freq_s == 0 then
return now_ms
end
local freq_ms = math.floor(freq_s * 1000)
local jitter = math.random(0, freq_ms // 8) -- up to ~12% jitter
return now_ms + freq_ms - jitter
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(name, 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 db_entry.next_update_ms = get_next_onupdate_timestamp(now_ms, w)
local jitter = math.random(0, freq_ms // 8) -- up to ~12% jitter if detect_frame_change(w, function() return w:overlay_onupdate(vs) end) then
db_entry.next_update_ms = now_ms + freq_ms - jitter
if detect_frame_change(w,
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 if active_triggered_screen then
active_triggered_widget = name active_triggered_widget = name
@ -402,6 +414,8 @@ function update_hotspot_widgets()
end end
end end
-- not subject to trigger lock since these widgets are already filtered by
-- viewscreen
function update_viewscreen_widgets(vs_name, vs) 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
@ -415,9 +429,8 @@ function feed_viewscreen_widgets(vs_name, keys)
local vs_widgets = active_viewscreen_widgets[vs_name] local vs_widgets = active_viewscreen_widgets[vs_name]
if not vs_widgets then return false end if not vs_widgets then return false end
for _,db_entry in pairs(vs_widgets) do for _,db_entry in pairs(vs_widgets) do
local widget = db_entry.widget local w = db_entry.widget
if detect_frame_change(widget, if detect_frame_change(w, function() return w:onInput(keys) end) then
function() return widget:onInput(keys) end) then
return true return true
end end
end end
@ -429,8 +442,8 @@ function render_viewscreen_widgets(vs_name)
if not vs_widgets then return false end if not vs_widgets then return false end
local dc = gui.Painter.new() local dc = gui.Painter.new()
for _,db_entry in pairs(vs_widgets) do for _,db_entry in pairs(vs_widgets) do
local widget = db_entry.widget local w = db_entry.widget
detect_frame_change(widget, function() widget:render(dc) end) detect_frame_change(w, function() w:render(dc) end)
end end
end end
@ -449,9 +462,9 @@ end
OverlayWidget = defclass(OverlayWidget, widgets.Widget) OverlayWidget = defclass(OverlayWidget, widgets.Widget)
OverlayWidget.ATTRS{ OverlayWidget.ATTRS{
name=DEFAULT_NIL, -- this is set by the framework to the widget name name=DEFAULT_NIL, -- this is set by the framework to the widget name
default_pos={x=DEFAULT_X_POS, y=DEFAULT_Y_POS}, -- initial widget screen pos, 1-based default_pos={x=DEFAULT_X_POS, y=DEFAULT_Y_POS}, -- 1-based widget screen pos
hotspot=false, -- whether to call overlay_onupdate for all screens hotspot=false, -- whether to call overlay_onupdate on all screens
viewscreens={}, -- override with list of viewscrens to interpose viewscreens={}, -- override with associated viewscreen or list of viewscrens
overlay_onupdate_max_freq_seconds=5, -- throttle calls to overlay_onupdate overlay_onupdate_max_freq_seconds=5, -- throttle calls to overlay_onupdate
} }
@ -462,8 +475,8 @@ function OverlayWidget:init()
end end
-- set defaults for frame. the widget is expected to keep these up to date -- set defaults for frame. the widget is expected to keep these up to date
-- if display contents change so the widget position can shift if the frame -- when display contents change so the widget position can shift if the
-- is relative to the right or bottom edges. -- frame is relative to the right or bottom edges.
self.frame = self.frame or {} self.frame = self.frame or {}
self.frame.w = self.frame.w or 5 self.frame.w = self.frame.w or 5
self.frame.h = self.frame.h or 1 self.frame.h = self.frame.h or 1