Gspot.lua 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  1. -- Original author: https://github.com/trubblegum
  2. -- This is a modified version of https://github.com/trubblegum/Gspot/blob/cf0a49d7d2073686d7ddb32a4fa04e90593d36c4/Gspot.lua
  3. -- The original program did not include a copyright notice.
  4. -- Modifications © Copyright 2015-2016, 2018, 2021 Pedro Gimeno Fortea.
  5. --
  6. -- This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
  7. -- Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
  8. -- 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
  9. -- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
  10. -- 3. This notice may not be removed or altered from any source distribution.
  11. -- Simplify version checking
  12. local version = love._version_major * 10000 + love._version_minor * 100 + love._version_revision
  13. -- Return the position of the first byte of the given UTF-8 char.
  14. local function utf8char_begin(s, idx)
  15. -- Precondition:
  16. --assert(idx >= 1 and idx <= #s + 1)
  17. local byte = s:byte(idx)
  18. while byte and byte >= 0x80 and byte < 0xC0 do
  19. idx = idx - 1
  20. byte = s:byte(idx)
  21. end
  22. -- Postcondition:
  23. --assert(idx >= 1 and idx <= #s + 1)
  24. return idx
  25. end
  26. -- Return the position immediately after the current UTF-8 char.
  27. local function utf8char_after(s, idx)
  28. -- Precondition:
  29. --assert(idx >= 1 and idx <= #s + 1)
  30. if idx <= #s then
  31. idx = idx + 1
  32. local byte = s:byte(idx)
  33. while byte and byte >= 0x80 and byte < 0xC0 do
  34. idx = idx + 1
  35. byte = s:byte(idx)
  36. end
  37. end
  38. -- Postcondition:
  39. --assert(idx >= 1 and idx <= #s + 1)
  40. return idx
  41. end
  42. -- Return the number of UTF-8 characters on the string
  43. local function utf8len(s)
  44. local p = 0
  45. for i=1,string.len(s) do
  46. p = (s:byte(i) >= 0x80 and s:byte(i) < 0xC0) and p or p+1
  47. end
  48. return p
  49. end
  50. -- Apply a scissor to the current scissor (intersect the rects)
  51. local clipScissor
  52. if version < 001000 then
  53. function clipScissor(nx, ny, nw, nh)
  54. local ox, oy, ow, oh = love.graphics.getScissor()
  55. if ox then
  56. -- Intersect both rects
  57. nw = nx + nw
  58. nh = ny + nh
  59. nx, ny = math.max(nx, ox), math.max(ny, oy)
  60. nw = math.max(0, math.min(nw, ox + ow) - nx)
  61. nh = math.max(0, math.min(nh, oy + oh) - ny)
  62. end
  63. -- Set new scissor
  64. love.graphics.setScissor(nx, ny, nw, nh)
  65. -- Return old scissor
  66. return ox, oy, ow, oh
  67. end
  68. else
  69. function clipScissor(nx, ny, nw, nh)
  70. local ox, oy, ow, oh = love.graphics.getScissor()
  71. love.graphics.intersectScissor(nx, ny, nw, nh)
  72. -- Return old scissor
  73. return ox, oy, ow, oh
  74. end
  75. end
  76. local DIV = version >= 110000 and 255 or 1
  77. -- 11.0 compatibility
  78. local compat_setColor, setColor, fileExists
  79. if version < 110000 then
  80. setColor = love.graphics.setColor
  81. fileExists = love.filesystem.exists
  82. else
  83. compat_setColor = function(...)
  84. local n = select('#', ...)
  85. if n >= 3 then
  86. local r, g, b, a = ...
  87. if n >= 4 then
  88. love.graphics.setColor(r / DIV, g / DIV, b / DIV, a / DIV)
  89. else
  90. love.graphics.setColor(r / DIV, g / DIV, b / DIV)
  91. end
  92. return
  93. end
  94. assert(n == 1, "Invalid number of parameters in setColor()")
  95. local t = ...
  96. assert(type(t) == "table", "setColor() only accepts a table parameter")
  97. if #t >= 4 then
  98. love.graphics.setColor(t[1] / DIV, t[2] / DIV, t[3] / DIV, t[4] / DIV)
  99. else
  100. love.graphics.setColor(t[1] / DIV, t[2] / DIV, t[3] / DIV)
  101. end
  102. end
  103. setColor = compat_setColor
  104. local info = {type = false, size = false, modtime = false}
  105. fileExists = function(path)
  106. return love.filesystem.getInfo(path, info) ~= nil
  107. and (info.type == "file" or info.type == "symlink")
  108. end
  109. end
  110. -- Deal with love.mouse API changes in 0.10
  111. local mouseL = version >= 001000 and 1 or 'l'
  112. local mouseR = version >= 001000 and 2 or 'r'
  113. -- 0.10.0 blurs text if rendered on fractional coordinates
  114. -- 0.9 redefines love.graphics.print[f] after first call.
  115. -- To prevent caching the wrong version, we call it now with no text.
  116. love.graphics.print("")
  117. love.graphics.printf("", 0, 0, 0)
  118. local lgprint = version < 001000 and love.graphics.print or function(text, x, y, ...) return love.graphics.print(text, math.floor(x+0.5), math.floor(y+0.5), ...) end
  119. local lgprintf = version < 001000 and love.graphics.printf or function(text, x, y, ...) return love.graphics.printf(text, math.floor(x+0.5), math.floor(y+0.5), ...) end
  120. local Gspot = {}
  121. Gspot.style = { -- see Gspot.setComponentMax for colour values
  122. unit = 16,
  123. font = love.graphics.newFont(10),
  124. fg = {},
  125. bg = {},
  126. labelfg = nil, -- defaults to fg when absent
  127. default = {},
  128. hilite = {},
  129. focus = {},
  130. }
  131. Gspot.setComponentMax = function(this, value)
  132. -- 'this' isn't used at all here - it modifies the base class instead.
  133. assert(tostring(value) == "255" or value == "native", "Gspot:setColorRange must be 255 or \"native\"")
  134. if (Gspot.nativeColorMax or false) ~= (value == "native") then
  135. local st = Gspot.style
  136. st.fg[1],st.fg[2],st.fg[3],st.fg[4] = 255,255,255,255
  137. st.bg[1],st.bg[2],st.bg[3],st.bg[4] = 64, 64, 64,255
  138. st.default[1],st.default[2],st.default[3],st.default[4] = 96,96,96,255
  139. st.hilite[1],st.hilite[2],st.hilite[3],st.hilite[4] = 128,128,128,255
  140. st.focus[1],st.focus[2],st.focus[3],st.focus[4] = 160,160,160,255
  141. if value == "native" then
  142. Gspot.nativeColorMax = true
  143. for i = 1, 4 do
  144. st.fg[i] = st.fg[i] / DIV
  145. st.bg[i] = st.bg[i] / DIV
  146. st.default[i] = st.default[i] / DIV
  147. st.hilite[i] = st.hilite[i] / DIV
  148. st.focus[i] = st.focus[i] / DIV
  149. end
  150. setColor = love.graphics.setColor
  151. else
  152. Gspot.nativeColorMax = nil
  153. setColor = compat_setColor
  154. end
  155. end
  156. return Gspot:load()
  157. end
  158. Gspot.load = function(this)
  159. local def = {
  160. style = {
  161. unit = this.style.unit,
  162. font = this.style.font,
  163. fg = this.style.fg,
  164. bg = this.style.bg,
  165. labelfg = this.style.labelfg,
  166. default = this.style.default,
  167. hilite = this.style.hilite,
  168. focus = this.style.focus,
  169. hs = this.style.hs or this.style.unit,
  170. },
  171. dblclickinterval = 0.25,
  172. -- no messin' past here
  173. maxid = 0, -- legacy
  174. mem = {},
  175. elements = {},
  176. mousein = nil,
  177. focus = nil,
  178. drag = nil,
  179. orepeat = nil,
  180. }
  181. def.mousedt = def.dblclickinterval -- Double click timer (make it expired)
  182. return setmetatable(def, {__index = this, __call = this.load})
  183. end
  184. Gspot.update = function(this, dt)
  185. this.mousedt = this.mousedt + dt
  186. local mouse = {}
  187. mouse.x, mouse.y = this:getmouse()
  188. local mousein = this.mousein
  189. this.mousein = false
  190. this.mouseover = false
  191. if this.drag then
  192. local element = this.drag
  193. if love.mouse.isDown(mouseL) then
  194. if type(element.drag) == 'function' then element:drag(mouse.x, mouse.y)
  195. else
  196. element.pos.y = mouse.y - element.offset.y
  197. element.pos.x = mouse.x - element.offset.x
  198. end
  199. elseif love.mouse.isDown(mouseR) then
  200. if type(element.rdrag) == 'function' then element:rdrag(mouse.x, mouse.y)
  201. else
  202. element.pos.y = mouse.y - element.offset.y
  203. element.pos.x = mouse.x - element.offset.x
  204. end
  205. end
  206. for i, bucket in ipairs(this.elements) do
  207. if bucket ~= element and bucket:containspoint(mouse) then this.mouseover = bucket end
  208. end
  209. end
  210. for i = #this.elements, 1, -1 do
  211. local element = this.elements[i]
  212. if element.display then
  213. if element:containspoint(mouse) then
  214. if element.parent and element.parent:type() == 'Gspot.element.scrollgroup' and element ~= element.parent.scrollv and element ~= element.parent.scrollh then
  215. if element.parent:containspoint(mouse) then this.mousein = element break end
  216. else this.mousein = element break end
  217. end
  218. end
  219. end
  220. for i = #this.elements, 1, -1 do
  221. local element = this.elements[i]
  222. if element.display then
  223. if element.update then
  224. if element.updateinterval then
  225. element.dt = element.dt + dt
  226. if element.dt >= element.updateinterval then
  227. element.dt = 0
  228. element:update(dt)
  229. end
  230. else element:update(dt) end
  231. end
  232. end
  233. end
  234. if this.mousein ~= mousein then
  235. if this.mousein and this.mousein.enter then this.mousein:enter() end
  236. if mousein and mousein.leave then mousein:leave() end
  237. end
  238. end
  239. Gspot.draw = function(this)
  240. local ostyle_font = love.graphics.getFont()
  241. local ostyle_r, ostyle_g, ostyle_b, ostyle_a = love.graphics.getColor()
  242. local ostyle_scissor_x, ostyle_scissor_y, ostyle_scissor_w, ostyle_scissor_h = love.graphics.getScissor()
  243. for i, element in ipairs(this.elements) do
  244. if element.display then
  245. local pos, scissor = element:getpos()
  246. if scissor then clipScissor(scissor.x, scissor.y, scissor.w, scissor.h) end
  247. love.graphics.setFont(element.style.font)
  248. element:draw(pos)
  249. love.graphics.setScissor(ostyle_scissor_x, ostyle_scissor_y, ostyle_scissor_w, ostyle_scissor_h)
  250. end
  251. end
  252. if this.mousein and this.mousein.tip then
  253. local element = this.mousein
  254. local tipfont = this.style.font
  255. local tipw, tiph = tipfont:getWidth(element.tip), tipfont:getHeight()
  256. local scrw, scrh = love.graphics.getDimensions()
  257. local pos = element:getpos()
  258. local _, countlf = element.tip:gsub("\n", "\n")
  259. local tippos = {x = pos.x + (this.style.unit / 2), y = pos.y + (this.style.unit / 2), w = tipw + this.style.unit, h = this.style.unit / 4 + tiph * (countlf + 1)}
  260. love.graphics.setFont(tipfont) -- use the default font
  261. setColor(this.style.bg)
  262. this.mousein:rect({x = math.max(0, math.min(tippos.x, scrw - (tipw + this.style.unit))), y = math.max(0, math.min(tippos.y, scrh - this.style.unit)), w = tippos.w, h = tippos.h})
  263. setColor(this.style.fg)
  264. lgprint(element.tip, math.max(this.style.unit / 2, math.min(tippos.x + (this.style.unit / 2), scrw - (tipw + (this.style.unit / 2)))), math.max((this.style.unit - tiph) / 2, math.min(tippos.y + ((this.style.unit - tiph) / 2), (scrh - this.style.unit) + ((this.style.unit - tiph) / 2))))
  265. end
  266. love.graphics.setFont(ostyle_font)
  267. love.graphics.setColor(ostyle_r, ostyle_g, ostyle_b, ostyle_a)
  268. end
  269. Gspot.mousepress = function(this, x, y, button)
  270. this:unfocus()
  271. local mousedt = this.mousedt
  272. this.mousedt = this.dblclickinterval -- expire unless clicked
  273. if this.mousein then
  274. local element = this.mousein
  275. if element.elementtype ~= 'hidden' then element:getparent():setlevel() end
  276. if button == mouseL then
  277. if element.drag then
  278. this.drag = element
  279. element.offset = {x = x - element:getpos().x, y = y - element:getpos().y}
  280. end
  281. if mousedt < this.dblclickinterval and element.dblclick then
  282. element:dblclick(x, y, button)
  283. else
  284. if element.click then element:click(x, y) end
  285. this.mousedt = 0
  286. end
  287. elseif button == mouseR and element.rclick then element:rclick(x, y)
  288. elseif button == 'wu' and element.wheelup then element:wheelup(x, y)
  289. elseif button == 'wd' and element.wheeldown then element:wheeldown(x, y)
  290. end
  291. end
  292. end
  293. Gspot.mouserelease = function(this, x, y, button)
  294. if this.drag then
  295. local element = this.drag
  296. if button == mouseR then
  297. if element.rdrop then element:rdrop(this.mouseover) end
  298. if this.mouseover and this.mouseover.rcatch then this.mouseover:rcatch(element.id) end
  299. else
  300. if element.drop then element:drop(this.mouseover) end
  301. if this.mouseover and this.mouseover.catch then this.mouseover:catch(element) end
  302. end
  303. end
  304. this.drag = nil
  305. end
  306. Gspot.mousewheel = function(this, x, y)
  307. if y ~= 0 and this.mousein then
  308. local element = this.mousein
  309. local call = y > 0 and element.wheelup or element.wheeldown
  310. if call then
  311. local mx, my = this.getmouse()
  312. call(element, mx, my)
  313. end
  314. end
  315. end
  316. Gspot.keypress = function(this, key)
  317. if this.focus then
  318. if (key == 'return' or key == 'kpenter') and this.focus.done then this.focus:done() end
  319. if this.focus and this.focus.keypress then this.focus:keypress(key) end
  320. end
  321. end
  322. Gspot.textinput = function(this, key)
  323. -- Due to a bug in love or some library, textinput can give us
  324. -- invalid UTF-8
  325. -- (for example, on Linux with Spanish keyboard:
  326. -- AltGr + Shift + "+" generates "\xAF" which does not
  327. -- start with \xC0-\xFF)
  328. if not key or key == "" or key:byte(1) >= 0x80 and key:byte(1) < 0xC0 then return end
  329. if this.focus and this.focus.textinput then this.focus:textinput(key) end
  330. end
  331. Gspot.getmouse = function(this)
  332. return love.mouse.getPosition()
  333. end
  334. -- legacy
  335. Gspot.newid = function(this)
  336. this.maxid = this.maxid + 1
  337. return this.maxid
  338. end
  339. -- /legacy
  340. Gspot.clone = function(this, t)
  341. local c = {}
  342. for i, v in pairs(t) do
  343. if v then
  344. if type(v) == 'table' then c[i] = this:clone(v) else c[i] = v end
  345. end
  346. end
  347. return setmetatable(c, getmetatable(t))
  348. end
  349. Gspot.getindex = function(tab, val)
  350. for i, v in pairs(tab) do if v == val then return i end end
  351. end
  352. Gspot.add = function(this, element)
  353. element.id = this:newid() -- legacy
  354. table.insert(this.elements, element)
  355. if element.parent then element.parent:addchild(element) end
  356. return element
  357. end
  358. Gspot.rem = function(this, element)
  359. if element.parent then element.parent:remchild(element) end
  360. while #element.children > 0 do
  361. for i, child in ipairs(element.children) do this:rem(child) end
  362. end
  363. if element == this.mousein then this.mousein = nil end
  364. if element == this.drag then this.drag = nil end
  365. if element == this.focus then this:unfocus() end
  366. return table.remove(this.elements, this.getindex(this.elements, element))
  367. end
  368. Gspot.setfocus = function(this, element)
  369. if element then
  370. this.focus = element
  371. local rep = element.keyrepeat
  372. if rep ~= nil then
  373. if element.keydelay then -- legacy stuff
  374. rep = element.keydelay > 0
  375. elseif rep == 0 then
  376. rep = false
  377. end
  378. this.orepeat = love.keyboard.hasKeyRepeat()
  379. love.keyboard.setKeyRepeat(rep)
  380. end
  381. end
  382. end
  383. Gspot.unfocus = function(this)
  384. this.focus = nil
  385. if this.orepeat ~= nil then
  386. love.keyboard.setKeyRepeat(this.orepeat)
  387. this.orepeat = nil
  388. end
  389. end
  390. local pos = {
  391. load = function(this, Gspot, t)
  392. assert(type(t) == 'table' or not t, 'invalid pos constructor argument : must be of type table or nil')
  393. t = t or {}
  394. t = t.pos or t
  395. local circ = false
  396. local pos = {}
  397. if t.r or t[5] or (#t == 3 and not t.w) then
  398. pos.r = t.r or t[5] or t[3]
  399. circ = true
  400. pos.w = t.w or t[3] or pos.r * 2
  401. pos.h = t.h or t[4] or pos.r * 2
  402. else
  403. pos.w = t.w or t[3] or Gspot.style.unit
  404. pos.h = t.h or t[4] or Gspot.style.unit
  405. end
  406. pos.x = t.x or t[1] or 0
  407. pos.y = t.y or t[2] or 0
  408. return setmetatable(pos, this), circ
  409. end,
  410. __unm = function(a)
  411. local c = {x = a.x, y = a.y, w = a.w, h = a.h, r = a.r}
  412. c.x = 0 - a.x
  413. c.y = 0 - a.y
  414. return setmetatable(c, getmetatable(a))
  415. end,
  416. __add = function(a, b)
  417. local c = {x = a.x, y = a.y, w = a.w, h = a.h, r = a.r}
  418. local d
  419. local success, e = pcall(function(b) return b.x end, b)
  420. if success then d = e else d = b end
  421. c.x = a.x + d
  422. local success, e = pcall(function(b) return b.y end, b)
  423. if success then d = e else d = b end
  424. c.y = a.y + d
  425. return setmetatable(c, getmetatable(a))
  426. end,
  427. __sub = function(a, b)
  428. local c = {x = a.x, y = a.y, w = a.w, h = a.h, r = a.r}
  429. local d
  430. local success, e = pcall(function(b) return b.x end, b)
  431. if success then d = e else d = b end
  432. c.x = a.x - d
  433. local success, e = pcall(function(b) return b.y end, b)
  434. if success then d = e else d = b end
  435. c.y = a.y - d
  436. return setmetatable(c, getmetatable(a))
  437. end,
  438. __mul = function(a, b)
  439. local c = {x = a.x, y = a.y, w = a.w, h = a.h, r = a.r}
  440. local d
  441. local success, e = pcall(function(b) return b.x end, b)
  442. if success then d = e else d = b end
  443. c.x = a.x * d
  444. local success, e = pcall(function(b) return b.y end, b)
  445. if success then d = e else d = b end
  446. c.y = c.y * d
  447. return setmetatable(c, getmetatable(a))
  448. end,
  449. __div = function(a, b)
  450. local c = {x = a.x, y = a.y, w = a.w, h = a.h, r = a.r}
  451. local d
  452. local success, e = pcall(function(b) return b.x end, b)
  453. if success then d = e else d = b end
  454. c.x = a.x / d
  455. local success, e = pcall(function(b) return b.y end, b)
  456. if success then d = e else d = b end
  457. c.y = a.y / d
  458. return setmetatable(c, getmetatable(a))
  459. end,
  460. __pow = function(a, b)
  461. local c = {x = a.x, y = a.y, w = a.w, h = a.h, r = a.r}
  462. local d
  463. local success, e = pcall(function(b) return b.x end, b)
  464. if success then d = e else d = b end
  465. c.x = a.x ^ d
  466. local success, e = pcall(function(b) return b.y end, b)
  467. if success then d = e else d = b end
  468. c.y = a.y ^ d
  469. return setmetatable(c, getmetatable(a))
  470. end,
  471. __tostring = function(this) return this:type() .. ' : x = ' .. tostring(this.x) .. ', y = ' .. tostring(this.y) .. ', w = ' .. tostring(this.w) .. ', h = ' .. tostring(this.h) .. ', r = ' .. tostring(this.r) end,
  472. __index = {
  473. type = function(this) return 'Gspot.pos' end,
  474. },
  475. }
  476. Gspot.pos = setmetatable(pos, {__call = pos.load})
  477. Gspot.util = {
  478. setshape = function(this, shape)
  479. assert(shape == 'circle' or shape == 'rect' or not shape, 'shape must be "rect" or "circle" or nil')
  480. this.shape = shape
  481. if this.shape == 'circle' and not this.pos.r then this.pos.r = this.pos.w / 2 end
  482. end,
  483. drawshape = function(this, pos)
  484. pos = pos or this:getpos()
  485. if this.shape == 'circle' then
  486. local segments = this.segments or math.max(pos.r, 8)
  487. love.graphics.circle('fill', pos.x + pos.r, pos.y + pos.r, pos.r, segments)
  488. else
  489. this:rect(pos)
  490. end
  491. end,
  492. rect = function(this, pos, mode)
  493. pos = this.Gspot:pos(pos.pos or pos or this.pos)
  494. assert(pos:type() == 'Gspot.pos')
  495. mode = mode or 'fill'
  496. love.graphics.rectangle(mode, pos.x, pos.y, pos.w, pos.h)
  497. end,
  498. setimage = function(this, img)
  499. if type(img) == 'string' and fileExists(img) then img = love.graphics.newImage(img) end
  500. if pcall(function(img) return img:type() == 'Image' end, img) then this.img = img
  501. else this.img = nil end
  502. end,
  503. drawimg = function(this, pos)
  504. local r, g, b, a = love.graphics.getColor()
  505. setColor(255, 255, 255, 255)
  506. love.graphics.draw(this.img, (pos.x + (pos.w / 2)) - (this.img:getWidth()) / 2, (pos.y + (pos.h / 2)) - (this.img:getHeight() / 2))
  507. love.graphics.setColor(r, g, b, a)
  508. end,
  509. setfont = function(this, font, size)
  510. if type(font) == 'string' and fileExists(font) then
  511. font = love.graphics.newFont(font, size)
  512. elseif type(font) == 'number' then
  513. font = love.graphics.newFont(font)
  514. end
  515. if type(font) == 'userdata' and type(font.type) == 'function' and font:type() == 'Font' then
  516. this.style.font = font
  517. if this.autosize then
  518. this.pos.w = font:getWidth(this.label) + this.style.unit / 2
  519. this.pos.h = font:getHeight() + this.style.unit / 4
  520. end
  521. else
  522. this.style.font = nil
  523. this.style = this.Gspot:clone(this.style)
  524. end
  525. end,
  526. getpos = function(this, scissor)
  527. local pos = this.Gspot:pos(this)
  528. if this.parent then
  529. local ppos = 0
  530. ppos, scissor = this.parent:getpos()
  531. pos = pos + ppos
  532. if this.parent:type() == 'Gspot.element.scrollgroup' and this ~= this.parent.scrollv and this ~= this.parent.scrollh then
  533. scissor = this.Gspot:clone(this.parent:getpos())
  534. if this.parent.scrollv then pos.y = pos.y - this.parent.scrollv.values.current end
  535. if this.parent.scrollh then pos.x = pos.x - this.parent.scrollh.values.current end
  536. end
  537. end
  538. return pos, scissor
  539. end,
  540. containspoint = function(this, point)
  541. local contains = true
  542. local pos = point.pos or point
  543. local thispos, scissor = this:getpos()
  544. if this.shape == 'circle' then
  545. if not this.withinradius(pos, thispos + this.pos.r, scissor) then contains = false end
  546. elseif not this.withinrect(pos, thispos, scissor) then
  547. contains = false
  548. end
  549. return contains
  550. end,
  551. withinrect = function(pos, rect, scissor)
  552. pos = pos.pos or pos
  553. rect = rect.pos or rect
  554. if scissor then
  555. return pos.x >= rect.x and pos.x < (rect.x + rect.w) and pos.y >= rect.y and pos.y < (rect.y + rect.h)
  556. and pos.x >= scissor.x and pos.x < scissor.x + scissor.w and pos.y >= scissor.y and pos.y < scissor.y + scissor.h
  557. end
  558. return pos.x >= rect.x and pos.x < (rect.x + rect.w) and pos.y >= rect.y and pos.y < (rect.y + rect.h)
  559. end,
  560. getdist = function(pos, target)
  561. pos = pos.pos or pos
  562. target = target.pos or target
  563. return math.sqrt((pos.x-target.x) * (pos.x-target.x) + (pos.y-target.y) * (pos.y-target.y))
  564. end,
  565. withinradius = function(pos, circ, scissor)
  566. pos = pos.pos or pos
  567. circ = circ.pos or circ
  568. if (pos.x - circ.x) * (pos.x - circ.x) + (pos.y - circ.y) * (pos.y - circ.y) < circ.r * circ.r then
  569. if scissor then
  570. return pos.x >= scissor.x and pos.x < scissor.x + scissor.w and pos.y >= scissor.y and pos.y < scissor.y + scissor.h
  571. else
  572. return true
  573. end
  574. end
  575. return false
  576. end,
  577. getparent = function(this)
  578. if this.parent then return this.parent:getparent()
  579. else return this end
  580. end,
  581. getmaxw = function(this)
  582. local maxw = 0
  583. for i, child in ipairs(this.children) do
  584. if (child ~= this.scrollv and child ~= this.scrollh) and child.pos.x + child.pos.w > maxw then maxw = child.pos.x + child.pos.w end
  585. end
  586. return maxw
  587. end,
  588. getmaxh = function(this)
  589. local maxh = 0
  590. for i, child in ipairs(this.children) do
  591. if (child ~= this.scrollv and child ~= this.scrollh) and child.pos.y + child.pos.h > maxh then maxh = child.pos.y + child.pos.h end
  592. end
  593. return maxh
  594. end,
  595. addchild = function(this, child, autostack)
  596. if autostack then
  597. if type(autostack) == 'number' or autostack == 'grid' then
  598. local limitx = (type(autostack) == 'number' and autostack) or this.pos.w
  599. local maxx, maxy = 0, 0
  600. for i, element in ipairs(this.children) do
  601. if element ~= this.scrollh and element ~= this.scrollv then
  602. if element.pos.y > maxy then maxy = element.pos.y end
  603. if element.pos.x + element.pos.w + child.pos.w <= limitx then maxx = element.pos.x + element.pos.w
  604. else maxx, maxy = 0, element.pos.y + element.pos.h end
  605. end
  606. end
  607. child.pos.x, child.pos.y = maxx, maxy
  608. elseif autostack == 'horizontal' then child.pos.x = this:getmaxw()
  609. elseif autostack == 'vertical' then child.pos.y = this:getmaxh() end
  610. end
  611. table.insert(this.children, child)
  612. child.parent = this
  613. child.style = this.Gspot:clone(child.style)
  614. setmetatable(child.style, {__index = this.style})
  615. if this.scrollh then this.scrollh.values.max = math.max(this:getmaxw() - this.pos.w, 0) end
  616. if this.scrollv then this.scrollv.values.max = math.max(this:getmaxh() - this.pos.h, 0) end
  617. return child
  618. end,
  619. remchild = function(this, child)
  620. child.pos = child:getpos()
  621. table.remove(this.children, this.Gspot.getindex(this.children, child))
  622. child.parent = nil
  623. setmetatable(child.style, {__index = this.Gspot.style})
  624. end,
  625. replace = function(this, replacement)
  626. this.Gspot.elements[this.Gspot.getindex(this.Gspot.elements, this)] = replacement
  627. return replacement
  628. end,
  629. getlevel = function(this)
  630. for i, element in pairs(this.Gspot.elements) do
  631. if element == this then return i end
  632. end
  633. end,
  634. setlevel = function(this, level)
  635. if level then
  636. table.insert(this.Gspot.elements, level, table.remove(this.Gspot.elements, this.Gspot.getindex(this.Gspot.elements, this)))
  637. for i, child in ipairs(this.children) do child:setlevel(level + i) end
  638. else
  639. table.insert(this.Gspot.elements, table.remove(this.Gspot.elements, this.Gspot.getindex(this.Gspot.elements, this)))
  640. for i, child in ipairs(this.children) do child:setlevel() end
  641. end
  642. end,
  643. show = function(this)
  644. this.display = true
  645. for i, child in pairs(this.children) do child:show() end
  646. end,
  647. hide = function(this)
  648. this.display = false
  649. for i, child in pairs(this.children) do child:hide() end
  650. end,
  651. focus = function(this)
  652. this.Gspot:setfocus(this)
  653. end,
  654. type = function(this)
  655. return 'Gspot.element.'..this.elementtype
  656. end,
  657. }
  658. Gspot.element = {
  659. load = function(this, Gspot, elementtype, label, pos, parent)
  660. assert(Gspot[elementtype], 'invalid element constructor argument : element.elementtype must be an existing element type')
  661. assert(type(label) == 'string' or type(label) == 'number' or not label, 'invalid element constructor argument : element.label must be of type string or number')
  662. assert(type(pos) == 'table' or not pos, 'invalid element constructor argument : element.pos must be of type table or nil')
  663. assert((type(parent) == 'table' and parent:type():sub(1, 13) == 'Gspot.element') or not parent, 'invalid element constructor argument : element.parent must be of type element or nil')
  664. local pos, circ = Gspot:pos(pos)
  665. local element = {elementtype = elementtype, label = label, pos = pos, display = true, dt = 0, parent = parent, children = {}, Gspot = Gspot}
  666. if circ then element.shape = 'circle' else element.shape = 'rect' end
  667. if parent then element.style = setmetatable({}, {__index = parent.style})
  668. else element.style = setmetatable({}, {__index = Gspot.style}) end
  669. return setmetatable(element, {__index = Gspot[elementtype], __tostring = function(this) return this:type() .. ' (' .. this:getlevel() .. ')' end})
  670. end,
  671. }
  672. setmetatable(Gspot.element, {__call = Gspot.element.load})
  673. Gspot.scrollvalues = function(this, values)
  674. local val = {}
  675. val.min = values.min or values[1] or 0
  676. val.max = values.max or values[2] or 0
  677. val.current = values.current or values[3] or val.min
  678. val.step = values.step or values[4] or this.style.unit
  679. val.axis = values.axis or values[5] or 'vertical'
  680. return val
  681. end
  682. -- elements
  683. Gspot.group = {
  684. load = function(this, Gspot, label, pos, parent)
  685. return Gspot:add(Gspot:element('group', label, pos, parent))
  686. end,
  687. draw = function(this, pos)
  688. setColor(this.style.bg)
  689. this:drawshape(pos)
  690. if this.label then
  691. setColor(this.style.labelfg or this.style.fg)
  692. lgprint(this.label, pos.x + ((pos.w - this.style.font:getWidth(this.label)) / 2), pos.y + ((this.style.unit - this.style.font:getHeight()) / 2))
  693. end
  694. end,
  695. }
  696. setmetatable(Gspot.group, {__index = Gspot.util, __call = Gspot.group.load})
  697. Gspot.collapsegroup = {
  698. load = function(this, Gspot, label, pos, parent)
  699. local element = Gspot:group(label, pos, parent)
  700. element.view = true
  701. element.orig = Gspot:clone(element.pos)
  702. element.toggle = function(this)
  703. this.view = not element.view
  704. this.pos.h = (this.view and this.orig.h) or this.style.unit
  705. for i, child in ipairs(this.children) do
  706. if child ~= this.control then
  707. if this.view then child:show() else child:hide() end
  708. end
  709. end
  710. this.control.label = (this.view and '-') or '='
  711. end
  712. element.control = Gspot:button('-', {element.pos.w - element.style.unit}, element)
  713. element.control.click = function(this)
  714. this.parent:toggle()
  715. end
  716. return element
  717. end,
  718. }
  719. setmetatable(Gspot.collapsegroup, {__index = Gspot.util, __call = Gspot.collapsegroup.load})
  720. Gspot.text = {
  721. load = function(this, Gspot, label, pos, parent, autosize)
  722. local element = Gspot:element('text', label, pos, parent)
  723. if autosize then
  724. element.pos.w = element.style.font:getWidth(label) + (element.style.unit / 2)
  725. element.autosize = autosize
  726. end
  727. element:setfont(element.style.font)
  728. return Gspot:add(element)
  729. end,
  730. setfont = function(this, font, size)
  731. this.Gspot.util.setfont(this, font, size)
  732. if not this.autosize then -- height needs adjustment regardless
  733. local width, lines = this.style.font:getWrap(this.label, this.pos.w - (this.style.unit / 2))
  734. if type(lines) == "table" then lines = #lines end
  735. lines = math.max(lines, 1)
  736. this.pos.h = this.style.font:getHeight() * lines + this.style.unit / 4
  737. end
  738. end,
  739. draw = function(this, pos)
  740. setColor(this.style.labelfg or this.style.fg)
  741. if this.autosize then lgprint(this.label, pos.x + (this.style.unit / 4), pos.y + (this.style.unit / 8))
  742. else lgprintf(this.label, pos.x + (this.style.unit / 4), pos.y + (this.style.unit / 8), (this.autosize and pos.w) or pos.w - (this.style.unit / 2), 'left') end
  743. end,
  744. }
  745. setmetatable(Gspot.text, {__index = Gspot.util, __call = Gspot.text.load})
  746. Gspot.typetext = {
  747. load = function(this, Gspot, label, pos, parent, autosize)
  748. local element = Gspot:text('', pos, parent, autosize)
  749. element.values = {text = label, cursor = 1}
  750. element.updateinterval = 0.1
  751. element.update = function(this, dt)
  752. this.values.cursor = utf8char_after(this.values.text, this.values.cursor + 1) - 1
  753. this.label = this.values.text:sub(1, this.values.cursor)
  754. end
  755. return Gspot:add(element)
  756. end,
  757. }
  758. setmetatable(Gspot.typetext, {__index = Gspot.util, __call = Gspot.typetext.load})
  759. Gspot.image = {
  760. load = function(this, Gspot, label, pos, parent, img)
  761. local element = Gspot:element('image', label, pos, parent)
  762. element:setimage(img)
  763. return Gspot:add(element)
  764. end,
  765. setimage = function(this, img)
  766. this.Gspot.util.setimage(this, img)
  767. if this.img then
  768. this.pos.w = this.img:getWidth()
  769. this.pos.h = this.img:getHeight()
  770. end
  771. end,
  772. draw = function(this, pos)
  773. if this.img then
  774. this:drawimg(pos)
  775. end
  776. if this.label then
  777. setColor(this.style.labelfg or this.style.fg)
  778. lgprint(this.label, pos.x + ((pos.w - this.style.font:getWidth(this.label)) / 2), (pos.y + pos.h) + ((this.style.unit - this.style.font:getHeight()) / 2))
  779. end
  780. end,
  781. }
  782. setmetatable(Gspot.image, {__index = Gspot.util, __call = Gspot.image.load})
  783. Gspot.button = {
  784. load = function(this, Gspot, label, pos, parent, autosize)
  785. if autosize then this.autosize = autosize end
  786. return Gspot:add(Gspot:element('button', label, pos, parent))
  787. end,
  788. draw = function(this, pos)
  789. if this.parent and this.value == this.parent.value then
  790. if this == this.Gspot.mousein then setColor(this.style.focus)
  791. else setColor(this.style.hilite) end
  792. else
  793. if this == this.Gspot.mousein then setColor(this.style.hilite)
  794. else setColor(this.style.default) end
  795. end
  796. this:drawshape(pos)
  797. setColor(this.style.labelfg or this.style.fg)
  798. if this.shape == 'circle' then
  799. if this.img then this:drawimg(pos) end
  800. if this.label then lgprint(this.label, (pos.x + pos.r) - (this.style.font:getWidth(this.label) / 2), (this.img and (pos.y + (pos.r * 2)) + ((this.style.unit - this.style.font:getHeight()) / 2)) or (pos.y + pos.r) - (this.style.font:getHeight() / 2)) end
  801. else
  802. if this.img then this:drawimg(pos) end
  803. if this.label then lgprint(this.label, (pos.x + (pos.w / 2)) - (this.style.font:getWidth(this.label) / 2), (this.img and pos.y + ((this.style.unit - this.style.font:getHeight()) / 2)) or (pos.y + (pos.h / 2)) - (this.style.font:getHeight() / 2)) end
  804. end
  805. end,
  806. }
  807. setmetatable(Gspot.button, {__index = Gspot.util, __call = Gspot.button.load})
  808. Gspot.imgbutton = {
  809. load = function(this, Gspot, label, pos, parent, img)
  810. local element = Gspot:button(label, pos, parent)
  811. element:setimage(img)
  812. return Gspot:add(element)
  813. end,
  814. }
  815. setmetatable(Gspot.imgbutton, {__index = Gspot.util, __call = Gspot.imgbutton.load})
  816. Gspot.option = {
  817. load = function(this, Gspot, label, pos, parent, value)
  818. local element = Gspot:button(label, pos, parent)
  819. element.value = value
  820. element.click = function(this) this.parent.value = this.value end
  821. return element
  822. end,
  823. }
  824. setmetatable(Gspot.option, {__index = Gspot.util, __call = Gspot.option.load})
  825. Gspot.checkbox = {
  826. load = function(this, Gspot, label, pos, parent, value)
  827. local element = Gspot:element('checkbox', label, pos, parent)
  828. element.value = value
  829. return Gspot:add(element)
  830. end,
  831. click = function(this) this.value = not this.value end,
  832. draw = function(this, pos)
  833. if this == this.Gspot.mousein then setColor(this.style.hilite)
  834. else setColor(this.style.default) end
  835. this:drawshape(pos)
  836. if this.value then
  837. setColor(this.style.fg)
  838. this:drawshape(this.Gspot:pos({x = pos.x + (pos.w / 4), y = pos.y + (pos.h / 4), w = pos.w / 2, h = pos.h / 2, r = pos.r and pos.r / 2}))
  839. end
  840. if this.label then
  841. setColor(this.style.labelfg or this.style.fg)
  842. lgprint(this.label, pos.x + pos.w + (this.style.unit / 2), pos.y + ((this.pos.h - this.style.font:getHeight()) / 2))
  843. end
  844. end,
  845. }
  846. setmetatable(Gspot.checkbox, {__index = Gspot.util, __call = Gspot.checkbox.load})
  847. Gspot.input = {
  848. load = function(this, Gspot, label, pos, parent, value, ispassword, passwordchar)
  849. local element = Gspot:element('input', label, pos, parent)
  850. element.value = (value and tostring(value)) or ''
  851. element.cursor = element.value:len()
  852. element.textorigin = 0
  853. element.cursorlife = 0
  854. element.keyrepeat = true
  855. element.ispassword = ispassword or false
  856. element.passwordchar = (passwordchar and tostring(passwordchar)) or '*'
  857. return Gspot:add(element)
  858. end,
  859. update = function(this, dt)
  860. if this.cursor > #this.value then this.cursor = #this.value end
  861. if this.Gspot.focus == this then
  862. if this.cursorlife >= 1 then this.cursorlife = 0
  863. else this.cursorlife = this.cursorlife + dt end
  864. end
  865. end,
  866. draw = function(this, pos)
  867. if this == this.Gspot.focus then
  868. setColor(this.style.bg)
  869. elseif this == this.Gspot.mousein then
  870. setColor(this.style.hilite)
  871. else
  872. setColor(this.style.default)
  873. end
  874. this:drawshape(pos)
  875. -- Margin of edit box is unit/4 on each side, so total margin is unit/2
  876. local editw = pos.w - this.style.unit / 2
  877. if editw >= 1 then -- won't be visible otherwise and we need room for the cursor
  878. -- We don't want to undo the current scissor, to avoid printing text where it shouldn't be
  879. -- (e.g. partially visible edit box inside a viewport) so we clip the current scissor.
  880. local sx, sy, sw, sh = clipScissor(pos.x + this.style.unit / 4, pos.y, editw, pos.h)
  881. setColor(this.style.fg)
  882. local str = this.ispassword and string.rep(this.passwordchar,utf8len(tostring(this.value))) or tostring(this.value)
  883. -- cursorx is the position relative to the start of the edit box
  884. -- (add pos.x + this.style.unit/4 to obtain the screen X coordinate)
  885. local cursorx = this.textorigin + this.style.font:getWidth(str:sub(1, this.cursor))
  886. -- adjust text origin so that the cursor is always within the edit box
  887. if cursorx < 0 then
  888. this.textorigin = math.min(0, this.textorigin - cursorx)
  889. cursorx = 0
  890. end
  891. if cursorx > editw - 1 then
  892. this.textorigin = math.min(0, this.textorigin - cursorx + editw - 1)
  893. cursorx = editw - 1
  894. end
  895. -- print the whole text and let the scissor do the clipping
  896. lgprint(str, pos.x + this.style.unit / 4 + this.textorigin, pos.y + (pos.h - this.style.font:getHeight()) / 2)
  897. if this == this.Gspot.focus and this.cursorlife < 0.5 then
  898. love.graphics.rectangle("fill", pos.x + this.style.unit / 4 + cursorx, pos.y + this.style.unit / 8, 1, pos.h - this.style.unit / 4)
  899. end
  900. -- restore current scissor
  901. love.graphics.setScissor(sx, sy, sw, sh)
  902. end
  903. if this.label then
  904. setColor(this.style.labelfg or this.style.fg)
  905. lgprint(this.label, pos.x - ((this.style.unit / 2) + this.style.font:getWidth(this.label)), pos.y + ((this.pos.h - this.style.font:getHeight()) / 2))
  906. end
  907. end,
  908. click = function(this) this:focus() end,
  909. done = function(this) this.Gspot:unfocus() end,
  910. keypress = function(this, key)
  911. local save_cursorlife = this.cursorlife
  912. this.cursorlife = 0
  913. -- fragments attributed to vrld's Quickie : https://github.com/vrld/Quickie
  914. if key == 'backspace' then
  915. local cur = this.cursor
  916. if cur > 0 then
  917. this.cursor = utf8char_begin(this.value, cur) - 1
  918. this.value = this.value:sub(1, this.cursor)..this.value:sub(cur + 1)
  919. end
  920. elseif key == 'delete' then
  921. local cur = utf8char_after(this.value, this.cursor + 1)
  922. this.value = this.value:sub(1, this.cursor)..this.value:sub(cur)
  923. elseif key == 'left' then
  924. if this.cursor > 0 then
  925. this.cursor = utf8char_begin(this.value, this.cursor) - 1
  926. end
  927. elseif key == 'right' then
  928. this.cursor = utf8char_after(this.value, this.cursor + 1) - 1
  929. elseif key == 'home' then
  930. this.cursor = 0
  931. elseif key == 'end' then
  932. this.cursor = this.value:len()
  933. elseif key == 'tab' and this.next and this.next.elementtype then
  934. this.next:focus()
  935. elseif key == 'escape' then
  936. this.Gspot:unfocus()
  937. else
  938. -- all of the above reset the blink timer, but otherwise it's retained
  939. this.cursorlife = save_cursorlife
  940. end
  941. -- /fragments
  942. end,
  943. textinput = function(this, key)
  944. this.value = this.value:sub(1, this.cursor) .. key .. this.value:sub(this.cursor + 1)
  945. this.cursor = this.cursor + #key
  946. -- reset blink timer
  947. this.cursorlife = 0
  948. end,
  949. }
  950. setmetatable(Gspot.input, {__index = Gspot.util, __call = Gspot.input.load})
  951. Gspot.scroll = {
  952. load = function(this, Gspot, label, pos, parent, values)
  953. local element = Gspot:element('scroll', label, pos, parent)
  954. element.values = Gspot:scrollvalues(values)
  955. return Gspot:add(element)
  956. end,
  957. update = function(this, dt)
  958. local mouse = {}
  959. mouse.x, mouse.y = this.Gspot:getmouse()
  960. if this.withinrect(mouse, this:getpos()) and not this.Gspot.drag then this.Gspot.mousein = this end
  961. end,
  962. step = function(this, step)
  963. if step > 0 then this.values.current = math.max(this.values.current - this.values.step, this.values.min)
  964. elseif step < 0 then this.values.current = math.min(this.values.current + this.values.step, this.values.max)
  965. end
  966. end,
  967. drag = function(this, x, y)
  968. local pos = this:getpos()
  969. local hs = this.style.hs
  970. if hs == 'auto' then
  971. if this.values.axis == 'vertical' then
  972. local h = this.parent and this.parent.pos.h or pos.h
  973. hs = math.max(this.style.unit / 4, math.min(pos.h, pos.h * h / (this.values.max - this.values.min + h)))
  974. else
  975. local w = this.parent and this.parent.pos.w or pos.w
  976. hs = math.max(this.style.unit / 4, math.min(pos.w, pos.w * w / (this.values.max - this.values.min + w)))
  977. end
  978. end
  979. if this.values.axis == 'vertical' and pos.h == hs or this.values.axis ~= 'vertical' and pos.w == hs then
  980. this.values.current = 0
  981. else
  982. this.values.current = this.values.min + ((this.values.max - this.values.min) * ((this.values.axis == 'vertical' and ((math.min(math.max(pos.y, y - math.floor(hs / 2)), (pos.y + pos.h - hs)) - pos.y) / (pos.h - hs))) or ((math.min(math.max(pos.x, x - math.floor(hs / 2)), (pos.x + pos.w - hs)) - pos.x) / (pos.w - hs))))
  983. end
  984. end,
  985. wheelup = function(this)
  986. if this.values.axis == 'horizontal' then this:step(-1) else this:step(1) end
  987. end,
  988. wheeldown = function(this)
  989. if this.values.axis == 'horizontal' then this:step(1) else this:step(-1) end
  990. end,
  991. keypress = function(this, key)
  992. if key == 'left' and this.values.axis == 'horizontal' then
  993. this:step(1)
  994. elseif key == 'right' and this.values.axis == 'horizontal' then
  995. this:step(-1)
  996. elseif key == 'up' and this.values.axis == 'vertical' then
  997. this:step(-1)
  998. elseif key == 'down' and this.values.axis == 'vertical' then
  999. this:step(1)
  1000. elseif key == 'tab' and this.next and this.next.elementtype then
  1001. this.next:focus()
  1002. elseif key == 'escape' then
  1003. this.Gspot:unfocus()
  1004. end
  1005. end,
  1006. done = function(this) this.Gspot:unfocus() end,
  1007. draw = function(this, pos)
  1008. if this == this.Gspot.mousein or this == this.Gspot.drag or this == this.Gspot.focus then setColor(this.style.default)
  1009. else setColor(this.style.bg) end
  1010. this:rect(pos)
  1011. if this == this.Gspot.mousein or this == this.Gspot.drag or this == this.Gspot.focus then setColor(this.style.fg)
  1012. else setColor(this.style.hilite) end
  1013. local hs = this.style.hs
  1014. if hs == 'auto' then
  1015. if this.values.axis == 'vertical' then
  1016. local h = this.parent and this.parent.pos.h or pos.h
  1017. hs = math.max(this.style.unit / 4, math.min(pos.h, pos.h * h / (this.values.max - this.values.min + h)))
  1018. else
  1019. local w = this.parent and this.parent.pos.w or pos.w
  1020. hs = math.max(this.style.unit / 4, math.min(pos.w, pos.w * w / (this.values.max - this.values.min + w)))
  1021. end
  1022. end
  1023. local handlepos = this.Gspot:pos({x = (this.values.axis == 'horizontal' and math.min(pos.x + (pos.w - hs), math.max(pos.x, pos.x + ((pos.w - hs) * (this.values.current / (this.values.max - this.values.min)))))) or pos.x, y = (this.values.axis == 'vertical' and math.min(pos.y + (pos.h - hs), math.max(pos.y, pos.y + ((pos.h - hs) * (this.values.current / (this.values.max - this.values.min)))))) or pos.y, w = this.values.axis == 'horizontal' and hs or this.style.unit, h = this.values.axis == 'vertical' and hs or this.style.unit, r = pos.r})
  1024. this:drawshape(handlepos)
  1025. if this.label then
  1026. setColor(this.style.labelfg or this.style.fg)
  1027. lgprint(this.label, (this.values.axis == 'horizontal' and pos.x - ((this.style.unit / 2) + this.style.font:getWidth(this.label))) or pos.x + ((pos.w - this.style.font:getWidth(this.label)) / 2), (this.values.axis == 'vertical' and (pos.y + pos.h) + ((this.style.unit - this.style.font:getHeight()) / 2)) or pos.y + ((this.style.unit - this.style.font:getHeight()) / 2))
  1028. end
  1029. end,
  1030. }
  1031. Gspot.scroll.rdrag = Gspot.scroll.drag
  1032. setmetatable(Gspot.scroll, {__index = Gspot.util, __call = Gspot.scroll.load})
  1033. Gspot.scrollgroup = {
  1034. load = function(this, Gspot, label, pos, parent, axis)
  1035. axis = axis or 'both'
  1036. local element = Gspot:element('scrollgroup', label, pos, parent)
  1037. element.maxh = 0
  1038. element = Gspot:add(element)
  1039. if axis ~= 'horizontal' then element.scrollv = Gspot:scroll(nil, {x = element.pos.w, y = 0, w = element.style.unit, h = element.pos.h}, element, {0, 0, 0, element.style.unit, 'vertical'}) end
  1040. if axis ~= 'vertical' then element.scrollh = Gspot:scroll(nil, {x = 0, y = element.pos.h, w = element.pos.w, h = element.style.unit}, element, {0, 0, 0, element.style.unit, 'horizontal'}) end
  1041. return element
  1042. end,
  1043. draw = function(this, pos)
  1044. setColor(this.style.bg)
  1045. this:drawshape(pos)
  1046. if this.label then
  1047. setColor(this.style.labelfg or this.style.fg)
  1048. lgprint(this.label, pos.x + ((pos.w - this.style.font:getWidth(this.label)) / 2), pos.y + ((this.style.unit - this.style.font:getHeight()) / 2))
  1049. end
  1050. end,
  1051. }
  1052. setmetatable(Gspot.scrollgroup, {__index = Gspot.util, __call = Gspot.scrollgroup.load})
  1053. Gspot.hidden = {
  1054. load = function(this, Gspot, label, pos, parent)
  1055. return Gspot:add(Gspot:element('hidden', label, pos, parent))
  1056. end,
  1057. draw = function(this, pos)
  1058. --
  1059. end,
  1060. }
  1061. setmetatable(Gspot.hidden, {__index = Gspot.util, __call = Gspot.hidden.load})
  1062. Gspot.radius = {
  1063. load = function(this, Gspot, label, pos, parent)
  1064. return Gspot:add(Gspot:element('radius', label, pos, parent))
  1065. end,
  1066. draw = function(this, pos)
  1067. --
  1068. end,
  1069. }
  1070. setmetatable(Gspot.radius, {__index = Gspot.util, __call = Gspot.radius.load})
  1071. Gspot.feedback = {
  1072. load = function(this, Gspot, label, pos, parent, autopos)
  1073. pos = pos or {}
  1074. autopos = (autopos == nil and true) or autopos
  1075. if autopos then
  1076. for i, element in ipairs(Gspot.elements) do
  1077. if element.elementtype == 'feedback' and element.autopos then element.pos.y = element.pos.y + element.style.unit end
  1078. end
  1079. end
  1080. pos.x = pos.x or pos[1] or 0
  1081. pos.y = pos.y or pos[2] or 0
  1082. pos.w = 0
  1083. pos.h = 0
  1084. local element = Gspot:add(Gspot:element('feedback', label, pos, parent))
  1085. element.style.fg = {255, 255, 255, 255}
  1086. element.alpha = 255
  1087. element.life = 5
  1088. element.autopos = autopos
  1089. return element
  1090. end,
  1091. update = function(this, dt)
  1092. this.alpha = this.alpha - ((255 * dt) / this.life)
  1093. if this.alpha < 0 then
  1094. this.Gspot:rem(this)
  1095. return
  1096. end
  1097. local color = this.style.fg
  1098. this.style.fg = {color[1], color[2], color[3], this.alpha}
  1099. end,
  1100. draw = function(this, pos)
  1101. setColor(this.style.fg)
  1102. lgprint(this.label, pos.x + (this.style.unit / 4), pos.y + ((this.style.unit - this.style.font:getHeight()) / 2))
  1103. end,
  1104. }
  1105. setmetatable(Gspot.feedback, {__index = Gspot.util, __call = Gspot.feedback.load})
  1106. Gspot.progress = {
  1107. load = function(this, Gspot, label, pos, parent)
  1108. local element = Gspot:add(Gspot:element('progress', label, pos, parent))
  1109. element.loaders = {}
  1110. element.values = Gspot.scrollvalues(element, {0, 0, 0, 1})
  1111. return element
  1112. end,
  1113. update = function(this, dt)
  1114. for i, loader in ipairs(this.loaders) do
  1115. if loader.status == 'waiting' then
  1116. local success, result = pcall(function(loader) return loader.func() end, loader)
  1117. loader.status = (success and 'done') or error
  1118. loader.result = result
  1119. this.values.current = this.values.current + 1
  1120. break
  1121. end
  1122. if i == #this.loaders then this:done() end
  1123. end
  1124. end,
  1125. draw = function(this, pos)
  1126. setColor(this.style.default)
  1127. this:drawshape(pos)
  1128. setColor(this.style.fg)
  1129. this:rect({x = pos.x, y = pos.y, w = pos.w * (this.values.current / this.values.max), h = pos.h})
  1130. if this.label then
  1131. setColor(this.style.labelfg or this.style.fg)
  1132. lgprint(this.label, pos.x - ((this.style.unit / 2) + this.style.font:getWidth(this.label)), pos.y + ((this.pos.h - this.style.font:getHeight()) / 2))
  1133. end
  1134. end,
  1135. done = function(this)
  1136. this.Gspot:rem(this)
  1137. end,
  1138. add = function(this, loader)
  1139. table.insert(this.loaders, {status = 'waiting', func = loader})
  1140. this.values.max = this.values.max + 1
  1141. end,
  1142. }
  1143. setmetatable(Gspot.progress, {__index = Gspot.util, __call = Gspot.progress.load})
  1144. return Gspot:setComponentMax("native")