Merge branch 'develop' into 5008-beta1

develop
Myk Taylor 2023-04-24 15:43:32 -07:00
commit e4d84e50e8
No known key found for this signature in database
35 changed files with 339 additions and 454 deletions

@ -192,7 +192,7 @@ endif()
# set up versioning.
set(DF_VERSION "50.08b1")
set(DFHACK_RELEASE "beta2")
set(DFHACK_RELEASE "beta3")
set(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

@ -0,0 +1,5 @@
{
"planner": {
"minimized": true
}
}

@ -144,4 +144,4 @@ enable \
alias add autounsuspend suspendmanager
alias add gui/dig gui/design
alias add toggle-kbd-cursor lua "local flags4 = df.global.d_init.flags4 if flags4.KEYBOARD_CURSOR then flags4.KEYBOARD_CURSOR = false else local guidm = require('gui.dwarfmode') guidm.setCursorPos(guidm.Viewport.get():getCenter()) flags4.KEYBOARD_CURSOR = true end"
alias add version help

@ -206,6 +206,12 @@ stocksettings
Along with ``copystock``, ``loadstock`` and ``savestock``, replaced with the new
`stockpiles` API.
.. _title-version:
title-version
=============
Replaced with an `overlay`.
.. _warn-stuck-trees:
warn-stuck-trees

@ -36,20 +36,30 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## New Plugins
## Fixes
- `autoclothing`: eliminate game lag when there are many inventory items in the fort
- `buildingplan`: fixed size limit calculations for rollers
- `dig-now`: properly detect and complete smoothing designations that have been converted into active jobs
## Misc Improvements
- `buildingplan`: minimized planner panel stays minimized until you change it again
- `buildingplan`: planner panel is minimized by default and now remembers minimized state
- `buildingplan`: can now filter by gems (for gem windows) and yarn (for ropes in wells)
- ``toggle-kbd-cursor``: add hotkey for toggling the keyboard cursor (Alt-K)
- ``version``: add alias to display the DFHack help (including the version number) so something happens when players try to run "version"
- `gui/control-panel`: add preference option for hiding the terminal console on startup
- `gui/control-panel`: add preference option for hiding "armok" tools in command lists
- ``Dwarf Therapist``: add a warning to the Labors screen when Dwarf Therapist is active so players know that changes they make to that screen will have no effect. If you're starting a new embark and nobody seems to be doing anything, check your Labors tab for this warning to see if Dwarf Therapist thinks it is in control (even if it's not running).
- `overlay`: add the DFHack version string to the DF title screen
## Documentation
## API
## Lua
- ``widgets.RangeSlider``: new mouse-controlled two-headed slider widget
- ``gui.ZScreenModal``: ZScreen subclass for modal dialogs
## Removed
- `title-version`: replaced by an `overlay` widget
# 50.07-r1

@ -4323,6 +4323,13 @@ Here is an example skeleton for a ZScreen tool window::
view = view and view:raise() or MyScreen{}:show()
ZScreenModal class
------------------
A ZScreen convenience subclass that sets the attributes to something
appropriate for modal dialogs. The game is force paused, and no input is passed
through to the underlying viewscreens.
FramedScreen class
------------------
@ -5130,6 +5137,20 @@ widget does not require direct usage of ``Tab``.
usage of ``Tab`` in ``TabBar:init()`` for an example. See the default value of ``active_tab_pens`` or ``inactive_tab_pens``
in ``TabBar`` for an example of how to construct pens.
RangeSlider class
-----------------
This widget implements a mouse-interactable range-slider. The player can move its two handles to set minimum and maximum values
to define a range, or they can drag the bar itself to move both handles at once.
The parent widget owns the range values, and can control them independently (e.g. with ``CycleHotkeyLabels``). If the range values change, the ``RangeSlider`` appearance will adjust automatically.
:num_stops: Used to specify the number of "notches" in the range slider, the places where handles can stop.
(this should match the parents' number of options)
:get_left_idx_fn: The function used by the RangeSlider to get the notch index on which to display the left handle.
:get_right_idx_fn: The function used by the RangeSlider to get the notch index on which to display the right handle.
:on_left_change: Callback executed when moving the left handle.
:on_right_change: Callback executed when moving the right handle.
.. _lua-plugins:
=======

@ -1,14 +0,0 @@
title-version
=============
.. dfhack-tool::
:summary: Displays the DFHack version on DF's title screen.
:tags: unavailable interface
:no-command:
Usage
-----
::
enable title-version

@ -273,7 +273,7 @@ static std::string dfhack_version_desc()
if (Version::is_release())
s << "(release)";
else
s << "(development build " << Version::git_description() << ")";
s << "(git: " << Version::git_commit(true) << ")";
s << " on " << (sizeof(void*) == 8 ? "x86_64" : "x86");
if (strlen(Version::dfhack_build_id()))
s << " [build ID: " << Version::dfhack_build_id() << "]";

@ -1,6 +1,8 @@
#define NO_DFHACK_VERSION_MACROS
#include "DFHackVersion.h"
#include "git-describe.h"
#include <string>
namespace DFHack {
namespace Version {
int dfhack_abi_version()
@ -27,9 +29,10 @@ namespace DFHack {
{
return DFHACK_GIT_DESCRIPTION;
}
const char *git_commit()
const char* git_commit(bool short_hash)
{
return DFHACK_GIT_COMMIT;
static std::string shorty(DFHACK_GIT_COMMIT, 0, 7);
return short_hash ? shorty.c_str() : DFHACK_GIT_COMMIT;
}
const char *git_xml_commit()
{

@ -8,7 +8,7 @@ namespace DFHack {
int dfhack_abi_version();
const char *git_description();
const char *git_commit();
const char* git_commit(bool short_hash = false);
const char *git_xml_commit();
const char *git_xml_expected_commit();
bool git_xml_match();

@ -342,26 +342,6 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT void PushInterfaceKeys(lua_State *L, const std::set<df::interface_key> &keys);
template<class T>
void PushVector(lua_State *state, const T &pvec, bool addn = false)
{
lua_createtable(state,pvec.size(), addn?1:0);
if (addn)
{
lua_pushinteger(state, pvec.size());
lua_setfield(state, -2, "n");
}
for (size_t i = 0; i < pvec.size(); i++)
{
Push(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
DFHACK_EXPORT void GetVector(lua_State *state, std::vector<std::string> &pvec, int idx = 1);
DFHACK_EXPORT int PushPosXYZ(lua_State *state, const df::coord &pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, const df::coord2d &pos);
@ -412,6 +392,26 @@ namespace DFHack {namespace Lua {
lua_settable(state, -3);
}
template<class T>
void PushVector(lua_State *state, const T &pvec, bool addn = false)
{
lua_createtable(state,pvec.size(), addn?1:0);
if (addn)
{
lua_pushinteger(state, pvec.size());
lua_setfield(state, -2, "n");
}
for (size_t i = 0; i < pvec.size(); i++)
{
Push(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
DFHACK_EXPORT void GetVector(lua_State *state, std::vector<std::string> &pvec, int idx = 1);
DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true);
DFHACK_EXPORT bool IsCoreContext(lua_State *state);

@ -867,8 +867,17 @@ function ZScreen:onGetSelectedPlant()
return zscreen_get_any(self, 'Plant')
end
--------------------------
-- Framed screen object --
-- convenience subclass for modal dialogs
ZScreenModal = defclass(ZScreenModal, ZScreen)
ZScreenModal.ATTRS{
defocusable = false,
force_pause = true,
pass_pause = false,
pass_movement_keys = false,
pass_mouse_clicks = false,
}
-- Framed screen object
--------------------------
-- Plain grey-colored frame.

@ -2293,4 +2293,141 @@ function TabBar:onInput(keys)
end
end
--------------------------------
-- RangeSlider
--
RangeSlider = defclass(RangeSlider, Widget)
RangeSlider.ATTRS{
num_stops=DEFAULT_NIL,
get_left_idx_fn=DEFAULT_NIL,
get_right_idx_fn=DEFAULT_NIL,
on_left_change=DEFAULT_NIL,
on_right_change=DEFAULT_NIL,
}
function RangeSlider:preinit(init_table)
init_table.frame = init_table.frame or {}
init_table.frame.h = init_table.frame.h or 1
end
function RangeSlider:init()
if self.num_stops < 2 then error('too few RangeSlider stops') end
self.is_dragging_target = nil -- 'left', 'right', or 'both'
self.is_dragging_idx = nil -- offset from leftmost dragged tile
end
local function rangeslider_get_width_per_idx(self)
return math.max(5, (self.frame_body.width-7) // (self.num_stops-1))
end
function RangeSlider:onInput(keys)
if not keys._MOUSE_L_DOWN then return false end
local x = self:getMousePos()
if not x then return false end
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
local width_per_idx = rangeslider_get_width_per_idx(self)
local left_pos = width_per_idx*(left_idx-1)
local right_pos = width_per_idx*(right_idx-1) + 4
if x < left_pos then
self.on_left_change(self.get_left_idx_fn() - 1)
elseif x < left_pos+3 then
self.is_dragging_target = 'left'
self.is_dragging_idx = x - left_pos
elseif x < right_pos then
self.is_dragging_target = 'both'
self.is_dragging_idx = x - left_pos
elseif x < right_pos+3 then
self.is_dragging_target = 'right'
self.is_dragging_idx = x - right_pos
else
self.on_right_change(self.get_right_idx_fn() + 1)
end
return true
end
local function rangeslider_do_drag(self, width_per_idx)
local x = self.frame_body:localXY(dfhack.screen.getMousePos())
local cur_pos = x - self.is_dragging_idx
cur_pos = math.max(0, cur_pos)
cur_pos = math.min(width_per_idx*(self.num_stops-1)+7, cur_pos)
local offset = self.is_dragging_target == 'right' and -2 or 1
local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1
local new_left_idx, new_right_idx
if self.is_dragging_target == 'right' then
new_right_idx = new_idx
else
new_left_idx = new_idx
if self.is_dragging_target == 'both' then
new_right_idx = new_left_idx + self.get_right_idx_fn() - self.get_left_idx_fn()
if new_right_idx > self.num_stops then
return
end
end
end
if new_left_idx and new_left_idx ~= self.get_left_idx_fn() then
self.on_left_change(new_left_idx)
end
if new_right_idx and new_right_idx ~= self.get_right_idx_fn() then
self.on_right_change(new_right_idx)
end
end
local SLIDER_LEFT_END = to_pen{ch=198, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK = to_pen{ch=205, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK_SELECTED = to_pen{ch=205, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
local SLIDER_TRACK_STOP = to_pen{ch=216, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK_STOP_SELECTED = to_pen{ch=216, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
local SLIDER_RIGHT_END = to_pen{ch=181, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TAB_LEFT = to_pen{ch=60, fg=COLOR_BLACK, bg=COLOR_YELLOW}
local SLIDER_TAB_CENTER = to_pen{ch=9, fg=COLOR_BLACK, bg=COLOR_YELLOW}
local SLIDER_TAB_RIGHT = to_pen{ch=62, fg=COLOR_BLACK, bg=COLOR_YELLOW}
function RangeSlider:onRenderBody(dc, rect)
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
local width_per_idx = rangeslider_get_width_per_idx(self)
-- draw track
dc:seek(1,0)
dc:char(nil, SLIDER_LEFT_END)
dc:char(nil, SLIDER_TRACK)
for stop_idx=1,self.num_stops-1 do
local track_stop_pen = SLIDER_TRACK_STOP_SELECTED
local track_pen = SLIDER_TRACK_SELECTED
if left_idx > stop_idx or right_idx < stop_idx then
track_stop_pen = SLIDER_TRACK_STOP
track_pen = SLIDER_TRACK
elseif right_idx == stop_idx then
track_pen = SLIDER_TRACK
end
dc:char(nil, track_stop_pen)
for i=2,width_per_idx do
dc:char(nil, track_pen)
end
end
if right_idx >= self.num_stops then
dc:char(nil, SLIDER_TRACK_STOP_SELECTED)
else
dc:char(nil, SLIDER_TRACK_STOP)
end
dc:char(nil, SLIDER_TRACK)
dc:char(nil, SLIDER_RIGHT_END)
-- draw tabs
dc:seek(width_per_idx*(left_idx-1))
dc:char(nil, SLIDER_TAB_LEFT)
dc:char(nil, SLIDER_TAB_CENTER)
dc:char(nil, SLIDER_TAB_RIGHT)
dc:seek(width_per_idx*(right_idx-1)+4)
dc:char(nil, SLIDER_TAB_LEFT)
dc:char(nil, SLIDER_TAB_CENTER)
dc:char(nil, SLIDER_TAB_RIGHT)
-- manage dragging
if self.is_dragging_target then
rangeslider_do_drag(self, width_per_idx)
end
if df.global.enabler.mouse_lbut == 0 then
self.is_dragging_target = nil
self.is_dragging_idx = nil
end
end
return _ENV

@ -1115,31 +1115,17 @@ static void createDesign(df::building *bld, bool rough)
static int getMaxStockpileId()
{
auto &vec = world->buildings.other[buildings_other_id::STOCKPILE];
int max_id = 0;
for (size_t i = 0; i < vec.size(); i++)
{
auto bld = strict_virtual_cast<df::building_stockpilest>(vec[i]);
if (bld)
max_id = std::max(max_id, bld->stockpile_number);
}
for (auto bld : world->buildings.other.STOCKPILE)
max_id = std::max(max_id, bld->stockpile_number);
return max_id;
}
static int getMaxCivzoneId()
{
auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE];
int max_id = 0;
for (size_t i = 0; i < vec.size(); i++)
{
auto bld = strict_virtual_cast<df::building_civzonest>(vec[i]);
if (bld)
max_id = std::max(max_id, bld->zone_num);
}
for (auto bld : world->buildings.other.ANY_ZONE)
max_id = std::max(max_id, bld->zone_num);
return max_id;
}

@ -84,6 +84,7 @@ using namespace DFHack;
#include "df/unit.h"
#include "df/unit_inventory_item.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_titlest.h"
#include "df/world.h"
const size_t MAX_REPORTS_SIZE = 3000; // DF clears old reports to maintain this vector size
@ -144,6 +145,17 @@ static std::map<virtual_identity*, getFocusStringsHandler> getFocusStringsHandle
); \
static void getFocusStrings_##screen_type(std::string &baseFocus, std::vector<std::string> &focusStrings, VIEWSCREEN(screen_type) *screen)
DEFINE_GET_FOCUS_STRING_HANDLER(title)
{
if (screen->managing_mods)
focusStrings.push_back(baseFocus + "/Mods");
else if (game->main_interface.settings.open)
focusStrings.push_back(baseFocus + "/Settings");
if (focusStrings.empty())
focusStrings.push_back(baseFocus + "/Default");
}
DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
{
std::string newFocusString;

@ -162,7 +162,6 @@ dfhack_plugin(strangemood strangemood.cpp)
dfhack_plugin(tailor tailor.cpp LINK_LIBRARIES lua)
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(title-version title-version.cpp)
#dfhack_plugin(trackstop trackstop.cpp)
#dfhack_plugin(tubefill tubefill.cpp)
#add_subdirectory(tweak)

@ -565,7 +565,7 @@ static void find_needed_clothing_items()
if (!item)
{
WARN(cycle).print("autoclothing: Invalid inventory item ID: %d\n", ownedItem);
DEBUG(cycle).print("autoclothing: Invalid inventory item ID: %d\n", ownedItem);
continue;
}
@ -818,7 +818,7 @@ static void generate_report(color_ostream& out)
auto item = Items::findItemByID(itemId);
if (!item)
{
WARN(cycle,out).print("autoclothing: Invalid inventory item ID: %d\n", itemId);
DEBUG(cycle, out).print("autoclothing: Invalid inventory item ID: %d\n", itemId);
continue;
}
if (item->getWear() >= 1)

@ -154,6 +154,7 @@ static const df::dfhack_material_category gem_cat(df::dfhack_material_category::
static const df::dfhack_material_category clay_cat(df::dfhack_material_category::mask_clay);
static const df::dfhack_material_category cloth_cat(df::dfhack_material_category::mask_cloth);
static const df::dfhack_material_category silk_cat(df::dfhack_material_category::mask_silk);
static const df::dfhack_material_category yarn_cat(df::dfhack_material_category::mask_yarn);
static void cache_matched(int16_t type, int32_t index) {
MaterialInfo mi;
@ -182,6 +183,9 @@ static void cache_matched(int16_t type, int32_t index) {
} else if (mi.matches(silk_cat)) {
DEBUG(status).print("cached silk material: %s (%d, %d)\n", mi.toString().c_str(), type, index);
mat_cache.emplace(mi.toString(), std::make_pair(mi, "silk"));
} else if (mi.matches(yarn_cat)) {
DEBUG(status).print("cached yarn material: %s (%d, %d)\n", mi.toString().c_str(), type, index);
mat_cache.emplace(mi.toString(), std::make_pair(mi, "yarn"));
}
else
TRACE(status).print("not matched: %s\n", mi.toString().c_str());
@ -208,6 +212,7 @@ static void load_material_cache() {
load_organic_material_cache(df::organic_mat_category::Wood);
load_organic_material_cache(df::organic_mat_category::PlantFiber);
load_organic_material_cache(df::organic_mat_category::Silk);
load_organic_material_cache(df::organic_mat_category::Yarn);
}
static HeatSafety get_heat_safety_filter(const BuildingTypeKey &key) {
@ -812,6 +817,8 @@ static int setMaterialMaskFilter(lua_State *L) {
mask |= cloth_cat.whole;
else if (cat == "silk")
mask |= silk_cat.whole;
else if (cat == "yarn")
mask |= yarn_cat.whole;
}
DEBUG(status,*out).print(
"setting material mask filter for building_type=%d subtype=%d custom=%d index=%d to %x\n",
@ -860,6 +867,7 @@ static int getMaterialMaskFilter(lua_State *L) {
ret.emplace("clay", !bits || bits & clay_cat.whole);
ret.emplace("cloth", !bits || bits & cloth_cat.whole);
ret.emplace("silk", !bits || bits & silk_cat.whole);
ret.emplace("yarn", !bits || bits & yarn_cat.whole);
Lua::Push(L, ret);
return 1;
}
@ -912,6 +920,8 @@ static int setMaterialFilter(lua_State *L) {
mask.whole |= cloth_cat.whole;
else if (mat.matches(silk_cat))
mask.whole |= silk_cat.whole;
else if (mat.matches(yarn_cat))
mask.whole |= yarn_cat.whole;
}
filter.setMaterialMask(mask.whole);
get_item_filters(*out, key).setItemFilter(*out, filter, index);

@ -113,14 +113,10 @@ public:
case job_type::CarveUpDownStaircase:
td.bits.dig = tile_dig_designation::UpDownStair;
break;
case job_type::DetailWall:
case job_type::DetailFloor: {
df::tiletype tt = map.tiletypeAt(job->pos);
if (tileSpecial(tt) != df::tiletype_special::SMOOTH) {
td.bits.smooth = 1;
}
case job_type::SmoothWall:
case job_type::SmoothFloor:
td.bits.smooth = 1;
break;
}
case job_type::CarveTrack:
to.bits.carve_track_north = (job->item_category.whole >> 18) & 1;
to.bits.carve_track_south = (job->item_category.whole >> 19) & 1;

@ -10,7 +10,7 @@ AutolaborOverlay.ATTRS{
default_enabled=true,
viewscreens='dwarfmode/Info/LABOR',
frame={w=29, h=5},
frame_style=gui.MEDIUM_FRAME,
frame_style=gui.THIN_FRAME,
frame_background=gui.CLEAR_PEN,
}
@ -18,9 +18,20 @@ function AutolaborOverlay:init()
self:addviews{
widgets.Label{
frame={t=0, l=0},
text_pen=COLOR_RED,
text_pen=COLOR_LIGHTRED,
text='DFHack autolabor is active!',
visible=isEnabled,
},
widgets.Label{
frame={t=0, l=0},
text_pen=COLOR_LIGHTRED,
text='Dwarf Therapist is active!',
visible=function() return not isEnabled() end,
},
widgets.Label{
frame={t=1, l=0},
text_pen=COLOR_WHITE,
text={
'DFHack autolabor is active!', NEWLINE,
'Any changes made on this', NEWLINE,
'screen will have no effect.'
},
@ -29,7 +40,7 @@ function AutolaborOverlay:init()
end
function AutolaborOverlay:render(dc)
if not isEnabled() then return false end
if df.global.game_extra.external_flag ~= 1 then return end
AutolaborOverlay.super.render(self, dc)
end

@ -12,143 +12,6 @@ local function get_cur_filters()
uibs.building_subtype, uibs.custom_type)
end
--------------------------------
-- Slider
--
Slider = defclass(Slider, widgets.Widget)
Slider.ATTRS{
num_stops=DEFAULT_NIL,
get_left_idx_fn=DEFAULT_NIL,
get_right_idx_fn=DEFAULT_NIL,
on_left_change=DEFAULT_NIL,
on_right_change=DEFAULT_NIL,
}
function Slider:preinit(init_table)
init_table.frame = init_table.frame or {}
init_table.frame.h = init_table.frame.h or 1
end
function Slider:init()
if self.num_stops < 2 then error('too few Slider stops') end
self.is_dragging_target = nil -- 'left', 'right', or 'both'
self.is_dragging_idx = nil -- offset from leftmost dragged tile
end
local function slider_get_width_per_idx(self)
return math.max(5, (self.frame_body.width-7) // (self.num_stops-1))
end
function Slider:onInput(keys)
if not keys._MOUSE_L_DOWN then return false end
local x = self:getMousePos()
if not x then return false end
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
local width_per_idx = slider_get_width_per_idx(self)
local left_pos = width_per_idx*(left_idx-1)
local right_pos = width_per_idx*(right_idx-1) + 4
if x < left_pos then
self.on_left_change(self.get_left_idx_fn() - 1)
elseif x < left_pos+3 then
self.is_dragging_target = 'left'
self.is_dragging_idx = x - left_pos
elseif x < right_pos then
self.is_dragging_target = 'both'
self.is_dragging_idx = x - left_pos
elseif x < right_pos+3 then
self.is_dragging_target = 'right'
self.is_dragging_idx = x - right_pos
else
self.on_right_change(self.get_right_idx_fn() + 1)
end
return true
end
local function slider_do_drag(self, width_per_idx)
local x = self.frame_body:localXY(dfhack.screen.getMousePos())
local cur_pos = x - self.is_dragging_idx
cur_pos = math.max(0, cur_pos)
cur_pos = math.min(width_per_idx*(self.num_stops-1)+7, cur_pos)
local offset = self.is_dragging_target == 'right' and -2 or 1
local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1
local new_left_idx, new_right_idx
if self.is_dragging_target == 'right' then
new_right_idx = new_idx
else
new_left_idx = new_idx
if self.is_dragging_target == 'both' then
new_right_idx = new_left_idx + self.get_right_idx_fn() - self.get_left_idx_fn()
if new_right_idx > self.num_stops then
return
end
end
end
if new_left_idx and new_left_idx ~= self.get_left_idx_fn() then
self.on_left_change(new_left_idx)
end
if new_right_idx and new_right_idx ~= self.get_right_idx_fn() then
self.on_right_change(new_right_idx)
end
end
local SLIDER_LEFT_END = to_pen{ch=198, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK = to_pen{ch=205, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK_SELECTED = to_pen{ch=205, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
local SLIDER_TRACK_STOP = to_pen{ch=216, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK_STOP_SELECTED = to_pen{ch=216, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
local SLIDER_RIGHT_END = to_pen{ch=181, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TAB_LEFT = to_pen{ch=60, fg=COLOR_BLACK, bg=COLOR_YELLOW}
local SLIDER_TAB_CENTER = to_pen{ch=9, fg=COLOR_BLACK, bg=COLOR_YELLOW}
local SLIDER_TAB_RIGHT = to_pen{ch=62, fg=COLOR_BLACK, bg=COLOR_YELLOW}
function Slider:onRenderBody(dc, rect)
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
local width_per_idx = slider_get_width_per_idx(self)
-- draw track
dc:seek(1,0)
dc:char(nil, SLIDER_LEFT_END)
dc:char(nil, SLIDER_TRACK)
for stop_idx=1,self.num_stops-1 do
local track_stop_pen = SLIDER_TRACK_STOP_SELECTED
local track_pen = SLIDER_TRACK_SELECTED
if left_idx > stop_idx or right_idx < stop_idx then
track_stop_pen = SLIDER_TRACK_STOP
track_pen = SLIDER_TRACK
elseif right_idx == stop_idx then
track_pen = SLIDER_TRACK
end
dc:char(nil, track_stop_pen)
for i=2,width_per_idx do
dc:char(nil, track_pen)
end
end
if right_idx >= self.num_stops then
dc:char(nil, SLIDER_TRACK_STOP_SELECTED)
else
dc:char(nil, SLIDER_TRACK_STOP)
end
dc:char(nil, SLIDER_TRACK)
dc:char(nil, SLIDER_RIGHT_END)
-- draw tabs
dc:seek(width_per_idx*(left_idx-1))
dc:char(nil, SLIDER_TAB_LEFT)
dc:char(nil, SLIDER_TAB_CENTER)
dc:char(nil, SLIDER_TAB_RIGHT)
dc:seek(width_per_idx*(right_idx-1)+4)
dc:char(nil, SLIDER_TAB_LEFT)
dc:char(nil, SLIDER_TAB_CENTER)
dc:char(nil, SLIDER_TAB_RIGHT)
-- manage dragging
if self.is_dragging_target then
slider_do_drag(self, width_per_idx)
end
if df.global.enabler.mouse_lbut == 0 then
self.is_dragging_target = nil
self.is_dragging_idx = nil
end
end
--------------------------------
-- QualityAndMaterialsPage
--
@ -328,7 +191,7 @@ function QualityAndMaterialsPage:init()
enabled=enable_item_quality,
on_change=function(val) self:set_max_quality(val+1) end,
},
Slider{
widgets.RangeSlider{
frame={l=0, t=6},
num_stops=7,
get_left_idx_fn=function()
@ -456,6 +319,7 @@ function QualityAndMaterialsPage:refresh()
make_cat_choice('Clay', 'clay', 'CUSTOM_SHIFT_C', cats),
make_cat_choice('Cloth', 'cloth', 'CUSTOM_SHIFT_L', cats),
make_cat_choice('Silk', 'silk', 'CUSTOM_SHIFT_K', cats),
make_cat_choice('Yarn', 'yarn', 'CUSTOM_SHIFT_Y', cats),
}
self.subviews.materials_categories:setChoices(category_choices)

@ -4,12 +4,15 @@ local itemselection = require('plugins.buildingplan.itemselection')
local filterselection = require('plugins.buildingplan.filterselection')
local gui = require('gui')
local guidm = require('gui.dwarfmode')
local json = require('json')
local overlay = require('plugins.overlay')
local pens = require('plugins.buildingplan.pens')
local utils = require('utils')
local widgets = require('gui.widgets')
require('dfhack.buildings')
config = config or json.open('dfhack-config/buildingplan.json')
local uibs = df.global.buildreq
reset_counts_flag = false
@ -31,9 +34,10 @@ local function get_selection_size_limits()
or btype == df.building_type.RoadPaved
or btype == df.building_type.RoadDirt then
return {w=31, h=31}
elseif btype == df.building_type.AxleHorizontal
or btype == df.building_type.Rollers then
elseif btype == df.building_type.AxleHorizontal then
return uibs.direction == 1 and {w=1, h=31} or {w=31, h=1}
elseif btype == df.building_type.Rollers then
return (uibs.direction == 1 or uibs.direction == 3) and {w=31, h=1} or {w=1, h=31}
end
end
@ -336,14 +340,14 @@ PlannerOverlay.ATTRS{
function PlannerOverlay:init()
self.selected = 1
self.minimized = false
self.state = ensure_key(config.data, 'planner')
local main_panel = widgets.Panel{
view_id='main',
frame={t=1, l=0, r=0, h=14},
frame_style=gui.INTERIOR_MEDIUM_FRAME,
frame_background=gui.CLEAR_PEN,
visible=function() return not self.minimized end,
visible=self:callback('is_not_minimized'),
}
local minimized_panel = widgets.Panel{
@ -355,8 +359,8 @@ function PlannerOverlay:init()
{text=' show Planner ', pen=pens.MINI_TEXT_PEN, hpen=pens.MINI_TEXT_HPEN},
{text='['..string.char(31)..']', pen=pens.MINI_BUTT_PEN, hpen=pens.MINI_BUTT_HPEN},
},
visible=function() return self.minimized end,
on_click=function() self.minimized = not self.minimized end,
visible=self:callback('is_minimized'),
on_click=self:callback('toggle_minimized'),
},
widgets.Label{
frame={t=0, r=0, h=1},
@ -364,8 +368,8 @@ function PlannerOverlay:init()
{text=' hide Planner ', pen=pens.MINI_TEXT_PEN, hpen=pens.MINI_TEXT_HPEN},
{text='['..string.char(30)..']', pen=pens.MINI_BUTT_PEN, hpen=pens.MINI_BUTT_HPEN},
},
visible=function() return not self.minimized end,
on_click=function() self.minimized = not self.minimized end,
visible=self:callback('is_not_minimized'),
on_click=self:callback('toggle_minimized'),
},
},
}
@ -554,7 +558,7 @@ function PlannerOverlay:init()
view_id='divider',
frame={t=10, l=0, r=0, h=1},
on_render=self:callback('draw_divider_h'),
visible=function() return not self.minimized end,
visible=self:callback('is_not_minimized'),
}
local error_panel = widgets.ResizingPanel{
@ -562,7 +566,7 @@ function PlannerOverlay:init()
frame={t=15, l=0, r=0},
frame_style=gui.BOLD_FRAME,
frame_background=gui.CLEAR_PEN,
visible=function() return not self.minimized end,
visible=self:callback('is_not_minimized'),
}
error_panel:addviews{
@ -609,7 +613,7 @@ function PlannerOverlay:init()
frame={t=0, l=1, w=37, h=1},
frame_inset=0,
frame_background=gui.CLEAR_PEN,
visible=function() return not self.minimized end,
visible=self:callback('is_not_minimized'),
subviews={
prev_next_selector,
},
@ -624,6 +628,19 @@ function PlannerOverlay:init()
}
end
function PlannerOverlay:is_minimized()
return self.state.minimized
end
function PlannerOverlay:is_not_minimized()
return not self.state.minimized
end
function PlannerOverlay:toggle_minimized()
self.state.minimized = not self.state.minimized
config:write()
end
function PlannerOverlay:draw_divider_h(dc)
local x2 = dc.width -1
for x=0,x2 do
@ -735,13 +752,13 @@ function PlannerOverlay:onInput(keys)
return false
end
if keys.CUSTOM_ALT_M then
self.minimized = not self.minimized
self:toggle_minimized()
return true
end
if PlannerOverlay.super.onInput(self, keys) then
return true
end
if self.minimized then return false end
if self:is_minimized() then return false end
if keys._MOUSE_L_DOWN then
if is_over_options_panel() then return false end
local detect_rect = copyall(self.frame_rect)
@ -837,7 +854,7 @@ function PlannerOverlay:onRenderFrame(dc, rect)
uibs.building_type, uibs.building_subtype, uibs.custom_type))
end
if self.minimized then return end
if self:is_minimized() then return end
local bounds = get_selected_bounds(self.saved_selection_pos, self.saved_pos)
if not bounds then return end

@ -39,7 +39,7 @@ HotspotMenuWidget.ATTRS{
-- 'new_region', -- conflicts with vanilla panel layouts
'savegame',
'setupdwarfgame',
'title',
'title/Default',
'update_region',
'world'
},

@ -562,4 +562,42 @@ function OverlayWidget:init()
self.frame.h = self.frame.h or 1
end
-- ------------------- --
-- TitleVersionOverlay --
-- ------------------- --
TitleVersionOverlay = defclass(TitleVersionOverlay, OverlayWidget)
TitleVersionOverlay.ATTRS{
default_pos={x=50, y=-2},
default_enabled=true,
viewscreens='title/Default',
frame={w=35, h=3},
}
function TitleVersionOverlay:init()
local text = {}
table.insert(text, 'DFHack ' .. dfhack.getDFHackVersion() ..
(dfhack.isPrerelease() and (' (git: %s)'):format(dfhack.getGitCommit(true)) or ''))
if #dfhack.getDFHackBuildID() > 0 then
table.insert(text, NEWLINE)
table.insert(text, 'Build ID: ' .. dfhack.getDFHackBuildID())
end
if dfhack.isPrerelease() then
table.insert(text, NEWLINE)
table.insert(text, {text='Pre-release build', pen=COLOR_LIGHTRED})
end
self:addviews{
widgets.Label{
frame={t=0, l=0},
text=text,
text_pen=COLOR_WHITE,
},
}
end
OVERLAY_WIDGETS = {
title_version = TitleVersionOverlay,
}
return _ENV

@ -1,109 +0,0 @@
#include <cmath>
#include <cstdio>
#include <cstring>
#include <stack>
#include <string>
#include <vector>
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "VTableInterpose.h"
#include "DFHackVersion.h"
#include "df/graphic.h"
#include "df/viewscreen_optionst.h"
#include "df/viewscreen_titlest.h"
#include "uicommon.h"
using std::vector;
using std::string;
using namespace DFHack;
DFHACK_PLUGIN("title-version");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
void draw_version(int start_x, int start_y) {
int x = start_x,
y = start_y;
OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION);
if (!DFHACK_IS_RELEASE)
{
OutputString(COLOR_WHITE, x, y, " (dev)");
x = start_x; y++;
OutputString(COLOR_WHITE, x, y, "Git: ");
OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION);
}
if (strlen(DFHACK_BUILD_ID))
{
x = start_x; y++;
OutputString(COLOR_WHITE, x, y, "Build ID: ");
OutputString(COLOR_WHITE, x, y, DFHACK_BUILD_ID);
}
if (DFHACK_IS_PRERELEASE)
{
x = start_x; y++;
OutputString(COLOR_LIGHTRED, x, y, "Pre-release build");
}
}
struct title_version_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (!loading)
draw_version(0, 0);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render);
struct options_version_hook : df::viewscreen_optionst {
typedef df::viewscreen_optionst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (!msg_quit && !in_retire_adv && !msg_peasant &&
!in_retire_dwf_abandon_adv && !in_abandon_dwf && !ending_game)
draw_version(2, gps->dimy - 6);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(options_version_hook, render);
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable) ||
!INTERPOSE_HOOK(options_version_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init (color_ostream &out, vector<PluginCommand> &commands)
{
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
INTERPOSE_HOOK(title_version_hook, render).remove();
INTERPOSE_HOOK(options_version_hook, render).remove();
return CR_OK;
}

@ -1 +1 @@
Subproject commit 5da969fce69a5b9330f183cc0629798bf9907b69
Subproject commit 6b4001dc2f9d0e662bb7d06d8ba6fcf343a656aa

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)

@ -1,9 +0,0 @@
#!/bin/sh
script_name="$(basename "$0")"
new_script_path="$(dirname "$0")/../ci/${script_name}"
printf >&2 "\nNote: travis/%s is deprecated. Use ci/%s instead.\n\n" "${script_name}" "${script_name}"
"${new_script_path}" "$@"
exit $?

@ -1,9 +0,0 @@
#!/bin/sh
script_name="$(basename "$0")"
new_script_path="$(dirname "$0")/../ci/${script_name}"
printf >&2 "\nNote: travis/%s is deprecated. Use ci/%s instead.\n\n" "${script_name}" "${script_name}"
"${new_script_path}" "$@"
exit $?

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)

@ -1,14 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
script_name = os.path.basename(__file__)
new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name)
sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name))
sys.stderr.flush()
p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:])
sys.exit(p.returncode)