local gfx=require("yaru.gfx") local unicode=require("unicode") local ui={} local function Y()return package.loaded["yaru.core"]end function ui.theme()return Y().theme end function ui.hit(win,x,y,w,h,fn,btn) local hits=win.hits hits[#hits+1]={x,y,w,h,fn,btn} end function ui.text(x,y,s,fg,bg)gfx.text(x,y,s,fg,bg)end function ui.trunc(s,n) if n<=0 then return""end if unicode.len(s)<=n then return s end if n==1 then return"…"end return unicode.sub(s,1,n-1).."…" end function ui.pad(s,n) s=ui.trunc(s,n) return s..string.rep(" ",n-unicode.len(s)) end function ui.pill(x,y,w,bg) gfx.text(x,y,"◖",bg) if w>2 then gfx.fill(x+1,y,w-2,1,bg)end gfx.text(x+w-1,y,"◗",bg) end function ui.round(x,y,w,h,bg) if h<=1 then return ui.pill(x,y,w,bg)end local tl,tr=gfx.bgAt(x,y),gfx.bgAt(x+w-1,y) local bl,br=gfx.bgAt(x,y+h-1),gfx.bgAt(x+w-1,y+h-1) gfx.fill(x,y,w,h,bg) gfx.text(x,y,"▟",bg,tl);gfx.text(x+w-1,y,"▙",bg,tr) gfx.text(x,y+h-1,"▜",bg,bl);gfx.text(x+w-1,y+h-1,"▛",bg,br) end function ui.card(x,y,w,h,bg,shadow) gfx.fill(x,y,w,h,bg) gfx.half(x+1,y-1,w-2,bg,false) gfx.text(x,y-1,"⣠",bg);gfx.text(x+w-1,y-1,"⣄",bg) gfx.half(x+1,y+h,w-2,bg,true) gfx.text(x,y+h,"⠙",bg);gfx.text(x+w-1,y+h,"⠋",bg) if shadow~=false then gfx.blend(x+w,y,1,h+1,0,0.4) gfx.blend(x+1,y+h+1,w,1,0,0.25) end end function ui.button(win,x,y,label,fn,kind,w) local t=ui.theme() local bg,fg=t.button,t.buttonFg if kind=="suggested"then bg,fg=t.accent,t.accentFg elseif kind=="destructive"then bg,fg=t.danger,0xFFFFFF elseif kind=="flat"then bg,fg=nil,t.fg end w=w or unicode.len(label)+4 if bg then ui.pill(x,y,w,bg)end gfx.text(x+(w-unicode.len(label))//2,y,label,fg,bg) if fn then ui.hit(win,x,y,w,1,fn)end return w end function ui.switch(win,x,y,on,fn) local t=ui.theme() local off=t.dark and 0x5E5C64 or 0xC0BFBC if on then gfx.text(x,y,"◖",t.accent) gfx.fill(x+1,y,2,1,t.accent) gfx.text(x+3,y,"◖",0xFFFFFF,t.accent) gfx.fill(x+4,y,1,1,0xFFFFFF) gfx.text(x+5,y,"◗",0xFFFFFF) else gfx.text(x,y,"◖",0xFFFFFF) gfx.fill(x+1,y,1,1,0xFFFFFF) gfx.text(x+2,y,"◗",0xFFFFFF,off) gfx.fill(x+3,y,2,1,off) gfx.text(x+5,y,"◗",off) end if fn then ui.hit(win,x,y,6,1,fn)end end function ui.radio(win,x,y,on,label,fn) local t=ui.theme() gfx.text(x,y,on and"◉"or"○",on and t.accent or t.dim) gfx.text(x+2,y,label,t.fg) if fn then ui.hit(win,x,y,unicode.len(label)+2,1,fn)end end function ui.progress(x,y,w,frac,fg,bg) local t=ui.theme() frac=math.max(0,math.min(1,frac)) local n=math.floor(w*frac+.5) if n>0 then gfx.text(x,y,string.rep("━",n),fg or t.accent)end if n>16)+(bg>>16))//2)<<16|((((fg>>8)&255)+((bg>>8)&255))//2)<<8|(((fg&255)+(bg&255))//2)) local n=#values local prev for i=1,math.min(w,n)do local v=values[n-i+1]/(maxv>0 and maxv or 1) local hp=math.max(0,math.min(ph,math.floor(v*ph+0.5))) local col=x+w-i if hp>0 then gfx.prect(col,py0+ph-hp,1,hp,fill) local top=py0+ph-hp gfx.pset(col,top,fg) if prev and math.abs(prev-top)>1 then local a,b=math.min(prev,top),math.max(prev,top) gfx.prect(col,a,1,b-a,fg) end prev=top end end end function ui.select(x,y,w,color)ui.pill(x,y,w,color)end function ui.entry(win,st,x,y,w,focused,placeholder,mask) local t=ui.theme() st.text=st.text or"" st.cur=math.max(0,math.min(st.cur or unicode.len(st.text),unicode.len(st.text))) st.off=st.off or 0 if st.curw-2 then st.off=st.cur-(w-2)end local ring=focused and t.accent or t.entry gfx.text(x,y,"◖",ring) gfx.text(x+w-1,y,"◗",ring) gfx.fill(x+1,y,w-2,1,t.entry) local shown=st.text if mask then shown=string.rep("•",unicode.len(shown))end if shown==""and placeholder then gfx.text(x+(focused and 2 or 1),y,ui.trunc(placeholder,w-3),t.dim,t.entry) else gfx.text(x+1,y,unicode.sub(shown,st.off+1,st.off+w-2),t.entryFg,t.entry) end if focused then local cx=x+1+st.cur-st.off local ch=unicode.sub(shown,st.cur+1,st.cur+1) if ch==""or ch==" "then gfx.cell(cx,y,"▏",t.accent,t.entry) else gfx.cell(cx,y,ch,t.entry,t.accent)end end if win then ui.hit(win,x,y,w,1,function(_,lx)st.cur=st.off+math.max(0,lx-x-1);st.focus=true;return"focus"end)end end function ui.entryKey(st,char,code) local k=require("keyboard").keys local len=unicode.len(st.text or"") st.cur=st.cur or len if code==k.enter or code==k.numpadenter then return"enter" elseif code==1 then return"esc" elseif code==k.back then if st.cur>0 then st.text=unicode.sub(st.text,1,st.cur-1)..unicode.sub(st.text,st.cur+1);st.cur=st.cur-1 end return true elseif code==k.delete then st.text=unicode.sub(st.text,1,st.cur)..unicode.sub(st.text,st.cur+2);return true elseif code==k.left then st.cur=math.max(0,st.cur-1)return true elseif code==k.right then st.cur=math.min(len,st.cur+1)return true elseif code==k.home then st.cur=0 return true elseif code==k["end"]then st.cur=len return true elseif char and char>=32 and char~=127 then st.text=unicode.sub(st.text,1,st.cur)..unicode.char(char)..unicode.sub(st.text,st.cur+1) st.cur=st.cur+1 return true end end function ui.entryPaste(st,s) s=s:gsub("[\r\n].*","") st.text=unicode.sub(st.text,1,st.cur)..s..unicode.sub(st.text,st.cur+1) st.cur=st.cur+unicode.len(s) end function ui.size(n) if n<1024 then return n.." B"end if n<1024*1024 then return string.format("%.1f kB",n/1024)end return string.format("%.1f MB",n/1048576) end function ui.scrollbar(x,y,h,first,visible,total) if total<=visible then return end local t=ui.theme() local bh=math.max(1,h*visible//total) local by=y+(h-bh)*first//math.max(1,total-visible) for i=0,bh-1 do gfx.text(x,by+i,"▐",t.dim)end end local font={["0"]="0000386CC6C6CED6E6C6C66C38000000",["1"]="0000183878181818181818187E000000", ["2"]="00003C666606060C183060607E000000",["3"]="00003C666606061C060666663C000000", ["4"]="00000C1C3C6CCCCCFE0C0C0C0C000000",["5"]="00007E6060607C06060666663C000000", ["6"]="00001C3060607C66666666663C000000",["7"]="00007E0606060C0C1818181818000000", ["8"]="00003C666666763C6E6666663C000000",["9"]="00003C666666663E0606060C38000000", [":"]="00000018181800000000181818000000"} local function adv(c)return c==":"and 5 or 8 end function ui.bigWidth(s)local n=0 for i=1,#s do n=n+adv(s:sub(i,i))end return n end function ui.big(x,y,s,fg) for i=1,#s do local c=s:sub(i,i) local g=font[c] if g then local xo=c==":"and-2 or 0 for row=0,7 do local top,bot=tonumber(g:sub(row*4+1,row*4+2),16),tonumber(g:sub(row*4+3,row*4+4),16) for px=0,7 do local bit=0x80>>px local tp,bp=top&bit~=0,bot&bit~=0 local ch=tp and bp and"█"or tp and"▀"or bp and"▄" if ch then gfx.text(x+px+xo,y+row,ch,fg)end end end end x=x+adv(c) end end return ui