mrWhiskasss

gml.lua

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