Merge pull request #3166 from TaxiService/bplan_planneroverlay

buildingplan: planneroverlay ui rework
develop
Myk 2023-04-07 02:19:40 -07:00 committed by GitHub
commit a91bc23dfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 84 deletions

@ -44,6 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Misc Improvements ## Misc Improvements
- `buildingplan`: items in the item selection dialog should now use the same item quality symbols as the base game - `buildingplan`: items in the item selection dialog should now use the same item quality symbols as the base game
-@ `buildingplan`: rearranged elements of ``planneroverlay`` interface
-@ `buildingplan`: rearranged elements of ``itemselection`` interface -@ `buildingplan`: rearranged elements of ``itemselection`` interface
- Mods: scripts in mods that are only in the steam workshop directory are now accessible. this means that a script-only mod that you never mark as "active" when generating a world will still receive automatic updates and be usable from in-game - Mods: scripts in mods that are only in the steam workshop directory are now accessible. this means that a script-only mod that you never mark as "active" when generating a world will still receive automatic updates and be usable from in-game
- Mods: scripts from only the most recent version of an installed mod are added to the script path - Mods: scripts from only the most recent version of an installed mod are added to the script path

@ -742,6 +742,8 @@ static int countAvailableItems(color_ostream &out, df::building_type type, int16
static bool hasFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index) { static bool hasFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index) {
TRACE(status,out).print("entering hasFilter\n"); TRACE(status,out).print("entering hasFilter\n");
if (!Core::getInstance().isWorldLoaded())
return false;
BuildingTypeKey key(type, subtype, custom); BuildingTypeKey key(type, subtype, custom);
auto &filters = get_item_filters(out, key); auto &filters = get_item_filters(out, key);
if (index < 0 || filters.getItemFilters().size() <= (size_t)index) if (index < 0 || filters.getItemFilters().size() <= (size_t)index)

@ -2,9 +2,10 @@ local _ENV = mkmodule('plugins.buildingplan.pens')
GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil
VERT_TOP_PEN, VERT_MID_PEN, VERT_BOT_PEN = nil, nil, nil VERT_TOP_PEN, VERT_MID_PEN, VERT_BOT_PEN = nil, nil, nil
HORI_LEFT_PEN, HORI_MID_PEN, HORI_RIGHT_PEN = nil, nil, nil
BUTTON_START_PEN, BUTTON_END_PEN = nil, nil BUTTON_START_PEN, BUTTON_END_PEN = nil, nil
SELECTED_ITEM_PEN = nil SELECTED_ITEM_PEN = nil
MINIMIZED_LEFT_PEN, MINIMIZED_RIGHT_PEN = nil, nil MINI_TEXT_PEN, MINI_TEXT_HPEN, MINI_BUTT_PEN, MINI_BUTT_HPEN = nil, nil, nil, nil
local to_pen = dfhack.pen.parse local to_pen = dfhack.pen.parse
@ -22,14 +23,20 @@ function reload_pens()
VERT_MID_PEN = to_pen{tile=tp(tb_texpos, 4), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK} VERT_MID_PEN = to_pen{tile=tp(tb_texpos, 4), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK}
VERT_BOT_PEN = to_pen{tile=tp(tb_texpos, 11), ch=193, fg=COLOR_GREY, bg=COLOR_BLACK} VERT_BOT_PEN = to_pen{tile=tp(tb_texpos, 11), ch=193, fg=COLOR_GREY, bg=COLOR_BLACK}
local mb_texpos = dfhack.textures.getMediumBordersTexposStart()
HORI_LEFT_PEN = to_pen{tile=tp(mb_texpos, 12), ch=195, fg=COLOR_GREY, bg=COLOR_BLACK}
HORI_MID_PEN = to_pen{tile=tp(mb_texpos, 5), ch=196, fg=COLOR_GREY, bg=COLOR_BLACK}
HORI_RIGHT_PEN = to_pen{tile=tp(mb_texpos, 13), ch=180, fg=COLOR_GREY, bg=COLOR_BLACK}
local cp_texpos = dfhack.textures.getControlPanelTexposStart() local cp_texpos = dfhack.textures.getControlPanelTexposStart()
BUTTON_START_PEN = to_pen{tile=tp(cp_texpos, 13), ch='[', fg=COLOR_YELLOW} BUTTON_START_PEN = to_pen{tile=tp(cp_texpos, 13), ch='[', fg=COLOR_YELLOW}
BUTTON_END_PEN = to_pen{tile=tp(cp_texpos, 15), ch=']', fg=COLOR_YELLOW} BUTTON_END_PEN = to_pen{tile=tp(cp_texpos, 15), ch=']', fg=COLOR_YELLOW}
SELECTED_ITEM_PEN = to_pen{tile=tp(cp_texpos, 9), ch=string.char(251), fg=COLOR_YELLOW} SELECTED_ITEM_PEN = to_pen{tile=tp(cp_texpos, 9), ch=string.char(251), fg=COLOR_YELLOW}
local wb_texpos = dfhack.textures.getWindowBordersTexposStart() MINI_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREY}
MINIMIZED_LEFT_PEN = to_pen{tile=tp(wb_texpos, 0), ch=199, fg=COLOR_WHITE} MINI_TEXT_HPEN = to_pen{fg=COLOR_BLACK, bg=COLOR_WHITE}
MINIMIZED_RIGHT_PEN = to_pen{tile=tp(wb_texpos, 2), ch=182, fg=COLOR_WHITE} MINI_BUTT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_LIGHTRED}
MINI_BUTT_HPEN = to_pen{fg=COLOR_WHITE, bg=COLOR_RED}
end end
reload_pens() reload_pens()

@ -108,6 +108,7 @@ end
-- adjusted from CycleHotkeyLabel on the planner panel -- adjusted from CycleHotkeyLabel on the planner panel
local weapon_quantity = 1 local weapon_quantity = 1
-- TODO: this should account for erroring constructions
local function get_quantity(filter, hollow, bounds) local function get_quantity(filter, hollow, bounds)
if is_pressure_plate() then if is_pressure_plate() then
local flags = uibs.plate_info.flags local flags = uibs.plate_info.flags
@ -207,40 +208,47 @@ ItemLine.ATTRS{
} }
function ItemLine:init() function ItemLine:init()
self.frame.h = 1 self.frame.h = 2
self.visible = function() return #get_cur_filters() >= self.idx end self.visible = function() return #get_cur_filters() >= self.idx end
self:addviews{ self:addviews{
widgets.Label{ widgets.Label{
view_id='item_symbol',
frame={t=0, l=0}, frame={t=0, l=0},
text='*', text=string.char(16), -- this is the "►" character
text_pen=COLOR_YELLOW,
auto_width=true, auto_width=true,
visible=self.is_selected_fn, visible=self.is_selected_fn,
}, },
widgets.Label{ widgets.Label{
frame={t=0, l=25}, view_id='item_desc',
frame={t=0, l=2},
text={ text={
{tile=pens.BUTTON_START_PEN}, {text=self:callback('get_item_line_text'),
{gap=6, tile=pens.BUTTON_END_PEN}, pen=function() return gui.invert_color(COLOR_WHITE, self.is_selected_fn()) end},
}, },
auto_width=true,
on_click=function() self.on_filter(self.idx) end,
}, },
widgets.Label{ widgets.Label{
frame={t=0, l=33}, view_id='item_filter',
frame={t=0, l=28},
text={ text={
{tile=pens.BUTTON_START_PEN}, {text=self:callback('get_filter_text'),
{gap=1, tile=pens.BUTTON_END_PEN}, pen=function() return gui.invert_color(COLOR_LIGHTCYAN, self.is_selected_fn()) end},
}, },
auto_width=true, auto_width=true,
on_click=function() self.on_filter(self.idx) end,
},
widgets.Label{
frame={t=0, l=42},
text='[clear]',
text_pen=COLOR_LIGHTRED,
auto_width=true,
visible=self:callback('has_filter'),
on_click=function() self.on_clear_filter(self.idx) end, on_click=function() self.on_clear_filter(self.idx) end,
}, },
widgets.Label{ widgets.Label{
frame={t=0, l=2}, frame={t=1, l=2},
text={ text={
{width=21, text=self:callback('get_item_line_text')}, {gap=2, text=function() return self.note end,
{gap=3, text='filter', pen=COLOR_GREEN},
{gap=2, text='x', pen=self:callback('get_x_pen')},
{gap=3, text=function() return self.note end,
pen=function() return self.note_pen end}, pen=function() return self.note_pen end},
}, },
}, },
@ -259,11 +267,6 @@ function ItemLine:onInput(keys)
return ItemLine.super.onInput(self, keys) return ItemLine.super.onInput(self, keys)
end end
function ItemLine:get_x_pen()
return require('plugins.buildingplan').hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.idx-1) and
COLOR_GREEN or COLOR_GREY
end
function ItemLine:get_item_line_text() function ItemLine:get_item_line_text()
local idx = self.idx local idx = self.idx
local filter = get_cur_filters()[idx] local filter = get_cur_filters()[idx]
@ -276,15 +279,26 @@ function ItemLine:get_item_line_text()
uibs.building_type, uibs.building_subtype, uibs.custom_type, idx - 1) uibs.building_type, uibs.building_subtype, uibs.custom_type, idx - 1)
if self.available >= quantity then if self.available >= quantity then
self.note_pen = COLOR_GREEN self.note_pen = COLOR_GREEN
self.note = 'Available now' self.note = ' Available now'
else else
self.note_pen = COLOR_YELLOW self.note_pen = COLOR_BROWN
self.note = 'Will link later' self.note = ' Will link later'
end end
self.note = string.char(192) .. self.note -- character 192 is "└"
return ('%d %s%s'):format(quantity, self.desc, quantity == 1 and '' or 's') return ('%d %s%s'):format(quantity, self.desc, quantity == 1 and '' or 's')
end end
function ItemLine:has_filter()
return require('plugins.buildingplan').hasFilter(
uibs.building_type, uibs.building_subtype, uibs.custom_type, self.idx-1)
end
function ItemLine:get_filter_text()
-- TODO: make this show the filter's materials instead of "edit filters"
return self:has_filter() and '[edit filters]' or '[any material]'
end
function ItemLine:reduce_quantity(used_quantity) function ItemLine:reduce_quantity(used_quantity)
if not self.available then return end if not self.available then return end
local filter = get_cur_filters()[self.idx] local filter = get_cur_filters()[self.idx]
@ -310,7 +324,7 @@ PlannerOverlay.ATTRS{
default_pos={x=5,y=9}, default_pos={x=5,y=9},
default_enabled=true, default_enabled=true,
viewscreens='dwarfmode/Building/Placement', viewscreens='dwarfmode/Building/Placement',
frame={w=56, h=20}, frame={w=56, h=22},
} }
function PlannerOverlay:init() function PlannerOverlay:init()
@ -319,31 +333,32 @@ function PlannerOverlay:init()
local main_panel = widgets.Panel{ local main_panel = widgets.Panel{
view_id='main', view_id='main',
frame={t=0, l=0, r=0, h=14}, frame={t=1, l=0, r=0, h=14},
frame_style=gui.MEDIUM_FRAME, frame_style=gui.INTERIOR_MEDIUM_FRAME,
frame_background=gui.CLEAR_PEN, frame_background=gui.CLEAR_PEN,
visible=function() return not self.minimized end, visible=function() return not self.minimized end,
} }
local minimized_panel = widgets.Panel{ local minimized_panel = widgets.Panel{
frame={t=0, r=0, w=4, h=1}, frame={t=0, r=1, w=17, h=1},
subviews={ subviews={
widgets.Label{ widgets.Label{
frame={t=0, l=0, w=1, h=1}, frame={t=0, r=0, h=1},
text={{tile=pens.MINIMIZED_LEFT_PEN}}, text={
visible=function() return self.minimized end, {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},
}, },
widgets.Label{ visible=function() return self.minimized end,
frame={t=0, l=1, w=2, h=1},
text=string.char(31)..string.char(30),
text_pen=dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_GREY},
text_hpen=dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_WHITE},
on_click=function() self.minimized = not self.minimized end, on_click=function() self.minimized = not self.minimized end,
}, },
widgets.Label{ widgets.Label{
frame={t=0, r=0, w=1, h=1}, frame={t=0, r=0, h=1},
text={{tile=pens.MINIMIZED_RIGHT_PEN}}, text={
visible=function() return self.minimized end, {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,
}, },
}, },
} }
@ -387,18 +402,18 @@ function PlannerOverlay:init()
on_clear_filter=self:callback('clear_filter')}, on_clear_filter=self:callback('clear_filter')},
widgets.CycleHotkeyLabel{ widgets.CycleHotkeyLabel{
view_id='hollow', view_id='hollow',
frame={t=3, l=4}, frame={b=4, l=1, w=21},
key='CUSTOM_H', key='CUSTOM_H',
label='Hollow area:', label='Hollow area:',
visible=is_construction, visible=is_construction,
options={ options={
{label='No', value=false}, {label='No', value=false},
{label='Yes', value=true}, {label='Yes', value=true, pen=COLOR_GREEN},
}, },
}, },
widgets.CycleHotkeyLabel{ widgets.CycleHotkeyLabel{
view_id='stairs_top_subtype', view_id='stairs_top_subtype',
frame={t=4, l=4}, frame={b=5, l=23, w=30},
key='CUSTOM_R', key='CUSTOM_R',
label='Top Stair Type: ', label='Top Stair Type: ',
visible=is_stairs, visible=is_stairs,
@ -410,7 +425,7 @@ function PlannerOverlay:init()
}, },
widgets.CycleHotkeyLabel { widgets.CycleHotkeyLabel {
view_id='stairs_bottom_subtype', view_id='stairs_bottom_subtype',
frame={t=5, l=4}, frame={b=4, l=23, w=30},
key='CUSTOM_B', key='CUSTOM_B',
label='Bottom Stair Type:', label='Bottom Stair Type:',
visible=is_stairs, visible=is_stairs,
@ -420,19 +435,30 @@ function PlannerOverlay:init()
{label='Up', value=df.construction_type.UpStair}, {label='Up', value=df.construction_type.UpStair},
}, },
}, },
widgets.CycleHotkeyLabel { widgets.CycleHotkeyLabel { -- TODO: this thing also needs a slider
view_id='weapons', view_id='weapons',
frame={t=5, l=4}, frame={b=4, l=1, w=28},
key='CUSTOM_T', key='CUSTOM_T',
key_back='CUSTOM_SHIFT_T', key_back='CUSTOM_SHIFT_T',
label='Num weapons:', label='Number of weapons:',
visible=is_weapon_or_spike_trap, visible=is_weapon_or_spike_trap,
options={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, options={
{label='(1)', value=1, pen=COLOR_YELLOW},
{label='(2)', value=2, pen=COLOR_YELLOW},
{label='(3)', value=3, pen=COLOR_YELLOW},
{label='(4)', value=4, pen=COLOR_YELLOW},
{label='(5)', value=5, pen=COLOR_YELLOW},
{label='(6)', value=6, pen=COLOR_YELLOW},
{label='(7)', value=7, pen=COLOR_YELLOW},
{label='(8)', value=8, pen=COLOR_YELLOW},
{label='(9)', value=9, pen=COLOR_YELLOW},
{label='(10)', value=10, pen=COLOR_YELLOW},
},
on_change=function(val) weapon_quantity = val end, on_change=function(val) weapon_quantity = val end,
}, },
widgets.ToggleHotkeyLabel { widgets.ToggleHotkeyLabel {
view_id='engraved', view_id='engraved',
frame={t=5, l=4}, frame={b=4, l=1, w=22},
key='CUSTOM_T', key='CUSTOM_T',
label='Engraved only:', label='Engraved only:',
visible=is_slab, visible=is_slab,
@ -441,7 +467,8 @@ function PlannerOverlay:init()
end, end,
}, },
widgets.Label{ widgets.Label{
frame={b=3, l=17}, frame={b=2, l=23},
text_pen=COLOR_DARKGREY,
text={ text={
'Selected area: ', 'Selected area: ',
{text=function() {text=function()
@ -457,32 +484,18 @@ function PlannerOverlay:init()
visible=function() return #get_cur_filters() > 0 end, visible=function() return #get_cur_filters() > 0 end,
subviews={ subviews={
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={b=1, l=0}, frame={b=1, l=1, w=22},
key='STRING_A042',
auto_width=true,
enabled=function() return #get_cur_filters() > 1 end,
on_activate=function() self.selected = ((self.selected - 2) % #get_cur_filters()) + 1 end,
},
widgets.HotkeyLabel{
frame={b=1, l=1},
key='STRING_A047',
label='Prev/next item',
auto_width=true,
enabled=function() return #get_cur_filters() > 1 end,
on_activate=function() self.selected = (self.selected % #get_cur_filters()) + 1 end,
},
widgets.HotkeyLabel{
frame={b=1, l=21},
key='CUSTOM_F', key='CUSTOM_F',
label='Set filter', label=function()
auto_width=true, return buildingplan.hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.selected - 1)
and 'Edit filter' or 'Set filter'
end,
on_activate=function() self:set_filter(self.selected) end, on_activate=function() self:set_filter(self.selected) end,
}, },
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={b=1, l=37}, frame={b=0, l=1, w=22},
key='CUSTOM_X', key='CUSTOM_X',
label='Clear filter', label='Clear filter',
auto_width=true,
on_activate=function() self:clear_filter(self.selected) end, on_activate=function() self:clear_filter(self.selected) end,
enabled=function() enabled=function()
return buildingplan.hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.selected - 1) return buildingplan.hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.selected - 1)
@ -490,19 +503,20 @@ function PlannerOverlay:init()
}, },
widgets.CycleHotkeyLabel{ widgets.CycleHotkeyLabel{
view_id='choose', view_id='choose',
frame={b=0, l=0}, frame={b=0, l=23},
key='CUSTOM_I', key='CUSTOM_Z',
label='Item selection:', label='Choose items:',
label_below=true,
options={ options={
{label='Use filters', value=0}, {label='With filters', value=0},
{ {
label=function() label=function()
local automaterial = itemselection.get_automaterial_selection(uibs.building_type) local automaterial = itemselection.get_automaterial_selection(uibs.building_type)
return ('Last choice (%s)'):format(automaterial or 'Will ask') return ('Last used (%s)'):format(automaterial or 'pick manually')
end, end,
value=2, value=2,
}, },
{label='Manual choice', value=1}, {label='Manually', value=1},
}, },
initial_option=0, initial_option=0,
on_change=function(choose) on_change=function(choose)
@ -511,7 +525,7 @@ function PlannerOverlay:init()
}, },
widgets.CycleHotkeyLabel{ widgets.CycleHotkeyLabel{
view_id='safety', view_id='safety',
frame={b=0, l=29, w=25}, frame={b=2, l=23, w=25},
key='CUSTOM_G', key='CUSTOM_G',
label='Building safety:', label='Building safety:',
options={ options={
@ -529,36 +543,94 @@ function PlannerOverlay:init()
}, },
} }
local divider_widget = widgets.Panel{
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,
}
local error_panel = widgets.ResizingPanel{ local error_panel = widgets.ResizingPanel{
view_id='errors', view_id='errors',
frame={t=14, l=0, r=0}, frame={t=15, l=0, r=0},
frame_style=gui.MEDIUM_FRAME, frame_style=gui.BOLD_FRAME,
frame_background=gui.CLEAR_PEN, frame_background=gui.CLEAR_PEN,
visible=function() return not self.minimized end, visible=function() return not self.minimized end,
} }
error_panel:addviews{ error_panel:addviews{
widgets.WrappedLabel{ widgets.WrappedLabel{
frame={t=0, l=0, r=0}, frame={t=0, l=1, r=0},
text_pen=COLOR_LIGHTRED, text_pen=COLOR_LIGHTRED,
text_to_wrap=get_placement_errors, text_to_wrap=get_placement_errors,
visible=function() return #uibs.errors > 0 end, visible=function() return #uibs.errors > 0 end,
}, },
widgets.Label{ widgets.Label{
frame={t=0, l=0, r=0}, frame={t=0, l=1, r=0},
text_pen=COLOR_GREEN, text_pen=COLOR_GREEN,
text='OK to build', text='OK to build',
visible=function() return #uibs.errors == 0 end, visible=function() return #uibs.errors == 0 end,
}, },
} }
local prev_next_selector = widgets.Panel{
frame={h=1},
auto_width=true,
subviews={
widgets.HotkeyLabel{
frame={t=0, l=1, w=9},
key='CUSTOM_SHIFT_Q',
key_sep='\0',
label=': Prev/',
on_activate=function() self.selected = ((self.selected - 2) % #get_cur_filters()) + 1 end,
},
widgets.HotkeyLabel{
frame={t=0, l=2, w=1},
key='CUSTOM_Q',
on_activate=function() self.selected = (self.selected % #get_cur_filters()) + 1 end,
},
widgets.Label{
frame={t=0,l=10},
text='next item',
on_click=function() self.selected = (self.selected % #get_cur_filters()) + 1 end,
},
},
visible=function() return #get_cur_filters() > 1 end,
}
local black_bar = widgets.Panel{
frame={t=0, l=1, w=37, h=1},
frame_inset=0,
frame_background=gui.CLEAR_PEN,
visible=function() return not self.minimized end,
subviews={
prev_next_selector,
},
}
self:addviews{ self:addviews{
main_panel, black_bar,
minimized_panel, minimized_panel,
main_panel,
divider_widget,
error_panel, error_panel,
} }
end end
function PlannerOverlay:draw_divider_h(dc)
local x2 = dc.width -1
for x=0,x2 do
dc:seek(x, 0)
if x == 0 then
dc:char(nil, pens.HORI_LEFT_PEN)
elseif x == x2 then
dc:char(nil, pens.HORI_RIGHT_PEN)
else
dc:char(nil, pens.HORI_MID_PEN)
end
end
end
function PlannerOverlay:reset() function PlannerOverlay:reset()
self.subviews.item1:reset() self.subviews.item1:reset()
self.subviews.item2:reset() self.subviews.item2:reset()