You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bastion/libs/StdUi/widgets/Autocomplete.lua

285 lines
6.5 KiB

--- @type StdUi
local StdUi = LibStub and LibStub('StdUi', true);
if not StdUi then
return
end
local module, version = 'Autocomplete', 4;
if not StdUi:UpgradeNeeded(module, version) then return end;
local TableInsert = tinsert;
StdUi.Util.autocompleteTransformer = function(_, value)
return value;
end
StdUi.Util.autocompleteValidator = function(self)
self.stdUi:MarkAsValid(self, true);
return true;
end
StdUi.Util.autocompleteItemTransformer = function(_, value)
if not value or value == '' then
return value;
end
local itemName = GetItemInfo(value);
return itemName;
end
StdUi.Util.autocompleteItemValidator = function(ac)
local itemName, itemId;
local t = ac:GetText();
local v = ac:GetValue();
if tonumber(t) ~= nil then
-- it's a number
itemName = GetItemInfo(tonumber(t));
if itemName then
itemId = tonumber(t);
end
elseif v then
itemName = GetItemInfo(v);
if itemName == t then
itemId = v;
end
end
if itemId then
ac.value = itemId;
ac:SetText(itemName);
self.stdUi:MarkAsValid(ac, true);
return true;
else
self.stdUi:MarkAsValid(ac, false);
return false;
end
end
local AutocompleteMethods = {
--- Private methods
buttonCreate = function(panel)
local optionButton;
optionButton = StdUi:HighlightButton(panel, panel:GetWidth(), 20, '');
optionButton.highlight = StdUi:HighlightButtonTexture(optionButton);
optionButton.highlight:Hide();
optionButton.text:SetJustifyH('LEFT');
optionButton.autocomplete = panel.autocomplete;
optionButton:SetFrameLevel(panel:GetFrameLevel() + 2);
optionButton:SetScript('OnClick', function(b)
local ac = b.autocomplete;
if b.boundItem then
b.autocomplete.selectedItem = b.boundItem;
end
ac.indexChosen = 0;
ac:SetValue(b.value, b:GetText());
b.autocomplete.dropdown:Hide();
ac:SetFocus();
end);
return optionButton;
end,
buttonUpdate = function(panel, optionButton, data)
optionButton.boundItem = data;
optionButton.value = data.value;
optionButton:SetWidth(panel:GetWidth());
optionButton:SetText(data.text);
end,
filterItems = function(ac, search, itemsToSearch)
local result = {};
for _, item in pairs(itemsToSearch) do
local valueString = tostring(item.value);
if
item.text:lower():find(search:lower(), nil, true) or
valueString:lower():find(search:lower(), nil, true)
then
TableInsert(result, item);
end
if #result >= ac.itemLimit then
break;
end
end
return result;
end,
--- Public methods
SetItems = function(self, newItems)
self.items = newItems;
self:RenderItems();
self.dropdown:Hide();
end,
RenderItems = function(self)
local dropdownHeight = 20 * #self.filteredItems;
self.dropdown:SetHeight(dropdownHeight);
self.stdUi:ObjectList(
self.dropdown,
self.itemTable,
self.buttonCreate,
self.buttonUpdate,
self.filteredItems
);
end,
ToggleHighlightItems = function(self, flag)
for _, button in pairs(self.itemTable) do
if flag then
button.highlight:Show();
else
button.highlight:Hide();
end
end
end,
ToggleHighlight = function(self, index, flag)
if flag then
self.itemTable[index].highlight:Show();
else
self.itemTable[index].highlight:Hide();
end
end,
ValueToText = function(self, value)
return self.transformer(value)
end,
SetValue = function(self, value, t)
self.value = value;
self:SetText(t or self:ValueToText(value) or '');
self:Validate();
self.button:Hide();
end,
Validate = function(self)
self.isValidated = true;
self.isValid = self:validator();
if self.isValid then
if self.OnValueChanged then
self:OnValueChanged(self.value, self:GetText());
end
end
self.isValidated = false;
end,
};
local AutocompleteEvents = {
OnEditFocusLost = function(s)
C_Timer.After(0.1, function() s.dropdown:Hide(); end);
end,
OnEnterPressed = function(ac)
ac.dropdown:Hide();
-- User was using arrows to select item
if ac.indexChosen > 0 and ac.indexChosen <= #ac.filteredItems then
local item = ac.filteredItems[ac.indexChosen];
ac:SetValue(item.value, item.text);
ac.indexChosen = 0;
ac:ToggleHighlightItems(false);
end
ac:Validate();
if ac.CustomEnterPressed then
ac:CustomEnterPressed(ac);
end
end,
OnTextChanged = function(ac, isUserInput)
local plainText = StdUi.Util.stripColors(ac:GetText());
ac.selectedItem = nil;
if isUserInput then
-- reset value if user changed something
ac.value = nil;
if type(ac.items) == 'function' then
-- We ensure to pass whole autocomplete as well
ac.filteredItems = ac:items(plainText);
elseif type(ac.items) == 'table' then
ac.filteredItems = ac:filterItems(plainText, ac.items);
end
if not ac.filteredItems or #ac.filteredItems == 0 then
ac.dropdown:Hide();
else
ac:RenderItems();
ac.dropdown:Show();
end
end
end,
OnKeyUp = function(ac, key)
local arrowKeyPressed = false;
if key == 'UP' then
ac.indexChosen = ac.indexChosen - 1;
arrowKeyPressed = true;
elseif key == 'DOWN' then
ac.indexChosen = ac.indexChosen + 1;
arrowKeyPressed = true;
end
if arrowKeyPressed then
if ac.indexChosen < 1 then
ac.indexChosen = #ac.filteredItems;
end
if ac.indexChosen > #ac.filteredItems then
ac.indexChosen = 1;
end
ac:ToggleHighlightItems(false);
ac:ToggleHighlight(ac.indexChosen, true);
end
end
}
--- Very similar to dropdown except it has the ability to create new records and filters results
--- @return EditBox
function StdUi:Autocomplete(parent, width, height, text, validator, transformer, items)
transformer = transformer or StdUi.Util.autocompleteTransformer;
validator = validator or StdUi.Util.autocompleteValidator;
local autocomplete = self:EditBox(parent, width, height, text, validator);
---@type StdUi
autocomplete.stdUi = self;
autocomplete.transformer = transformer;
autocomplete.items = items;
autocomplete.filteredItems = {};
autocomplete.selectedItem = nil;
autocomplete.itemLimit = 8;
autocomplete.itemTable = {};
autocomplete.indexChosen = 0;
autocomplete.dropdown = self:Panel(parent, width, 20);
autocomplete.dropdown:SetPoint('TOPLEFT', autocomplete, 'BOTTOMLEFT', 0, 0);
autocomplete.dropdown:SetPoint('TOPRIGHT', autocomplete, 'BOTTOMRIGHT', 0, 0);
autocomplete.dropdown:Hide();
autocomplete.dropdown:SetFrameLevel(autocomplete:GetFrameLevel() + 10);
-- keep back reference
autocomplete.dropdown.autocomplete = autocomplete;
for k, v in pairs(AutocompleteMethods) do
autocomplete[k] = v;
end
for k, v in pairs(AutocompleteEvents) do
autocomplete:SetScript(k, v);
end
return autocomplete;
end
StdUi:RegisterModule(module, version);