local gc=love.graphics local gc_origin=gc.origin local gc_translate,gc_replaceTransform=gc.translate,gc.replaceTransform local gc_stencil,gc_setStencilTest=gc.stencil,gc.setStencilTest local gc_push,gc_pop=gc.push,gc.pop local gc_setCanvas,gc_setBlendMode=gc.setCanvas,gc.setBlendMode local gc_setColor,gc_setLineWidth=gc.setColor,gc.setLineWidth local gc_draw,gc_line=gc.draw,gc.line local gc_rectangle=gc.rectangle local gc_print,gc_printf=gc.print,gc.printf local kb=love.keyboard local timer=love.timer.getTime local next=next local floor,ceil=math.floor,math.ceil local max,min=math.max,math.min local match=string.match local sub,ins,rem=string.sub,table.insert,table.remove local xOy=SCR.xOy local FONT=FONT local mStr=GC.mStr local approach=MATH.expApproach local downArrowIcon=GC.DO{40,25,{'fPoly',0,0,20,25,40,0}} local upArrowIcon=GC.DO{40,25,{'fPoly',0,25,20,0,40,25}} local clearIcon=GC.DO{40,40, {'fRect',16,5,8,3}, {'fRect',8,8,24,3}, {'fRect',11,14,18,21}, } local sureIcon=GC.DO{40,40, {'rawFT',35}, {'mText',"?",20,0}, } local smallerThen=GC.DO{20,20, {'setLW',5}, {'line',18,2,1,10,18,18}, } local largerThen=GC.DO{20,20, {'setLW',5}, {'line',2,2,19,10,2,18}, } local STW,STH-- stencil-wid/hei local function _rectangleStencil() gc.rectangle('fill',1,1,STW-2,STH-2) end local onChange=NULL local WIDGET={} function WIDGET.setOnChange(func) onChange=assert(type(func)=='function' and func,"WIDGET.setOnChange(func): func must be function") end local widgetMetatable={ __tostring=function(self) return self:getInfo() end, } local text={ type='text', mustHaveText=true, alpha=0, } function text:reset() end function text:update(dt) if self.hideF and self.hideF() then if self.alpha>0 then self.alpha=max(self.alpha-dt*7.5,0) end elseif self.alpha<1 then self.alpha=min(self.alpha+dt*7.5,1) end end function text:draw() if self.alpha>0 then local c=self.color gc_setColor(c[1],c[2],c[3],self.alpha) local w=self.obj:getWidth() local k=min(self.lim/self.obj:getWidth(),1) if self.align=='M' then gc_draw(self.obj,self.x,self.y,nil,k,1,w*.5,0) elseif self.align=='L' then gc_draw(self.obj,self.x,self.y,nil,k,1) elseif self.align=='R' then gc_draw(self.obj,self.x,self.y,nil,k,1,w,0) end end end function WIDGET.newText(D)-- name,x,y[,lim][,fText][,color][,font=30][,fType][,align='M'][,hideF][,hide] local _={ name= D.name or "_", x= D.x, y= D.y, lim= D.lim or 1e99, fText=D.fText, color=D.color and (COLOR[D.color] or D.color) or COLOR.Z, font= D.font or 30, fType=D.fType, align=D.align or 'M', hideF=D.hideF, } for k,v in next,text do _[k]=v end if not _.hideF then _.alpha=1 end setmetatable(_,widgetMetatable) return _ end local image={ type='image', } function image:reset() if type(self.img)=='string' then self.img=IMG[self.img] end end function image:draw() gc_setColor(1,1,1,self.alpha) gc_draw(self.img,self.x,self.y,self.ang,self.k) end function WIDGET.newImage(D)-- name[,img(name)],x,y[,ang][,k][,hideF][,hide] local _={ name= D.name or "_", img= D.img or D.name or "_", alpha=D.alpha, x= D.x, y= D.y, ang= D.ang, k= D.k, hideF=D.hideF, hide= D.hide, } for k,v in next,image do _[k]=v end setmetatable(_,widgetMetatable) return _ end local button={ type='button', mustHaveText=true, ATV=0,-- Activating time(0~8) textAlreadyWrapped=false,-- Text already wrapped? (Managed by :setObject, can be override, this will be true if obj has a '\n') } function button:reset() self.ATV=0 end function button:setObject(obj) if type(obj)=='string' or type(obj)=='number' then if match(obj,"\n") then self.textAlreadyWrapped=true self.obj=gc.newText(FONT.get(self.font,self.fType)) self.obj:addf(obj,self.w-self.edge*2,(self.align=='L' and 'left') or (self.align=='R' and 'right') or 'center') else self.textAlreadyWrapped=false self.obj=gc.newText(FONT.get(self.font,self.fType),obj) end elseif obj then self.obj=obj end end function button:isAbove(x,y) local ATV=self.ATV return x>self.x-ATV and y>self.y and x0 then self.ATV=max(ATV-dt*30,0) end end end function button:draw() local x,y,w,h=self.x,self.y,self.w,self.h local ATV=self.ATV local c=self.color local r,g,b=c[1],c[2],c[3] -- Button gc_setColor(.15+r*.7,.15+g*.7,.15+b*.7,.9) gc_rectangle('fill',x-ATV,y,w+2*ATV,h,4) gc_setLineWidth(2) gc_setColor(.3+r*.7,.3+g*.7,.3+b*.7) gc_rectangle('line',x-ATV,y,w+2*ATV,h,5) if ATV>0 then gc_setColor(.97,.97,.97,ATV*.125) gc_rectangle('line',x-ATV,y,w+2*ATV,h,3) end -- Drawable local obj=self.obj local ox,oy=obj:getWidth()*.5,obj:getHeight()*.5 local y0=y+h*.5 gc_setColor(1,1,1,.2+ATV*.05) if self.align=='L' or self.textAlreadyWrapped then local edge=self.edge gc_draw(obj,x+edge-1,y0-1-oy) gc_draw(obj,x+edge-1,y0+1-oy) gc_draw(obj,x+edge+1,y0-1-oy) gc_draw(obj,x+edge+1,y0+1-oy) gc_setColor(r*.55,g*.55,b*.55) gc_draw(obj,x+edge,y0-oy) elseif self.align=='R' then local x0=x+w-self.edge-ox*2 gc_draw(obj,x0-1,y0-1-oy) gc_draw(obj,x0-1,y0+1-oy) gc_draw(obj,x0+1,y0-1-oy) gc_draw(obj,x0+1,y0+1-oy) gc_setColor(r*.55,g*.55,b*.55) gc_draw(obj,x0,y0-oy) else--if self.align=='M' then local x0=x+w*.5 local kx=obj:type()=='Text' and min(w/ox/2,1) or 1 gc_draw(obj,x0-1,y0-1,nil,kx,1,ox,oy) gc_draw(obj,x0-1,y0+1,nil,kx,1,ox,oy) gc_draw(obj,x0+1,y0-1,nil,kx,1,ox,oy) gc_draw(obj,x0+1,y0+1,nil,kx,1,ox,oy) gc_setColor(r*.55,g*.55,b*.55) gc_draw(obj,x0,y0,nil,kx,1,ox,oy) end end function button:getInfo() return("x=%d,y=%d,w=%d,h=%d,font=%d"):format(self.x+self.w*.5,self.y+self.h*.5,self.w,self.h,self.font,self.fType) end function button:press(_,_,k) end function button:drag(x,y) if not self:isAbove(x,y) and self==WIDGET.sel then WIDGET.unFocus() end end function button:release(_,_,k) self.code(k) local ATV=self.ATV SYSFX.newRectRipple( 6, self.x-ATV, self.y-WIDGET.scrollPos, self.w+2*ATV, self.h ) if self.sound then SFX.play(self.sound) end end function WIDGET.newButton(D)-- name,x,y,w[,h][,fText][,color][,font=30][,fType][,sound][,align='M'][,edge=0][,code][,hideF][,hide] if not D.h then D.h=D.w end local w={ name= D.name or "_", x= D.x-D.w*.5, y= D.y-D.h*.5, w= D.w, h= D.h, resCtr={ D.x,D.y, D.x-D.w*.35,D.y-D.h*.35, D.x-D.w*.35,D.y+D.h*.35, D.x+D.w*.35,D.y-D.h*.35, D.x+D.w*.35,D.y+D.h*.35, }, fText=D.fText, color=D.color and (COLOR[D.color] or D.color) or COLOR.Z, font= D.font or 30, fType=D.fType, align=D.align or 'M', edge= D.edge or 0, code= D.code or NULL, hideF=D.hideF, hide= D.hide, } if D.sound==false then w.sound=false elseif type(D.sound)=='string' then w.sound=D.sound else w.sound='button' end for k,v in next,button do w[k]=v end setmetatable(w,widgetMetatable) return w end local key={ type='key', mustHaveText=true, ATV=0,-- Activating time(0~4) textAlreadyWrapped=false,---See button.setObject (line 146) } function key:reset() self.ATV=0 end function key:setObject(obj) if type(obj)=='string' or type(obj)=='number' then if match(obj,"\n") then self.textAlreadyWrapped=true self.obj=gc.newText(FONT.get(self.font,self.fType)) self.obj:addf(obj,self.w-self.edge*2,(self.align=='L' and 'left') or (self.align=='R' and 'right') or 'center') else self.textAlreadyWrapped=false self.obj=gc.newText(FONT.get(self.font,self.fType),obj) end elseif obj then self.obj=obj end end function key:isAbove(x,y) return x>self.x and y>self.y and x0 then self.ATV=max(ATV-dt*30,0) end end end function key:draw() local x,y,w,h=self.x,self.y,self.w,self.h local ATV=self.ATV local c=self.color local align=self.align local r,g,b=c[1],c[2],c[3] -- Fill if self.fShade then gc_setColor(r,g,b,ATV*.25) if align=='M' then gc_draw(self.fShade,x+w*.5-self.fShade:getWidth()*.5,y+h*.5-self.fShade:getHeight()*.5) elseif align=='L' then gc_draw(self.fShade,x+self.edge,y+h*.5-self.fShade:getHeight()*.5) elseif align=='R' then gc_draw(self.fShade,x+w-self.edge-self.fShade:getWidth(),y+h*.5-self.fShade:getHeight()*.5) end else -- Background gc_setColor(0,0,0,.3) gc_rectangle('fill',x,y,w,h,4) -- Frame gc_setColor(.2+r*.8,.2+g*.8,.2+b*.8,.7) gc_setLineWidth(2) gc_rectangle('line',x,y,w,h,3) -- Shade gc_setColor(1,1,1,ATV*.05) gc_rectangle('fill',x,y,w,h,3) end -- Drawable local obj=self.obj local ox,oy=obj:getWidth()*.5,obj:getHeight()*.5 gc_setColor(r,g,b) if align=='L' or self.textAlreadyWrapped then gc_draw(obj,x+self.edge,y+h*.5-oy) elseif align=='R' then gc_draw(obj,x+w-self.edge-ox*2,y-oy+h*.5) else--if align=='M' then local kx=obj:type()=='Text' and min(w/ox/2,1) or 1 gc_draw(obj,x+w*.5,y+h*.5,nil,kx,1,ox,oy) end end function key:getInfo() return("x=%d,y=%d,w=%d,h=%d,font=%d"):format(self.x+self.w*.5,self.y+self.h*.5,self.w,self.h,self.font,self.fType) end function key:press() end function key:drag(x,y) if not self:isAbove(x,y) and self==WIDGET.sel then WIDGET.unFocus() end end function key:release(_,_,k) self.code(k) if self.sound then SFX.play(self.sound) end end function WIDGET.newKey(D)-- name,x,y,w[,h][,fText][,fShade][,color][,font=30][,fType][,sound][,align='M'][,edge=0][,code][,hideF][,hide] if not D.h then D.h=D.w end local _={ name= D.name or "_", x= D.x-D.w*.5, y= D.y-D.h*.5, w= D.w, h= D.h, resCtr={ D.x,D.y, D.x-D.w*.35,D.y-D.h*.35, D.x-D.w*.35,D.y+D.h*.35, D.x+D.w*.35,D.y-D.h*.35, D.x+D.w*.35,D.y+D.h*.35, }, fText= D.fText, fShade= D.fShade, color= D.color and (COLOR[D.color] or D.color) or COLOR.Z, font= D.font or 30, fType= D.fType, align= D.align or 'M', edge= D.edge or 0, code= D.code or NULL, hideF= D.hideF, hide= D.hide, } if D.sound==false then _.sound=false elseif type(D.sound)=='string' then _.sound=D.sound else _.sound='key' end for k,v in next,key do _[k]=v end setmetatable(_,widgetMetatable) return _ end local switch={ type='switch', mustHaveText=true, ATV=0,-- Activating time(0~8) CHK=0,-- Check alpha(0~6) } function switch:reset() self.ATV=0 self.CHK=0 end function switch:isAbove(x,y) return x>self.x and xself.y-25 and y0 then self.ATV=max(ATV-dt*30,0) end end local chk=self.CHK if self:disp() then if chk<6 then self.CHK=min(chk+dt*60,6) end else if chk>0 then self.CHK=max(chk-dt*60,0) end end end function switch:draw() local x,y=self.x,self.y local ATV=self.ATV -- Background gc_setColor(0,0,0,.3) gc_rectangle('fill',x,y-25,50,50,4) -- Frame gc_setLineWidth(2) gc_setColor(1,1,1,.6+ATV*.1) gc_rectangle('line',x,y-25,50,50,3) -- Checked if ATV>0 then gc_setColor(1,1,1,ATV*.06) gc_rectangle('fill',x,y-25,50,50,3) end if self.CHK>0 then gc_setColor(.9,1,.9,self.CHK/6) gc_setLineWidth(5) gc_line(x+5,y,x+18,y+13,x+45,y-14) end -- Drawable local obj=self.obj gc_setColor(self.color) gc_draw(obj,x-12-ATV,y,nil,min(self.lim/obj:getWidth(),1),1,obj:getWidth(),obj:getHeight()*.5) end function switch:getInfo() return("x=%d,y=%d,font=%d"):format(self.x,self.y,self.font,self.fType) end function switch:press() self.code() if self.sound then SFX.play(self.disp() and 'check' or 'uncheck') end end function WIDGET.newSwitch(D)-- name,x,y[,lim][,fText][,color][,font=30][,fType][,sound=true][,disp][,code][,hideF][,hide] local _={ name= D.name or "_", x= D.x, y= D.y, lim= D.lim or 1e99, resCtr={ D.x+25,D.y, }, fText=D.fText, color=D.color and (COLOR[D.color] or D.color) or COLOR.Z, font= D.font or 30, fType=D.fType, sound=D.sound~=false, disp= D.disp, code= D.code or NULL, hideF=D.hideF, hide= D.hide, } for k,v in next,switch do _[k]=v end setmetatable(_,widgetMetatable) return _ end local slider={ type='slider', ATV=0,-- Activating time(0~8) TAT=0,-- Text activating time(0~180) pos=0,-- Position shown lastTime=0,-- Last value changing time } local sliderShowFunc={ int=function(S) return S.disp() end, float=function(S) return floor(S.disp()*100+.5)*.01 end, percent=function(S) return floor(S.disp()*100+.5).."%" end, } function slider:reset() self.ATV=0 self.TAT=180 self.pos=0 end function slider:isAbove(x,y) return x>self.x-10 and xself.y-25 and y0 then self.TAT=max(self.TAT-dt*60,0) end if WIDGET.sel==self then if ATV<6 then self.ATV=min(ATV+dt*60,6) end self.TAT=180 else if ATV>0 then self.ATV=max(ATV-dt*30,0) end end if not self.hide then self.pos=approach(self.pos,self.disp(),dt*26) end end function slider:draw() local x,y=self.x,self.y local ATV=self.ATV local x2=x+self.w gc_setColor(1,1,1,.5+ATV*.06) -- Units if not self.smooth then gc_setLineWidth(2) for p=self.rangeL,self.rangeR,self.unit do local X=x+(x2-x)*(p-self.rangeL)/(self.rangeR-self.rangeL) gc_line(X,y+7,X,y-7) end end -- Axis gc_setLineWidth(4) gc_line(x,y,x2,y) -- Block local cx=x+(x2-x)*(self.pos-self.rangeL)/(self.rangeR-self.rangeL) local bx,by,bw,bh=cx-10-ATV*.5,y-16-ATV,20+ATV,32+2*ATV gc_setColor(.8,.8,.8) gc_rectangle('fill',bx,by,bw,bh,3) -- Glow if ATV>0 then gc_setLineWidth(2) gc_setColor(.97,.97,.97,ATV*.16) gc_rectangle('line',bx+1,by+1,bw-2,bh-2,3) end -- Float text if self.TAT>0 and self.show then FONT.set(25) gc_setColor(.97,.97,.97,self.TAT/180) mStr(self:show(),cx,by-30) end -- Drawable local obj=self.obj if obj then gc_setColor(self.color) gc_draw(obj,x-12-ATV,y,nil,min(self.lim/obj:getWidth(),1),1,obj:getWidth(),obj:getHeight()*.5) end end function slider:getInfo() return("x=%d,y=%d,w=%d"):format(self.x,self.y,self.w) end function slider:press(x) self:drag(x) end function slider:drag(x) if not x then return end x=x-self.x local newPos=MATH.clamp(x/self.w,0,1) local newVal if not self.unit then newVal=(1-newPos)*self.rangeL+newPos*self.rangeR else newVal=newPos*(self.rangeR-self.rangeL) newVal=self.rangeL+floor(newVal/self.unit+.5)*self.unit end if newVal~=self.disp() then self.code(newVal) end if self.change and timer()-self.lastTime>.5 then self.lastTime=timer() self.change() end end function slider:release(x) self:drag(x) self.lastTime=0 end function slider:scroll(n) local p=self.disp() local u=self.unit or .01 local P=MATH.clamp(p+u*n,self.rangeL,self.rangeR) if p==P or not P then return end self.code(P) if self.change and timer()-self.lastTime>.18 then self.lastTime=timer() self.change() end end function slider:arrowKey(k) self:scroll((k=='left' or k=='up') and -1 or 1) end function WIDGET.newSlider(D)-- name,x,y,w[,lim][,fText][,color][,axis][,smooth][,font=30][,fType][,change],disp[,show][,code],hide if not D.axis then D.axis={0,1,false} D.smooth=true elseif not D.axis[3] then D.smooth=true end local _={ name= D.name or "_", x= D.x, y= D.y, w= D.w, lim= D.lim or 1e99, resCtr={ D.x,D.y, D.x+D.w*.25,D.y, D.x+D.w*.5,D.y, D.x+D.w*.75,D.y, D.x+D.w,D.y, }, fText= D.fText, color= D.color and (COLOR[D.color] or D.color) or COLOR.Z, rangeL=D.axis[1], rangeR=D.axis[2], unit= D.axis[3], smooth=D.smooth, font= D.font or 30, fType= D.fType, change=D.change, disp= D.disp, code= D.code or NULL, hideF= D.hideF, hide= D.hide, show= false, } if D.show then if type(D.show)=='function' then _.show=D.show else _.show=sliderShowFunc[D.show] end elseif D.show~=false then-- Use default if nil if _.unit and _.unit%1==0 then _.show=sliderShowFunc.int else _.show=sliderShowFunc.percent end end for k,v in next,slider do _[k]=v end setmetatable(_,widgetMetatable) return _ end local selector={ type='selector', mustHaveText=true, ATV=8,-- Activating time(0~4) select=false,-- Selected item ID selText=false,-- Selected item name } function selector:reset() self.ATV=0 local V,L=self.disp(),self.list for i=1,#L do if L[i]==V then self.select=i self.selText=self.list[i] return end end self.select=0 self.selText="" MES.new('error',"Selector "..self.name.." dead, disp= "..tostring(V)) end function selector:isAbove(x,y) return x>self.x and xself.y and y0 then self.ATV=max(ATV-dt*30,0) end end end function selector:draw() local x,y=self.x,self.y local w=self.w local ATV=self.ATV -- Background gc_setColor(0,0,0,.3) gc_rectangle('fill',x,y,w,60,4) -- Frame gc_setColor(1,1,1,.6+ATV*.1) gc_setLineWidth(2) gc_rectangle('line',x,y,w,60,3) -- Arrow gc_setColor(1,1,1,.2+ATV*.1) local t=(timer()%.5)^.5 if self.select>1 then gc_draw(smallerThen,x+6,y+33) if ATV>0 then gc_setColor(1,1,1,ATV*.4*(.5-t)) gc_draw(smallerThen,x+6-t*40,y+33) gc_setColor(1,1,1,.2+ATV*.1) end end if self.select<#self.list then gc_draw(largerThen,x+w-26,y+33) if ATV>0 then gc_setColor(1,1,1,ATV*.4*(.5-t)) gc_draw(largerThen,x+w-26+t*40,y+33) end end -- Drawable gc_setColor(self.color) gc_draw(self.obj,x+w*.5,y-4,nil,min((w-20)/self.obj:getWidth(),1),1,self.obj:getWidth()*.5,0) gc_setColor(1,1,1) FONT.set(30) mStr(self.selText,x+w*.5,y+22) end function selector:getInfo() return("x=%d,y=%d,w=%d"):format(self.x+self.w*.5,self.y+30,self.w) end function selector:press(x) if x then local s=self.select if x1 then s=s-1 SYSFX.newShade(3,self.x,self.y-WIDGET.scrollPos,self.w*.5,60) end else if s<#self.list then s=s+1 SYSFX.newShade(3,self.x+self.w*.5,self.y-WIDGET.scrollPos,self.w*.5,60) end end if self.select~=s then self.code(self.list[s],s) self.select=s self.selText=self.list[s] if self.sound then SFX.play('selector') end end end end function selector:scroll(n) local s=self.select if n==-1 then if s==1 then return end s=s-1 SYSFX.newShade(3,self.x,self.y-WIDGET.scrollPos,self.w*.5,60) else if s==#self.list then return end s=s+1 SYSFX.newShade(3,self.x+self.w*.5,self.y-WIDGET.scrollPos,self.w*.5,60) end self.code(self.list[s]) self.select=s self.selText=self.list[s] if self.sound then SFX.play('selector') end end function selector:arrowKey(k) self:scroll((k=='left' or k=='up') and -1 or 1) end function WIDGET.newSelector(D)-- name,x,y,w[,fText][,color][,sound=true],list,disp[,code],hide local _={ name= D.name or "_", x= D.x-D.w*.5, y= D.y-30, w= D.w, resCtr={ D.x,D.y, D.x+D.w*.25,D.y, D.x+D.w*.5,D.y, D.x+D.w*.75,D.y, D.x+D.w,D.y, }, fText=D.fText, color=D.color and (COLOR[D.color] or D.color) or COLOR.Z, sound=D.sound~=false, font= 30, list= D.list, disp= D.disp, code= D.code or NULL, hideF=D.hideF, hide= D.hide, } for k,v in next,selector do _[k]=v end setmetatable(_,widgetMetatable) return _ end local inputBox={ type='inputBox', keepFocus=true, ATV=0,-- Activating time(0~4) value="",-- Text contained } function inputBox:reset() self.ATV=0 end function inputBox:hasText() return #self.value>0 end function inputBox:getText() return self.value end function inputBox:setText(str) if not str then str="" end assert(type(str)=='string',"Arg #1 must be string") self.value=str end function inputBox:addText(str) if not str then str="" end assert(type(str)=='string',"Arg #1 must be string") self.value=self.value..str end function inputBox:clear() self.value="" end function inputBox:isAbove(x,y) return x>self.x and y>self.y and x0 then self.ATV=max(ATV-dt*15,0) end end end function inputBox:draw() local x,y,w,h=self.x,self.y,self.w,self.h local ATV=self.ATV -- Background gc_setColor(0,0,0,.4) gc_rectangle('fill',x,y,w,h,4) -- Highlight gc_setColor(1,1,1,ATV*.08*(math.sin(timer()*4.2)*.2+.8)) gc_rectangle('fill',x,y,w,h,4) -- Frame gc_setColor(1,1,1) gc_setLineWidth(3) gc_rectangle('line',x,y,w,h,3) -- Drawable local f=self.font FONT.set(f,self.fType) if self.obj then gc_draw(self.obj,x-12-self.obj:getWidth(),y+h*.5-self.obj:getHeight()*.5) end if self.secret then y=y+h*.5-f*.2 for i=1,#self.value do gc_rectangle("fill",x+f*.6*i,y,f*.4,f*.4) end else gc_printf(self.value,x+10,y,self.w) FONT.set(f-10) if WIDGET.sel==self then gc_print(EDITING,x+10,y+12-f*1.4) end end end function inputBox:getInfo() return("x=%d,y=%d,w=%d,h=%d"):format(self.x+self.w*.5,self.y+self.h*.5,self.w,self.h) end function inputBox:keypress(k) local t=self.value if #t>0 and EDITING=="" then if k=='backspace' then local p=#t while t:byte(p)>=128 and t:byte(p)<192 do p=p-1 end t=sub(t,1,p-1) SFX.play('lock') elseif k=='delete' then t="" SFX.play('hold') end self.value=t end end function WIDGET.newInputBox(D)-- name,x,y,w[,h][,font=30][,fType][,secret][,regex][,limit],hide local _={ name= D.name or "_", x= D.x, y= D.y, w= D.w, h= D.h, resCtr={ D.x+D.w*.2,D.y, D.x+D.w*.5,D.y, D.x+D.w*.8,D.y, }, font= D.font or floor(D.h/7-1)*5, fType= D.fType, secret=D.secret==true, regex= D.regex, limit= D.limit, hideF= D.hideF, hide= D.hide, } for k,v in next,inputBox do _[k]=v end setmetatable(_,widgetMetatable) return _ end local textBox={ type='textBox', scrollPos=0,-- Scroll-down-distance sure=0,-- Sure-timer for clear history } function textBox:reset() self.lineH=self.font*7/5 self.capacity=ceil((self.h-10)/self.lineH) end function textBox:setTexts(t) assert(type(t)=='table',"Arg #1 must be table") TABLE.clear(self.texts) TABLE.connect(self.texts,t) self.scrollPos=0 end function textBox:clear() self.texts={} self.scrollPos=0 SFX.play('fall') end function textBox:isAbove(x,y) return x>self.x and y>self.y and x0 then self.sure=max(self.sure-dt,0) end end function textBox:push(t) ins(self.texts,t) if self.scrollPos>(#self.texts-1.5)*self.lineH-self.h then-- minus 1 for the new message self.scrollPos=max(0,min(self.scrollPos+self.lineH,#self.texts*self.lineH-self.h)) end end function textBox:press(x,y) if not (x and y) then return end self:drag(0,0,0,0) if not self.fix and x>self.x+self.w-40 and y0 then self:clear() self.sure=0 else self.sure=1 end end end function textBox:drag(_,_,_,dy) self.scrollPos=max(0,min(self.scrollPos-dy,#self.texts*self.lineH-self.h)) end function textBox:scroll(dir) if type(dir)=='string' then if dir=="up" then dir=-1 elseif dir=="down" then dir=1 else return end end self:drag(nil,nil,nil,-dir*self.lineH) end function textBox:arrowKey(k) if k=='up' then self:scroll(-1) elseif k=='down' then self:scroll(-1) end end function textBox:draw() local x,y,w,h=self.x,self.y,self.w,self.h local list=self.texts local scrollPos=self.scrollPos local lineH=self.lineH local H=#list*lineH -- Background gc_setColor(0,0,0,.3) gc_rectangle('fill',x,y,w,h,4) -- Frame gc_setLineWidth(2) gc_setColor(WIDGET.sel==self and COLOR.lN or COLOR.Z) gc_rectangle('line',x,y,w,h,3) -- Texts FONT.set(self.font,self.fType) gc_push('transform') gc_translate(x,y) -- Slider gc_setColor(1,1,1) if #list>self.capacity then local len=h*h/H gc_rectangle('fill',-15,(h-len)*scrollPos/(H-h),12,len,3) end -- Clear button if not self.fix then gc_rectangle('line',w-40,0,40,40,3) gc_draw(self.sure==0 and clearIcon or sureIcon,w-40,0) end gc_setStencilTest('equal',1) STW,STH=w,h gc_stencil(_rectangleStencil) gc_translate(0,-(scrollPos%lineH)) local pos=floor(scrollPos/lineH) for i=pos+1,min(pos+self.capacity+1,#list) do if list[i]~=nil then gc_printf(list[i],10,4,w-16) end gc_translate(0,lineH) end gc_setStencilTest() gc_pop() end function textBox:getInfo() return("x=%d,y=%d,w=%d,h=%d"):format(self.x+self.w*.5,self.y+self.h*.5,self.w,self.h) end function WIDGET.newTextBox(D)-- name,x,y,w,h[,font=30][,fType][,lineH][,fix],hide local _={ name= D.name or "_", resCtr={ D.x+D.w*.5,D.y+D.h*.5, D.x+D.w*.5,D.y, D.x-D.w*.5,D.y, D.x,D.y+D.h*.5, D.x,D.y-D.h*.5, D.x,D.y, D.x+D.w,D.y, D.x,D.y+D.h, D.x+D.w,D.y+D.h, }, x= D.x, y= D.y, w= D.w, h= D.h, font= D.font or 30, lineH=D.lineH, capacity=nil, fType=D.fType, fix= D.fix, texts={}, hideF=D.hideF, hide= D.hide, } for k,v in next,textBox do _[k]=v end setmetatable(_,widgetMetatable) return _ end local listBox={ type='listBox', keepFocus=true, scrollPos=0,-- Scroll-down-distance selected=0,-- Hidden wheel move value _pressX=false, _pressY=false, } function listBox:reset() -- haha nothing here too, techmino is really fun! end function listBox:clear() self.list={} self.scrollPos=0 end function listBox:setList(t) assert(type(t)=='table',"Arg #1 must be table") self.list=t self.selected=1 self.scrollPos=0 end function listBox:getList() return self.list end function listBox:getLen() return #self.list end function listBox:getSel() return self.list[self.selected] end function listBox:isAbove(x,y) return x>self.x and y>self.y and x0 then rem(self.list) listBox:drag(0,0,0,0) end end function listBox:remove() if self.selected then rem(self.list,self.selected) if not self.list[self.selected] then self:arrowKey('up') end self:drag(0,0,0,0) end end function listBox:press(x,y) self._pressX=x self._pressY=y end function listBox:release(x,y) if not (x and y) then return end if self._pressX then self._pressX,self._pressY=false,false x,y=x-self.x,y-self.y if not (x and y and x>0 and y>0 and x<=self.w and y<=self.h) then return end y=math.floor((y+self.scrollPos)/self.lineH)+1 if self.list[y] then if self.selected~=y then self.selected=y self:drag(0,0,0,0) SFX.play('selector',.8,0,12) end end end end function listBox:drag(x,y,_,dy) if x and y and self._pressX and MATH.distance(x,y,self._pressX,self._pressY)>10 then self._pressX,self._pressY=false,false end self.scrollPos=max(0,min(self.scrollPos-dy,#self.list*self.lineH-self.h)) end function listBox:scroll(n) self:drag(nil,nil,nil,-n*self.lineH) end function listBox:arrowKey(dir) if dir=="up" then self.selected=max(self.selected-1,1) if self.selectedfloor(self.scrollPos/self.lineH)+self.capacity-1 then self:drag(nil,nil,nil,-self.lineH) end end end function listBox:select(i) self.selected=i if self.selectedfloor(self.scrollPos/self.lineH)+self.capacity-1 then self:drag(nil,nil,nil,-1e99) end end function listBox:draw() local x,y,w,h=self.x,self.y,self.w,self.h local list=self.list local scrollPos=self.scrollPos local lineH=self.lineH local H=#list*lineH gc_push('transform') gc_translate(x,y) -- Background gc_setColor(0,0,0,.4) gc_rectangle('fill',0,0,w,h,4) -- Frame gc_setColor(WIDGET.sel==self and COLOR.lN or COLOR.Z) gc_setLineWidth(2) gc_rectangle('line',0,0,w,h,3) -- Slider if #list>self.capacity then gc_setColor(1,1,1) local len=h*h/H gc_rectangle('fill',-15,(h-len)*scrollPos/(H-h),12,len,3) end -- List gc_setStencilTest('equal',1) STW,STH=w,h gc_stencil(_rectangleStencil) local pos=floor(scrollPos/lineH) gc_translate(0,-(scrollPos%lineH)) for i=pos+1,min(pos+self.capacity+1,#list) do if list[i]~=nil then self.drawF(list[i],i,i==self.selected) end gc_translate(0,lineH) end gc_setStencilTest() gc_pop() end function listBox:getInfo() return("x=%d,y=%d,w=%d,h=%d"):format(self.x+self.w*.5,self.y+self.h*.5,self.w,self.h) end function WIDGET.newListBox(D)-- name,x,y,w,h,lineH,drawF[,hideF][,hide] local _={ name= D.name or "_", resCtr={ D.x+D.w*.5,D.y+D.h*.5, D.x+D.w*.5,D.y, D.x-D.w*.5,D.y, D.x,D.y+D.h*.5, D.x,D.y-D.h*.5, D.x,D.y, D.x+D.w,D.y, D.x,D.y+D.h, D.x+D.w,D.y+D.h, }, x= D.x, y= D.y, w= D.w, h= D.h, list= {}, lineH= D.lineH, capacity=ceil(D.h/D.lineH), drawF= D.drawF, hideF= D.hideF, hide= D.hide, } for k,v in next,listBox do _[k]=v end setmetatable(_,widgetMetatable) return _ end WIDGET.active={}-- Table contains all active widgets WIDGET.scrollHeight=0-- Max drag height, not actual container height! WIDGET.scrollPos=0-- Current scroll position WIDGET.sel=false-- Selected widget WIDGET.indexMeta={ __index=function(L,k) for i=1,#L do if L[i].name==k then return L[i] end end end } function WIDGET.setWidgetList(list) WIDGET.unFocus(true) WIDGET.active=list or NONE WIDGET.cursorMove(SCR.xOy:inverseTransformPoint(love.mouse.getPosition())) -- Reset all widgets if list then for i=1,#list do list[i]:reset() end onChange() end end function WIDGET.setScrollHeight(height) WIDGET.scrollHeight=height and height or 0 WIDGET.scrollPos=0 end function WIDGET.setLang(widgetText) for S,L in next,SCN.scenes do if L.widgetList then for _,W in next,L.widgetList do local t=W.fText or widgetText[S][W.name] if not t and W.mustHaveText then t=W.name or "##" W.color=COLOR.dV end if type(W.setObject)=='function' then W:setObject(t) elseif type(t)=='string' and W.font then W.obj=gc.newText(FONT.get(W.font or 30),t) else W.obj=t end end end end end function WIDGET.getSelected() return WIDGET.sel end function WIDGET.isFocus(W) if W then return W and WIDGET.sel==W else return WIDGET.sel~=false end end function WIDGET.focus(W) if WIDGET.sel==W or (W and W.hide) then return end if WIDGET.sel and WIDGET.sel.type=='inputBox' then kb.setTextInput(false) EDITING="" end WIDGET.sel=W if W and W.type=='inputBox' then local _,y1=xOy:transformPoint(0,W.y+W.h) kb.setTextInput(true,0,y1,1,1) end end function WIDGET.unFocus(force) local W=WIDGET.sel if W and (force or not W.keepFocus) then if W.type=='inputBox' then kb.setTextInput(false) EDITING="" end WIDGET.sel=false end end function WIDGET.cursorMove(x,y) for _,W in next,WIDGET.active do if not W.hide and W.resCtr and W:isAbove(x,y+WIDGET.scrollPos) then WIDGET.focus(W) return end end if WIDGET.sel and not WIDGET.sel.keepFocus then WIDGET.unFocus() end end function WIDGET.press(x,y,k) local W=WIDGET.sel if W then if W.press then W:press(x,y and y+WIDGET.scrollPos,k) end if W.hide then WIDGET.unFocus() end end end function WIDGET.drag(x,y,dx,dy) if WIDGET.sel then local W=WIDGET.sel if W.drag then W:drag(x,y+WIDGET.scrollPos,dx,dy) end else WIDGET.scrollPos=max(min(WIDGET.scrollPos-dy,WIDGET.scrollHeight),0) end end function WIDGET.release(x,y,k) local W=WIDGET.sel if W and W.release then W:release(x,y+WIDGET.scrollPos,k) end end function WIDGET.textinput(texts) local W=WIDGET.sel if W and W.type=='inputBox' then if (not W.regex or texts:match(W.regex)) and (not W.limit or #(WIDGET.sel.value..texts)<=W.limit) then WIDGET.sel.value=WIDGET.sel.value..texts SFX.play('touch') else SFX.play('drop_cancel') end end end function WIDGET.update(dt) for _,W in next,WIDGET.active do if W.hideF then W.hide=W.hideF() if W.hide and W==WIDGET.sel then WIDGET.unFocus(true) end end if W.update then W:update(dt) end end end local widgetCanvas local widgetCover do local L={1,360,{'fRect',0,30,1,300}} for i=0,30 do ins(L,{'setCL',1,1,1,i/30}) ins(L,{'fRect',0,i,1,2}) ins(L,{'fRect',0,360-i,1,2}) end widgetCover=GC.DO(L) end local scr_w,scr_h function WIDGET.resize(w,h) scr_w,scr_h=w,h widgetCanvas=gc.newCanvas(w,h) end function WIDGET.draw() gc_setCanvas({stencil=true},widgetCanvas) gc_translate(0,-WIDGET.scrollPos) for _,W in next,WIDGET.active do if not W.hide then W:draw() end end gc_origin() gc_setColor(1,1,1) if WIDGET.scrollHeight>0 then if WIDGET.scrollPos>0 then gc_draw(upArrowIcon,scr_w*.5,10,0,SCR.k,nil,upArrowIcon:getWidth()*.5,0) end if WIDGET.scrollPos