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