├── README.md ├── gitrepo.lua ├── gml ├── bin │ ├── gmltest.lua │ └── savedlg.lua ├── lib │ ├── canvas.lua │ ├── colorutils.lua │ ├── default.gss │ ├── gfxbuffer.lua │ ├── gml.lua │ └── gmlDialogs.lua └── usr │ ├── gmltest.gml │ ├── output.lua │ └── test.lua └── programs.cfg /README.md: -------------------------------------------------------------------------------- 1 | Gophers-Programs 2 | ================ 3 | 4 | ### Contents 5 | 6 | #### GML 7 | 8 | A gui library, with example programs. In development, fully usable but with only a few gui components implemented so far, already fully capable of doing dialog boxes and basic forms. 9 | 10 | See the wiki for documentation, or just head to the /bin directory to look at some sample programs. 11 | 12 | 13 | #### gitrepo 14 | 15 | simple command-line program to pull an entire git repo, all files and directories. Make sure you have enough disk space, and be careful of the rather low unauthorized api usage rate cap on the git api, I didn't feel like making enough sense of OAuth to even determine with certainty whether it's possible to authorize (my gut says no, my head says I'm not sure I'd want to be giving OC my github account credentials anyway). 16 | 17 | example: 18 | 19 | gitrepo OpenPrograms/Gopher-Programs /hd1/gp 20 | 21 | This one is complete DWTFYW license. Steal the code, modify it, extend it, take full credit, whatever. There's a hackish but effective json->lua table converter in there, as well as an unused base64 decoding function which has been tested against github's base64 encoded data included in some gitapi json responses (be sure to strip "\n"s and the trailing "=" from the encoded data first, for some reason github thinks we need neatly line-wrapped base64). Go to town, have fun. 22 | 23 | ### Contributions 24 | 25 | If you want to modify this code, feel free! I've kindof moved on but I'll try to keep an eye on pull requests, and if someone is interested in doing more than minor tweaks/fixes, I'll see about getting you added to the repo and such... 26 | -------------------------------------------------------------------------------- /gitrepo.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | git repo downloader 3 | 4 | downloads the full contents of a git repo to a directory on the computer. 5 | --]] 6 | 7 | 8 | local internet=require("internet") 9 | local text=require("text") 10 | local filesystem=require("filesystem") 11 | local unicode=require("unicode") 12 | local term=require("term") 13 | local event=require("event") 14 | local keyboard=require("keyboard") 15 | 16 | 17 | local repo,target 18 | 19 | local args={...} 20 | 21 | if #args<1 or #args>2 then 22 | print("Usage: gitrepo []]\nrepo should be the owner/repo, ex, \"OpenPrograms/Gopher-Programs\"\ntargetdir is an optional local path to download to, default will be /tmp//") 23 | return 24 | end 25 | 26 | repo=args[1] 27 | if not repo:match("^[%w-.]*/[%w-.]*$") then 28 | print('"'..args[1]..'" does not look like a valid repo identifier.\nShould be /') 29 | return 30 | end 31 | 32 | target=args[2] 33 | target=target and ("/"..target:match("^/?(.-)/?$").."/") or "/tmp/"..repo 34 | if filesystem.exists(target) then 35 | if not filesystem.isDirectory(target) then 36 | print("target directory already exists and is not a directory.") 37 | return 38 | end 39 | if filesystem.get(target).isReadOnly() then 40 | print("target directory is read-only.") 41 | return 42 | end 43 | else 44 | if not filesystem.makeDirectory(target) then 45 | print("target directory is read-only") 46 | return 47 | end 48 | end 49 | 50 | 51 | 52 | -- this isn't acually used, but it is tested and works on decoding the base64 encoded data that github 53 | --sends for some queries, leaving it in here for possible future/related use, might be able to pull 54 | --and display difs and things like that? 55 | local symb="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 56 | 57 | function decode64(text) 58 | local val,bits=0,0 59 | local output="" 60 | for ch in text:gmatch(".") do 61 | if symb:find(ch) then 62 | --print("ch "..ch.."-> "..(symb:find(ch)-1)) 63 | val=bit32.lshift(val,6)+symb:find(ch)-1 64 | else 65 | print(ch.."?") 66 | return 67 | end 68 | bits=bits+6 69 | --print("bits : "..bits) 70 | --print(string.format("val : 0x%04x",val)) 71 | if bits>=8 then 72 | local och=unicode.char(bit32.rshift(val,bits-8)) 73 | --print("os<<"..och) 74 | --print("& with "..(2^(bits-8)-1)) 75 | val=bit32.band(val,2^(bits-8)-1) 76 | bits=bits-8 77 | --print(string.format("val : 0x%04x",val)) 78 | output=output..och 79 | end 80 | end 81 | return output 82 | end 83 | 84 | 85 | 86 | local function gitContents(repo,dir) 87 | print("fetching contents for "..repo..dir) 88 | local url="https://api.github.com/repos/"..repo.."/contents"..dir 89 | local result,response=pcall(internet.request,url) 90 | local raw="" 91 | local files={} 92 | local directories={} 93 | 94 | if result then 95 | for chunk in response do 96 | raw=raw..chunk 97 | end 98 | else 99 | error("you've been cut off. Serves you right.") 100 | end 101 | 102 | response=nil 103 | raw=raw:gsub("%[","{"):gsub("%]","}"):gsub("(\".-\"):(.-[,{}])",function(a,b) return "["..a.."]="..b end) 104 | local t=load("return "..raw)() 105 | 106 | for i=1,#t do 107 | if t[i].type=="dir" then 108 | table.insert(directories,dir.."/"..t[i].name) 109 | 110 | local subfiles,subdirs=gitContents(repo,dir.."/"..t[i].name) 111 | for i=1,#subfiles do 112 | table.insert(files,subfiles[i]) 113 | end 114 | for i=1,#subdirs do 115 | table.insert(directories,subdirs[i]) 116 | end 117 | else 118 | files[#files+1]=dir.."/"..t[i].name 119 | end 120 | end 121 | 122 | return files, directories 123 | end 124 | 125 | local files,dirs=gitContents(repo,"") 126 | 127 | for i=1,#dirs do 128 | print("making dir "..target..dirs[i]) 129 | if filesystem.exists(target..dirs[i]) then 130 | if not filesystem.isDirectory(target..dirs[i]) then 131 | print("error: directory "..target..dirs[i].." blocked by file with the same name") 132 | return 133 | end 134 | else 135 | filesystem.makeDirectory(target..dirs[i]) 136 | end 137 | end 138 | 139 | local replaceMode="ask" 140 | for i=1,#files do 141 | local replace=nil 142 | if filesystem.exists(target..files[i]) then 143 | if filesystem.isDirectory(target..files[i]) then 144 | print("Error: file "..target..files[i].." blocked by directory with same name!") 145 | return 146 | end 147 | if replaceMode=="always" then 148 | replace=true 149 | elseif replaceMode=="never" then 150 | replace=false 151 | else 152 | print("\nFile "..target..files[i].." already exists.\nReplace with new version?") 153 | local response="" 154 | while replace==nil do 155 | term.write("yes,no,always,skip all[ynAS]: ") 156 | local char 157 | repeat 158 | _,_,char=event.pull("key_down") 159 | until not keyboard.isControl(char) 160 | char=unicode.char(char) 161 | print(char) 162 | if char=="A" then 163 | replaceMode="always" 164 | replace=true 165 | char="y" 166 | elseif char=="S" then 167 | replaceMode="never" 168 | replace=false 169 | char="n" 170 | elseif char:lower()=="y" then 171 | replace=true 172 | elseif char:lower()=="n" then 173 | replace=false 174 | else 175 | print("invalid response.") 176 | end 177 | end 178 | end 179 | if replace then 180 | filesystem.remove(target..files[i]) 181 | end 182 | end 183 | if replace~=false then 184 | print("downloading "..files[i]) 185 | local url="https://raw.github.com/"..repo.."/master"..files[i] 186 | local result,response=pcall(internet.request,url) 187 | if result then 188 | local raw="" 189 | for chunk in response do 190 | raw=raw..chunk 191 | end 192 | print("writing to "..target..files[i]) 193 | local file=io.open(target..files[i],"w") 194 | file:write(raw) 195 | file:close() 196 | 197 | else 198 | print("failed, skipping") 199 | end 200 | end 201 | end 202 | 203 | 204 | -------------------------------------------------------------------------------- /gml/bin/gmltest.lua: -------------------------------------------------------------------------------- 1 | --just hrere to force reloading the api so I don't have to reboot 2 | package.loaded.gml=nil 3 | package.loaded.gfxbuffer=nil 4 | 5 | local gml=require("gml") 6 | local component=require("component") 7 | 8 | local gui=gml.create("center","center",32,19) 9 | 10 | local label=gui:addLabel("center",2,13,"Hello, World!") 11 | label:hide() 12 | 13 | local function toggleLabel() 14 | if label.visible then 15 | label:hide() 16 | else 17 | label:show() 18 | end 19 | end 20 | 21 | local textField=gui:addTextField("center",4,18) 22 | 23 | local button1=gui:addButton(4,6,10,1,"Toggle",toggleLabel) 24 | local button2=gui:addButton(-4,6,10,1,"Close",gui.close) 25 | 26 | gui:addHandler("key_down", 27 | function(event,addy,char,key) 28 | --ctrl-r 29 | if char==18 then 30 | local fg,bg=component.gpu.getForeground(), component.gpu.getBackground() 31 | label["text-color"]=math.random(0,0xffffff) 32 | label:draw() 33 | component.gpu.setForeground(fg) 34 | component.gpu.setBackground(bg) 35 | end 36 | end) 37 | 38 | local scrollBarV,scrollBarH 39 | local label2=gui:addLabel(-2,-2,7," 0, 0") 40 | 41 | local function setLabelToScroll() 42 | label2.text=string.format("%3s,%3s",scrollBarH.scrollPos,scrollBarV.scrollPos) 43 | label2:draw() 44 | end 45 | 46 | scrollBarV=gui:addScrollBarV(-1,1,16,100,setLabelToScroll) 47 | scrollBarH=gui:addScrollBarH(1,-1,29,100,setLabelToScroll) 48 | 49 | 50 | local listBox=gui:addListBox("center",8,16,8,{"one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen","twenty","twenty-one","twenty-two","twenty-three","twenty-four","twenty-five"}) 51 | 52 | gui:run() 53 | 54 | -------------------------------------------------------------------------------- /gml/bin/savedlg.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | save/load dialog gui 3 | 4 | **NOTE** This is the actual source of the filePicker function in the gmlDialogs library. 5 | There is no reason to reproduce this in your code, it is simple included as example code 6 | for using the gml library to create guis. 7 | If you want to use this file picker, just require gmlDialogs and call 8 | gmlDialogs.filePicker() 9 | 10 | See the wiki for documentation of the methods 11 | TODO: link here when doc exists... 12 | 13 | --]] 14 | package.loaded.gml=nil 15 | package.loaded.gfxbuffer=nil 16 | 17 | local gml=require("gml") 18 | local shell=require("shell") 19 | local filesystem=require("filesystem") 20 | 21 | function filePicker(mode,curDir,name,extension) 22 | checkArg(1,mode,"string") 23 | checkArg(2,path,"nil","string") 24 | checkArg(3,name,"nil","string") 25 | checkArg(4,extensions,"nil","string") 26 | 27 | curDir=curDir or "/" 28 | if not filesystem.exists(curDir) or not filesystem.isDirectory(curDir) then 29 | error("invalid path arg to filePicker",2) 30 | end 31 | 32 | name=name or "" 33 | 34 | if mode=="load" then 35 | mode=false 36 | elseif mode~="save" then 37 | error("Invalid mode arg to gml.filePicker, must be \"save\" or \"load\"",2) 38 | end 39 | 40 | local result=nil 41 | 42 | local gui=gml.create("center","center",50,16) 43 | 44 | gui:addLabel(1,1,14,"Common Folders") 45 | local commonDirList=gui:addListBox(1,2,16,11,{"/","/usr/","/tmp/","/lib/","/bin/"}) 46 | 47 | local contentsLabel=gui:addLabel(18,1,31,"contents of /") 48 | local directoryList=gui:addListBox(17,2,32,11,{}) 49 | 50 | local messageLabel=gui:addLabel(1,-1,30,"") 51 | messageLabel.class="error" 52 | 53 | local filename=gui:addTextField(22,-2,26,name) 54 | local fileLabel=gui:addLabel(17,-2,5,"File:") 55 | gui:addButton(-2,-1,8,1,mode and "Save" or "Open",function() 56 | --require an actual filename 57 | local ext=filename.text 58 | local t=curDir..filename.text 59 | ext=ext:match("%.([^/]*)$") 60 | local failed 61 | local function fail(msg) 62 | messageLabel.text=msg 63 | messageLabel:draw() 64 | failed=true 65 | end 66 | 67 | if mode then 68 | --saving 69 | if filesystem.get(curDir).isReadOnly() then 70 | fail("Directory is read-only") 71 | elseif extension then 72 | if ext then 73 | --has an extension, is it the right one? 74 | if ext~=extension then 75 | fail("Invalid extension, use "..extension) 76 | end 77 | else 78 | t=t.."."..extension 79 | end 80 | end 81 | else 82 | --loading 83 | if not filesystem.exists(t) then 84 | fail("That file doesn't exist") 85 | elseif extension then 86 | if ext and ext~=extension then 87 | fail("Invalid extension, use ."..extension) 88 | end 89 | end 90 | end 91 | 92 | if not failed then 93 | result=t 94 | gui.close() 95 | end 96 | end) 97 | 98 | gui:addButton(-11,-1,8,1,"Cancel",gui.close) 99 | 100 | local function updateDirectoryList(dir) 101 | local list={} 102 | curDir=dir 103 | if dir~="/" then 104 | list[1]=".." 105 | end 106 | local nextDir=#list+1 107 | for file in filesystem.list(dir) do 108 | if filesystem.isDirectory(file) then 109 | if file:sub(-1)~="/" then 110 | file=file.."/" 111 | end 112 | table.insert(list,nextDir,file) 113 | nextDir=nextDir+1 114 | else 115 | table.insert(list,file) 116 | end 117 | end 118 | curDir=dir 119 | directoryList:updateList(list) 120 | contentsLabel.text="contents of "..curDir 121 | contentsLabel:draw() 122 | end 123 | 124 | local function onDirSelect(lb,prevIndex,selIndex) 125 | updateDirectoryList(commonDirList:getSelected()) 126 | end 127 | 128 | commonDirList.onChange=onDirSelect 129 | local function onActivateItem() 130 | local selected=directoryList:getSelected() 131 | if selected==".." then 132 | selected=curDir:match("^(.*/)[^/]*/") 133 | else 134 | selected=curDir..selected 135 | end 136 | if filesystem.isDirectory(selected) then 137 | updateDirectoryList(selected) 138 | else 139 | filename.text=selected:match("([^/]*)$") 140 | gui:changeFocusTo(filename) 141 | end 142 | end 143 | 144 | directoryList.onDoubleClick=onActivateItem 145 | directoryList.onEnter=onActivateItem 146 | 147 | updateDirectoryList(curDir) 148 | 149 | gui:run() 150 | 151 | return result 152 | end 153 | 154 | print(filePicker("save","/usr/","derp.lua","lua")) 155 | 156 | --[[ 157 | ultimaely will integrate this into gml.lua and boil down to: 158 | 159 | filename = gml.filePicker(<"save" or "load"> [, startPath [, extensions ] ]) 160 | 161 | startPath will determine waht folder the file list view starts on 162 | if startPath is a file, rather than a folder, will start in the containing folder 163 | and select that file, populating the textfield with it. 164 | 165 | extensions, if specified, can be a string or list of strings, and the file list 166 | will show only files matching that extension. For save, the extension will also be 167 | automatically applied to the name, if it does not end with it already. 168 | 169 | 170 | 171 | 172 | --]] -------------------------------------------------------------------------------- /gml/lib/canvas.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | canvas library 4 | 5 | creates canvas objects, which can be used interchangably with the gpu for drawing. 6 | Store both the totall buffer and the set of differences since last presented to their 7 | parents, and do sorting and grouping to optimally draw those buffers up to their parents. 8 | 9 | --]] 10 | local component=require("component") 11 | local colorutils=require("colorutils") 12 | 13 | --copy these to file local, they're called a lot in performance-intensive loops 14 | local convColor_hto8=colorutils.convColor_hto8 15 | local convColor_hto4=colorutils.convColor_hto4 16 | local convColor_hto1=colorutils.convColor_hto1 17 | local convColor_8toh=colorutils.convColor_8toh 18 | local convColor_4toh=colorutils.convColor_4toh 19 | local convColor_1toh=colorutils.convColor_1toh 20 | 21 | local canvas={VERSION="1.0"} 22 | local canvasMeta={} 23 | 24 | local function round(x) 25 | return math.floor(x+.5) 26 | end 27 | 28 | 29 | 30 | local function canvas_initBuffer(canvas) 31 | local color="2 " 32 | if canvas.depth==8 then 33 | color="FF00 " 34 | elseif canvas.depth==4 then 35 | color="F0 " 36 | end 37 | 38 | canvas.buffer=color:rep(canvas.width*canvas.height) 39 | end 40 | 41 | local function canvas_posToIndex8(canvas,x,y) 42 | return (x-1+(y-1)*canvas.width)*5+1 43 | end 44 | 45 | local function canvas_posToIndex4(canvas,x,y) 46 | return (x-1+(y-1)*canvas.width)*3+1 47 | end 48 | 49 | local function canvas_posToIndex1(canvas,x,y) 50 | return (x-1+(y-1)*canvas.width)*2+1 51 | end 52 | 53 | local function colorToStr8(fg,bg) 54 | return string.format("%02x%02x",convColor_hto8(fg),convColor_hto8(bg)) 55 | end 56 | 57 | local function colorToStr4(fg,bg) 58 | return string.format("%x%x",convColor_hto8(fg),convColor_hto8(bg)) 59 | end 60 | 61 | local function colorToStr1(fg,bg) 62 | return convColor_hto8(fg)..convColor_hto8(bg) 63 | end 64 | 65 | function canvasMeta.strToSpan(canvas,string) 66 | local color=canvas.colorStr 67 | local outStr="" 68 | for ch in string:gmatch(".") do 69 | outStr=outStr..color..ch 70 | end 71 | return outStr 72 | end 73 | 74 | function canvasMeta.getResolution(canvas) 75 | return canvas.width, canvas.height 76 | end 77 | 78 | function canvasMeta.setResolution(canvas,width,height) 79 | assert(width==canvas.width and height==canvas.height, 80 | "unsupported resolution - canvases are not resizable") 81 | 82 | return false 83 | end 84 | 85 | function canvasMeta.maxResolution(canvas) 86 | return canvas.width,canvas.height 87 | end 88 | 89 | function canvasMeta.getDepth(canvas) 90 | return canvas.depth 91 | end 92 | 93 | function canvasMeta.setDepth(canvas) 94 | assert(depth==canvas.depth, 95 | "unsupported depth - canvas depth cannot be changed") 96 | 97 | return false 98 | end 99 | 100 | function canvasMeta.maxDepth(canvas) 101 | return canvas.depth 102 | end 103 | 104 | function canvasMeta.getSize(canvas) 105 | return 1,1 106 | end 107 | 108 | function canvasMeta.getBackground(canvas) 109 | return canvas.colorBackground 110 | end 111 | 112 | function canvasMeta.setBackground(canvas,color) 113 | local p=canvas.colorBackground 114 | canvas.colorBackground=color 115 | canvas.colorStr=canvas.colorToStr(canvas.colorForeground,canvas.colorBackground) 116 | return p 117 | end 118 | 119 | function canvasMeta.getForeground(canvas) 120 | return canvas.colorForeground 121 | end 122 | 123 | function canvasMeta.setForeground(canvas,color) 124 | local p=canvas.colorForeground 125 | canvas.colorForeground=color 126 | canvas.colorStr=canvas.colorToStr(canvas.colorForeground,canvas.colorBackground) 127 | return p 128 | end 129 | 130 | function canvasMeta.set(canvas,x,y,string) 131 | local index=canvas.posToIndex(x,y) 132 | local span=canvas.strToSpan(string) 133 | canvas.buffer=canvas.buffer:sub(1,index-1)..span..canvas.buffer:sub(index+#span) 134 | end 135 | 136 | local function canvas_get8(canvas,x,y) 137 | local index=canvas_posToIndex8(canvas,x,y) 138 | local pixel=canvas.buffer:sub(index,index+4) 139 | local fg,bg,ch=pixel:match("(%x%x)(%x%x)(.)") 140 | if not fg or not bg or not ch then 141 | error("err in canvas_get8, x="..x..", y="..y..", pixel=\""..pixel..'"') 142 | end 143 | return ch,convColor_8toh(tonumber(fg,16)),convColor_8toh(tonumber(bg,16)) 144 | end 145 | 146 | local function canvas_get4(canvas,x,y) 147 | local index=canvas_posToIndex4(canvas,x,y) 148 | local pixel=canvas.buffer:sub(index,index+2) 149 | local fg,bg,ch=pixel:match("(%x)(%x)(.)") 150 | return ch,convColor_4toh(tonumber(fg,16)),convColor_4toh(tonumber(bg,16)) 151 | end 152 | 153 | local function canvas_get1(canvas,x,y) 154 | local index=canvas_posToIndex1(canvas,x,y) 155 | local pixel=canvas.buffer:sub(index,index+2) 156 | local color,ch=pixel:match("(%x)(.)") 157 | return ch,color>1 and 0xffffff or 0, color%1==1 and 0xffffff or 0 158 | end 159 | 160 | function canvasMeta.copy(canvas,x,y,w,h,dx,dy) 161 | local sx,ex,xstep=x,x+w-1,1 162 | local sy,ey,ystep=y,y+h-1,1 163 | local pixelw=canvas.depth==8 and 5 or canvas.depth==4 and 3 or 2 164 | local function constrain(i,l,m,d) 165 | if i+l-1>m then 166 | l=m-l+1 167 | end 168 | if i+d+l-1>m then 169 | l=m-d-l+1 170 | end 171 | if i+d<1 then 172 | local t=1-d-i 173 | i=i+t 174 | l=l-t 175 | end 176 | return x,l 177 | end 178 | x,w=constrain(x,w,canvas.width,dx) 179 | y,h=constrain(y,h,canvas.height,dy) 180 | 181 | if dy>0 then 182 | sy,ey=ey,sy 183 | ystep=-1 184 | end 185 | 186 | print("sy="..sy..", ey="..ey..", sx="..sx..", ex="..ex) 187 | for y=sy,ey,ystep do 188 | --pull the whole substring for this row 189 | local si, ei=canvas.posToIndex(sx,y), canvas.posToIndex(ex,y)+pixelw-1 190 | local str=canvas.buffer:sub(si,ei) 191 | print("row "..y.." segment len : "..#str) 192 | si,ei=canvas.posToIndex(sx+dx,y+dy),canvas.posToIndex(ex+dx,y+dy)+pixelw-1 193 | canvas.buffer=canvas.buffer:sub(1,si-1)..str..canvas.buffer:sub(ei+1) 194 | end 195 | 196 | 197 | end 198 | 199 | function canvasMeta.fill(canvas,x,y,w,h,char) 200 | local line=(canvas.colorStr..char):rep(w) 201 | local lineLen=#line 202 | local xoff=(x-1)*(#canvas.colorStr+1)+1 203 | local yoffstep=canvas.width*(#canvas.colorStr+1) 204 | local yoff=(y-1)*yoffstep 205 | for y=y, y+h-1 do 206 | canvas.buffer=canvas.buffer:sub(1,xoff+yoff-1)..line..canvas.buffer:sub(xoff+yoff+lineLen) 207 | yoff=yoff+yoffstep 208 | end 209 | end 210 | 211 | function canvasMeta.draw(canvas,targX,targY) 212 | --TODO: make better. This is a quick'n'dirty to test the rest. 213 | local parent=canvas.parent 214 | local pfg,pbg=parent.getForeground(), parent.getBackground() 215 | for y=1,canvas.height do 216 | local str,cfg,cbg=canvas.get(1,y) 217 | local sx=1 218 | 219 | for x=2,canvas.width do 220 | local ch,fg,bg=canvas.get(x,y) 221 | if fg==cfg and bg==cbg then 222 | str=str..ch 223 | else 224 | parent.setForeground(cfg) 225 | parent.setBackground(cbg) 226 | parent.set(sx+targX-1,y+targY-1,str) 227 | str,cfg,cbg,sx=ch,fg,bg,x 228 | end 229 | end 230 | parent.setForeground(cfg) 231 | parent.setBackground(cbg) 232 | parent.set(sx+targX-1,y+targY-1,str) 233 | end 234 | 235 | parent.setForeground(pfg) 236 | parent.setBackground(pbg) 237 | 238 | end 239 | 240 | 241 | function canvas.create(width,height,depth,parent) 242 | parent=parent or component.gpu 243 | if width==nil then 244 | width,height=parent.getResolution() 245 | elseif height==nil then 246 | _,height=parent.getResolution() 247 | end 248 | 249 | depth=depth or parent.getDepth() 250 | 251 | local posToIndex=depth==8 and canvas_posToIndex8 or (depth==4 and canvas_posToIndex4 or canvas_posToIndex1) 252 | local get=depth==8 and canvas_get8 or (depth==4 and canvas_get4 or canvas_get1) 253 | 254 | local newCanvas={ 255 | colorForeground=0xffffff, 256 | colorBackground=0x000000, 257 | depth=depth, 258 | width=width, 259 | height=height, 260 | parent=parent, 261 | } 262 | 263 | 264 | --wrap suitable bit version 265 | newCanvas.posToIndex=function(...) return posToIndex(newCanvas,...) end 266 | newCanvas.get=function(...) return get(newCanvas,...) end 267 | --no canvas arg required, just pick the suitable one 268 | newCanvas.colorToStr=depth==8 and colorToStr8 or (depth==4 and colorToStr4 or colorToStr1) 269 | 270 | canvas_initBuffer(newCanvas) 271 | 272 | setmetatable(newCanvas,{__index=function(tbl,key) local v=canvasMeta[key] if type(v)=="function" then return function(...) return v(tbl,...) end end return v end}) 273 | 274 | newCanvas.colorStr=newCanvas.colorToStr(0xffffff,0x000000) 275 | 276 | return newCanvas 277 | end 278 | 279 | return canvas -------------------------------------------------------------------------------- /gml/lib/colorutils.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | colorutils 3 | 4 | a simple library with some useful functions for dealing with colors. 5 | Shared by gfxbuffer and canvas, and possibly gml later? 6 | 7 | --]] 8 | local colorutils = {VERSION="1.0"} 9 | 10 | function colorutils.convColor_hto8(hex) 11 | local r,g,b=bit32.rshift(hex,16),bit32.rshift(hex,8)%256,hex%256 12 | r=round(r*7/255) 13 | g=round(g*7/255) 14 | b=round(b*3/255) 15 | return r*32+g*4+b 16 | end 17 | 18 | function colorutils.convColor_8toh(c) 19 | local r,g,b=math.floor(c/32),math.floor(c/4)%8,c%4 20 | r=round(r*255/7) 21 | g=round(g*255/7) 22 | b=round(b*255/3) 23 | return r*65536+g*256+b 24 | end 25 | 26 | function colorutils.convColor_hto4(hex) 27 | local r,g,b=bit32.rshift(hex,16),bit32.rshift(hex,8)%256,hex%256 28 | r=round(r/255) 29 | g=round(g*3/255) 30 | b=round(b/255) 31 | return r*8+g*2+b 32 | end 33 | 34 | function colorutils.convColor_4toh(c) 35 | local r,g,b=math.floor(c/8),math.floor(c/2)%4,c%2 36 | r=r*0xff0000 37 | g=round(g*0xff/3)*256 38 | b=b*0x0000ff 39 | return r+g+b 40 | end 41 | 42 | function colorutils.convColor_hto1(hex) 43 | return hex<0 and 1 or 0 44 | end 45 | 46 | function colorutils.convColor_1toh(c) 47 | return c<0 and 0xffffff or 0 48 | end 49 | 50 | return colorutils -------------------------------------------------------------------------------- /gml/lib/default.gss: -------------------------------------------------------------------------------- 1 | /* default style for gml gui library */ 2 | 3 | * { 4 | fill-ch: " "; 5 | 6 | border: false; 7 | border-top: false; 8 | border-bottom: false; 9 | border-left: false; 10 | border-right: false; 11 | 12 | border-color-fg: 0xffffff; 13 | border-color-bg: 0xb4b4b4; 14 | border-ch-top: U+2550; 15 | border-ch-bottom: U+2550; 16 | border-ch-left: U+2551; 17 | border-ch-right: U+2551; 18 | border-ch-topleft: U+2554; 19 | border-ch-bottomleft: U+255a; 20 | border-ch-topright: U+2557; 21 | border-ch-bottomright: U+255d; 22 | 23 | } 24 | 25 | 26 | gui { 27 | border: true; 28 | border-top: true; 29 | border-bottom: true; 30 | border-left: true; 31 | border-right: true; 32 | 33 | fill-color-fg: 0xffffff; 34 | fill-color-bg: 0xb4b4b4; 35 | fill-ch: " "; 36 | 37 | border-color-fg: 0xffffff; 38 | border-color-bg: 0xb4b4b4; 39 | border-ch-top: U+2550; 40 | border-ch-bottom: U+2550; 41 | border-ch-left: U+2551; 42 | border-ch-right: U+2551; 43 | border-ch-topleft: U+2554; 44 | border-ch-bottomleft: U+255a; 45 | border-ch-topright: U+2557; 46 | border-ch-bottomright: U+255d; 47 | } 48 | 49 | label { 50 | text-color: 0x000000; 51 | text-background: 0xb4b4b4; 52 | } 53 | 54 | label.error { 55 | text-color: 0xff0000; 56 | } 57 | 58 | label.error!1 { 59 | text-color:0x000000; 60 | text-background: 0xffffff 61 | } 62 | 63 | button { 64 | text-color: 0xffffff; 65 | text-background: 0x000080; 66 | fill-color-fg: 0xb4b4b4; 67 | fill-color-bg: 0x000080; 68 | border: false; 69 | border-left: false; 70 | border-right: false; 71 | border-top: false; 72 | border-bottom: false; 73 | border-color-bg: 0x000080; 74 | border-color-fg: 0xb4b4b4; 75 | border-ch-left: "["; 76 | border-ch-right: "]"; 77 | } 78 | 79 | button:focus { 80 | text-color: 0xffffff; 81 | text-background: 0x0000ff; 82 | fill-color-fg: 0xffffff; 83 | fill-color-bg: 0x0000ff; 84 | border-color-bg: 0x0000ff; 85 | border-color-fg: 0xffffff; 86 | border: true; 87 | border-left: true; 88 | border-right: true; 89 | } 90 | 91 | textfield, textbox { 92 | selected-color: 0xffffff; 93 | selected-background: 0x0000ff; 94 | text-color: 0xffffff; 95 | text-background: 0x5a5a5a; 96 | } 97 | 98 | 99 | textbox:focus, textfield:focus { 100 | text-color: 0xffffff; 101 | text-background: 0x000000; 102 | } 103 | 104 | 105 | scrollbar { 106 | button-color-fg: 0xffffff; 107 | button-color-bg: 0x0000ff; 108 | button-ch-up: U+25b2; 109 | button-ch-down: U+25bc; 110 | button-ch-left: U+25c4; 111 | button-ch-right: U+25ba; 112 | grip-ch-v: U+2261; 113 | grip-ch-h: "|"; 114 | bar-ch: U+2592; 115 | bar-color-fg: 0xffffff; 116 | bar-color-bg: 0x000080; 117 | grip-color-fg: 0xffffff; 118 | grip-color-bg: 0x000080; 119 | } 120 | 121 | listbox { 122 | border:true; 123 | border-top:true; 124 | border-left:true; 125 | border-bottom:true; 126 | border-color-fg: 0xffffff; 127 | border-color-bg: 0x000080; 128 | } 129 | 130 | listbox:focus { 131 | border-color-fg: 0xffffff; 132 | border-color-bg: 0x0000FF; 133 | 134 | } 135 | 136 | label.listbox:selected { 137 | text-color: 0xffffff; 138 | text-background: 0x0000ff; 139 | 140 | } 141 | 142 | 143 | /*one-bit styles*/ 144 | *!1 { 145 | text-color:0xffffff; 146 | text-background:0x000000; 147 | 148 | border-color-fg: 0; 149 | border-color-bg: 0xffffff; 150 | 151 | fill-color-fg: 0xffffff; 152 | fill-color-bg: 0x000000; 153 | 154 | button-color-fg: 0x000000; 155 | button-color-bg: 0xffffff; 156 | bar-color-fg: 0xffffff; 157 | bar-color-bg: 0x000000; 158 | grip-color-fg: 0x000000; 159 | grip-color-bg: 0xffffff; 160 | 161 | } 162 | 163 | listbox!1 { 164 | border-color-fg: 0xffffff; 165 | border-color-bg: 0x000000; 166 | } 167 | listbox:focus!1 { 168 | border-color-fg: 0x000000; 169 | border-color-bg: 0xffffff; 170 | } 171 | 172 | textfield!1 { 173 | text-color: 0x000000; 174 | text-background: 0xffffff; 175 | } 176 | 177 | textfield:focus!1 { 178 | selected-color: 0x0; 179 | selected-background: 0xffffff; 180 | text-color: 0xffffff; 181 | text-background: 0x000000; 182 | } 183 | 184 | button!1 { 185 | text-color:0x000000; 186 | text-background:0xffffff; 187 | 188 | border-color-fg: 0; 189 | border-color-bg: 0xffffff; 190 | 191 | fill-color-fg: 0x000000; 192 | fill-color-bg: 0xffffff; 193 | } 194 | 195 | label.listbox:selected!1 { 196 | text-color: 0x000000; 197 | text-background: 0xffffff; 198 | 199 | } 200 | 201 | 202 | -------------------------------------------------------------------------------- /gml/lib/gfxbuffer.lua: -------------------------------------------------------------------------------- 1 | local component=require("component") 2 | local unicode=require("unicode") 3 | local len = unicode.len 4 | 5 | local buffer={VERSION="1.0"} 6 | local bufferMeta={} 7 | 8 | local debugPrint=function() end 9 | 10 | 11 | local function convColor_8toh(hex) 12 | local r,g,b=bit32.rshift(hex,16),bit32.rshift(hex,8)%256,hex%256 13 | r=round(r*7/255) 14 | g=round(g*7/255) 15 | b=round(b*3/255) 16 | return r*32+g*4+b 17 | end 18 | 19 | local function encodeColor(fg,bg) 20 | return bg*0x1000000+fg 21 | end 22 | 23 | local function decodeColor(c) 24 | return math.floor(c/0x1000000),c%0x1000000 25 | end 26 | 27 | 28 | 29 | function bufferMeta.getBackground(buffer) 30 | return convColor_8toh(buffer.colorBackground) 31 | end 32 | 33 | function bufferMeta.setBackground(buffer,color) 34 | local p=buffer.colorBackground 35 | buffer.colorBackground=color 36 | buffer.color=encodeColor(buffer.colorForeground,color) 37 | return p 38 | end 39 | 40 | function bufferMeta.getForeground(buffer) 41 | return buffer.colorForeground 42 | end 43 | 44 | function bufferMeta.setForeground(buffer,color) 45 | local p=buffer.colorForeground 46 | buffer.colorForeground=color 47 | buffer.color=encodeColor(color,buffer.colorBackground) 48 | return p 49 | end 50 | 51 | 52 | function bufferMeta.copy(buffer,x,y,w,h,dx,dy) 53 | buffer.flush() 54 | return buffer.parent.copy(x,y,w,h,dx,dy) 55 | end 56 | 57 | function bufferMeta.fill(buffer,x,y,w,h,char) 58 | buffer.flush() 59 | buffer.parent.setForeground(buffer.colorForeground) 60 | buffer.parent.setBackground(buffer.colorBackground) 61 | return buffer.parent.fill(x,y,w,h,char) 62 | end 63 | 64 | function bufferMeta.get(buffer,x,y) 65 | buffer.flush() 66 | return buffer.parent.get(x,y) 67 | end 68 | 69 | function bufferMeta.set(buffer,x,y,str) 70 | local spans=buffer.spans 71 | 72 | local spanI=1 73 | local color=buffer.color 74 | local e=x+len(str)-1 75 | 76 | while spans[spanI] and (spans[spanI].y span.e then 104 | span.e=e 105 | end 106 | else 107 | --can't, gonna have to make a new span 108 | --but first, split this guy as needed 109 | debugPrint("can't merge. Splitting") 110 | local a,b=unicode.sub(span.str,1,math.max(0,x-span.x)),unicode.sub(span.str,e-span.x+2) 111 | if len(a)>0 then 112 | span.str=a 113 | span.e=span.x+len(a) 114 | --span is a new span 115 | span={str=true,e=true,x=true,y=y,color=span.color} 116 | --insert after this span 117 | spanI=spanI+1 118 | table.insert(spans,spanI,span) 119 | end 120 | if len(b)>0 then 121 | span.str=b 122 | span.x=e+1 123 | span.e=span.x+len(b) 124 | 125 | --and another new span 126 | span={str=true,e=true,x=true,y=y,color=color} 127 | --insert /before/ this one 128 | table.insert(spans,spanI,span) 129 | end 130 | --now make whatever span we're left with me. 131 | span.color=color 132 | span.x, span.e = x, e 133 | span.str=str 134 | span.y=y 135 | end 136 | else 137 | --starts after me. just insert. 138 | local span={x=x,e=e,y=y,color=color,str=str} 139 | table.insert(spans,spanI,span) 140 | end 141 | --ok. We are span. We are at spanI. We've inserted ourselves. Now just check if we've obliterated anyone. 142 | --while the next span starts before I end... 143 | spanI=spanI+1 144 | while spans[spanI] and spans[spanI].y==y and spans[spanI].x<=e do 145 | local span=spans[spanI] 146 | if span.e>e then 147 | --it goes past me, we just circumcise it 148 | span.str=unicode.sub(span.str,e-span.x+2) 149 | span.x=e+1 150 | break--and there can't be more 151 | end 152 | --doesn't end after us, means we obliterated it 153 | table.remove(spans,spanI) 154 | --spanI will now point to the next, if any 155 | end 156 | end 157 | 158 | --[[this..won't work. Was forgetting I have a table per row, this would count rows. 159 | if #spans>=buffer.autoFlushCount then 160 | buffer.flush() 161 | end 162 | --]] 163 | end 164 | 165 | 166 | function bufferMeta.flush(buffer) 167 | debugPrint("flush?") 168 | if #buffer.spans==0 then 169 | return 170 | end 171 | 172 | --sort by colors. bg is added as high value, so this will group all with common bg together, 173 | --and all with common fg together within same bg. 174 | table.sort(buffer.spans, 175 | function(spanA,spanB) 176 | if spanA.color==spanB.color then 177 | if spanA.y==spanB.y then 178 | return spanA.x