-- 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