diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 7d8058a93..cb8d66176 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -149,8 +149,8 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' ListBox.ATTRS{ - selection = 1, - choices = {}, + with_filter = false, + cursor_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL, on_select = DEFAULT_NIL } @@ -160,11 +160,16 @@ function ListBox:preinit(info) end function ListBox:init(info) - local spen = gui.to_pen(COLOR_CYAN, info.select_pen, nil, false) - local cpen = gui.to_pen(COLOR_LIGHTCYAN, info.cursor_pen or info.select_pen, nil, true) + local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false) + local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) + + local list_widget = widgets.List + if self.with_filter then + list_widget = widgets.FilteredList + end self:addviews{ - widgets.List{ + list_widget{ view_id = 'list', selected = info.selected, choices = info.choices, @@ -199,7 +204,7 @@ function ListBox:onInput(keys) end end -function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width) +function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter) ListBox{ frame_title = title, text = text, @@ -208,6 +213,7 @@ function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_ on_select = on_select, on_cancel = on_cancel, frame_width = min_width, + with_filter = filter, }:show() end diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 17fcf2928..e8f1e19ca 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -43,28 +43,17 @@ function MaterialDialog:init(info) frame = { r = 0, b = 0 }, auto_width = true, }, - widgets.List{ + widgets.FilteredList{ view_id = 'list', - frame = { l = 0, r = 0, t = 6, b = 2 }, + not_found_label = 'No matching materials', + frame = { l = 0, r = 0, t = 4, b = 2 }, icon_width = 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 + disabled = function() return not self.subviews.list:canSubmit() end } }, frame = { l = 0, b = 0 }, } @@ -198,18 +187,15 @@ function MaterialDialog:pushContext(name, choices) 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, + all_choices = self.subviews.list:getChoices(), + edit_text = self.subviews.list:getFilter(), + selected = self.subviews.list:getSelected(), }) self.subviews.back.visible = true end self.context_str = name - self.all_choices = choices - self.subviews.edit.text = '' - self:setChoices(choices, 1) + self.subviews.list:setChoices(choices, 1) end function MaterialDialog:onGoBack() @@ -217,55 +203,8 @@ function MaterialDialog:onGoBack() 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 + self.subviews.list:setChoices(save.all_choices) + self.subviews.list:setFilter(save.edit_text, save.selected) end function MaterialDialog:submitMaterial(typ, index) diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua index 06079c426..e15f6c1b9 100644 --- a/library/lua/gui/script.lua +++ b/library/lua/gui/script.lua @@ -133,13 +133,14 @@ function showInputPrompt(title, text, tcolor, input, min_width) return wait() end -function showListPrompt(title, text, tcolor, choices, min_width) +function showListPrompt(title, text, tcolor, choices, min_width, filter) dlg.ListBox{ frame_title = title, text = text, text_pen = tcolor, choices = choices, frame_width = min_width, + with_filter = filter, on_select = mkresume(true), on_cancel = mkresume(false), on_close = qresume(nil) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 16568aa16..0a6d99ba6 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -22,6 +22,14 @@ local function getval(obj) end end +local function map_opttab(tab,idx) + if tab then + return tab[idx] + else + return idx + end +end + ------------ -- Widget -- ------------ @@ -393,6 +401,10 @@ function List:setSelected(selected) return self.selected end +function List:getChoices() + return self.choices +end + function List:getSelected() return self.selected, self.choices[self.selected] end @@ -533,4 +545,141 @@ function List:onInput(keys) end end +------------------- +-- Filtered List -- +------------------- + +FilteredList = defclass(FilteredList, Widget) + +function FilteredList:init(info) + self.edit = EditField{ + text_pen = info.cursor_pen, + frame = { l = info.icon_width, t = 0 }, + on_change = self:callback('onFilterChange'), + on_char = self:callback('onFilterChar'), + } + self.list = List{ + frame = { t = 2 }, + text_pen = info.text_pen, + cursor_pen = info.cursor_pen, + inactive_pen = info.inactive_pen, + row_height = info.row_height, + scroll_keys = info.scroll_keys, + icon_width = info.icon_width, + } + if info.on_select then + self.list.on_select = function() + return info.on_select(self:getSelected()) + end + end + if info.on_submit then + self.list.on_submit = function() + return info.on_submit(self:getSelected()) + end + end + self.not_found = Label{ + visible = false, + text = info.not_found_label or 'No matches', + text_pen = COLOR_LIGHTRED, + frame = { l = info.icon_width, t = 2 }, + } + self:addviews{ self.list, self.edit, self.not_found } + self:setChoices(info.choices, info.selected) +end + +function FilteredList:getChoices() + return self.choices +end + +function FilteredList:setChoices(choices, pos) + choices = choices or {} + self.choices = choices + self.edit.text = '' + self.list:setChoices(choices, pos) + self.not_found.visible = (#choices == 0) +end + +function FilteredList:submit() + return self.list:submit() +end + +function FilteredList:canSubmit() + return not self.not_found.visible +end + +function FilteredList:getSelected() + local i,v = self.list:getSelected() + return map_opttab(self.choice_index, i), v +end + +function FilteredList:getContentWidth() + return self.list:getContentWidth() +end + +function FilteredList:getContentHeight() + return self.list:getContentHeight() + 2 +end + +function FilteredList:getFilter() + return self.edit.text, self.list.choices +end + +function FilteredList:setFilter(filter, pos) + local choices = self.choices + local cidx = nil + + filter = filter or '' + self.edit.text = filter + + if filter ~= '' then + local tokens = utils.split_string(filter, ' ') + local ipos = pos + + choices = {} + cidx = {} + pos = nil + + for i,v in ipairs(self.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) + cidx[#choices] = i + if ipos == i then + pos = #choices + end + end + end + end + + self.choice_index = cidx + self.list:setChoices(choices, pos) + self.not_found.visible = (#choices == 0) +end + +function FilteredList:onFilterChange(text) + self:setFilter(text) +end + +local bad_chars = { + ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, + ['['] = true, [']'] = true, ['('] = true, [')'] = true, +} + +function FilteredList:onFilterChar(char, text) + if bad_chars[char] then + return false + end + if char == ' ' then + return string.match(text, '%S$') + end + return true +end + return _ENV