Lot of bug fixxes
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
local markdown = require("tools/markdown")
|
||||
local log = require("src/log")
|
||||
|
||||
if not fs.exists("docs/references") then
|
||||
fs.makeDir("docs/references")
|
||||
end
|
||||
|
||||
local function processFile(inputFile)
|
||||
local parsed = markdown.parseFile(inputFile)
|
||||
local md = markdown.makeMarkdown(parsed)
|
||||
|
||||
local relativePath = inputFile:match("Basalt2/src/(.+)")
|
||||
if not relativePath then return end
|
||||
|
||||
local outputFile = "docs/references/" .. relativePath:gsub("%.lua$", "")
|
||||
|
||||
local dir = fs.getDir(outputFile)
|
||||
if not fs.exists(dir) then
|
||||
fs.makeDir(dir)
|
||||
end
|
||||
|
||||
--print(string.format("Processing: %s -> %s", inputFile, outputFile))
|
||||
|
||||
markdown.saveToFile(outputFile, md)
|
||||
end
|
||||
|
||||
|
||||
local function processDirectory(path)
|
||||
for _, file in ipairs(fs.list(path)) do
|
||||
local fullPath = fs.combine(path, file)
|
||||
if fs.isDir(fullPath) then
|
||||
processDirectory(fullPath)
|
||||
elseif file:match("%.lua$") and not file:match("LuaLS%.lua$") then
|
||||
processFile(fullPath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
processDirectory("Basalt2/src")
|
||||
@@ -34,12 +34,13 @@ BaseElement.defineProperty(BaseElement, "eventCallbacks", {default = {}, type =
|
||||
--- @shortDescription Registers an event that this class can listen to
|
||||
--- @param class table The class to add the event to
|
||||
--- @param eventName string The name of the event to register
|
||||
--- @param event? string The event to handle
|
||||
--- @usage BaseElement.listenTo(MyClass, "mouse_click")
|
||||
function BaseElement.listenTo(class, eventName)
|
||||
function BaseElement.listenTo(class, eventName, event)
|
||||
if not class._events then
|
||||
class._events = {}
|
||||
end
|
||||
class._events[eventName] = true
|
||||
class._events[eventName] = {enabled=true, name=eventName, event=event}
|
||||
end
|
||||
|
||||
--- Creates a new BaseElement instance
|
||||
@@ -64,13 +65,13 @@ function BaseElement:init(props, basalt)
|
||||
self.basalt = basalt
|
||||
self._registeredEvents = {}
|
||||
if BaseElement._events then
|
||||
for event in pairs(BaseElement._events) do
|
||||
self._registeredEvents[event] = true
|
||||
local handlerName = "on" .. event:gsub("_(%l)", function(c)
|
||||
for key,event in pairs(BaseElement._events) do
|
||||
self._registeredEvents[event.event or event.name] = true
|
||||
local handlerName = "on" .. event.name:gsub("_(%l)", function(c)
|
||||
return c:upper()
|
||||
end):gsub("^%l", string.upper)
|
||||
self[handlerName] = function(self, ...)
|
||||
self:registerCallback(event, ...)
|
||||
self:registerCallback(event.name, ...)
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,9 +93,14 @@ end
|
||||
--- @param x number The x position to set the cursor to
|
||||
--- @param y number The y position to set the cursor to
|
||||
--- @param blink boolean Whether the cursor should blink
|
||||
function BaseFrame:setCursor(x, y, blink)
|
||||
function BaseFrame:setCursor(x, y, blink, color)
|
||||
local term = self.get("term")
|
||||
self._render:setCursor(x, y, blink)
|
||||
self._render:setCursor(x, y, blink, color)
|
||||
end
|
||||
|
||||
function BaseFrame:mouse_up(button, x, y)
|
||||
Container.mouse_up(self, button, x, y)
|
||||
Container.mouse_release(self, button, x, y)
|
||||
end
|
||||
|
||||
--- Renders the Frame
|
||||
@@ -110,4 +115,15 @@ function BaseFrame:render()
|
||||
end
|
||||
end
|
||||
|
||||
function BaseFrame:term_resize()
|
||||
local width, height = self.get("term").getSize()
|
||||
if(width == self.get("width") and height == self.get("height")) then
|
||||
return
|
||||
end
|
||||
self.set("width", width)
|
||||
self.set("height", height)
|
||||
self._render:setSize(width, height)
|
||||
self._renderUpdate = true
|
||||
end
|
||||
|
||||
return BaseFrame
|
||||
@@ -3,8 +3,6 @@ local VisualElement = elementManager.getElement("VisualElement")
|
||||
local expect = require("libraries/expect")
|
||||
local split = require("libraries/utils").split
|
||||
|
||||
local max = math.max
|
||||
|
||||
--- The container class. It is a visual element that can contain other elements. It is the base class for all containers,
|
||||
--- like Frames, BaseFrames, and more.
|
||||
---@class Container : VisualElement
|
||||
@@ -303,7 +301,7 @@ local function convertMousePosition(self, event, ...)
|
||||
return args
|
||||
end
|
||||
|
||||
local function callChildrenEvents(self, visibleOnly, event, ...)
|
||||
function Container:callChildrenEvents(visibleOnly, event, ...)
|
||||
local children = visibleOnly and self.get("visibleChildrenEvents") or self.get("childrenEvents")
|
||||
if children[event] then
|
||||
local events = children[event]
|
||||
@@ -325,7 +323,7 @@ end
|
||||
function Container:handleEvent(event, ...)
|
||||
VisualElement.handleEvent(self, event, ...)
|
||||
local args = convertMousePosition(self, event, ...)
|
||||
return callChildrenEvents(self, false, event, table.unpack(args))
|
||||
return self:callChildrenEvents(false, event, table.unpack(args))
|
||||
end
|
||||
|
||||
--- Handles mouse click events
|
||||
@@ -337,7 +335,7 @@ end
|
||||
function Container:mouse_click(button, x, y)
|
||||
if VisualElement.mouse_click(self, button, x, y) then
|
||||
local args = convertMousePosition(self, "mouse_click", button, x, y)
|
||||
local success, child = callChildrenEvents(self, true, "mouse_click", table.unpack(args))
|
||||
local success, child = self:callChildrenEvents(true, "mouse_click", table.unpack(args))
|
||||
if(success)then
|
||||
self.set("focusedChild", child)
|
||||
return true
|
||||
@@ -357,7 +355,7 @@ end
|
||||
function Container:mouse_up(button, x, y)
|
||||
if VisualElement.mouse_up(self, button, x, y) then
|
||||
local args = convertMousePosition(self, "mouse_up", button, x, y)
|
||||
local success, child = callChildrenEvents(self, true, "mouse_up", table.unpack(args))
|
||||
local success, child = self:callChildrenEvents(true, "mouse_up", table.unpack(args))
|
||||
if(success)then
|
||||
return true
|
||||
end
|
||||
@@ -365,6 +363,45 @@ function Container:mouse_up(button, x, y)
|
||||
return false
|
||||
end
|
||||
|
||||
function Container:mouse_release(button, x, y)
|
||||
VisualElement.mouse_release(self, button, x, y)
|
||||
local args = convertMousePosition(self, "mouse_release", button, x, y)
|
||||
self:callChildrenEvents(false, "mouse_release", table.unpack(args))
|
||||
end
|
||||
|
||||
function Container:mouse_move(_, x, y)
|
||||
if VisualElement.mouse_move(self, _, x, y) then
|
||||
local args = convertMousePosition(self, "mouse_move", _, x, y)
|
||||
local success, child = self:callChildrenEvents(true, "mouse_move", table.unpack(args))
|
||||
if(success)then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Container:mouse_drag(button, x, y)
|
||||
if VisualElement.mouse_drag(self, button, x, y) then
|
||||
local args = convertMousePosition(self, "mouse_drag", button, x, y)
|
||||
local success, child = self:callChildrenEvents(true, "mouse_drag", table.unpack(args))
|
||||
if(success)then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Container:mouse_scroll(direction, x, y)
|
||||
if VisualElement.mouse_scroll(self, direction, x, y) then
|
||||
local args = convertMousePosition(self, "mouse_scroll", direction, x, y)
|
||||
local success, child = self:callChildrenEvents(true, "mouse_scroll", table.unpack(args))
|
||||
if(success)then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles key events
|
||||
--- @shortDescription Handles key events
|
||||
--- @param key number The key that was pressed
|
||||
@@ -438,6 +475,7 @@ function Container:textFg(x, y, text, fg)
|
||||
if textLen <= 0 then return self end
|
||||
|
||||
VisualElement.textFg(self, math.max(1, x), math.max(1, y), text:sub(textStart, textStart + textLen - 1), fg)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Draws a line of text and fg and bg as colors, it is usually used in the render loop
|
||||
|
||||
@@ -23,6 +23,8 @@ Input.defineProperty(Input, "placeholderColor", {default = colors.gray, type = "
|
||||
Input.defineProperty(Input, "focusedColor", {default = colors.blue, type = "number"})
|
||||
---@property pattern string? nil Regular expression pattern for input validation
|
||||
Input.defineProperty(Input, "pattern", {default = nil, type = "string"})
|
||||
---@property cursorColor number nil Color of the cursor
|
||||
Input.defineProperty(Input, "cursorColor", {default = nil, type = "number"})
|
||||
|
||||
Input.listenTo(Input, "mouse_click")
|
||||
Input.listenTo(Input, "key")
|
||||
@@ -106,7 +108,7 @@ function Input:key(key)
|
||||
end
|
||||
|
||||
local relativePos = self.get("cursorPos") - self.get("viewOffset")
|
||||
self:setCursor(relativePos, 1, true)
|
||||
self:setCursor(relativePos, 1, true, self.get("cursorColor") or self.get("foreground"))
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -134,7 +136,7 @@ function Input:mouse_click(button, x, y)
|
||||
if VisualElement.mouse_click(self, button, x, y) then
|
||||
local relX, relY = self:getRelativePosition(x, y)
|
||||
local text = self.get("text")
|
||||
self:setCursor(math.min(relX, #text + 1), relY, true)
|
||||
self:setCursor(math.min(relX, #text + 1), relY, true, self.get("cursorColor") or self.get("foreground"))
|
||||
self:set("cursorPos", relX + self.get("viewOffset"))
|
||||
return true
|
||||
end
|
||||
|
||||
180
src/elements/Scrollbar.lua
Normal file
180
src/elements/Scrollbar.lua
Normal file
@@ -0,0 +1,180 @@
|
||||
local VisualElement = require("elements/VisualElement")
|
||||
local tHex = require("libraries/colorHex")
|
||||
|
||||
---A scrollbar element that can be attached to other elements to control their scroll properties
|
||||
---@class Scrollbar : VisualElement
|
||||
local Scrollbar = setmetatable({}, VisualElement)
|
||||
Scrollbar.__index = Scrollbar
|
||||
|
||||
---@property value number 0 Current scroll value
|
||||
Scrollbar.defineProperty(Scrollbar, "value", {default = 0, type = "number", canTriggerRender = true})
|
||||
---@property min number 0 Minimum scroll value
|
||||
Scrollbar.defineProperty(Scrollbar, "min", {default = 0, type = "number", canTriggerRender = true})
|
||||
---@property max number 100 Maximum scroll value
|
||||
Scrollbar.defineProperty(Scrollbar, "max", {default = 100, type = "number", canTriggerRender = true})
|
||||
---@property step number 1 Step size for scroll operations
|
||||
Scrollbar.defineProperty(Scrollbar, "step", {default = 10, type = "number"})
|
||||
---@property dragMultiplier number 1 How fast the scrollbar moves when dragging
|
||||
Scrollbar.defineProperty(Scrollbar, "dragMultiplier", {default = 1, type = "number"})
|
||||
---@property symbol string " " Symbol used for the scrollbar handle
|
||||
Scrollbar.defineProperty(Scrollbar, "symbol", {default = " ", type = "string", canTriggerRender = true})
|
||||
---@property backgroundSymbol string "\127" Symbol used for the scrollbar background
|
||||
Scrollbar.defineProperty(Scrollbar, "symbolColor", {default = colors.gray, type = "number", canTriggerRender = true})
|
||||
---@property symbolBackgroundColor color black Background color of the scrollbar handle
|
||||
Scrollbar.defineProperty(Scrollbar, "symbolBackgroundColor", {default = colors.black, type = "number", canTriggerRender = true})
|
||||
---@property backgroundSymbol string "\127" Symbol used for the scrollbar background
|
||||
Scrollbar.defineProperty(Scrollbar, "backgroundSymbol", {default = "\127", type = "string", canTriggerRender = true})
|
||||
---@property attachedElement table? nil The element this scrollbar is attached to
|
||||
Scrollbar.defineProperty(Scrollbar, "attachedElement", {default = nil, type = "table"})
|
||||
---@property attachedProperty string? nil The property being controlled
|
||||
Scrollbar.defineProperty(Scrollbar, "attachedProperty", {default = nil, type = "string"})
|
||||
---@property minValue number|function 0 Minimum value or function that returns it
|
||||
Scrollbar.defineProperty(Scrollbar, "minValue", {default = 0, type = "number"})
|
||||
---@property maxValue number|function 100 Maximum value or function that returns it
|
||||
Scrollbar.defineProperty(Scrollbar, "maxValue", {default = 100, type = "number"})
|
||||
---@property orientation string vertical Orientation of the scrollbar ("vertical" or "horizontal")
|
||||
Scrollbar.defineProperty(Scrollbar, "orientation", {default = "vertical", type = "string", canTriggerRender = true})
|
||||
|
||||
---@property handleSize number 2 Size of the scrollbar handle in characters
|
||||
Scrollbar.defineProperty(Scrollbar, "handleSize", {default = 2, type = "number", canTriggerRender = true})
|
||||
|
||||
Scrollbar.listenTo(Scrollbar, "mouse_click")
|
||||
Scrollbar.listenTo(Scrollbar, "mouse_release")
|
||||
Scrollbar.listenTo(Scrollbar, "mouse_drag")
|
||||
Scrollbar.listenTo(Scrollbar, "mouse_scroll")
|
||||
|
||||
--- Creates a new Scrollbar instance
|
||||
--- @shortDescription Creates a new Scrollbar instance
|
||||
--- @return Scrollbar self The newly created Scrollbar instance
|
||||
--- @usage local scrollbar = Scrollbar.new()
|
||||
function Scrollbar.new()
|
||||
local self = setmetatable({}, Scrollbar):__init()
|
||||
self.set("width", 1)
|
||||
self.set("height", 10)
|
||||
return self
|
||||
end
|
||||
|
||||
function Scrollbar:init(props, basalt)
|
||||
VisualElement.init(self, props, basalt)
|
||||
self.set("type", "Scrollbar")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Attaches the scrollbar to an element's property
|
||||
--- @param element BaseElement The element to attach to
|
||||
--- @param config table Configuration {property = "propertyName", min = number|function, max = number|function}
|
||||
--- @return Scrollbar self The scrollbar instance
|
||||
function Scrollbar:attach(element, config)
|
||||
self.set("attachedElement", element)
|
||||
self.set("attachedProperty", config.property)
|
||||
self.set("minValue", config.min or 0)
|
||||
self.set("maxValue", config.max or 100)
|
||||
return self
|
||||
end
|
||||
|
||||
function Scrollbar:updateAttachedElement()
|
||||
local element = self.get("attachedElement")
|
||||
if not element then return end
|
||||
|
||||
local value = self.get("value")
|
||||
local min = self.get("minValue")
|
||||
local max = self.get("maxValue")
|
||||
|
||||
if type(min) == "function" then min = min() end
|
||||
if type(max) == "function" then max = max() end
|
||||
|
||||
local mappedValue = min + (value / 100) * (max - min)
|
||||
element.set(self.get("attachedProperty"), math.floor(mappedValue + 0.5))
|
||||
end
|
||||
|
||||
local function getScrollbarSize(self)
|
||||
return self.get("orientation") == "vertical" and self.get("height") or self.get("width")
|
||||
end
|
||||
|
||||
local function getRelativeScrollPosition(self, x, y)
|
||||
local relX, relY = self:getRelativePosition(x, y)
|
||||
return self.get("orientation") == "vertical" and relY or relX
|
||||
end
|
||||
|
||||
function Scrollbar:mouse_click(button, x, y)
|
||||
if VisualElement.mouse_click(self, button, x, y) then
|
||||
local size = getScrollbarSize(self)
|
||||
local value = self.get("value")
|
||||
local handleSize = self.get("handleSize")
|
||||
|
||||
local handlePos = math.floor((value / 100) * (size - handleSize)) + 1
|
||||
local relPos = getRelativeScrollPosition(self, x, y)
|
||||
|
||||
if relPos >= handlePos and relPos < handlePos + handleSize then
|
||||
self.dragOffset = relPos - handlePos
|
||||
else
|
||||
local newValue = ((relPos - 1) / (size - handleSize)) * 100
|
||||
self.set("value", math.min(100, math.max(0, newValue)))
|
||||
self:updateAttachedElement()
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Scrollbar:mouse_drag(button, x, y)
|
||||
if(VisualElement.mouse_drag(self, button, x, y))then
|
||||
local size = getScrollbarSize(self)
|
||||
local handleSize = self.get("handleSize")
|
||||
local dragMultiplier = self.get("dragMultiplier")
|
||||
local relPos = getRelativeScrollPosition(self, x, y)
|
||||
|
||||
relPos = math.max(1, math.min(size, relPos))
|
||||
|
||||
local newPos = relPos - (self.dragOffset or 0)
|
||||
local newValue = (newPos - 1) / (size - handleSize) * 100 * dragMultiplier
|
||||
|
||||
self.set("value", math.min(100, math.max(0, newValue)))
|
||||
self:updateAttachedElement()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Scrollbar:mouse_scroll(direction, x, y)
|
||||
if not self:isInBounds(x, y) then return false end
|
||||
direction = direction > 0 and -1 or 1
|
||||
local step = self.get("step")
|
||||
local currentValue = self.get("value")
|
||||
local newValue = currentValue - direction * step
|
||||
|
||||
self.set("value", math.min(100, math.max(0, newValue)))
|
||||
self:updateAttachedElement()
|
||||
return true
|
||||
end
|
||||
|
||||
function Scrollbar:render()
|
||||
VisualElement.render(self)
|
||||
|
||||
local size = getScrollbarSize(self)
|
||||
local value = self.get("value")
|
||||
local handleSize = self.get("handleSize")
|
||||
local symbol = self.get("symbol")
|
||||
local symbolColor = self.get("symbolColor")
|
||||
local symbolBackgroundColor = self.get("symbolBackgroundColor")
|
||||
local bgSymbol = self.get("backgroundSymbol")
|
||||
local isVertical = self.get("orientation") == "vertical"
|
||||
|
||||
local handlePos = math.floor((value / 100) * (size - handleSize)) + 1
|
||||
|
||||
for i = 1, size do
|
||||
if isVertical then
|
||||
self:blit(1, i, bgSymbol, tHex[self.get("foreground")], tHex[self.get("background")])
|
||||
else
|
||||
self:blit(i, 1, bgSymbol, tHex[self.get("foreground")], tHex[self.get("background")])
|
||||
end
|
||||
end
|
||||
|
||||
for i = handlePos, handlePos + handleSize - 1 do
|
||||
if isVertical then
|
||||
self:blit(1, i, symbol, tHex[symbolColor], tHex[symbolBackgroundColor])
|
||||
else
|
||||
self:blit(i, 1, symbol, tHex[symbolColor], tHex[symbolBackgroundColor])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Scrollbar
|
||||
@@ -20,6 +20,8 @@ TextBox.defineProperty(TextBox, "scrollY", {default = 0, type = "number", canTri
|
||||
TextBox.defineProperty(TextBox, "editable", {default = true, type = "boolean"})
|
||||
---@property syntaxPatterns table {} Syntax highlighting patterns
|
||||
TextBox.defineProperty(TextBox, "syntaxPatterns", {default = {}, type = "table"})
|
||||
---@property cursorColor number nil Color of the cursor
|
||||
TextBox.defineProperty(TextBox, "cursorColor", {default = nil, type = "number"})
|
||||
|
||||
TextBox.listenTo(TextBox, "mouse_click")
|
||||
TextBox.listenTo(TextBox, "key")
|
||||
@@ -41,7 +43,7 @@ end
|
||||
|
||||
--- Adds a new syntax highlighting pattern
|
||||
--- @param pattern string The regex pattern to match
|
||||
--- @param color color The color to apply
|
||||
--- @param color colors The color to apply
|
||||
function TextBox:addSyntaxPattern(pattern, color)
|
||||
table.insert(self.get("syntaxPatterns"), {pattern = pattern, color = color})
|
||||
return self
|
||||
@@ -162,7 +164,14 @@ end
|
||||
function TextBox:mouse_scroll(direction, x, y)
|
||||
if self:isInBounds(x, y) then
|
||||
local scrollY = self.get("scrollY")
|
||||
self.set("scrollY", math.max(0, scrollY + direction))
|
||||
local height = self.get("height")
|
||||
local lines = self.get("lines")
|
||||
|
||||
local maxScroll = math.max(0, #lines - height + 2)
|
||||
|
||||
local newScroll = math.max(0, math.min(maxScroll, scrollY + direction))
|
||||
|
||||
self.set("scrollY", newScroll)
|
||||
self:updateRender()
|
||||
return true
|
||||
end
|
||||
@@ -182,16 +191,22 @@ function TextBox:mouse_click(button, x, y)
|
||||
self.set("cursorY", targetY)
|
||||
self.set("cursorX", math.min(relX + scrollX, #lines[targetY] + 1))
|
||||
end
|
||||
self:updateRender()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function TextBox:setText(text)
|
||||
self.set("lines", {""})
|
||||
for line in text:gmatch("[^\n]+") do
|
||||
table.insert(self.get("lines"), line)
|
||||
local lines = {}
|
||||
if text == "" then
|
||||
lines = {""}
|
||||
else
|
||||
for line in (text.."\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
end
|
||||
self.set("lines", lines)
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -244,7 +259,7 @@ function TextBox:render()
|
||||
local relativeX = self.get("cursorX") - scrollX
|
||||
local relativeY = self.get("cursorY") - scrollY
|
||||
if relativeX >= 1 and relativeX <= width and relativeY >= 1 and relativeY <= height then
|
||||
self:setCursor(relativeX, relativeY, true)
|
||||
self:setCursor(relativeX, relativeY, true, self.get("cursorColor") or self.get("foreground"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,8 +13,10 @@ Tree.defineProperty(Tree, "nodes", {default = {}, type = "table", canTriggerRend
|
||||
Tree.defineProperty(Tree, "selectedNode", {default = nil, type = "table", canTriggerRender = true})
|
||||
---@property expandedNodes table {} Table of nodes that are currently expanded
|
||||
Tree.defineProperty(Tree, "expandedNodes", {default = {}, type = "table", canTriggerRender = true})
|
||||
---@property scrollOffset number 0 Current scroll position
|
||||
---@property scrollOffset number 0 Current vertical scroll position
|
||||
Tree.defineProperty(Tree, "scrollOffset", {default = 0, type = "number", canTriggerRender = true})
|
||||
---@property horizontalOffset number 0 Current horizontal scroll position
|
||||
Tree.defineProperty(Tree, "horizontalOffset", {default = 0, type = "number", canTriggerRender = true})
|
||||
---@property nodeColor color white Color of unselected nodes
|
||||
Tree.defineProperty(Tree, "nodeColor", {default = colors.white, type = "number"})
|
||||
---@property selectedColor color lightBlue Background color of selected node
|
||||
@@ -106,24 +108,24 @@ local function flattenTree(nodes, expandedNodes, level, result)
|
||||
end
|
||||
|
||||
function Tree:mouse_click(button, x, y)
|
||||
if not VisualElement.mouse_click(self, button, x, y) then return false end
|
||||
if VisualElement.mouse_click(self, button, x, y) then
|
||||
local relX, relY = self:getRelativePosition(x, y)
|
||||
local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes"))
|
||||
local visibleIndex = relY + self.get("scrollOffset")
|
||||
|
||||
local relX, relY = self:getRelativePosition(x, y)
|
||||
local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes"))
|
||||
local visibleIndex = relY + self.get("scrollOffset")
|
||||
if flatNodes[visibleIndex] then
|
||||
local nodeInfo = flatNodes[visibleIndex]
|
||||
local node = nodeInfo.node
|
||||
|
||||
if flatNodes[visibleIndex] then
|
||||
local nodeInfo = flatNodes[visibleIndex]
|
||||
local node = nodeInfo.node
|
||||
if relX <= nodeInfo.level * 2 + 2 then
|
||||
self:toggleNode(node)
|
||||
end
|
||||
|
||||
if relX <= nodeInfo.level * 2 + 2 then
|
||||
self:toggleNode(node)
|
||||
self.set("selectedNode", node)
|
||||
self:fireEvent("node_select", node)
|
||||
end
|
||||
|
||||
self.set("selectedNode", node)
|
||||
self:fireEvent("node_select", node)
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Tree:onSelect(callback)
|
||||
@@ -131,13 +133,25 @@ function Tree:onSelect(callback)
|
||||
return self
|
||||
end
|
||||
|
||||
function Tree:mouse_scroll(direction)
|
||||
local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes"))
|
||||
local maxScroll = math.max(0, #flatNodes - self.get("height"))
|
||||
local newScroll = math.min(maxScroll, math.max(0, self.get("scrollOffset") + direction))
|
||||
function Tree:mouse_scroll(direction, x, y)
|
||||
if VisualElement.mouse_scroll(self, direction, x, y) then
|
||||
local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes"))
|
||||
local maxScroll = math.max(0, #flatNodes - self.get("height"))
|
||||
local newScroll = math.min(maxScroll, math.max(0, self.get("scrollOffset") + direction))
|
||||
|
||||
self.set("scrollOffset", newScroll)
|
||||
return true
|
||||
self.set("scrollOffset", newScroll)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Tree:getNodeSize()
|
||||
local width, height = 0, 0
|
||||
local flatNodes = flattenTree(self.get("nodes"), self.get("expandedNodes"))
|
||||
for _, nodeInfo in ipairs(flatNodes) do
|
||||
width = math.max(width, nodeInfo.level + #nodeInfo.node.text)
|
||||
end
|
||||
height = #flatNodes
|
||||
return width, height
|
||||
end
|
||||
|
||||
function Tree:render()
|
||||
@@ -148,6 +162,7 @@ function Tree:render()
|
||||
local selectedNode = self.get("selectedNode")
|
||||
local expandedNodes = self.get("expandedNodes")
|
||||
local scrollOffset = self.get("scrollOffset")
|
||||
local horizontalOffset = self.get("horizontalOffset")
|
||||
|
||||
for y = 1, height do
|
||||
local nodeInfo = flatNodes[y + scrollOffset]
|
||||
@@ -162,11 +177,10 @@ function Tree:render()
|
||||
end
|
||||
|
||||
local bg = node == selectedNode and self.get("selectedColor") or self.get("background")
|
||||
local text = indent .. symbol .." " .. (node.text or "Node")
|
||||
text = sub(text, 1, self.get("width"))
|
||||
local fullText = indent .. symbol .." " .. (node.text or "Node")
|
||||
local text = sub(fullText, horizontalOffset + 1, horizontalOffset + self.get("width"))
|
||||
|
||||
self:textFg(1, y, text .. string.rep(" ", self.get("width") - #text), self.get("foreground"))
|
||||
|
||||
else
|
||||
self:textFg(1, y, string.rep(" ", self.get("width")), self.get("foreground"), self.get("background"))
|
||||
end
|
||||
|
||||
@@ -30,6 +30,8 @@ VisualElement.defineProperty(VisualElement, "background", {default = colors.blac
|
||||
VisualElement.defineProperty(VisualElement, "foreground", {default = colors.white, type = "number", canTriggerRender = true})
|
||||
---@property clicked boolean false Whether the element is currently clicked
|
||||
VisualElement.defineProperty(VisualElement, "clicked", {default = false, type = "boolean"})
|
||||
---@property hover boolean false Whether the mouse is currently hover over the element (Craftos-PC only)
|
||||
VisualElement.defineProperty(VisualElement, "hover", {default = false, type = "boolean"})
|
||||
---@property backgroundEnabled boolean true Whether to render the background
|
||||
VisualElement.defineProperty(VisualElement, "backgroundEnabled", {default = true, type = "boolean", canTriggerRender = true})
|
||||
---@property focused boolean false Whether the element has input focus
|
||||
@@ -81,6 +83,8 @@ VisualElement.combineProperties(VisualElement, "color", "foreground", "backgroun
|
||||
|
||||
VisualElement.listenTo(VisualElement, "focus")
|
||||
VisualElement.listenTo(VisualElement, "blur")
|
||||
VisualElement.listenTo(VisualElement, "mouse_enter", "mouse_move")
|
||||
VisualElement.listenTo(VisualElement, "mouse_leave", "mouse_move")
|
||||
|
||||
local max, min = math.max, math.min
|
||||
|
||||
@@ -185,12 +189,12 @@ end
|
||||
--- @param y number The y position of the release
|
||||
--- @return boolean release Whether the element was released on the element
|
||||
function VisualElement:mouse_up(button, x, y)
|
||||
self.set("clicked", false)
|
||||
if self:isInBounds(x, y) then
|
||||
self:fireEvent("mouse_up", button, x, y)
|
||||
self.set("clicked", false)
|
||||
self:fireEvent("mouse_up", button, self:getRelativePosition(x, y))
|
||||
return true
|
||||
end
|
||||
self:fireEvent("mouse_release", button, self:getRelativePosition(x, y))
|
||||
return false
|
||||
end
|
||||
|
||||
--- Handles a mouse release event
|
||||
@@ -198,11 +202,42 @@ end
|
||||
--- @param button number The button that was released
|
||||
--- @param x number The x position of the release
|
||||
--- @param y number The y position of the release
|
||||
--- @return boolean release Whether the element was released on the element
|
||||
function VisualElement:mouse_release(button, x, y)
|
||||
if self.get("clicked") then
|
||||
self:fireEvent("mouse_release", button, self:getRelativePosition(x, y))
|
||||
self.set("clicked", false)
|
||||
self:fireEvent("mouse_release", button, self:getRelativePosition(x, y))
|
||||
self.set("clicked", false)
|
||||
end
|
||||
|
||||
function VisualElement:mouse_move(_, x, y)
|
||||
if(x==nil)or(y==nil)then
|
||||
return
|
||||
end
|
||||
local hover = self.get("hover")
|
||||
if(self:isInBounds(x, y))then
|
||||
if(not hover)then
|
||||
self.set("hover", true)
|
||||
self:fireEvent("mouse_enter", self:getRelativePosition(x, y))
|
||||
end
|
||||
return true
|
||||
else
|
||||
if(hover)then
|
||||
self.set("hover", false)
|
||||
self:fireEvent("mouse_leave", self:getRelativePosition(x, y))
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function VisualElement:mouse_scroll(direction, x, y)
|
||||
if(self:isInBounds(x, y))then
|
||||
self:fireEvent("mouse_scroll", direction, self:getRelativePosition(x, y))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function VisualElement:mouse_drag(button, x, y)
|
||||
if(self.get("clicked"))then
|
||||
self:fireEvent("mouse_drag", button, self:getRelativePosition(x, y))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
@@ -272,11 +307,11 @@ end
|
||||
--- @param x number The x position of the cursor
|
||||
--- @param y number The y position of the cursor
|
||||
--- @param blink boolean Whether the cursor should blink
|
||||
function VisualElement:setCursor(x, y, blink)
|
||||
function VisualElement:setCursor(x, y, blink, color)
|
||||
if self.parent then
|
||||
local absX, absY = self:getAbsolutePosition(x, y)
|
||||
absX = max(self.get("x"), min(absX, self.get("width") + self.get("x") - 1))
|
||||
return self.parent:setCursor(absX, absY, blink)
|
||||
return self.parent:setCursor(absX, absY, blink, color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
64
src/main.lua
64
src/main.lua
@@ -1,6 +1,7 @@
|
||||
local elementManager = require("elementManager")
|
||||
local errorManager = require("errorManager")
|
||||
local propertySystem = require("propertySystem")
|
||||
local expect = require("libraries/expect")
|
||||
|
||||
|
||||
--- This is the UI Manager and the starting point for your project. The following functions allow you to influence the default behavior of Basalt.
|
||||
@@ -123,27 +124,42 @@ function basalt.setActiveFrame(frame)
|
||||
mainFrame = frame
|
||||
end
|
||||
|
||||
--- Schedules a function to be updated
|
||||
--- @shortDescription Schedules a function to be updated
|
||||
--- Schedules a function to run in a coroutine
|
||||
--- @shortDescription Schedules a function to run in a coroutine
|
||||
--- @function scheduleUpdate
|
||||
--- @param func function The function to schedule
|
||||
--- @return number Id The schedule ID
|
||||
--- @return thread func The scheduled function
|
||||
--- @usage local id = basalt.scheduleUpdate(myFunction)
|
||||
function basalt.scheduleUpdate(func)
|
||||
table.insert(basalt._schedule, func)
|
||||
return #basalt._schedule
|
||||
function basalt.schedule(func)
|
||||
expect(1, func, "function")
|
||||
|
||||
local co = coroutine.create(func)
|
||||
local ok, result = coroutine.resume(co)
|
||||
if(ok)then
|
||||
table.insert(basalt._schedule, {coroutine=co, filter=result})
|
||||
else
|
||||
errorManager.header = "Basalt Schedule Error"
|
||||
errorManager.error(result)
|
||||
end
|
||||
return co
|
||||
end
|
||||
|
||||
--- Removes a scheduled update
|
||||
--- @shortDescription Removes a scheduled update
|
||||
--- @function removeSchedule
|
||||
--- @param id number The schedule ID to remove
|
||||
--- @param func thread The scheduled function to remove
|
||||
--- @return boolean success Whether the scheduled function was removed
|
||||
--- @usage basalt.removeSchedule(scheduleId)
|
||||
function basalt.removeSchedule(id)
|
||||
basalt._schedule[id] = nil
|
||||
function basalt.removeSchedule(func)
|
||||
for i, v in ipairs(basalt._schedule) do
|
||||
if(v.coroutine==func)then
|
||||
table.remove(basalt._schedule, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@private
|
||||
local function updateEvent(event, ...)
|
||||
if(event=="terminate")then basalt.stop() end
|
||||
if lazyElementsEventHandler(event, ...) then return end
|
||||
@@ -154,6 +170,19 @@ local function updateEvent(event, ...)
|
||||
end
|
||||
end
|
||||
|
||||
for _, func in ipairs(basalt._schedule) do
|
||||
if(event==func.filter)then
|
||||
local ok, result = coroutine.resume(func.coroutine, event, ...)
|
||||
if(not ok)then
|
||||
errorManager.header = "Basalt Schedule Error"
|
||||
errorManager.error(result)
|
||||
end
|
||||
end
|
||||
if(coroutine.status(func.coroutine)=="dead")then
|
||||
basalt.removeSchedule(func.coroutine)
|
||||
end
|
||||
end
|
||||
|
||||
if basalt._events[event] then
|
||||
for _, callback in ipairs(basalt._events[event]) do
|
||||
callback(...)
|
||||
@@ -161,22 +190,19 @@ local function updateEvent(event, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
local function renderFrames()
|
||||
if(mainFrame)then
|
||||
mainFrame:render()
|
||||
end
|
||||
end
|
||||
|
||||
--- Updates all scheduled functions
|
||||
--- @shortDescription Updates all scheduled functions
|
||||
--- Runs basalt once
|
||||
--- @shortDescription Runs basalt once
|
||||
--- @vararg any The event to run with
|
||||
--- @usage basalt.update()
|
||||
function basalt.update()
|
||||
for k,v in pairs(basalt._schedule) do
|
||||
if type(v)=="function" then
|
||||
v()
|
||||
end
|
||||
end
|
||||
function basalt.update(...)
|
||||
updateEvent(...)
|
||||
renderFrames()
|
||||
end
|
||||
|
||||
--- Stops the Basalt runtime
|
||||
|
||||
@@ -194,6 +194,9 @@ PropertySystem.addSetterHook(function(element, propertyName, value, config)
|
||||
end
|
||||
end)
|
||||
|
||||
--- This module provides reactive functionality for elements, it adds no new functionality for elements.
|
||||
--- It is used to evaluate expressions in property values and update the element when the expression changes.
|
||||
---@class Reactive
|
||||
local BaseElement = {}
|
||||
|
||||
BaseElement.hooks = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local colorChars = require("libraries/colorHex")
|
||||
local log = require("log")
|
||||
|
||||
--- This is the render module for Basalt. It tries to mimic the functionality of the `term` API. but with additional
|
||||
--- functionality. It also has a buffer system to reduce the number of calls
|
||||
@@ -13,6 +14,8 @@ local colorChars = require("libraries/colorHex")
|
||||
local Render = {}
|
||||
Render.__index = Render
|
||||
|
||||
local sub = string.sub
|
||||
|
||||
--- Creates a new Render object
|
||||
--- @param terminal table The terminal object to render to
|
||||
--- @return Render
|
||||
@@ -66,9 +69,9 @@ function Render:blit(x, y, text, fg, bg)
|
||||
error("Text, fg, and bg must be the same length")
|
||||
end
|
||||
|
||||
self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text)
|
||||
self.buffer.fg[y] = self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg)
|
||||
self.buffer.bg[y] = self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg)
|
||||
self.buffer.text[y] = sub(self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text), 1, self.width)
|
||||
self.buffer.fg[y] = sub(self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg), 1, self.width)
|
||||
self.buffer.bg[y] = sub(self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg), 1, self.width)
|
||||
self:addDirtyRect(x, y, #text, 1)
|
||||
|
||||
return self
|
||||
@@ -96,9 +99,9 @@ function Render:multiBlit(x, y, width, height, text, fg, bg)
|
||||
for dy=0, height-1 do
|
||||
local cy = y + dy
|
||||
if cy >= 1 and cy <= self.height then
|
||||
self.buffer.text[cy] = self.buffer.text[cy]:sub(1,x-1) .. text .. self.buffer.text[cy]:sub(x+#text)
|
||||
self.buffer.fg[cy] = self.buffer.fg[cy]:sub(1,x-1) .. fg .. self.buffer.fg[cy]:sub(x+#fg)
|
||||
self.buffer.bg[cy] = self.buffer.bg[cy]:sub(1,x-1) .. bg .. self.buffer.bg[cy]:sub(x+#bg)
|
||||
self.buffer.text[cy] = sub(self.buffer.text[cy]:sub(1,x-1) .. text .. self.buffer.text[cy]:sub(x+#text), 1, self.width)
|
||||
self.buffer.fg[cy] = sub(self.buffer.fg[cy]:sub(1,x-1) .. fg .. self.buffer.fg[cy]:sub(x+#fg), 1, self.width)
|
||||
self.buffer.bg[cy] = sub(self.buffer.bg[cy]:sub(1,x-1) .. bg .. self.buffer.bg[cy]:sub(x+#bg), 1, self.width)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -115,9 +118,10 @@ end
|
||||
function Render:textFg(x, y, text, fg)
|
||||
if y < 1 or y > self.height then return self end
|
||||
fg = colorChars[fg] or "0"
|
||||
fg = fg:rep(#text)
|
||||
|
||||
self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text)
|
||||
self.buffer.fg[y] = self.buffer.fg[y]:sub(1,x-1) .. fg:rep(#text) .. self.buffer.fg[y]:sub(x+#text)
|
||||
self.buffer.text[y] = sub(self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text), 1, self.width)
|
||||
self.buffer.fg[y] = sub(self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg), 1, self.width)
|
||||
self:addDirtyRect(x, y, #text, 1)
|
||||
|
||||
return self
|
||||
@@ -133,8 +137,8 @@ function Render:textBg(x, y, text, bg)
|
||||
if y < 1 or y > self.height then return self end
|
||||
bg = colorChars[bg] or "f"
|
||||
|
||||
self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text)
|
||||
self.buffer.bg[y] = self.buffer.bg[y]:sub(1,x-1) .. bg:rep(#text) .. self.buffer.bg[y]:sub(x+#text)
|
||||
self.buffer.text[y] = sub(self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text), 1, self.width)
|
||||
self.buffer.bg[y] = sub(self.buffer.bg[y]:sub(1,x-1) .. bg:rep(#text) .. self.buffer.bg[y]:sub(x+#text), 1, self.width)
|
||||
self:addDirtyRect(x, y, #text, 1)
|
||||
|
||||
return self
|
||||
@@ -148,7 +152,7 @@ end
|
||||
function Render:text(x, y, text)
|
||||
if y < 1 or y > self.height then return self end
|
||||
|
||||
self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text)
|
||||
self.buffer.text[y] = sub(self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text), 1, self.width)
|
||||
self:addDirtyRect(x, y, #text, 1)
|
||||
|
||||
return self
|
||||
@@ -162,7 +166,7 @@ end
|
||||
function Render:fg(x, y, fg)
|
||||
if y < 1 or y > self.height then return self end
|
||||
|
||||
self.buffer.fg[y] = self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg)
|
||||
self.buffer.fg[y] = sub(self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg), 1, self.width)
|
||||
self:addDirtyRect(x, y, #fg, 1)
|
||||
|
||||
return self
|
||||
@@ -176,7 +180,7 @@ end
|
||||
function Render:bg(x, y, bg)
|
||||
if y < 1 or y > self.height then return self end
|
||||
|
||||
self.buffer.bg[y] = self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg)
|
||||
self.buffer.bg[y] = sub(self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg), 1, self.width)
|
||||
self:addDirtyRect(x, y, #bg, 1)
|
||||
|
||||
return self
|
||||
@@ -230,6 +234,7 @@ function Render:render()
|
||||
self.buffer.dirtyRects = {}
|
||||
|
||||
if self.blink then
|
||||
self.terminal.setTextColor(self.cursorColor)
|
||||
self.terminal.setCursorPos(self.xCursor, self.yCursor)
|
||||
self.terminal.setCursorBlink(true)
|
||||
else
|
||||
@@ -271,12 +276,14 @@ end
|
||||
--- @param y number The y position of the cursor
|
||||
--- @param blink boolean Whether the cursor should blink
|
||||
--- @return Render
|
||||
function Render:setCursor(x, y, blink)
|
||||
function Render:setCursor(x, y, blink, color)
|
||||
if color ~= nil then self.terminal.setTextColor(color) end
|
||||
self.terminal.setCursorPos(x, y)
|
||||
self.terminal.setCursorBlink(blink)
|
||||
self.xCursor = x
|
||||
self.yCursor = y
|
||||
self.blink = blink
|
||||
self.cursorColor = color
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -306,4 +313,15 @@ function Render:getSize()
|
||||
return self.width, self.height
|
||||
end
|
||||
|
||||
function Render:setSize(width, height)
|
||||
self.width = width
|
||||
self.height = height
|
||||
for y=1, self.height do
|
||||
self.buffer.text[y] = string.rep(" ", self.width)
|
||||
self.buffer.fg[y] = string.rep("0", self.width)
|
||||
self.buffer.bg[y] = string.rep("f", self.width)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return Render
|
||||
Reference in New Issue
Block a user