local Y=require("yaru.core") local gfx=require("yaru.gfx") local ui=require("yaru.ui") local unicode=require("unicode") local M={} local KEYS={ {"C","(",")","÷"},{"7","8","9","×"},{"4","5","6","−"},{"1","2","3","+"},{"0",".","⌫","="}, } local ENV={sqrt=math.sqrt,pi=math.pi,abs=math.abs,floor=math.floor,sin=math.sin,cos=math.cos} local function evaluate(expr) local e=expr:gsub("÷","/"):gsub("×","*"):gsub("−","-"):gsub("√","sqrt"):gsub("π","pi") if e:find("[^%d%.%+%-%*/%%%^%(%)%s%a]")then return nil,"invalid"end for word in e:gmatch("%a+")do if not ENV[word]then return nil,"invalid"end end local f=load("return "..e,"=calc","t",ENV) if not f then return nil,"syntax error"end local ok,v=pcall(f) if not ok or type(v)~="number"then return nil,"error"end if v~=v or v==math.huge or v==-math.huge then return nil,"undefined"end if math.type(v)=="float"and v==math.floor(v)and math.abs(v)<1e15 then v=math.floor(v)end local s=tostring(v) if math.type(v)=="float"then s=string.format("%.10g",v)end return s end function M.open() local st={expr="",result=nil,history={}} local win=Y.newWindow{app="calc",title="Calculator",w=34,h=20,fixed=true} local function press(k) if k=="C"then st.expr,st.result="",nil elseif k=="⌫"then st.expr=unicode.sub(st.expr,1,-2) elseif k=="="then local r,err=evaluate(st.expr) if r then table.insert(st.history,1,st.expr.." = "..r) st.history[4]=nil st.expr,st.result=r,nil else st.result=err end else st.expr=st.expr..k st.result=nil end end function win.draw(w,cw,ch) local t=Y.theme gfx.fill(1,1,cw,5,t.view) for i=1,2 do local hline=st.history[3-i] if hline then gfx.text(cw-1-unicode.len(hline),i,ui.trunc(hline,cw-2),t.dim,t.view)end end local e=st.expr==""and"0"or st.expr gfx.text(math.max(2,cw-1-unicode.len(e)),4,unicode.sub(e,-(cw-3)),t.fg,t.view) if st.result then gfx.text(2,3,st.result,t.danger,t.view)end local bw=(cw-3)//4 for r,rowKeys in ipairs(KEYS)do for c,k in ipairs(rowKeys)do local x,y=2+(c-1)*(bw+0),6+(r-1)*3 local kind=k=="="and"suggested"or nil local bg=kind and t.accent or(k:match("%d")or k==".")and t.button or(t.dark and 0x303030 or 0xEBEBEB) ui.round(x,y,bw-1,2,bg) gfx.text(x+(bw-1-unicode.len(k))//2,y,k,kind and 0xFFFFFF or t.fg,bg) ui.hit(w,x,y,bw-1,2,function()press(k)end) end end end function win.key(w,char,code) local K=Y.K if code==K.enter or char==61 then press("=") elseif code==K.back then press("⌫") elseif code==K.esc or code==K.del then press("C") elseif char and char>32 then local c=unicode.char(char) local map={["/"]="÷",["*"]="×",["-"]="−"} c=map[c]or c if c:match("^[%d%.%(%)%+%^%%a-z]$")or c=="÷"or c=="×"or c=="−"then press(c)end end end function win.paste(w,s)st.expr=st.expr..s:gsub("%s","")end return win end return M