Merge branch 'develop' into spectate

develop
Josh Cooper 2022-10-23 10:41:28 -07:00
commit c611a7615c
10 changed files with 89 additions and 44 deletions

@ -4,9 +4,9 @@
# defaults when you update DFHack. Instead, add your configuration to # defaults when you update DFHack. Instead, add your configuration to
# dfhack-config/init/dfhack.init # dfhack-config/init/dfhack.init
############################## ###################
# Generic dwarfmode bindings # # Global bindings #
############################## ###################
# the GUI command launcher (two bindings since some keyboards don't have `) # the GUI command launcher (two bindings since some keyboards don't have `)
keybinding add ` gui/launcher keybinding add ` gui/launcher
@ -16,6 +16,13 @@ keybinding add Ctrl-Shift-D gui/launcher
keybinding add Ctrl-F1 hotkeys keybinding add Ctrl-F1 hotkeys
keybinding add Alt-F1 hotkeys keybinding add Alt-F1 hotkeys
# on-screen keyboard
keybinding add Ctrl-Shift-K gui/cp437-table
##############################
# Generic dwarfmode bindings #
##############################
# toggle the display of water level as 1-7 tiles # toggle the display of water level as 1-7 tiles
keybinding add Ctrl-W twaterlvl keybinding add Ctrl-W twaterlvl

@ -3354,7 +3354,7 @@ A module for reading custom tokens added to the raws by mods.
Where ``typeInstance`` is a unit, entity, item, job, projectile, building, plant, or interaction Where ``typeInstance`` is a unit, entity, item, job, projectile, building, plant, or interaction
instance. Gets ``typeDefinition`` and then returns the same as ``getToken(typeDefinition, token)``. instance. Gets ``typeDefinition`` and then returns the same as ``getToken(typeDefinition, token)``.
For units, it gets the token from the race or caste instead if applicable. For plants growth items, For units, it gets the token from the race or caste instead if applicable. For plant growth items,
it gets the token from the plant or plant growth instead if applicable. For plants it does the same it gets the token from the plant or plant growth instead if applicable. For plants it does the same
but with growth number -1. but with growth number -1.
@ -4052,7 +4052,11 @@ Scrollbars have the following attributes:
:fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN``. :fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN``.
:bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN``. :bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN``.
:on_scroll: A callback called when the scrollbar is scrolled. It will be called with a single string parameter with a value of "up_large", "down_large", "up_small", or "down_small". :on_scroll: A callback called when the scrollbar is scrolled. If the scrollbar is clicked,
the callback will be called with one of the following string parameters: "up_large",
"down_large", "up_small", or "down_small". If the scrollbar is dragged, the callback will
be called with the value that ``top_elem`` should be set to on the next call to
``update()`` (see below).
The Scrollbar widget implements the following methods: The Scrollbar widget implements the following methods:
@ -4062,8 +4066,10 @@ The Scrollbar widget implements the following methods:
The ``top_elem`` param is the (one-based) index of the first visible element. The ``top_elem`` param is the (one-based) index of the first visible element.
The ``elems_per_page`` param is the maximum number of elements that can be The ``elems_per_page`` param is the maximum number of elements that can be
shown at one time. The ``num_elems`` param is the total number of elements shown at one time. The ``num_elems`` param is the total number of elements
that the paried widget can scroll through. The scrollbar will adjust its that the paried widget can scroll through. If ``elems_per_page`` or
scrollbar size and position accordingly. ``num_elems`` is not specified, the most recently specified value for these
parameters is used. The scrollbar will adjust its scrollbar size and position
according to the values passed to this function.
Clicking on the arrows at the top or the bottom of a scrollbar will scroll an Clicking on the arrows at the top or the bottom of a scrollbar will scroll an
associated widget by a small amount. Clicking on the unfilled portion of the associated widget by a small amount. Clicking on the unfilled portion of the
@ -4382,7 +4388,8 @@ supports:
:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field.
:edit_below: If true, the edit field is placed below the list instead of above. :edit_below: If true, the edit field is placed below the list instead of above.
:edit_key: If specified, the edit field is disabled until this key is pressed. :edit_key: If specified, the edit field is disabled until this key is pressed.
:edit_ignore_keys: If specified, must be a list of key names that the filter edit field should ignore. :edit_ignore_keys: If specified, will be passed to the filter edit field as its ``ignore_keys`` attribute.
:edit_on_char: If specified, will be passed to the filter edit field as its ``on_char`` attribute.
:not_found_label: Specifies the text of the label shown when no items match the filter. :not_found_label: Specifies the text of the label shown when no items match the filter.
The list choices may include the following attributes: The list choices may include the following attributes:

@ -10,7 +10,7 @@ bindings are not remembered between runs of the game unless re-created in
`dfhack.init`. `dfhack.init`.
Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or
``\``` (the key below the ``Esc`` key. \` (the key below the :kbd:`Esc` key.
Usage Usage
----- -----

@ -36,6 +36,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## New Plugins ## New Plugins
## Fixes ## Fixes
- `gui/create-item`: prevent materials list filter from intercepting sublist hotkeys
- `tiletypes`: no longer resets dig priority to the default when updating other properties of a tile
## Misc Improvements ## Misc Improvements
- `blueprint`: new ``--smooth`` option for recording all smoothed floors and walls instead of just the ones that require smoothing for later carving - `blueprint`: new ``--smooth`` option for recording all smoothed floors and walls instead of just the ones that require smoothing for later carving
@ -58,6 +60,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `spectate`: new ``focus-jobs`` option for following a dwarf after their job has finished (when disabled). - `spectate`: new ``focus-jobs`` option for following a dwarf after their job has finished (when disabled).
- `spectate`: new ``tick-threshold``, option for specifying the change interval (maximum follow time when focus-jobs is enabled) - `spectate`: new ``tick-threshold``, option for specifying the change interval (maximum follow time when focus-jobs is enabled)
- `spectate`: added persistent configuration of the plugin settings - `spectate`: added persistent configuration of the plugin settings
- `gui/cp437-table`: new global keybinding for the clickable on-screen keyboard for players with keyboard layouts that prevent them from using certain keys: Ctrl-Shift-K
## Documentation ## Documentation
- `spectate`: improved documentation of features and functionality - `spectate`: improved documentation of features and functionality
@ -69,6 +72,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``widgets.Scrollbar``: new scrollbar widget that can be paired with an associated scrollable widget. Integrated with ``widgets.Label`` and ``widgets.List``. - ``widgets.Scrollbar``: new scrollbar widget that can be paired with an associated scrollable widget. Integrated with ``widgets.Label`` and ``widgets.List``.
- ``dfhack.constructions.findAtTile()``: exposed preexisting function to Lua. - ``dfhack.constructions.findAtTile()``: exposed preexisting function to Lua.
- ``dfhack.constructions.insert()``: exposed new function to Lua. - ``dfhack.constructions.insert()``: exposed new function to Lua.
- ``widgets.EditField`` now allows other widgets to process characters that the ``on_char`` callback rejects.
# 0.47.05-r7 # 0.47.05-r7

@ -963,7 +963,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
<< " keybinding set <key>[@context] \"cmdline\" \"cmdline\"..." << endl << " keybinding set <key>[@context] \"cmdline\" \"cmdline\"..." << endl
<< " keybinding add <key>[@context] \"cmdline\" \"cmdline\"..." << endl << " keybinding add <key>[@context] \"cmdline\" \"cmdline\"..." << endl
<< "Later adds, and earlier items within one command have priority." << endl << "Later adds, and earlier items within one command have priority." << endl
<< "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, or Enter)." << endl << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << endl
<< "Context may be used to limit the scope of the binding, by" << endl << "Context may be used to limit the scope of the binding, by" << endl
<< "requiring the current context to have a certain prefix." << endl << "requiring the current context to have a certain prefix." << endl
<< "Current UI context is: " << "Current UI context is: "

@ -267,7 +267,7 @@ public:
{ {
return index_tile(designation,p); return index_tile(designation,p);
} }
bool setDesignationAt(df::coord2d p, df::tile_designation des, int32_t priority = 4000) bool setDesignationAt(df::coord2d p, df::tile_designation des, int32_t priority = 0)
{ {
if(!valid) return false; if(!valid) return false;
dirty_designations = true; dirty_designations = true;
@ -276,6 +276,12 @@ public:
index_tile(designation,p) = des; index_tile(designation,p) = des;
if((des.bits.dig || des.bits.smooth) && block) { if((des.bits.dig || des.bits.smooth) && block) {
block->flags.bits.designated = true; block->flags.bits.designated = true;
// if priority is not specified, keep the existing priority if it
// is set. otherwise default to 4000.
if (priority <= 0)
priority = priorityAt(p);
if (priority <= 0)
priority = 4000;
setPriorityAt(p, priority); setPriorityAt(p, priority);
} }
return true; return true;
@ -554,8 +560,8 @@ class DFHACK_EXPORT MapCache
Block * b= BlockAtTile(tilecoord); Block * b= BlockAtTile(tilecoord);
return b ? b->DesignationAt(tilecoord) : df::tile_designation(); return b ? b->DesignationAt(tilecoord) : df::tile_designation();
} }
// priority is optional, only set if >= 0 // if priority is 0, it is kept unchanged if previously set, otherwise 4000
bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = 4000) bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = 0)
{ {
if (Block *b = BlockAtTile(tilecoord)) if (Block *b = BlockAtTile(tilecoord))
{ {

@ -56,6 +56,7 @@ function MaterialDialog:init(info)
frame = { l = 0, r = 0, t = 4, b = 2 }, frame = { l = 0, r = 0, t = 4, b = 2 },
icon_width = 2, icon_width = 2,
on_submit = self:callback('onSubmitItem'), on_submit = self:callback('onSubmitItem'),
edit_on_char=function(c) return c:match('%l') end,
}, },
widgets.Label{ widgets.Label{
text = { { text = { {

@ -326,6 +326,8 @@ function EditField:onInput(keys)
if not self.on_char or self.on_char(cv, old) then if not self.on_char or self.on_char(cv, old) then
self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor), self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor),
self.cursor + 1) self.cursor + 1)
elseif self.on_char then
return self.modal
end end
end end
if self.on_change and self.text ~= old then if self.on_change and self.text ~= old then
@ -363,8 +365,6 @@ end
-- Scrollbar -- -- Scrollbar --
--------------- ---------------
-- these can be overridden by the user, e.g.:
-- require('gui.widgets').SCROLL_DELAY_MS = 100
SCROLL_INITIAL_DELAY_MS = 300 SCROLL_INITIAL_DELAY_MS = 300
SCROLL_DELAY_MS = 20 SCROLL_DELAY_MS = 20
@ -389,6 +389,17 @@ function Scrollbar:init()
self:update(1, 1, 1) self:update(1, 1, 1)
end end
local function scrollbar_get_max_pos_and_height(scrollbar)
local frame_body = scrollbar.frame_body
local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2
local height = math.max(1, math.floor(
(math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) /
scrollbar.num_elems))
return scrollbar_body_height - height, height
end
-- calculate and cache the number of tiles of empty space above the top of the -- calculate and cache the number of tiles of empty space above the top of the
-- scrollbar and the number of tiles the scrollbar should occupy to represent -- scrollbar and the number of tiles the scrollbar should occupy to represent
-- the percentage of text that is on the screen. -- the percentage of text that is on the screen.
@ -398,39 +409,33 @@ function Scrollbar:update(top_elem, elems_per_page, num_elems)
if not top_elem then error('must specify index of new top element') end if not top_elem then error('must specify index of new top element') end
elems_per_page = elems_per_page or self.elems_per_page elems_per_page = elems_per_page or self.elems_per_page
num_elems = num_elems or self.num_elems num_elems = num_elems or self.num_elems
self.top_elem = top_elem
self.elems_per_page, self.num_elems = elems_per_page, num_elems
local frame_height = self.frame_body and self.frame_body.height or 3 local max_pos, height = scrollbar_get_max_pos_and_height(self)
local scrollbar_body_height = frame_height - 2
local height = math.max(1, math.floor(
(math.min(elems_per_page, num_elems) * scrollbar_body_height) /
num_elems))
local max_pos = scrollbar_body_height - height
local pos = (num_elems == elems_per_page) and 0 or local pos = (num_elems == elems_per_page) and 0 or
math.ceil(((top_elem-1) * max_pos) / math.ceil(((top_elem-1) * max_pos) /
(num_elems - elems_per_page)) (num_elems - elems_per_page))
self.top_elem = top_elem
self.elems_per_page, self.num_elems = elems_per_page, num_elems
self.bar_offset, self.bar_height = pos, height self.bar_offset, self.bar_height = pos, height
end end
local function scrollbar_do_drag(scrollbar) local function scrollbar_do_drag(scrollbar)
local x,y = dfhack.screen.getMousePos() local _,y = scrollbar.frame_body:localXY(dfhack.screen.getMousePos())
x,y = scrollbar.frame_body:localXY(x,y) local cur_pos = y - scrollbar.is_dragging
local bar_idx = y - scrollbar.bar_offset local max_top = scrollbar.num_elems - scrollbar.elems_per_page + 1
local delta = bar_idx - scrollbar.is_dragging local max_pos = scrollbar_get_max_pos_and_height(scrollbar)
if delta < -scrollbar.bar_height then local new_top_elem = math.floor(cur_pos * max_top / max_pos) + 1
scrollbar.on_scroll('up_large') new_top_elem = math.max(1, math.min(new_top_elem, max_top))
elseif delta < 0 then if new_top_elem ~= scrollbar.top_elem then
scrollbar.on_scroll('up_small') scrollbar.on_scroll(new_top_elem)
elseif delta > scrollbar.bar_height then
scrollbar.on_scroll('down_large')
elseif delta > 0 then
scrollbar.on_scroll('down_small')
end end
end end
local function scrollbar_is_visible(scrollbar)
return scrollbar.elems_per_page < scrollbar.num_elems
end
local UP_ARROW_CHAR = string.char(24) local UP_ARROW_CHAR = string.char(24)
local DOWN_ARROW_CHAR = string.char(25) local DOWN_ARROW_CHAR = string.char(25)
local NO_ARROW_CHAR = string.char(32) local NO_ARROW_CHAR = string.char(32)
@ -439,7 +444,7 @@ local BAR_BG_CHAR = string.char(179)
function Scrollbar:onRenderBody(dc) function Scrollbar:onRenderBody(dc)
-- don't draw if all elements are visible -- don't draw if all elements are visible
if self.elems_per_page >= self.num_elems then return end if not scrollbar_is_visible(self) then return end
-- render up arrow if we're not at the top -- render up arrow if we're not at the top
dc:seek(0, 0):char( dc:seek(0, 0):char(
self.top_elem == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, self.fg, self.bg) self.top_elem == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, self.fg, self.bg)
@ -482,7 +487,10 @@ function Scrollbar:onRenderBody(dc)
end end
function Scrollbar:onInput(keys) function Scrollbar:onInput(keys)
if not keys._MOUSE_L_DOWN or not self.on_scroll then return false end if not keys._MOUSE_L_DOWN or not self.on_scroll
or not scrollbar_is_visible(self) then
return false
end
local _,y = self:getMousePos() local _,y = self:getMousePos()
if not y then return false end if not y then return false end
local scroll_spec = nil local scroll_spec = nil
@ -761,7 +769,9 @@ end
function Label:on_scrollbar(scroll_spec) function Label:on_scrollbar(scroll_spec)
local v = 0 local v = 0
if scroll_spec == 'down_large' then if tonumber(scroll_spec) then
v = scroll_spec - self.start_line_num
elseif scroll_spec == 'down_large' then
v = '+halfpage' v = '+halfpage'
elseif scroll_spec == 'up_large' then elseif scroll_spec == 'up_large' then
v = '-halfpage' v = '-halfpage'
@ -1134,7 +1144,9 @@ end
function List:on_scrollbar(scroll_spec) function List:on_scrollbar(scroll_spec)
local v = 0 local v = 0
if scroll_spec == 'down_large' then if tonumber(scroll_spec) then
v = scroll_spec - self.page_top
elseif scroll_spec == 'down_large' then
v = math.ceil(self.page_size / 2) v = math.ceil(self.page_size / 2)
elseif scroll_spec == 'up_large' then elseif scroll_spec == 'up_large' then
v = -math.ceil(self.page_size / 2) v = -math.ceil(self.page_size / 2)
@ -1192,7 +1204,7 @@ function List:onRenderBody(dc)
if obj.key then if obj.key then
local keystr = gui.getKeyDisplay(obj.key) local keystr = gui.getKeyDisplay(obj.key)
ip = ip-2-#keystr ip = ip-3-#keystr
dc:seek(ip,y):pen(self.text_pen) dc:seek(ip,y):pen(self.text_pen)
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
end end
@ -1274,14 +1286,22 @@ FilteredList.ATTRS {
edit_below = false, edit_below = false,
edit_key = DEFAULT_NIL, edit_key = DEFAULT_NIL,
edit_ignore_keys = DEFAULT_NIL, edit_ignore_keys = DEFAULT_NIL,
edit_on_char = DEFAULT_NIL,
} }
function FilteredList:init(info) function FilteredList:init(info)
local on_char = self:callback('onFilterChar')
if self.edit_on_char then
on_char = function(c, text)
return self.edit_on_char(c, text) and self:onFilterChar(c, text)
end
end
self.edit = EditField{ self.edit = EditField{
text_pen = info.edit_pen or info.cursor_pen, text_pen = info.edit_pen or info.cursor_pen,
frame = { l = info.icon_width, t = 0, h = 1 }, frame = { l = info.icon_width, t = 0, h = 1 },
on_change = self:callback('onFilterChange'), on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'), on_char = on_char,
key = self.edit_key, key = self.edit_key,
ignore_keys = self.edit_ignore_keys, ignore_keys = self.edit_ignore_keys,
} }

@ -303,7 +303,7 @@ int32_t MapExtras::Block::priorityAt(df::coord2d pos)
bool MapExtras::Block::setPriorityAt(df::coord2d pos, int32_t priority) bool MapExtras::Block::setPriorityAt(df::coord2d pos, int32_t priority)
{ {
if (!block || priority < 0) if (!block || priority <= 0)
return false; return false;
auto event = getPriorityEvent(block, true); auto event = getPriorityEvent(block, true);

@ -1 +1 @@
Subproject commit 0be5cc0eaf625029ef26392224db5c3d2822a63d Subproject commit 23b95d66b1c6219e7718627edfe02c8a451cc192