More fixes

* Use word_wrap()

* add_proper_report utility fn; have addCombatReportAuto use this

* Update Lua API.rst

* Update Gui.cpp
develop
Ryan Williams 2022-05-28 12:35:49 -07:00 committed by GitHub
parent 7d2ecae8b4
commit 2b29431806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 273 additions and 198 deletions

@ -8,9 +8,9 @@
DFHack is a Dwarf Fortress memory access library, distributed with scripts DFHack is a Dwarf Fortress memory access library, distributed with scripts
and plugins implementing a wide variety of useful functions and tools. and plugins implementing a wide variety of useful functions and tools.
The full documentation [is available online here](https://dfhack.readthedocs.org), The full documentation [is available online here](https://dfhack.readthedocs.org).
from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder. It is also accessible via the README.html page in the DFHack distribution or as raw text in the `./docs` folder.
If you're an end-user, modder, or interested in contributing to DFHack - If you're an end-user, modder, or interested in contributing to DFHack -
go read those docs. go read those docs.
If that's unclear, or you need more help checkout our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for up-to-date options. If the docs are unclear or you need more help, please check out our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for ways to contact the DFHack developers.

@ -240,7 +240,7 @@ end
-- output doesn't trigger its own dfhack.printerr usage detection (see -- output doesn't trigger its own dfhack.printerr usage detection (see
-- detect_printerr below) -- detect_printerr below)
local orig_printerr = dfhack.printerr local orig_printerr = dfhack.printerr
local function wrap_expect(func, private) local function wrap_expect(func, private, path)
return function(...) return function(...)
private.checks = private.checks + 1 private.checks = private.checks + 1
local ret = {func(...)} local ret = {func(...)}
@ -269,7 +269,7 @@ local function wrap_expect(func, private)
end end
-- Skip any frames corresponding to C calls, or Lua functions defined in another file -- Skip any frames corresponding to C calls, or Lua functions defined in another file
-- these could include pcall(), with_finalize(), etc. -- these could include pcall(), with_finalize(), etc.
if info.what == 'Lua' and info.short_src == caller_src then if info.what == 'Lua' and (info.short_src == caller_src or info.short_src == path) then
orig_printerr((' at %s:%d'):format(info.short_src, info.currentline)) orig_printerr((' at %s:%d'):format(info.short_src, info.currentline))
end end
frame = frame + 1 frame = frame + 1
@ -278,7 +278,7 @@ local function wrap_expect(func, private)
end end
end end
local function build_test_env() local function build_test_env(path)
local env = { local env = {
test = utils.OrderedTable(), test = utils.OrderedTable(),
-- config values can be overridden in the test file to define -- config values can be overridden in the test file to define
@ -309,7 +309,7 @@ local function build_test_env()
checks_ok = 0, checks_ok = 0,
} }
for name, func in pairs(expect) do for name, func in pairs(expect) do
env.expect[name] = wrap_expect(func, private) env.expect[name] = wrap_expect(func, private, path)
end end
setmetatable(env, {__index = _G}) setmetatable(env, {__index = _G})
return env, private return env, private
@ -345,9 +345,9 @@ local function finish_tests(done_command)
end end
local function load_tests(file, tests) local function load_tests(file, tests)
local short_filename = file:sub((file:find('test') or -4)+5, -1) local short_filename = file:sub((file:find('test') or -4) + 5, -1)
print('Loading file: ' .. short_filename) print('Loading file: ' .. short_filename)
local env, env_private = build_test_env() local env, env_private = build_test_env(file)
local code, err = loadfile(file, 't', env) local code, err = loadfile(file, 't', env)
if not code then if not code then
dfhack.printerr('Failed to load file: ' .. tostring(err)) dfhack.printerr('Failed to load file: ' .. tostring(err))

@ -1 +1 @@
Subproject commit 3e877cbb3c9bc8f22946053e70490d2e5431f4d5 Subproject commit 6ac8628a3c7a1677b27fb007db96f665b684a183

@ -1007,17 +1007,19 @@ Fortress mode
* ``dfhack.gui.pauseRecenter(pos[,pause])`` * ``dfhack.gui.pauseRecenter(pos[,pause])``
``dfhack.gui.pauseRecenter(x,y,z[,pause])`` ``dfhack.gui.pauseRecenter(x,y,z[,pause])``
Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, and respects Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``. Respects
RECENTER_INTERFACE_SHUTDOWN_MS (the delay before input is recognized when a recenter occurs) in DF's init.txt. ``RECENTER_INTERFACE_SHUTDOWN_MS`` in DF's ``init.txt`` (the delay before input is recognized when a recenter occurs.)
* ``dfhack.gui.recenterViewscreen(pos[,zoom])`` * ``dfhack.gui.recenterViewscreen(pos[,zoom])``
``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])``
``dfhack.gui.recenterViewscreen([zoom])`` ``dfhack.gui.recenterViewscreen([zoom])``
Recenter the view on a position using a specific zoom type. If no position is given, Recenter the view on a position using a specific zoom type. If no position is given,
recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` (see: `enum definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L438>`_), recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type``
where ``Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) ``Item`` brings (see: `enum definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml>`_),
the position onscreen without centering, and ``Unit`` centers the screen on the position. Default zoom type is Item. where ``df.report_zoom_type.Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,)
``df.report_zoom_type.Item`` brings the position onscreen without centering, and
``df.report_zoom_type.Unit`` centers the screen on the position. Default zoom type is ``df.report_zoom_type.Item``.
* ``dfhack.gui.revealInDwarfmodeMap(pos)`` * ``dfhack.gui.revealInDwarfmodeMap(pos)``
@ -1091,11 +1093,15 @@ Announcements
* ``dfhack.gui.autoDFAnnouncement(report,text)`` * ``dfhack.gui.autoDFAnnouncement(report,text)``
``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring])`` ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring])``
Takes a ``df.report_init`` (see: `structure definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L451>`_) Takes a ``df.report_init`` (see: `structure definition <https://github.com/DFHack/df-structures/blob/master/df.announcements.xml>`_)
and a string and processes them just like DF does. Sometimes this means the announcement won't occur. and a string and processes them just like DF does. Can also be built from parameters instead of a ``report_init``.
Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report Setting ``is_sparring`` to *true* means the report will be added to sparring logs (if applicable) rather than hunting or combat.
will be added to sparring logs (if applicable) rather than hunting or combat. Text is parsed using ``&`` as an escape character, with ``&r``
adding a blank line (equivalent to ``\n \n``,) ``&&`` being just ``&``, and any other combination causing neither character to display. The announcement will not display if units are involved and the player can't see them (or hear, for adventure mode sound announcement types.)
Text is parsed using ``&`` as an escape character, with ``&r`` adding a blank line (equivalent to ``\n \n``,)
``&&`` being just ``&``, and any other combination causing neither character to display.
If you want a guaranteed announcement without parsing, use ``dfhack.gui.showAutoAnnouncement`` instead.
Other Other
~~~~~ ~~~~~

@ -36,7 +36,13 @@ end
function MessageBox:getWantedFrameSize() function MessageBox:getWantedFrameSize()
local label = self.subviews.label local label = self.subviews.label
local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4) local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4)
return math.max(width, label:getTextWidth()), label:getTextHeight() local text_area_width = label:getTextWidth()
if label.frame_inset then
-- account for scroll icons
text_area_width = text_area_width + (label.frame_inset.l or 0)
text_area_width = text_area_width + (label.frame_inset.r or 0)
end
return math.max(width, text_area_width), label:getTextHeight()
end end
function MessageBox:onRenderFrame(dc,rect) function MessageBox:onRenderFrame(dc,rect)

@ -478,8 +478,16 @@ function Label:render_scroll_icons(dc, x, y1, y2)
end end
end end
function Label:postComputeFrame() function Label:computeFrame(parent_rect)
self:update_scroll_inset() local frame_rect,body_rect = Label.super.computeFrame(self, parent_rect)
self.frame_rect = frame_rect
self.frame_body = parent_rect:viewport(body_rect or frame_rect)
self:update_scroll_inset() -- frame_body is now set
-- recalc with updated frame_inset
return Label.super.computeFrame(self, parent_rect)
end end
function Label:preUpdateLayout() function Label:preUpdateLayout()

@ -32,12 +32,17 @@ function _patch_impl(patches_raw, callback, restore_only)
end end
--[[ --[[
Replaces `table[key]` with `value`, calls `callback()`, then restores the
original value of `table[key]`.
Usage: Usage:
patch(table, key, value, callback) patch(table, key, value, callback)
patch({ patch({
{table, key, value}, {table, key, value},
{table2, key2, value2}, {table2, key2, value2},
}, callback) }, callback)
]] ]]
function mock.patch(...) function mock.patch(...)
local args = {...} local args = {...}
@ -57,12 +62,18 @@ function mock.patch(...)
end end
--[[ --[[
Restores the original value of `table[key]` after calling `callback()`.
Equivalent to: patch(table, key, table[key], callback)
Usage: Usage:
restore(table, key, callback) restore(table, key, callback)
restore({ restore({
{table, key}, {table, key},
{table2, key2}, {table2, key2},
}, callback) }, callback)
]] ]]
function mock.restore(...) function mock.restore(...)
local args = {...} local args = {...}
@ -81,9 +92,19 @@ function mock.restore(...)
return _patch_impl(patches, callback, true) return _patch_impl(patches, callback, true)
end end
function mock.func(...) --[[
Returns a callable object that tracks the arguments it is called with, then
passes those arguments to `callback()`.
The returned object has the following properties:
- `call_count`: the number of times the object has been called
- `call_args`: a table of function arguments (shallow-copied) corresponding
to each time the object was called
]]
function mock.observe_func(callback)
local f = { local f = {
return_values = {...},
call_count = 0, call_count = 0,
call_args = {}, call_args = {},
} }
@ -101,11 +122,36 @@ function mock.func(...)
end end
end end
table.insert(self.call_args, args) table.insert(self.call_args, args)
return table.unpack(self.return_values) return callback(...)
end, end,
}) })
return f return f
end end
--[[
Returns a callable object similar to `mock.observe_func()`, but which when
called, only returns the given `return_value`(s) with no additional side effects.
Intended for use by `patch()`.
Usage:
func(return_value [, return_value2 ...])
See `observe_func()` for a description of the return value.
The return value also has an additional `return_values` field, which is a table
of values returned when the object is called. This can be modified.
]]
function mock.func(...)
local f
f = mock.observe_func(function()
return table.unpack(f.return_values)
end)
f.return_values = {...}
return f
end
return mock return mock

@ -1370,46 +1370,15 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message)
namespace namespace
{ // Utility functions for reports { // Utility functions for reports
/*bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73) bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73)
{ { // parse a string into output strings like DF does for reports
if (str.empty() || line_length == 0)
return false;
string parsed = "";
size_t i = 0;
while (i < str.length())
{
if (str[i] == '&') // escape character
{
i++; // ignore the '&' itself
if (i >= str.length())
break;
if (str[i] == 'r') // "&r" adds a blank line
parsed += "\n \n"; // DF adds a line with a space for some reason
else if (str[i] == '&') // "&&" is '&'
parsed += "&";
// else next char is ignored
}
else
{
parsed += str[i];
}
i++;
}
return word_wrap(out, parsed, line_length, true);
}*/
/*bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length = 73)
{
if (str.empty() || line_length == 0) if (str.empty() || line_length == 0)
return false; return false;
string parsed = ""; string parsed;
size_t i = 0; size_t i = 0;
while (i < str.length()) do
{ {
if (str[i] == '&') // escape character if (str[i] == '&') // escape character
{ {
@ -1419,95 +1388,21 @@ namespace
if (str[i] == 'r') // "&r" adds a blank line if (str[i] == 'r') // "&r" adds a blank line
{ {
word_wrap(out, parsed, line_length, true); word_wrap(out, parsed, line_length, false, true);
out->push_back(" "); // DF adds a line with a space for some reason out->push_back(" "); // DF adds a line with a space for some reason
parsed = ""; parsed.clear();
} }
else if (str[i] == '&') // "&&" is '&' else if (str[i] == '&') // "&&" is '&'
parsed += "&"; parsed.push_back('&');
// else next char is ignored // else next char is ignored
} }
else else
{ parsed.push_back(str[i]);
parsed += str[i];
}
i++;
} }
while (++i < str.length());
if (parsed != "") if (parsed.length())
word_wrap(out, parsed, line_length, true); word_wrap(out, parsed, line_length, false, true);
return true;
}*/
bool parseReportString(std::vector<std::string> *out, const std::string &str, size_t line_length)
{ // out vector will contain strings cut to line_length, avoiding cutting up words
// Reverse-engineered from DF announcement code, fixes applied
if (str.empty() || line_length == 0)
return false;
bool ignore_space = false;
string current_line = "";
size_t iter = 0;
do
{
if (ignore_space)
{
if (str[iter] == ' ')
continue;
ignore_space = false;
}
if (str[iter] == '&') // escape character
{
iter++; // ignore the '&' itself
if (iter >= str.length())
break;
if (str[iter] == 'r') // "&r" adds a blank line
{
if (!current_line.empty())
{
out->push_back(string(current_line));
current_line = "";
}
out->push_back(" "); // DF adds a line with a space for some reason
continue; // don't add 'r' to current_line
}
else if (str[iter] != '&')
{ // not "&&", don't add character to current_line
continue;
}
}
current_line += str[iter];
if (current_line.length() > line_length)
{
size_t i = current_line.length(); // start of current word
size_t j; // end of previous word
while (--i > 0 && current_line[i] != ' '); // find start of current word
if (i == 0)
{ // need to push at least one char
j = i = line_length; // last char ends up on next line
}
else
{
j = i;
while (j > 1 && current_line[j - 1] == ' ')
j--; // consume excess spaces at the split point
}
out->push_back(current_line.substr(0, j)); // push string before j
if (current_line[i] == ' ')
i++; // don't keep this space
current_line.erase(0, i); // current_line now starts at last word or is empty
ignore_space = current_line.empty(); // ignore leading spaces on new line
}
} while (++iter < str.length());
if (!current_line.empty())
out->push_back(current_line);
return true; return true;
} }
@ -1549,7 +1444,7 @@ namespace
} }
int32_t check_repeat_report(vector<string> &results) int32_t check_repeat_report(vector<string> &results)
{ { // returns the new repeat count, else 0
if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size()) if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size())
{ {
auto &reports = world->status.reports; auto &reports = world->status.reports;
@ -1697,6 +1592,19 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_
return true; return true;
} }
namespace
{ // An additional utility function for reports
bool add_proper_report(df::unit *unit, bool is_sparring, int report_index)
{
if (is_sparring)
return addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt)
return addCombatReport(unit, unit_report_type::Hunting, report_index);
else
return addCombatReport(unit, unit_report_type::Combat, report_index);
}
}
bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index) bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index)
{ {
using df::global::world; using df::global::world;
@ -1710,14 +1618,7 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r
bool ok = false; bool ok = false;
if (mode.bits.UNIT_COMBAT_REPORT) if (mode.bits.UNIT_COMBAT_REPORT)
{ ok |= add_proper_report(unit, unit->flags2.bits.sparring, report_index);
if (unit->flags2.bits.sparring)
ok |= addCombatReport(unit, unit_report_type::Sparring, report_index);
else if (unit->job.current_job && unit->job.current_job->job_type == job_type::Hunt)
ok |= addCombatReport(unit, unit_report_type::Hunting, report_index);
else
ok |= addCombatReport(unit, unit_report_type::Combat, report_index);
}
if (mode.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) if (mode.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
{ {
@ -1783,28 +1684,24 @@ void Gui::showAutoAnnouncement(
bool Gui::autoDFAnnouncement(df::report_init r, string message) bool Gui::autoDFAnnouncement(df::report_init r, string message)
{ // Reverse-engineered from DF announcement code { // Reverse-engineered from DF announcement code
if (!world->allow_announcements) if (!world->allow_announcements)
{ {
DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str()); DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str());
return false; return false;
} }
else if (!is_valid_enum_item(r.type))
df::announcement_flags a_flags;
if (is_valid_enum_item(r.type))
a_flags = df::global::d_init->announcements.flags[r.type];
else
{ {
WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str()); WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str());
return false; return false;
} }
else if (message.empty())
if (message.empty())
{ {
Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt
return false; return false;
} }
df::announcement_flags a_flags = df::global::d_init->announcements.flags[r.type];
// Check if the announcement will actually be announced // Check if the announcement will actually be announced
if (*gamemode == game_mode::ADVENTURE) if (*gamemode == game_mode::ADVENTURE)
{ {
@ -1814,11 +1711,13 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
r.type != announcement_type::CONFLICT_CONVERSATION && r.type != announcement_type::CONFLICT_CONVERSATION &&
r.type != announcement_type::MECHANISM_SOUND) r.type != announcement_type::MECHANISM_SOUND)
{ // If not sound, make sure we can see pos { // If not sound, make sure we can see pos
if ((world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0])) && if (world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0]))
((Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)) // Adventure mode uses this bit to determine current visibility { // Adventure mode reuses a dwarf mode digging designation bit to determine current visibility
{ if (!Maps::isValidTilePos(r.pos) || (Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)
DEBUG(gui).print("Adventure mode announcement not heard:\n%s\n", message.c_str()); {
return false; DEBUG(gui).print("Adventure mode announcement not detected:\n%s\n", message.c_str());
return false;
}
} }
} }
} }
@ -1826,7 +1725,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
{ // Dwarf mode { // Dwarf mode
if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2))) if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2)))
{ {
DEBUG(gui).print("Dwarf mode announcement not heard:\n%s\n", message.c_str()); DEBUG(gui).print("Dwarf mode announcement not detected:\n%s\n", message.c_str());
return false; return false;
} }
@ -1927,24 +1826,10 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
if (a_flags.bits.UNIT_COMBAT_REPORT) if (a_flags.bits.UNIT_COMBAT_REPORT)
{ {
if (r.unit1 != NULL) if (r.unit1 != NULL)
{ success |= add_proper_report(r.unit1, !r.flags.bits.hostile_combat, new_report_index);
if (r.flags.bits.hostile_combat)
success |= addCombatReport(r.unit1, unit_report_type::Combat, new_report_index);
else if (r.unit1->job.current_job != NULL && r.unit1->job.current_job->job_type == job_type::Hunt)
success |= addCombatReport(r.unit1, unit_report_type::Hunting, new_report_index);
else
success |= addCombatReport(r.unit1, unit_report_type::Sparring, new_report_index);
}
if (r.unit2 != NULL) if (r.unit2 != NULL)
{ success |= add_proper_report(r.unit2, !r.flags.bits.hostile_combat, new_report_index);
if (r.flags.bits.hostile_combat)
success |= addCombatReport(r.unit2, unit_report_type::Combat, new_report_index);
else if (r.unit2->job.current_job != NULL && r.unit2->job.current_job->job_type == job_type::Hunt)
success |= addCombatReport(r.unit2, unit_report_type::Hunting, new_report_index);
else
success |= addCombatReport(r.unit2, unit_report_type::Sparring, new_report_index);
}
} }
if (a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) if (a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE)
@ -1953,6 +1838,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
{ {
if (recent_report(r.unit1, slot)) if (recent_report(r.unit1, slot))
success |= addCombatReport(r.unit1, slot, new_report_index); success |= addCombatReport(r.unit1, slot, new_report_index);
if (recent_report(r.unit2, slot)) if (recent_report(r.unit2, slot))
success |= addCombatReport(r.unit2, slot, new_report_index); success |= addCombatReport(r.unit2, slot, new_report_index);
} }
@ -2044,9 +1930,7 @@ df::coord Gui::getCursorPos()
} }
void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom) void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom)
{ { // Reverse-engineered from DF announcement code, also used when scrolling
// Reverse-engineered from DF announcement code, also used when scrolling
auto dims = getDwarfmodeViewDims(); auto dims = getDwarfmodeViewDims();
int32_t w = dims.map_x2 - dims.map_x1 + 1; int32_t w = dims.map_x2 - dims.map_x1 + 1;
int32_t h = dims.map_y2 - dims.map_y1 + 1; int32_t h = dims.map_y2 - dims.map_y1 + 1;
@ -2062,7 +1946,7 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty
} }
else // report_zoom_type::Item else // report_zoom_type::Item
{ {
if (new_win_x > (x - 5)) if (new_win_x > (x - 5)) // equivalent to: "while (new_win_x > x - 5) new_win_x -= 10;"
new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10; new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10;
if (new_win_y > (y - 5)) if (new_win_y > (y - 5))
new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10; new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10;
@ -2072,8 +1956,6 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty
new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10; new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10;
} }
if (new_win_z != z)
ui_sidebar_menus->minimap.need_scan = true;
new_win_z = z; new_win_z = z;
} }
@ -2081,23 +1963,21 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty
*df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h)); *df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h));
*df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1)); *df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1));
ui_sidebar_menus->minimap.need_render = true;
ui_sidebar_menus->minimap.need_scan = true;
return; return;
} }
void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause)
{ { // Reverse-engineered from DF announcement code
// Reverse-engineered from DF announcement code
if (*gamemode != game_mode::DWARF) if (*gamemode != game_mode::DWARF)
return; return;
resetDwarfmodeView(pause); resetDwarfmodeView(pause);
if (x != -30000) if (x != -30000)
{
recenterViewscreen(x, y, z, report_zoom_type::Item); recenterViewscreen(x, y, z, report_zoom_type::Item);
ui_sidebar_menus->minimap.need_render = true;
ui_sidebar_menus->minimap.need_scan = true;
}
if (init->input.pause_zoom_no_interface_ms > 0) if (init->input.pause_zoom_no_interface_ms > 0)
{ {

@ -1 +1 @@
Subproject commit a59495e8f72115909772e6df20a7b9dec272f14c Subproject commit 1dfe6c5ab9887507cdcdebdd9390352fe0bba2dd

@ -1 +1 @@
Subproject commit 05d46b32a3aff4f5f98534fdccfbf9ae88dd31a3 Subproject commit 741c84ada2ec7fdd0083744afab294d9a1b6e370

@ -0,0 +1,94 @@
-- test -dhack/scripts/devel/tests -twidgets.Label
local gui = require('gui')
local widgets = require('gui.widgets')
local xtest = {} -- use to temporarily disable tests (change `function xtest.somename` to `function xxtest.somename`)
local wait = function(n)
delay(n or 30) -- enable for debugging the tests
end
local fs = defclass(fs, gui.FramedScreen)
fs.ATTRS = {
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'TestFramedScreen',
frame_width = 10,
frame_height = 10,
frame_inset = 0,
focus_path = 'test-framed-screen',
}
function test.Label_correct_frame_body_with_scroll_icons()
local t = {}
for i = 1, 12 do
t[#t+1] = tostring(i)
t[#t+1] = NEWLINE
end
function fs:init(args)
self:addviews{
widgets.Label{
view_id = 'text',
frame_inset = 0,
text = t,
--show_scroll_icons = 'right',
},
}
end
local o = fs{}
--o:show()
--wait()
expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scroll_icons.")
--o:dismiss()
end
function test.Label_correct_frame_body_with_few_text_lines()
local t = {}
for i = 1, 10 do
t[#t+1] = tostring(i)
t[#t+1] = NEWLINE
end
function fs:init(args)
self:addviews{
widgets.Label{
view_id = 'text',
frame_inset = 0,
text = t,
--show_scroll_icons = 'right',
},
}
end
local o = fs{}
--o:show()
--wait()
expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.")
--o:dismiss()
end
function test.Label_correct_frame_body_without_show_scroll_icons()
local t = {}
for i = 1, 12 do
t[#t+1] = tostring(i)
t[#t+1] = NEWLINE
end
function fs:init(args)
self:addviews{
widgets.Label{
view_id = 'text',
frame_inset = 0,
text = t,
show_scroll_icons = false,
},
}
end
local o = fs{}
--o:show()
--wait()
expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.")
--o:dismiss()
end

@ -208,9 +208,44 @@ function test.func_call_return_value()
end end
function test.func_call_return_multiple_values() function test.func_call_return_multiple_values()
local f = mock.func(7,5,{imatable='snarfsnarf'}) local f = mock.func(7, 5, {imatable='snarfsnarf'})
local a, b, c = f() local a, b, c = f()
expect.eq(7, a) expect.eq(7, a)
expect.eq(5, b) expect.eq(5, b)
expect.table_eq({imatable='snarfsnarf'}, c) expect.table_eq({imatable='snarfsnarf'}, c)
end end
function test.observe_func()
-- basic end-to-end test for common cases;
-- most edge cases are covered by mock.func() tests
local counter = 0
local function target()
counter = counter + 1
return counter
end
local observer = mock.observe_func(target)
expect.eq(observer(), 1)
expect.eq(counter, 1)
expect.eq(observer.call_count, 1)
expect.table_eq(observer.call_args, {{}})
expect.eq(observer('x', 'y'), 2)
expect.eq(counter, 2)
expect.eq(observer.call_count, 2)
expect.table_eq(observer.call_args, {{}, {'x', 'y'}})
end
function test.observe_func_error()
local function target()
error('asdf')
end
local observer = mock.observe_func(target)
expect.error_match('asdf', function()
observer('x')
end)
-- make sure the call was still tracked
expect.eq(observer.call_count, 1)
expect.table_eq(observer.call_args, {{'x'}})
end