diff --git a/src/elements/BaseElement.lua b/src/elements/BaseElement.lua index 114ae88..f163a35 100644 --- a/src/elements/BaseElement.lua +++ b/src/elements/BaseElement.lua @@ -259,15 +259,12 @@ end --- Destroys the element and cleans up all references --- @shortDescription Destroys the element and cleans up all references function BaseElement:destroy() - self._destroyed = true - self:removeAllObservers() - self:setFocused(false) - for event in pairs(self._registeredEvents) do - self:listenEvent(event, false) - end if(self.parent) then self.parent:removeChild(self) end + self._destroyed = true + self:removeAllObservers() + self:setFocused(false) end --- Requests a render update for this element diff --git a/src/elements/Container.lua b/src/elements/Container.lua index 21deeaf..b1cf064 100644 --- a/src/elements/Container.lua +++ b/src/elements/Container.lua @@ -164,7 +164,7 @@ local function sortAndFilterChildren(self, children) local visibleChildren = {} for _, child in ipairs(children) do - if self:isChildVisible(child) and child.get("visible") then + if self:isChildVisible(child) and child.get("visible") and not child._destroyed then table.insert(visibleChildren, child) end end @@ -250,7 +250,7 @@ function Container:registerChildEvent(child, eventName) end for _, registeredChild in ipairs(self._values.childrenEvents[eventName]) do - if registeredChild == child then + if registeredChild.get("id") == child.get("id") then return self end end @@ -347,13 +347,11 @@ end local function convertMousePosition(self, event, ...) local args = {...} - if event then - if event:find("mouse_") then - local button, absX, absY = ... - local xOffset, yOffset = self.get("offsetX"), self.get("offsetY") - local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset) - args = {button, relX, relY} - end + if event:find("mouse_") then + local button, absX, absY = ... + local xOffset, yOffset = self.get("offsetX"), self.get("offsetY") + local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset) + args = {button, relX, relY} end return args end @@ -488,13 +486,10 @@ end --- @return boolean handled Whether the event was handled --- @protected function Container:mouse_scroll(direction, x, y) - local args = convertMousePosition(self, "mouse_scroll", direction, x, y) - local success, child = self:callChildrenEvent(true, "mouse_scroll", table.unpack(args)) - if(success)then - return true - end if(VisualElement.mouse_scroll(self, direction, x, y))then - return true + local args = convertMousePosition(self, "mouse_scroll", direction, x, y) + local success, child = self:callChildrenEvent(true, "mouse_scroll", table.unpack(args)) + return success end return false end @@ -690,10 +685,6 @@ end --- @private function Container:destroy() if not self:isType("BaseFrame") then - for _, child in ipairs(self.get("children")) do - child:destroy() - end - self.set("childrenSorted", false) VisualElement.destroy(self) return self else diff --git a/src/elements/Flexbox.lua b/src/elements/Flexbox.lua index 51143b8..c953241 100644 --- a/src/elements/Flexbox.lua +++ b/src/elements/Flexbox.lua @@ -787,7 +787,6 @@ function Flexbox:addChild(element) return self end ---- Removes a child element from the flexbox --- @shortDescription Removes a child element from the flexbox --- @param element Element The child element to remove --- @return Flexbox self The flexbox instance diff --git a/src/elements/Frame.lua b/src/elements/Frame.lua index 2fb7e64..c892ce4 100644 --- a/src/elements/Frame.lua +++ b/src/elements/Frame.lua @@ -19,6 +19,13 @@ Frame.defineProperty(Frame, "draggable", {default = false, type = "boolean", set end}) ---@property draggingMap table {} The map of dragging positions Frame.defineProperty(Frame, "draggingMap", {default = {{x=1, y=1, width="width", height=1}}, type = "table"}) +---@property scrollable boolean false Whether the frame is scrollable +Frame.defineProperty(Frame, "scrollable", {default = false, type = "boolean", setter=function(self, value) + if value then + self:listenEvent("mouse_scroll", true) + end + return value +end}) --- Creates a new Frame instance --- @shortDescription Creates a new Frame instance @@ -94,9 +101,12 @@ end --- @return boolean handled Whether the event was handled --- @protected function Frame:mouse_up(button, x, y) - self.dragging = false - self.dragStartX = nil - self.dragStartY = nil + if self.dragging then + self.dragging = false + self.dragStartX = nil + self.dragStartY = nil + return true + end return Container.mouse_up(self, button, x, y) end @@ -121,4 +131,58 @@ function Frame:mouse_drag(button, x, y) return false end +--- @shortDescription Calculates the total height of all children elements +--- @return number height The total height needed for all children +--- @protected +function Frame:getChildrenHeight() + local maxHeight = 0 + local children = self.get("children") + + for _, child in ipairs(children) do + if child.get("visible") then + local childY = child.get("y") + local childHeight = child.get("height") + local totalHeight = childY + childHeight - 1 + + if totalHeight > maxHeight then + maxHeight = totalHeight + end + end + end + + return maxHeight +end + +--- @shortDescription Handles mouse scroll events +--- @param direction number The scroll direction +--- @param x number The x position of the scroll +--- @param y number The y position of the scroll +--- @return boolean handled Whether the event was handled +--- @protected +function Frame:mouse_scroll(direction, x, y) + if Container.mouse_scroll(self, direction, x, y) then + return true + end + + if self.get("scrollable") then + local relX, relY = self:getRelativePosition(x, y) + local width = self.get("width") + local height = self.get("height") + + if relX >= 1 and relX <= width and relY >= 1 and relY <= height then + local childrenHeight = self:getChildrenHeight() + local currentOffset = self.get("offsetY") + local maxScroll = math.max(0, childrenHeight - height) + + local newOffset = currentOffset + direction + newOffset = math.max(0, math.min(maxScroll, newOffset)) + + self.set("offsetY", newOffset) + return true + end + end + + return false +end + return Frame \ No newline at end of file diff --git a/src/elements/List.lua b/src/elements/List.lua index d486583..099de8a 100644 --- a/src/elements/List.lua +++ b/src/elements/List.lua @@ -173,38 +173,6 @@ function List:mouse_scroll(direction, x, y) return false end ---- Selects an item by index ---- @shortDescription Selects an item by index ---- @param index number The index of the item to select ---- @return List self The List instance -function List:selectItem(index) - local items = self.get("items") - - if not self.get("multiSelection") then - for _, item in ipairs(items) do - if type(item) == "table" then - item.selected = false - end - end - end - - local item = items[index] - if type(item) == "string" then - item = {text = item} - items[index] = item - end - - item.selected = true - - if item.callback then - item.callback(self) - end - - self:fireEvent("select", index, item) - self:updateRender() - return self -end - --- Registers a callback for the select event --- @shortDescription Registers a callback for the select event --- @param callback function The callback function to register diff --git a/src/elements/Table.lua b/src/elements/Table.lua index 7a9d018..e852a12 100644 --- a/src/elements/Table.lua +++ b/src/elements/Table.lua @@ -23,7 +23,13 @@ Table.defineProperty(Table, "columns", {default = {}, type = "table", canTrigger return t end}) ---@property data table {} The table data as array of row arrays -Table.defineProperty(Table, "data", {default = {}, type = "table", canTriggerRender = true}) +Table.defineProperty(Table, "data", {default = {}, type = "table", canTriggerRender = true, setter=function(self, value) + self.set("scrollOffset", 0) + self.set("selectedRow", nil) + self.set("sortColumn", nil) + self.set("sortDirection", "asc") + return value +end}) ---@property selectedRow number? nil Currently selected row index Table.defineProperty(Table, "selectedRow", {default = nil, type = "number", canTriggerRender = true}) ---@property headerColor color blue Color of the column headers @@ -33,9 +39,9 @@ Table.defineProperty(Table, "selectedColor", {default = colors.lightBlue, type = ---@property gridColor color gray Color of grid lines Table.defineProperty(Table, "gridColor", {default = colors.gray, type = "color"}) ---@property sortColumn number? nil Currently sorted column index -Table.defineProperty(Table, "sortColumn", {default = nil, type = "number"}) +Table.defineProperty(Table, "sortColumn", {default = nil, type = "number", canTriggerRender = true}) ---@property sortDirection string "asc" Sort direction ("asc" or "desc") -Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string"}) +Table.defineProperty(Table, "sortDirection", {default = "asc", type = "string", canTriggerRender = true}) ---@property scrollOffset number 0 Current scroll position Table.defineProperty(Table, "scrollOffset", {default = 0, type = "number", canTriggerRender = true}) @@ -144,17 +150,11 @@ function Table:mouse_click(button, x, y) if relY > 1 then local rowIndex = relY - 2 + self.get("scrollOffset") if rowIndex >= 0 and rowIndex < #self.get("data") then - local newIndex = rowIndex + 1 - self.set("selectedRow", newIndex) - self:fireEvent("select", newIndex, self.get("data")[newIndex]) + self.set("selectedRow", rowIndex + 1) end end - return true -end -function Table:onSelect(callback) - self:registerCallback("select", callback) - return self + return true end --- @shortDescription Handles scrolling through the table data @@ -168,7 +168,7 @@ function Table:mouse_scroll(direction, x, y) local data = self.get("data") local height = self.get("height") local visibleRows = height - 2 - local maxScroll = math.max(0, #data - visibleRows + 1) + local maxScroll = math.max(0, #data - visibleRows - 1) local newOffset = math.min(maxScroll, math.max(0, self.get("scrollOffset") + direction)) self.set("scrollOffset", newOffset) @@ -189,14 +189,31 @@ function Table:render() local height = self.get("height") local width = self.get("width") + local totalWidth = 0 + local lastVisibleColumn = #columns + for i, col in ipairs(columns) do + if totalWidth + col.width > width then + if i == 1 then + col.visibleWidth = width + else + col.visibleWidth = width - totalWidth + lastVisibleColumn = i + end + break + end + col.visibleWidth = col.width + totalWidth = totalWidth + col.width + end + local currentX = 1 for i, col in ipairs(columns) do + if i > lastVisibleColumn then break end local text = col.name if i == sortCol then text = text .. (self.get("sortDirection") == "asc" and "\30" or "\31") end - self:textFg(currentX, 1, text:sub(1, col.width), self.get("headerColor")) - currentX = currentX + col.width + self:textFg(currentX, 1, text:sub(1, col.visibleWidth), self.get("headerColor")) + currentX = currentX + col.visibleWidth end for y = 2, height do @@ -208,17 +225,18 @@ function Table:render() local bg = (rowIndex + 1) == selected and self.get("selectedColor") or self.get("background") for i, col in ipairs(columns) do + if i > lastVisibleColumn then break end local cellText = tostring(rowData[i] or "") - local paddedText = cellText .. string.rep(" ", col.width - #cellText) - if i < #columns then - paddedText = string.sub(paddedText, 1, col.width - 1) .. " " + local paddedText = cellText .. string.rep(" ", col.visibleWidth - #cellText) + if i < lastVisibleColumn then + paddedText = string.sub(paddedText, 1, col.visibleWidth - 1) .. " " end - local finalText = string.sub(paddedText, 1, col.width) - local finalForeground = string.rep(tHex[self.get("foreground")], #finalText) - local finalBackground = string.rep(tHex[bg], #finalText) + local finalText = string.sub(paddedText, 1, col.visibleWidth) + local finalForeground = string.rep(tHex[self.get("foreground")], col.visibleWidth) + local finalBackground = string.rep(tHex[bg], col.visibleWidth) self:blit(currentX, y, finalText, finalForeground, finalBackground) - currentX = currentX + col.width + currentX = currentX + col.visibleWidth end else self:blit(1, y, string.rep(" ", self.get("width")), diff --git a/src/elements/VisualElement.lua b/src/elements/VisualElement.lua index a35aa7b..9d4c7e3 100644 --- a/src/elements/VisualElement.lua +++ b/src/elements/VisualElement.lua @@ -99,7 +99,7 @@ VisualElement.registerEventCallback(VisualElement, "ClickUp", "mouse_up", "mouse VisualElement.registerEventCallback(VisualElement, "Drag", "mouse_drag", "mouse_click", "mouse_up") VisualElement.registerEventCallback(VisualElement, "Scroll", "mouse_scroll") VisualElement.registerEventCallback(VisualElement, "Enter", "mouse_enter", "mouse_move") -VisualElement.registerEventCallback(VisualElement, "Leave", "mouse_leave", "mouse_move") +VisualElement.registerEventCallback(VisualElement, "LeEave", "mouse_leave", "mouse_move") VisualElement.registerEventCallback(VisualElement, "Focus", "focus", "blur") VisualElement.registerEventCallback(VisualElement, "Blur", "blur", "focus") VisualElement.registerEventCallback(VisualElement, "Key", "key", "key_up") @@ -210,6 +210,9 @@ end --- @param y number The y position to check --- @return boolean isInBounds Whether the coordinates are within the bounds of the element function VisualElement:isInBounds(x, y) + if x == nil or y == nil then + return false + end local xPos, yPos = self.get("x"), self.get("y") local width, height = self.get("width"), self.get("height") if(self.get("ignoreOffset"))then @@ -467,4 +470,9 @@ end function VisualElement:postRender() end +function VisualElement:destroy() + self.set("visible", false) + BaseElement.destroy(self) +end + return VisualElement \ No newline at end of file diff --git a/src/main.lua b/src/main.lua index 3f413fd..6e4cb45 100644 --- a/src/main.lua +++ b/src/main.lua @@ -305,12 +305,6 @@ local function renderFrames() end end ---- Renders all frames in the Basalt runtime ---- @shortDescription Renders all frames -function basalt.render() - renderFrames() -end - --- Runs basalt once, can be used to update the UI manually, but you have to feed it the events --- @shortDescription Runs basalt once --- @vararg any The event to run with diff --git a/src/plugins/xml.lua b/src/plugins/xml.lua index 20a22da..f1f9147 100644 --- a/src/plugins/xml.lua +++ b/src/plugins/xml.lua @@ -95,9 +95,6 @@ local function findExpressions(text) end local function convertValue(value, scope) - if type(value) ~= "string" then - return value - end if value:sub(1,1) == "\"" and value:sub(-1) == "\"" then value = value:sub(2, -2) end