Fixed ComboBox (updated for to the rework dropdown version)

Fixed Dropdown rendering 1 Item more than it shoud
Added dropdown:prioritize() when focusing the dropdown
This commit is contained in:
Robert Jelic
2025-11-02 10:55:49 +01:00
parent f14928859c
commit 482dc266bc
2 changed files with 109 additions and 71 deletions

View File

@@ -1,4 +1,5 @@
local VisualElement = require("elements/VisualElement")
local List = require("elements/List")
local DropDown = require("elements/DropDown")
local tHex = require("libraries/colorHex")
@@ -18,7 +19,7 @@ local tHex = require("libraries/colorHex")
--- {text = "Spain"},
--- {text = "Italy"}
--- })
--- :setPlaceholder("Select country...")
--- :setSelectedText("Select country...") -- Placeholder text
--- :setAutoComplete(true) -- Enable filtering while typing
---
--- -- Handle selection changes
@@ -34,18 +35,15 @@ ComboBox.__index = ComboBox
---@property editable boolean true Enables direct text input in the field
ComboBox.defineProperty(ComboBox, "editable", {default = true, type = "boolean", canTriggerRender = true})
---@property text string "" The current text value of the input field
ComboBox.defineProperty(ComboBox, "text", {default = "", type = "string", canTriggerRender = true, seetter = function(self, value)
self.set("cursorPos", #self.get("text") + 1)
ComboBox.defineProperty(ComboBox, "text", {default = "", type = "string", canTriggerRender = true, setter = function(self, value)
self.set("cursorPos", #value + 1)
self:updateViewport()
return value
end})
---@property cursorPos number 1 Current cursor position in the text input
ComboBox.defineProperty(ComboBox, "cursorPos", {default = 1, type = "number"})
---@property viewOffset number 0 Horizontal scroll position for viewing long text
ComboBox.defineProperty(ComboBox, "viewOffset", {default = 0, type = "number", canTriggerRender = true})
---@property placeholder string "..." Text shown when the input is empty
ComboBox.defineProperty(ComboBox, "placeholder", {default = "...", type = "string"})
---@property placeholderColor color gray Color used for placeholder text
ComboBox.defineProperty(ComboBox, "placeholderColor", {default = colors.gray, type = "color"})
---@property autoComplete boolean false Enables filtering dropdown items while typing
ComboBox.defineProperty(ComboBox, "autoComplete", {default = false, type = "boolean"})
---@property manuallyOpened boolean false Indicates if dropdown was opened by user action
@@ -247,22 +245,17 @@ function ComboBox:mouse_click(button, x, y)
if relY == 1 then
if relX >= width - #dropSymbol + 1 and relX <= width then
if isOpen then
self:unsetState("opened")
self.set("height", 1)
self.set("manuallyOpened", false)
else
self:setState("opened")
end
if not isOpen then
local allItems = self.get("items") or {}
local dropdownHeight = self.get("dropdownHeight") or 5
local actualHeight = math.min(dropdownHeight, #allItems)
self.set("height", 1 + actualHeight)
self.set("manuallyOpened", true)
else
self.set("height", 1)
self.set("manuallyOpened", false)
end
self:updateRender()
return true
@@ -275,68 +268,112 @@ function ComboBox:mouse_click(button, x, y)
local targetPos = math.min(maxPos, viewOffset + relX)
self.set("cursorPos", targetPos)
if not isOpen then
self:setState("opened")
local allItems = self.get("items") or {}
local dropdownHeight = self.get("dropdownHeight") or 5
local actualHeight = math.min(dropdownHeight, #allItems)
self.set("height", 1 + actualHeight)
self.set("manuallyOpened", true)
end
self:updateRender()
return true
end
return true
elseif isOpen and relY > 1 and self.get("selectable") then
local itemIndex = (relY - 1) + self.get("offset")
local items = self.get("items")
if itemIndex <= #items then
local item = items[itemIndex]
if type(item) == "string" then
item = {text = item}
items[itemIndex] = item
end
if not self.get("multiSelection") then
for _, otherItem in ipairs(items) do
if type(otherItem) == "table" then
otherItem.selected = false
end
end
end
item.selected = true
if item.text then
self:setText(item.text)
end
self:unsetState("opened")
self.set("height", 1)
self:updateRender()
return true
end
elseif isOpen and relY > 1 then
return DropDown.mouse_click(self, button, x, y)
end
return false
end
--- Handles mouse up events for item selection
--- @shortDescription Handles mouse up for selection
--- @param button number The mouse button that was released
--- @param x number The x-coordinate of the release
--- @param y number The y-coordinate of the release
--- @return boolean handled Whether the event was handled
--- @protected
function ComboBox:mouse_up(button, x, y)
if self:hasState("opened") then
local relX, relY = self:getRelativePosition(x, y)
if relY > 1 and self.get("selectable") and not self._scrollBarDragging then
local itemIndex = (relY - 1) + self.get("offset")
local items
if self.get("autoComplete") and not self.get("manuallyOpened") then
items = self:getFilteredItems()
else
items = self.get("items")
end
if itemIndex <= #items then
local item = items[itemIndex]
if type(item) == "string" then
item = {text = item}
items[itemIndex] = item
end
if not self.get("multiSelection") then
for _, otherItem in ipairs(self.get("items")) do
if type(otherItem) == "table" then
otherItem.selected = false
end
end
end
item.selected = true
if item.text then
self.set("text", item.text)
self.set("cursorPos", #item.text + 1)
self:updateViewport()
end
if item.callback then
item.callback(self)
end
self:fireEvent("select", itemIndex, item)
self:unsetState("opened")
self:unsetState("clicked")
self.set("height", 1)
self.set("manuallyOpened", false)
self:updateRender()
return true
end
end
return DropDown.mouse_up(self, button, x, y)
end
return VisualElement.mouse_up and VisualElement.mouse_up(self, button, x, y) or false
end
--- Renders the ComboBox
--- @shortDescription Renders the ComboBox
--- @protected
function ComboBox:render()
VisualElement.render(self)
local text = self.getResolved("text")
local text = self.get("text")
local width = self.get("width")
local dropSymbol = self.getResolved("dropSymbol")
local dropSymbol = self.get("dropSymbol")
local isFocused = self:hasState("focused")
local isOpen = self:hasState("opened")
local viewOffset = self.get("viewOffset")
local placeholder = self.getResolved("placeholder")
local selectedText = self.getResolved("selectedText")
local bg = self.getResolved("background")
local fg = self.getResolved("foreground")
local displayText = text
local textWidth = width - #dropSymbol
if #text == 0 and not isFocused and #placeholder > 0 then
displayText = placeholder
fg = self.get("placeholderColor")
if #text == 0 and not isFocused and #selectedText > 0 then
displayText = selectedText
fg = colors.gray
end
if #displayText > 0 then
@@ -360,36 +397,33 @@ function ComboBox:render()
end
if isOpen then
local items
local actualHeight = self.get("height")
local items = self.get("items")
if self.get("autoComplete") and not self.get("manuallyOpened") then
items = self:getFilteredItems()
else
items = self.get("items")
end
local dropdownHeight = math.min(self.get("dropdownHeight"), #items)
if dropdownHeight > 0 then
local offset = self.get("offset")
for i = 1, dropdownHeight do
local itemIndex = i + offset
if items[itemIndex] then
local item = items[itemIndex]
local itemText = item.text or ""
local isSelected = item.selected or false
local originalItems = self._values.items
self._values.items = items
self.set("height", dropdownHeight)
local itemBg = isSelected and self.get("selectedBackground") or bg
local itemFg = isSelected and self.get("selectedForeground") or fg
List.render(self, 1)
if #itemText > width then
itemText = itemText:sub(1, width)
end
self._values.items = originalItems
self.set("height", actualHeight)
itemText = itemText .. string.rep(" ", width - #itemText)
self:blit(1, i + 1, itemText,
string.rep(tHex[itemFg], width),
string.rep(tHex[itemBg], width))
end
self:blit(1, 1, fullText,
string.rep(tHex[fg], width),
string.rep(tHex[bg], width))
if isFocused and self.get("editable") then
local cursorPos = self.get("cursorPos")
local cursorX = cursorPos - viewOffset
if cursorX >= 1 and cursorX <= textWidth then
self:setCursor(cursorX, 1, true, fg)
end
end
end

View File

@@ -196,7 +196,11 @@ function DropDown:render()
end
if isOpen then
local actualHeight = self.get("height")
local dropdownHeight = math.min(self.get("dropdownHeight"), #self.get("items"))
self.set("height", dropdownHeight)
List.render(self, 1)
self.set("height", actualHeight)
end
self:blit(1, 1, text .. string.rep(" ", self.get("width") - #text - 1) .. (isOpen and "\31" or "\17"),