Newer
Older
Radon / DefaultLayout.lua
@Alyssa May Alyssa May on 5 Jul 2023 23 KB Option to hide negative quantity prices
--- Imports
local _ = require("util.score")
local sound = require("util.sound")
local eventHook = require("util.eventHook")
local renderHelpers = require("util.renderHelpers")

local Display = require("modules.display")

local Solyd = require("modules.solyd")
local hooks = require("modules.hooks")
local useCanvas = hooks.useCanvas

local Button = require("components.Button")
local BasicButton = require("components.BasicButton")
local SmolButton = require("components.SmolButton")
local BigText = require("components.BigText")
local bigFont = require("fonts.bigfont")
local SmolText = require("components.SmolText")
local smolFont = require("fonts.smolfont")
local BasicText = require("components.BasicText")
local Rect = require("components.Rect")
local RenderCanvas = require("components.RenderCanvas")
local Core = require("core.ShopState")
local Pricing = require("core.Pricing")
local ShopRunner = require("core.ShopRunner")
local ConfigValidator = require("core.ConfigValidator")


local loadRIF = require("modules.rif")

local function render(canvas, display, props, theme, version)
    local elements = {}
    local selectedProduct, setSelectedProduct = Solyd.useState("")
    local categories = renderHelpers.getCategories(props.shopState.products)
    local selectedCategory = props.shopState.selectedCategory

    local currencyEndX = 3
    if #props.configState.config.currencies > 1 then
        for i = 1, #props.configState.config.currencies do
            local symbol = renderHelpers.getCurrencySymbol(props.configState.config.currencies[i], "large")
            local symbolSize = bigFont:getWidth(symbol)+6
            currencyEndX = currencyEndX + symbolSize + 2
        end
    end

    local categoryX = display.bgCanvas.width - 2
    if #categories > 1 then
        for i = #categories, 1, -1 do
            local category = categories[i]
            local categoryName = category.name
            if i == selectedCategory then
                categoryName = "[" .. categoryName .. "]"
            end
            local categoryWidth = smolFont:getWidth(categoryName)+6
            categoryX = categoryX - categoryWidth - 2
        end
    end

    local headerCx = math.floor((display.bgCanvas.width - bigFont:getWidth(props.configState.config.branding.title)) / 2)
    local header
    -- TODO: Change header font size based on width
    local headerHeight = 15
    if theme.formatting.headerAlign == "center" and headerCx < currencyEndX and #categories == 1 then
        table.insert(elements, Rect { display=display, x=1, y=1, width=currencyEndX, height=bigFont.height+6, color=theme.colors.headerBgColor })
        header = BigText { display=display, text=props.configState.config.branding.title, x=currencyEndX, y=1, align="left", bg=theme.colors.headerBgColor, color = theme.colors.headerColor, width=display.bgCanvas.width }
    elseif theme.formatting.headerAlign == "center" and headerCx+bigFont:getWidth(props.configState.config.branding.title) > categoryX and #categories > 1 then
        table.insert(elements, Rect { display=display, x=categoryX, y=1, width=display.bgCanvas.width-categoryX+1, height=bigFont.height+6, color=theme.colors.headerBgColor })
        header = BigText { display=display, text=props.configState.config.branding.title, x=1, y=1, align="right", bg=theme.colors.headerBgColor, color = theme.colors.headerColor, width=categoryX-1 }
    else
        header = BigText { display=display, text=props.configState.config.branding.title, x=1, y=1, align=theme.formatting.headerAlign, bg=theme.colors.headerBgColor, color = theme.colors.headerColor, width=display.bgCanvas.width }
    end
    if props.configState.config.branding.subtitle then
        table.insert(elements, BasicText {
            display = display,
            text = props.configState.config.branding.subtitle,
            x = 1,
            y = 6,
            align = theme.formatting.subtitleAlign,
            bg = theme.colors.subtitleBgColor,
            color = theme.colors.subtitleColor,
            width = math.floor(display.bgCanvas.width / 2)
        })
        headerHeight = headerHeight + 3
    end

    table.insert(elements, header)

    local footerHeight = 0
    if props.configState.config.settings.showFooter then
        local footerMessage
        if props.shopState.selectedCurrency.name or not props.configState.config.lang.footerNoName then
            footerMessage = props.configState.config.lang.footer
        else
            footerMessage = props.configState.config.lang.footerNoName
        end
        if props.shopState.selectedCurrency.name and footerMessage:find("%%name%%") then
            footerMessage = footerMessage:gsub("%%name%%", props.shopState.selectedCurrency.name)
        end
        if footerMessage:find("%%addr%%") and props.shopState.selectedCurrency.host then
            footerMessage = footerMessage:gsub("%%addr%%", props.shopState.selectedCurrency.host)
        end
        if footerMessage:find("%%version%%") then
            footerMessage = footerMessage:gsub("%%version%%", version)
        end
        if selectedProduct and #selectedProduct > 0 and footerMessage:find("<item>") then
            footerMessage = footerMessage:gsub("<item>", selectedProduct)
        end

        if props.shopState.selectedCurrency then
            local footer
            local footerSize = theme.formatting.footerSize
            if footerSize == "auto" and bigFont:getWidth(footerMessage) < display.bgCanvas.width then
                footerSize = "large"
            elseif footerSize == "auto" and smolFont:getWidth(footerMessage) < display.bgCanvas.width then
                footerSize = "medium"
            elseif footerSize == "auto" then
                footerSize = "small"
            end
            if footerSize == "large" then
                footer = BigText { display=display, text=footerMessage, x=1, y=display.bgCanvas.height-bigFont.height-5, align=theme.formatting.footerAlign, bg=theme.colors.footerBgColor, color = theme.colors.footerColor, width=display.bgCanvas.width }
                footerHeight = smolFont.height + 6
            elseif footerSize == "medium" then
                footer = SmolText { display=display, text=footerMessage, x=1, y=display.bgCanvas.height-smolFont.height-2, align=theme.formatting.footerAlign, bg=theme.colors.footerBgColor, color = theme.colors.footerColor, width=display.bgCanvas.width }
                footerHeight = smolFont.height + 4
            else
                footer = BasicText { display=display, text=footerMessage, x=1, y=math.floor(display.bgCanvas.height/3), align=theme.formatting.footerAlign, bg=theme.colors.footerBgColor, color = theme.colors.footerColor, width=math.ceil(display.bgCanvas.width/2) }
                footerHeight = smolFont.height + 4
            end
            table.insert(elements, footer)
        end
    end

    local maxAddrWidth = 0
    local maxQtyWidth = 0
    local maxPriceWidth = 0
    local maxNameWidth = 0
    props.shopState.numCategories = #categories
    local catName = "*"
    local shopProducts = {}
    if categories[selectedCategory] then
        catName = categories[selectedCategory].name
        shopProducts = renderHelpers.getDisplayedProducts(categories[selectedCategory].products, props.configState.config.settings, props.shopState.selectedCurrency)
    end
    local productsHeight = display.bgCanvas.height - headerHeight - footerHeight - 2
    local heightPerProduct = math.floor(productsHeight / #shopProducts)
    local layout
    if theme.formatting.layout == "auto" then
        if heightPerProduct >= 15 then
            layout = "large"
        elseif heightPerProduct >= 9 then
            layout = "medium"
        else
            layout = "small"
       end
    else
        layout = theme.formatting.layout
    end

    local currency = props.shopState.selectedCurrency
    local currencySymbol = renderHelpers.getCurrencySymbol(currency, layout)
    while #shopProducts > 0 and (maxAddrWidth == 0 or maxAddrWidth + maxQtyWidth + maxPriceWidth + maxNameWidth > display.bgCanvas.width - 3) do
        if props.configState.config.theme.formatting.layout == "auto" and (maxAddrWidth + maxQtyWidth + maxPriceWidth + maxNameWidth > display.bgCanvas.width - 3) then
            if layout == "large" then
                layout = "medium"
                maxAddrWidth = 0
                maxQtyWidth = 0
                maxPriceWidth = 0
                maxNameWidth = 0
            elseif layout == "medium" then
                layout = "small"
                maxAddrWidth = 0
                maxQtyWidth = 0
                maxPriceWidth = 0
                maxNameWidth = 0
            end
        end
        currencySymbol = renderHelpers.getCurrencySymbol(currency, layout)
        for i = 1, #shopProducts do
            local product = shopProducts[i]
            local productAddr = product.address .. "@"
            if props.shopState.selectedCurrency.name then
                if layout == "small" then
                    if props.configState.config.settings.smallTextKristPayCompatability then
                        productAddr = product.address .. "@" .. props.shopState.selectedCurrency.name
                    else
                        productAddr = product.address .. "@ "
                    end
                end
            else
                productAddr = product.address
            end
            product.quantity = product.quantity or 0
            local productPrice = Pricing.getProductPrice(product, props.shopState.selectedCurrency)
            if layout == "large" then
                maxAddrWidth = math.max(maxAddrWidth, renderHelpers.getWidth(productAddr, layout)+2)
                maxQtyWidth = math.max(maxQtyWidth, renderHelpers.getWidth(tostring(product.quantity), layout)+4+2)
                maxPriceWidth = math.max(maxPriceWidth, renderHelpers.getWidth(tostring(productPrice) .. currencySymbol, layout)+2)
                maxNameWidth = math.max(maxNameWidth, renderHelpers.getWidth(product.name, layout)+2)
            elseif layout == "medium" then
                maxAddrWidth = math.max(maxAddrWidth, renderHelpers.getWidth(productAddr, layout)+2)
                maxQtyWidth = math.max(maxQtyWidth, renderHelpers.getWidth(tostring(product.quantity), layout)+4+2)
                maxPriceWidth = math.max(maxPriceWidth, renderHelpers.getWidth(tostring(productPrice) .. currencySymbol, layout)+2)
                maxNameWidth = math.max(maxNameWidth, renderHelpers.getWidth(product.name, layout)+2)
            else
                maxAddrWidth = math.max(maxAddrWidth, renderHelpers.getWidth(productAddr, layout)+1)
                maxQtyWidth = math.max(maxQtyWidth, renderHelpers.getWidth(tostring(product.quantity), layout)+2)
                maxPriceWidth = math.max(maxPriceWidth, renderHelpers.getWidth(tostring(productPrice) .. currencySymbol, layout)+1)
                maxNameWidth = math.max(maxNameWidth, renderHelpers.getWidth(product.name, layout)+1)
            end
        end
        if props.configState.config.theme.formatting.layout ~= "auto" or layout == "small" then
            break
        end
    end
    local startY = headerHeight + 1
    local startTextY = math.ceil(headerHeight / 3) + 1
    for i = 1, #shopProducts do
        local product = shopProducts[i]
        -- Display products in format:
        -- <quantity> <name> <price> <address>
        product.quantity = product.quantity or 0
        local productPrice = Pricing.getProductPrice(product, props.shopState.selectedCurrency)
        local qtyColor = theme.colors.normalQtyColor
        if product.quantity == 0 then
            qtyColor = theme.colors.outOfStockQtyColor
        elseif product.quantity < 10 then
            qtyColor = theme.colors.lowQtyColor
        elseif product.quantity < 64 then
            qtyColor = theme.colors.warningQtyColor
        end
        local productNameColor = theme.colors.productNameColor
        if product.quantity == 0 then
            productNameColor = theme.colors.outOfStockNameColor
        end
        local productAddr = product.address .. "@"
        if props.shopState.selectedCurrency.name then
            if layout == "small" then
                if props.configState.config.settings.smallTextKristPayCompatability then
                    productAddr = product.address .. "@" .. props.shopState.selectedCurrency.name
                else
                    productAddr = product.address .. "@ "
                end
            end
        else
            productAddr = product.address
        end
        local kristpayHelperText = props.shopState.selectedCurrency.host or ""
        if props.shopState.selectedCurrency.name then
            kristpayHelperText = product.address .. "@" .. props.shopState.selectedCurrency.name
        end
        local productBgColor = theme.colors.productBgColors[((i-1) % #theme.colors.productBgColors) + 1]
        local productClickFunction = function()
            setSelectedProduct(product.address)
            if props.configState.eventHooks and props.configState.eventHooks.onProductSelected then
                eventHook.execute(props.configState.eventHooks.onProductSelected, product, currency)
            end
            if props.configState.config.settings.playSounds then
                sound.playSound(props.peripherals.speaker, props.configState.config.sounds.button)
            end
        end
        if layout == "large" then
            table.insert(elements, Button {
                key="qty-"..catName..tostring(product.id),
                display=display,
                text=tostring(product.quantity),
                x=1,
                y=startY+((i-1)*15),
                align="center",
                bg=productBgColor,
                color=qtyColor,
                width=maxQtyWidth,
                onClick=productClickFunction
            })
            table.insert(elements, Button {
                key="name-"..catName..tostring(product.id),
                display=display,
                text=product.name,
                x=maxQtyWidth+1,
                y=startY+((i-1)*15),
                align=theme.formatting.productNameAlign,
                bg=productBgColor,
                color=productNameColor,
                width=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth-maxQtyWidth,
                onClick=productClickFunction
            })
            table.insert(elements, Button {
                key="price-"..catName..tostring(product.id),
                display=display,
                text=tostring(productPrice) .. currencySymbol,
                x=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth,
                y=startY+((i-1)*15),
                align="right",
                bg=productBgColor,
                color=theme.colors.priceColor,
                width=maxPriceWidth,
                onClick=productClickFunction
            })
            table.insert(elements, Button {
                key="addr-"..catName..tostring(product.id),
                display=display,
                text=productAddr,
                x=display.bgCanvas.width-3-maxAddrWidth,
                y=startY+((i-1)*15),
                align="right",
                bg=productBgColor,
                color=theme.colors.addressColor,
                width=maxAddrWidth+4,
                onClick=productClickFunction
            })
            table.insert(elements, BasicText {
                key="invis-" .. catName .. tostring(product.id),
                display=display,
                text=kristpayHelperText,
                x=1,
                y=startTextY+((i-1)*5),
                align="center",
                bg=productBgColor,
                color=productBgColor,
                width=#(kristpayHelperText)
            })
        elseif layout == "medium" then
            table.insert(elements, SmolButton {
                key="qty-"..catName..tostring(product.id),
                display=display,
                text=tostring(product.quantity),
                x=1,
                y=startY+((i-1)*9),
                align="center",
                bg=productBgColor,
                color=qtyColor,
                width=maxQtyWidth,
                onClick=productClickFunction
            })
            table.insert(elements, SmolButton {
                key="name-"..catName..tostring(product.id),
                display=display,
                text=product.name,
                x=maxQtyWidth+1,
                y=startY+((i-1)*9),
                align=theme.formatting.productNameAlign,
                bg=productBgColor,
                color=productNameColor,
                width=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth-maxQtyWidth,
                onClick=productClickFunction
            })
            table.insert(elements, SmolButton {
                key="price-"..catName..tostring(product.id),
                display=display,
                text=tostring(productPrice) .. currencySymbol,
                x=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth,
                y=startY+((i-1)*9),
                align="right",
                bg=productBgColor,
                color=theme.colors.priceColor,
                width=maxPriceWidth,
                onClick=productClickFunction
            })
            table.insert(elements, SmolButton { 
                ey="addr-"..catName..tostring(product.id),
                display=display,
                text=productAddr,
                x=display.bgCanvas.width-3-maxAddrWidth,
                y=startY+((i-1)*9),
                align="right",
                bg=productBgColor,
                color=theme.colors.addressColor,
                width=maxAddrWidth+4,
                onClick=productClickFunction
            })
            table.insert(elements, BasicText {
                key="invis-" .. catName .. tostring(product.id),
                display=display,
                text=kristpayHelperText,
                x=1,
                y=startTextY+((i-1)*3),
                align="center",
                bg=productBgColor,
                color=productBgColor,
                width=#(kristpayHelperText)
            })
        else
            table.insert(elements, BasicButton {
                key="qty-"..catName..tostring(product.id),
                display=display,
                text=tostring(product.quantity),
                x=1,
                y=startTextY+((i-1)*1),
                align="center",
                bg=productBgColor,
                color=qtyColor,
                width=maxQtyWidth,
                onClick=productClickFunction
            })
            table.insert(elements, BasicButton {
                key="name-"..catName..tostring(product.id),
                display=display,
                text=product.name,
                x=maxQtyWidth+1,
                y=startTextY+((i-1)*1),
                align=theme.formatting.productNameAlign,
                bg=productBgColor,
                color=productNameColor,
                width=(display.bgCanvas.width/2)-1-maxAddrWidth-maxPriceWidth-maxQtyWidth,
                onClick=productClickFunction
            })
            table.insert(elements, BasicButton {
                key="price-"..catName..tostring(product.id),
                display=display,
                text=tostring(productPrice) .. currencySymbol,
                x=(display.bgCanvas.width/2)-1-maxAddrWidth-maxPriceWidth,
                y=startTextY+((i-1)*1),
                align="right",
                bg=productBgColor,
                color=theme.colors.priceColor,
                width=maxPriceWidth,
                onClick=productClickFunction
            })
            table.insert(elements, BasicButton {
                key="addr-"..catName..tostring(product.id),
                display=display,
                text=productAddr,
                x=(display.bgCanvas.width/2)-1-maxAddrWidth,
                y=startTextY+((i-1)*1),
                align="right",
                bg=productBgColor,
                color=theme.colors.addressColor,
                width=maxAddrWidth+2,
                onClick=productClickFunction
            })
        end
    end

    local currencyX = 3
    if #props.configState.config.currencies > 1 then
        for i = 1, #props.configState.config.currencies do
            local symbol = renderHelpers.getCurrencySymbol(props.configState.config.currencies[i], "large")
            local symbolSize = bigFont:getWidth(symbol)+6+1
            local bgColor = theme.colors.currencyBgColors[((i-1) % #theme.colors.currencyBgColors) + 1]
            table.insert(elements, Button {
                display = display,
                align = "center",
                text = symbol,
                x = currencyX,
                y = 1,
                bg = bgColor,
                color = theme.colors.currencyTextColor,
                width = symbolSize,
                onClick = function()
                    props.shopState.selectedCurrency = props.configState.config.currencies[i]
                    props.shopState.lastTouched = os.epoch("utc")
                    if props.configState.config.settings.playSounds then
                        sound.playSound(props.peripherals.speaker, props.configState.config.sounds.button)
                    end
                end
            })
            currencyX = currencyX + symbolSize + 2
        end
    end

    local categoryX = display.bgCanvas.width - 2
    if #categories > 1 then
        for i = #categories, 1, -1 do
            local category = categories[i]
            local categoryName = category.name
            local categoryColor
            if i == selectedCategory then
                categoryColor = theme.colors.activeCategoryColor
                categoryName = "[" .. categoryName .. "]"
            else
                categoryColor = theme.colors.categoryBgColors[((i-1) % #theme.colors.categoryBgColors) + 1]
            end
            local categoryWidth = smolFont:getWidth(categoryName)+6
            categoryX = categoryX - categoryWidth - 2

            table.insert(elements, SmolButton {
                display = display,
                align = "center",
                text = categoryName,
                x = categoryX,
                y = 4,
                bg = categoryColor,
                color = theme.colors.categoryTextColor,
                width = categoryWidth,
                onClick = function()
                    props.shopState.selectedCategory = i
                    setSelectedProduct("")
                    props.shopState.lastTouched = os.epoch("utc")
                    if props.configState.config.settings.playSounds then
                        sound.playSound(props.peripherals.speaker, props.configState.config.sounds.button)
                    end
                    -- canvas:markRect(1, 16, canvas.width, canvas.height-16)
                end
            })
        end
    end
    return elements
end

return render