Advertisement
Light83

Gml Lua (OpenComputer LanteaCraft)

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