Region.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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 = require(SLAB_PATH .. ".Internal.Core.DrawCommands")
  21. local MenuState = require(SLAB_PATH .. ".Internal.UI.MenuState")
  22. local Mouse = require(SLAB_PATH .. ".Internal.Input.Mouse")
  23. local Style = require(SLAB_PATH .. ".Style")
  24. local Utility = require(SLAB_PATH .. ".Internal.Core.Utility")
  25. local Region = {}
  26. local Instances = {}
  27. local Stack = {}
  28. local ActiveInstance = nil
  29. local ScrollPad = 3.0
  30. local ScrollBarSize = 10.0
  31. local WheelX = 0.0
  32. local WheelY = 0.0
  33. local WheelSpeed = 3.0
  34. local HotInstance = nil
  35. local WheelInstance = nil
  36. local ScrollInstance = nil
  37. local function GetXScrollSize(Instance)
  38. if Instance ~= nil then
  39. return math.max(Instance.W - (Instance.ContentW - Instance.W), 10.0)
  40. end
  41. return 0.0
  42. end
  43. local function GetYScrollSize(Instance)
  44. if Instance ~= nil then
  45. return math.max(Instance.H - (Instance.ContentH - Instance.H), 10.0)
  46. end
  47. return 0.0
  48. end
  49. local function IsScrollHovered(Instance, X, Y)
  50. local HasScrollX, HasScrollY = false, false
  51. if Instance ~= nil then
  52. if Instance.HasScrollX then
  53. local PosY = Instance.Y + Instance.H - ScrollPad - ScrollBarSize
  54. local SizeX = GetXScrollSize(Instance)
  55. local PosX = Instance.ScrollPosX
  56. HasScrollX = Instance.X + PosX <= X and X < Instance.X + PosX + SizeX and PosY <= Y and Y < PosY + ScrollBarSize
  57. end
  58. if Instance.HasScrollY then
  59. local PosX = Instance.X + Instance.W - ScrollPad - ScrollBarSize
  60. local SizeY = GetYScrollSize(Instance)
  61. local PosY = Instance.ScrollPosY
  62. HasScrollY = PosX <= X and X < PosX + ScrollBarSize and Instance.Y + PosY <= Y and Y < Instance.Y + PosY + SizeY
  63. end
  64. end
  65. return HasScrollX, HasScrollY
  66. end
  67. local function Contains(Instance, X, Y)
  68. if Instance ~= nil then
  69. return Instance.X <= X and X <= Instance.X + Instance.W and Instance.Y <= Y and Y <= Instance.Y + Instance.H
  70. end
  71. return false
  72. end
  73. local function UpdateScrollBars(Instance, IsObstructed)
  74. if Instance.IgnoreScroll then
  75. return
  76. end
  77. Instance.HasScrollX = Instance.ContentW > Instance.W
  78. Instance.HasScrollY = Instance.ContentH > Instance.H
  79. local X, Y = Instance.MouseX, Instance.MouseY
  80. Instance.HoverScrollX, Instance.HoverScrollY = IsScrollHovered(Instance, X, Y)
  81. local XSize = Instance.W - GetXScrollSize(Instance)
  82. local YSize = Instance.H - GetYScrollSize(Instance)
  83. if IsObstructed then
  84. Instance.HoverScrollX = false
  85. Instance.HoverScrollY = false
  86. end
  87. local IsMouseReleased = Mouse.IsReleased(1)
  88. local IsMouseClicked = Mouse.IsClicked(1)
  89. local DeltaX, DeltaY = Mouse.GetDelta()
  90. if WheelInstance == Instance then
  91. Instance.HoverScrollX = WheelX ~= 0.0
  92. Instance.HoverScrollY = WheelY ~= 0.0
  93. end
  94. if not IsObstructed and Contains(Instance, X, Y) or (Instance.HoverScrollX or Instance.HoverScrollY) then
  95. if WheelInstance == Instance then
  96. if WheelX ~= 0.0 then
  97. Instance.ScrollPosX = math.max(Instance.ScrollPosX + WheelX, 0.0)
  98. Instance.IsScrollingX = true
  99. IsMouseReleased = true
  100. WheelX = 0.0
  101. end
  102. if WheelY ~= 0.0 then
  103. Instance.ScrollPosY = math.max(Instance.ScrollPosY - WheelY, 0.0)
  104. Instance.IsScrollingY = true
  105. IsMouseReleased = true
  106. WheelY = 0.0
  107. end
  108. WheelInstance = nil
  109. ScrollInstance = Instance
  110. end
  111. if ScrollInstance == nil and IsMouseClicked and (Instance.HoverScrollX or Instance.HoverScrollY) then
  112. ScrollInstance = Instance
  113. ScrollInstance.IsScrollingX = Instance.HoverScrollX
  114. ScrollInstance.IsScrollingY = Instance.HoverScrollY
  115. end
  116. end
  117. if ScrollInstance == Instance and IsMouseReleased then
  118. Instance.IsScrollingX = false
  119. Instance.IsScrollingY = false
  120. ScrollInstance = nil
  121. end
  122. if Instance.HasScrollX then
  123. if Instance.HasScrollY then
  124. XSize = XSize - ScrollBarSize - ScrollPad
  125. end
  126. XSize = math.max(XSize, 0.0)
  127. if ScrollInstance == Instance then
  128. MenuState.RequestClose = false
  129. if Instance.IsScrollingX then
  130. Instance.ScrollPosX = math.max(Instance.ScrollPosX + DeltaX, 0.0)
  131. end
  132. end
  133. Instance.ScrollPosX = math.min(Instance.ScrollPosX, XSize)
  134. end
  135. if Instance.HasScrollY then
  136. if Instance.HasScrollX then
  137. YSize = YSize - ScrollBarSize - ScrollPad
  138. end
  139. YSize = math.max(YSize, 0.0)
  140. if ScrollInstance == Instance then
  141. MenuState.RequestClose = false
  142. if Instance.IsScrollingY then
  143. Instance.ScrollPosY = math.max(Instance.ScrollPosY + DeltaY, 0.0)
  144. end
  145. end
  146. Instance.ScrollPosY = math.min(Instance.ScrollPosY, YSize)
  147. end
  148. local XRatio, YRatio = 0.0, 0.0
  149. if XSize ~= 0.0 then
  150. XRatio = math.max(Instance.ScrollPosX / XSize, 0.0)
  151. end
  152. if YSize ~= 0.0 then
  153. YRatio = math.max(Instance.ScrollPosY / YSize, 0.0)
  154. end
  155. local TX = math.max(Instance.ContentW - Instance.W, 0.0) * -XRatio
  156. local TY = math.max(Instance.ContentH - Instance.H, 0.0) * -YRatio
  157. Instance.Transform:setTransformation(math.floor(TX), math.floor(TY))
  158. end
  159. local function DrawScrollBars(Instance)
  160. if not Instance.HasScrollX and not Instance.HasScrollY then
  161. return
  162. end
  163. if HotInstance ~= Instance and ScrollInstance ~= Instance then
  164. local dt = love.timer.getDelta()
  165. Instance.ScrollAlphaX = math.max(Instance.ScrollAlphaX - dt, 0.0)
  166. Instance.ScrollAlphaY = math.max(Instance.ScrollAlphaY - dt, 0.0)
  167. else
  168. Instance.ScrollAlphaX = 1.0
  169. Instance.ScrollAlphaY = 1.0
  170. end
  171. if Instance.HasScrollX then
  172. local XSize = GetXScrollSize(Instance)
  173. local Color = Utility.MakeColor(Style.ScrollBarColor)
  174. if Instance.HoverScrollX or Instance.IsScrollingX then
  175. Color = Utility.MakeColor(Style.ScrollBarHoveredColor)
  176. end
  177. Color[4] = Instance.ScrollAlphaX
  178. local XPos = Instance.ScrollPosX
  179. DrawCommands.Rectangle('fill', Instance.X + XPos, Instance.Y + Instance.H - ScrollPad - ScrollBarSize, XSize, ScrollBarSize, Color, Style.ScrollBarRounding)
  180. end
  181. if Instance.HasScrollY then
  182. local YSize = GetYScrollSize(Instance)
  183. local Color = Utility.MakeColor(Style.ScrollBarColor)
  184. if Instance.HoverScrollY or Instance.IsScrollingY then
  185. Color = Utility.MakeColor(Style.ScrollBarHoveredColor)
  186. end
  187. Color[4] = Instance.ScrollAlphaY
  188. local YPos = Instance.ScrollPosY
  189. DrawCommands.Rectangle('fill', Instance.X + Instance.W - ScrollPad - ScrollBarSize, Instance.Y + YPos, ScrollBarSize, YSize, Color, Style.ScrollBarRounding)
  190. end
  191. end
  192. local function GetInstance(Id)
  193. if Id == nil then
  194. return ActiveInstance
  195. end
  196. if Instances[Id] == nil then
  197. local Instance = {}
  198. Instance.Id = Id
  199. Instance.X = 0.0
  200. Instance.Y = 0.0
  201. Instance.W = 0.0
  202. Instance.H = 0.0
  203. Instance.SX = 0.0
  204. Instance.SY = 0.0
  205. Instance.ContentW = 0.0
  206. Instance.ContentH = 0.0
  207. Instance.HasScrollX = false
  208. Instance.HasScrollY = false
  209. Instance.HoverScrollX = false
  210. Instance.HoverScrollY = false
  211. Instance.IsScrollingX = false
  212. Instance.IsScrollingY = false
  213. Instance.ScrollPosX = 0.0
  214. Instance.ScrollPosY = 0.0
  215. Instance.ScrollAlphaX = 0.0
  216. Instance.ScrollAlphaY = 0.0
  217. Instance.Intersect = false
  218. Instance.AutoSizeContent = false
  219. Instance.Transform = love.math.newTransform()
  220. Instance.Transform:reset()
  221. Instances[Id] = Instance
  222. end
  223. return Instances[Id]
  224. end
  225. function Region.Begin(Id, Options)
  226. Options = Options == nil and {} or Options
  227. Options.X = Options.X == nil and 0.0 or Options.X
  228. Options.Y = Options.Y == nil and 0.0 or Options.Y
  229. Options.W = Options.W == nil and 0.0 or Options.W
  230. Options.H = Options.H == nil and 0.0 or Options.H
  231. Options.SX = Options.SX == nil and Options.X or Options.SX
  232. Options.SY = Options.SY == nil and Options.Y or Options.SY
  233. Options.ContentW = Options.ContentW == nil and 0.0 or Options.ContentW
  234. Options.ContentH = Options.ContentH == nil and 0.0 or Options.ContentH
  235. Options.AutoSizeContent = Options.AutoSizeContent == nil and false or Options.AutoSizeContent
  236. Options.BgColor = Options.BgColor == nil and Style.WindowBackgroundColor or Options.BgColor
  237. Options.NoOutline = Options.NoOutline == nil and false or Options.NoOutline
  238. Options.NoBackground = Options.NoBackground == nil and false or Options.NoBackground
  239. Options.IsObstructed = Options.IsObstructed == nil and false or Options.IsObstructed
  240. Options.Intersect = Options.Intersect == nil and false or Options.Intersect
  241. Options.IgnoreScroll = Options.IgnoreScroll == nil and false or Options.IgnoreScroll
  242. Options.MouseX = Options.MouseX == nil and 0.0 or Options.MouseX
  243. Options.MouseY = Options.MouseY == nil and 0.0 or Options.MouseY
  244. Options.ResetContent = Options.ResetContent == nil and false or Options.ResetContent
  245. Options.Rounding = Options.Rounding == nil and 0.0 or Options.Rounding
  246. local Instance = GetInstance(Id)
  247. Instance.X = Options.X
  248. Instance.Y = Options.Y
  249. Instance.W = Options.W
  250. Instance.H = Options.H
  251. Instance.SX = Options.SX
  252. Instance.SY = Options.SY
  253. Instance.Intersect = Options.Intersect
  254. Instance.IgnoreScroll = Options.IgnoreScroll
  255. Instance.MouseX = Options.MouseX
  256. Instance.MouseY = Options.MouseY
  257. Instance.AutoSizeContent = Options.AutoSizeContent
  258. if Options.ResetContent then
  259. Instance.ContentW = 0.0
  260. Instance.ContentH = 0.0
  261. end
  262. if not Options.AutoSizeContent then
  263. Instance.ContentW = Options.ContentW
  264. Instance.ContentH = Options.ContentH
  265. end
  266. ActiveInstance = Instance
  267. table.insert(Stack, 1, ActiveInstance)
  268. UpdateScrollBars(Instance, Options.IsObstructed)
  269. if HotInstance == Instance and not Contains(Instance, Instance.MouseX, Instance.MouseY) then
  270. HotInstance = nil
  271. end
  272. if not IsObstructed and Contains(Instance, Instance.MouseX, Instance.MouseY) or (Instance.HoverScrollX or Instance.HoverScrollY) then
  273. if ScrollInstance == nil then
  274. HotInstance = Instance
  275. else
  276. HotInstance = ScrollInstance
  277. end
  278. end
  279. if not Options.NoBackground then
  280. DrawCommands.Rectangle('fill', Instance.X, Instance.Y, Instance.W, Instance.H, Options.BgColor, Options.Rounding)
  281. end
  282. if not Options.NoOutline then
  283. DrawCommands.Rectangle('line', Instance.X, Instance.Y, Instance.W, Instance.H, nil, Options.Rounding)
  284. end
  285. DrawCommands.TransformPush()
  286. DrawCommands.ApplyTransform(Instance.Transform)
  287. Region.ApplyScissor()
  288. end
  289. function Region.End()
  290. DrawCommands.TransformPop()
  291. DrawScrollBars(ActiveInstance)
  292. if HotInstance == ActiveInstance
  293. and WheelInstance == nil
  294. and (WheelX ~= 0.0 or WheelY ~= 0.0)
  295. and not ActiveInstance.IgnoreScroll then
  296. WheelInstance = ActiveInstance
  297. end
  298. if ActiveInstance.Intersect then
  299. DrawCommands.IntersectScissor()
  300. else
  301. DrawCommands.Scissor()
  302. end
  303. ActiveInstance = nil
  304. table.remove(Stack, 1)
  305. if #Stack > 0 then
  306. ActiveInstance = Stack[1]
  307. end
  308. end
  309. function Region.IsHoverScrollBar(Id)
  310. local Instance = GetInstance(Id)
  311. if Instance ~= nil then
  312. return Instance.HoverScrollX or Instance.HoverScrollY
  313. end
  314. return false
  315. end
  316. function Region.Translate(Id, X, Y)
  317. local Instance = GetInstance(Id)
  318. if Instance ~= nil then
  319. Instance.Transform:translate(X, Y)
  320. local TX, TY = Instance.Transform:inverseTransformPoint(0, 0)
  321. if not Instance.IgnoreScroll then
  322. if X ~= 0.0 and Instance.HasScrollX then
  323. local XSize = Instance.W - GetXScrollSize(Instance)
  324. local ContentW = Instance.ContentW - Instance.W
  325. if Instance.HasScrollY then
  326. XSize = XSize - ScrollPad - ScrollBarSize
  327. end
  328. XSize = math.max(XSize, 0.0)
  329. Instance.ScrollPosX = (TX / ContentW) * XSize
  330. Instance.ScrollPosX = math.max(Instance.ScrollPosX, 0.0)
  331. Instance.ScrollPosX = math.min(Instance.ScrollPosX, XSize)
  332. end
  333. if Y ~= 0.0 and Instance.HasScrollY then
  334. local YSize = Instance.H - GetYScrollSize(Instance)
  335. if Instance.HasScrollX then
  336. YSize = YSize - ScrollPad - ScrollBarSize
  337. end
  338. YSize = math.max(YSize, 0.0)
  339. local ContentH = Instance.ContentH - Instance.H
  340. Instance.ScrollPosY = (TY / ContentH) * YSize
  341. Instance.ScrollPosY = math.max(Instance.ScrollPosY, 0.0)
  342. Instance.ScrollPosY = math.min(Instance.ScrollPosY, YSize)
  343. end
  344. end
  345. end
  346. end
  347. function Region.Transform(Id, X, Y)
  348. local Instance = GetInstance(Id)
  349. if Instance ~= nil then
  350. return Instance.Transform:transformPoint(X, Y)
  351. end
  352. return X, Y
  353. end
  354. function Region.InverseTransform(Id, X, Y)
  355. local Instance = GetInstance(Id)
  356. if Instance ~= nil then
  357. return Instance.Transform:inverseTransformPoint(X, Y)
  358. end
  359. return X, Y
  360. end
  361. function Region.ResetTransform(Id)
  362. local Instance = GetInstance(Id)
  363. if Instance ~= nil then
  364. Instance.Transform:reset()
  365. Instance.ScrollPosX = 0.0
  366. Instance.ScrollPosY = 0.0
  367. end
  368. end
  369. function Region.IsActive(Id)
  370. if ActiveInstance ~= nil then
  371. return ActiveInstance.Id == Id
  372. end
  373. return false
  374. end
  375. function Region.AddItem(X, Y, W, H)
  376. if ActiveInstance ~= nil and ActiveInstance.AutoSizeContent then
  377. local NewW = X + W - ActiveInstance.X
  378. local NewH = Y + H - ActiveInstance.Y
  379. ActiveInstance.ContentW = math.max(ActiveInstance.ContentW, NewW)
  380. ActiveInstance.ContentH = math.max(ActiveInstance.ContentH, NewH)
  381. end
  382. end
  383. function Region.ApplyScissor()
  384. if ActiveInstance ~= nil then
  385. if ActiveInstance.Intersect then
  386. DrawCommands.IntersectScissor(ActiveInstance.SX, ActiveInstance.SY, ActiveInstance.W, ActiveInstance.H)
  387. else
  388. DrawCommands.Scissor(ActiveInstance.SX, ActiveInstance.SY, ActiveInstance.W, ActiveInstance.H)
  389. end
  390. end
  391. end
  392. function Region.GetBounds()
  393. if ActiveInstance ~= nil then
  394. return ActiveInstance.X, ActiveInstance.Y, ActiveInstance.W, ActiveInstance.H
  395. end
  396. return 0.0, 0.0, 0.0, 0.0
  397. end
  398. function Region.GetContentSize()
  399. if ActiveInstance ~= nil then
  400. return ActiveInstance.ContentW, ActiveInstance.ContentH
  401. end
  402. return 0.0, 0.0
  403. end
  404. function Region.Contains(X, Y)
  405. if ActiveInstance ~= nil then
  406. return ActiveInstance.X <= X and X <= ActiveInstance.X + ActiveInstance.W and ActiveInstance.Y <= Y and Y <= ActiveInstance.Y + ActiveInstance.H
  407. end
  408. return false
  409. end
  410. function Region.ResetContentSize(Id)
  411. local Instance = GetInstance(Id)
  412. if Instance ~= nil then
  413. Instance.ContentW = 0.0
  414. Instance.ContentH = 0.0
  415. end
  416. end
  417. function Region.GetScrollPad()
  418. return ScrollPad
  419. end
  420. function Region.GetScrollBarSize()
  421. return ScrollBarSize
  422. end
  423. function Region.WheelMoved(X, Y)
  424. WheelX = X * WheelSpeed
  425. WheelY = Y * WheelSpeed
  426. end
  427. function Region.GetWheelDelta()
  428. return WheelX, WheelY
  429. end
  430. function Region.IsScrolling(Id)
  431. if Id ~= nil then
  432. local Instance = GetInstance(Id)
  433. return ScrollInstance == Instance or WheelInstance == Instance
  434. end
  435. return ScrollInstance ~= nil or WheelInstance ~= nil
  436. end
  437. function Region.GetHotInstanceId()
  438. if HotInstance ~= nil then
  439. return HotInstance.Id
  440. end
  441. return ''
  442. end
  443. function Region.GetInstanceIds()
  444. local Result = {}
  445. for K, V in pairs(Instances) do
  446. table.insert(Result, K)
  447. end
  448. return Result
  449. end
  450. function Region.GetDebugInfo(Id)
  451. local Result = {}
  452. local Instance = nil
  453. for K, V in pairs(Instances) do
  454. if K == Id then
  455. Instance = V
  456. break
  457. end
  458. end
  459. table.insert(Result, "ScrollInstance: " .. (ScrollInstance ~= nil and ScrollInstance.Id or "nil"))
  460. table.insert(Result, "WheelInstance: " .. (WheelInstance ~= nil and WheelInstance.Id or "nil"))
  461. table.insert(Result, "WheelX: " .. WheelX)
  462. table.insert(Result, "WheelY: " .. WheelY)
  463. if Instance ~= nil then
  464. table.insert(Result, "Id: " .. Instance.Id)
  465. table.insert(Result, "W: " .. Instance.W)
  466. table.insert(Result, "H: " .. Instance.H)
  467. table.insert(Result, "ContentW: " .. Instance.ContentW)
  468. table.insert(Result, "ContentH: " .. Instance.ContentH)
  469. table.insert(Result, "ScrollPosX: " .. Instance.ScrollPosX)
  470. table.insert(Result, "ScrollPosY: " .. Instance.ScrollPosY)
  471. local TX, TY = Instance.Transform:transformPoint(0, 0)
  472. table.insert(Result, "TX: " .. TX)
  473. table.insert(Result, "TY: " .. TY)
  474. table.insert(Result, "Max TX: " .. Instance.ContentW - Instance.W)
  475. table.insert(Result, "Max TY: " .. Instance.ContentH - Instance.H)
  476. end
  477. return Result
  478. end
  479. return Region