DrawCommands.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. --[[
  2. MIT License
  3. Copyright (c) 2019 Mitchell Davis <coding.jackalope@gmail.com>
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. --]]
  20. local DrawCommands = {}
  21. local LayerTable = {}
  22. local PendingBatches = {}
  23. local ActiveBatch = nil
  24. local Types =
  25. {
  26. Rect = 1,
  27. Triangle = 2,
  28. Text = 3,
  29. Scissor = 4,
  30. TransformPush = 5,
  31. TransformPop = 6,
  32. ApplyTransform = 7,
  33. Check = 8,
  34. Line = 9,
  35. TextFormatted = 10,
  36. IntersectScissor = 11,
  37. Cross = 12,
  38. Image = 13,
  39. SubImage = 14,
  40. Circle = 15,
  41. DrawCanvas = 16,
  42. Mesh = 17,
  43. TextObject = 18
  44. }
  45. local Layers =
  46. {
  47. Normal = 1,
  48. ContextMenu = 2,
  49. MainMenuBar = 3,
  50. Dialog = 4,
  51. Debug = 5
  52. }
  53. local ActiveLayer = Layers.Normal
  54. local function GetLayerDebugInfo(Layer)
  55. local Result = {}
  56. Result['Channel Count'] = #Layer
  57. local Channels = {}
  58. for K, Channel in pairs(Layer) do
  59. local Collection = {}
  60. Collection['Batch Count'] = #Channel
  61. table.insert(Channels, Collection)
  62. end
  63. Result['Channels'] = Channels
  64. return Result
  65. end
  66. local function DrawRect(Rect)
  67. love.graphics.setColor(Rect.Color)
  68. love.graphics.rectangle(Rect.Mode, Rect.X, Rect.Y, Rect.Width, Rect.Height, Rect.Radius, Rect.Radius)
  69. end
  70. local function GetTriangleVertices(X, Y, Radius, Direction)
  71. local Result = {}
  72. if Direction == 'north' then
  73. Result =
  74. {
  75. X, Y - Radius,
  76. X - Radius, Y + Radius,
  77. X + Radius, Y + Radius
  78. }
  79. elseif Direction == 'east' then
  80. Result =
  81. {
  82. X + Radius, Y,
  83. X - Radius, Y - Radius,
  84. X - Radius, Y + Radius
  85. }
  86. elseif Direction == 'south' then
  87. Result =
  88. {
  89. X, Y + Radius,
  90. X + Radius, Y - Radius,
  91. X - Radius, Y - Radius
  92. }
  93. elseif Direction == 'west' then
  94. Result =
  95. {
  96. X - Radius, Y,
  97. X + Radius, Y + Radius,
  98. X + Radius, Y - Radius
  99. }
  100. else
  101. assert(false, "Invalid direction given: " .. Direction)
  102. end
  103. return Result
  104. end
  105. local function DrawTriangle(Triangle)
  106. love.graphics.setColor(Triangle.Color)
  107. local Vertices = GetTriangleVertices(Triangle.X, Triangle.Y, Triangle.Radius, Triangle.Direction)
  108. love.graphics.polygon(Triangle.Mode, Vertices)
  109. end
  110. local function DrawCheck(Check)
  111. love.graphics.setColor(Check.Color)
  112. local Vertices =
  113. {
  114. Check.X - Check.Radius * 0.5, Check.Y,
  115. Check.X, Check.Y + Check.Radius,
  116. Check.X + Check.Radius, Check.Y - Check.Radius
  117. }
  118. love.graphics.line(Vertices)
  119. end
  120. local function DrawText(Text)
  121. love.graphics.setFont(Text.Font)
  122. love.graphics.setColor(Text.Color)
  123. love.graphics.print(Text.Text, Text.X, Text.Y)
  124. end
  125. local function DrawTextFormatted(Text)
  126. love.graphics.setFont(Text.Font)
  127. love.graphics.setColor(Text.Color)
  128. love.graphics.printf(Text.Text, Text.X, Text.Y, Text.W, Text.Align)
  129. end
  130. local function DrawTextObject(Text)
  131. love.graphics.setColor(Text.Color)
  132. love.graphics.draw(Text.Text, Text.X, Text.Y)
  133. end
  134. local function DrawLine(Line)
  135. love.graphics.setColor(Line.Color)
  136. local LineW = love.graphics.getLineWidth()
  137. love.graphics.setLineWidth(Line.Width)
  138. love.graphics.line(Line.X1, Line.Y1, Line.X2, Line.Y2)
  139. love.graphics.setLineWidth(LineW)
  140. end
  141. local function DrawCross(Cross)
  142. local X, Y = Cross.X, Cross.Y
  143. local R = Cross.Radius
  144. love.graphics.setColor(Cross.Color)
  145. love.graphics.line(X - R, Y - R, X + R, Y + R)
  146. love.graphics.line(X - R, Y + R, X + R, Y - R)
  147. end
  148. local function DrawImage(Image)
  149. love.graphics.setColor(Image.Color)
  150. love.graphics.draw(Image.Image, Image.X, Image.Y, Image.Rotation, Image.ScaleX, Image.ScaleY)
  151. end
  152. local function DrawSubImage(Image)
  153. love.graphics.setColor(Image.Color)
  154. love.graphics.draw(Image.Image, Image.Quad, Image.Transform)
  155. end
  156. local function DrawCircle(Circle)
  157. love.graphics.setColor(Circle.Color)
  158. love.graphics.circle(Circle.Mode, Circle.X, Circle.Y, Circle.Radius, Circle.Segments)
  159. end
  160. local function DrawElements(Elements)
  161. for K, V in pairs(Elements) do
  162. if V.Type == Types.Rect then
  163. DrawRect(V)
  164. elseif V.Type == Types.Triangle then
  165. DrawTriangle(V)
  166. elseif V.Type == Types.Text then
  167. DrawText(V)
  168. elseif V.Type == Types.Scissor then
  169. love.graphics.setScissor(V.X, V.Y, V.W, V.H)
  170. elseif V.Type == Types.TransformPush then
  171. love.graphics.push()
  172. elseif V.Type == Types.TransformPop then
  173. love.graphics.pop()
  174. elseif V.Type == Types.ApplyTransform then
  175. love.graphics.applyTransform(V.Transform)
  176. elseif V.Type == Types.Check then
  177. DrawCheck(V)
  178. elseif V.Type == Types.Line then
  179. DrawLine(V)
  180. elseif V.Type == Types.TextFormatted then
  181. DrawTextFormatted(V)
  182. elseif V.Type == Types.IntersectScissor then
  183. love.graphics.intersectScissor(V.X, V.Y, V.W, V.H)
  184. elseif V.Type == Types.Cross then
  185. DrawCross(V)
  186. elseif V.Type == Types.Image then
  187. DrawImage(V)
  188. elseif V.Type == Types.SubImage then
  189. DrawSubImage(V)
  190. elseif V.Type == Types.Circle then
  191. DrawCircle(V)
  192. elseif V.Type == Types.DrawCanvas then
  193. love.graphics.setBlendMode('alpha', 'premultiplied')
  194. love.graphics.setColor(1.0, 1.0, 1.0, 1.0)
  195. love.graphics.draw(V.Canvas, V.X, V.Y)
  196. love.graphics.setBlendMode('alpha')
  197. elseif V.Type == Types.Mesh then
  198. love.graphics.setColor(1.0, 1.0, 1.0, 1.0)
  199. love.graphics.draw(V.Mesh, V.X, V.Y)
  200. elseif V.Type == Types.TextObject then
  201. DrawTextObject(V)
  202. end
  203. end
  204. end
  205. local function AssertActiveBatch()
  206. assert(ActiveBatch ~= nil, "DrawCommands.Begin was not called before commands were issued!")
  207. end
  208. local function DrawLayer(Layer, Name)
  209. if Layer.Channels == nil then
  210. return
  211. end
  212. local Keys = {}
  213. for K, Channel in pairs(Layer.Channels) do
  214. table.insert(Keys, K)
  215. end
  216. table.sort(Keys)
  217. for Index, C in ipairs(Keys) do
  218. local Channel = Layer.Channels[C]
  219. if Channel ~= nil then
  220. for I, V in ipairs(Channel) do
  221. DrawElements(V.Elements)
  222. end
  223. end
  224. end
  225. end
  226. function DrawCommands.Reset()
  227. LayerTable = {}
  228. LayerTable[Layers.Normal] = {}
  229. LayerTable[Layers.ContextMenu] = {}
  230. LayerTable[Layers.MainMenuBar] = {}
  231. LayerTable[Layers.Dialog] = {}
  232. LayerTable[Layers.Debug] = {}
  233. ActiveLayer = Layers.Normal
  234. PendingBatches = {}
  235. ActiveBatch = nil
  236. end
  237. function DrawCommands.Begin(Options)
  238. Options = Options == nil and {} or Options
  239. Options.Channel = Options.Channel == nil and 1 or Options.Channel
  240. if LayerTable[ActiveLayer].Channels == nil then
  241. LayerTable[ActiveLayer].Channels = {}
  242. end
  243. if LayerTable[ActiveLayer].Channels[Options.Channel] == nil then
  244. LayerTable[ActiveLayer].Channels[Options.Channel] = {}
  245. end
  246. local Channel = LayerTable[ActiveLayer].Channels[Options.Channel]
  247. ActiveBatch = {}
  248. ActiveBatch.Elements = {}
  249. table.insert(Channel, ActiveBatch)
  250. table.insert(PendingBatches, 1, ActiveBatch)
  251. end
  252. function DrawCommands.End()
  253. if ActiveBatch ~= nil then
  254. love.graphics.setScissor()
  255. table.remove(PendingBatches, 1)
  256. ActiveBatch = nil
  257. if #PendingBatches > 0 then
  258. ActiveBatch = PendingBatches[1]
  259. end
  260. end
  261. end
  262. function DrawCommands.SetLayer(Layer)
  263. if Layer == 'Normal' then
  264. ActiveLayer = Layers.Normal
  265. elseif Layer == 'ContextMenu' then
  266. ActiveLayer = Layers.ContextMenu
  267. elseif Layer == 'MainMenuBar' then
  268. ActiveLayer = Layers.MainMenuBar
  269. elseif Layer == 'Dialog' then
  270. ActiveLayer = Layers.Dialog
  271. elseif Layer == 'Debug' then
  272. ActiveLayer = Layers.Debug
  273. end
  274. end
  275. function DrawCommands.Rectangle(Mode, X, Y, Width, Height, Color, Radius)
  276. AssertActiveBatch()
  277. local Item = {}
  278. Item.Type = Types.Rect
  279. Item.Mode = Mode
  280. Item.X = X
  281. Item.Y = Y
  282. Item.Width = Width
  283. Item.Height = Height
  284. Item.Color = Color and Color or {0.0, 0.0, 0.0, 1.0}
  285. Item.Radius = Radius and Radius or 0.0
  286. table.insert(ActiveBatch.Elements, Item)
  287. end
  288. function DrawCommands.Triangle(Mode, X, Y, Radius, Direction, Color)
  289. AssertActiveBatch()
  290. local Item = {}
  291. Item.Type = Types.Triangle
  292. Item.Mode = Mode
  293. Item.X = X
  294. Item.Y = Y
  295. Item.Radius = Radius
  296. Item.Direction = Direction
  297. Item.Color = Color and Color or {0.0, 0.0, 0.0, 1.0}
  298. table.insert(ActiveBatch.Elements, Item)
  299. end
  300. function DrawCommands.Print(Text, X, Y, Color, Font)
  301. AssertActiveBatch()
  302. local Item = {}
  303. Item.Type = Types.Text
  304. Item.Text = Text
  305. Item.X = X
  306. Item.Y = Y
  307. Item.Color = Color and Color or {1.0, 1.0, 1.0, 1.0}
  308. Item.Font = Font
  309. table.insert(ActiveBatch.Elements, Item)
  310. end
  311. function DrawCommands.Printf(Text, X, Y, W, Align, Color, Font)
  312. AssertActiveBatch()
  313. local Item = {}
  314. Item.Type = Types.TextFormatted
  315. Item.Text = Text
  316. Item.X = X
  317. Item.Y = Y
  318. Item.W = W
  319. Item.Align = Align and Align or 'left'
  320. Item.Color = Color and Color or {1.0, 1.0, 1.0, 1.0}
  321. Item.Font = Font
  322. table.insert(ActiveBatch.Elements, Item)
  323. end
  324. function DrawCommands.Scissor(X, Y, W, H)
  325. AssertActiveBatch()
  326. if W ~= nil then
  327. assert(W >= 0.0, "Cannot set scissor with negative width.")
  328. end
  329. if H ~= nil then
  330. assert(H >= 0.0, "Cannot set scissor with negative height.")
  331. end
  332. local Item = {}
  333. Item.Type = Types.Scissor
  334. Item.X = X
  335. Item.Y = Y
  336. Item.W = W
  337. Item.H = H
  338. table.insert(ActiveBatch.Elements, Item)
  339. end
  340. function DrawCommands.IntersectScissor(X, Y, W, H)
  341. AssertActiveBatch()
  342. local Item = {}
  343. Item.Type = Types.IntersectScissor
  344. Item.X = X and X or 0.0
  345. Item.Y = Y and Y or 0.0
  346. Item.W = W and W or 0.0
  347. Item.H = H and H or 0.0
  348. table.insert(ActiveBatch.Elements, Item)
  349. end
  350. function DrawCommands.TransformPush()
  351. AssertActiveBatch()
  352. local Item = {}
  353. Item.Type = Types.TransformPush
  354. table.insert(ActiveBatch.Elements, Item)
  355. end
  356. function DrawCommands.TransformPop()
  357. AssertActiveBatch()
  358. local Item = {}
  359. Item.Type = Types.TransformPop
  360. table.insert(ActiveBatch.Elements, Item)
  361. end
  362. function DrawCommands.ApplyTransform(Transform)
  363. AssertActiveBatch()
  364. local Item = {}
  365. Item.Type = Types.ApplyTransform
  366. Item.Transform = Transform
  367. table.insert(ActiveBatch.Elements, Item)
  368. end
  369. function DrawCommands.Check(X, Y, Radius, Color)
  370. AssertActiveBatch()
  371. local Item = {}
  372. Item.Type = Types.Check
  373. Item.X = X
  374. Item.Y = Y
  375. Item.Radius = Radius
  376. Item.Color = Color and Color or {0.0, 0.0, 0.0, 1.0}
  377. table.insert(ActiveBatch.Elements, Item)
  378. end
  379. function DrawCommands.Line(X1, Y1, X2, Y2, Width, Color)
  380. AssertActiveBatch()
  381. local Item = {}
  382. Item.Type = Types.Line
  383. Item.X1 = X1
  384. Item.Y1 = Y1
  385. Item.X2 = X2
  386. Item.Y2 = Y2
  387. Item.Width = Width
  388. Item.Color = Color and Color or {0.0, 0.0, 0.0, 1.0}
  389. table.insert(ActiveBatch.Elements, Item)
  390. end
  391. function DrawCommands.Cross(X, Y, Radius, Color)
  392. AssertActiveBatch()
  393. local Item = {}
  394. Item.Type = Types.Cross
  395. Item.X = X
  396. Item.Y = Y
  397. Item.Radius = Radius
  398. Item.Color = Color and Color or {0.0, 0.0, 0.0, 1.0}
  399. table.insert(ActiveBatch.Elements, Item)
  400. end
  401. function DrawCommands.Image(X, Y, Image, Rotation, ScaleX, ScaleY, Color)
  402. AssertActiveBatch()
  403. local Item = {}
  404. Item.Type = Types.Image
  405. Item.X = X
  406. Item.Y = Y
  407. Item.Image = Image
  408. Item.Rotation = Rotation
  409. Item.ScaleX = ScaleX
  410. Item.ScaleY = ScaleY
  411. Item.Color = Color and Color or {1.0, 1.0, 1.0, 1.0}
  412. table.insert(ActiveBatch.Elements, Item)
  413. end
  414. function DrawCommands.SubImage(X, Y, Image, SX, SY, SW, SH, Rotation, ScaleX, ScaleY, Color)
  415. AssertActiveBatch()
  416. local Item = {}
  417. Item.Type = Types.SubImage
  418. Item.Transform = love.math.newTransform(X, Y, Rotation, ScaleX, ScaleY)
  419. Item.Image = Image
  420. Item.Quad = love.graphics.newQuad(SX, SY, SW, SH, Image:getWidth(), Image:getHeight())
  421. Item.Color = Color and Color or {1.0, 1.0, 1.0, 1.0}
  422. table.insert(ActiveBatch.Elements, Item)
  423. end
  424. function DrawCommands.Circle(Mode, X, Y, Radius, Color, Segments)
  425. AssertActiveBatch()
  426. local Item = {}
  427. Item.Type = Types.Circle
  428. Item.Mode = Mode
  429. Item.X = X
  430. Item.Y = Y
  431. Item.Radius = Radius
  432. Item.Color = Color and Color or {0.0, 0.0, 0.0, 1.0}
  433. Item.Segments = Segments and Segments or 24
  434. table.insert(ActiveBatch.Elements, Item)
  435. end
  436. function DrawCommands.DrawCanvas(Canvas, X, Y)
  437. AssertActiveBatch()
  438. local Item = {}
  439. Item.Type = Types.DrawCanvas
  440. Item.Canvas = Canvas
  441. Item.X = X
  442. Item.Y = Y
  443. table.insert(ActiveBatch.Elements, Item)
  444. end
  445. function DrawCommands.Mesh(Mesh, X, Y)
  446. AssertActiveBatch()
  447. local Item = {}
  448. Item.Type = Types.Mesh
  449. Item.Mesh = Mesh
  450. Item.X = X
  451. Item.Y = Y
  452. table.insert(ActiveBatch.Elements, Item)
  453. end
  454. function DrawCommands.Text(Text, X, Y)
  455. AssertActiveBatch()
  456. local Item = {}
  457. Item.Type = Types.TextObject
  458. Item.Text = Text
  459. Item.X = X
  460. Item.Y = Y
  461. Item.Color = {0, 0, 0, 1}
  462. table.insert(ActiveBatch.Elements, Item)
  463. end
  464. function DrawCommands.Execute()
  465. DrawLayer(LayerTable[Layers.Normal], 'Normal')
  466. DrawLayer(LayerTable[Layers.ContextMenu], 'ContextMenu')
  467. DrawLayer(LayerTable[Layers.MainMenuBar], 'MainMenuBar')
  468. DrawLayer(LayerTable[Layers.Dialog], 'Dialog')
  469. DrawLayer(LayerTable[Layers.Debug], 'Debug')
  470. end
  471. function DrawCommands.GetDebugInfo()
  472. local Result = {}
  473. Result['Normal'] = GetLayerDebugInfo(LayerTable[Layers.Normal])
  474. Result['ContextMenu'] = GetLayerDebugInfo(LayerTable[Layers.ContextMenu])
  475. Result['MainMenuBar'] = GetLayerDebugInfo(LayerTable[Layers.MainMenuBar])
  476. Result['Dialog'] = GetLayerDebugInfo(LayerTable[Layers.Dialog])
  477. Result['Debug'] = GetLayerDebugInfo(LayerTable[Layers.Debug])
  478. return Result
  479. end
  480. return DrawCommands