609 lines
21 KiB
Lua
609 lines
21 KiB
Lua
local _ENV = mkmodule('plugins.buildingplan.filterselection')
|
|
|
|
local gui = require('gui')
|
|
local pens = require('plugins.buildingplan.pens')
|
|
local widgets = require('gui.widgets')
|
|
|
|
local uibs = df.global.buildreq
|
|
local to_pen = dfhack.pen.parse
|
|
|
|
local function get_cur_filters()
|
|
return dfhack.buildings.getFiltersByType({}, uibs.building_type,
|
|
uibs.building_subtype, uibs.custom_type)
|
|
end
|
|
|
|
--------------------------------
|
|
-- QualityAndMaterialsPage
|
|
--
|
|
|
|
QualityAndMaterialsPage = defclass(QualityAndMaterialsPage, widgets.Panel)
|
|
QualityAndMaterialsPage.ATTRS{
|
|
frame={t=0, l=0},
|
|
index=DEFAULT_NIL,
|
|
desc=DEFAULT_NIL,
|
|
}
|
|
|
|
local TYPE_COL_WIDTH = 20
|
|
local HEADER_HEIGHT = 7
|
|
local QUALITY_HEIGHT = 9
|
|
local FOOTER_HEIGHT = 4
|
|
|
|
-- returns whether the items matched by the specified filter can have a quality
|
|
-- rating. This also conveniently indicates whether an item can be decorated.
|
|
local function can_be_improved(idx)
|
|
local filter = get_cur_filters()[idx]
|
|
if filter.flags2 and filter.flags2.building_material then
|
|
return false;
|
|
end
|
|
return filter.item_type ~= df.item_type.WOOD and
|
|
filter.item_type ~= df.item_type.BLOCKS and
|
|
filter.item_type ~= df.item_type.BAR and
|
|
filter.item_type ~= df.item_type.BOULDER
|
|
end
|
|
|
|
local function mat_sort_by_name(a, b)
|
|
return a.name < b.name
|
|
end
|
|
|
|
local function mat_sort_by_quantity(a, b)
|
|
return a.quantity > b.quantity or
|
|
(a.quantity == b.quantity and mat_sort_by_name(a, b))
|
|
end
|
|
|
|
function QualityAndMaterialsPage:init()
|
|
self.dirty = true
|
|
self.summary = ''
|
|
|
|
local enable_item_quality = can_be_improved(self.index)
|
|
|
|
self:addviews{
|
|
widgets.Panel{
|
|
view_id='header',
|
|
frame={l=0, t=0, h=HEADER_HEIGHT, r=0},
|
|
frame_inset={l=1},
|
|
subviews={
|
|
widgets.Label{
|
|
frame={l=0, t=0},
|
|
text='Current filter:',
|
|
},
|
|
widgets.WrappedLabel{
|
|
frame={l=16, t=0, h=2, r=0},
|
|
text_pen=COLOR_LIGHTCYAN,
|
|
text_to_wrap=function() return self.summary end,
|
|
auto_height=false,
|
|
},
|
|
widgets.CycleHotkeyLabel{
|
|
view_id='mat_sort',
|
|
frame={l=0, t=3, w=21},
|
|
label='Sort by:',
|
|
key='CUSTOM_SHIFT_R',
|
|
options={
|
|
{label='name', value=mat_sort_by_name},
|
|
{label='available', value=mat_sort_by_quantity}
|
|
},
|
|
on_change=function() self.dirty = true end,
|
|
},
|
|
widgets.ToggleHotkeyLabel{
|
|
view_id='hide_zero',
|
|
frame={l=0, t=4, w=24},
|
|
label='Hide unavailable:',
|
|
key='CUSTOM_SHIFT_H',
|
|
initial_option=false,
|
|
on_change=function() self.dirty = true end,
|
|
},
|
|
widgets.EditField{
|
|
view_id='search',
|
|
frame={l=26, t=3},
|
|
label_text='Search: ',
|
|
on_char=function(ch) return ch:match('[%l -]') end,
|
|
},
|
|
widgets.Label{
|
|
frame={l=1, b=0},
|
|
text='Type',
|
|
text_pen=COLOR_LIGHTRED,
|
|
},
|
|
widgets.Label{
|
|
frame={l=TYPE_COL_WIDTH, b=0},
|
|
text='Material',
|
|
text_pen=COLOR_LIGHTRED,
|
|
},
|
|
},
|
|
},
|
|
widgets.Panel{
|
|
view_id='materials_lists',
|
|
frame={l=0, t=HEADER_HEIGHT, r=0, b=FOOTER_HEIGHT+QUALITY_HEIGHT},
|
|
frame_style=gui.INTERIOR_FRAME,
|
|
subviews={
|
|
widgets.List{
|
|
view_id='materials_categories',
|
|
frame={l=1, t=0, b=0, w=TYPE_COL_WIDTH-3},
|
|
scroll_keys={},
|
|
icon_width=2,
|
|
cursor_pen=COLOR_CYAN,
|
|
on_submit=self:callback('toggle_category'),
|
|
},
|
|
widgets.FilteredList{
|
|
view_id='materials_mats',
|
|
frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0},
|
|
icon_width=2,
|
|
on_submit=self:callback('toggle_material'),
|
|
},
|
|
},
|
|
},
|
|
widgets.Panel{
|
|
view_id='divider',
|
|
frame={l=TYPE_COL_WIDTH-1, t=HEADER_HEIGHT, b=FOOTER_HEIGHT+QUALITY_HEIGHT, w=1},
|
|
on_render=self:callback('draw_divider'),
|
|
},
|
|
widgets.Panel{
|
|
view_id='quality_panel',
|
|
frame={l=0, r=0, h=QUALITY_HEIGHT, b=FOOTER_HEIGHT},
|
|
frame_style=gui.INTERIOR_FRAME,
|
|
frame_title='Item quality',
|
|
subviews={
|
|
widgets.CycleHotkeyLabel{
|
|
view_id='decorated',
|
|
frame={l=0, t=1, w=23},
|
|
key='CUSTOM_SHIFT_D',
|
|
label='Decorated only:',
|
|
options={
|
|
{label='No', value=false},
|
|
{label='Yes', value=true},
|
|
},
|
|
enabled=enable_item_quality,
|
|
on_change=self:callback('set_decorated'),
|
|
},
|
|
widgets.CycleHotkeyLabel{
|
|
view_id='min_quality',
|
|
frame={l=0, t=3, w=18},
|
|
label='Min quality:',
|
|
label_below=true,
|
|
key_back='CUSTOM_SHIFT_Z',
|
|
key='CUSTOM_SHIFT_X',
|
|
options={
|
|
{label='Ordinary', value=0},
|
|
{label='Well Crafted', value=1},
|
|
{label='Finely Crafted', value=2},
|
|
{label='Superior', value=3},
|
|
{label='Exceptional', value=4},
|
|
{label='Masterful', value=5},
|
|
{label='Artifact', value=6},
|
|
},
|
|
enabled=enable_item_quality,
|
|
on_change=function(val) self:set_min_quality(val+1) end,
|
|
},
|
|
widgets.CycleHotkeyLabel{
|
|
view_id='max_quality',
|
|
frame={r=1, t=3, w=18},
|
|
label='Max quality:',
|
|
label_below=true,
|
|
key_back='CUSTOM_SHIFT_Q',
|
|
key='CUSTOM_SHIFT_W',
|
|
options={
|
|
{label='Ordinary', value=0},
|
|
{label='Well Crafted', value=1},
|
|
{label='Finely Crafted', value=2},
|
|
{label='Superior', value=3},
|
|
{label='Exceptional', value=4},
|
|
{label='Masterful', value=5},
|
|
{label='Artifact', value=6},
|
|
},
|
|
enabled=enable_item_quality,
|
|
on_change=function(val) self:set_max_quality(val+1) end,
|
|
},
|
|
widgets.RangeSlider{
|
|
frame={l=0, t=6},
|
|
num_stops=7,
|
|
get_left_idx_fn=function()
|
|
return self.subviews.min_quality:getOptionValue() + 1
|
|
end,
|
|
get_right_idx_fn=function()
|
|
return self.subviews.max_quality:getOptionValue() + 1
|
|
end,
|
|
on_left_change=self:callback('set_min_quality'),
|
|
on_right_change=self:callback('set_max_quality'),
|
|
active=enable_item_quality,
|
|
},
|
|
},
|
|
},
|
|
widgets.Panel{
|
|
view_id='footer',
|
|
frame={l=0, r=0, b=0, h=FOOTER_HEIGHT},
|
|
frame_inset={t=1, l=1},
|
|
subviews={
|
|
widgets.HotkeyLabel{
|
|
frame={l=0, t=0},
|
|
label='Toggle',
|
|
auto_width=true,
|
|
key='SELECT',
|
|
},
|
|
widgets.HotkeyLabel{
|
|
frame={l=0, t=2},
|
|
label='Done',
|
|
auto_width=true,
|
|
key='LEAVESCREEN',
|
|
},
|
|
widgets.HotkeyLabel{
|
|
frame={l=30, t=0},
|
|
label='Invert selection',
|
|
auto_width=true,
|
|
key='CUSTOM_SHIFT_I',
|
|
on_activate=self:callback('invert_materials'),
|
|
},
|
|
widgets.HotkeyLabel{
|
|
frame={l=30, t=2},
|
|
label='Delete filter',
|
|
auto_width=true,
|
|
key='CUSTOM_CTRL_D',
|
|
on_activate=self:callback('clear_filter'),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
-- replace the FilteredList's built-in EditField with our own
|
|
self.subviews.materials_mats.list.frame.t = 0
|
|
self.subviews.materials_mats.edit.visible = false
|
|
self.subviews.materials_mats.edit = self.subviews.search
|
|
self.subviews.search.on_change = self.subviews.materials_mats:callback('onFilterChange')
|
|
end
|
|
|
|
local MAT_ENABLED_PEN = to_pen{ch=string.char(251), fg=COLOR_LIGHTGREEN}
|
|
local MAT_DISABLED_PEN = to_pen{ch='x', fg=COLOR_RED}
|
|
|
|
local function make_cat_choice(label, cat, key, cats)
|
|
local enabled = cats[cat]
|
|
local icon = nil
|
|
if not cats.unset then
|
|
icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN
|
|
end
|
|
return {
|
|
text=label,
|
|
key=key,
|
|
enabled=enabled,
|
|
cat=cat,
|
|
icon=icon,
|
|
}
|
|
end
|
|
|
|
local function make_mat_choice(name, props, enabled, cats)
|
|
local quantity = tonumber(props.count)
|
|
local text = ('%5d - %s'):format(quantity, name)
|
|
local icon = nil
|
|
if not cats.unset then
|
|
icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN
|
|
end
|
|
return {
|
|
text=text,
|
|
enabled=enabled,
|
|
icon=icon,
|
|
name=name,
|
|
cat=props.category,
|
|
quantity=quantity,
|
|
}
|
|
end
|
|
|
|
function QualityAndMaterialsPage:refresh()
|
|
local summary = self.desc
|
|
local subviews = self.subviews
|
|
|
|
local buildingplan = require('plugins.buildingplan')
|
|
|
|
local heat = buildingplan.getHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type)
|
|
if heat >= 2 then summary = 'Magma safe ' .. summary
|
|
elseif heat == 1 then summary = 'Fire safe ' .. summary
|
|
else summary = 'Any ' .. summary
|
|
end
|
|
|
|
local specials = buildingplan.getSpecials(uibs.building_type, uibs.building_subtype, uibs.custom_type)
|
|
if next(specials) then
|
|
local specials_list = {}
|
|
for special in pairs(specials) do
|
|
table.insert(specials_list, special)
|
|
end
|
|
summary = summary .. ' [' .. table.concat(specials_list, ', ') .. ']'
|
|
end
|
|
|
|
local quality = buildingplan.getQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1)
|
|
subviews.decorated:setOption(quality.decorated ~= 0)
|
|
subviews.min_quality:setOption(quality.min_quality)
|
|
subviews.max_quality:setOption(quality.max_quality)
|
|
|
|
local cats = buildingplan.getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1)
|
|
local category_choices={
|
|
make_cat_choice('Stone', 'stone', 'CUSTOM_SHIFT_S', cats),
|
|
make_cat_choice('Wood', 'wood', 'CUSTOM_SHIFT_O', cats),
|
|
make_cat_choice('Metal', 'metal', 'CUSTOM_SHIFT_M', cats),
|
|
make_cat_choice('Glass', 'glass', 'CUSTOM_SHIFT_G', cats),
|
|
make_cat_choice('Gem', 'gem', 'CUSTOM_SHIFT_E', cats),
|
|
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)
|
|
|
|
local mats = buildingplan.getMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1)
|
|
local mat_choices = {}
|
|
local hide_zero = self.subviews.hide_zero:getOptionValue()
|
|
local enabled_mat_names = {}
|
|
for name,props in pairs(mats) do
|
|
local enabled = props.enabled == 'true' and cats[props.category]
|
|
if not cats.unset and enabled then
|
|
table.insert(enabled_mat_names, name)
|
|
end
|
|
if not hide_zero or tonumber(props.count) > 0 then
|
|
table.insert(mat_choices, make_mat_choice(name, props, enabled, cats))
|
|
end
|
|
end
|
|
table.sort(mat_choices, self.subviews.mat_sort:getOptionValue())
|
|
|
|
local prev_filter = self.subviews.search.text
|
|
self.subviews.materials_mats:setChoices(mat_choices)
|
|
self.subviews.materials_mats:setFilter(prev_filter)
|
|
|
|
if #enabled_mat_names > 0 then
|
|
table.sort(enabled_mat_names)
|
|
summary = summary .. (' of %s'):format(table.concat(enabled_mat_names, ', '))
|
|
end
|
|
|
|
self.summary = summary
|
|
self.dirty = false
|
|
self:updateLayout()
|
|
end
|
|
|
|
function QualityAndMaterialsPage:toggle_category(_, choice)
|
|
local cats = {}
|
|
if not choice.icon then
|
|
-- toggling from unset to something is set
|
|
table.insert(cats, choice.cat)
|
|
else
|
|
choice.enabled = not choice.enabled
|
|
for _,c in ipairs(self.subviews.materials_categories:getChoices()) do
|
|
if c.enabled then
|
|
table.insert(cats, c.cat)
|
|
end
|
|
end
|
|
end
|
|
require('plugins.buildingplan').setMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, cats)
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:toggle_material(_, choice)
|
|
local mats = {}
|
|
if not choice.icon then
|
|
-- toggling from unset to something is set
|
|
table.insert(mats, choice.name)
|
|
else
|
|
for _,c in ipairs(self.subviews.materials_mats:getChoices()) do
|
|
local enabled = c.enabled
|
|
if choice.name == c.name then
|
|
enabled = not c.enabled
|
|
end
|
|
if enabled then
|
|
table.insert(mats, c.name)
|
|
end
|
|
end
|
|
end
|
|
require('plugins.buildingplan').setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats)
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:invert_materials()
|
|
local mats = {}
|
|
for _,c in ipairs(self.subviews.materials_mats:getChoices()) do
|
|
if not c.icon then return end
|
|
if not c.enabled then
|
|
table.insert(mats, c.name)
|
|
end
|
|
end
|
|
require('plugins.buildingplan').setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats)
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:clear_filter()
|
|
require('plugins.buildingplan').clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1)
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:set_decorated(decorated)
|
|
local subviews = self.subviews
|
|
require('plugins.buildingplan').setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1,
|
|
decorated and 1 or 0, subviews.min_quality:getOptionValue(), subviews.max_quality:getOptionValue())
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:set_min_quality(idx)
|
|
idx = math.min(6, math.max(0, idx-1))
|
|
local subviews = self.subviews
|
|
subviews.min_quality:setOption(idx)
|
|
if subviews.max_quality:getOptionValue() < idx then
|
|
subviews.max_quality:setOption(idx)
|
|
end
|
|
require('plugins.buildingplan').setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1,
|
|
subviews.decorated:getOptionValue() and 1 or 0, idx, subviews.max_quality:getOptionValue())
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:set_max_quality(idx)
|
|
idx = math.min(6, math.max(0, idx-1))
|
|
local subviews = self.subviews
|
|
subviews.max_quality:setOption(idx)
|
|
if subviews.min_quality:getOptionValue() > idx then
|
|
subviews.min_quality:setOption(idx)
|
|
end
|
|
require('plugins.buildingplan').setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1,
|
|
subviews.decorated:getOptionValue() and 1 or 0, subviews.min_quality:getOptionValue(), idx)
|
|
self.dirty = true
|
|
end
|
|
|
|
function QualityAndMaterialsPage:draw_divider(dc)
|
|
local y2 = dc.height - 1
|
|
for y=0,y2 do
|
|
dc:seek(0, y)
|
|
if y == 0 then
|
|
dc:char(nil, pens.VERT_TOP_PEN)
|
|
elseif y == y2 then
|
|
dc:char(nil, pens.VERT_BOT_PEN)
|
|
else
|
|
dc:char(nil, pens.VERT_MID_PEN)
|
|
end
|
|
end
|
|
end
|
|
|
|
function QualityAndMaterialsPage:onRenderFrame(dc, rect)
|
|
QualityAndMaterialsPage.super.onRenderFrame(self, dc, rect)
|
|
if self.dirty then
|
|
self:refresh()
|
|
end
|
|
end
|
|
|
|
--------------------------------
|
|
-- GlobalSettingsPage
|
|
--
|
|
|
|
GlobalSettingsPage = defclass(GlobalSettingsPage, widgets.ResizingPanel)
|
|
GlobalSettingsPage.ATTRS{
|
|
autoarrange_subviews=true,
|
|
frame={t=0, l=0},
|
|
frame_style=gui.INTERIOR_FRAME,
|
|
}
|
|
|
|
function GlobalSettingsPage:init()
|
|
self:addviews{
|
|
widgets.WrappedLabel{
|
|
frame={l=0},
|
|
text_to_wrap='These options will affect the selection of "Generic Materials" for all future buildings.',
|
|
},
|
|
widgets.Panel{
|
|
frame={h=1},
|
|
},
|
|
widgets.ToggleHotkeyLabel{
|
|
view_id='blocks',
|
|
frame={l=0},
|
|
key='CUSTOM_B',
|
|
label='Blocks',
|
|
label_width=8,
|
|
on_change=self:callback('update_setting', 'blocks'),
|
|
},
|
|
widgets.ToggleHotkeyLabel{
|
|
view_id='logs',
|
|
frame={l=0},
|
|
key='CUSTOM_L',
|
|
label='Logs',
|
|
label_width=8,
|
|
on_change=self:callback('update_setting', 'logs'),
|
|
},
|
|
widgets.ToggleHotkeyLabel{
|
|
view_id='boulders',
|
|
frame={l=0},
|
|
key='CUSTOM_O',
|
|
label='Boulders',
|
|
label_width=8,
|
|
on_change=self:callback('update_setting', 'boulders'),
|
|
},
|
|
widgets.ToggleHotkeyLabel{
|
|
view_id='bars',
|
|
frame={l=0},
|
|
key='CUSTOM_R',
|
|
label='Bars',
|
|
label_width=8,
|
|
on_change=self:callback('update_setting', 'bars'),
|
|
},
|
|
}
|
|
|
|
self:init_settings()
|
|
end
|
|
|
|
function GlobalSettingsPage:init_settings()
|
|
local settings = require('plugins.buildingplan').getGlobalSettings()
|
|
local subviews = self.subviews
|
|
subviews.blocks:setOption(settings.blocks)
|
|
subviews.logs:setOption(settings.logs)
|
|
subviews.boulders:setOption(settings.boulders)
|
|
subviews.bars:setOption(settings.bars)
|
|
end
|
|
|
|
function GlobalSettingsPage:update_setting(setting, val)
|
|
dfhack.run_command('buildingplan', 'set', setting, tostring(val))
|
|
self:init_settings()
|
|
end
|
|
|
|
--------------------------------
|
|
-- FilterSelection
|
|
--
|
|
|
|
FilterSelection = defclass(FilterSelection, widgets.Window)
|
|
FilterSelection.ATTRS{
|
|
frame_title='Choose filters',
|
|
frame={w=55, h=53, l=30, t=8},
|
|
frame_inset={t=1},
|
|
resizable=true,
|
|
index=DEFAULT_NIL,
|
|
desc=DEFAULT_NIL,
|
|
autoarrange_subviews=true,
|
|
}
|
|
|
|
function FilterSelection:init()
|
|
self:addviews{
|
|
widgets.TabBar{
|
|
frame={t=0},
|
|
labels={
|
|
'Quality and materials',
|
|
'Global settings',
|
|
},
|
|
on_select=function(idx)
|
|
self.subviews.pages:setSelected(idx)
|
|
self:updateLayout()
|
|
end,
|
|
get_cur_page=function() return self.subviews.pages:getSelected() end,
|
|
key='CUSTOM_CTRL_T',
|
|
},
|
|
widgets.Widget{
|
|
frame={h=1},
|
|
},
|
|
widgets.Pages{
|
|
view_id='pages',
|
|
frame={t=5, l=0, b=0, r=0},
|
|
subviews={
|
|
QualityAndMaterialsPage{
|
|
index=self.index,
|
|
desc=self.desc
|
|
},
|
|
GlobalSettingsPage{},
|
|
},
|
|
},
|
|
}
|
|
end
|
|
|
|
FilterSelectionScreen = defclass(FilterSelectionScreen, gui.ZScreen)
|
|
FilterSelectionScreen.ATTRS {
|
|
focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/filterselection',
|
|
pass_movement_keys=true,
|
|
pass_mouse_clicks=false,
|
|
defocusable=false,
|
|
index=DEFAULT_NIL,
|
|
desc=DEFAULT_NIL,
|
|
}
|
|
|
|
function FilterSelectionScreen:init()
|
|
self:addviews{
|
|
FilterSelection{
|
|
index=self.index,
|
|
desc=self.desc
|
|
}
|
|
}
|
|
end
|
|
|
|
function FilterSelectionScreen:onShow()
|
|
-- don't let the building "shadow" follow the mouse cursor while this screen is open
|
|
df.global.game.main_interface.bottom_mode_selected = -1
|
|
end
|
|
|
|
function FilterSelectionScreen:onDismiss()
|
|
-- re-enable building shadow
|
|
df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT
|
|
end
|
|
|
|
return _ENV
|