jille_Jr icon

CC: Cannon mail [Client] - gui.lua

jille_Jr | PRO | 02/22/16 07:23:51 PM UTC | 0 ⭐ | 467 👁️ | Never ⏰ | []
Lua |

19.57 KB

|

None

|

0 👍

/

0 👎

-- gui.lua
 
--(( Settings ))--
 
--(( Variables ))--
 
local termW,termH = term.getSize()
local pageW,pageH = 25,21
 
local lockedSender = "kalle"
 
local grid = {}
 
--(( Functions ))--
 
function updateGrid()
    -- Add lost parts
    for x = 1,termW do
        if type(grid[x]) ~= "table" then
            grid[x] = {}
        end
        for y = 1,termH do
            if type(grid[x][y]) ~= "table" then
                grid[x][y] = {
                    fg = util.colorToHex(colors.white),
                    bg = util.colorToHex(colors.black),
                    char = " ",
                }
            end
        end
    end
 
    -- Remove excess
    for x in pairs(grid) do
        if type(x) ~= "number"
        or (x < 1 or x > termW) then
            grid[x] = nil
        else
            for y in pairs(grid[x]) do
                if type(y) ~= "number"
                or (y < 1 or y > termH) then
                    grid[x][y] = nil
                end
            end
        end
    end
end
 
-- Get each row seperatly
-- so you can draw it with term.blit
function blitReadyGrid()
    local rows = {}
    -- note that I'm looping Y first
    for y = 1,termH do
        local row = {"","",""}
        for x = 1,termW do
            local pixel = getPixel(x,y)
            row[1] = row[1] .. pixel.char
            row[2] = row[2] .. pixel.fg
            row[3] = row[3] .. pixel.bg
        end
        rows[y] = row
    end
 
    return rows
end
 
function drawGrid()
    updateGrid()
    local rows = blitReadyGrid()
    for y,row in ipairs(rows) do
        term.setCursorPos(1,y)
        term.blit(unpack(row))
    end
end
 
function validPixel(x,y)
    return (x >= 1 and x <= termW)
    and (y >= 1 and y <= termH)
    and (x%1==0) and (y%1==0)
end
 
function getPixel( x,y )
    if validPixel(x,y) then
        return grid[x][y]
    end
end
 
function setPixel(char,x,y,fg,bg)
    if type(fg) == "number" then fg = util.colorToHex(fg) end
    if type(bg) == "number" then bg = util.colorToHex(bg) end
 
    if validPixel(x,y) then
        grid[x][y].char = ((char or grid[x][y].char or "") .. " "):sub(1,1)
        grid[x][y].fg = fg or grid[x][y].fg
        grid[x][y].bg = bg or grid[x][y].bg
    end
end
 
function setRect(char,x1,y1,x2,y2,fg,bg)
    for x = x1,x2 or x1 do
        for y = y1,y2 or y1 do
            setPixel(char,x,y,fg,bg)
        end
    end
end
 
function writeText(text,x,y,fg,bg)
    text = tostring(text)
    for i = 1,#text do
        setPixel(text:sub(i,i),x+i-1,y,fg,bg)
    end
end
 
function writeCentered(text,y,fg,bg)
    writeText(text,math.floor(termW/2-#text/2),y,fg,bg)
end
 
function previewMail(data)
    local tPages = mail.splitPages(mail.prepareMessage(data))
    local nPage = 1
    local pageTitle = mail.getTitle(data,nPage,#tPages)
 
    local scroll = -1
    local scrollbarH = 4
 
    local pageX = 20
    local pageY = 3
 
    -- height of visable area
    local viewH = 17
 
    local running = true
    local redraw = true
 
    local function changePage(newPage)
        nPage = math.max(1,math.min(#tPages, newPage))
        scroll = -1
        redraw = true
        pageTitle = mail.getTitle(data,nPage,#tPages)
    end
 
    local function nextPage() changePage(nPage+1) end
    local function prevPage() changePage(nPage-1) end
 
    local function draw()
        -- Draw header
        setRect(" ",1,1,termW,2,_,colors.cyan)
        writeCentered("Preview of",1,colors.white)
        writeCentered(pageTitle,2,colors.white)
 
        -- Draw background
        setRect(" ",1,3,termW,termH,_,colors.lightBlue)
        -- paper edge
        local y2 = scroll == 5 and termH-1 or termH
        setRect("|",pageX-1,3,_,y2,colors.lightBlue,colors.white)
        setRect("|",pageX+pageW,3,_,y2,colors.lightBlue,colors.white)
 
        -- Draw message
        local tPage = tPages[nPage]
        for nRow = 1+scroll, termH-2+scroll do
            -- filler char
            if nRow == 0 then
                -- row before page
                setRect("~",pageX,3,pageX+pageW-1,_,colors.lightBlue,colors.white)
            elseif nRow == pageH+1 then
                -- row after page
                setRect("~",pageX,termH,pageX+pageW-1,_,colors.white,colors.lightBlue)
            else
                -- get page
                sRow = tPage[nRow] or ""
                -- fill out so it redraws the entire thing
                sRow = sRow .. string.rep(" ", pageW - #sRow)
 
                -- write it out
                local y = pageY+nRow-scroll-1
                writeText(sRow,pageX,y,colors.gray,colors.white)
            end
        end
 
        -- Draw scrollbar
        local y = math.floor(pageY+(viewH-scrollbarH)*(scroll+1)/(6))
        setRect(" ", termW, 3, _, termH, _, colors.lightGray)
        setRect(" ", termW, y, _, y+scrollbarH-1, _, colors.gray)
        setPixel("^", termW, y, colors.lightGray)
        setPixel("v", termW, y+scrollbarH-1,colors.lightGray)
 
        -- Draw buttons
        setRect(" ", 1, pageY, 13, termH, _, colors.gray)
        writeText("Page", 2, termH-4, colors.lightGray)
        local txt = nPage.."/"..#tPages
        writeText(txt, 13-#txt, termH-4, colors.lightGray)
        writeText("  <  ", 2, termH-3, colors.white, (nPage > 1) and colors.cyan or colors.lightGray)
        writeText("  >  ", 8, termH-3, colors.white, (nPage < #tPages) and colors.cyan or colors.lightGray)
 
        writeText("   Back    ", 2, termH-1, colors.white, colors.cyan)
 
        -- Draw to screen
        drawGrid()
    end
 
    local function changeScroll(delta)
        scroll = math.min(math.max(math.floor(scroll + delta),-1),pageH-viewH+1)
        redraw = true
    end
 
    local function handleEvents(ev,p1,p2,p3,p4,p5)
        if ev == "key" then
            if p1 == keys.down then
                changeScroll(1)
            elseif p1 == keys.up then
                changeScroll(-1)
            elseif p1 == keys.q then
                running = false
            end
        end
        if ev == "mouse_scroll" then
            changeScroll(p1)
        end
        if ev == "mouse_click" and p1 == 1 then
            if p3 == termH-3 then
                -- prev page
                if p2 >= 2 and p2 <= 6 and nPage > 1 then
                    prevPage()
                end
                -- next page
                if p2 >= 8 and p2 <= 12 and nPage < #tPages then
                    nextPage()
                end
            end
            -- exit
            if p3 == termH-1 and p2 >= 2 and p2 <= 12 then
                running = false
            end
        end
    end
 
    -- Main loop
    while running do
        if redraw then
            draw()
        end
        handleEvents(os.pullEvent())
    end
end
 
function writeMail()
 
    local running = true
    local redraw = true
    local selected = false
    local fields = {}
 
    local lshift = false
    local rshift = false
    local rctrl = false
    local lctrl = false
    local tabJump = false
 
    local diskAvailable = false
    local anyReceivers = false
 
    local function newTextArea(x1,y1,x2,y2,multiline)
        x2 = x2 or x1
        y2 = y2 or y1
 
        local textArea = {
            x1 = x1, x2 = x2,
            y1 = y1, y2 = y2,
            w = x2 - x1 + 1, h = y2 - y1 + 1,
            multiline = multiline,
            scrollX = 0, scrollY = 0,
            cursorX = 1, cursorY = 1,
            visableCursor = false,
            enabled = true,
 
            onSelect = false,
            onDeselect = false,
            lateDraw = false,
            elemType = "textArea",
 
            nextElem = false,
            prevElem = false,
 
            text = {""}
        }
        table.insert(fields,textArea)
 
        function textArea.destroy(self)
            for i,v in ipairs(fields) do
                if v == self then
                    table.remove(fields, i)
                    break
                end
            end
        end
 
        function textArea.localToWorld(self,x,y)
            return x + self.x1 - self.scrollX - 1, y + self.y1 - self.scrollY - 1
        end
 
        function textArea.worldtoLocal(self,x,y)
            return x - self.x1 + self.scrollX + 1, y - self.y1 + self.scrollY + 1
        end
 
        function textArea.pointInArea(self,x,y)
            return x >= self.x1 and x <= self.x2
            and y >= self.y1 and y <= self.y2
        end
 
        function textArea.draw(self)
            local bg = self==selected and colors.black or colors.gray
            local fg = self==selected and colors.white or colors.lightGray
 
            if self.elemType == "button" then
                bg = self.enabled and colors.cyan or colors.gray
                fg = self.enabled and colors.white or colors.lightGray
            end
 
            setRect(" ",self.x1,self.y1,self.x2,self.y2,_,bg)
 
            for row = 1 + self.scrollY, self.h + self.scrollY do
                if row <= #self.text then
                    local x = self.x1
                    local y = self.y1 + row - self.scrollY - 1
                    local text = self.text[row]
                    if row == 1 and self.elemType == "textArea" and not self.enabled then
                        text = "(locked) "..text
                    end
                    writeText(text:sub(1+self.scrollX,self.scrollX+self.w),x,y,fg)
                end
            end
 
            if selected == self then
                local x,y = self:localToWorld(self.cursorX,self.cursorY)
                self.visableCursor = self:pointInArea(self:localToWorld(self.cursorX,self.cursorY))
            end
        end
 
        function textArea.setScroll(self, x,y)
            local maxW = 0
            for _,line in ipairs(self.text) do
                maxW = math.max(maxW, #line)
            end
 
            -- lock
            self.scrollX = math.max(math.min(maxW-1,x),0)
            self.scrollY = math.max(math.min(y,#self.text-self.h),0)
        end
 
        function textArea.changeScroll(self,dx,dy)
            self:setScroll(self.scrollX+dx,self.scrollY+dy)
        end
 
        function textArea.focusCursor(self)
            self:setScroll(
                math.min(math.max(self.scrollX,self.cursorX-self.w),self.cursorX-1),
                math.min(math.max(self.scrollY,self.cursorY-self.h),self.cursorY-1)
            )
        end
 
        function textArea.getText(self)
            local text = ""
            for row,str in ipairs(self.text) do
                text = text .. str
                if row < #self.text then -- not last row
                    text = text .. " \n"
                end
            end
            return text
        end
 
        function textArea.isChecked(self)
            return self.elemType == "checkbox" and self.text[1] == "[x]"
        end
 
        function textArea.handleEvents(self,ev,p1,p2,p3,p4,p5)
            if not self.enabled then
                return false
            end
            if self==selected then
                if ev == "char" or ev == "paste" then
                    -- Add character
                    local line = self.text[self.cursorY]
 
                    self.text[self.cursorY] = line:sub(1,self.cursorX) .. p1 .. line:sub(self.cursorX + 1,-1)
                    self.cursorX = self.cursorX + 1
                    self:focusCursor()
                    return true
                elseif ev == "key" then
                    if p1 == keys.left then
                        if lctrl or rctrl then
                            self:changeScroll(-1,0)
                        else
                            -- left
                            self.cursorX = self.cursorX - 1
                            if self.cursorX < 1 then
                                if self.cursorY > 1 then -- not first row
                                    self.cursorY = self.cursorY - 1
                                    self.cursorX = #self.text[self.cursorY] + 1
                                else
                                    self.cursorX = 1
                                end
                            end
                            self:focusCursor()
                        end
                        return true
                    elseif p1 == keys.right then
                        if lctrl or rctrl then
                            self:changeScroll(1,0)
                        else
                            -- right
                            self.cursorX = self.cursorX + 1
                            if self.cursorX > #self.text[self.cursorY] + 1 then
                                if self.cursorY < #self.text then -- not last row
                                    self.cursorY = self.cursorY + 1
                                    self.cursorX = 1
                                else
                                    self.cursorX = #self.text[self.cursorY] + 1
                                end
                            end
                            self:focusCursor()
                        end
                        return true
                    elseif p1 == keys.up then
                        if lctrl or rctrl then
                            self:changeScroll(0,1)
                        else
                            -- up
                            if self.cursorY > 1 then -- not first row
                                self.cursorX = math.min(self.cursorX,#self.text[self.cursorY - 1] + 1)
                                self.cursorY = self.cursorY - 1
                            else
                                self.cursorX = 1
                            end
                            self:focusCursor()
                        end
                        return true
                    elseif p1 == keys.down then
                        if lctrl or rctrl then
                            self:changeScroll(0,-1)
                        else
                            -- down
                            if self.cursorY < #self.text then -- not last row
                                self.cursorX = math.min(self.cursorX,#self.text[self.cursorY + 1] + 1)
                                self.cursorY = self.cursorY + 1
                            else
                                self.cursorX = #self.text[self.cursorY] + 1
                            end
                            self:focusCursor()
                        end
                        return true
                    elseif p1 == keys.backspace then
                        if self.cursorX > 1 then
                            -- remove a char
                            local line = self.text[self.cursorY]
                            self.text[self.cursorY] = line:sub(1,self.cursorX - 2) .. line:sub(self.cursorX,-1)
                            self.cursorX = self.cursorX - 1
                            self:focusCursor()
                            return true
                        else
                            if self.cursorY > 1 then
                                -- compress two lines
                                self.text[self.cursorY - 1] = self.text[self.cursorY - 1] .. self.text[self.cursorY]
                                table.remove(self.text,self.cursorY)
                                self.cursorY = self.cursorY - 1
                                self.cursorX = #self.text[self.cursorY] + 1
                                self:focusCursor()
                                return true
                            end
                        end
                    elseif p1 == keys.delete then
                        if self.cursorX < #self.text[self.cursorY]+1 then
                            -- remove a char
                            local line = self.text[self.cursorY]
                            self.text[self.cursorY] = line:sub(1,self.cursorX - 1) .. line:sub(self.cursorX + 1,-1)
                            return true
                        else
                            if self.cursorY < #self.text then
                                -- compress two lines
                                self.text[self.cursorY] = self.text[self.cursorY] .. self.text[self.cursorY + 1]
                                table.remove(self.text,self.cursorY + 1)
                                self:focusCursor()
                                return true
                            end
                        end
                    elseif p1 == keys.enter then
                        if self.multiline then
                            -- split into new line
                            local line = self.text[self.cursorY]
                            self.text[self.cursorY] = line:sub(1, self.cursorX - 1)
                            line = line:sub(self.cursorX, -1)
 
                            table.insert(self.text, self.cursorY + 1, line)
                            self.cursorY = self.cursorY + 1
                            self.cursorX = 1
                            self:focusCursor()
                            return true
                        end
                    elseif p1 == keys["end"] then
                        self.cursorX = #self.text[self.cursorY] + 1
                        self:focusCursor()
                        return true
                    elseif p1 == keys.home then
                        self.cursorX = 1
                        self:focusCursor()
                        return true
                    elseif p1 == keys.tab then
                        if not tabJump then
                            if rshift or lshift then
                                -- prev
                                if self.prevElem then
                                    if type(self.onDeselect) == "function" then
                                        self:onDeselect()
                                    end
                                    selected = self.prevElem
                                    if type(selected.onSelect) == "function" then
                                        selected:onSelect()
                                    end
                                    tabJump = true
                                    return true
                                end
                            else
                                -- next
                                if self.nextElem then
                                    if type(self.onDeselect) == "function" then
                                        self:onDeselect()
                                    end
                                    selected = self.nextElem
                                    if type(selected.onSelect) == "function" then
                                        selected:onSelect()
                                    end
                                    tabJump = true
                                    return true
                                end
                            end
                        end
                    end
                elseif ev == "mouse_scroll" then
                    self:changeScroll(0,p1)
                    return true
                end
            end
            if ev == "mouse_click" and p1 == 1 then
                if self:pointInArea(p2,p3) then
                    if self.elemType == "textArea" then
                        selected = self
                        local x,y = self:worldtoLocal(p2,p3)
                        self.cursorY = math.min(#self.text, y)
                        self.cursorX = math.min(#self.text[self.cursorY]+1, x)
                        self:focusCursor()
                    elseif self.elemType == "checkbox" then
                        local checked = self:isChecked()
                        self.text[1] = checked and "[ ]" or "[x]"
                    end
                    if type(self.onSelect) == "function" then
                        self:onSelect()
                    end
                    return true
                elseif selected == self then
                    if type(self.onDeselect) == "function" then
                        self:onDeselect()
                    end
                    selected = false
                    return true
                end
            end
 
            return false
        end
 
        return textArea
    end
 
    local function newCheckbox( x,y,checked )
        local textArea = newTextArea(x,y,x+2,_,false)
        textArea.elemType = "checkbox"
        textArea.text[1] = checked and "[x]" or "[ ]"
        return textArea
    end
 
    local function newButton(x1,y1,x2,y2,str,onSelect)
        local button = newTextArea(x1,y1,x2,y2,true)
        button.elemType = "button"
        button.onSelect = onSelect
        button.enabled = true
 
        button.text = {}
        for i=1,button.h do
            if i == math.floor(button.h/2)+1 then
                table.insert(button.text,string.rep(" ",math.floor(button.w/2-#str/2))..str)
            else
                table.insert(button.text,"")
            end
        end
 
        return button
    end
 
    local exitField = newButton(termW,1,_,_,"X")
    local fromField = newTextArea(10,4,termW-1,_,false)
    local subjectField = newTextArea(10,6,termW-1,_,false)
    local messageField = newTextArea(10,8,termW-1,14,true)
    local previewField = newButton(termW - 14,termH-3,termW-1,_,"Preview")
    local sendField = newButton(termW - 14,termH-1,termW-1,_,"Send")
 
    fromField.nextElem = subjectField
    fromField.prevElem = messageField
    subjectField.nextElem = messageField
    subjectField.prevElem = fromField
    messageField.nextElem = fromField
    messageField.prevElem = subjectField
    sendField.enabled = false
 
    if type(lockedSender) == "string" and #lockedSender > 0 then
        fromField.enabled = false
        fromField.text[1] = lockedSender
 
        subjectField.prevElem = messageField
        messageField.nextElem = subjectField
    end
 
    local jagField = newCheckbox(10,termH - 3)
    local knautField = newCheckbox(10,termH - 2)
    local bejnoField = newCheckbox(10,termH - 1)
 
    local function getReceivers()
        local receivers = {}
        if jagField:isChecked() then table.insert(receivers,"jag") end
        if knautField:isChecked() then table.insert(receivers,"knaut") end
        if bejnoField:isChecked() then table.insert(receivers,"bejno") end
        return receivers
    end
 
    local function checkForErrors()
        local oldA,oldB = diskAvailable,anyReceivers
        diskAvailable = mail.getDrive().hasData()
        anyReceivers = #getReceivers() > 0
        sendField.enabled = diskAvailable and anyReceivers
        if diskAvailable ~= oldA or anyReceivers ~= oldB then redraw = true end
    end
 
    local function getMailData()
        return {
            sender = fromField:getText(),
            receivers = getReceivers(),
            message = messageField:getText(),
            subject = subjectField:getText(),
        }
    end
 
    previewField.onSelect = function(self)
        previewMail(getMailData())
    end
 
    sendField.onSelect = function(self)
        if self.enabled then
            mail.writeMail(getMailData())
        end
    end
 
    exitField.onSelect = function(self)
        running = false
    end
 
    local function draw()
        term.setCursorBlink(false)
 
        -- draw background
        setRect(" ",1,1,termW,2,_,colors.cyan)
        setRect(" ",1,3,termW,termH, _, colors.lightBlue)
 
        -- Draw text
        writeCentered("Write new mail",2,colors.white)
 
        writeText("From", 2, 4, colors.gray)
        writeText("Subject", 2, 6, colors.gray)
        writeText("Message", 2, 8, colors.gray)
 
        writeText("Send to", 2, termH - 3, colors.gray)
        writeText("- jag", 14, termH - 3, jagField:isChecked() and colors.black or colors.gray)
        writeText("- knaut", 14, termH - 2, knautField:isChecked() and colors.black or colors.gray)
        writeText("- bejno", 14, termH - 1, bejnoField:isChecked() and colors.black or colors.gray)
 
        if not diskAvailable then
            writeText("Requires disk in disk drive!",23,termH,colors.red)
        elseif not anyReceivers then
            writeText("   Need at least 1 receiver!",23,termH,colors.red)
        end
 
        -- Draw fields
        for _,field in pairs(fields) do
            if type(field.draw) == "function" then
                field:draw()
            end
        end
 
        for _,field in pairs(fields) do
            if type(field.lateDraw) == "function" then
                field:lateDraw()
            end
        end
 
        -- Draw to screen
        drawGrid()
 
        -- Show cursor
        if selected and selected.visableCursor then
            term.setCursorPos(selected:localToWorld(selected.cursorX,selected.cursorY))
            term.setCursorBlink(true)
        else
            term.setCursorBlink(false)
        end
    end
 
    local function handleEvents(ev,p1,p2,p3,p4,p5)
        checkForErrors()
 
        if ev == "key" then
            if p1 == keys.leftShift then lshift = true end
            if p1 == keys.rightShift then rshift = true end
            if p1 == keys.leftCtrl then lctrl = true end
            if p1 == keys.rightCtrl then rctrl = true end
        end
        if ev == "key_up" then
            if p1 == keys.leftShift then lshift = false end
            if p1 == keys.rightShift then rshift = false end
            if p1 == keys.leftCtrl then lctrl = false end
            if p1 == keys.rightCtrl then rctrl = false end
        end
 
        -- Update fields
        tabJump = false
        for _,field in pairs(fields) do
            if field:handleEvents(ev,p1,p2,p3,p4,p5) then
                redraw = true
            end
        end
    end
 
    -- Main loop
    checkForErrors()
    while running do
        if redraw then
            draw()
            redraw = false
        end
        handleEvents(os.pullEvent())
    end
 
    term.setBackgroundColor(colors.black)
    term.clear()
    term.setCursorPos(1,1)
 
    return data
end
 
-- eof

Comments