Files
Techmino/Zframework/widget.lua
SweetSea 396293c8af Custom image from clipboard (#1157)
* Update how text renders in button and key

* Add a placeholder

* Add actual code and language entry

* Sort buttons

* Alter to keep original behaviour

* I HAVE OCD

* Add back legacy behaviour
2024-10-26 23:44:31 +08:00

1547 lines
39 KiB
Lua

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
x<self.x+self.w+2*ATV and
y<self.y+self.h
end
function button:getCenter()
return self.x+self.w*.5,self.y+self.h*.5
end
function button:update(dt)
local ATV=self.ATV
if WIDGET.sel==self then
if ATV<8 then self.ATV=min(ATV+dt*60,8) end
else
if ATV>0 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
x<self.x+self.w and
y<self.y+self.h
end
function key:getCenter()
return self.x+self.w*.5,self.y+self.h*.5
end
function key:update(dt)
local ATV=self.ATV
if WIDGET.sel==self then
if ATV<4 then self.ATV=min(ATV+dt*60,4) end
else
if ATV>0 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 x<self.x+50 and y>self.y-25 and y<self.y+25
end
function switch:getCenter()
return self.x,self.y
end
function switch:update(dt)
local ATV=self.ATV
if WIDGET.sel==self then
if ATV<8 then self.ATV=min(ATV+dt*60,8) end
else
if ATV>0 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 x<self.x+self.w+10 and y>self.y-25 and y<self.y+25
end
function slider:getCenter()
return self.x+self.w*((self.pos-self.rangeL)/(self.rangeR-self.rangeL)),self.y
end
function slider:update(dt)
local ATV=self.ATV
if self.TAT>0 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
x<self.x+self.w+2 and
y>self.y and
y<self.y+60
end
function selector:getCenter()
return self.x+self.w*.5,self.y+30
end
function selector:update(dt)
local ATV=self.ATV
if WIDGET.sel==self then
if ATV<8 then self.ATV=min(ATV+dt*60,8) end
else
if ATV>0 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 x<self.x+self.w*.5 then
if s>1 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
x<self.x+self.w and
y<self.y+self.h
end
function inputBox:getCenter()
return self.x+self.w*.5,self.y
end
function inputBox:update(dt)
local ATV=self.ATV
if WIDGET.sel==self then
if ATV<3 then self.ATV=min(ATV+dt*60,3) end
else
if ATV>0 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
x<self.x+self.w and
y<self.y+self.h
end
function textBox:getCenter()
return self.x+self.w*.5,self.y+self.w
end
function textBox:update(dt)
if self.sure>0 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 y<self.y+40 then
if self.sure>0 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
x<self.x+self.w and
y<self.y+self.h
end
function listBox:getCenter()
return self.x+self.w*.5,self.y+self.h*.5
end
function listBox:push(t)
ins(self.list,t)
end
function listBox:pop()
if #self.list>0 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.selected<floor(self.scrollPos/self.lineH)+2 then
self:drag(nil,nil,nil,self.lineH)
end
elseif dir=="down" then
self.selected=min(self.selected+1,#self.list)
if self.selected>floor(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.selected<floor(self.scrollPos/self.lineH)+2 then
self:drag(nil,nil,nil,1e99)
elseif self.selected>floor(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<WIDGET.scrollHeight then
gc_draw(downArrowIcon,scr_w*.5,scr_h-10,0,SCR.k,nil,downArrowIcon:getWidth()*.5,downArrowIcon:getHeight())
end
gc_setBlendMode('multiply','premultiplied')
gc_draw(widgetCover,nil,nil,nil,scr_w,scr_h/360)
end
gc_setCanvas({stencil=false})
gc_setBlendMode('alpha','premultiplied')
gc_draw(widgetCanvas)
gc_setBlendMode('alpha')
gc_replaceTransform(SCR.xOy)
end
return WIDGET