panel.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. local love = require("compat")
  2. local START_X, START_Y, START_ALPHA = 0, 30, 0
  3. local START_TIME = 0.3
  4. local PATH = (...):match("(.-)[^%.^/]+$")
  5. local class = require( PATH .. "middleclass" )
  6. local utility = require( PATH .. "utility" )
  7. local TextBlock = require( PATH .. "textBlock" )
  8. local InputBlock = require( PATH .. "inputBlock" )
  9. local col = require(PATH .. "colors")
  10. local COLORS, COLORS_INACTIVE = col[1], col[2]
  11. local Panel = class("PunchUiPanel")
  12. function Panel:initialize( name, x, y, w, h, font, padding, corners )
  13. self.name = name or ""
  14. self.x = x or 0
  15. self.y = y or 0
  16. --width and height:
  17. self.w = w
  18. self.h = h
  19. self.font = font
  20. self.padding = padding or 10
  21. self.texts = {}
  22. self.events = {}
  23. self.inputs = {}
  24. self.lines = {}
  25. self.activeInput = nil
  26. self.corners = corners or {3,3,3,3}
  27. self:calcBorder()
  28. self.isList = false
  29. self.startX = START_X
  30. self.startY = START_Y
  31. self.alpha = START_ALPHA
  32. self.startTime = 0
  33. self.animationTime = START_TIME
  34. end
  35. function Panel:calcBorder()
  36. self.border = {}
  37. -- top left:
  38. if self.corners[1] > 0 then
  39. table.insert( self.border, 0 )
  40. table.insert( self.border, 0 + self.corners[1] )
  41. table.insert( self.border, 0 + self.corners[1] )
  42. table.insert( self.border, 0 )
  43. else
  44. table.insert( self.border, 0 )
  45. table.insert( self.border, 0 )
  46. end
  47. -- top right
  48. if self.corners[2] > 0 then
  49. table.insert( self.border, self.w - self.corners[2] )
  50. table.insert( self.border, 0 )
  51. table.insert( self.border, self.w )
  52. table.insert( self.border, 0 + self.corners[2] )
  53. else
  54. table.insert( self.border, self.w )
  55. table.insert( self.border, 0 )
  56. end
  57. -- bottom right
  58. if self.corners[3] > 0 then
  59. table.insert( self.border, self.w )
  60. table.insert( self.border, self.h - self.corners[3] )
  61. table.insert( self.border, self.w - self.corners[3] )
  62. table.insert( self.border, self.h )
  63. else
  64. table.insert( self.border, self.w )
  65. table.insert( self.border, self.h )
  66. end
  67. -- bottom left:
  68. if self.corners[4] > 0 then
  69. table.insert( self.border, self.corners[4] )
  70. table.insert( self.border, self.h )
  71. table.insert( self.border, 0 )
  72. table.insert( self.border, self.h - self.corners[4] )
  73. else
  74. table.insert( self.border, 0 )
  75. table.insert( self.border, self.h )
  76. end
  77. end
  78. function Panel:addText( name, x, y, width, height, txt )
  79. self:removeText( name )
  80. -- if the width is not given, make sure text does not
  81. -- move outside of panel:
  82. x = x + self.padding
  83. y = y + self.padding
  84. local maxWidth = self.w - x - self.padding
  85. width = math.min( width or math.huge, maxWidth )
  86. local t = TextBlock:new( name, x, y, width, height, txt, self.font, true )
  87. table.insert( self.texts, t )
  88. return t, t.trueWidth or t.width, t.height
  89. end
  90. function Panel:removeText( name )
  91. for k, t in ipairs(self.texts) do
  92. if t.name == name then
  93. table.remove(self.texts, k)
  94. end
  95. end
  96. end
  97. function Panel:addHeader( name, x, y, txt )
  98. return self:addText( name, x, y, math.huge, 1, COLORS.HEADER.ID ..txt )
  99. end
  100. function Panel:update( dt )
  101. if self.startTime < self.animationTime then
  102. self.startTime = self.startTime + dt
  103. -- let amount go towards zero:
  104. local t = math.max(self.startTime/self.animationTime, 0)
  105. --local amount = math.pow( linear, 6)
  106. local amount = -2.5*t^2+3.5*t
  107. self.startX = START_X*(1-amount)
  108. self.startY = START_Y*(1-amount)
  109. self.alpha = t
  110. if self.startTime >= self.animationTime then
  111. self.startX = 0
  112. self.startY = 0
  113. self.alpha = 1
  114. end
  115. end
  116. end
  117. function Panel:draw( inactive )
  118. local COL = COLORS
  119. if inactive then
  120. COL = COLORS_INACTIVE
  121. end
  122. love.graphics.push()
  123. love.graphics.translate( self.x + self.startX, self.y + self.startY )
  124. love.graphics.setColor( COL.PANEL_BG[1], COL.PANEL_BG[2], COL.PANEL_BG[3], COL.PANEL_BG[4]*self.alpha )
  125. love.graphics.polygon( "fill", self.border )
  126. love.graphics.setColor( COL.BORDER[1], COL.BORDER[2], COL.BORDER[3], COL.BORDER[4]*self.alpha )
  127. love.graphics.polygon( "line", self.border )
  128. love.graphics.setColor( COL.BORDER[1], COL.BORDER[2], COL.BORDER[3], COL.BORDER[4]*self.alpha*0.5 )
  129. for k, l in ipairs( self.lines ) do
  130. love.graphics.line( l.x1, l.y1, l.x2, l.y2 )
  131. end
  132. for k, e in ipairs( self.events ) do
  133. if e.highlight then
  134. love.graphics.setColor( COL.HLIGHT[1], COL.HLIGHT[2], COL.HLIGHT[3], COL.HLIGHT[4]*self.alpha )
  135. love.graphics.rectangle( "fill", e.x, e.y,
  136. e.w, e.h )
  137. end
  138. end
  139. for k, v in ipairs( self.texts ) do
  140. v:draw( inactive )
  141. end
  142. for k, v in ipairs( self.inputs ) do
  143. v:draw( inactive )
  144. end
  145. love.graphics.pop()
  146. end
  147. function Panel:addFunction( name, x, y, txt, key, event, tooltip )
  148. local fullTxt = COLORS.FUNCTION.ID .. string.upper(key) .. " "
  149. fullTxt = fullTxt .. COLORS.PLAIN_TEXT.ID .. txt
  150. local t, w, h = self:addText( name, x, y, math.huge, 1, fullTxt )
  151. local newEvent = {
  152. name = name,
  153. key = key,
  154. event = event,
  155. tooltip = tooltip,
  156. x = x + self.padding - 1,
  157. y = y + self.padding - 1,
  158. w = w + 2,
  159. h = h + 2,
  160. }
  161. table.insert( self.events, newEvent )
  162. return newEvent, w, h
  163. end
  164. function Panel:removeFunction( name )
  165. for k, ev in pairs( self.events ) do
  166. if ev.name == name then
  167. table.remove( self.events, k )
  168. break
  169. end
  170. end
  171. -- Also remove the text which describes this function:
  172. self:removeText( name )
  173. end
  174. function Panel:addInput( name, x, y, width, height, key, returnEvent, password, content, maxLetters )
  175. -- add a function which will set the new input box to active:
  176. -- add the key infront of the input box:
  177. local event = function()
  178. self.activeInput = self:inputByName( name )
  179. self.activeInput:setActive( true )
  180. end
  181. local ev = self:addFunction( name, x, y, "", key, event )
  182. x = x + self.padding
  183. y = y + self.padding
  184. local maxWidth = self.w - x - self.padding
  185. local keyWidth = self.font:getWidth( key .. " " )
  186. width = math.min( width or math.huge, maxWidth )
  187. height = height or self.font:getHeight()
  188. ev.w = width
  189. local i = InputBlock:new( name, x + keyWidth, y, width-keyWidth, height, self.font, returnEvent, password, maxLetters )
  190. if content and type(content) == "string" then
  191. i:setContent( content )
  192. end
  193. table.insert(self.inputs, i)
  194. return i
  195. end
  196. function Panel:inputByName( name )
  197. for k, i in ipairs( self.inputs ) do
  198. if i.name == name then
  199. return i
  200. end
  201. end
  202. end
  203. function Panel:disableInput()
  204. if self.activeInput then
  205. self.activeInput:setActive(false)
  206. self.activeInput = nil
  207. end
  208. end
  209. function Panel:keypressed( key, unicode )
  210. if not self.activeInput then
  211. for k, f in pairs( self.events ) do
  212. if f.key == key then
  213. if love.keyboard.isDown("lshift") then
  214. if f.tooltip then
  215. f.tooltip()
  216. end
  217. elseif f.event then
  218. f.event( f )
  219. end
  220. return true
  221. end
  222. end
  223. else
  224. local re = self.activeInput:keypressed( key, unicode )
  225. -- if "esc" was pressed (or similar), stop:
  226. if re == "stop" then
  227. --self.activeInput:setActive(false)
  228. --self.activeInput = nil
  229. self:disableInput()
  230. elseif re == "forward" then -- tab pressed: go to next input
  231. self.activeInput:setActive(false)
  232. local current = self.activeInput
  233. self.activeInput = nil
  234. local found = false
  235. for k, inp in ipairs(self.inputs) do
  236. if found then
  237. self.activeInput = inp
  238. self.activeInput:setActive(true)
  239. break
  240. end
  241. if inp == current then
  242. found = true
  243. end
  244. end
  245. elseif re == "backward" then -- tab pressed: go to next input
  246. self.activeInput:setActive(false)
  247. local current = self.activeInput
  248. self.activeInput = nil
  249. local found = false
  250. local inp
  251. for k = #self.inputs, 1, -1 do
  252. inp = self.inputs[k]
  253. if found then
  254. self.activeInput = inp
  255. self.activeInput:setActive(true)
  256. break
  257. end
  258. if inp == current then
  259. found = true
  260. end
  261. end
  262. end
  263. end
  264. end
  265. function Panel:textinput( key )
  266. if self.activeInput then
  267. -- type the key into the current input box:
  268. self.activeInput:textinput( key, true )
  269. end
  270. end
  271. function Panel:addLine( x1, y1, x2, y2 )
  272. self.lines[#self.lines+1] = {x1=x1, y1=y1, x2=x2, y2=y2 }
  273. end
  274. --------------------------------------
  275. -- Handle lists:
  276. function Panel:putListItem( item, i, curY )
  277. local ev, w, h
  278. local ev = function()
  279. if item.event then
  280. item.event()
  281. end
  282. end
  283. local tip = item.tooltip or "Choose option " .. i .. "."
  284. local tooltipEv = function()
  285. self:newTooltip( tip )
  286. end
  287. local key = item.key or tostring(i)
  288. ev, w, h = self:addFunction( key, 5, curY, item.txt, key, ev, tooltipEv )
  289. curY = curY + self.list.lineHeight
  290. return curY, w, ev
  291. end
  292. function Panel:buildList()
  293. local curY = self.padding
  294. local maxWidth = self.list.minWidth or 0
  295. self.events = {}
  296. self.texts = {}
  297. local itemsAdded = 1
  298. -- Display a scroll up button?
  299. if self.list.startItem > 1 then
  300. local item = {
  301. event = function()
  302. self:scrollList( self.list.startItem - 1 )
  303. end,
  304. tooltip = "Scroll up",
  305. key = "u",
  306. txt = "Up",
  307. }
  308. curY = self:putListItem( item, nil, curY )
  309. end
  310. local count = 0
  311. local moreItemsAvailable = false
  312. for k = self.list.startItem, #self.list.items do
  313. v = self.list.items[k]
  314. count = count + 1
  315. if curY + self.list.lineHeight*2 > self.h then
  316. moreItemsAvailable = true
  317. break
  318. end
  319. curY, w = self:putListItem( v, count, curY )
  320. maxWidth = math.max( maxWidth, w )
  321. itemsAdded = itemsAdded + 1
  322. end
  323. -- Display a scroll down button?
  324. if self.list.startItem < #self.list.items then
  325. local item = {
  326. event = function()
  327. self:scrollList( self.list.startItem + 1 )
  328. end,
  329. tooltip = "Scroll down",
  330. key = "d",
  331. txt = "Down",
  332. }
  333. curY = self:putListItem( item, nil, self.h - self.list.lineHeight -self.padding )
  334. end
  335. self.w = 12 + maxWidth
  336. end
  337. function Panel:toList( list, minWidth, listLength )
  338. self.list = {
  339. items = list,
  340. displayLength = math.max(listLength, 3),
  341. minWidth = minWidth,
  342. startItem = 1,
  343. lineHeight = self.font:getHeight() + 8
  344. }
  345. self.h = self.list.lineHeight*listLength + 2*self.padding
  346. self:buildList()
  347. --[[curY = 0
  348. for k, v in ipairs( list ) do
  349. curY = curY + self.font:getHeight() + 8
  350. if k < #list then
  351. self:addLine( 4, curY , maxWidth + 4, curY )
  352. end
  353. end]]
  354. self:calcBorder()
  355. self.isList = true
  356. end
  357. function Panel:scrollList( num )
  358. if self.isList then
  359. local prev = self.list.startItem
  360. self.list.startItem = math.min( math.max( num, 1 ), #self.list.items )
  361. if prev ~= self.list.startItem then
  362. self:buildList()
  363. end
  364. end
  365. end
  366. function Panel:addListItem( item )
  367. if not self.isList then
  368. return
  369. end
  370. table.insert( self.list.items, item )
  371. self:buildList()
  372. --[[if #self.events > 0 then
  373. self:addLine( 4, self.h , self.w - 8, self.h )
  374. end
  375. local curY = self.h
  376. local ev = function()
  377. if item.event then
  378. item.event()
  379. end
  380. end
  381. local tip = item.tooltip or "Choose option " .. #self.events + 1 .. "."
  382. local tooltipEv = function()
  383. self:newTooltip( tip )
  384. end
  385. local key = item.key or tostring( #self.events + 1 )
  386. ev, w, h = self:addFunction( key, 5, curY, item.txt, key, ev, tooltipEv )
  387. maxWidth = math.max( self.w - 12, w )
  388. curY = curY + self.font:getHeight() + 8
  389. self.h = curY
  390. self.w = maxWidth + 12
  391. self:calcBorder()]]
  392. end
  393. ------------------------------------------------------
  394. -- Handle mouse input:
  395. function Panel:mousemoved( x, y )
  396. for k, e in pairs( self.events ) do
  397. if utility.isInside( x, y, e.x + self.x, e.y + self.y, e.w, e.h ) then
  398. e.highlight = true
  399. return e
  400. end
  401. end
  402. end
  403. function Panel:mousepressed( x, y, button )
  404. for k, e in pairs( self.events ) do
  405. if utility.isInside( x, y, e.x + self.x, e.y + self.y, e.w, e.h ) then
  406. if e.event then
  407. e.event( e )
  408. end
  409. return e
  410. end
  411. end
  412. end
  413. return Panel