Advertisement
Guest User

gml.lua

a guest
Jan 19th, 2017
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 43.14 KB | None | 0 0
  1. --[[*********************************************
  2.  
  3. gui library
  4.  
  5. by GopherAtl
  6.  
  7. do whatever you want, just don't be a dick. Give
  8. me credit whenever you redistribute, modified or
  9. otherwise.
  10.  
  11. For the latest updates and documentations, check
  12. out the github repo and it's wiki at
  13. https://github.com/OpenPrograms/Gopher-Programs
  14.  
  15. *************************************************
  16. Edited by Krutoy242
  17. Added unicode, color fixes and syntax improvments
  18. --***********************************************]]
  19.  
  20. local event=require("event")
  21. local component=require("component")
  22. local term=require("term")
  23. local computer=require("computer")
  24. local shell=require("shell")
  25. local filesystem=require("filesystem")
  26. local keyboard=require("keyboard")
  27. local unicode=require("unicode")
  28. local gfxbuffer=require("gfxbuffer")
  29. local process = require("process")
  30.  
  31. local len = unicode.len
  32.  
  33. local doubleClickThreshold=.25
  34.  
  35. local gml={VERSION="1.0"}
  36.  
  37. local defaultStyle=nil
  38.  
  39. --clipboard is global between guis and gui sessions, as long as you don't reboot.
  40. local clipboard=nil
  41.  
  42. local validElements = {
  43.   ["*"]=true,
  44.   gui=true,       --top gui container
  45.   label=true,     --text labels, non-focusable (naturally), non-readable
  46.   button=true,    --buttons, text label, clickable
  47.   textfield=true, --single-line text input, can scroll left-right, never has scrollbar, just scrolls with cursor
  48.   scrollbar=true, --scroll bar, scrolls. Can be horizontal or vertical.
  49.   textbox=true,   --multi-line text input, line wraps, scrolls up-down, has scroll bar if needed
  50.   listbox=true,   --list, vertical stack of labels with a scrollbar
  51. }
  52.  
  53. local validStates = {
  54.   ["*"]=true,
  55.   enabled=true,
  56.   disabled=true,
  57.   checked=true,
  58.   focus=true,
  59.   empty=true,
  60.   selected=true,
  61. }
  62.  
  63. local validDepths = {
  64.   ["*"]=true,
  65.   [1]=true,
  66.   [4]=true,
  67.   [8]=true,
  68. }
  69.  
  70. local screen = {
  71.       posX=1, posY=1,
  72.       bodyX=1,bodyY=1,
  73.       hidden=false,
  74.       isHidden=function() return false end,
  75.       renderTarget=component.gpu
  76.     }
  77.  
  78. screen.width,screen.height=component.gpu.getResolution()
  79. screen.bodyW,screen.bodyH=screen.width,screen.height
  80.  
  81. --**********************
  82. --utility functions
  83.  
  84. function round(v)
  85.   return math.floor(v+.5)
  86. end
  87.  
  88.  
  89. --**********************
  90. --api functions
  91.  
  92. function gml.loadStyle(name)
  93.   --search for file
  94.   local fullname=name
  95.   if name:match(".gss$") then
  96.     name=name:match("^(.*)%.gss$")
  97.   else
  98.     fullname=name..".gss"
  99.   end
  100.  
  101.   local filepath
  102.  
  103.   --search for styles in working directory, running program directory, /lib /usr/lib. Just because.
  104.   local dirs={shell.getWorkingDirectory(),process.running():match("^(.*/).+$"), "/lib/", "/usr/lib/"}
  105.   if dirs[1]~="/" then
  106.     dirs[1]=dirs[1].."/"
  107.   end
  108.   for i=1,#dirs do
  109.     if filesystem.exists(dirs[i]..fullname) and not filesystem.isDirectory(dirs[i]..fullname) then
  110.       filepath=dirs[i]..fullname
  111.       break
  112.     end
  113.   end
  114.  
  115.   if not filepath then
  116.     error("Could not find gui stylesheet \""..name.."\"",2)
  117.   end
  118.  
  119.   --found it, open and parse
  120.   local file=assert(io.open(filepath,"r"))
  121.  
  122.   local text=file:read("*all")
  123.   file:close()
  124.   text=text:gsub("/%*.-%*/",""):gsub("\r\n","\n")
  125.  
  126.   local styleTree={}
  127.  
  128.   --util method used in loop later when building styleTree
  129.   local function descend(node,to)
  130.     if node[to]==nil then
  131.       node[to]={}
  132.     end
  133.     return node[to]
  134.   end
  135.  
  136.  
  137.   for selectorStr, body in text:gmatch("%s*([^{]*)%s*{([^}]*)}") do
  138.     --parse the selectors!
  139.     local selectors={}
  140.     for element in selectorStr:gmatch("([^,^%s]+)") do
  141.       --could have a !depth modifier
  142.       local depth,state,class, temp
  143.       temp,depth=element:match("(%S+)!(%S+)")
  144.       element=temp or element
  145.       temp,state=element:match("(%S+):(%S+)")
  146.       element=temp or element
  147.       temp,class=element:match("(%S+)%.(%S+)")
  148.       element=temp or element
  149.       if element and validElements[element]==nil then
  150.         error("Encountered invalid element "..element.." loading style "..name)
  151.       end
  152.       if state and validStates[state]==nil then
  153.         error("Encountered invalid state "..state.." loading style "..name)
  154.       end
  155.       if depth and validDepths[tonumber(depth)]==nil then
  156.         error("Encountered invalid depth "..depth.." loading style "..name)
  157.       end
  158.  
  159.       selectors[#selectors+1]={element=element or "*",depth=tonumber(depth) or "*",state=state or "*",class=class or "*"}
  160.     end
  161.  
  162.     local props={}
  163.     for prop,val in body:gmatch("(%S*)%s*:%s*(.-);") do
  164.       if tonumber(val) then
  165.         val=tonumber(val)
  166.       elseif val:match("U%+%x+") then
  167.         val=unicode.char(tonumber("0x"..val:match("U%+(.*)")))
  168.       elseif val:match("^%s*[tT][rR][uU][eE]%s*$") then
  169.         val=true
  170.       elseif val:match("^%s*[fF][aA][lL][sS][eE]%s*$") then
  171.         val=false
  172.       elseif val:match("%s*(['\"]).*(%1)%s*") then
  173.         _,val=val:match("%s*(['\"])(.*)%1%s*")
  174.       else
  175.         error("invalid property value '"..val.."'!")
  176.       end
  177.  
  178.       props[prop]=val
  179.     end
  180.  
  181.     for i=1,#selectors do
  182.       local sel=selectors[i]
  183.       local node=styleTree
  184.  
  185.  
  186.       node=descend(node,sel.depth)
  187.       node=descend(node,sel.state)
  188.       node=descend(node,sel.class)
  189.       node=descend(node,sel.element)
  190.       --much as I'd like to save mem, dupe selectors cause merges, which, if
  191.       --instances are duplicated in the final style tree, could result in spraying
  192.       --props in inappropriate places
  193.       for k,v in pairs(props) do
  194.         node[k]=v
  195.       end
  196.     end
  197.  
  198.   end
  199.  
  200.   return styleTree
  201. end
  202.  
  203.  
  204. --**********************
  205. --internal style-related utility functions
  206.  
  207. local function tableCopy(t1)
  208.   local copy={}
  209.   for k,v in pairs(t1) do
  210.     if type(v)=="table" then
  211.       copy[k]=tableCopy(v)
  212.     else
  213.       copy[j]=v
  214.     end
  215.   end
  216. end
  217.  
  218. local function mergeStyles(t1, t2)
  219.   for k,v in pairs(t2) do
  220.     if t1[k]==nil then
  221.       t1[k]=tableCopy(v)
  222.     elseif type(t1[k])=="table" then
  223.       if type(v)=="table" then
  224.         tableMerge(t1[k],v)
  225.       else
  226.         error("inexplicable error in mergeStyles - malformed style table, attempt to merge "..type(v).." with "..type(t1[k]))
  227.       end
  228.     elseif type(v)=="table" then
  229.       error("inexplicable error in mergeStyles - malformed style table, attempt to merge "..type(v).." with "..type(t1[k]))
  230.     else
  231.       t1[k]=v
  232.     end
  233.   end
  234. end
  235.  
  236.  
  237. function getAppliedStyles(element)
  238.   local styleRoot=element.style
  239.   assert(styleRoot)
  240.  
  241.   --descend, unless empty, then back up... so... wtf
  242.   local depth,state,class,elementType=element.renderTarget.getDepth(),element.state or "*",element.class or "*", element.type
  243.  
  244.   local nodes={styleRoot}
  245.   local function filterDown(nodes,key)
  246.     local newNodes={}
  247.     for i=1,#nodes do
  248.       if key~="*" and nodes[i][key] then
  249.         newNodes[#newNodes+1]=nodes[i][key]
  250.       end
  251.       if nodes[i]["*"] then
  252.         newNodes[#newNodes+1]=nodes[i]["*"]
  253.       end
  254.     end
  255.     return newNodes
  256.   end
  257.   nodes=filterDown(nodes,depth)
  258.   nodes=filterDown(nodes,state)
  259.   nodes=filterDown(nodes,class)
  260.   nodes=filterDown(nodes,elementType)
  261.   return nodes
  262. end
  263.  
  264.  
  265. function extractProperty(element,styles,property)
  266.   if element[property] then
  267.     return element[property]
  268.   end
  269.   for j=1,#styles do
  270.     local v=styles[j][property]
  271.     if v~=nil then
  272.       return v
  273.     end
  274.   end
  275. end
  276.  
  277. local function extractProperties(element,styles,...)
  278.   local props={...}
  279.  
  280.   --nodes is now a list of all terminal branches that could possibly apply to me
  281.   local vals={}
  282.   for i=1,#props do
  283.     vals[#vals+1]=extractProperty(element,styles,props[i])
  284.     if #vals~=i then
  285.       for k,v in pairs(styles[1]) do print('"'..k..'"',v,k==props[i] and "<-----!!!" or "") end
  286.       error("Could not locate value for style property "..props[i].."!")
  287.     end
  288.   end
  289.   return table.unpack(vals)
  290. end
  291.  
  292. local function findStyleProperties(element,...)
  293.   local props={...}
  294.   local nodes=getAppliedStyles(element)
  295.   return extractProperties(element,nodes,...)
  296. end
  297.  
  298.  
  299. --**********************
  300. --drawing and related functions
  301.  
  302.  
  303. local function parsePosition(x,y,width,height,maxWidth, maxHeight)
  304.  
  305.   width=math.min(width,maxWidth)
  306.   height=math.min(height,maxHeight)
  307.  
  308.   if x=="left" then
  309.     x=1
  310.   elseif x=="right" then
  311.     x=maxWidth-width+1
  312.   elseif x=="center" then
  313.     x=math.max(1,math.floor((maxWidth-width)/2))
  314.   elseif x<0 then
  315.     x=maxWidth-width+2+x
  316.   elseif x<1 then
  317.     x=1
  318.   elseif x+width-1>maxWidth then
  319.     x=maxWidth-width+1
  320.   end
  321.  
  322.   if y=="top" then
  323.     y=1
  324.   elseif y=="bottom" then
  325.     y=maxHeight-height+1
  326.   elseif y=="center" then
  327.     y=math.max(1,math.floor((maxHeight-height)/2))
  328.   elseif y<0 then
  329.     y=maxHeight-height+2+y
  330.   elseif y<1 then
  331.     y=1
  332.   elseif y+height-1>maxHeight then
  333.     y=maxHeight-height+1
  334.   end
  335.  
  336.   return x,y,width,height
  337. end
  338.  
  339. --draws a frame, based on the relevant style properties, and
  340. --returns the effective client area inside the frame
  341. local function drawBorder(element,styles)
  342.   local screenX,screenY=element:getScreenPosition()
  343.  
  344.   local borderFG, borderBG,
  345.         border,borderLeft,borderRight,borderTop,borderBottom,
  346.         borderChL,borderChR,borderChT,borderChB,
  347.         borderChTL,borderChTR,borderChBL,borderChBR =
  348.       extractProperties(element,styles,
  349.         "border-color-fg","border-color-bg",
  350.         "border","border-left","border-right","border-top","border-bottom",
  351.         "border-ch-left","border-ch-right","border-ch-top","border-ch-bottom",
  352.         "border-ch-topleft","border-ch-topright","border-ch-bottomleft","border-ch-bottomright")
  353.  
  354.   local width,height=element.width,element.height
  355.  
  356.   local bodyX,bodyY=screenX,screenY
  357.   local bodyW,bodyH=width,height
  358.  
  359.   local gpu=element.renderTarget
  360.  
  361.   if border then
  362.     gpu.setBackground(borderBG)
  363.     gpu.setForeground(borderFG)
  364.  
  365.     --as needed, leave off top and bottom borders if height doesn't permit them
  366.     if borderTop and bodyW>1 then
  367.       bodyY=bodyY+1
  368.       bodyH=bodyH-1
  369.       --do the top bits
  370.       local str=(borderLeft and borderChTL or borderChT)..borderChT:rep(bodyW-2)..(borderRight and borderChTR or borderChB)
  371.       gpu.set(screenX,screenY,str)
  372.     end
  373.     if borderBottom and bodyW>1 then
  374.       bodyH=bodyH-1
  375.       --do the top bits
  376.       local str=(borderLeft and borderChBL or borderChB)..borderChB:rep(bodyW-2)..(borderRight and borderChBR or borderChB)
  377.       gpu.set(screenX,screenY+height-1,str)
  378.     end
  379.     if borderLeft then
  380.       bodyX=bodyX+1
  381.       bodyW=bodyW-1
  382.       for y=bodyY,bodyY+bodyH-1 do
  383.         gpu.set(screenX,y,borderChL)
  384.       end
  385.     end
  386.     if borderRight then
  387.       bodyW=bodyW-1
  388.       for y=bodyY,bodyY+bodyH-1 do
  389.         gpu.set(screenX+width-1,y,borderChR)
  390.       end
  391.     end
  392.   end
  393.  
  394.   return bodyX,bodyY,bodyW,bodyH
  395. end
  396.  
  397. --calculates the body coords of an element based on it's true coords
  398. --and border style properties
  399. local function calcBody(element)
  400.   local x,y,w,h=element.posX,element.posY,element.width,element.height
  401.   local border,borderTop,borderBottom,borderLeft,borderRight =
  402.      findStyleProperties(element,"border","border-top","border-bottom","border-left","border-right")
  403.  
  404.   if border then
  405.     if borderTop then
  406.       y=y+1
  407.       h=h-1
  408.     end
  409.     if borderBottom then
  410.       h=h-1
  411.     end
  412.     if borderLeft then
  413.       x=x+1
  414.       w=w-1
  415.     end
  416.     if borderRight then
  417.       w=w-1
  418.     end
  419.   end
  420.   return x,y,w,h
  421. end
  422.  
  423. local function correctForBorder(element,px,py)
  424.   px=px-(element.bodyX and element.bodyX-element.posX or 0)
  425.   py=py-(element.bodyY and element.bodyY-element.posY or 0)
  426.   return px,py
  427. end
  428.  
  429. local function frameAndSave(element)
  430.   local t={}
  431.   local x,y,width,height=element.posX,element.posY,element.width,element.height
  432.  
  433.   local pcb=term.getCursorBlink()
  434.   local curx,cury=term.getCursor()
  435.   local pfg,pbg=element.renderTarget.getForeground(),element.renderTarget.getBackground()
  436.   local rtg=element.renderTarget.get
  437.   --preserve background
  438.   for ly=1,height do
  439.     t[ly]={}
  440.     local str, cfg, cbg=rtg(x,y+ly-1)
  441.     for lx=2,width do
  442.       local ch, fg, bg=rtg(x+lx-1,y+ly-1)
  443.       if fg==cfg and bg==cbg then
  444.         str=str..ch
  445.       else
  446.         t[ly][#t[ly]+1]={str,cfg,cbg}
  447.         str,cfg,cbg=ch,fg,bg
  448.       end
  449.     end
  450.     t[ly][#t[ly]+1]={str,cfg,cbg}
  451.   end
  452.   local styles=getAppliedStyles(element)
  453.  
  454.   local bodyX,bodyY,bodyW,bodyH=drawBorder(element,styles)
  455.  
  456.   local fillCh,fillFG,fillBG=extractProperties(element,styles,"fill-ch","fill-color-fg","fill-color-bg")
  457.  
  458.   local blankRow=fillCh:rep(bodyW)
  459.  
  460.   element.renderTarget.setForeground(fillFG)
  461.   element.renderTarget.setBackground(fillBG)
  462.   term.setCursorBlink(false)
  463.  
  464.   element.renderTarget.fill(bodyX,bodyY,bodyW,bodyH,fillCh)
  465.  
  466.   return {curx,cury,pcb,pfg,pbg, t}
  467.  
  468. end
  469.  
  470. local function restoreFrame(renderTarget,x,y,prevState)
  471.  
  472.   local curx,cury,pcb,pfg,pbg, behind=table.unpack(prevState)
  473.  
  474.   for ly=1,#behind do
  475.     local lx=x
  476.     for i=1,#behind[ly] do
  477.       local str,fg,bg=table.unpack(behind[ly][i])
  478.       renderTarget.setForeground(fg)
  479.       renderTarget.setBackground(bg)
  480.       renderTarget.set(lx,ly+y-1,str)
  481.       lx=lx+len(str)
  482.     end
  483.   end
  484.  
  485.  
  486.   term.setCursor(curx,cury)
  487.   renderTarget.setForeground(pfg)
  488.   renderTarget.setBackground(pbg)
  489.   renderTarget.flush()
  490.  
  491.   term.setCursorBlink(pcb)
  492.  
  493. end
  494.  
  495. local function elementHide(element)
  496.   if element.visible then
  497.     element.visible=false
  498.     element.gui:redrawRect(element.posX,element.posY,element.width,1)
  499.   end
  500.   element.hidden=true
  501. end
  502.  
  503. local function elementShow(element)
  504.   element.hidden=false
  505.   if not element.visible then
  506.     element:draw()
  507.   end
  508. end
  509.  
  510.  
  511. local function drawLabel(label)
  512.   if not label:isHidden() then
  513.     local screenX,screenY=label:getScreenPosition()
  514.     local fg, bg=findStyleProperties(label,"text-color","text-background")
  515.     label.renderTarget.setForeground(fg)
  516.     label.renderTarget.setBackground(bg)
  517.     label.renderTarget.set(screenX,screenY, unicode.sub(label.text, 1,label.width) .. (" "):rep(label.width-len(label.text)))
  518.     label.visible=true
  519.   end
  520. end
  521.  
  522.  
  523.  
  524. local function drawButton(button)
  525.   if not button:isHidden() then
  526.     local styles=getAppliedStyles(button)
  527.     local gpu=button.renderTarget
  528.  
  529.     local fg,bg,
  530.           fillFG,fillBG,fillCh=
  531.       findStyleProperties(button,
  532.         "text-color","text-background",
  533.         "fill-color-fg","fill-color-bg","fill-ch")
  534.  
  535.     local bodyX,bodyY,bodyW,bodyH=drawBorder(button,styles)
  536.  
  537.     gpu.setBackground(fillBG)
  538.     gpu.setForeground(fillFG)
  539.     local bodyRow=fillCh:rep(bodyW)
  540.     for i=1,bodyH do
  541.       gpu.set(bodyX,bodyY+i-1,bodyRow)
  542.     end
  543.  
  544.     --now center the label
  545.     gpu.setBackground(bg)
  546.     gpu.setForeground(fg)
  547.     --calc position
  548.     local text=button.text
  549.     local textX=bodyX
  550.     local textY=bodyY+math.floor((bodyH-1)/2)
  551.     if len(text)>bodyW then
  552.       text=unicode.sub(text, 1,bodyW)
  553.     else
  554.       textX=bodyX+math.floor((bodyW-len(text))/2)
  555.     end
  556.     gpu.set(textX,textY,text)
  557.   end
  558. end
  559.  
  560.  
  561. local function drawTextField(tf)
  562.   if not tf:isHidden() then
  563.     local textFG,textBG,selectedFG,selectedBG=
  564.         findStyleProperties(tf,"text-color","text-background","selected-color","selected-background")
  565.     local screenX,screenY=tf:getScreenPosition()
  566.     local gpu=tf.renderTarget
  567.  
  568.     --grab the subset of text visible
  569.     local text=tf.text
  570.  
  571.     local visibleText=unicode.sub(text, tf.scrollIndex,tf.scrollIndex+tf.width-1)
  572.     visibleText=visibleText..(" "):rep(tf.width-len(visibleText))
  573.     --this may be split into as many as 3 parts - pre-selection, selection, and post-selection
  574.     --if there is any selection at all...
  575.     if tf.state=="focus" and not tf.dragging then
  576.       term.setCursorBlink(false)
  577.     end
  578.     if tf.selectEnd~=0 then
  579.       local visSelStart, visSelEnd, preSelText,selText,postSelText
  580.       visSelStart=math.max(1,tf.selectStart-tf.scrollIndex+1)
  581.       visSelEnd=math.min(tf.width,tf.selectEnd-tf.scrollIndex+1)
  582.  
  583.       selText=unicode.sub(visibleText, visSelStart,visSelEnd)
  584.  
  585.       if visSelStart>1 then
  586.         preSelText=unicode.sub(visibleText, 1,visSelStart-1)
  587.       end
  588.  
  589.       if visSelEnd<tf.width then
  590.         postSelText=unicode.sub(visibleText, visSelEnd+1,tf.width)
  591.       end
  592.  
  593.       gpu.setForeground(selectedFG)
  594.       gpu.setBackground(selectedBG)
  595.       gpu.set(screenX+visSelStart-1,screenY,selText)
  596.  
  597.       if preSelText or postSelText then
  598.         gpu.setForeground(textFG)
  599.         gpu.setBackground(textBG)
  600.         if preSelText then
  601.           gpu.set(screenX,screenY,preSelText)
  602.         end
  603.         if postSelText then
  604.           gpu.set(screenX+visSelEnd,screenY,postSelText)
  605.         end
  606.       end
  607.     else
  608.       --no selection, just draw
  609.       gpu.setForeground(textFG)
  610.       gpu.setBackground(textBG)
  611.       gpu.set(screenX,screenY,visibleText)
  612.     end
  613.     if tf.state=="focus" and not tf.dragging then
  614.       term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  615.       term.setCursorBlink(true)
  616.     end
  617.   end
  618. end
  619.  
  620.  
  621. local function drawScrollBarH(bar)
  622.   if not bar:isHidden() then
  623.     local leftCh,rightCh,btnFG,btnBG,
  624.           barCh, barFG, barBG,
  625.           gripCh, gripFG, gripBG =
  626.       findStyleProperties(bar,
  627.           "button-ch-left","button-ch-right","button-color-fg","button-color-bg",
  628.           "bar-ch","bar-color-fg","bar-color-bg",
  629.           "grip-ch-h","grip-color-fg","grip-color-bg")
  630.  
  631.     local gpu=bar.renderTarget
  632.     local screenX,screenY=bar:getScreenPosition()
  633.  
  634.     local w,gs,ge=bar.width,bar.gripStart+screenX,bar.gripEnd+screenX
  635.     --buttons
  636.     gpu.setBackground(btnBG)
  637.     gpu.setForeground(btnFG)
  638.     gpu.set(screenX,screenY,leftCh)
  639.     gpu.set(screenX+w-1,screenY,rightCh)
  640.  
  641.     --scroll area
  642.     gpu.setBackground(barBG)
  643.     gpu.setForeground(barFG)
  644.  
  645.     gpu.set(screenX+1,screenY,barCh:rep(w-2))
  646.  
  647.     --grip
  648.     gpu.setBackground(gripBG)
  649.     gpu.setForeground(gripFG)
  650.     gpu.set(gs,screenY,gripCh:rep(ge-gs+1))
  651.   end
  652. end
  653.  
  654. local function drawScrollBarV(bar)
  655.   if not bar:isHidden() then
  656.     local upCh,dnCh,btnFG,btnBG,
  657.           barCh, barFG, barBG,
  658.           gripCh, gripFG, gripBG =
  659.       findStyleProperties(bar,
  660.           "button-ch-up","button-ch-down","button-color-fg","button-color-bg",
  661.           "bar-ch","bar-color-fg","bar-color-bg",
  662.           "grip-ch-v","grip-color-fg","grip-color-bg")
  663.  
  664.     local gpu=bar.renderTarget
  665.     local screenX,screenY=bar:getScreenPosition()
  666.     local h,gs,ge=bar.height,bar.gripStart+screenY,bar.gripEnd+screenY
  667.     --buttons
  668.     gpu.setBackground(btnBG)
  669.     gpu.setForeground(btnFG)
  670.     gpu.set(screenX,screenY,upCh)
  671.     gpu.set(screenX,screenY+h-1,dnCh)
  672.  
  673.     --scroll area
  674.     gpu.setBackground(barBG)
  675.     gpu.setForeground(barFG)
  676.  
  677.     for screenY=screenY+1,gs-1 do
  678.       gpu.set(screenX,screenY,barCh)
  679.     end
  680.     for screenY=ge+1,screenY+h-2 do
  681.       gpu.set(screenX,screenY,barCh)
  682.     end
  683.  
  684.     --grip
  685.     gpu.setBackground(gripBG)
  686.     gpu.setForeground(gripFG)
  687.     for screenY=gs,ge do
  688.       gpu.set(screenX,screenY,gripCh)
  689.     end
  690.   end
  691. end
  692.  
  693.  
  694. --**********************
  695. --object creation functions and their utility functions
  696.  
  697. local function loadHandlers(gui)
  698.   local handlers=gui.handlers
  699.   for i=1,#handlers do
  700.     event.listen(handlers[i][1],handlers[i][2])
  701.   end
  702. end
  703.  
  704. local function unloadHandlers(gui)
  705.   local handlers=gui.handlers
  706.   for i=1,#handlers do
  707.     event.ignore(handlers[i][1],handlers[i][2])
  708.   end
  709. end
  710.  
  711. local function guiAddHandler(gui,eventType,func)
  712.   checkArg(1,gui,"table")
  713.   checkArg(2,eventType,"string")
  714.   checkArg(3,func,"function")
  715.  
  716.   gui.handlers[#gui.handlers+1]={eventType,func}
  717.   if gui.running then
  718.     event.listen(eventType,func)
  719.   end
  720. end
  721.  
  722.  
  723. local function cleanup(gui)
  724.   --remove handlers
  725.   unloadHandlers(gui)
  726.  
  727.   --hide gui, redraw beneath?
  728.   if gui.prevTermState then
  729.     restoreFrame(gui.renderTarget,gui.posX,gui.posY,gui.prevTermState)
  730.     gui.prevTermState=nil
  731.   end
  732. end
  733.  
  734. local function contains(element,x,y)
  735.   local ex,ey,ew,eh=element.posX,element.posY,element.width,element.height
  736.  
  737.   return x>=ex and x<=ex+ew-1 and y>=ey and y<=ey+eh-1
  738. end
  739.  
  740. local function runGui(gui)
  741.   gui.running=true
  742.   --draw gui background, preserving underlying screen
  743.   gui.prevTermState=frameAndSave(gui)
  744.   gui.hidden=false
  745.  
  746.   --drawing components
  747.   local firstFocusable, prevFocusable
  748.   for i=1,#gui.components do
  749.     if not gui.components[i].hidden then
  750.       if gui.components[i].focusable and not gui.components[i].hidden then
  751.         if firstFocusable==nil then
  752.           firstFocusable=gui.components[i]
  753.         else
  754.           gui.components[i].tabPrev=prevFocusable
  755.           prevFocusable.tabNext=gui.components[i]
  756.         end
  757.         prevFocusable=gui.components[i]
  758.       end
  759.       gui.components[i]:draw()
  760.     end
  761.   end
  762.   if firstFocusable then
  763.     firstFocusable.tabPrev=prevFocusable
  764.     prevFocusable.tabNext=firstFocusable
  765.     if not gui.focusElement and not gui.components[i].hidden then
  766.       gui.focusElement=gui.components[i]
  767.       gui.focusElement.state="focus"
  768.     end
  769.   end
  770.   if gui.focusElement and gui.focusElement.gotFocus then
  771.     gui.focusElement.gotFocus()
  772.   end
  773.  
  774.   loadHandlers(gui)
  775.  
  776.   --run the gui's onRun, if any
  777.   if gui.onRun then
  778.     gui.onRun()
  779.   end
  780.  
  781.   local function getComponentAt(tx,ty)
  782.     for i=1,#gui.components do
  783.       local c=gui.components[i]
  784.       if not c:isHidden() and c:contains(tx,ty) then
  785.         return c
  786.       end
  787.     end
  788.   end
  789.  
  790.   local lastClickTime, lastClickPos, lastClickButton, dragButton, dragging=0,{0,0},nil,nil,false
  791.   local draggingObj=nil
  792.  
  793.   while true do
  794.     gui.renderTarget:flush()
  795.     local e={event.pull()}
  796.     if e[1]=="gui_close" then
  797.       break
  798.     elseif e[1]=="touch" then
  799.       --figure out what was touched!
  800.       local tx, ty, button=e[3],e[4],e[5]
  801.       if gui:contains(tx,ty) then
  802.         tx=tx-gui.bodyX+1
  803.         ty=ty-gui.bodyY+1
  804.         lastClickPos={tx,ty}
  805.         local tickTime=computer.uptime()
  806.         dragButton=button
  807.         local target=getComponentAt(tx,ty)
  808.         clickedOn=target
  809.         if target then
  810.           if target.focusable and target~=gui.focusElement then
  811.             gui:changeFocusTo(clickedOn)
  812.           end
  813.           if lastClickPos[1]==tx and lastClickPos[2]==ty and lastClickButton==button and
  814.               tickTime - lastClickTime<doubleClickThreshold then
  815.             if target.onDoubleClick then
  816.               target:onDoubleClick(tx-target.posX+1,ty-target.posY+1,button)
  817.             end
  818.           elseif target.onClick then
  819.             target:onClick(tx-target.posX+1,ty-target.posY+1,button)
  820.           end
  821.         end
  822.         lastClickTime=tickTime
  823.         lastClickButton=button
  824.       end
  825.     elseif e[1]=="drag" then
  826.       --if we didn't click /on/ something to start this drag, we do nada
  827.       if clickedOn then
  828.         local tx,ty=e[3],e[4]
  829.         tx=tx-gui.bodyX+1
  830.         ty=ty-gui.bodyY+1
  831.         --is this is the beginning of a drag?
  832.         if not dragging then
  833.           if clickedOn.onBeginDrag then
  834.             draggingObj=clickedOn:onBeginDrag(lastClickPos[1]-clickedOn.posX+1,lastClickPos[2]-clickedOn.posY+1,dragButton)
  835.             dragging=true
  836.           end
  837.         end
  838.         --now do the actual drag bit
  839.         --draggingObj is for drag proxies, which are for drag and drop operations like moving files
  840.         if draggingObj and draggingObj.onDrag then
  841.           draggingObj:onDrag(tx,ty)
  842.         end
  843.         --
  844.         if clickedOn and clickedOn.onDrag then
  845.           tx,ty=tx-clickedOn.posX+1,ty-clickedOn.posY+1
  846.           clickedOn:onDrag(tx,ty)
  847.         end
  848.       end
  849.     elseif e[1]=="drop" then
  850.       local tx,ty=e[3],e[4]
  851.       tx=tx-gui.bodyX+1
  852.       ty=ty-gui.bodyY+1
  853.       if draggingObj and draggingObj.onDrop then
  854.         local dropOver=getComponentAt(tx,ty)
  855.         draggingObj:onDrop(tx,ty,dropOver)
  856.       end
  857.       if clickedOn and clickedOn.onDrop then
  858.         tx,ty=tx-clickedOn.posX+1,ty-clickedOn.posY+1
  859.         clickedOn:onDrop(tx,ty,dropOver)
  860.       end
  861.       draggingObj=nil
  862.       dragging=false
  863.  
  864.     elseif e[1]=="key_down" then
  865.       local char,code=e[3],e[4]
  866.       --tab
  867.       if code==15 and gui.focusElement then
  868.         local newFocus=gui.focusElement
  869.         if keyboard.isShiftDown() then
  870.           repeat
  871.             newFocus=newFocus.tabPrev
  872.           until newFocus.hidden==false
  873.         else
  874.           repeat
  875.             newFocus=newFocus.tabNext
  876.           until newFocus.hidden==false
  877.         end
  878.         if newFocus~=gui.focusElement then
  879.           gui:changeFocusTo(newFocus)
  880.         end
  881.       elseif char==3 then
  882.         --copy!
  883.         if gui.focusElement and gui.focusElement.doCopy then
  884.           clipboard=gui.focusElement:doCopy() or clipboard
  885.         end
  886.       elseif char==22 then
  887.         --paste!
  888.         if gui.focusElement.doPaste and type(clipboard)=="string" then
  889.           gui.focusElement:doPaste(clipboard)
  890.         end
  891.       elseif char==24 then
  892.         --cut!
  893.         if gui.focusElement.doCut then
  894.           clipboard=gui.focusElement:doCut() or clipboard
  895.         end
  896.       elseif gui.focusElement and gui.focusElement.keyHandler then
  897.         gui.focusElement:keyHandler(char,code)
  898.       end
  899.  
  900.       if gui.focusElement and gui.focusElement.onKey then
  901.         gui.focusElement.onKey(char,code)
  902.       end
  903.     end
  904.   end
  905.  
  906.   running=false
  907.  
  908.   cleanup(gui)
  909.  
  910.   if gui.onExit then
  911.     gui.onExit()
  912.   end
  913. end
  914.  
  915. local function baseComponent(gui,x,y,width,height,type,focusable)
  916.   local c={
  917.       visible=false,
  918.       hidden=false,
  919.       gui=gui,
  920.       style=gui.style,
  921.       focusable=focusable,
  922.       type=type,
  923.       renderTarget=gui.renderTarget,
  924.     }
  925.  
  926.   c.isHidden=function(c)
  927.      return c.hidden or c.gui:isHidden()
  928.     end
  929.  
  930.   c.posX, c.posY, c.width, c.height =
  931.     parsePosition(x, y, width, height, gui.bodyW, gui.bodyH)
  932.  
  933.   c.getScreenPosition=function(element)
  934.       local e=element
  935.       local x,y=e.posX,e.posY
  936.       while e.gui and e.gui~=screen do
  937.         e=e.gui
  938.         x=x+e.bodyX-1
  939.         y=y+e.bodyY-1
  940.       end
  941.       return x,y
  942.     end
  943.  
  944.   c.hide=elementHide
  945.   c.show=elementShow
  946.   c.contains=contains
  947.  
  948.   return c
  949. end
  950.  
  951.  
  952. local function addLabel(gui,x,y,width,labelText)
  953.   local label=baseComponent(gui,x,y,width,1,"label",false)
  954.  
  955.   label.text=labelText
  956.  
  957.   label.draw=drawLabel
  958.  
  959.   gui:addComponent(label)
  960.   return label
  961. end
  962.  
  963. local function addButton(gui,x,y,width,height,buttonText,onClick)
  964.   local button=baseComponent(gui,x,y,width,height,"button",true)
  965.  
  966.   button.text=buttonText
  967.   button.onClick=onClick
  968.  
  969.   button.draw=drawButton
  970.   button.keyHandler=function(button,char,code)
  971.       if code==28 then
  972.          button:onClick(0,0,-1)
  973.       end
  974.     end
  975.   gui:addComponent(button)
  976.   return button
  977. end
  978.  
  979. local function updateSelect(tf, prevCI )
  980.   if tf.selectEnd==0 then
  981.     --begin selecting
  982.     tf.selectOrigin=prevCI
  983.   end
  984.   if tf.cursorIndex==tf.selectOrigin then
  985.     tf.selectEnd=0
  986.   elseif tf.cursorIndex>tf.selectOrigin then
  987.     tf.selectStart=tf.selectOrigin
  988.     tf.selectEnd=tf.cursorIndex-1
  989.   else
  990.     tf.selectStart=tf.cursorIndex
  991.     tf.selectEnd=tf.selectOrigin-1
  992.   end
  993. end
  994.  
  995. local function removeSelectedTF(tf)
  996.   tf.text=unicode.sub(tf.text, 1,tf.selectStart-1) .. unicode.sub(tf.text, tf.selectEnd+1)
  997.   tf.cursorIndex=tf.selectStart
  998.   tf.selectEnd=0
  999. end
  1000.  
  1001. local function insertTextTF(tf,text)
  1002.   if tf.selectEnd~=0 then
  1003.     tf:removeSelected()
  1004.   end
  1005.   tf.text=unicode.sub(tf.text, 1,tf.cursorIndex-1)..text..unicode.sub(tf.text, tf.cursorIndex)
  1006.   tf.cursorIndex=tf.cursorIndex+len(text)
  1007.   if tf.cursorIndex-tf.scrollIndex+1>tf.width then
  1008.     local ts=tf.scrollIndex+math.floor(tf.width/3)
  1009.     if tf.cursorIndex-ts+1>tf.width then
  1010.       ts=tf.cursorIndex-tf.width+math.floor(tf.width/3)
  1011.     end
  1012.     tf.scrollIndex=ts
  1013.   end
  1014. end
  1015.  
  1016. local function addTextField(gui,x,y,width,text)
  1017.   local tf=baseComponent(gui,x,y,width,1,"textfield",true)
  1018.  
  1019.   tf.text=text or ""
  1020.   tf.cursorIndex=1
  1021.   tf.scrollIndex=1
  1022.   tf.selectStart=1
  1023.   tf.selectEnd=0
  1024.   tf.draw=drawTextField
  1025.   tf.insertText=insertTextTF
  1026.   tf.removeSelected=removeSelectedTF
  1027.  
  1028.   tf.doPaste=function(tf,text)
  1029.       tf:insertText(text)
  1030.       tf:draw()
  1031.     end
  1032.   tf.doCopy=function(tf)
  1033.       if tf.selectEnd~=0 then
  1034.         return unicode.sub(tf.text, tf.selectStart,tf.selectEnd)
  1035.       end
  1036.       return nil
  1037.     end
  1038.   tf.doCut=function(tf)
  1039.       local text=tf:doCopy()
  1040.       tf:removeSelected()
  1041.       tf:draw()
  1042.       return text
  1043.     end
  1044.  
  1045.   tf.onClick=function(tf,tx,ty,button)
  1046.       tf.selectEnd=0
  1047.       tf.cursorIndex=math.min(tx+tf.scrollIndex-1,len(tf.text)+1)
  1048.       tf:draw()
  1049.     end
  1050.  
  1051.   tf.onBeginDrag=function(tf,tx,ty,button)
  1052.       --drag events are in gui coords, not component, so correct
  1053.       if button==0 then
  1054.         tf.selectOrigin=math.min(tx+tf.scrollIndex,len(tf.text)+1)
  1055.         tf.dragging=tf.selectOrigin
  1056.         term.setCursorBlink(false)
  1057.  
  1058.       end
  1059.     end
  1060.  
  1061.   tf.onDrag=function(tf,tx,ty)
  1062.       if tf.dragging then
  1063.         local dragX=tx
  1064.         local prevCI=tf.cursorIndex
  1065.         tf.cursorIndex=math.max(math.min(dragX+tf.scrollIndex-1,len(tf.text)+1),1)
  1066.         if prevCI~=cursorIndex then
  1067.           updateSelect(tf,tf.selectOrigin)
  1068.           tf:draw()
  1069.         end
  1070.         if dragX<1 or dragX>tf.width then
  1071.           --it's dragging outside.
  1072.           local dragMagnitude=dragX-1
  1073.           if dragMagnitude>=0 then
  1074.             dragMagnitude=dragX-tf.width
  1075.           end
  1076.           local dragDir=dragMagnitude<0 and -1 or 1
  1077.           dragMagnitude=math.abs(dragMagnitude)
  1078.           local dragStep, dragRate
  1079.           if dragMagnitude>5 then
  1080.             dragRate=.1
  1081.             dragStep=dragMagnitude/5*dragDir
  1082.           else
  1083.             dragRate=(6-dragMagnitude)/10
  1084.             dragStep=dragDir
  1085.           end
  1086.           if tf.dragTimer then
  1087.             event.cancel(tf.dragTimer)
  1088.           end
  1089.           tf.dragTimer=event.timer(dragRate,function()
  1090.               assert(tf.gui.running)
  1091.                 tf.cursorIndex=math.max(math.min(tf.cursorIndex+dragStep,len(tf.text)+1),1)
  1092.               if tf.cursorIndex<tf.scrollIndex then
  1093.                 tf.scrollIndex=tf.cursorIndex
  1094.               elseif tf.cursorIndex>tf.scrollIndex+tf.width-2 then
  1095.                 tf.scrollIndex=tf.cursorIndex-tf.width+1
  1096.               end
  1097.               updateSelect(tf,tf.selectOrigin)
  1098.               tf:draw()
  1099.             end, math.huge)
  1100.         else
  1101.           if tf.dragTimer then
  1102.             event.cancel(tf.dragTimer)
  1103.           end
  1104.         end
  1105.  
  1106.       end
  1107.     end
  1108.  
  1109.   tf.onDrop=function(tf)
  1110.     if tf.dragging then
  1111.       tf.dragging=nil
  1112.       if tf.dragTimer then
  1113.         event.cancel(tf.dragTimer)
  1114.       end
  1115.       local screenX,screenY=tf:getScreenPosition()
  1116.       term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  1117.       term.setCursorBlink(true)
  1118.     end
  1119.   end
  1120.  
  1121.   tf.keyHandler=function(tfclear,char,code)
  1122.       local screenX,screenY=tf:getScreenPosition()
  1123.       local dirty=false
  1124.       if not keyboard.isControl(char) then
  1125.         tf:insertText(unicode.char(char))
  1126.         dirty=true
  1127.       elseif code==28 and tf.tabNext then
  1128.         gui:changeFocusTo(tf.tabNext)
  1129.       elseif code==keyboard.keys.left then
  1130.         local prevCI=tf.cursorIndex
  1131.         if tf.cursorIndex>1 then
  1132.           tf.cursorIndex=tf.cursorIndex-1
  1133.           if tf.cursorIndex<tf.scrollIndex then
  1134.             tf.scrollIndex=math.max(1,tf.scrollIndex-math.floor(tf.width/3))
  1135.             dirty=true
  1136.           else
  1137.             term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  1138.           end
  1139.           term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  1140.         end
  1141.         if keyboard.isShiftDown() then
  1142.           updateSelect(tf,prevCI)
  1143.           dirty=true
  1144.         elseif tf.selectEnd~=0 then
  1145.           tf.selectEnd=0
  1146.           dirty=true
  1147.         end
  1148.       elseif code==keyboard.keys.right then
  1149.         local prevCI=tf.cursorIndex
  1150.         if tf.cursorIndex<len(tf.text)+1 then
  1151.           tf.cursorIndex=tf.cursorIndex+1
  1152.  
  1153.           if tf.cursorIndex>=tf.scrollIndex+tf.width then
  1154.             tf.scrollIndex=tf.scrollIndex+math.floor(tf.width/3)
  1155.             dirty=true
  1156.           else
  1157.             term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  1158.           end
  1159.         end
  1160.         if keyboard.isShiftDown() then
  1161.           updateSelect(tf,prevCI)
  1162.           dirty=true
  1163.         elseif tf.selectEnd~=0 then
  1164.           tf.selectEnd=0
  1165.           dirty=true
  1166.         end
  1167.       elseif code==keyboard.keys.home then
  1168.         local prevCI=tf.cursorIndex
  1169.         if tf.cursorIndex~=1 then
  1170.           tf.cursorIndex=1
  1171.           if tf.scrollIndex~=1 then
  1172.             tf.scrollIndex=1
  1173.             dirty=true
  1174.           else
  1175.             term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  1176.           end
  1177.         end
  1178.         if keyboard.isShiftDown() then
  1179.           updateSelect(tf,prevCI)
  1180.           dirty=true
  1181.         elseif tf.selectEnd~=0 then
  1182.           tf.selectEnd=0
  1183.           dirty=true
  1184.         end
  1185.       elseif code==keyboard.keys["end"] then
  1186.         local prevCI=tf.cursorIndex
  1187.         if tf.cursorIndex~=len(tf.text)+1 then
  1188.           tf.cursorIndex=len(tf.text)+1
  1189.           if tf.scrollIndex+tf.width-1<=tf.cursorIndex then
  1190.             tf.scrollIndex=tf.cursorIndex-tf.width+1
  1191.             dirty=true
  1192.           else
  1193.             term.setCursor(screenX+tf.cursorIndex-tf.scrollIndex,screenY)
  1194.           end
  1195.         end
  1196.         if keyboard.isShiftDown() then
  1197.           updateSelect(tf,prevCI)
  1198.           dirty=true
  1199.         elseif tf.selectEnd~=0 then
  1200.           tf.selectEnd=0
  1201.           dirty=true
  1202.         end
  1203.       elseif code==keyboard.keys.back then
  1204.         if tf.selectEnd~=0 then
  1205.           tf:removeSelected()
  1206.           dirty=true
  1207.         elseif tf.cursorIndex>1 then
  1208.           tf.text=unicode.sub(tf.text,1,tf.cursorIndex-2)..unicode.sub(tf.text,tf.cursorIndex)
  1209.           tf.cursorIndex=tf.cursorIndex-1
  1210.           if tf.cursorIndex<tf.scrollIndex then
  1211.             tf.scrollIndex=math.max(1,tf.scrollIndex-math.floor(tf.width/3))
  1212.           end
  1213.           dirty=true
  1214.         end
  1215.       elseif code==keyboard.keys.delete then
  1216.         if tf.selectEnd~=0 then
  1217.           tf:removeSelected()
  1218.           dirty=true
  1219.         elseif tf.cursorIndex<=len(tf.text) then
  1220.           tf.text=unicode.sub(tf.text,1,tf.cursorIndex-1)..unicode.sub(tf.text,tf.cursorIndex+1)
  1221.           dirty=true
  1222.         end
  1223.       end
  1224.       if dirty then
  1225.         tf:draw()
  1226.       end
  1227.     end
  1228.  
  1229.  
  1230.   tf.gotFocus=function()
  1231.     --we may want to scroll here, cursor to end of text on gaining focus
  1232.     local effText=tf.text
  1233.  
  1234.     if len(effText)>tf.width then
  1235.       tf.scrollIndex=len(effText)-tf.width+3
  1236.     else
  1237.       tf.scrollIndex=1
  1238.     end
  1239.     tf.cursorIndex=len(effText)+1
  1240.     tf:draw()
  1241.   end
  1242.  
  1243.   tf.lostFocus=function()
  1244.     tf.scrollIndex=1
  1245.     tf.selectEnd=0
  1246.     term.setCursorBlink(false)
  1247.     tf:draw()
  1248.   end
  1249.  
  1250.   gui:addComponent(tf)
  1251.   return tf
  1252. end
  1253.  
  1254. local function updateScrollBarGrip(sb)
  1255.   local gripStart,gripEnd
  1256.   local pos,max,length=sb.scrollPos,sb.scrollMax,sb.length
  1257.  
  1258.   --grip size
  1259.   -- gripSize / height-2 == height / scrollMax
  1260.   local gripSize=math.max(1,math.min(math.floor(math.min(1,length / (max+length-2)) * (length-2)),length-2))
  1261.   if gripSize==length-2 then
  1262.     --grip fills everything
  1263.     sb.gripStart=1
  1264.     sb.gripEnd=length-2
  1265.   else
  1266.     --grip position
  1267.     pos=round((pos-1)/(max-1)*(length-2-gripSize))+1
  1268.  
  1269.     --from pos and size, figure gripStart and gripEnd
  1270.     sb.gripStart=pos
  1271.     sb.gripEnd=pos+gripSize-1
  1272.   end
  1273.  
  1274. end
  1275.  
  1276.  
  1277. local function scrollBarBase(gui,x,y,width,height,scrollMax,onScroll)
  1278.   local sb=baseComponent(gui,x,y,width,height,"scrollbar",false)
  1279.   sb.scrollMax=scrollMax or 1
  1280.   sb.scrollPos=1
  1281.   sb.length=math.max(sb.width,sb.height)
  1282.   assert(sb.length>2,"Scroll bars must be at least 3 long.")
  1283.  
  1284.   sb.onScroll=onScroll
  1285.  
  1286.  
  1287.   updateScrollBarGrip(sb)
  1288.  
  1289.   sb._onClick=function(sb,tpos,button)
  1290.       local newPos=sb.scrollPos
  1291.       if tpos==1 then
  1292.         --up button
  1293.         newPos=math.max(1,sb.scrollPos-1)
  1294.       elseif tpos==sb.length then
  1295.         newPos=math.min(sb.scrollMax,sb.scrollPos+1)
  1296.       elseif tpos<sb.gripStart then
  1297.         --before grip, scroll up a page
  1298.         newPos=math.max(1,sb.scrollPos-sb.length+1)
  1299.       elseif tpos>sb.gripEnd then
  1300.         --before grip, scroll up a page
  1301.         newPos=math.min(sb.scrollMax,sb.scrollPos+sb.length-1)
  1302.       end
  1303.       if newPos~=sb.scrollPos then
  1304.         sb.scrollPos=newPos
  1305.         updateScrollBarGrip(sb)
  1306.         sb:draw()
  1307.         if sb.onScroll then
  1308.           sb:onScroll(sb.scrollPos)
  1309.         end
  1310.       end
  1311.     end
  1312.  
  1313.   sb._onBeginDrag=function(sb,tpos,button)
  1314.       if button==0 and sb.length>3 and (sb.length/sb.scrollMax<1) then
  1315.         sb.dragging=true
  1316.         sb.lastDragPos=tpos
  1317.       end
  1318.     end
  1319.  
  1320.   sb._onDrag=function(sb,tpos)
  1321.       if sb.dragging then
  1322.         local py=sb.lastDragPos
  1323.         local dif=tpos-py
  1324.         if dif~=0 then
  1325.           --calc the grip position for this y position
  1326.           --first clamp to range of scroll area
  1327.           local scroll=math.min(math.max(tpos,2),sb.length-1)-2
  1328.           --scale to 0-1
  1329.           scroll=scroll/(sb.length-3)
  1330.           --scale to maxScroll
  1331.           scroll=round(scroll*(sb.scrollMax-1)+1)
  1332.           --see if this is different from our current scroll position
  1333.           if scroll~=sb.scrollPos then
  1334.             --it is. We actually scrolled, then.
  1335.             sb.scrollPos=scroll
  1336.             updateScrollBarGrip(sb)
  1337.             sb:draw()
  1338.             if onScroll then
  1339.               sb:onScroll()
  1340.             end
  1341.           end
  1342.         end
  1343.       end
  1344.     end
  1345.  
  1346.   sb.onDrop=function(sb)
  1347.       sb.dragging=false
  1348.     end
  1349.  
  1350.   return sb
  1351. end
  1352.  
  1353. local function addScrollBarV(gui,x,y,height,scrollMax, onScroll)
  1354.   local sb=scrollBarBase(gui,x,y,1,height,scrollMax,onScroll)
  1355.  
  1356.   sb.draw=drawScrollBarV
  1357.  
  1358.   sb.onClick=function(sb,tx,ty,button) sb:_onClick(ty,button) end
  1359.   sb.onBeginDrag=function(sb,tx,ty,button) sb:_onBeginDrag(ty,button) end
  1360.   sb.onDrag=function(sb,tx,ty,button) sb:_onDrag(ty,button) end
  1361.  
  1362.   gui:addComponent(sb)
  1363.   return sb
  1364. end
  1365.  
  1366. local function addScrollBarH(gui,x,y,width,scrollMax,onScroll)
  1367.  
  1368.   local sb=scrollBarBase(gui,x,y,width,1,scrollMax,onScroll)
  1369.  
  1370.   sb.draw=drawScrollBarH
  1371.  
  1372.   sb.onClick=function(sb,tx,ty,button) sb:_onClick(tx,button) end
  1373.   sb.onBeginDrag=function(sb,tx,ty,button) sb:_onBeginDrag(tx,button) end
  1374.   sb.onDrag=function(sb,tx,ty,button) sb:_onDrag(tx,button) end
  1375.  
  1376.   gui:addComponent(sb)
  1377.   return sb
  1378. end
  1379.  
  1380.  
  1381. local function compositeBase(gui,x,y,width,height,objType,focusable)
  1382.   local comp=baseComponent(gui,x,y,width,height,objType,focusable)
  1383.   comp.bodyX,comp.bodyY,comp.bodyW,comp.bodyH=calcBody(comp)
  1384.  
  1385.   comp.components={}
  1386.  
  1387.   function comp.addComponent(obj,component)
  1388.     obj.components[#obj.components+1]=component
  1389.   end
  1390.  
  1391.   return comp
  1392. end
  1393.  
  1394. local function scrollListBox(sb)
  1395.   local lb=sb.listBox
  1396.  
  1397.   for i=1,#lb.labels do
  1398.     local listI=sb.scrollPos+i-1
  1399.     local l=lb.labels[i]
  1400.     if listI<=#lb.list then
  1401.       l.state=lb.selectedLabel==listI and "selected" or nil
  1402.       l.text=lb.list[listI]
  1403.     else
  1404.       l.state=nil
  1405.       l.text=""
  1406.     end
  1407.     l:draw()
  1408.   end
  1409. end
  1410.  
  1411.  
  1412. local function clickListBox(lb,tx,ty,button)
  1413.   if tx==lb.width then
  1414.     lb.scrollBar:_onClick(ty,button)
  1415.   else
  1416.     tx,ty=correctForBorder(lb,tx,ty)
  1417.     if ty>=1 and ty<=lb.bodyH then
  1418.       --ty is now index of the label clicked on
  1419.       --but is it valid?
  1420.       if ty<=#lb.list then
  1421.         lb:select(ty+lb.scrollBar.scrollPos-1)
  1422.       end
  1423.     end
  1424.   end
  1425.  
  1426. end
  1427.  
  1428. local function listBoxSelect(lb,index)
  1429.   if index<1 or index>#lb.list then
  1430.     error("index out of range to listBoxSelect",2)
  1431.   end
  1432.   local prevSelected=lb.selectedLabel
  1433.   if index==prevSelected then
  1434.     return
  1435.   end
  1436.  
  1437.   lb.selectedLabel=index
  1438.   --do I need to scroll?
  1439.   local scrolled=false
  1440.   local scrollIndex=lb.scrollBar.scrollPos
  1441.   if index<scrollIndex then
  1442.     scrollIndex=index
  1443.     scrolled=true
  1444.   elseif index>scrollIndex+lb.bodyH-1 then
  1445.     scrollIndex=index-lb.bodyH+1
  1446.     scrolled=true
  1447.   end
  1448.   if scrolled then
  1449.     --update scroll position
  1450.     lb.scrollBar.scrollPos=scrollIndex
  1451.     scrollListBox(lb.scrollBar)
  1452.   else
  1453.     if prevSelected>=scrollIndex and prevSelected<=scrollIndex+lb.bodyH-1 then
  1454.       local pl=lb.labels[prevSelected-scrollIndex+1]
  1455.       pl.state=nil
  1456.       pl:draw()
  1457.     end
  1458.     local l=lb.labels[index-scrollIndex+1]
  1459.     l.state="selected"
  1460.     l:draw()
  1461.   end
  1462.  
  1463.   if lb.onChange then
  1464.     lb:onChange(prevSelected,index)
  1465.   end
  1466. end
  1467.  
  1468.  
  1469. local function getListBoxSelected(lb)
  1470.   return lb.list[lb.selectedLabel]
  1471. end
  1472.  
  1473. local function updateListBoxList(lb,newList)
  1474.   lb.list=newList
  1475.   lb.scrollBar.scrollPos=1
  1476.   lb.scrollBar.scrollMax=math.max(1,#newList-lb.bodyH+1)
  1477.   updateScrollBarGrip(lb.scrollBar)
  1478.   lb.selectedLabel=1
  1479.   scrollListBox(lb.scrollBar)
  1480.   lb:draw()
  1481. end
  1482.  
  1483. local function addListBox(gui,x,y,width,height,list)
  1484.   local lb=compositeBase(gui,x,y,width,height,"listbox",true)
  1485.   lb.list=list
  1486.  
  1487.   lb.scrollBar=addScrollBarV(lb,lb.bodyW,lb.bodyY,lb.bodyH,math.max(1,#list-lb.bodyH+1),scrollListBox)
  1488.   lb.scrollBar.class="listbox"
  1489.   lb.scrollBar.listBox=lb
  1490.  
  1491.   lb.scrollBar.posY=lb.posY-lb.bodyY+1
  1492.   lb.scrollBar.height=lb.height
  1493.   lb.scrollBar.length=lb.height
  1494.  
  1495.   lb.selectedLabel=1
  1496.   updateScrollBarGrip(lb.scrollBar)
  1497.  
  1498.   lb.labels={}
  1499.   lb.list=list
  1500.   lb.onBeginDrag=function(lb,tx,ty,button) if tx==lb.width then lb.scrollBar:_onBeginDrag(ty,button) end end
  1501.   lb.onDrag=function(lb,...) lb.scrollBar:onDrag(...) end
  1502.   lb.onDrop=function(lb,...) lb.scrollBar:onDrop(...) end
  1503.  
  1504.   for i=1,lb.bodyH do
  1505.     lb.labels[i]=addLabel(lb,1,i,lb.bodyW-1,list[i] or "")
  1506.     lb.labels[i].class="listbox"
  1507.   end
  1508.   lb.labels[1].state="selected"
  1509.  
  1510.   lb.select=listBoxSelect
  1511.   lb.getSelected=getListBoxSelected
  1512.  
  1513.   lb.keyHandler=function(lb,char,code)
  1514.     if code==keyboard.keys.up then
  1515.       if lb.selectedLabel>1 then
  1516.         lb:select(lb.selectedLabel-1)
  1517.       end
  1518.     elseif code==keyboard.keys.down then
  1519.       if lb.selectedLabel<#lb.list then
  1520.         lb:select(lb.selectedLabel+1)
  1521.       end
  1522.     elseif code==keyboard.keys.enter and lb.onEnter then
  1523.       lb:onEnter()
  1524.     end
  1525.   end
  1526.  
  1527.   lb.updateList=updateListBoxList
  1528.  
  1529.   lb.onClick=clickListBox
  1530.   lb.draw=function(lb)
  1531.     if not lb:isHidden() then
  1532.       local styles=getAppliedStyles(lb)
  1533.       drawBorder(lb,styles)
  1534.       lb.scrollBar:draw()
  1535.       for i=1,#lb.labels do
  1536.         lb.labels[i]:draw()
  1537.       end
  1538.     end
  1539.   end
  1540.  
  1541.   gui:addComponent(lb)
  1542.   return lb
  1543. end
  1544.  
  1545.  
  1546.  
  1547. function gml.create(x,y,width,height,renderTarget)
  1548.  
  1549.   local newGui=compositeBase(screen,x,y,width,height,"gui",false)
  1550.   newGui.handlers={}
  1551.   newGui.hidden=true
  1552.   newGui.renderTarget=gfxbuffer.create(renderTarget)
  1553.  
  1554.   local running=false
  1555.   function newGui.close()
  1556.     computer.pushSignal("gui_close")
  1557.   end
  1558.  
  1559.   function newGui.addComponent(obj,component)
  1560.     newGui.components[#obj.components+1]=component
  1561.     if obj.focusElement==nil and component.focusable then
  1562.       component.state="focus"
  1563.       obj.focusElement=component
  1564.     end
  1565.   end
  1566.  
  1567.  
  1568.   newGui.addHandler=guiAddHandler
  1569.  
  1570.   function newGui.redrawRect(gui,x,y,w,h)
  1571.     local fillCh,fillFG,fillBG=findStyleProperties(newGui,"fill-ch","fill-color-fg","fill-color-bg")
  1572.     local blank=(fillCh):rep(w)
  1573.     gui.renderTarget.setForeground(fillFG)
  1574.     gui.renderTarget.setBackground(fillBG)
  1575.  
  1576.     x=x+newGui.bodyX-1
  1577.     for y=y+newGui.bodyY-1,y+h+newGui.bodyY-2 do
  1578.       gui.renderTarget.set(x,y,blank)
  1579.     end
  1580.   end
  1581.  
  1582.   function newGui.changeFocusTo(gui,target)
  1583.     if gui.focusElement then
  1584.       gui.focusElement.state=nil
  1585.       if gui.focusElement.lostFocus then
  1586.         gui.focusElement.lostFocus()
  1587.       elseif not gui.hidden then
  1588.         gui.focusElement:draw()
  1589.       end
  1590.     end
  1591.     gui.focusElement=target
  1592.     target.state="focus"
  1593.     if target.gotFocus then
  1594.       target.gotFocus()
  1595.     elseif not gui.hidden then
  1596.       target:draw()
  1597.     end
  1598.   end
  1599.  
  1600.   newGui.run=runGui
  1601.   newGui.contains=contains
  1602.   newGui.addLabel=addLabel
  1603.   newGui.addButton=addButton
  1604.   newGui.addTextField=addTextField
  1605.   newGui.addScrollBarV=addScrollBarV
  1606.   newGui.addScrollBarH=addScrollBarH
  1607.   newGui.addListBox=addListBox
  1608.   newGui.draw=function(gui)
  1609.       local styles=getAppliedStyles(gui)
  1610.       local bodyX,bodyY,bodyW,bodyH=drawBorder(gui,styles)
  1611.       local fillCh,fillFG,fillBG=extractProperties(gui,styles,"fill-ch","fill-color-fg","fill-color-bg")
  1612.  
  1613.       gui.renderTarget.setForeground(fillFG)
  1614.       gui.renderTarget.setBackground(fillBG)
  1615.       term.setCursorBlink(false)
  1616.  
  1617.       gui.renderTarget.fill(bodyX,bodyY,bodyW,bodyH,fillCh)
  1618.  
  1619.       for i=1,#gui.components do
  1620.         gui.components[i]:draw()
  1621.         gui.renderTarget:flush()
  1622.       end
  1623.  
  1624.       if gui.onDraw then
  1625.         gui.onDraw()
  1626.       end
  1627.     end
  1628.  
  1629.   return newGui
  1630. end
  1631.  
  1632.  
  1633.  
  1634.  
  1635. --**********************
  1636.  
  1637. defaultStyle=gml.loadStyle("default")
  1638. screen.style=defaultStyle
  1639.  
  1640. return gml
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement