local Y=require("yaru.core") local gfx=require("yaru.gfx") local ui=require("yaru.ui") local unicode=require("unicode") local M={} local function matches(q,s)return q==""or unicode.lower(s):find(unicode.lower(q),1,true)end function M.open(mode) local o={kind="overview",mode=mode or"windows",opaque=true,search={text="",cur=0},sel=1} local W,H,D=Y.size() local t=Y.theme local items local function runCmd(cmd) cmd=cmd:gsub("^%s+",""):gsub("%s+$","") if cmd==""then return end if cmd=="r"or cmd=="restart"then Y.closeOverlay();Y.quit("restart-shell")return end Y.closeOverlay() Y.launch("terminal",cmd) end local function appList(q) local r={} for _,id in ipairs(Y.appOrder)do local a=Y.apps[id] if matches(q,a.name)or matches(q,id)then r[#r+1]=a end end return r end local CW,CH=16,7 local function gridCols()return math.max(1,(W-D-8)//CW)end local function drawAppGrid(list,y0) local cols=math.min(#list,gridCols()) local x0=D+1+(W-D-cols*CW)//2 for i,a in ipairs(list)do local c,r=(i-1)%gridCols(),(i-1)//gridCols() local x,y=x0+c*CW,y0+r*CH if y+5>H then break end if o.sel==#items+1 then gfx.blend(x+1,y,CW-2,6,0xFFFFFF,0.15) gfx.half(x+2,y-1,CW-4,0xFFFFFF,false,0.15) gfx.half(x+2,y+6,CW-4,0xFFFFFF,true,0.15) end Y.drawIcon(x+4,y,a.icon or"generic") local name=ui.trunc(a.name,CW-2) gfx.text(x+(CW-unicode.len(name))//2,y+5,name,0xFFFFFF) local fn=function()Y.closeOverlay();Y.launch(a.id)end items[#items+1]={fn=fn} o.hits[#o.hits+1]={x,y,CW,6,fn} o.hits[#o.hits+1]={x,y,CW,6,function() Y.closeOverlay() local fav for _,f in ipairs(Y.cfg.favorites)do if f==a.id then fav=true end end Y.menu(x+4,y+4,{{"Open",function()Y.launch(a.id)end}, {fav and"Unpin"or"Pin to Dash",function() if fav then for i2,f in ipairs(Y.cfg.favorites)do if f==a.id then table.remove(Y.cfg.favorites,i2)break end end else table.insert(Y.cfg.favorites,a.id)end Y.saveConfig();Y.dockDirty() end}}) end,1} end return math.ceil(#list/gridCols())*CH end local function drawWorkspaces(y) local tw,th=22,6 local total=Y.wsCount*(tw+3)-3 local x=D+1+(W-D-total)//2 for i=1,Y.wsCount do local cur=i==Y.ws if cur then ui.card(x-1,y,tw+2,th,t.accent,false)end local bx,by=x,y gfx.pushClip(bx,by,tw,th) gfx.origin(0,0) gfx.drawWallpaperScaled(bx,by,tw,th) if not cur then gfx.blend(bx,by,tw,th,0,0.35)end for _,w in ipairs(Y.windows)do if w.ws==i and not w.minimized then local sx=bx+(w.x-D-1)*tw//(W-D) local sy=by+(w.y-2)*th//(H-1) local sw=math.max(3,w.w*tw//(W-D)) local sh=math.max(1,w.h*th//(H-1)) gfx.fill(sx,sy,sw,sh,w==Y.focus and t.header or t.headerIn) gfx.fill(sx,sy,sw,1,t.header) end end gfx.popClip() gfx.text(bx+1,by+th-1,tostring(i),0xFFFFFF) local ws=i o.hits[#o.hits+1]={bx,by,tw,th,function()Y.switchWs(ws);o.sel=1 end} x=x+tw+3 end end local function drawWindows(y0) local list={} for i=#Y.windows,1,-1 do local w=Y.windows[i]if w.ws==Y.ws then list[#list+1]=w end end if#list==0 then local s="No open windows" gfx.text(D+1+(W-D-#s)//2,y0+6,s,0xC0BFBC) return end local cw,ch=34,10 local cols=math.max(1,math.min(#list,(W-D-4)//(cw+4))) local x0=D+1+(W-D-cols*(cw+4)+4)//2 for i,w in ipairs(list)do local c,r=(i-1)%cols,(i-1)//cols local x,y=x0+c*(cw+4),y0+r*(ch+4) if y+ch+2>H then break end local selected=o.sel==#items+1 if selected then ui.card(x-1,y,cw+2,ch,t.accent,false)end ui.card(x,y,cw,ch-1,t.bg,not selected) gfx.fill(x,y,cw,1,t.header) gfx.text(x+1,y,ui.trunc(w.title or"",cw-5),t.headerFg,t.header) ui.pill(x+cw-4,y,3,t.dark and 0x454545 or 0xDEDDDA) gfx.text(x+cw-3,y,"×",t.headerFg,t.dark and 0x454545 or 0xDEDDDA) local a=Y.apps[w.app] Y.drawIcon(x+(cw-8)//2,y+3,a and a.icon or"generic") local name=ui.trunc(a and a.name or w.app or"",cw) gfx.text(x+(cw-unicode.len(name))//2,y+ch+1,name,0xFFFFFF) if w.minimized then gfx.text(x+2,y+ch-2,"minimized",t.dim,t.bg)end local fn=function()Y.closeOverlay();Y.focusWin(w)end items[#items+1]={fn=fn} o.hits[#o.hits+1]={x,y,cw,ch,fn} o.hits[#o.hits+1]={x+cw-4,y,3,1,function()Y.closeWin(w);o.sel=1 end} o.hits[#o.hits+1]={x,y,cw,ch,function() local its={} for n=1,Y.wsCount do if n~=w.ws then its[#its+1]={"Move to Workspace "..n,function()w.ws=n;Y.switchWs(n);Y.focusWin(w)end}end end its[#its+1]="-" its[#its+1]={"Close",function()Y.closeWin(w)end} Y.menu(x+2,y+2,its) end,1} end end function o.draw() items={} W,H,D=Y.size() t=Y.theme gfx.blend(D+1,2,W-D,H-1,0,0.55) if o.mode=="run"then local bw=56 local x,y=(W-bw)//2,H//3 ui.card(x,y,bw,6,t.menu) gfx.text(x+(bw-15)//2,y+1,"Enter a Command",t.menuFg,t.menu) ui.entry(nil,o.search,x+3,y+3,bw-6,true,"command") gfx.text(x+3,y+5,ui.trunc("Runs in a new Terminal · 'r' restarts the shell",bw-6),t.dim,t.menu) return end local sw=44 local sx=D+1+(W-D-sw)//2 ui.entry(nil,o.search,sx,3,sw,true,"Type to search") local q=o.search.text if q~=""then local list=appList(q) local y=6 if#list>0 then y=y+drawAppGrid(list,y)+1 end if y#items then o.sel=math.max(1,#items)end end function o.touch()Y.closeOverlay()return true end function o.scroll(_,x,y,dir)Y.switchWs(Y.ws-dir);o.sel=1 end function o.key(_,char,code) local K=Y.K if code==K.esc then if o.search.text~=""and o.mode~="run"then o.search.text,o.search.cur="",0;gfx.damageAll()return end return Y.closeOverlay() end if o.mode=="run"then local r=ui.entryKey(o.search,char,code) if r=="enter"then runCmd(o.search.text)end gfx.damageAll() return end if code==K.enter then local it=items and items[o.sel] if it then it.fn()elseif o.search.text~=""then runCmd(o.search.text)end return end if code==K.tab or code==K.right or code==K.down then o.sel=o.sel+1 elseif code==K.left or code==K.up then o.sel=math.max(1,o.sel-1) else local r=ui.entryKey(o.search,char,code) if r then o.sel=1 end end gfx.damageAll() end function o.paste(_,s)ui.entryPaste(o.search,s);gfx.damageAll()end Y.openOverlay(o) return o end return M