2
0

ui2d.lua 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145
  1. local utf8 = require "utf8"
  2. local UI2D = {}
  3. local framework = {}
  4. local has_text_input = false
  5. local has_mouse = false
  6. local e_mouse_state = { clicked = 1, held = 2, released = 3, idle = 4 }
  7. local e_slider_type = { int = 1, float = 2 }
  8. local modal_window = nil
  9. local active_window = nil
  10. local active_widget = nil
  11. local active_textbox = nil
  12. local dragged_window = nil
  13. local repeating_key = nil
  14. local text_input_character = nil
  15. local begin_idx = nil
  16. local margin = 8
  17. local next_z = 0
  18. local separator_thickness = 2
  19. local begin_end_pairs = { b = 0, e = 0 }
  20. local windows = {}
  21. local color_themes = {}
  22. local overriden_colors = {}
  23. local listbox_state = {}
  24. local caret_blink = { prev = 0, on = false }
  25. local font = { handle = nil, w = nil, h = nil }
  26. local dragged_window_offset = { x = 0, y = 0 }
  27. local mouse = { x = 0, y = 0, state = e_mouse_state.idle, prev_frame = 0, this_frame = 0, wheel_x = 0, wheel_y = 0 }
  28. local layout = { x = 0, y = 0, w = 0, h = 0, row_h = 0, total_w = 0, total_h = 0, same_line = false, same_column = false }
  29. local texture_flags = { mipmaps = true, usage = { 'sample', 'render', 'transfer' } }
  30. local clamp_sampler
  31. local active_tooltip = { text = "", x = 0, y = 0 }
  32. local keys = {
  33. [ "right" ] = { 0, 0, 0 },
  34. [ "left" ] = { 0, 0, 0 },
  35. [ "backspace" ] = { 0, 0, 0 },
  36. [ "delete" ] = { 0, 0, 0 },
  37. [ "tab" ] = { 0, 0, 0 },
  38. [ "return" ] = { 0, 0, 0 },
  39. [ "kpenter" ] = { 0, 0, 0 }
  40. }
  41. color_themes.dark =
  42. {
  43. text = { 0.8, 0.8, 0.8 },
  44. tooltip_bg = { 0, 0, 0 },
  45. tooltip_border = { 0.3, 0.3, 0.3 },
  46. window_bg = { 0.26, 0.26, 0.26 },
  47. window_border = { 0, 0, 0 },
  48. window_titlebar = { 0.08, 0.08, 0.08 },
  49. window_titlebar_active = { 0, 0, 0 },
  50. button_bg = { 0.14, 0.14, 0.14 },
  51. button_bg_hover = { 0.19, 0.19, 0.19 },
  52. button_bg_click = { 0.12, 0.12, 0.12 },
  53. button_border = { 0, 0, 0 },
  54. check_border = { 0, 0, 0 },
  55. check_border_hover = { 0.5, 0.5, 0.5 },
  56. check_mark = { 0.3, 0.3, 1 },
  57. toggle_border = { 0, 0, 0 },
  58. toggle_border_hover = { 0.5, 0.5, 0.5 },
  59. toggle_handle = { 0.8, 0.8, 0.8 },
  60. toggle_bg_off = { 0.3, 0.3, 0.3 },
  61. toggle_bg_on = { 0.3, 0.3, 1 },
  62. radio_border = { 0, 0, 0 },
  63. radio_border_hover = { 0.5, 0.5, 0.5 },
  64. radio_mark = { 0.3, 0.3, 1 },
  65. slider_bg = { 0.3, 0.3, 1 },
  66. slider_bg_hover = { 0.38, 0.38, 1 },
  67. slider_thumb = { 0.2, 0.2, 1 },
  68. list_bg = { 0.14, 0.14, 0.14 },
  69. list_border = { 0, 0, 0 },
  70. list_selected = { 0.3, 0.3, 1 },
  71. list_highlight = { 0.3, 0.3, 0.3 },
  72. list_track = { 0.08, 0.08, 0.08 },
  73. list_thumb = { 0.36, 0.36, 0.36 },
  74. list_thumb_hover = { 0.42, 0.42, 0.42 },
  75. list_thumb_click = { 0.24, 0.24, 0.24 },
  76. list_button = { 0.8, 0.8, 0.8 },
  77. list_button_hover = { 1, 1, 1 },
  78. list_button_click = { 0.5, 0.5, 0.5 },
  79. textbox_bg = { 0.03, 0.03, 0.03 },
  80. textbox_bg_hover = { 0.11, 0.11, 0.11 },
  81. textbox_border = { 0.1, 0.1, 0.1 },
  82. textbox_border_focused = { 0.58, 0.58, 1 },
  83. image_button_border_highlight = { 0.5, 0.5, 0.5 },
  84. tab_bar_bg = { 0.1, 0.1, 0.1 },
  85. tab_bar_border = { 0, 0, 0 },
  86. tab_bar_hover = { 0.2, 0.2, 0.2 },
  87. tab_bar_highlight = { 0.3, 0.3, 1 },
  88. progress_bar_bg = { 0.2, 0.2, 0.2 },
  89. progress_bar_fill = { 0.3, 0.3, 1 },
  90. progress_bar_border = { 0, 0, 0 },
  91. modal_tint = { 0.3, 0.3, 0.3 },
  92. separator = { 0, 0, 0 }
  93. }
  94. color_themes.light =
  95. {
  96. text = { 0.02, 0.02, 0.02 },
  97. tooltip_bg = { 1, 1, 1 },
  98. tooltip_border = { 0, 0, 0 },
  99. window_bg = { 0.930, 0.930, 0.930 },
  100. window_border = { 0.000, 0.000, 0.000 },
  101. window_titlebar = { 0.8, 0.8, 0.8 },
  102. window_titlebar_active = { 0.54, 0.54, 0.54 },
  103. button_bg = { 0.800, 0.800, 0.800 },
  104. button_bg_hover = { 0.900, 0.900, 0.900 },
  105. button_bg_click = { 0.120, 0.120, 0.120 },
  106. button_border = { 0.000, 0.000, 0.000 },
  107. check_border = { 0.000, 0.000, 0.000 },
  108. check_border_hover = { 0.760, 0.760, 0.760 },
  109. check_mark = { 0.000, 0.000, 0.000 },
  110. toggle_border = { 0, 0, 0 },
  111. toggle_border_hover = { 1, 1, 1 },
  112. toggle_handle = { 1, 1, 1 },
  113. toggle_bg_off = { 0.4, 0.4, 0.4 },
  114. toggle_bg_on = { 0.830, 0.830, 0.830 },
  115. radio_border = { 0.000, 0.000, 0.000 },
  116. radio_border_hover = { 0.760, 0.760, 0.760 },
  117. radio_mark = { 0.172, 0.172, 0.172 },
  118. slider_bg = { 0.830, 0.830, 0.830 },
  119. slider_bg_hover = { 0.870, 0.870, 0.870 },
  120. slider_thumb = { 0.700, 0.700, 0.700 },
  121. list_bg = { 0.9, 0.9, 0.9 },
  122. list_border = { 0.000, 0.000, 0.000 },
  123. list_selected = { 0.686, 0.687, 0.688 },
  124. list_highlight = { 0.808, 0.810, 0.811 },
  125. list_track = { 0.82, 0.82, 0.82 },
  126. list_thumb = { 0.65, 0.65, 0.65 },
  127. list_thumb_hover = { 0.72, 0.72, 0.72 },
  128. list_thumb_click = { 0.58, 0.58, 0.58 },
  129. list_button = { 0, 0, 0 },
  130. list_button_hover = { 0.3, 0.3, 0.3 },
  131. list_button_click = { 0.1, 0.1, 0.1 },
  132. textbox_bg = { 0.700, 0.700, 0.700 },
  133. textbox_bg_hover = { 0.570, 0.570, 0.570 },
  134. textbox_border = { 0.000, 0.000, 0.000 },
  135. textbox_border_focused = { 0.000, 0.000, 1.000 },
  136. image_button_border_highlight = { 0.500, 0.500, 0.500 },
  137. tab_bar_bg = { 1.000, 0.994, 0.999 },
  138. tab_bar_border = { 0.000, 0.000, 0.000 },
  139. tab_bar_hover = { 0.802, 0.797, 0.795 },
  140. tab_bar_highlight = { 0.151, 0.140, 1.000 },
  141. progress_bar_bg = { 1.000, 1.000, 1.000 },
  142. progress_bar_fill = { 0.830, 0.830, 1.000 },
  143. progress_bar_border = { 0.000, 0.000, 0.000 },
  144. modal_tint = { 0.15, 0.15, 0.15 },
  145. separator = { 0.5, 0.5, 0.5 }
  146. }
  147. local colors = color_themes.dark
  148. -- -------------------------------------------------------------------------- --
  149. -- Framework --
  150. -- -------------------------------------------------------------------------- --
  151. -- LOVR implementation
  152. function framework.GetKeyDown_LOVR( key )
  153. return lovr.system.isKeyDown( key )
  154. end
  155. function framework.NewSampler_LOVR()
  156. return lovr.graphics.newSampler( { wrap = 'clamp' } )
  157. end
  158. function framework.LoadFont_LOVR( lib_path, size )
  159. return lovr.graphics.newFont( lib_path .. "DejaVuSansMono.ttf", size or 14, 4 )
  160. end
  161. function framework.SetPixelDensity_LOVR( handle )
  162. handle:setPixelDensity( 1.0 )
  163. end
  164. function framework.SetKeyRepeat_LOVR()
  165. lovr.system.setKeyRepeat( true )
  166. end
  167. function framework.IsMouseDown_LOVR( btn )
  168. return lovr.system.isMouseDown( btn )
  169. end
  170. function framework.GetMousePosition_LOVR()
  171. return lovr.system.getMousePosition()
  172. end
  173. function framework.GetWindowDimensions_LOVR()
  174. return lovr.system.getWindowDimensions()
  175. end
  176. function framework.GetTime_LOVR()
  177. return lovr.timer.getTime()
  178. end
  179. function framework.NewTexture_LOVR( w, h )
  180. return lovr.graphics.newTexture( w, h, texture_flags )
  181. end
  182. function framework.SetCanvas_LOVR( pass, tex )
  183. if not pass then return end
  184. pass:setCanvas( tex )
  185. end
  186. function framework.NewPass_LOVR( tex )
  187. return lovr.graphics.newPass( tex )
  188. end
  189. function framework.SetFont_LOVR( pass )
  190. pass:setFont( font.handle )
  191. end
  192. function framework.ResetPass_LOVR( pass )
  193. pass:reset()
  194. end
  195. function framework.ClearWindow_LOVR( win )
  196. win.pass:setDepthTest( nil )
  197. win.pass:setProjection( 1, mat4():orthographic( win.pass:getDimensions() ) )
  198. win.pass:setColor( colors.window_bg )
  199. win.pass:fill()
  200. end
  201. function framework.SetColor_LOVR( pass, color )
  202. pass:setColor( color )
  203. end
  204. function framework.DrawRect_LOVR( pass, x, y, w, h, type )
  205. pass:plane( x, y, 0, w, h, 0, 0, 0, 0, type )
  206. end
  207. function framework.DrawCircle_LOVR( pass, x, y, radius, type )
  208. pass:circle( x, y, 0, radius, 0, 0, 0, 0, type )
  209. end
  210. function framework.DrawCircleHalf_LOVR( pass, x, y, radius, type, angle1, angle2 )
  211. pass:circle( x, y, 0, radius, 0, 0, 0, 0, type, angle1, angle2 )
  212. end
  213. function framework.DrawLine_LOVR( pass, x1, y1, x2, y2 )
  214. pass:line( x1, y1, 0, x2, y2, 0 )
  215. end
  216. function framework.DrawText_LOVR( pass, text, x, y, w, h, text_w )
  217. pass:text( text, x + (w / 2), y + (h / 2), 0 )
  218. end
  219. function framework.DrawImage_LOVR( pass, tex, x, y, w, h, sampler )
  220. pass:setMaterial( tex )
  221. pass:setSampler( sampler )
  222. pass:plane( x, y, 0, w, -h )
  223. pass:setMaterial()
  224. pass:setColor( 1, 1, 1 )
  225. end
  226. function framework.SetProjection_LOVR( pass )
  227. pass:setProjection( 1, mat4():orthographic( pass:getDimensions() ) )
  228. end
  229. function framework.ReleaseTexture_LOVR( tex )
  230. -- noop
  231. end
  232. function framework.SetMaterial_LOVR( pass, tex )
  233. pass:setMaterial( tex )
  234. end
  235. -- LOVE implementation
  236. function framework.GetKeyDown_LOVE( key )
  237. return love.keyboard.isDown( key )
  238. end
  239. function framework.NewSampler_LOVE()
  240. -- noop
  241. end
  242. function framework.LoadFont_LOVE( lib_path, size )
  243. return love.graphics.newFont( lib_path .. "DejaVuSansMono.ttf", size or 14 )
  244. end
  245. function framework.SetPixelDensity_LOVE( handle )
  246. -- noop
  247. end
  248. function framework.SetKeyRepeat_LOVE()
  249. love.keyboard.setKeyRepeat( true )
  250. end
  251. function framework.IsMouseDown_LOVE( btn )
  252. return love.mouse.isDown( btn )
  253. end
  254. function framework.GetMousePosition_LOVE()
  255. return love.mouse.getPosition()
  256. end
  257. function framework.GetWindowDimensions_LOVE()
  258. return love.window.getMode()
  259. end
  260. function framework.GetTime_LOVE()
  261. return love.timer.getTime()
  262. end
  263. function framework.NewTexture_LOVE( w, h )
  264. return love.graphics.newCanvas( w, h )
  265. end
  266. function framework.SetCanvas_LOVE( pass, tex )
  267. if not tex then
  268. love.graphics.setCanvas()
  269. end
  270. love.graphics.setCanvas( tex )
  271. end
  272. function framework.NewPass_LOVE( tex )
  273. -- noop
  274. end
  275. function framework.SetFont_LOVE( pass )
  276. love.graphics.setFont( font.handle )
  277. end
  278. function framework.ResetPass_LOVE( pass )
  279. -- noop
  280. end
  281. function framework.ClearWindow_LOVE( win )
  282. love.graphics.clear( colors.window_bg )
  283. end
  284. function framework.SetColor_LOVE( pass, color )
  285. love.graphics.setColor( color )
  286. end
  287. function framework.DrawRect_LOVE( pass, x, y, w, h, type )
  288. love.graphics.rectangle( type, x - (w / 2), y - (h / 2), w, h )
  289. end
  290. function framework.DrawCircle_LOVE( pass, x, y, radius, type )
  291. love.graphics.circle( type, x, y, radius )
  292. end
  293. function framework.DrawCircleHalf_LOVE( pass, x, y, radius, type, angle1, angle2 )
  294. love.graphics.arc( type, "open", x, y, radius, angle1, angle2 )
  295. end
  296. function framework.DrawLine_LOVE( pass, x1, y1, x2, y2 )
  297. love.graphics.line( x1, y1, x2, y2 )
  298. end
  299. function framework.DrawText_LOVE( pass, text, x, y, w, h, text_w )
  300. local posx = (x + (w - text_w) / 2)
  301. local posy = (y + (h - font.h) / 2)
  302. love.graphics.print( text, posx, posy )
  303. end
  304. function framework.DrawImage_LOVE( pass, tex, x, y, w, h, sampler, image_w, image_h )
  305. love.graphics.draw( tex, x - (w / 2), y - (h / 2), 0, w / image_w, h / image_h )
  306. end
  307. function framework.SetProjection_LOVE( pass )
  308. -- noop
  309. end
  310. function framework.ReleaseTexture_LOVE( tex )
  311. tex:release()
  312. end
  313. function framework.SetMaterial_LOVE( pass, tex )
  314. -- noop
  315. end
  316. -- -------------------------------------------------------------------------- --
  317. -- Internals --
  318. -- -------------------------------------------------------------------------- --
  319. local function Clamp( n, n_min, n_max )
  320. if n < n_min then
  321. n = n_min
  322. elseif n > n_max then
  323. n = n_max
  324. end
  325. return n
  326. end
  327. local function GetLineCount( str )
  328. -- https://stackoverflow.com/questions/24690910/how-to-get-lines-count-in-string/70137660#70137660
  329. local lines = 1
  330. for i = 1, #str do
  331. local c = str:sub( i, i )
  332. if c == '\n' then lines = lines + 1 end
  333. end
  334. return lines
  335. end
  336. local function WindowExists( id )
  337. for i, v in ipairs( windows ) do
  338. if v.id == id then
  339. return true, i
  340. end
  341. end
  342. return false, 0
  343. end
  344. local function WidgetExists( win, id )
  345. for i, v in ipairs( win.cw ) do
  346. if v.id == id then
  347. return true, i
  348. end
  349. end
  350. return false, 0
  351. end
  352. local function ListBoxExists( id )
  353. for i, v in ipairs( listbox_state ) do
  354. if v.id == id then
  355. return true, i
  356. end
  357. end
  358. return false, 0
  359. end
  360. local function PointInRect( px, py, rx, ry, rw, rh )
  361. if px >= rx and px <= rx + rw and py >= ry and py <= ry + rh then
  362. return true
  363. end
  364. return false
  365. end
  366. local function MapRange( from_min, from_max, to_min, to_max, v )
  367. return (v - from_min) * (to_max - to_min) / (from_max - from_min) + to_min
  368. end
  369. local function GetLabelPart( name )
  370. local i = string.find( name, "##" )
  371. if i then
  372. return string.sub( name, 1, i - 1 )
  373. end
  374. return name
  375. end
  376. local function GetLongerStringLen( t )
  377. local len = 0
  378. local idx = 0
  379. for i, v in ipairs( t ) do
  380. local cur = utf8.len( v )
  381. if cur > len then
  382. len = cur
  383. idx = i
  384. end
  385. end
  386. return len
  387. end
  388. local function ResetLayout()
  389. layout = { x = 0, y = 0, w = 0, h = 0, row_h = 0, total_w = 0, total_h = 0, same_line = false, same_column = false }
  390. end
  391. local function UpdateLayout( bbox )
  392. -- Update row height
  393. if layout.same_line then
  394. if bbox.h > layout.row_h then
  395. layout.row_h = bbox.h
  396. end
  397. elseif layout.same_column then
  398. if bbox.h + layout.h + margin < layout.row_h then
  399. layout.row_h = layout.row_h - layout.h - margin
  400. else
  401. layout.row_h = bbox.h
  402. end
  403. else
  404. layout.row_h = bbox.h
  405. end
  406. -- Calculate current layout w/h
  407. if bbox.x + bbox.w + margin > layout.total_w then
  408. layout.total_w = bbox.x + bbox.w + margin
  409. end
  410. if bbox.y + layout.row_h + margin > layout.total_h then
  411. layout.total_h = bbox.y + layout.row_h + margin
  412. end
  413. -- Update layout x/y/w/h and same_line
  414. layout.x = bbox.x
  415. layout.y = bbox.y
  416. layout.w = bbox.w
  417. layout.h = bbox.h
  418. layout.same_line = false
  419. layout.same_column = false
  420. end
  421. local function Slider( type, name, v, v_min, v_max, width, num_decimals, tooltip )
  422. local text = GetLabelPart( name )
  423. local cur_window = windows[ begin_idx ]
  424. local text_w = font.handle:getWidth( text )
  425. local slider_w = 10 * font.w
  426. local bbox = {}
  427. if layout.same_line then
  428. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = slider_w + margin + text_w, h = (2 * margin) + font.h }
  429. elseif layout.same_column then
  430. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = slider_w + margin + text_w, h = (2 * margin) + font.h }
  431. else
  432. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = slider_w + margin + text_w, h = (2 * margin) + font.h }
  433. end
  434. if width and width > bbox.w then
  435. bbox.w = width
  436. slider_w = width - margin - text_w
  437. end
  438. UpdateLayout( bbox )
  439. local col = colors.slider_bg
  440. local result = false
  441. if not modal_window or (modal_window and modal_window == cur_window) then
  442. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, slider_w, bbox.h ) and cur_window == active_window then
  443. if tooltip then
  444. active_tooltip.text = tooltip
  445. active_tooltip.x = mouse.x
  446. active_tooltip.y = mouse.y
  447. end
  448. col = colors.slider_bg_hover
  449. if mouse.state == e_mouse_state.clicked then
  450. active_widget = cur_window.id .. name
  451. end
  452. end
  453. end
  454. if mouse.state == e_mouse_state.held and active_widget == cur_window.id .. name and cur_window == active_window then
  455. v = MapRange( bbox.x + 2, bbox.x + slider_w - 2, v_min, v_max, mouse.x - cur_window.x )
  456. if type == e_slider_type.float then
  457. v = Clamp( v, v_min, v_max )
  458. else
  459. v = Clamp( math.ceil( v ), v_min, v_max )
  460. if v == 0 then v = 0 end
  461. end
  462. end
  463. if mouse.state == e_mouse_state.released and active_widget == cur_window.id .. name then
  464. active_widget = nil
  465. result = true
  466. end
  467. local value_text_w = font.handle:getWidth( v )
  468. local text_label_rect = { x = bbox.x + slider_w + margin, y = bbox.y, w = text_w, h = bbox.h }
  469. local text_value_rect = { x = bbox.x, y = bbox.y, w = slider_w, h = bbox.h }
  470. local slider_rect = { x = bbox.x, y = bbox.y + (bbox.h / 2) - (font.h / 2), w = slider_w, h = font.h }
  471. local thumb_pos = MapRange( v_min, v_max, bbox.x, bbox.x + slider_w - font.h, v )
  472. local thumb_rect = { x = thumb_pos, y = bbox.y + (bbox.h / 2) - (font.h / 2), w = font.h, h = font.h }
  473. local value
  474. if type == e_slider_type.float then
  475. num_decimals = num_decimals or 2
  476. local str_fmt = "%." .. num_decimals .. "f"
  477. value = string.format( str_fmt, v )
  478. else
  479. value = v
  480. end
  481. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = slider_rect, color = col } )
  482. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = thumb_rect, color = colors.slider_thumb } )
  483. table.insert( windows[ begin_idx ].command_list, { type = "text", text = text, bbox = text_label_rect, color = colors.text } )
  484. table.insert( windows[ begin_idx ].command_list, { type = "text", text = value, bbox = text_value_rect, color = colors.text } )
  485. return v, result
  486. end
  487. function utf8.sub( s, i, j )
  488. i = utf8.offset( s, i ) or 1
  489. local nextOffset = utf8.offset( s, j + 1 )
  490. j = (nextOffset and nextOffset - 1) or #tostring( s )
  491. return string.sub( s, i, j )
  492. end
  493. -- -------------------------------------------------------------------------- --
  494. -- User --
  495. -- -------------------------------------------------------------------------- --
  496. function UI2D.KeyPressed( key, repeating )
  497. if repeating then
  498. if key == "right" then
  499. repeating_key = "right"
  500. elseif key == "left" then
  501. repeating_key = "left"
  502. elseif key == "backspace" then
  503. repeating_key = "backspace"
  504. elseif key == "delete" then
  505. repeating_key = "delete"
  506. end
  507. end
  508. end
  509. function UI2D.TextInput( text )
  510. text_input_character = text
  511. end
  512. function UI2D.KeyReleased()
  513. repeating_key = nil
  514. end
  515. function UI2D.WheelMoved( x, y )
  516. mouse.wheel_x = x
  517. mouse.wheel_y = y
  518. end
  519. function UI2D.Init( type, size )
  520. framework.type = type
  521. if type == "lovr" then
  522. framework.GetKeyDown = framework.GetKeyDown_LOVR
  523. framework.NewSampler = framework.NewSampler_LOVR
  524. framework.LoadFont = framework.LoadFont_LOVR
  525. framework.SetPixelDensity = framework.SetPixelDensity_LOVR
  526. framework.SetKeyRepeat = framework.SetKeyRepeat_LOVR
  527. framework.IsMouseDown = framework.IsMouseDown_LOVR
  528. framework.GetMousePosition = framework.GetMousePosition_LOVR
  529. framework.GetWindowDimensions = framework.GetWindowDimensions_LOVR
  530. framework.GetTime = framework.GetTime_LOVR
  531. framework.NewTexture = framework.NewTexture_LOVR
  532. framework.SetCanvas = framework.SetCanvas_LOVR
  533. framework.NewPass = framework.NewPass_LOVR
  534. framework.SetFont = framework.SetFont_LOVR
  535. framework.ResetPass = framework.ResetPass_LOVR
  536. framework.ClearWindow = framework.ClearWindow_LOVR
  537. framework.SetColor = framework.SetColor_LOVR
  538. framework.DrawRect = framework.DrawRect_LOVR
  539. framework.DrawCircle = framework.DrawCircle_LOVR
  540. framework.DrawText = framework.DrawText_LOVR
  541. framework.DrawImage = framework.DrawImage_LOVR
  542. framework.SetProjection = framework.SetProjection_LOVR
  543. framework.ReleaseTexture = framework.ReleaseTexture_LOVR
  544. framework.SetMaterial = framework.SetMaterial_LOVR
  545. framework.DrawCircleHalf = framework.DrawCircleHalf_LOVR
  546. framework.DrawLine = framework.DrawLine_LOVR
  547. else
  548. framework.GetKeyDown = framework.GetKeyDown_LOVE
  549. framework.NewSampler = framework.NewSampler_LOVE
  550. framework.LoadFont = framework.LoadFont_LOVE
  551. framework.SetPixelDensity = framework.SetPixelDensity_LOVE
  552. framework.SetKeyRepeat = framework.SetKeyRepeat_LOVE
  553. framework.IsMouseDown = framework.IsMouseDown_LOVE
  554. framework.GetMousePosition = framework.GetMousePosition_LOVE
  555. framework.GetWindowDimensions = framework.GetWindowDimensions_LOVE
  556. framework.GetTime = framework.GetTime_LOVE
  557. framework.NewTexture = framework.NewTexture_LOVE
  558. framework.SetCanvas = framework.SetCanvas_LOVE
  559. framework.NewPass = framework.NewPass_LOVE
  560. framework.SetFont = framework.SetFont_LOVE
  561. framework.ResetPass = framework.ResetPass_LOVE
  562. framework.ClearWindow = framework.ClearWindow_LOVE
  563. framework.SetColor = framework.SetColor_LOVE
  564. framework.DrawRect = framework.DrawRect_LOVE
  565. framework.DrawCircle = framework.DrawCircle_LOVE
  566. framework.DrawText = framework.DrawText_LOVE
  567. framework.DrawImage = framework.DrawImage_LOVE
  568. framework.SetProjection = framework.SetProjection_LOVE
  569. framework.ReleaseTexture = framework.ReleaseTexture_LOVE
  570. framework.SetMaterial = framework.SetMaterial_LOVE
  571. framework.DrawCircleHalf = framework.DrawCircleHalf_LOVE
  572. framework.DrawLine = framework.DrawLine_LOVE
  573. end
  574. local info = debug.getinfo( 1, "S" )
  575. local lib_path = info.source:match( "@(.*[\\/])" )
  576. font.handle = framework.LoadFont( lib_path, size )
  577. framework.SetPixelDensity( font.handle )
  578. font.h = font.handle:getHeight()
  579. font.w = font.handle:getWidth( "W" )
  580. font.size = size or 14
  581. framework.SetKeyRepeat()
  582. margin = math.floor( font.h / 2 )
  583. separator_thickness = math.floor( font.h / 7 )
  584. end
  585. function UI2D.InputInfo()
  586. for i, v in pairs( keys ) do
  587. if framework.GetKeyDown( i ) then
  588. if v[ 1 ] == 0 then
  589. v[ 1 ] = 1
  590. v[ 2 ] = 1
  591. v[ 3 ] = 1 -- pressed
  592. else
  593. v[ 1 ] = 1
  594. v[ 2 ] = 0
  595. v[ 3 ] = 2 -- held
  596. end
  597. else
  598. if v[ 1 ] == 1 then
  599. v[ 1 ] = 0
  600. v[ 3 ] = 3 -- released
  601. else
  602. v[ 1 ] = 0
  603. v[ 1 ] = 0
  604. v[ 3 ] = 0 -- idle
  605. end
  606. end
  607. end
  608. if framework.IsMouseDown( 1 ) then
  609. if mouse.prev_frame == 0 then
  610. mouse.prev_frame = 1
  611. mouse.this_frame = 1
  612. mouse.state = e_mouse_state.clicked
  613. else
  614. mouse.prev_frame = 1
  615. mouse.this_frame = 0
  616. mouse.state = e_mouse_state.held
  617. end
  618. else
  619. if mouse.prev_frame == 1 then
  620. mouse.state = e_mouse_state.released
  621. mouse.prev_frame = 0
  622. else
  623. mouse.state = e_mouse_state.idle
  624. end
  625. end
  626. mouse.x, mouse.y = framework.GetMousePosition()
  627. -- Set active window on click
  628. local hovers_active = false
  629. local hovers_any = false
  630. for i, v in ipairs( windows ) do
  631. if PointInRect( mouse.x, mouse.y, v.x, v.y, v.w, v.h ) then
  632. if v == active_window then
  633. hovers_active = true
  634. end
  635. hovers_any = true
  636. has_mouse = true
  637. end
  638. end
  639. if modal_window then
  640. active_window = modal_window
  641. hovers_active = false
  642. end
  643. local z = 0
  644. local win = nil
  645. if not hovers_active then
  646. for i, v in ipairs( windows ) do
  647. if PointInRect( mouse.x, mouse.y, v.x, v.y, v.w, v.h ) and mouse.state == e_mouse_state.clicked then
  648. if v.z > z then
  649. win = v
  650. z = v.z
  651. end
  652. end
  653. end
  654. if win and not modal_window then
  655. next_z = next_z + 0.01
  656. win.z = next_z
  657. active_window = win
  658. end
  659. end
  660. -- Set active to none
  661. if not hovers_any and mouse.state == e_mouse_state.clicked then
  662. active_window = nil
  663. has_text_input = false
  664. end
  665. -- Give back mouse
  666. if not hovers_any then
  667. has_mouse = false
  668. end
  669. -- Handle window dragging
  670. if active_window then
  671. local v = active_window
  672. if PointInRect( mouse.x, mouse.y, v.x, v.y, v.w, (2 * margin) + font.h ) and mouse.state == e_mouse_state.clicked then
  673. dragged_window = active_window
  674. dragged_window_offset.x = mouse.x - active_window.x
  675. dragged_window_offset.y = mouse.y - active_window.y
  676. end
  677. if dragged_window then
  678. if mouse.state == e_mouse_state.held then
  679. local mx = mouse.x
  680. local my = mouse.y
  681. local w, h = framework.GetWindowDimensions()
  682. mx = Clamp( mx, 10, w - 10 )
  683. my = Clamp( my, 10, h - 10 )
  684. dragged_window.x = mx - dragged_window_offset.x
  685. dragged_window.y = my - dragged_window_offset.y
  686. end
  687. end
  688. end
  689. if mouse.state == e_mouse_state.released then
  690. dragged_window = nil
  691. end
  692. local now = framework.GetTime()
  693. if now > caret_blink.prev + 0.4 then
  694. caret_blink.on = true
  695. end
  696. if now > caret_blink.prev + 0.8 then
  697. caret_blink.on = false
  698. caret_blink.prev = now
  699. end
  700. end
  701. function UI2D.Begin( name, x, y, is_modal )
  702. local exists, idx = WindowExists( name ) -- TODO: Can't currently change window title on runtime
  703. if not exists then
  704. next_z = next_z + 0.01
  705. local window = {
  706. id = name,
  707. title = GetLabelPart( name ),
  708. x = x,
  709. y = y,
  710. z = next_z,
  711. w = 0,
  712. h = 0,
  713. command_list = {},
  714. texture = nil,
  715. texture_w = 0,
  716. texture_h = 0,
  717. pass = nil,
  718. is_hovered = false,
  719. is_modal = is_modal or false,
  720. was_called_this_frame = true,
  721. cw = {}
  722. }
  723. table.insert( windows, window )
  724. if is_modal then
  725. modal_window = window
  726. end
  727. end
  728. layout.y = (2 * margin) + font.h
  729. if idx == 0 then
  730. begin_idx = #windows
  731. else
  732. begin_idx = idx
  733. end
  734. if idx > 0 then
  735. windows[ idx ].was_called_this_frame = true
  736. end
  737. begin_end_pairs.b = begin_end_pairs.b + 1
  738. end
  739. function UI2D.End( main_pass )
  740. local cur_window = windows[ begin_idx ]
  741. cur_window.w = layout.total_w
  742. cur_window.h = layout.total_h
  743. assert( cur_window.w > 0, "Begin/End block without widgets!" )
  744. -- Cache texture
  745. if cur_window.texture then
  746. if cur_window.texture_w ~= cur_window.w or cur_window.texture_h ~= cur_window.h then
  747. cur_window.texture:release()
  748. cur_window.texture_w = cur_window.w
  749. cur_window.texture_h = cur_window.h
  750. cur_window.texture = framework.NewTexture( cur_window.w, cur_window.h )
  751. framework.SetCanvas( cur_window.pass, cur_window.texture )
  752. end
  753. else
  754. cur_window.texture = framework.NewTexture( cur_window.w, cur_window.h )
  755. cur_window.texture_w = cur_window.w
  756. cur_window.texture_h = cur_window.h
  757. cur_window.pass = framework.NewPass( cur_window.texture )
  758. end
  759. framework.SetCanvas( nil, cur_window.texture )
  760. framework.ResetPass( cur_window.pass )
  761. framework.SetFont( cur_window.pass )
  762. framework.ClearWindow( cur_window )
  763. -- Title bar and border
  764. local title_col = colors.window_titlebar
  765. if cur_window == active_window then
  766. title_col = colors.window_titlebar_active
  767. end
  768. table.insert( windows[ begin_idx ].command_list,
  769. { type = "rect_fill", bbox = { x = 0, y = 0, w = cur_window.w, h = (2 * margin) + font.h }, color = title_col } )
  770. local txt = cur_window.title
  771. local title_w = utf8.len( txt ) * font.w
  772. if title_w > cur_window.w - (2 * margin) then -- Truncate title
  773. local num_chars = ((cur_window.w - (2 * margin)) / font.w) - 3
  774. txt = string.sub( txt, 1, num_chars ) .. "..."
  775. title_w = utf8.len( txt ) * font.w
  776. end
  777. table.insert( windows[ begin_idx ].command_list,
  778. { type = "text", text = txt, bbox = { x = margin, y = 0, w = title_w, h = (2 * margin) + font.h }, color = colors.text } )
  779. table.insert( windows[ begin_idx ].command_list,
  780. { type = "rect_wire", bbox = { x = 0, y = 0, w = cur_window.w, h = cur_window.h }, color = colors.window_border } )
  781. -- Do draw commands
  782. for i, v in ipairs( cur_window.command_list ) do
  783. if v.type == "rect_fill" then
  784. if v.is_separator then
  785. framework.SetColor( cur_window.pass, v.color )
  786. framework.DrawRect( cur_window.pass, v.bbox.x + (cur_window.w / 2), v.bbox.y, cur_window.w - (2 * margin), separator_thickness, "fill" )
  787. else
  788. framework.SetColor( cur_window.pass, v.color )
  789. framework.DrawRect( cur_window.pass, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w, v.bbox.h, "fill" )
  790. end
  791. elseif v.type == "rect_wire" then
  792. framework.SetColor( cur_window.pass, v.color )
  793. framework.DrawRect( cur_window.pass, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w, v.bbox.h, "line" )
  794. elseif v.type == "circle_wire" then
  795. framework.SetColor( cur_window.pass, v.color )
  796. framework.DrawCircle( cur_window.pass, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w / 2, "line" )
  797. elseif v.type == "circle_fill" then
  798. framework.SetColor( cur_window.pass, v.color )
  799. framework.DrawCircle( cur_window.pass, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w / 3, "fill" )
  800. elseif v.type == "circle_wire_half" then
  801. framework.SetColor( cur_window.pass, v.color )
  802. framework.DrawCircleHalf( cur_window.pass, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w / 2, "line", v.angle1, v.angle2 )
  803. elseif v.type == "circle_fill_half" then
  804. framework.SetColor( cur_window.pass, v.color )
  805. framework.DrawCircleHalf( cur_window.pass, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w / 2, "fill", v.angle1, v.angle2 )
  806. elseif v.type == "line" then
  807. framework.SetColor( cur_window.pass, v.color )
  808. framework.DrawLine( cur_window.pass, v.x1, v.y1, v.x2, v.y2 )
  809. elseif v.type == "text" then
  810. framework.SetColor( cur_window.pass, v.color )
  811. local text_w = font.handle:getWidth( v.text )
  812. framework.DrawText( cur_window.pass, v.text, v.bbox.x, v.bbox.y, v.bbox.w, v.bbox.h, text_w )
  813. elseif v.type == "image" then
  814. -- NOTE Temp fix. Had to do negative vertical scale. Otherwise image gets flipped?
  815. framework.SetColor( cur_window.pass, v.color )
  816. framework.DrawImage( cur_window.pass, v.texture, v.bbox.x + (v.bbox.w / 2), v.bbox.y + (v.bbox.h / 2), v.bbox.w, v.bbox.h, clamp_sampler, v.image_w, v.image_h )
  817. end
  818. end
  819. ResetLayout()
  820. begin_end_pairs.e = begin_end_pairs.e + 1
  821. end
  822. function UI2D.HasMouse()
  823. return has_mouse
  824. end
  825. function UI2D.SetWindowPosition( name, x, y )
  826. local exists, idx = WindowExists( name )
  827. if exists then
  828. windows[ idx ].x = x
  829. windows[ idx ].y = y
  830. return true
  831. end
  832. return false
  833. end
  834. function UI2D.GetWindowPosition( name )
  835. local exists, idx = WindowExists( name )
  836. if exists then
  837. return windows[ idx ].x, windows[ idx ].y
  838. end
  839. return nil
  840. end
  841. function UI2D.GetWindowSize( name )
  842. local exists, idx = WindowExists( name )
  843. if exists then
  844. return windows[ idx ].w, windows[ idx ].h
  845. end
  846. return nil
  847. end
  848. function UI2D.SetColorTheme( theme, copy_from )
  849. if type( theme ) == "string" then
  850. colors = color_themes[ theme ]
  851. elseif type( theme ) == "table" then
  852. copy_from = copy_from or "dark"
  853. for i, v in pairs( color_themes[ copy_from ] ) do
  854. if theme[ i ] == nil then
  855. theme[ i ] = v
  856. end
  857. end
  858. colors = theme
  859. end
  860. end
  861. function UI2D.GetColorTheme()
  862. for i, v in pairs( color_themes ) do
  863. if v == colors then
  864. return i
  865. end
  866. end
  867. end
  868. function UI2D.OverrideColor( col_name, color )
  869. if not overriden_colors[ col_name ] then
  870. local old_color = colors[ col_name ]
  871. overriden_colors[ col_name ] = old_color
  872. colors[ col_name ] = color
  873. end
  874. end
  875. function UI2D.ResetColor( col_name )
  876. if overriden_colors[ col_name ] then
  877. colors[ col_name ] = overriden_colors[ col_name ]
  878. overriden_colors[ col_name ] = nil
  879. end
  880. end
  881. function UI2D.SetFontSize( size )
  882. local info = debug.getinfo( 1, "S" )
  883. local lib_path = info.source:match( "@(.*[\\/])" )
  884. clamp_sampler = framework.NewSampler()
  885. local lib_path = info.source:match( "@(.*[\\/])" )
  886. font.handle = framework.LoadFont( lib_path, size )
  887. framework.SetPixelDensity( font.handle )
  888. font.h = font.handle:getHeight()
  889. font.w = font.handle:getWidth( "W" )
  890. font.size = size
  891. margin = math.floor( font.h / 2 )
  892. separator_thickness = math.floor( font.h / 7 )
  893. end
  894. function UI2D.GetFontSize()
  895. return font.size
  896. end
  897. function UI2D.HasTextInput()
  898. return has_text_input
  899. end
  900. function UI2D.IsModalOpen()
  901. return modal_window
  902. end
  903. function UI2D.EndModalWindow()
  904. modal_window = nil
  905. end
  906. function UI2D.SameLine()
  907. layout.same_line = true
  908. end
  909. function UI2D.SameColumn()
  910. layout.same_column = true
  911. end
  912. function UI2D.Button( name, width, height, tooltip )
  913. local text = GetLabelPart( name )
  914. local cur_window = windows[ begin_idx ]
  915. local text_w = utf8.len( text ) * font.w
  916. local num_lines = GetLineCount( text )
  917. local bbox = {}
  918. if layout.same_line then
  919. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = (2 * margin) + text_w, h = (2 * margin) + (num_lines * font.h) }
  920. elseif layout.same_column then
  921. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = (2 * margin) + text_w, h = (2 * margin) + (num_lines * font.h) }
  922. else
  923. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = (2 * margin) + text_w, h = (2 * margin) + (num_lines * font.h) }
  924. end
  925. if width and type( width ) == "number" and width > bbox.w then
  926. bbox.w = width
  927. end
  928. if height and type( height ) == "number" and height > bbox.h then
  929. bbox.h = height
  930. end
  931. UpdateLayout( bbox )
  932. local result = false
  933. local col = colors.button_bg
  934. if not modal_window or (modal_window and modal_window == cur_window) then
  935. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  936. if tooltip then
  937. active_tooltip.text = tooltip
  938. active_tooltip.x = mouse.x
  939. active_tooltip.y = mouse.y
  940. end
  941. col = colors.button_bg_hover
  942. if mouse.state == e_mouse_state.clicked then
  943. active_widget = cur_window.id .. name
  944. end
  945. if mouse.state == e_mouse_state.held then
  946. col = colors.button_bg_click
  947. end
  948. if mouse.state == e_mouse_state.released and active_widget == cur_window.id .. name then
  949. active_widget = nil
  950. result = true
  951. end
  952. end
  953. end
  954. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = bbox, color = col } )
  955. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = bbox, color = colors.button_border } )
  956. table.insert( windows[ begin_idx ].command_list, { type = "text", text = text, bbox = bbox, color = colors.text } )
  957. return result
  958. end
  959. function UI2D.SliderInt( name, v, v_min, v_max, width, tooltip )
  960. return Slider( e_slider_type.int, name, v, v_min, v_max, width, tooltip )
  961. end
  962. function UI2D.SliderFloat( name, v, v_min, v_max, width, num_decimals, tooltip )
  963. return Slider( e_slider_type.float, name, v, v_min, v_max, width, num_decimals, tooltip )
  964. end
  965. function UI2D.ProgressBar( progress, width, tooltip )
  966. local cur_window = windows[ begin_idx ]
  967. if width and width >= (2 * margin) + (4 * font.w) then
  968. width = width
  969. else
  970. width = 300
  971. end
  972. local bbox = {}
  973. if layout.same_line then
  974. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = width, h = (2 * margin) + font.h }
  975. elseif layout.same_column then
  976. bbox = { x = layout.x, y = layout.y + layout.h, w = width, h = (2 * margin) + font.h }
  977. else
  978. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = width, h = (2 * margin) + font.h }
  979. end
  980. UpdateLayout( bbox )
  981. if not modal_window or (modal_window and modal_window == cur_window) then
  982. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  983. if tooltip then
  984. active_tooltip.text = tooltip
  985. active_tooltip.x = mouse.x
  986. active_tooltip.y = mouse.y
  987. end
  988. end
  989. end
  990. progress = Clamp( progress, 0, 100 )
  991. local fill_w = math.floor( (width * progress) / 100 )
  992. local str = progress .. "%"
  993. table.insert( windows[ begin_idx ].command_list,
  994. { type = "rect_fill", bbox = { x = bbox.x, y = bbox.y, w = fill_w, h = bbox.h }, color = colors.progress_bar_fill } )
  995. table.insert( windows[ begin_idx ].command_list,
  996. { type = "rect_fill", bbox = { x = bbox.x + fill_w, y = bbox.y, w = bbox.w - fill_w, h = bbox.h }, color = colors.progress_bar_bg } )
  997. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = bbox, color = colors.progress_bar_border } )
  998. table.insert( windows[ begin_idx ].command_list, { type = "text", text = str, bbox = bbox, color = colors.text } )
  999. end
  1000. function UI2D.Separator()
  1001. local bbox = {}
  1002. if layout.same_line or layout.same_column then
  1003. return
  1004. else
  1005. bbox = { x = 0, y = layout.y + layout.row_h + margin, w = 0, h = 0 }
  1006. end
  1007. UpdateLayout( bbox )
  1008. table.insert( windows[ begin_idx ].command_list, { is_separator = true, type = "rect_fill", bbox = bbox, color = colors.separator } )
  1009. end
  1010. function UI2D.ImageButton( texture, width, height, text, tooltip )
  1011. local cur_window = windows[ begin_idx ]
  1012. local width = width or texture:getWidth()
  1013. local height = height or texture:getHeight()
  1014. local bbox = {}
  1015. if layout.same_line then
  1016. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = width, h = height }
  1017. elseif layout.same_column then
  1018. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = width, height = height }
  1019. else
  1020. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = width, h = height }
  1021. end
  1022. local text_w
  1023. if text then
  1024. text_w = font.handle:getWidth( text )
  1025. font.h = font.handle:getHeight()
  1026. if font.h > bbox.h then
  1027. bbox.h = font.h
  1028. end
  1029. bbox.w = bbox.w + (2 * margin) + text_w
  1030. end
  1031. UpdateLayout( bbox )
  1032. local result = false
  1033. local col = 1
  1034. if not modal_window or (modal_window and modal_window == cur_window) then
  1035. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  1036. if tooltip then
  1037. active_tooltip.text = tooltip
  1038. active_tooltip.x = mouse.x
  1039. active_tooltip.y = mouse.y
  1040. end
  1041. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = bbox, color = colors.image_button_border_highlight } )
  1042. if mouse.state == e_mouse_state.clicked then
  1043. active_widget = cur_window.id .. tostring( texture )
  1044. end
  1045. if mouse.state == e_mouse_state.held then
  1046. col = 0.7
  1047. end
  1048. if mouse.state == e_mouse_state.released and active_widget == cur_window.id .. tostring( texture ) then
  1049. active_widget = nil
  1050. result = true
  1051. end
  1052. end
  1053. end
  1054. local original_w = texture:getWidth()
  1055. local original_h = texture:getHeight()
  1056. if text then
  1057. table.insert( windows[ begin_idx ].command_list,
  1058. {
  1059. type = "image",
  1060. bbox = { x = bbox.x, y = bbox.y + ((bbox.h - height) / 2), w = width, h = height },
  1061. texture = texture,
  1062. image_w = original_w,
  1063. image_h = original_h,
  1064. color = { col, col, col }
  1065. } )
  1066. table.insert( windows[ begin_idx ].command_list,
  1067. { type = "text", text = text, bbox = { x = bbox.x + width, y = bbox.y, w = text_w + (2 * margin), h = bbox.h }, color = colors.text } )
  1068. else
  1069. table.insert( windows[ begin_idx ].command_list, { type = "image", bbox = bbox, texture = texture, image_w = original_w, image_h = original_h, color = { col, col, col } } )
  1070. end
  1071. return result
  1072. end
  1073. function UI2D.Dummy( width, height )
  1074. local bbox = {}
  1075. if layout.same_line then
  1076. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = width, h = height }
  1077. else
  1078. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = width, h = height }
  1079. end
  1080. UpdateLayout( bbox )
  1081. end
  1082. function UI2D.TabBar( name, tabs, idx, tooltip )
  1083. local cur_window = windows[ begin_idx ]
  1084. local bbox = {}
  1085. if layout.same_line then
  1086. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = 0, h = (2 * margin) + font.h }
  1087. elseif layout.same_column then
  1088. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = 0, h = (2 * margin) + font.h }
  1089. else
  1090. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = 0, h = (2 * margin) + font.h }
  1091. end
  1092. local result = false, idx
  1093. local total_w = 0
  1094. local col = colors.tab_bar_bg
  1095. local x_off = bbox.x
  1096. for i, v in ipairs( tabs ) do
  1097. local text_w = font.handle:getWidth( v )
  1098. local tab_w = text_w + (2 * margin)
  1099. bbox.w = bbox.w + tab_w
  1100. if not modal_window or (modal_window and modal_window == cur_window) then
  1101. if PointInRect( mouse.x, mouse.y, x_off + cur_window.x, bbox.y + cur_window.y, tab_w, bbox.h ) and cur_window == active_window then
  1102. if tooltip then
  1103. active_tooltip.text = tooltip
  1104. active_tooltip.x = mouse.x
  1105. active_tooltip.y = mouse.y
  1106. end
  1107. col = colors.tab_bar_hover
  1108. if mouse.state == e_mouse_state.clicked and cur_window.id .. name then
  1109. idx = i
  1110. result = true
  1111. end
  1112. else
  1113. col = colors.tab_bar_bg
  1114. end
  1115. end
  1116. local tab_rect = { x = x_off, y = bbox.y, w = tab_w, h = bbox.h }
  1117. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = tab_rect, color = col } )
  1118. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = tab_rect, color = colors.tab_bar_border } )
  1119. table.insert( windows[ begin_idx ].command_list, { type = "text", text = v, bbox = tab_rect, color = colors.text } )
  1120. if idx == i then
  1121. local highlight_thickness = math.floor( font.h / 4 )
  1122. table.insert( windows[ begin_idx ].command_list,
  1123. {
  1124. type = "rect_fill",
  1125. bbox = { x = tab_rect.x + 2, y = tab_rect.y + tab_rect.h - (highlight_thickness), w = tab_rect.w - 4, h = highlight_thickness },
  1126. color = colors.tab_bar_highlight
  1127. } )
  1128. end
  1129. x_off = x_off + tab_w
  1130. end
  1131. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = bbox, color = colors.tab_bar_border } )
  1132. UpdateLayout( bbox )
  1133. return result, idx
  1134. end
  1135. function UI2D.Label( text, compact )
  1136. local text_w = font.handle:getWidth( text )
  1137. local num_lines = GetLineCount( text )
  1138. local mrg = (2 * margin)
  1139. if compact then
  1140. mrg = 0
  1141. end
  1142. local bbox = {}
  1143. if layout.same_line then
  1144. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = text_w, h = mrg + (num_lines * font.h) }
  1145. elseif layout.same_column then
  1146. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = text_w, h = mrg + (num_lines * font.h) }
  1147. else
  1148. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = text_w, h = mrg + (num_lines * font.h) }
  1149. end
  1150. UpdateLayout( bbox )
  1151. table.insert( windows[ begin_idx ].command_list, { type = "text", text = text, bbox = bbox, color = colors.text } )
  1152. end
  1153. function UI2D.CheckBox( text, checked, tooltip )
  1154. local cur_window = windows[ begin_idx ]
  1155. local text_w = font.handle:getWidth( text )
  1156. local bbox = {}
  1157. if layout.same_line then
  1158. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = font.h + margin + text_w, h = (2 * margin) + font.h }
  1159. elseif layout.same_column then
  1160. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = font.h + margin + text_w, h = (2 * margin) + font.h }
  1161. else
  1162. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = font.h + margin + text_w, h = (2 * margin) + font.h }
  1163. end
  1164. UpdateLayout( bbox )
  1165. local result = false
  1166. local col = colors.check_border
  1167. if not modal_window or (modal_window and modal_window == cur_window) then
  1168. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  1169. if tooltip then
  1170. active_tooltip.text = tooltip
  1171. active_tooltip.x = mouse.x
  1172. active_tooltip.y = mouse.y
  1173. end
  1174. col = colors.check_border_hover
  1175. if mouse.state == e_mouse_state.clicked then
  1176. active_widget = cur_window.id .. text
  1177. end
  1178. if mouse.state == e_mouse_state.released and active_widget == cur_window.id .. text then
  1179. active_widget = nil
  1180. result = true
  1181. end
  1182. end
  1183. end
  1184. local check_rect = { x = bbox.x, y = bbox.y + margin, w = font.h, h = font.h }
  1185. local text_rect = { x = bbox.x + font.h + margin, y = bbox.y, w = text_w + margin, h = bbox.h }
  1186. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = check_rect, color = col } )
  1187. table.insert( windows[ begin_idx ].command_list, { type = "text", text = text, bbox = text_rect, color = colors.text } )
  1188. if checked and type( checked ) == "boolean" then
  1189. table.insert( windows[ begin_idx ].command_list, { type = "text", text = "✔", bbox = check_rect, color = colors.check_mark } )
  1190. end
  1191. return result
  1192. end
  1193. function UI2D.ToggleButton( text, checked, tooltip )
  1194. local cur_window = windows[ begin_idx ]
  1195. local text_w = font.handle:getWidth( text )
  1196. local bbox = {}
  1197. if layout.same_line then
  1198. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = (2 * font.h) + margin + text_w, h = (2 * margin) + font.h }
  1199. elseif layout.same_column then
  1200. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = (2 * font.h) + margin + text_w, h = (2 * margin) + font.h }
  1201. else
  1202. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = (2 * font.h) + margin + text_w, h = (2 * margin) + font.h }
  1203. end
  1204. UpdateLayout( bbox )
  1205. local result = false
  1206. local col_border = colors.toggle_border
  1207. if not modal_window or (modal_window and modal_window == cur_window) then
  1208. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  1209. if tooltip then
  1210. active_tooltip.text = tooltip
  1211. active_tooltip.x = mouse.x
  1212. active_tooltip.y = mouse.y
  1213. end
  1214. col_border = colors.toggle_border_hover
  1215. if mouse.state == e_mouse_state.clicked then
  1216. active_widget = cur_window.id .. text
  1217. end
  1218. if mouse.state == e_mouse_state.released and active_widget == cur_window.id .. text then
  1219. active_widget = nil
  1220. result = true
  1221. end
  1222. end
  1223. end
  1224. local half_left = { x = bbox.x, y = bbox.y + margin, w = font.h, h = font.h }
  1225. local half_right = { x = bbox.x + font.h, y = bbox.y + margin, w = font.h, h = font.h }
  1226. local middle = { x = bbox.x + (font.h / 2), y = bbox.y + margin, w = font.h, h = font.h }
  1227. local text_rect = { x = bbox.x + (2 * font.h) + margin, y = bbox.y, w = text_w + margin, h = bbox.h }
  1228. table.insert( windows[ begin_idx ].command_list, { type = "text", text = text, bbox = text_rect, color = colors.text } )
  1229. if checked and type( checked ) == "boolean" then
  1230. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill_half", bbox = half_left, color = colors.toggle_bg_on, angle1 = math.pi / 2, angle2 = math.pi * 1.5 } )
  1231. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill_half", bbox = half_right, color = colors.toggle_bg_on, angle1 = -math.pi / 2, angle2 = math.pi / 2 } )
  1232. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = middle, color = colors.toggle_bg_on } )
  1233. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill", bbox = half_right, color = colors.toggle_handle } )
  1234. else
  1235. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill_half", bbox = half_left, color = colors.toggle_bg_off, angle1 = math.pi / 2, angle2 = math.pi * 1.5 } )
  1236. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill_half", bbox = half_right, color = colors.toggle_bg_off, angle1 = -math.pi / 2, angle2 = math.pi / 2 } )
  1237. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = middle, color = colors.toggle_bg_off } )
  1238. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill", bbox = half_left, color = colors.toggle_handle } )
  1239. end
  1240. table.insert( windows[ begin_idx ].command_list, { type = "circle_wire_half", bbox = half_left, color = col_border, angle1 = math.pi / 2, angle2 = math.pi * 1.5 } )
  1241. table.insert( windows[ begin_idx ].command_list, { type = "circle_wire_half", bbox = half_right, color = col_border, angle1 = -math.pi / 2, angle2 = math.pi / 2 } )
  1242. table.insert( windows[ begin_idx ].command_list,
  1243. { type = "line", x1 = bbox.x + (font.h / 2), y1 = bbox.y + margin, x2 = bbox.x + (font.h * 1.5), y2 = bbox.y + margin, color = col_border } )
  1244. table.insert( windows[ begin_idx ].command_list,
  1245. { type = "line", x1 = bbox.x + (font.h / 2), y1 = bbox.y + margin + font.h, x2 = bbox.x + (font.h * 1.5), y2 = bbox.y + margin + font.h, color = col_border } )
  1246. return result
  1247. end
  1248. function UI2D.RadioButton( text, checked, tooltip )
  1249. local cur_window = windows[ begin_idx ]
  1250. local text_w = font.handle:getWidth( text )
  1251. local bbox = {}
  1252. if layout.same_line then
  1253. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = font.h + margin + text_w, h = (2 * margin) + font.h }
  1254. elseif layout.same_column then
  1255. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = font.h + margin + text_w, h = (2 * margin) + font.h }
  1256. else
  1257. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = font.h + margin + text_w, h = (2 * margin) + font.h }
  1258. end
  1259. UpdateLayout( bbox )
  1260. local result = false
  1261. local col = colors.radio_border
  1262. if not modal_window or (modal_window and modal_window == cur_window) then
  1263. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  1264. if tooltip then
  1265. active_tooltip.text = tooltip
  1266. active_tooltip.x = mouse.x
  1267. active_tooltip.y = mouse.y
  1268. end
  1269. col = colors.radio_border_hover
  1270. if mouse.state == e_mouse_state.clicked then
  1271. active_widget = cur_window.id .. text
  1272. end
  1273. if mouse.state == e_mouse_state.released and active_widget == cur_window.id .. text then
  1274. active_widget = nil
  1275. result = true
  1276. end
  1277. end
  1278. end
  1279. local check_rect = { x = bbox.x, y = bbox.y + margin, w = font.h, h = font.h }
  1280. local text_rect = { x = bbox.x + font.h + margin, y = bbox.y, w = text_w + margin, h = bbox.h }
  1281. table.insert( windows[ begin_idx ].command_list, { type = "circle_wire", bbox = check_rect, color = col } )
  1282. table.insert( windows[ begin_idx ].command_list, { type = "text", text = text, bbox = text_rect, color = colors.text } )
  1283. if checked and type( checked ) == "boolean" then
  1284. table.insert( windows[ begin_idx ].command_list, { type = "circle_fill", bbox = check_rect, color = colors.radio_mark } )
  1285. end
  1286. return result
  1287. end
  1288. function UI2D.TextBox( name, num_visible_chars, text, tooltip )
  1289. local cur_window = windows[ begin_idx ]
  1290. local label = GetLabelPart( name )
  1291. local label_w = font.handle:getWidth( label )
  1292. local bbox = {}
  1293. if layout.same_line then
  1294. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = (4 * margin) + (num_visible_chars * font.w) + label_w, h = (2 * margin) + font.h }
  1295. elseif layout.same_column then
  1296. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = (4 * margin) + (num_visible_chars * font.w) + label_w, h = (2 * margin) + font.h }
  1297. else
  1298. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = (4 * margin) + (num_visible_chars * font.w) + label_w, h = (2 * margin) + font.h }
  1299. end
  1300. UpdateLayout( bbox )
  1301. local scroll = 0
  1302. if active_textbox and active_textbox.id == cur_window.id .. name then
  1303. scroll = active_textbox.scroll
  1304. end
  1305. local text_rect = { x = bbox.x, y = bbox.y, w = (2 * margin) + (num_visible_chars * font.w), h = bbox.h }
  1306. local visible_text = nil
  1307. if utf8.len( text ) > num_visible_chars then
  1308. visible_text = utf8.sub( text, scroll + 1, scroll + num_visible_chars )
  1309. else
  1310. visible_text = text
  1311. end
  1312. local label_rect = { x = text_rect.x + text_rect.w + margin, y = bbox.y, w = label_w, h = bbox.h }
  1313. local char_rect = { x = text_rect.x + margin, y = text_rect.y, w = (utf8.len( visible_text ) * font.w), h = text_rect.h }
  1314. -- Text editing
  1315. local caret_rect = nil
  1316. if active_widget == cur_window.id .. name then
  1317. if text_input_character then
  1318. local p = active_textbox.caret + active_textbox.scroll
  1319. local part1 = utf8.sub( text, 1, p )
  1320. local part2 = utf8.sub( text, p + 1, utf8.len( text ) )
  1321. text = part1 .. text_input_character .. part2
  1322. active_textbox.caret = active_textbox.caret + 1
  1323. if active_textbox.caret > num_visible_chars then
  1324. active_textbox.scroll = active_textbox.scroll + 1
  1325. end
  1326. end
  1327. if keys[ "backspace" ][ 3 ] == 1 or repeating_key == "backspace" then
  1328. if active_textbox.caret > 0 then
  1329. local p = active_textbox.caret + active_textbox.scroll
  1330. local part1 = utf8.sub( text, 1, p - 1 )
  1331. local part2 = utf8.sub( text, p + 1, utf8.len( text ) )
  1332. text = part1 .. part2
  1333. local max_scroll = utf8.len( text ) - num_visible_chars
  1334. if active_textbox.scroll < max_scroll or utf8.len( text ) < num_visible_chars then
  1335. active_textbox.caret = active_textbox.caret - 1
  1336. end
  1337. end
  1338. end
  1339. if keys[ "delete" ][ 3 ] == 1 or repeating_key == "delete" then
  1340. if active_textbox.caret < num_visible_chars and active_textbox.caret < utf8.len( text ) then
  1341. local p = active_textbox.caret + active_textbox.scroll
  1342. local part1 = utf8.sub( text, 1, p )
  1343. local part2 = utf8.sub( text, p + 2, utf8.len( text ) )
  1344. text = part1 .. part2
  1345. local max_scroll = utf8.len( text ) - num_visible_chars
  1346. if active_textbox.scroll >= max_scroll and utf8.len( text ) > num_visible_chars then
  1347. active_textbox.caret = active_textbox.caret + 1
  1348. end
  1349. end
  1350. end
  1351. if keys[ "left" ][ 3 ] == 1 or repeating_key == "left" then
  1352. if active_textbox.caret == 0 then
  1353. if active_textbox.scroll > 0 then
  1354. active_textbox.scroll = active_textbox.scroll - 1
  1355. end
  1356. end
  1357. active_textbox.caret = active_textbox.caret - 1
  1358. end
  1359. if keys[ "right" ][ 3 ] == 1 or repeating_key == "right" then
  1360. local full_length = utf8.len( text )
  1361. local visible_length = utf8.len( visible_text )
  1362. if active_textbox.caret == num_visible_chars and full_length > num_visible_chars and active_textbox.scroll < (full_length - visible_length) then
  1363. active_textbox.scroll = active_textbox.scroll + 1
  1364. end
  1365. if active_textbox.caret < full_length then
  1366. active_textbox.caret = active_textbox.caret + 1
  1367. end
  1368. end
  1369. local max_scroll = utf8.len( text ) - num_visible_chars
  1370. if max_scroll < 0 then max_scroll = 0 end
  1371. active_textbox.scroll = Clamp( active_textbox.scroll, 0, max_scroll )
  1372. scroll = active_textbox.scroll
  1373. active_textbox.caret = Clamp( active_textbox.caret, 0, num_visible_chars )
  1374. caret_rect = { x = char_rect.x + (active_textbox.caret * font.w), y = char_rect.y + margin, w = 2, h = font.h }
  1375. end
  1376. local col1 = colors.textbox_bg
  1377. local col2 = colors.textbox_border
  1378. if not modal_window or (modal_window and modal_window == cur_window) then
  1379. if PointInRect( mouse.x, mouse.y, text_rect.x + cur_window.x, text_rect.y + cur_window.y, text_rect.w, text_rect.h ) and cur_window == active_window then
  1380. if tooltip then
  1381. active_tooltip.text = tooltip
  1382. active_tooltip.x = mouse.x
  1383. active_tooltip.y = mouse.y
  1384. end
  1385. col1 = colors.textbox_bg_hover
  1386. if mouse.state == e_mouse_state.clicked then
  1387. has_text_input = true
  1388. local pos = math.floor( (mouse.x - cur_window.x - text_rect.x) / font.w )
  1389. if pos > utf8.len( text ) then
  1390. pos = utf8.len( text )
  1391. end
  1392. if active_widget ~= cur_window.id .. name then
  1393. active_textbox = { id = cur_window.id .. name, caret = pos }
  1394. active_textbox.scroll = 0
  1395. active_widget = cur_window.id .. name
  1396. else
  1397. active_textbox.caret = pos
  1398. end
  1399. end
  1400. else
  1401. if mouse.state == e_mouse_state.clicked then
  1402. if active_widget == cur_window.id .. name then -- Deactivate self
  1403. has_text_input = false
  1404. active_textbox = nil
  1405. active_widget = nil
  1406. return text, true
  1407. end
  1408. end
  1409. end
  1410. if active_widget == cur_window.id .. name then
  1411. if keys[ "tab" ][ 3 ] == 1 or keys[ "return" ][ 3 ] == 1 or keys[ "kpenter" ][ 3 ] == 1 then -- Deactivate self
  1412. has_text_input = false
  1413. active_textbox = nil
  1414. active_widget = nil
  1415. return text, true
  1416. end
  1417. end
  1418. end
  1419. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = text_rect, color = col1 } )
  1420. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = text_rect, color = col2 } )
  1421. table.insert( windows[ begin_idx ].command_list, { type = "text", text = visible_text, bbox = char_rect, color = colors.text } )
  1422. table.insert( windows[ begin_idx ].command_list, { type = "text", text = label, bbox = label_rect, color = colors.text } )
  1423. if caret_rect and caret_blink.on then
  1424. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = caret_rect, color = colors.text } )
  1425. end
  1426. return text, false
  1427. end
  1428. function UI2D.ListBoxSetSelected( name, idx )
  1429. local cur_window = windows[ begin_idx ]
  1430. local exists, lst_idx = ListBoxExists( cur_window.id .. name )
  1431. if exists then
  1432. if type( idx ) == "table" then
  1433. listbox_state[ lst_idx ].selection = {}
  1434. for i, v in ipairs( idx ) do
  1435. table.insert( listbox_state[ lst_idx ].selection, v )
  1436. end
  1437. else
  1438. listbox_state[ lst_idx ].selected_idx = idx
  1439. end
  1440. end
  1441. end
  1442. function UI2D.ListBox( name, num_visible_rows, num_visible_chars, collection, selected, multi_select, tooltip )
  1443. local cur_window = windows[ begin_idx ]
  1444. local exists, lst_idx = ListBoxExists( cur_window.id .. name )
  1445. if not exists then
  1446. local selected_idx = 0
  1447. if type( selected ) == "number" then
  1448. selected_idx = selected
  1449. elseif type( selected ) == "string" then
  1450. for i = 1, #collection do
  1451. if selected == collection[ i ] then
  1452. selected_idx = i
  1453. break
  1454. end
  1455. end
  1456. end
  1457. local lb = { id = cur_window.id .. name, selected_idx = selected_idx, scroll_x = 0, scroll_y = 0, selection = {} }
  1458. if selected_idx > 0 then
  1459. table.insert( lb.selection, selected_idx )
  1460. end
  1461. table.insert( listbox_state, lb )
  1462. end
  1463. if lst_idx == 0 then
  1464. lst_idx = #listbox_state
  1465. end
  1466. local sbt = font.h -- scrollbar thickness
  1467. local bbox = {}
  1468. if layout.same_line then
  1469. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = (2 * margin) + (num_visible_chars * font.w) + sbt, h = (num_visible_rows * font.h) + sbt }
  1470. elseif layout.same_column then
  1471. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = (2 * margin) + (num_visible_chars * font.w) + sbt, h = (num_visible_rows * font.h) + sbt }
  1472. else
  1473. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = (2 * margin) + (num_visible_chars * font.w) + sbt, h = (num_visible_rows * font.h) + sbt }
  1474. end
  1475. UpdateLayout( bbox )
  1476. local sb_vertical = { x = bbox.x + bbox.w - sbt, y = bbox.y + sbt, w = sbt, h = bbox.h - (3 * sbt) }
  1477. local sb_horizontal = { x = bbox.x + sbt, y = bbox.y + bbox.h - sbt, w = bbox.w - (3 * sbt), h = sbt }
  1478. local sb_button_top = { x = bbox.x + bbox.w - sbt, y = bbox.y, w = sbt, h = sbt }
  1479. local sb_button_bottom = { x = bbox.x + bbox.w - sbt, y = bbox.y + bbox.h - (2 * sbt), w = sbt, h = sbt }
  1480. local sb_button_left = { x = bbox.x, y = bbox.y + bbox.h - sbt, w = sbt, h = sbt }
  1481. local sb_button_right = { x = bbox.x + bbox.w - (2 * sbt), y = bbox.y + bbox.h - sbt, w = sbt, h = sbt }
  1482. local max_total_chars_x = GetLongerStringLen( collection )
  1483. local highlight_idx = nil
  1484. local result = false
  1485. -- Input for buttons and selection
  1486. local t_btn_col = colors.list_button
  1487. local b_btn_col = colors.list_button
  1488. local l_btn_col = colors.list_button
  1489. local r_btn_col = colors.list_button
  1490. if not modal_window or (modal_window and modal_window == cur_window) then
  1491. if cur_window == active_window then
  1492. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) then -- whole listbox
  1493. if tooltip then
  1494. active_tooltip.text = tooltip
  1495. active_tooltip.x = mouse.x
  1496. active_tooltip.y = mouse.y
  1497. end
  1498. listbox_state[ lst_idx ].scroll_y = listbox_state[ lst_idx ].scroll_y - mouse.wheel_y
  1499. listbox_state[ lst_idx ].scroll_x = listbox_state[ lst_idx ].scroll_x - mouse.wheel_x
  1500. end
  1501. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w - sbt, bbox.h - sbt ) and #collection > 0 then -- content area
  1502. highlight_idx = math.floor( (mouse.y - cur_window.y - bbox.y) / (font.h) ) + 1
  1503. highlight_idx = Clamp( highlight_idx, 1, #collection )
  1504. if mouse.state == e_mouse_state.clicked then
  1505. listbox_state[ lst_idx ].selected_idx = highlight_idx + listbox_state[ lst_idx ].scroll_y
  1506. result = true
  1507. if multi_select then
  1508. if framework.GetKeyDown( "lctrl" ) then
  1509. local exists = false
  1510. local idx = 0
  1511. for i, v in ipairs( listbox_state[ lst_idx ].selection ) do
  1512. if v == listbox_state[ lst_idx ].selected_idx then
  1513. idx = i
  1514. exists = true
  1515. break
  1516. end
  1517. end
  1518. if not exists then
  1519. table.insert( listbox_state[ lst_idx ].selection, listbox_state[ lst_idx ].selected_idx )
  1520. else
  1521. table.remove( listbox_state[ lst_idx ].selection, idx )
  1522. end
  1523. else
  1524. listbox_state[ lst_idx ].selection = {}
  1525. table.insert( listbox_state[ lst_idx ].selection, listbox_state[ lst_idx ].selected_idx )
  1526. end
  1527. end
  1528. end
  1529. elseif PointInRect( mouse.x, mouse.y, sb_vertical.x + cur_window.x, sb_vertical.y + cur_window.y, sbt, sb_vertical.h ) then -- v_scrollbar
  1530. elseif PointInRect( mouse.x, mouse.y, sb_horizontal.x + cur_window.x, sb_horizontal.y + cur_window.y, sb_horizontal.w, sbt ) then -- h_scrollbar
  1531. elseif PointInRect( mouse.x, mouse.y, sb_button_top.x + cur_window.x, sb_button_top.y + cur_window.y, sb_button_top.w, sbt ) then -- button top
  1532. t_btn_col = colors.list_button_hover
  1533. if mouse.state == e_mouse_state.clicked then
  1534. listbox_state[ lst_idx ].scroll_y = listbox_state[ lst_idx ].scroll_y - 1
  1535. t_btn_col = colors.list_button_click
  1536. end
  1537. elseif PointInRect( mouse.x, mouse.y, sb_button_bottom.x + cur_window.x, sb_button_bottom.y + cur_window.y, sb_button_bottom.w, sbt ) then -- button bottom
  1538. b_btn_col = colors.list_button_hover
  1539. if mouse.state == e_mouse_state.clicked then
  1540. listbox_state[ lst_idx ].scroll_y = listbox_state[ lst_idx ].scroll_y + 1
  1541. b_btn_col = colors.list_button_click
  1542. end
  1543. elseif PointInRect( mouse.x, mouse.y, sb_button_left.x + cur_window.x, sb_button_left.y + cur_window.y, sb_button_left.w, sbt ) then -- button left
  1544. l_btn_col = colors.list_button_hover
  1545. if mouse.state == e_mouse_state.clicked then
  1546. listbox_state[ lst_idx ].scroll_x = listbox_state[ lst_idx ].scroll_x - 1
  1547. l_btn_col = colors.list_button_click
  1548. end
  1549. elseif PointInRect( mouse.x, mouse.y, sb_button_right.x + cur_window.x, sb_button_right.y + cur_window.y, sb_button_right.w, sbt ) then -- button right
  1550. r_btn_col = colors.list_button_hover
  1551. if mouse.state == e_mouse_state.clicked then
  1552. listbox_state[ lst_idx ].scroll_x = listbox_state[ lst_idx ].scroll_x + 1
  1553. r_btn_col = colors.list_button_click
  1554. end
  1555. end
  1556. end
  1557. end
  1558. -- Draw scrollbars
  1559. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = bbox, color = colors.list_bg } )
  1560. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = bbox, color = colors.list_border } )
  1561. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = sb_vertical, color = colors.list_track } )
  1562. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = sb_horizontal, color = colors.list_track } )
  1563. table.insert( windows[ begin_idx ].command_list, { type = "text", text = "△", bbox = sb_button_top, color = t_btn_col } )
  1564. table.insert( windows[ begin_idx ].command_list, { type = "text", text = "▽", bbox = sb_button_bottom, color = b_btn_col } )
  1565. table.insert( windows[ begin_idx ].command_list, { type = "text", text = "◁", bbox = sb_button_left, color = l_btn_col } )
  1566. table.insert( windows[ begin_idx ].command_list, { type = "text", text = "▷", bbox = sb_button_right, color = r_btn_col } )
  1567. local max_scroll_y = 0
  1568. if #collection > num_visible_rows then
  1569. max_scroll_y = #collection - num_visible_rows
  1570. end
  1571. local max_scroll_x = max_total_chars_x - num_visible_chars - 1
  1572. if max_scroll_x < 0 then
  1573. max_scroll_x = 0
  1574. end
  1575. listbox_state[ lst_idx ].scroll_y = Clamp( listbox_state[ lst_idx ].scroll_y, 0, max_scroll_y )
  1576. listbox_state[ lst_idx ].scroll_x = Clamp( listbox_state[ lst_idx ].scroll_x, 0, max_scroll_x )
  1577. local scroll_y = listbox_state[ lst_idx ].scroll_y
  1578. local scroll_x = listbox_state[ lst_idx ].scroll_x
  1579. local first = scroll_y + 1
  1580. local last = scroll_y + num_visible_rows
  1581. if #collection < num_visible_rows then
  1582. last = #collection
  1583. end
  1584. -- Input for thumbs
  1585. if not modal_window or (modal_window and modal_window == cur_window) then
  1586. -- thumb vertical
  1587. if max_scroll_y > 0 then
  1588. local v_thumb_height = sb_vertical.h * (num_visible_rows / #collection)
  1589. local max_dist = sb_vertical.h - v_thumb_height
  1590. local scroll_distance = MapRange( 0, max_scroll_y, 0, max_dist, scroll_y )
  1591. local thumb_vertical = { x = bbox.x + bbox.w - sbt, y = bbox.y + sbt + scroll_distance, w = sbt, h = v_thumb_height }
  1592. local col = colors.list_thumb
  1593. if PointInRect( mouse.x, mouse.y, thumb_vertical.x + cur_window.x, thumb_vertical.y + cur_window.y, sbt, thumb_vertical.h ) then
  1594. col = colors.list_thumb_hover
  1595. if mouse.state == e_mouse_state.clicked then
  1596. listbox_state[ lst_idx ].mouse_start_y = mouse.y
  1597. listbox_state[ lst_idx ].old_scroll_y = listbox_state[ lst_idx ].scroll_y
  1598. end
  1599. end
  1600. if mouse.state == e_mouse_state.held and listbox_state[ lst_idx ].mouse_start_y then
  1601. col = colors.list_thumb_click
  1602. local pixel_steps = max_scroll_y / font.h
  1603. local diff = mouse.y - listbox_state[ lst_idx ].mouse_start_y
  1604. listbox_state[ lst_idx ].scroll_y = math.floor( diff / pixel_steps ) + listbox_state[ lst_idx ].old_scroll_y
  1605. end
  1606. if mouse.state == e_mouse_state.released and listbox_state[ lst_idx ].mouse_start_y then
  1607. listbox_state[ lst_idx ].mouse_start_y = nil
  1608. listbox_state[ lst_idx ].old_scroll_y = nil
  1609. end
  1610. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = thumb_vertical, color = col } )
  1611. end
  1612. -- thumb horizontal
  1613. if max_scroll_x > 0 then
  1614. local h_thumb_width = sb_horizontal.w * (num_visible_chars / max_total_chars_x)
  1615. local max_dist = sb_horizontal.w - h_thumb_width
  1616. local scroll_distance = MapRange( 0, max_scroll_x, 0, max_dist, scroll_x )
  1617. local thumb_horizontal = { x = bbox.x + sbt + scroll_distance, y = bbox.y + bbox.h - sbt, w = h_thumb_width, h = sbt }
  1618. local col = colors.list_thumb
  1619. if PointInRect( mouse.x, mouse.y, thumb_horizontal.x + cur_window.x, thumb_horizontal.y + cur_window.y, thumb_horizontal.w, sbt ) then
  1620. col = colors.list_thumb_hover
  1621. if mouse.state == e_mouse_state.clicked then
  1622. listbox_state[ lst_idx ].mouse_start_x = mouse.x
  1623. listbox_state[ lst_idx ].old_scroll_x = listbox_state[ lst_idx ].scroll_x
  1624. end
  1625. end
  1626. if mouse.state == e_mouse_state.held and listbox_state[ lst_idx ].mouse_start_x then
  1627. col = colors.list_thumb_click
  1628. local pixel_steps = max_scroll_x / font.h
  1629. local diff = mouse.x - listbox_state[ lst_idx ].mouse_start_x
  1630. listbox_state[ lst_idx ].scroll_x = math.floor( diff / pixel_steps ) + listbox_state[ lst_idx ].old_scroll_x
  1631. end
  1632. if mouse.state == e_mouse_state.released and listbox_state[ lst_idx ].mouse_start_x then
  1633. listbox_state[ lst_idx ].mouse_start_x = nil
  1634. listbox_state[ lst_idx ].old_scroll_x = nil
  1635. end
  1636. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = thumb_horizontal, color = col } )
  1637. end
  1638. end
  1639. -- Draw selected rect
  1640. if multi_select then
  1641. for i, v in ipairs( listbox_state[ lst_idx ].selection ) do
  1642. local sel_idx = v
  1643. if sel_idx >= first and sel_idx <= last then
  1644. local selected_rect = { x = bbox.x, y = bbox.y + (sel_idx - scroll_y - 1) * font.h, w = bbox.w - sbt, h = font.h }
  1645. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = selected_rect, color = colors.list_selected } )
  1646. end
  1647. end
  1648. else
  1649. local sel_idx = listbox_state[ lst_idx ].selected_idx
  1650. if sel_idx >= first and sel_idx <= last then
  1651. local selected_rect = { x = bbox.x, y = bbox.y + (sel_idx - scroll_y - 1) * font.h, w = bbox.w - sbt, h = font.h }
  1652. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = selected_rect, color = colors.list_selected } )
  1653. end
  1654. end
  1655. -- Draw highlight rect
  1656. if highlight_idx then
  1657. local highlight_rect = { x = bbox.x, y = bbox.y + ((highlight_idx - 1) * font.h), w = bbox.w - sbt, h = font.h }
  1658. table.insert( windows[ begin_idx ].command_list, { type = "rect_fill", bbox = highlight_rect, color = colors.list_highlight } )
  1659. end
  1660. -- Draw entries
  1661. local y_offset = bbox.y
  1662. for i = first, last do
  1663. local final_str = nil
  1664. local cur = collection[ i ]
  1665. local cur_len = utf8.len( cur )
  1666. if cur_len - scroll_x > num_visible_chars + 1 then
  1667. final_str = utf8.sub( cur, scroll_x + 1, num_visible_chars + scroll_x + 1 )
  1668. else
  1669. if scroll_x < cur_len then
  1670. final_str = utf8.sub( cur, scroll_x + 1, cur_len )
  1671. else
  1672. final_str = nil
  1673. end
  1674. end
  1675. if final_str then
  1676. local final_len = utf8.len( final_str )
  1677. local item_w = final_len * font.w
  1678. table.insert( windows[ begin_idx ].command_list,
  1679. { type = "text", text = final_str, bbox = { x = bbox.x, y = y_offset, w = item_w + margin, h = font.h }, color = colors.text } )
  1680. end
  1681. y_offset = y_offset + font.h
  1682. end
  1683. if #collection > 0 then
  1684. listbox_state[ lst_idx ].selected_idx = Clamp( listbox_state[ lst_idx ].selected_idx, 0, #collection )
  1685. end
  1686. local t = {}
  1687. if multi_select then
  1688. t = listbox_state[ lst_idx ].selection
  1689. end
  1690. return result, listbox_state[ lst_idx ].selected_idx, t
  1691. end
  1692. function UI2D.CustomWidget( name, width, height, tooltip )
  1693. local cur_window = windows[ begin_idx ]
  1694. local exists, idx = WidgetExists( cur_window, cur_window.id .. name )
  1695. if not exists then
  1696. local new_widget = {}
  1697. new_widget.id = cur_window.id .. name
  1698. new_widget.width = width
  1699. new_widget.height = height
  1700. new_widget.texture = framework.NewTexture( width, height )
  1701. new_widget.pass = framework.NewPass( new_widget.texture )
  1702. framework.SetProjection( new_widget.pass )
  1703. table.insert( cur_window.cw, new_widget )
  1704. idx = #cur_window.cw
  1705. else
  1706. if cur_window.cw[ idx ].width ~= width or cur_window.cw[ idx ].height ~= height then
  1707. cur_window.cw[ idx ].width = width
  1708. cur_window.cw[ idx ].height = height
  1709. framework.ReleaseTexture()
  1710. cur_window.cw[ idx ].texture = framework.NewTexture( width, height )
  1711. framework.SetCanvas( cur_window.cw[ idx ].pass, cur_window.cw[ idx ].texture )
  1712. end
  1713. end
  1714. local bbox = {}
  1715. if layout.same_line then
  1716. bbox = { x = layout.x + layout.w + margin, y = layout.y, w = width, h = height }
  1717. elseif layout.same_column then
  1718. bbox = { x = layout.x, y = layout.y + layout.h + margin, w = width, h = height }
  1719. else
  1720. bbox = { x = margin, y = layout.y + layout.row_h + margin, w = width, h = height }
  1721. end
  1722. UpdateLayout( bbox )
  1723. local clicked = false
  1724. local held = false
  1725. local released = false
  1726. local hovered = false
  1727. local wheelx, wheely = 0, 0
  1728. if not modal_window or (modal_window and modal_window == cur_window) then
  1729. if PointInRect( mouse.x, mouse.y, bbox.x + cur_window.x, bbox.y + cur_window.y, bbox.w, bbox.h ) and cur_window == active_window then
  1730. if tooltip then
  1731. active_tooltip.text = tooltip
  1732. active_tooltip.x = mouse.x
  1733. active_tooltip.y = mouse.y
  1734. end
  1735. hovered = true
  1736. wheelx, wheely = mouse.wheel_x, mouse.wheel_y
  1737. if mouse.state == e_mouse_state.clicked then
  1738. clicked = true
  1739. active_widget = cur_window.cw[ idx ]
  1740. end
  1741. end
  1742. if mouse.state == e_mouse_state.held and cur_window == active_window and active_widget == cur_window.cw[ idx ] then
  1743. held = true
  1744. end
  1745. if mouse.state == e_mouse_state.released and cur_window == active_window and active_widget == cur_window.cw[ idx ] then
  1746. released = true
  1747. active_widget = nil
  1748. end
  1749. end
  1750. cur_window.cw[ idx ].bbox = bbox
  1751. table.insert( windows[ begin_idx ].command_list, { type = "rect_wire", bbox = bbox, color = colors.button_border } )
  1752. framework.ResetPass( cur_window.cw[ idx ].pass )
  1753. framework.SetProjection( cur_window.cw[ idx ].pass )
  1754. if framework.type == "lovr" then
  1755. return cur_window.cw[ idx ].pass, clicked, held, released, hovered, mouse.x - cur_window.x - bbox.x, mouse.y - cur_window.y - bbox.y, wheelx, wheely
  1756. else
  1757. return cur_window.cw[ idx ].texture, clicked, held, released, hovered, mouse.x - cur_window.x - bbox.x, mouse.y - cur_window.y - bbox.y, wheelx, wheely
  1758. end
  1759. end
  1760. function UI2D.RenderFrame( main_pass )
  1761. assert( begin_end_pairs.b == begin_end_pairs.e, "Begin/End pairs don't match! Begin calls: " .. begin_end_pairs.b .. " - End calls: " .. begin_end_pairs.e )
  1762. begin_end_pairs.b = 0
  1763. begin_end_pairs.e = 0
  1764. table.sort( windows, function( a, b ) return a.z > b.z end )
  1765. framework.SetCanvas()
  1766. local count = #windows
  1767. for i = count, 1, -1 do
  1768. local win = windows[ i ]
  1769. if win.was_called_this_frame then
  1770. framework.SetColor( main_pass, { 1, 1, 1 } )
  1771. if modal_window and win ~= modal_window then
  1772. framework.SetColor( main_pass, colors.modal_tint )
  1773. end
  1774. if framework.type == "lovr" then
  1775. framework.SetMaterial( main_pass, win.texture )
  1776. framework.DrawRect( main_pass, win.x + (win.w / 2), win.y + (win.h / 2), win.w, -win.h, "fill" ) --NOTE flip Y fix
  1777. framework.SetMaterial( main_pass )
  1778. else
  1779. love.graphics.draw( win.texture, win.x, win.y )
  1780. end
  1781. for j, k in ipairs( windows[ i ].cw ) do
  1782. framework.SetColor( win.pass, { 1, 1, 1 } )
  1783. framework.SetMaterial( win.pass, k.texture )
  1784. if framework.type == "lovr" then
  1785. framework.DrawRect( win.pass, k.bbox.x + (k.bbox.w / 2), k.bbox.y + (k.bbox.h / 2), k.bbox.w, -k.bbox.h, "fill" )
  1786. else
  1787. love.graphics.draw( k.texture, windows[ i ].x + k.bbox.x, windows[ i ].y + k.bbox.y )
  1788. end
  1789. framework.SetMaterial( win.pass )
  1790. framework.SetColor( win.pass, { 1, 1, 1 } )
  1791. end
  1792. if i == 1 and active_tooltip.text ~= "" then -- Draw tooltip
  1793. local num_lines = GetLineCount( active_tooltip.text )
  1794. local text_w = font.handle:getWidth( active_tooltip.text )
  1795. local rect_x = active_tooltip.x + (text_w / 2) + font.h
  1796. local rect_w = text_w + (2 * margin)
  1797. local rect_h = (num_lines * font.h) + (2 * margin)
  1798. local rect_y = active_tooltip.y - (rect_h / 2)
  1799. local text_y = 0
  1800. local text_x = active_tooltip.x + font.h
  1801. if framework.type == "lovr" then
  1802. text_y = rect_y - margin
  1803. else
  1804. text_y = active_tooltip.y - (font.h / 2) - (num_lines * font.h)
  1805. end
  1806. local width, height
  1807. if framework.type == "lovr" then
  1808. width, height = lovr.system.getWindowDimensions()
  1809. else
  1810. width, height = love.window.getMode()
  1811. end
  1812. if mouse.x > width - rect_w - margin then
  1813. rect_x = active_tooltip.x - (text_w / 2) - font.h
  1814. text_x = active_tooltip.x - font.h - text_w
  1815. end
  1816. if mouse.y < rect_h then
  1817. rect_y = active_tooltip.y + (rect_h / 2)
  1818. text_y = active_tooltip.y + (font.h / 2)
  1819. end
  1820. framework.SetColor( main_pass, colors.tooltip_bg )
  1821. framework.DrawRect( main_pass, rect_x, rect_y, rect_w, rect_h, "fill" )
  1822. framework.SetColor( main_pass, colors.tooltip_border )
  1823. framework.DrawRect( main_pass, rect_x, rect_y, rect_w, rect_h, "line" )
  1824. framework.SetColor( main_pass, colors.text )
  1825. framework.SetFont( main_pass )
  1826. framework.DrawText( main_pass, active_tooltip.text, text_x, text_y, text_w, font.h, text_w )
  1827. active_tooltip.text = ""
  1828. end
  1829. else
  1830. table.remove( windows, i )
  1831. end
  1832. end
  1833. mouse.wheel_x = 0
  1834. mouse.wheel_y = 0
  1835. text_input_character = nil
  1836. local passes = {}
  1837. for i, v in ipairs( windows ) do
  1838. v.command_list = nil
  1839. v.command_list = {}
  1840. v.was_called_this_frame = false
  1841. if framework.type == "lovr" then
  1842. for j, k in ipairs( v.cw ) do
  1843. table.insert( passes, k.pass )
  1844. end
  1845. table.insert( passes, v.pass )
  1846. end
  1847. end
  1848. if framework.type == "lovr" then
  1849. return passes
  1850. end
  1851. end
  1852. return UI2D