Implement a material selection dialog.

develop
Alexander Gavrilov 2012-10-16 18:33:00 +04:00
parent d336abfd97
commit 023dc82564
5 changed files with 361 additions and 9 deletions

@ -3,16 +3,16 @@
local _ENV = mkmodule('class') local _ENV = mkmodule('class')
-- Metatable template for a class -- Metatable template for a class
class_obj = {} or class_obj class_obj = class_obj or {}
-- Methods shared by all classes -- Methods shared by all classes
common_methods = {} or common_methods common_methods = common_methods or {}
-- Forbidden names for class fields and methods. -- Forbidden names for class fields and methods.
reserved_names = { super = true, ATTRS = true } reserved_names = { super = true, ATTRS = true }
-- Attribute table metatable -- Attribute table metatable
attrs_meta = {} or attrs_meta attrs_meta = attrs_meta or {}
-- Create or updates a class; a class has metamethods and thus own metatable. -- Create or updates a class; a class has metamethods and thus own metatable.
function defclass(class,parent) function defclass(class,parent)
@ -133,6 +133,14 @@ function common_methods:callback(method, ...)
return dfhack.curry(self[method], self, ...) return dfhack.curry(self[method], self, ...)
end end
function common_methods:cb_getfield(field)
return function() return self[field] end
end
function common_methods:cb_setfield(field)
return function(val) self[field] = val end
end
function common_methods:assign(data) function common_methods:assign(data)
for k,v in pairs(data) do for k,v in pairs(data) do
self[k] = v self[k] = v

@ -377,7 +377,7 @@ function View:addviews(list)
end end
for _,dir in ipairs(list) do for _,dir in ipairs(list) do
for id,obj in pairs(dir) do for id,obj in pairs(dir.subviews) do
if id and type(id) ~= 'number' and sv[id] == nil then if id and type(id) ~= 'number' and sv[id] == nil then
sv[id] = obj sv[id] = obj
end end
@ -616,7 +616,7 @@ end
function FramedScreen:computeFrame(parent_rect) function FramedScreen:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height local sw, sh = parent_rect.width, parent_rect.height
local fw, fh = self:getWantedFrameSize() local fw, fh = self:getWantedFrameSize(parent_rect)
return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1)
end end

@ -0,0 +1,311 @@
-- Stock dialog for selecting materials
local _ENV = mkmodule('gui.materials')
local gui = require('gui')
local widgets = require('gui.widgets')
local utils = require('utils')
ARROW = string.char(26)
CREATURE_BASE = 19
PLANT_BASE = 419
MaterialDialog = defclass(MaterialDialog, gui.FramedScreen)
MaterialDialog.focus_path = 'MaterialDialog'
MaterialDialog.ATTRS{
prompt = 'Type or select a material from this list',
frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
frame_title = 'Select Material',
-- new attrs
on_select = DEFAULT_NIL,
on_cancel = DEFAULT_NIL,
on_close = DEFAULT_NIL,
}
function MaterialDialog:init(info)
self:addviews{
widgets.Label{
text = {
self.prompt, '\n\n',
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
},
text_pen = COLOR_WHITE,
frame = { l = 0, t = 0 },
},
widgets.Label{
view_id = 'back',
visible = false,
text = { { key = 'LEAVESCREEN', text = ': Back' } },
frame = { r = 0, b = 0 },
auto_width = true,
},
widgets.List{
view_id = 'list',
frame = { l = 0, r = 0, t = 6, b = 2 },
on_submit = self:callback('onSubmitItem'),
},
widgets.EditField{
view_id = 'edit',
frame = { l = 2, t = 4 },
on_change = self:callback('onFilterChoices'),
on_char = self:callback('onFilterChar'),
},
widgets.Label{
view_id = 'not_found',
text = 'No matching materials.',
text_pen = COLOR_LIGHTRED,
frame = { l = 2, r = 0, t = 6 },
},
widgets.Label{
text = { {
key = 'SELECT', text = ': Select',
disabled = function() return self.subviews.not_found.visible end
} },
frame = { l = 0, b = 0 },
}
}
self:initBuiltinMode()
end
function MaterialDialog:getWantedFrameSize(rect)
return math.max(40, #self.prompt), math.min(28, rect.height-8)
end
function MaterialDialog:onDestroy()
if self.on_close then
self.on_close()
end
end
function MaterialDialog:initBuiltinMode()
local choices = {
{ text = ' none', mat_type = -1, mat_index = -1 },
{ text = ARROW..' inorganic', key = 'CUSTOM_SHIFT_I',
cb = self:callback('initInorganicMode') },
{ text = ARROW..' creature', key = 'CUSTOM_SHIFT_C',
cb = self:callback('initCreatureMode')},
{ text = ARROW..' plant', key = 'CUSTOM_SHIFT_P',
cb = self:callback('initPlantMode') },
}
self:addMaterials(
choices,
df.global.world.raws.mat_table.builtin, 0, -1,
df.builtin_mats._last_item
)
self:pushContext('Any material', choices)
end
function MaterialDialog:initInorganicMode()
local choices = {}
self:addMaterials(
choices,
df.global.world.raws.inorganics, 0, nil,
nil, 'material'
)
self:pushContext('Inorganic materials', choices)
end
function MaterialDialog:initCreatureMode()
local choices = {}
for i,v in ipairs(df.global.world.raws.creatures.all) do
self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i)
end
self:pushContext('Creature materials', choices)
end
function MaterialDialog:initPlantMode()
local choices = {}
for i,v in ipairs(df.global.world.raws.plants.all) do
self:addObjectChoice(choices, v, v.name, PLANT_BASE, i)
end
self:pushContext('Plant materials', choices)
end
function MaterialDialog:addObjectChoice(choices, obj, name, typ, index)
if #obj.material == 1 then
self:addMaterial(choices, obj.material[0], typ, index, true)
else
table.insert(choices, {
text = ARROW..' '..name, mat_type = typ, mat_index = index,
ctx = name, obj = obj, cb = self:callback('onSelectObj')
})
end
end
function MaterialDialog:onSelectObj(item)
local choices = {}
self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index)
self:pushContext(item.ctx, choices)
end
function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field)
for i=0,(maxid or #vector-1) do
local mat = vector[i]
if mat and field then
mat = mat[field]
end
if mat then
local typ, idx
if index then
typ, idx = tid+i, index
else
typ, idx = tid, i
end
self:addMaterial(choices, mat, typ, idx)
end
end
end
function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix)
local state = 0
if mat.heat.melting_point <= 10015 then
state = 1
end
local name = mat.state_name[state]
name = string.gsub(name, '^frozen ','')
name = string.gsub(name, '^molten ','')
name = string.gsub(name, '^condensed ','')
local key
if pfix and mat.prefix ~= '' then
name = mat.prefix .. ' ' .. name
key = mat.prefix
end
table.insert(choices, {
text = ' '..name,
search_key = key,
material = mat,
mat_type = typ, mat_index = idx
})
end
function MaterialDialog:pushContext(name, choices)
if not self.back_stack then
self.back_stack = {}
self.subviews.back.visible = false
else
table.insert(self.back_stack, {
context_str = self.context_str,
all_choices = self.all_choices,
edit_text = self.subviews.edit.text,
choices = self.choices,
selected = self.subviews.list.selected,
})
self.subviews.back.visible = true
end
self.context_str = name
self.all_choices = choices
self.subviews.edit.text = ''
self:setChoices(choices, 1)
end
function MaterialDialog:onGoBack()
local save = table.remove(self.back_stack)
self.subviews.back.visible = (#self.back_stack > 0)
self.context_str = save.context_str
self.all_choices = save.all_choices
self.subviews.edit.text = save.edit_text
self:setChoices(save.choices, save.selected)
end
function MaterialDialog:setChoices(choices, pos)
self.choices = choices
self.subviews.list:setChoices(self.choices, pos)
self.subviews.not_found.visible = (#self.choices == 0)
end
function MaterialDialog:onFilterChoices(text)
local tokens = utils.split_string(text, ' ')
local choices = {}
for i,v in ipairs(self.all_choices) do
local ok = true
local search_key = v.search_key or v.text
for _,key in ipairs(tokens) do
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
ok = false
break
end
end
if ok then
table.insert(choices, v)
end
end
self:setChoices(choices)
end
local bad_chars = {
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
['['] = true, [']'] = true, ['('] = true, [')'] = true,
}
function MaterialDialog:onFilterChar(char, text)
if bad_chars[char] then
return false
end
if char == ' ' then
if #self.choices == 1 then
self.subviews.list:submit()
return false
end
return string.match(text, '%S$')
end
return true
end
function MaterialDialog:submitMaterial(typ, index)
self:dismiss()
if self.on_select then
local info = dfhack.matinfo.decode(typ, index)
self.on_select(info, typ, index)
end
end
function MaterialDialog:onSubmitItem(idx, item)
if item.cb then
item:cb(idx)
else
self:submitMaterial(item.mat_type, item.mat_index)
end
end
function MaterialDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
self:onGoBack()
else
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
end
end
function showMaterialPrompt(title, prompt, on_select, on_cancel)
MaterialDialog{
frame_title = title,
prompt = prompt,
on_select = on_select,
on_cancel = on_cancel,
}:show()
end
return _ENV

@ -148,4 +148,16 @@ function showListPrompt(title, text, tcolor, choices, min_width)
return wait() return wait()
end end
function showMaterialPrompt(title, prompt)
require('gui.materials').MaterialDialog{
frame_title = title,
prompt = prompt,
on_select = mkresume(true,
on_cancel = mkresume(false),
on_close = qresume(nil)
}:show()
return wait()
end
return _ENV return _ENV

@ -242,6 +242,8 @@ function render_text(obj,dc,x0,y0,pen,dpen)
local keystr = gui.getKeyDisplay(token.key) local keystr = gui.getKeyDisplay(token.key)
local sep = token.key_sep or '' local sep = token.key_sep or ''
x = x + #keystr
if sep == '()' then if sep == '()' then
if dc then if dc then
dc:string(text) dc:string(text)
@ -285,6 +287,7 @@ Label.ATTRS{
text_pen = COLOR_WHITE, text_pen = COLOR_WHITE,
text_dpen = COLOR_DARKGREY, text_dpen = COLOR_DARKGREY,
auto_height = true, auto_height = true,
auto_width = false,
} }
function Label:init(args) function Label:init(args)
@ -301,6 +304,13 @@ function Label:setText(text)
end end
end end
function Label:preUpdateLayout()
if self.auto_width then
self.frame = self.frame or {}
self.frame.w = self:getTextWidth()
end
end
function Label:itemById(id) function Label:itemById(id)
if self.text_ids then if self.text_ids then
return self.text_ids[id] return self.text_ids[id]
@ -404,6 +414,13 @@ end
function List:moveCursor(delta, force_cb) function List:moveCursor(delta, force_cb)
local page = math.max(1, self.page_size) local page = math.max(1, self.page_size)
local cnt = #self.choices local cnt = #self.choices
if cnt < 1 then
self.page_top = 1
self.selected = 1
return
end
local off = self.selected+delta-1 local off = self.selected+delta-1
local ds = math.abs(delta) local ds = math.abs(delta)
@ -458,9 +475,15 @@ function List:onRenderBody(dc)
end end
end end
function List:submit()
if self.on_submit and #self.choices > 0 then
self.on_submit(self:getSelected())
end
end
function List:onInput(keys) function List:onInput(keys)
if self.on_submit and keys.SELECT then if self.on_submit and keys.SELECT then
self.on_submit(self:getSelected()) self:submit()
return true return true
else else
for k,v in pairs(self.scroll_keys) do for k,v in pairs(self.scroll_keys) do
@ -479,9 +502,7 @@ function List:onInput(keys)
for i,v in ipairs(self.choices) do for i,v in ipairs(self.choices) do
if v.key and keys[v.key] then if v.key and keys[v.key] then
self:setSelected(i) self:setSelected(i)
if self.on_submit then self:submit()
self.on_submit(self:getSelected())
end
return true return true
end end
end end