Newer
Older
Radon / components / TextInput.lua
@Alyssa May Alyssa May on 3 Jul 2023 10 KB official ShopSync support
local Solyd = require("modules.solyd")
local hooks = require("modules.hooks")
local useBoundingBox = hooks.useBoundingBox
local useInput = hooks.useInput

local BasicText = require("components.BasicText")

local function numToHex(num)
    return "#" .. string.format("%06x", num)
end

return Solyd.wrapComponent("TextInput", function(props)
    --print("Test")
    -- local canvas = Solyd.useContext("canvas")
    -- local canvas = useCanvas()

    if props.inputState.value and type(props.inputState.value) == "number" then
        if props.type == "number" then
            props.inputState.value = tostring(props.inputState.value)
        elseif props.type == "colorpicker" then
            props.inputState.value = numToHex(props.inputState.value)
        end
    end
    if not props.inputState.value then
        props.inputState.value = ""
    end
    local inputState, setInputState = Solyd.useState(props.inputState)
    if not inputState.prevValue then
        inputState.prevValue = inputState.value
        --setInputState(inputState)
    end
    if not inputState.active then
        inputState.active = false
        --setInputState(inputState)
    end
    inputState.cursorY = props.y
    if not inputState.cursorPos then
        inputState.cursorPos = #inputState.value + 1
        inputState.viewPort = 1
        inputState.cursorX = props.x + inputState.cursorPos - 1
        --setInputState(inputState)
    end

    function addChar(char)
        if props.type == "number" then
            if char == "." then
                if inputState.value:find("%.") then
                    return
                end
            elseif char == "-" then
                if inputState.cursorPos ~= 1 or inputState.value:find("%-") then
                    return
                end
            elseif char:match("%D") then
                return
            end
        elseif props.type == "colorpicker" then
            if char == "x" then
                if inputState.cursorPos == 1 and inputState.value:find("x") then
                    return
                elseif inputState.cursorPos == 2 and (inputState.value:sub(1, 1) ~= "0" or inputState.value:find("x")) then
                    return
                elseif inputState.cursorPos >= 3 then
                    return
                end
            elseif char == "#" then
                if inputState.cursorPos == 1 and inputState.value:find("#") then
                    return
                elseif inputState.cursorPos >= 2 then
                    return
                end
            elseif char:match("%X") then
                return
            else
                if inputState.cursorPos == 1 and (inputState.value:find("x") or inputState.value:find("#")) then
                    return
                elseif inputState.cursorPos == 2 and (inputState.value:sub(2, 2) == "x") then
                    return
                end
            end
        end
        inputState.value = inputState.value:sub(1, inputState.cursorPos-1) .. char .. inputState.value:sub(inputState.cursorPos)
        inputState.cursorPos = inputState.cursorPos + 1
        inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
        if inputState.cursorPos > inputState.viewPort + props.width - 1 then
            inputState.viewPort = inputState.viewPort + 1
        end
        setInputState(inputState)
    end

    return BasicText {
        display = props.display,
        align = props.align,
        text = inputState.value:sub(inputState.viewPort, inputState.viewPort + props.width - 1),
        x = props.x,
        y = props.y,
        bg = props.bg,
        color = props.color,
        width = props.width,
    },
---@diagnostic disable-next-line: redundant-return-value
    {
        -- canvas = canvas,
        aabb = useBoundingBox((props.x*2)-1, (props.y*3)-2, (props.width)*2, (props.height)*3,
            function() -- onClick
                inputState.active = true
                inputState.viewPort = math.max(1, inputState.cursorPos - props.width + 1)
                inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                setInputState(inputState)
            end,
            function(dir) -- onScroll
                if props.type == "number" and inputState.value and inputState.value ~= "" then
                    inputState.value = tostring(tonumber(inputState.value) - dir)
                    setInputState(inputState)
                    if props.onChange then
                        if props.type == "number" and inputState.value ~= nil then
                            props.onChange(tonumber(inputState.value))
                        else
                            props.onChange(inputState.value)
                        end
                    end
                    return true
                end
            end),
        input = useInput(props.x, props.y, props.width, props.height, inputState, addChar,
            function(key, held)
                if key == keys.backspace then
                    if inputState.cursorPos > 1 then
                        inputState.value = inputState.value:sub(1, inputState.cursorPos-2) .. inputState.value:sub(inputState.cursorPos)
                        inputState.cursorPos = inputState.cursorPos - 1
                        inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                        if inputState.cursorPos < inputState.viewPort then
                            inputState.viewPort = inputState.viewPort - 1
                        end
                        setInputState(inputState)
                    end
                elseif key == keys.delete then
                    if inputState.cursorPos < #inputState.value + 1 then
                        inputState.value = inputState.value:sub(1, inputState.cursorPos-1) .. inputState.value:sub(inputState.cursorPos+1)
                        setInputState(inputState)
                    end
                elseif key == keys.enter then
                    inputState.active = false
                    inputState.viewPort = 1
                    if inputState.value ~= inputState.prevValue then
                        if props.onChange then
                            if props.type == "number" and inputState.value ~= nil then
                                props.onChange(tonumber(inputState.value))
                            elseif props.type == "colorpicker" and inputState.value ~= nil then
                                -- Convert hex to number
                                local hex = inputState.value
                                hex = hex:gsub("#", "")
                                hex = hex:gsub("x", "")
                                props.onChange(tonumber(hex, 16))
                            else
                                props.onChange(inputState.value)
                            end
                        end
                        inputState.prevValue = inputState.value
                    end
                    setInputState(inputState)
                elseif key == keys.left then
                    if inputState.cursorPos > 1 then
                        inputState.cursorPos = inputState.cursorPos - 1
                        if inputState.cursorPos < inputState.viewPort then
                            inputState.viewPort = inputState.viewPort - 1
                        end
                        inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                        setInputState(inputState)
                    end
                elseif key == keys.right then
                    if inputState.cursorPos < #inputState.value + 1 then
                        inputState.cursorPos = inputState.cursorPos + 1
                        if inputState.cursorPos > inputState.viewPort + props.width - 1 then
                            inputState.viewPort = inputState.viewPort + 1
                        end
                        inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                        setInputState(inputState)
                    end
                elseif key == keys.home then
                    inputState.cursorPos = 1
                    inputState.viewPort = 1
                    inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                    setInputState(inputState)
                elseif key == keys["end"] then
                    inputState.cursorPos = #inputState.value + 1
                    inputState.viewPort = math.max(1, inputState.cursorPos - props.width + 1)
                    inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                    setInputState(inputState)
                end
            end,
            function()
                -- On blur
                if inputState.value ~= inputState.prevValue then
                    if props.onChange then
                        if props.type == "number" and inputState.value ~= nil then
                            props.onChange(tonumber(inputState.value))
                        else
                            props.onChange(inputState.value)
                        end
                    end
                    inputState.prevValue = inputState.value
                end
                setInputState(inputState)
            end,
            function(contents)
                -- On paste
                if props.type == "number" then
                    if contents:sub(1, 1) == "-" then
                        contents = "-" .. contents:gsub("[^%d]", "")
                    else
                        contents = contents:gsub("[^%d]", "")
                    end
                elseif props.type == "colorpicker" then
                    if contents:sub(1, 1) ~= "#" or contents:sub(1,2):find("x") then
                        contents = "#" .. contents:gsub("[^%x]", "")
                    else
                        contents:gsub("[^%x]", "")
                    end
                end
                inputState.value = inputState.value:sub(1, inputState.cursorPos-1) .. contents .. inputState.value:sub(inputState.cursorPos)
                inputState.cursorPos = inputState.cursorPos + #contents
                if inputState.cursorPos > inputState.viewPort + props.width - 1 then
                    inputState.viewPort = inputState.cursorPos - props.width + 2
                end
                inputState.cursorX = props.x + inputState.cursorPos - inputState.viewPort
                setInputState(inputState)
            end
        ),
    }
end)