local component=require("component") local computer=require("computer") local event=require("event") local keyboard=require("keyboard") local term=require("term") local unicode=require("unicode") local fs=require("filesystem") local shell=require("shell") local W=require("ocweb") local gpu=component.gpu local inet=component.isAvailable("internet")and component.internet or nil local args=shell.parse(...) local SW,SH=gpu.getResolution() local C={ bar=0x16213e,urlBox=0x0f3460,urlFocus=0x533483,urlText=0xe0e0e0,btnFg=0xffffff,off=0x6c7086, accent=0x533483,pageBg=0x1e1e2e,status=0xa6adc8,scrollbar=0x313244,scrollFg=0x585b70,sel=0x533483, find=0x45475a, } local PAGE_COLORS={text=0xcdd6f4,heading=0xcba6f7,link=0x89b4fa,dim=0x6c7086,accent=0xf5c2e7, code=0xa6e3a1,strong=0xffffff,em=0xf9e2af} local PAGE_Y=4 local PAGE_H=SH-PAGE_Y+1 local PAGE_W=SW-1 local BTN={{" ◄ ","back",2},{" ► ","fwd",6},{" ↺ ","reload",10},{" ⌂ ","home",14},{" ◇ ","mark",18}} local URL_X,URL_W=23,SW-26 local GO_X=SW-2 local ui={editing=false,addr="",cur=0,find=nil,findLine=nil,dirtyBar=true,dirtyPage=true} local S=W.session{inet=inet,width=PAGE_W-1,colors=PAGE_COLORS, onChange=function()ui.dirtyBar,ui.dirtyPage=true,true end, download=function(u,body) fs.makeDirectory("/home/Downloads") local name=(u:match("([^/?#]+)[?#]?[^/]*$")or"download"):gsub("[^%w%._%-]","_") local f=io.open("/home/Downloads/"..name,"wb") if f then f:write(body or"");f:close()end S.status="Saved /home/Downloads/"..name end} local function set(x,y,s,fg,bg)if bg then gpu.setBackground(bg)end if fg then gpu.setForeground(fg)end gpu.set(x,y,s)end local function clamp(s,w)if unicode.len(s)<=w then return s end return unicode.sub(s,1,math.max(0,w-1)).."…"end local function drawBar() gpu.setBackground(C.bar) gpu.fill(1,1,SW,3," ") set(2,1,clamp(S.title or"OCBrowser",SW-2),C.status,C.bar) for _,b in ipairs(BTN)do local on=(b[2]~="back"or#S.back>0)and(b[2]~="fwd"or#S.fwd>0) local label=b[2]=="reload"and S.job and" × "or b[2]=="mark"and(S.isBookmarked()and" ◆ "or" ◇ ")or b[1] set(b[3],2,label,on and C.btnFg or C.off,C.urlBox) end local bg=ui.editing and C.urlFocus or C.urlBox gpu.setBackground(bg) gpu.fill(URL_X,2,URL_W,1," ") local text=ui.editing and ui.addr or S.url local off=ui.editing and math.max(0,ui.cur-(URL_W-3))or 0 set(URL_X+1,2,clamp(unicode.sub(text,off+1),URL_W-2),C.urlText,bg) if ui.editing then local ch=unicode.sub(ui.addr,ui.cur+1,ui.cur+1) set(URL_X+1+ui.cur-off,2,ch==""and" "or ch,bg,C.urlText) end set(GO_X,2," ▶",C.btnFg,C.accent) gpu.setBackground(C.bar) local status=ui.find and("Find: "..ui.find.."_ (Enter next, Esc close)")or(S.status or"") local sel=S.page and S.page.links[S.sel or 0] if sel and not ui.find then status="→ "..W.resolve(S.url,sel.href)end set(2,3,clamp(status,SW-24),C.status,C.bar) if S.job then local bw=20 local p=S.job.progress or((computer.uptime()*0.7)%1) local n=math.floor((bw-2)*p) set(SW-bw,3,"["..string.rep("■",n)..string.rep(" ",bw-2-n).."]",C.status,C.bar) end end local function drawPage() gpu.setBackground(C.pageBg) gpu.fill(1,PAGE_Y,PAGE_W,PAGE_H," ") local page=S.page if not page then return end local lines=page.lines S.scroll=math.max(0,math.min(S.scroll or 0,math.max(0,#lines-PAGE_H))) local sel=page.links[S.sel or 0] for row=1,PAGE_H do local li=S.scroll+row local line=lines[li] if not line then break end local y=PAGE_Y+row-1 local rowBg=ui.findLine==li and C.find or C.pageBg if rowBg~=C.pageBg then gpu.setBackground(rowBg);gpu.fill(1,y,PAGE_W,1," ")end local x=2+(line.indent or 0) for _,seg in ipairs(line.segs)do local isSel=sel and seg.href and sel.line==li and sel.href==seg.href set(x,y,seg.text,isSel and 0xffffff or seg.fg or PAGE_COLORS.text,isSel and C.sel or rowBg) x=x+unicode.len(seg.text) end end gpu.setBackground(C.scrollbar) gpu.fill(SW,PAGE_Y,1,PAGE_H," ") if#lines>PAGE_H then local h=math.max(1,PAGE_H*PAGE_H//#lines) local y=(PAGE_H-h)*S.scroll//math.max(1,#lines-PAGE_H) gpu.setBackground(C.scrollFg) gpu.fill(SW,PAGE_Y+y,1,h," ") end end local function redraw() if ui.dirtyBar then drawBar();ui.dirtyBar=false end if ui.dirtyPage then drawPage();ui.dirtyPage=false end end local function dirty(page)ui.dirtyBar=true;if page then ui.dirtyPage=true end end local function scrollBy(n)if S.page then S.scroll=(S.scroll or 0)+n;dirty(true)end end local function selectLink(dir) local links=S.page and S.page.links or{} if#links==0 then return end S.sel=((S.sel or 0)-1+dir)%#links+1 local li=links[S.sel].line if li<=S.scroll then S.scroll=li-1 elseif li>S.scroll+PAGE_H then S.scroll=li-PAGE_H end dirty(true) end local function follow(href)if href then ui.editing=false;S.navigate(W.resolve(S.url,href));dirty(true)end end local function editAddress()ui.editing,ui.addr=true,S.url;ui.cur=unicode.len(ui.addr);dirty()end local function findNext() local i=S.find(ui.find or"",ui.findLine or S.scroll) ui.findLine=i if i then S.scroll=math.max(0,i-3)end dirty(true) end local function savePage() fs.makeDirectory("/home/Downloads") local p="/home/Downloads/"..((S.title or"page"):gsub("[^%w%-_]","_"):sub(1,24))..".html" local f=io.open(p,"w") if f then f:write(S.body or"");f:close();S.status="Saved "..p end dirty() end local function onClick(x,y,btn) if y==2 then for _,b in ipairs(BTN)do if x>=b[3]and x=URL_X and x=GO_X then if ui.editing then ui.editing=false;S.navigate(ui.addr)end return dirty(true)end end if y>=PAGE_Y and S.page then local li=(S.scroll or 0)+y-PAGE_Y+1 for _,l in ipairs(S.page.links)do if l.line==li and x-2>=l.x1 and x-20 then ui.addr=unicode.sub(ui.addr,1,ui.cur-1)..unicode.sub(ui.addr,ui.cur+1);ui.cur=ui.cur-1 end elseif code==K.delete then ui.addr=unicode.sub(ui.addr,1,ui.cur)..unicode.sub(ui.addr,ui.cur+2) elseif code==K.left then ui.cur=math.max(0,ui.cur-1) elseif code==K.right then ui.cur=math.min(unicode.len(ui.addr),ui.cur+1) elseif code==K.home then ui.cur=0 elseif code==K["end"]then ui.cur=unicode.len(ui.addr) elseif char>=32 then ui.addr=unicode.sub(ui.addr,1,ui.cur)..unicode.char(char)..unicode.sub(ui.addr,ui.cur+1);ui.cur=ui.cur+1 end return dirty(true) end if ui.find then if code==K.enter then return findNext() elseif code==K.escape then ui.find,ui.findLine=nil,nil;return dirty(true) elseif code==K.back then ui.find=unicode.sub(ui.find,1,-2);return dirty() elseif char>=32 and not ctrl then ui.find=ui.find..unicode.char(char);return dirty()end end if ctrl then if code==K.l then editAddress() elseif code==K.f then ui.find="";dirty() elseif code==K.d then S.toggleBookmark();dirty() elseif code==K.u then S.toggleSource();dirty(true) elseif code==K.s then savePage() elseif code==K.b then S.navigate("about:bookmarks") elseif code==K.h then S.navigate("about:history") elseif code==K.q then return"quit"end return end if alt and code==K.left then S.goBack()elseif alt and code==K.right then S.goForward() elseif code==K.f5 then S.reload() elseif code==K.escape then S.stop();dirty() elseif code==K.up then scrollBy(-1)elseif code==K.down then scrollBy(1) elseif code==K.pageUp then scrollBy(-PAGE_H+1)elseif code==K.pageDown or char==32 then scrollBy(PAGE_H-1) elseif code==K.home then S.scroll=0;dirty(true)elseif code==K["end"]then scrollBy(1e9) elseif code==K.tab then selectLink(keyboard.isShiftDown()and-1 or 1) elseif code==K.enter then local l=S.page and S.page.links[S.sel or 0]if l then follow(l.href)end elseif code==K.back then S.goBack()end dirty(true) end local function main() local fg0,bg0=gpu.getForeground(),gpu.getBackground() gpu.setBackground(C.pageBg) gpu.fill(1,1,SW,SH," ") if not inet then S.status="No Internet Card: only about: pages work"end S.navigate(args[1]or"about:home",false) while true do redraw() local name,_,a,b,c=event.pull(S.job and 0.05 or 1) if name=="interrupted"then break elseif name=="key_down"then if onKey(a,b)=="quit"then break end elseif name=="touch"then onClick(math.floor(a),math.floor(b),c) elseif name=="scroll"then scrollBy(c>0 and-3 or 3) elseif name=="clipboard"then if ui.editing then ui.addr=ui.addr..a;ui.cur=unicode.len(ui.addr);dirty()end end if S.job then S.step();dirty(S.job==nil)end end S.stop() gpu.setForeground(fg0);gpu.setBackground(bg0) gpu.fill(1,1,SW,SH," ") term.setCursor(1,1) end local ok,err=pcall(main) if not ok then gpu.setBackground(0);gpu.setForeground(0xffffff) term.clear() io.stderr:write("browser: "..tostring(err).."\n") end