iron_ui.c 107 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332
  1. #include "iron_ui.h"
  2. #include <assert.h>
  3. #include <math.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. #include <ctype.h>
  8. #include <iron_gpu.h>
  9. #include <iron_system.h>
  10. #include "iron_draw.h"
  11. #include "iron_string.h"
  12. #include "iron_gc.h"
  13. #include <iron_file.h>
  14. static ui_t *current = NULL;
  15. static ui_theme_t *theme;
  16. static bool ui_key_repeat = true; // Emulate key repeat for non-character keys
  17. static bool ui_dynamic_glyph_load = true; // Allow text input fields to push new glyphs into the font atlas
  18. static float ui_key_repeat_time = 0.0;
  19. static char ui_text_to_paste[1024];
  20. static char ui_text_to_copy[1024];
  21. static ui_t *ui_copy_receiver = NULL;
  22. static int ui_copy_frame = 0;
  23. static bool ui_combo_first = true;
  24. static ui_handle_t *ui_combo_search_handle = NULL;
  25. ui_t *ui_instances[UI_MAX_INSTANCES];
  26. int ui_instances_count;
  27. bool ui_touch_scroll = false; // Pan with finger to scroll
  28. bool ui_touch_hold = false; // Touch and hold finger for right click
  29. bool ui_touch_tooltip = false; // Show extra tooltips above finger / on-screen keyboard
  30. bool ui_is_cut = false;
  31. bool ui_is_copy = false;
  32. bool ui_is_paste = false;
  33. void (*ui_on_border_hover)(ui_handle_t *, int) = NULL; // Mouse over window border, use for resizing
  34. void (*ui_on_text_hover)(void) = NULL; // Mouse over text input, use to set I-cursor
  35. void (*ui_on_deselect_text)(void) = NULL; // Text editing finished
  36. void (*ui_on_tab_drop)(ui_handle_t *, int, ui_handle_t *, int) = NULL; // Tab reorder via drag and drop
  37. #ifdef WITH_EVAL
  38. float js_eval(char *str);
  39. #endif
  40. f32_array_t *_ui_row2 = NULL;
  41. f32_array_t *_ui_row3 = NULL;
  42. f32_array_t *_ui_row4 = NULL;
  43. f32_array_t *_ui_row5 = NULL;
  44. f32_array_t *_ui_row6 = NULL;
  45. f32_array_t *_ui_row7 = NULL;
  46. float UI_SCALE() {
  47. return current->ops->scale_factor;
  48. }
  49. float UI_ELEMENT_W() {
  50. return theme->ELEMENT_W * UI_SCALE();
  51. }
  52. float UI_ELEMENT_H() {
  53. return theme->ELEMENT_H * UI_SCALE();
  54. }
  55. float UI_ELEMENT_OFFSET() {
  56. return theme->ELEMENT_OFFSET * UI_SCALE();
  57. }
  58. float UI_ARROW_SIZE() {
  59. return theme->ARROW_SIZE * UI_SCALE();
  60. }
  61. float UI_BUTTON_H() {
  62. return theme->BUTTON_H * UI_SCALE();
  63. }
  64. float UI_CHECK_SIZE() {
  65. return theme->CHECK_SIZE * UI_SCALE();
  66. }
  67. float UI_CHECK_SELECT_SIZE() {
  68. return theme->CHECK_SELECT_SIZE * UI_SCALE();
  69. }
  70. float UI_FONT_SIZE() {
  71. return theme->FONT_SIZE * UI_SCALE();
  72. }
  73. float UI_SCROLL_W() {
  74. return theme->SCROLL_W * UI_SCALE();
  75. }
  76. float UI_SCROLL_MINI_W() {
  77. return theme->SCROLL_MINI_W * UI_SCALE();
  78. }
  79. float UI_TEXT_OFFSET() {
  80. return theme->TEXT_OFFSET * UI_SCALE();
  81. }
  82. float UI_TAB_W() {
  83. return theme->TAB_W * UI_SCALE();
  84. }
  85. float UI_HEADER_DRAG_H() {
  86. return 15.0 * UI_SCALE();
  87. }
  88. float UI_TOOLTIP_DELAY() {
  89. return 1.0;
  90. }
  91. ui_t *ui_get_current() {
  92. return current;
  93. }
  94. void ui_set_current(ui_t *_current) {
  95. current = _current;
  96. theme = current->ops->theme;
  97. }
  98. ui_handle_t *ui_handle_create() {
  99. ui_handle_t *h = (ui_handle_t *)gc_alloc(sizeof(ui_handle_t));
  100. memset(h, 0, sizeof(ui_handle_t));
  101. h->redraws = 2;
  102. h->color = 0xffffffff;
  103. h->text = "";
  104. h->init = true;
  105. return h;
  106. }
  107. ui_handle_t *ui_nest(ui_handle_t *handle, int pos) {
  108. if (handle->children == NULL) {
  109. handle->children = any_array_create(0);
  110. }
  111. while(pos >= handle->children->length) {
  112. ui_handle_t *h = ui_handle_create();
  113. any_array_push(handle->children, h);
  114. if (pos == handle->children->length - 1) {
  115. // Return now so init stays true
  116. return h;
  117. }
  118. }
  119. // This handle already exists, set init to false
  120. handle->children->buffer[pos]->init = false;
  121. return handle->children->buffer[pos];
  122. }
  123. void ui_fade_color(float alpha) {
  124. uint32_t color = draw_get_color();
  125. uint8_t r = (color & 0x00ff0000) >> 16;
  126. uint8_t g = (color & 0x0000ff00) >> 8;
  127. uint8_t b = (color & 0x000000ff);
  128. uint8_t a = (uint8_t)(255.0 * alpha);
  129. draw_set_color((a << 24) | (r << 16) | (g << 8) | b);
  130. }
  131. void ui_fill(float x, float y, float w, float h, uint32_t color) {
  132. draw_set_color(color);
  133. if (!current->enabled) {
  134. ui_fade_color(0.25);
  135. }
  136. draw_filled_rect(current->_x + x * UI_SCALE(), current->_y + y * UI_SCALE() - 1, w * UI_SCALE(), h * UI_SCALE());
  137. draw_set_color(0xffffffff);
  138. }
  139. void ui_rect(float x, float y, float w, float h, uint32_t color, float strength) {
  140. draw_set_color(color);
  141. if (!current->enabled) {
  142. ui_fade_color(0.25);
  143. }
  144. draw_rect(current->_x + x * UI_SCALE(), current->_y + y * UI_SCALE(), w * UI_SCALE(), h * UI_SCALE(), strength);
  145. draw_set_color(0xffffffff);
  146. }
  147. void ui_draw_shadow(float x, float y, float w, float h) {
  148. if (theme->SHADOWS) {
  149. // for (int i = 0; i < 8; i++) {
  150. // float offset = i * UI_SCALE();
  151. // float a = (8 - i + 1) * 0.01;
  152. // draw_set_color(((uint8_t)(a * 255) << 24) | (0 << 16) | (0 << 8) | 0);
  153. // ui_draw_rect(true, x - offset, y - offset, w + offset * 2, h + offset * 2);
  154. // }
  155. float max_offset = 4.0 * UI_SCALE();
  156. for (int i = 0; i < 4; i++) {
  157. float offset = (max_offset / 4) * (i + 1);
  158. float a = 0.1 - (0.1 / 4) * i;
  159. draw_set_color(((uint8_t)(a * 255) << 24) | (0 << 16) | (0 << 8) | 0);
  160. ui_draw_rect(true, x + offset, y + offset, w + (max_offset - offset) * 2, h + (max_offset - offset) * 2);
  161. }
  162. }
  163. }
  164. void ui_draw_rect(bool fill, float x, float y, float w, float h) {
  165. float strength = 1.0;
  166. if (!current->enabled) {
  167. ui_fade_color(0.25);
  168. }
  169. x = (int)x;
  170. y = (int)y;
  171. w = (int)w;
  172. h = (int)h;
  173. if (fill) {
  174. int r = current->filled_round_corner_image.width;
  175. if (theme->ROUND_CORNERS && current->enabled && r > 0 && w >= r * 2.0) {
  176. y -= 1; // Make it pixel perfect with non-round draw
  177. h += 1;
  178. draw_scaled_image(&current->filled_round_corner_image, x, y, r, r);
  179. draw_scaled_image(&current->filled_round_corner_image, x, y + h, r, -r);
  180. draw_scaled_image(&current->filled_round_corner_image, x + w, y, -r, r);
  181. draw_scaled_image(&current->filled_round_corner_image, x + w, y + h, -r, -r);
  182. draw_filled_rect(x + r, y, w - r * 2.0, h);
  183. draw_filled_rect(x, y + r, w, h - r * 2.0);
  184. }
  185. else {
  186. draw_filled_rect(x, y - 1, w, h + 1);
  187. }
  188. }
  189. else {
  190. int r = current->round_corner_image.width;
  191. if (theme->ROUND_CORNERS && current->enabled && r > 0) {
  192. x -= 1;
  193. w += 1;
  194. y -= 1;
  195. h += 1;
  196. draw_scaled_image(&current->round_corner_image, x, y, r, r);
  197. draw_scaled_image(&current->round_corner_image, x, y + h, r, -r);
  198. draw_scaled_image(&current->round_corner_image, x + w, y, -r, r);
  199. draw_scaled_image(&current->round_corner_image, x + w, y + h, -r, -r);
  200. draw_filled_rect(x + r, y, w - r * 2.0, strength);
  201. draw_filled_rect(x + r, y + h - 1, w - r * 2.0, strength);
  202. draw_filled_rect(x, y + r, strength, h - r * 2.0);
  203. draw_filled_rect(x + w - 1, y + r, strength, h - r * 2.0);
  204. }
  205. else {
  206. draw_rect(x, y, w, h, strength);
  207. }
  208. }
  209. }
  210. void ui_draw_round_bottom(float x, float y, float w) {
  211. if (theme->ROUND_CORNERS) {
  212. int r = current->filled_round_corner_image.width;
  213. int h = 4;
  214. y -= 1; // Make it pixel perfect with non-round draw
  215. h += 1;
  216. draw_set_color(theme->SEPARATOR_COL);
  217. draw_scaled_image(&current->filled_round_corner_image, x, y + h, r, -r);
  218. draw_scaled_image(&current->filled_round_corner_image, x + w, y + h, -r, -r);
  219. draw_filled_rect(x + r, y, w - r * 2.0, h);
  220. }
  221. }
  222. bool ui_is_char(int code) {
  223. return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
  224. }
  225. int ui_check_start(int i, char *text, char **start, int start_count) {
  226. for (int x = 0; x < start_count; ++x) {
  227. if (strncmp(text + i, start[x], strlen(start[x])) == 0) {
  228. return strlen(start[x]);
  229. }
  230. }
  231. return 0;
  232. }
  233. ui_text_extract_t ui_extract_coloring(char *text, ui_coloring_t *col) {
  234. ui_text_extract_t res;
  235. res.colored[0] = '\0';
  236. res.uncolored[0] = '\0';
  237. bool coloring = false;
  238. int start_from = 0;
  239. int start_length = 0;
  240. for (int i = 0; i < strlen(text); ++i) {
  241. bool skip_first = false;
  242. // Check if upcoming text should be colored
  243. int length = ui_check_start(i, text, col->start->buffer, col->start->length);
  244. // Not touching another character
  245. bool separated_left = i == 0 || !ui_is_char(text[i - 1]);
  246. bool separated_right = i + length >= strlen(text) || !ui_is_char(text[i + length]);
  247. bool is_separated = separated_left && separated_right;
  248. // Start coloring
  249. if (length > 0 && (!coloring || col->end[0] == '\0') && (!col->separated || is_separated)) {
  250. coloring = true;
  251. start_from = i;
  252. start_length = length;
  253. if (col->end[0] != '\0' && col->end[0] != '\n') skip_first = true;
  254. }
  255. // End coloring
  256. else if (col->end[0] == '\0') {
  257. if (i == start_from + start_length) coloring = false;
  258. }
  259. else if (strncmp(text + i, col->end, strlen(col->end)) == 0) {
  260. coloring = false;
  261. }
  262. // If true, add current character to colored string
  263. int len_c = strlen(res.colored);
  264. int len_uc = strlen(res.uncolored);
  265. if (coloring && !skip_first) {
  266. res.colored[len_c] = text[i];
  267. res.colored[len_c + 1] = '\0';
  268. res.uncolored[len_uc] = ' ';
  269. res.uncolored[len_uc + 1] = '\0';
  270. }
  271. else {
  272. res.colored[len_c] = ' ';
  273. res.colored[len_c + 1] = '\0';
  274. res.uncolored[len_uc] = text[i];
  275. res.uncolored[len_uc + 1] = '\0';
  276. }
  277. }
  278. return res;
  279. }
  280. void ui_draw_string(char *text, float x_offset, float y_offset, int align, bool truncation) {
  281. static char temp[1024];
  282. static char truncated[1024];
  283. if (text == NULL) {
  284. return;
  285. }
  286. if (truncation) {
  287. assert(strlen(text) < 1024 - 2);
  288. char *full_text = text;
  289. strcpy(truncated, text);
  290. text = &truncated[0];
  291. while (strlen(text) > 0 && draw_string_width(current->ops->font, current->font_size, text) > current->_w - 6.0 * UI_SCALE()) {
  292. text[strlen(text) - 1] = 0;
  293. }
  294. if (strlen(text) < strlen(full_text)) {
  295. strcat(text, "..");
  296. // Strip more to fit ".."
  297. while (strlen(text) > 2 && draw_string_width(current->ops->font, current->font_size, text) > current->_w - 10.0 * UI_SCALE()) {
  298. text[strlen(text) - 3] = 0;
  299. strcat(text, "..");
  300. }
  301. if (current->is_hovered) {
  302. ui_tooltip(full_text);
  303. }
  304. }
  305. }
  306. if (ui_dynamic_glyph_load) {
  307. int len = strlen(text);
  308. for (int i = 0; i < len; ++i) {
  309. if (text[i] > 126 && !draw_font_has_glyph((int)text[i])) {
  310. int glyph = text[i];
  311. draw_font_add_glyph(glyph);
  312. }
  313. }
  314. }
  315. if (x_offset < 0) {
  316. x_offset = theme->TEXT_OFFSET;
  317. }
  318. x_offset *= UI_SCALE();
  319. draw_set_font(current->ops->font, current->font_size);
  320. if (align == UI_ALIGN_CENTER) {
  321. x_offset = current->_w / 2.0 - draw_string_width(current->ops->font, current->font_size, text) / 2.0;
  322. }
  323. else if (align == UI_ALIGN_RIGHT) {
  324. x_offset = current->_w - draw_string_width(current->ops->font, current->font_size, text) - UI_TEXT_OFFSET();
  325. }
  326. if (!current->enabled) {
  327. ui_fade_color(0.25);
  328. }
  329. if (current->text_coloring == NULL) {
  330. draw_string(text, current->_x + x_offset, current->_y + current->font_offset_y + y_offset);
  331. }
  332. else {
  333. // Monospace fonts only for now
  334. strcpy(temp, text);
  335. for (int i = 0; i < current->text_coloring->colorings->length; ++i) {
  336. ui_coloring_t *coloring = current->text_coloring->colorings->buffer[i];
  337. ui_text_extract_t result = ui_extract_coloring(temp, coloring);
  338. if (result.colored[0] != '\0') {
  339. draw_set_color(coloring->color);
  340. draw_string(result.colored, current->_x + x_offset, current->_y + current->font_offset_y + y_offset);
  341. }
  342. strcpy(temp, result.uncolored);
  343. }
  344. draw_set_color(current->text_coloring->default_color);
  345. draw_string(temp, current->_x + x_offset, current->_y + current->font_offset_y + y_offset);
  346. }
  347. }
  348. bool ui_get_initial_hover(float elem_h) {
  349. if (current->scissor && current->input_y < current->_window_y + current->window_header_h) {
  350. return false;
  351. }
  352. return current->enabled && current->input_enabled &&
  353. current->input_started_x >= current->_window_x + current->_x && current->input_started_x < (current->_window_x + current->_x + current->_w) &&
  354. current->input_started_y >= current->_window_y + current->_y && current->input_started_y < (current->_window_y + current->_y + elem_h);
  355. }
  356. bool ui_get_hover(float elem_h) {
  357. if (current->scissor && current->input_y < current->_window_y + current->window_header_h) {
  358. return false;
  359. }
  360. current->is_hovered = current->enabled && current->input_enabled &&
  361. current->input_x >= current->_window_x + current->_x && current->input_x < (current->_window_x + current->_x + current->_w) &&
  362. current->input_y >= current->_window_y + current->_y && current->input_y < (current->_window_y + current->_y + elem_h);
  363. return current->is_hovered;
  364. }
  365. bool ui_get_released(float elem_h) { // Input selection
  366. current->is_released = current->enabled && current->input_enabled && current->input_released && ui_get_hover(elem_h) && ui_get_initial_hover(elem_h);
  367. return current->is_released;
  368. }
  369. bool ui_get_pushed(float elem_h) {
  370. current->is_pushed = current->enabled && current->input_enabled && current->input_down && ui_get_hover(elem_h) && ui_get_initial_hover(elem_h);
  371. return current->is_pushed;
  372. }
  373. bool ui_get_started(float elem_h) {
  374. current->is_started = current->enabled && current->input_enabled && current->input_started && ui_get_hover(elem_h);
  375. return current->is_started;
  376. }
  377. bool ui_is_visible(float elem_h) {
  378. if (current->current_window == NULL) return true;
  379. return (current->_y + elem_h > current->window_header_h && current->_y < current->current_window->texture.height);
  380. }
  381. float ui_get_ratio(float ratio, float dyn) {
  382. return ratio < 0 ? -ratio : ratio * dyn;
  383. }
  384. // Draw the upcoming elements in the same row
  385. // Negative values will be treated as absolute, positive values as ratio to `window width`
  386. void ui_row(f32_array_t *ratios) {
  387. if (ratios->length == 0) {
  388. current->ratios = NULL;
  389. return;
  390. }
  391. current->ratios = ratios;
  392. current->current_ratio = 0;
  393. current->x_before_split = current->_x;
  394. current->w_before_split = current->_w;
  395. current->_w = ui_get_ratio(ratios->buffer[current->current_ratio], current->_w);
  396. }
  397. void ui_row2() {
  398. ui_row(_ui_row2);
  399. }
  400. void ui_row3() {
  401. ui_row(_ui_row3);
  402. }
  403. void ui_row4() {
  404. ui_row(_ui_row4);
  405. }
  406. void ui_row5() {
  407. ui_row(_ui_row5);
  408. }
  409. void ui_row6() {
  410. ui_row(_ui_row6);
  411. }
  412. void ui_row7() {
  413. ui_row(_ui_row7);
  414. }
  415. void ui_indent() {
  416. current->_x += UI_TAB_W();
  417. current->_w -= UI_TAB_W();
  418. }
  419. void ui_unindent() {
  420. current->_x -= UI_TAB_W();
  421. current->_w += UI_TAB_W();
  422. }
  423. void ui_end_element_of_size(float element_size) {
  424. if (current->current_window == NULL || current->current_window->layout == UI_LAYOUT_VERTICAL) {
  425. if (current->current_ratio == -1 || (current->ratios != NULL && current->current_ratio == current->ratios->length - 1)) { // New line
  426. current->_y += element_size;
  427. if ((current->ratios != NULL && current->current_ratio == current->ratios->length - 1)) { // Last row element
  428. current->current_ratio = -1;
  429. current->ratios = NULL;
  430. current->_x = current->x_before_split;
  431. current->_w = current->w_before_split;
  432. }
  433. }
  434. else { // Row
  435. current->current_ratio++;
  436. current->_x += current->_w; // More row elements to place
  437. current->_w = ui_get_ratio(current->ratios->buffer[current->current_ratio], current->w_before_split);
  438. }
  439. }
  440. else { // Horizontal
  441. current->_x += current->_w + UI_ELEMENT_OFFSET();
  442. }
  443. }
  444. void ui_end_element() {
  445. ui_end_element_of_size(UI_ELEMENT_H() + UI_ELEMENT_OFFSET());
  446. }
  447. void ui_resize(ui_handle_t *handle, int w, int h) {
  448. handle->redraws = 2;
  449. if (handle->texture.width != 0) {
  450. gpu_texture_destroy(&handle->texture);
  451. }
  452. if (w < 1) {
  453. w = 1;
  454. }
  455. if (h < 1) {
  456. h = 1;
  457. }
  458. gpu_render_target_init(&handle->texture, w, h, GPU_TEXTURE_FORMAT_RGBA32);
  459. }
  460. bool ui_input_in_rect(float x, float y, float w, float h) {
  461. return current->enabled && current->input_enabled &&
  462. current->input_x >= x && current->input_x < (x + w) &&
  463. current->input_y >= y && current->input_y < (y + h);
  464. }
  465. bool ui_input_changed() {
  466. return current->input_dx != 0 || current->input_dy != 0 || current->input_wheel_delta != 0 || current->input_started || current->input_started_r || current->input_released || current->input_released_r || current->input_down || current->input_down_r || current->is_key_pressed;
  467. }
  468. void ui_end_input() {
  469. if (ui_on_tab_drop != NULL && current->drag_tab_handle != NULL) {
  470. if (current->input_dx != 0 || current->input_dy != 0) {
  471. iron_mouse_set_cursor(1); // Hand
  472. }
  473. if (current->input_released) {
  474. iron_mouse_set_cursor(0); // Default
  475. current->drag_tab_handle = NULL;
  476. }
  477. }
  478. current->is_key_pressed = false;
  479. current->input_started = false;
  480. current->input_started_r = false;
  481. current->input_released = false;
  482. current->input_released_r = false;
  483. current->input_dx = 0;
  484. current->input_dy = 0;
  485. current->input_wheel_delta = 0;
  486. current->pen_in_use = false;
  487. if (ui_key_repeat && current->is_key_down && iron_time() - ui_key_repeat_time > 0.05) {
  488. if (current->key_code == IRON_KEY_BACKSPACE || current->key_code == IRON_KEY_DELETE || current->key_code == IRON_KEY_LEFT || current->key_code == IRON_KEY_RIGHT || current->key_code == IRON_KEY_UP || current->key_code == IRON_KEY_DOWN) {
  489. ui_key_repeat_time = iron_time();
  490. current->is_key_pressed = true;
  491. }
  492. }
  493. if (ui_touch_hold && current->input_down && current->input_x == current->input_started_x && current->input_y == current->input_started_y && current->input_started_time > 0 && iron_time() - current->input_started_time > 0.7) {
  494. current->touch_hold_activated = true;
  495. current->input_released_r = true;
  496. current->input_started_time = 0;
  497. }
  498. }
  499. void ui_scroll(float delta) {
  500. current->current_window->scroll_offset -= delta;
  501. }
  502. int ui_line_count(char *str) {
  503. if (str == NULL) return 0;
  504. int i = 0;
  505. int count = 1;
  506. while (str[i] != '\0') {
  507. if (str[i] == '\n') count++;
  508. i++;
  509. }
  510. return count;
  511. }
  512. char *ui_extract_line(char *str, int line) {
  513. static char temp[1024];
  514. int pos = 0;
  515. int len = strlen(str);
  516. int line_i = 0;
  517. for (int i = 0; i < len; ++i) {
  518. if (str[i] == '\n') {
  519. line_i++; continue;
  520. }
  521. if (line_i < line) {
  522. continue;
  523. }
  524. if (line_i > line) {
  525. break;
  526. }
  527. temp[pos++] = str[i];
  528. }
  529. temp[pos] = 0;
  530. return temp;
  531. }
  532. char *ui_lower_case(char *dest, char *src) {
  533. int len = strlen(src);
  534. assert(len < 1024);
  535. for (int i = 0; i < len; ++i) {
  536. dest[i] = tolower(src[i]);
  537. }
  538. return dest;
  539. }
  540. void ui_draw_tooltip_text(bool bind_global_g) {
  541. int line_count = ui_line_count(current->tooltip_text);
  542. float tooltip_w = 0.0;
  543. for (int i = 0; i < line_count; ++i) {
  544. float line_tooltip_w = draw_string_width(current->ops->font, current->font_size, ui_extract_line(current->tooltip_text, i));
  545. if (line_tooltip_w > tooltip_w) {
  546. tooltip_w = line_tooltip_w;
  547. }
  548. }
  549. current->tooltip_x = fmin(current->tooltip_x, iron_window_width() - tooltip_w - 20);
  550. if (bind_global_g) draw_begin(NULL, false, 0);
  551. float font_height = draw_font_height(current->ops->font, current->font_size);
  552. float off = 0;
  553. if (current->tooltip_img != NULL) {
  554. float w = current->tooltip_img->width;
  555. if (current->tooltip_img_max_width != 0 && w > current->tooltip_img_max_width) {
  556. w = current->tooltip_img_max_width;
  557. }
  558. off = current->tooltip_img->height * (w / current->tooltip_img->width);
  559. }
  560. int x = current->tooltip_x - 5;
  561. int y = current->tooltip_y + off - 5;
  562. int w = tooltip_w + 20 + 10;
  563. int h = font_height * line_count + 10;
  564. ui_draw_shadow(x, y, w, h);
  565. draw_set_color(theme->SEPARATOR_COL);
  566. draw_filled_rect(x, y, w, h);
  567. draw_set_font(current->ops->font, current->font_size);
  568. draw_set_color(theme->TEXT_COL);
  569. for (int i = 0; i < line_count; ++i) {
  570. draw_string(ui_extract_line(current->tooltip_text, i), current->tooltip_x + 5, current->tooltip_y + off + i * current->font_size);
  571. }
  572. if (bind_global_g) draw_end();
  573. }
  574. void ui_draw_tooltip_image(bool bind_global_g) {
  575. float w = current->tooltip_img->width;
  576. if (current->tooltip_img_max_width != 0 && w > current->tooltip_img_max_width) {
  577. w = current->tooltip_img_max_width;
  578. }
  579. float h = current->tooltip_img->height * (w / current->tooltip_img->width);
  580. current->tooltip_x = fmin(current->tooltip_x, iron_window_width() - w - 20);
  581. current->tooltip_y = fmin(current->tooltip_y, iron_window_height() - h - 20);
  582. if (bind_global_g) {
  583. draw_begin(NULL, false, 0);
  584. }
  585. draw_set_color(0xff000000);
  586. draw_filled_rect(current->tooltip_x, current->tooltip_y, w, h);
  587. draw_set_color(0xffffffff);
  588. current->tooltip_invert_y ?
  589. draw_scaled_image(current->tooltip_img, current->tooltip_x, current->tooltip_y + h, w, -h) :
  590. draw_scaled_image(current->tooltip_img, current->tooltip_x, current->tooltip_y, w, h);
  591. if (bind_global_g) draw_end();
  592. }
  593. void ui_draw_tooltip(bool bind_global_g) {
  594. static char temp[1024];
  595. if (current->slider_tooltip) {
  596. if (bind_global_g) {
  597. draw_begin(NULL, false, 0);
  598. }
  599. draw_set_font(current->ops->font, current->font_size * 2);
  600. sprintf(temp, "%f", round(current->scroll_handle->value * 100.0) / 100.0);
  601. string_strip_trailing_zeros(temp);
  602. char *text = temp;
  603. float x_off = draw_string_width(current->ops->font, current->font_size * 2.0, text) / 2.0;
  604. float y_off = draw_font_height(current->ops->font, current->font_size * 2.0);
  605. float x = fmin(fmax(current->slider_tooltip_x, current->input_x), current->slider_tooltip_x + current->slider_tooltip_w);
  606. draw_set_color(theme->BUTTON_COL);
  607. draw_filled_rect(x - x_off, current->slider_tooltip_y - y_off, x_off * 2.0, y_off);
  608. draw_set_color(theme->TEXT_COL);
  609. draw_string(text, x - x_off, current->slider_tooltip_y - y_off);
  610. if (bind_global_g) draw_end();
  611. }
  612. if (ui_touch_tooltip && current->text_selected_handle != NULL) {
  613. if (bind_global_g) {
  614. draw_begin(NULL, false, 0);
  615. }
  616. draw_set_font(current->ops->font, current->font_size * 2.0);
  617. float x_off = draw_string_width(current->ops->font, current->font_size * 2.0, current->text_selected) / 2.0;
  618. float y_off = draw_font_height(current->ops->font, current->font_size * 2.0) / 2.0;
  619. float x = iron_window_width() / 2.0;
  620. float y = iron_window_height() / 3.0;
  621. draw_set_color(theme->BUTTON_COL);
  622. draw_filled_rect(x - x_off, y - y_off, x_off * 2.0, y_off * 2.0);
  623. draw_set_color(theme->TEXT_COL);
  624. draw_string(current->text_selected, x - x_off, y - y_off);
  625. if (bind_global_g) draw_end();
  626. }
  627. if (current->tooltip_text[0] != '\0' || current->tooltip_img != NULL) {
  628. if (ui_input_changed()) {
  629. current->tooltip_shown = false;
  630. current->tooltip_wait = current->input_dx == 0 && current->input_dy == 0; // Wait for movement before showing up again
  631. }
  632. if (!current->tooltip_shown) {
  633. current->tooltip_shown = true;
  634. current->tooltip_x = current->input_x;
  635. current->tooltip_time = iron_time();
  636. }
  637. if (!current->tooltip_wait && iron_time() - current->tooltip_time > UI_TOOLTIP_DELAY()) {
  638. if (current->tooltip_img != NULL) {
  639. ui_draw_tooltip_image(bind_global_g);
  640. }
  641. if (current->tooltip_text[0] != '\0') {
  642. ui_draw_tooltip_text(bind_global_g);
  643. }
  644. }
  645. }
  646. else current->tooltip_shown = false;
  647. }
  648. void ui_draw_combo(bool begin /*= true*/) {
  649. if (current->combo_selected_handle == NULL) {
  650. return;
  651. }
  652. draw_set_color(theme->SEPARATOR_COL);
  653. if (begin) {
  654. draw_begin(NULL, false, 0);
  655. }
  656. float combo_h = (current->combo_selected_texts->length + (current->combo_selected_label != NULL ? 1 : 0) + (current->combo_search_bar ? 1 : 0)) * UI_ELEMENT_H();
  657. float dist_top = current->combo_selected_y - combo_h - UI_ELEMENT_H() - current->window_border_top;
  658. float dist_bottom = iron_window_height() - current->window_border_bottom - (current->combo_selected_y + combo_h );
  659. bool unroll_up = dist_bottom < 0 && dist_bottom < dist_top;
  660. ui_begin_region(current, current->combo_selected_x, current->combo_selected_y, current->combo_selected_w);
  661. if (unroll_up) {
  662. float off = current->combo_selected_label != NULL ? UI_ELEMENT_H() / UI_SCALE() : 0.0;
  663. ui_draw_shadow(current->_x, current->_y - combo_h - off, current->_w, combo_h);
  664. }
  665. else {
  666. ui_draw_shadow(current->_x, current->_y, current->_w, combo_h);
  667. }
  668. if (current->is_key_pressed || current->input_wheel_delta != 0) {
  669. int arrow_up = current->is_key_pressed && current->key_code == (unroll_up ? IRON_KEY_DOWN : IRON_KEY_UP);
  670. int arrow_down = current->is_key_pressed && current->key_code == (unroll_up ? IRON_KEY_UP : IRON_KEY_DOWN);
  671. int wheel_up = (unroll_up && current->input_wheel_delta > 0) || (!unroll_up && current->input_wheel_delta < 0);
  672. int wheel_down = (unroll_up && current->input_wheel_delta < 0) || (!unroll_up && current->input_wheel_delta > 0);
  673. if ((arrow_up || wheel_up) && current->combo_to_submit > 0) {
  674. int step = 1;
  675. if (current->combo_search_bar && strlen(current->text_selected) > 0) {
  676. char search[512];
  677. char str[512];
  678. ui_lower_case(search, current->text_selected);
  679. while (true) {
  680. ui_lower_case(str, current->combo_selected_texts->buffer[current->combo_to_submit - step]);
  681. if (strstr(str, search) == NULL && current->combo_to_submit - step > 0) {
  682. ++step;
  683. }
  684. else {
  685. break;
  686. }
  687. }
  688. // Corner case: current position is the top one according to the search pattern
  689. ui_lower_case(str, current->combo_selected_texts->buffer[current->combo_to_submit - step]);
  690. if (strstr(str, search) == NULL) {
  691. step = 0;
  692. }
  693. }
  694. current->combo_to_submit -= step;
  695. current->submit_combo_handle = current->combo_selected_handle;
  696. }
  697. else if ((arrow_down || wheel_down) && current->combo_to_submit < current->combo_selected_texts->length - 1) {
  698. int step = 1;
  699. if (current->combo_search_bar && strlen(current->text_selected) > 0) {
  700. char search[512];
  701. char str[512];
  702. ui_lower_case(search, current->text_selected);
  703. while (true) {
  704. ui_lower_case(str, current->combo_selected_texts->buffer[current->combo_to_submit + step]);
  705. if (strstr(str, search) == NULL && current->combo_to_submit + step > 0) {
  706. ++step;
  707. }
  708. else {
  709. break;
  710. }
  711. }
  712. // Corner case: current position is the top one according to the search pattern
  713. ui_lower_case(str, current->combo_selected_texts->buffer[current->combo_to_submit + step]);
  714. if (strstr(str, search) == NULL) {
  715. step = 0;
  716. }
  717. }
  718. current->combo_to_submit += step;
  719. current->submit_combo_handle = current->combo_selected_handle;
  720. }
  721. if (current->combo_selected_window != NULL) {
  722. current->combo_selected_window->redraws = 2;
  723. }
  724. }
  725. current->input_enabled = true;
  726. int _BUTTON_COL = theme->BUTTON_COL;
  727. int _ELEMENT_OFFSET = theme->ELEMENT_OFFSET;
  728. theme->ELEMENT_OFFSET = 0;
  729. float unroll_right = current->_x + current->combo_selected_w * 2.0 < iron_window_width() - current->window_border_right ? 1 : -1;
  730. bool reset_position = false;
  731. char search[512];
  732. search[0] = '\0';
  733. if (current->combo_search_bar) {
  734. if (unroll_up) {
  735. current->_y -= UI_ELEMENT_H() * 2.0;
  736. }
  737. if (ui_combo_first) {
  738. ui_combo_search_handle->text = "";
  739. }
  740. ui_fill(0, 0, current->_w / UI_SCALE(), UI_ELEMENT_H() / UI_SCALE(), theme->SEPARATOR_COL);
  741. strcpy(search, ui_text_input(ui_combo_search_handle, "", UI_ALIGN_LEFT, true, true));
  742. ui_lower_case(search, search);
  743. if (current->is_released) {
  744. ui_combo_first = true; // Keep combo open
  745. }
  746. if (ui_combo_first) {
  747. #if !defined(IRON_ANDROID) && !defined(IRON_IOS)
  748. ui_start_text_edit(ui_combo_search_handle, UI_ALIGN_LEFT); // Focus search bar
  749. #endif
  750. }
  751. reset_position = ui_combo_search_handle->changed;
  752. }
  753. for (int i = 0; i < current->combo_selected_texts->length; ++i) {
  754. char str[512];
  755. ui_lower_case(str, current->combo_selected_texts->buffer[i]);
  756. if (strlen(search) > 0 && strstr(str, search) == NULL) {
  757. continue; // Don't show items that don't fit the current search pattern
  758. }
  759. if (reset_position) { // The search has changed, select first entry that matches
  760. current->combo_to_submit = current->combo_selected_handle->position = i;
  761. current->submit_combo_handle = current->combo_selected_handle;
  762. reset_position = false;
  763. }
  764. if (unroll_up) {
  765. current->_y -= UI_ELEMENT_H() * 2.0;
  766. }
  767. theme->BUTTON_COL = i == current->combo_selected_handle->position ?
  768. theme->HIGHLIGHT_COL :
  769. theme->SEPARATOR_COL;
  770. ui_fill(0, 0, current->_w / UI_SCALE(), UI_ELEMENT_H() / UI_SCALE(), theme->SEPARATOR_COL);
  771. if (ui_button(current->combo_selected_texts->buffer[i], current->combo_selected_align, "")) {
  772. current->combo_to_submit = i;
  773. current->submit_combo_handle = current->combo_selected_handle;
  774. if (current->combo_selected_window != NULL) {
  775. current->combo_selected_window->redraws = 2;
  776. }
  777. break;
  778. }
  779. if (current->_y + UI_ELEMENT_H() > iron_window_height() - current->window_border_bottom || current->_y - UI_ELEMENT_H() * 2 < current->window_border_top) {
  780. current->_x += current->combo_selected_w * unroll_right; // Next column
  781. current->_y = current->combo_selected_y;
  782. }
  783. }
  784. theme->BUTTON_COL = _BUTTON_COL;
  785. theme->ELEMENT_OFFSET = _ELEMENT_OFFSET;
  786. if (current->combo_selected_label != NULL) { // Unroll down
  787. if (unroll_up) {
  788. current->_y -= UI_ELEMENT_H() * 2.0;
  789. ui_fill(0, 0, current->_w / UI_SCALE(), UI_ELEMENT_H() / UI_SCALE(), theme->SEPARATOR_COL);
  790. draw_set_color(theme->LABEL_COL);
  791. ui_draw_string(current->combo_selected_label, theme->TEXT_OFFSET, 0, UI_ALIGN_RIGHT, true);
  792. current->_y += UI_ELEMENT_H();
  793. ui_fill(0, 0, current->_w / UI_SCALE(), 1.0 * UI_SCALE(), theme->ACCENT_COL); // Separator
  794. }
  795. else {
  796. ui_fill(0, 0, current->_w / UI_SCALE(), UI_ELEMENT_H() / UI_SCALE(), theme->SEPARATOR_COL);
  797. ui_fill(0, 0, current->_w / UI_SCALE(), 1.0 * UI_SCALE(), theme->ACCENT_COL); // Separator
  798. draw_set_color(theme->LABEL_COL);
  799. ui_draw_string(current->combo_selected_label, theme->TEXT_OFFSET, 0, UI_ALIGN_RIGHT, true);
  800. current->_y += UI_ELEMENT_H();
  801. ui_draw_round_bottom(current->_x, current->_y - 1, current->_w);
  802. }
  803. }
  804. if ((current->input_released || current->input_released_r || current->is_escape_down || current->is_return_down) && !ui_combo_first) {
  805. current->combo_selected_handle = NULL;
  806. ui_combo_first = true;
  807. }
  808. else {
  809. ui_combo_first = false;
  810. }
  811. current->input_enabled = current->combo_selected_handle == NULL;
  812. ui_end_region(false);
  813. if (begin) {
  814. draw_end();
  815. }
  816. }
  817. void ui_bake_elements() {
  818. if (current->check_select_image.width != 0) {
  819. gpu_texture_destroy(&current->check_select_image);
  820. }
  821. float r = UI_CHECK_SELECT_SIZE();
  822. gpu_render_target_init(&current->check_select_image, r, r, GPU_TEXTURE_FORMAT_RGBA32);
  823. draw_begin(&current->check_select_image, true, 0x00000000);
  824. draw_set_color(0xffffffff);
  825. draw_line(0, r / 2.0, r / 2.0 - 2.0 * UI_SCALE(), r - 2.0 * UI_SCALE(), 2.0 * UI_SCALE());
  826. draw_line(r / 2.0 - 3.0 * UI_SCALE(), r - 3.0 * UI_SCALE(), r / 2.0 + 5.0 * UI_SCALE(), r - 11.0 * UI_SCALE(), 2.0 * UI_SCALE());
  827. draw_end();
  828. if (current->radio_image.width != 0) {
  829. gpu_texture_destroy(&current->radio_image);
  830. }
  831. r = UI_CHECK_SIZE();
  832. gpu_render_target_init(&current->radio_image, r, r, GPU_TEXTURE_FORMAT_RGBA32);
  833. draw_begin(&current->radio_image, true, 0x00000000);
  834. draw_set_color(0xffaaaaaa);
  835. draw_filled_circle(r / 2.0, r / 2.0, r / 2.0, 0);
  836. draw_set_color(0xffffffff);
  837. draw_circle(r / 2.0, r / 2.0, r / 2.0, 0, 1.0 * UI_SCALE());
  838. draw_end();
  839. if (current->radio_select_image.width != 0) {
  840. gpu_texture_destroy(&current->radio_select_image);
  841. }
  842. r = UI_CHECK_SELECT_SIZE();
  843. gpu_render_target_init(&current->radio_select_image, r, r, GPU_TEXTURE_FORMAT_RGBA32);
  844. draw_begin(&current->radio_select_image, true, 0x00000000);
  845. draw_set_color(0xffaaaaaa);
  846. draw_filled_circle(r / 2.0, r / 2.0, 4.5 * UI_SCALE(), 0);
  847. draw_set_color(0xffffffff);
  848. draw_filled_circle(r / 2.0, r / 2.0, 4.0 * UI_SCALE(), 0);
  849. draw_end();
  850. if (theme->ROUND_CORNERS) {
  851. if (current->filled_round_corner_image.width != 0) {
  852. gpu_texture_destroy(&current->filled_round_corner_image);
  853. }
  854. r = 4.0 * UI_SCALE();
  855. gpu_render_target_init(&current->filled_round_corner_image, r, r, GPU_TEXTURE_FORMAT_RGBA32);
  856. draw_begin(&current->filled_round_corner_image, true, 0x00000000);
  857. draw_set_color(0xffffffff);
  858. draw_filled_circle(r, r, r, 0);
  859. draw_end();
  860. if (current->round_corner_image.width != 0) {
  861. gpu_texture_destroy(&current->round_corner_image);
  862. }
  863. gpu_render_target_init(&current->round_corner_image, r, r, GPU_TEXTURE_FORMAT_RGBA32);
  864. draw_begin(&current->round_corner_image, true, 0x00000000);
  865. draw_set_color(0xffffffff);
  866. draw_circle(r, r, r, 0, 1);
  867. draw_end();
  868. }
  869. current->elements_baked = true;
  870. }
  871. void ui_begin_region(ui_t *ui, int x, int y, int w) {
  872. ui_set_current(ui);
  873. if (!current->elements_baked) {
  874. draw_end();
  875. ui_bake_elements();
  876. draw_begin(NULL, false, 0);
  877. }
  878. current->changed = false;
  879. current->current_window = NULL;
  880. current->tooltip_text[0] = '\0';
  881. current->tooltip_img = NULL;
  882. current->_window_x = 0;
  883. current->_window_y = 0;
  884. current->_window_w = w;
  885. current->_x = x;
  886. current->_y = y;
  887. current->_w = w;
  888. current->_h = 0;
  889. }
  890. void ui_end_region(bool last) {
  891. ui_draw_tooltip(false);
  892. current->tab_pressed_handle = NULL;
  893. if (last) {
  894. ui_draw_combo(false); // Handle active combo
  895. ui_end_input();
  896. }
  897. }
  898. void ui_set_cursor_to_input(int align) {
  899. float off = align == UI_ALIGN_LEFT ? UI_TEXT_OFFSET() : current->_w - draw_string_width(current->ops->font, current->font_size, current->text_selected);
  900. float x = current->input_x - (current->_window_x + current->_x + off);
  901. current->cursor_x = 0;
  902. while (current->cursor_x < strlen(current->text_selected) && draw_sub_string_width(current->ops->font, current->font_size, current->text_selected, 0, current->cursor_x) < x) {
  903. current->cursor_x++;
  904. }
  905. current->highlight_anchor = current->cursor_x;
  906. }
  907. void ui_start_text_edit(ui_handle_t *handle, int align) {
  908. current->is_typing = true;
  909. current->submit_text_handle = current->text_selected_handle;
  910. strcpy(current->text_to_submit, current->text_selected);
  911. current->text_selected_handle = handle;
  912. strcpy(current->text_selected, handle->text);
  913. current->cursor_x = strlen(handle->text);
  914. if (current->tab_pressed) {
  915. current->tab_pressed = false;
  916. current->is_key_pressed = false; // Prevent text deselect after tab press
  917. }
  918. else if (!current->highlight_on_select) { // Set cursor to click location
  919. ui_set_cursor_to_input(align);
  920. }
  921. current->tab_pressed_handle = handle;
  922. current->highlight_anchor = current->highlight_on_select ? 0 : current->cursor_x;
  923. iron_keyboard_show();
  924. }
  925. void ui_submit_text_edit() {
  926. current->changed = strcmp(current->submit_text_handle->text, current->text_to_submit) != 0;
  927. current->submit_text_handle->changed = current->changed;
  928. current->submit_text_handle->text = string_copy(current->text_to_submit);
  929. current->submit_text_handle = NULL;
  930. current->text_to_submit[0] = '\0';
  931. current->text_selected[0] = '\0';
  932. }
  933. void ui_deselect_text(ui_t *ui) {
  934. if (ui->text_selected_handle == NULL) {
  935. return;
  936. }
  937. ui->submit_text_handle = ui->text_selected_handle;
  938. strcpy(ui->text_to_submit, ui->text_selected);
  939. ui->text_selected_handle = NULL;
  940. ui->is_typing = false;
  941. if (ui->current_window != NULL) {
  942. ui->current_window->redraws = 2;
  943. }
  944. iron_keyboard_hide();
  945. ui->highlight_anchor = ui->cursor_x;
  946. if (ui_on_deselect_text != NULL) {
  947. ui_on_deselect_text();
  948. }
  949. }
  950. void ui_remove_char_at(char *str, int at) {
  951. int len = strlen(str);
  952. for (int i = at; i < len; ++i) {
  953. str[i] = str[i + 1];
  954. }
  955. }
  956. void ui_remove_chars_at(char *str, int at, int count) {
  957. for (int i = 0; i < count; ++i) {
  958. ui_remove_char_at(str, at);
  959. }
  960. }
  961. void ui_insert_char_at(char *str, int at, char c) {
  962. int len = strlen(str);
  963. for (int i = len + 1; i > at; --i) {
  964. str[i] = str[i - 1];
  965. }
  966. str[at] = c;
  967. }
  968. void ui_insert_chars_at(char *str, int at, char *cs) {
  969. int len = strlen(cs);
  970. for (int i = 0; i < len; ++i) {
  971. ui_insert_char_at(str, at + i, cs[i]);
  972. }
  973. }
  974. void ui_update_text_edit(int align, bool editable, bool live_update) {
  975. char text[256];
  976. strcpy(text, current->text_selected);
  977. if (current->is_key_pressed) { // Process input
  978. if (current->key_code == IRON_KEY_LEFT) { // Move cursor
  979. if (current->cursor_x > 0) {
  980. current->cursor_x--;
  981. }
  982. }
  983. else if (current->key_code == IRON_KEY_RIGHT) {
  984. if (current->cursor_x < strlen(text)) {
  985. current->cursor_x++;
  986. }
  987. }
  988. else if (editable && current->key_code == IRON_KEY_BACKSPACE) { // Remove char
  989. if (current->cursor_x > 0 && current->highlight_anchor == current->cursor_x) {
  990. ui_remove_char_at(text, current->cursor_x - 1);
  991. current->cursor_x--;
  992. }
  993. else if (current->highlight_anchor < current->cursor_x) {
  994. int count = current->cursor_x - current->highlight_anchor;
  995. ui_remove_chars_at(text, current->highlight_anchor, count);
  996. current->cursor_x = current->highlight_anchor;
  997. }
  998. else {
  999. int count = current->highlight_anchor - current->cursor_x;
  1000. ui_remove_chars_at(text, current->cursor_x, count);
  1001. }
  1002. }
  1003. else if (editable && current->key_code == IRON_KEY_DELETE) {
  1004. if (current->highlight_anchor == current->cursor_x) {
  1005. ui_remove_char_at(text, current->cursor_x);
  1006. }
  1007. else if (current->highlight_anchor < current->cursor_x) {
  1008. int count = current->cursor_x - current->highlight_anchor;
  1009. ui_remove_chars_at(text, current->highlight_anchor, count);
  1010. current->cursor_x = current->highlight_anchor;
  1011. }
  1012. else {
  1013. int count = current->highlight_anchor - current->cursor_x;
  1014. ui_remove_chars_at(text, current->cursor_x, count);
  1015. }
  1016. }
  1017. else if (current->key_code == IRON_KEY_RETURN) { // Deselect
  1018. ui_deselect_text(current);
  1019. }
  1020. else if (current->key_code == IRON_KEY_ESCAPE) { // Cancel
  1021. strcpy(current->text_selected, current->text_selected_handle->text);
  1022. ui_deselect_text(current);
  1023. }
  1024. else if (current->key_code == IRON_KEY_TAB && current->tab_switch_enabled && !current->is_ctrl_down) { // Next field
  1025. current->tab_pressed = true;
  1026. ui_deselect_text(current);
  1027. current->key_code = 0;
  1028. }
  1029. else if (current->key_code == IRON_KEY_HOME) {
  1030. current->cursor_x = 0;
  1031. }
  1032. else if (current->key_code == IRON_KEY_END) {
  1033. current->cursor_x = strlen(text);
  1034. }
  1035. else if (current->is_ctrl_down && current->is_a_down) { // Select all
  1036. current->cursor_x = strlen(text);
  1037. current->highlight_anchor = 0;
  1038. }
  1039. else if (editable && // Write
  1040. current->key_code != IRON_KEY_SHIFT &&
  1041. current->key_code != IRON_KEY_CAPS_LOCK &&
  1042. current->key_code != IRON_KEY_CONTROL &&
  1043. current->key_code != IRON_KEY_META &&
  1044. current->key_code != IRON_KEY_ALT &&
  1045. current->key_code != IRON_KEY_UP &&
  1046. current->key_code != IRON_KEY_DOWN &&
  1047. current->key_char >= 32) {
  1048. ui_remove_chars_at(text, current->highlight_anchor, current->cursor_x - current->highlight_anchor);
  1049. ui_insert_char_at(text, current->highlight_anchor, current->key_char);
  1050. current->cursor_x = current->cursor_x + 1 > strlen(text) ? strlen(text) : current->cursor_x + 1;
  1051. }
  1052. bool selecting = current->is_shift_down && (current->key_code == IRON_KEY_LEFT || current->key_code == IRON_KEY_RIGHT || current->key_code == IRON_KEY_SHIFT);
  1053. // isCtrlDown && isAltDown is the condition for AltGr was pressed
  1054. // AltGr is part of the German keyboard layout and part of key combinations like AltGr + e -> €
  1055. if (!selecting && (!current->is_ctrl_down || (current->is_ctrl_down && current->is_alt_down))) {
  1056. current->highlight_anchor = current->cursor_x;
  1057. }
  1058. }
  1059. if (editable && ui_text_to_paste[0] != '\0') { // Process cut copy paste
  1060. ui_remove_chars_at(text, current->highlight_anchor, current->cursor_x - current->highlight_anchor);
  1061. ui_insert_chars_at(text, current->highlight_anchor, ui_text_to_paste);
  1062. current->cursor_x += strlen(ui_text_to_paste);
  1063. current->highlight_anchor = current->cursor_x;
  1064. ui_text_to_paste[0] = 0;
  1065. ui_is_paste = false;
  1066. }
  1067. if (current->highlight_anchor == current->cursor_x) {
  1068. strcpy(ui_text_to_copy, text); // Copy
  1069. }
  1070. else if (current->highlight_anchor < current->cursor_x) {
  1071. int len = current->cursor_x - current->highlight_anchor;
  1072. strncpy(ui_text_to_copy, text + current->highlight_anchor, len);
  1073. ui_text_to_copy[len] = '\0';
  1074. }
  1075. else {
  1076. int len = current->highlight_anchor - current->cursor_x;
  1077. strncpy(ui_text_to_copy, text + current->cursor_x, len);
  1078. ui_text_to_copy[len] = '\0';
  1079. }
  1080. if (editable && ui_is_cut) { // Cut
  1081. if (current->highlight_anchor == current->cursor_x) {
  1082. text[0] = '\0';
  1083. }
  1084. else if (current->highlight_anchor < current->cursor_x) {
  1085. ui_remove_chars_at(text, current->highlight_anchor, current->cursor_x - current->highlight_anchor);
  1086. current->cursor_x = current->highlight_anchor;
  1087. }
  1088. else {
  1089. ui_remove_chars_at(text, current->cursor_x, current->highlight_anchor - current->cursor_x);
  1090. }
  1091. }
  1092. float off = UI_TEXT_OFFSET();
  1093. float line_height = UI_ELEMENT_H();
  1094. float cursor_height = line_height - current->button_offset_y * 3.0;
  1095. // Draw highlight
  1096. if (current->highlight_anchor != current->cursor_x) {
  1097. float istart = current->cursor_x;
  1098. float iend = current->highlight_anchor;
  1099. if (current->highlight_anchor < current->cursor_x) {
  1100. istart = current->highlight_anchor;
  1101. iend = current->cursor_x;
  1102. }
  1103. float hlstrw = draw_sub_string_width(current->ops->font, current->font_size, text, istart, iend);
  1104. float start_off = draw_sub_string_width(current->ops->font, current->font_size, text, 0, istart);
  1105. float hl_start = align == UI_ALIGN_LEFT ? current->_x + start_off + off : current->_x + current->_w - hlstrw - off;
  1106. if (align == UI_ALIGN_RIGHT) {
  1107. hl_start -= draw_sub_string_width(current->ops->font, current->font_size, text, iend, strlen(text));
  1108. }
  1109. draw_set_color(theme->ACCENT_COL);
  1110. draw_filled_rect(hl_start, current->_y + current->button_offset_y * 1.5, hlstrw, cursor_height);
  1111. }
  1112. // Draw cursor
  1113. int str_start = align == UI_ALIGN_LEFT ? 0 : current->cursor_x;
  1114. int str_length = align == UI_ALIGN_LEFT ? current->cursor_x : (strlen(text) - current->cursor_x);
  1115. float strw = draw_sub_string_width(current->ops->font, current->font_size, text, str_start, str_length);
  1116. float cursor_x = align == UI_ALIGN_LEFT ? current->_x + strw + off : current->_x + current->_w - strw - off;
  1117. draw_set_color(theme->TEXT_COL); // Cursor
  1118. draw_filled_rect(cursor_x, current->_y + current->button_offset_y * 1.5, 1.0 * UI_SCALE(), cursor_height);
  1119. strcpy(current->text_selected, text);
  1120. if (live_update && current->text_selected_handle != NULL) {
  1121. current->text_selected_handle->changed = strcmp(current->text_selected_handle->text, current->text_selected) != 0;
  1122. current->text_selected_handle->text = string_copy(current->text_selected);
  1123. }
  1124. }
  1125. void ui_set_hovered_tab_name(char *name) {
  1126. if (ui_input_in_rect(current->_window_x, current->_window_y, current->_window_w, current->_window_h)) {
  1127. strcpy(current->hovered_tab_name, name);
  1128. current->hovered_tab_x = current->_window_x;
  1129. current->hovered_tab_y = current->_window_y;
  1130. current->hovered_tab_w = current->_window_w;
  1131. current->hovered_tab_h = current->_window_h;
  1132. }
  1133. }
  1134. void ui_draw_tabs() {
  1135. current->input_x = current->restore_x;
  1136. current->input_y = current->restore_y;
  1137. if (current->current_window == NULL) {
  1138. return;
  1139. }
  1140. float tab_x = 0.0;
  1141. float tab_y = 0.0;
  1142. float tab_h_min = UI_BUTTON_H() * 1.1;
  1143. float header_h = current->current_window->drag_enabled ? UI_HEADER_DRAG_H() : 0;
  1144. float tab_h = (theme->FULL_TABS && current->tab_vertical) ? ((current->_window_h - header_h) / current->tab_count) : tab_h_min;
  1145. float orig_y = current->_y;
  1146. current->_y = header_h;
  1147. current->tab_handle->changed = false;
  1148. if (current->is_ctrl_down && current->is_tab_down) { // Next tab
  1149. current->tab_handle->position++;
  1150. if (current->tab_handle->position >= current->tab_count) {
  1151. current->tab_handle->position = 0;
  1152. }
  1153. current->tab_handle->changed = true;
  1154. current->is_tab_down = false;
  1155. }
  1156. if (current->tab_handle->position >= current->tab_count) {
  1157. current->tab_handle->position = current->tab_count - 1;
  1158. }
  1159. draw_set_color(theme->SEPARATOR_COL); // Tab background
  1160. if (current->tab_vertical) {
  1161. draw_filled_rect(0, current->_y, UI_ELEMENT_W(), current->_window_h);
  1162. }
  1163. else {
  1164. draw_filled_rect(0, current->_y, current->_window_w, current->button_offset_y + tab_h + 2);
  1165. }
  1166. draw_set_color(theme->BUTTON_COL); // Underline tab buttons
  1167. if (current->tab_vertical) {
  1168. draw_filled_rect(UI_ELEMENT_W(), current->_y, 1, current->_window_h);
  1169. }
  1170. else {
  1171. draw_filled_rect(current->button_offset_y, current->_y + current->button_offset_y + tab_h + 2, current->_window_w - current->button_offset_y * 2.0, 1);
  1172. }
  1173. float base_y = current->tab_vertical ? current->_y : current->_y + 2;
  1174. bool _enabled = current->enabled;
  1175. for (int i = 0; i < current->tab_count; ++i) {
  1176. current->enabled = current->tab_enabled[i];
  1177. current->_x = tab_x;
  1178. current->_y = base_y + tab_y;
  1179. current->_w = current->tab_vertical ? (UI_ELEMENT_W() - 1 * UI_SCALE()) :
  1180. theme->FULL_TABS ? (current->_window_w / current->tab_count) :
  1181. (draw_string_width(current->ops->font, current->font_size, current->tab_names[i]) + current->button_offset_y * 2.0 + 18.0 * UI_SCALE());
  1182. bool released = ui_get_released(tab_h);
  1183. bool started = ui_get_started(tab_h);
  1184. bool pushed = ui_get_pushed(tab_h);
  1185. bool hover = ui_get_hover(tab_h);
  1186. if (ui_on_tab_drop != NULL) {
  1187. if (started) {
  1188. current->drag_tab_handle = current->tab_handle;
  1189. current->drag_tab_position = i;
  1190. }
  1191. if (current->drag_tab_handle != NULL && hover && current->input_released) {
  1192. ui_on_tab_drop(current->tab_handle, i, current->drag_tab_handle, current->drag_tab_position);
  1193. current->tab_handle->position = i;
  1194. }
  1195. }
  1196. if (released) {
  1197. ui_handle_t *h = ui_nest(current->tab_handle, current->tab_handle->position); // Restore tab scroll
  1198. h->scroll_offset = current->current_window->scroll_offset;
  1199. h = ui_nest(current->tab_handle, i);
  1200. current->tab_scroll = h->scroll_offset;
  1201. current->tab_handle->position = i; // Set new tab
  1202. current->current_window->redraws = 3;
  1203. current->tab_handle->changed = true;
  1204. }
  1205. bool selected = current->tab_handle->position == i;
  1206. draw_set_color((pushed || hover) ? theme->HOVER_COL :
  1207. current->tab_colors[i] != -1 ? current->tab_colors[i] :
  1208. selected ? theme->WINDOW_BG_COL :
  1209. theme->SEPARATOR_COL);
  1210. if (current->tab_vertical) {
  1211. tab_y += tab_h + 1;
  1212. }
  1213. else {
  1214. tab_x += current->_w + 1;
  1215. }
  1216. // ui_draw_rect(true, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w, tab_h); // Round corners
  1217. draw_filled_rect(current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w, tab_h);
  1218. draw_set_color(theme->TEXT_COL);
  1219. if (!selected) {
  1220. ui_fade_color(0.65);
  1221. }
  1222. ui_draw_string(current->tab_names[i], theme->TEXT_OFFSET, (tab_h - tab_h_min) / 2.0, (theme->FULL_TABS || !current->tab_vertical) ? UI_ALIGN_CENTER : UI_ALIGN_LEFT, true);
  1223. if (selected) { // Hide underline for active tab
  1224. if (current->tab_vertical) {
  1225. // Hide underline
  1226. // draw_set_color(theme->WINDOW_BG_COL);
  1227. // draw_filled_rect(current->_x + current->button_offset_y + current->_w - 1, current->_y + current->button_offset_y - 1, 2, tab_h + current->button_offset_y);
  1228. // Highlight
  1229. draw_set_color(theme->HIGHLIGHT_COL);
  1230. draw_filled_rect(current->_x + current->button_offset_y, current->_y + current->button_offset_y - 1, 2, tab_h + current->button_offset_y);
  1231. }
  1232. else {
  1233. // Hide underline
  1234. draw_set_color(theme->WINDOW_BG_COL);
  1235. draw_filled_rect(current->_x + current->button_offset_y, current->_y + current->button_offset_y + tab_h, current->_w, 1);
  1236. // Highlight
  1237. draw_set_color(theme->HIGHLIGHT_COL);
  1238. draw_filled_rect(current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w, 2);
  1239. }
  1240. }
  1241. // Tab separator
  1242. if (i < current->tab_count - 1) {
  1243. int sep_col = theme->SEPARATOR_COL - 0x00050505;
  1244. if (sep_col < 0xff000000) {
  1245. sep_col = theme->SEPARATOR_COL + 0x00050505;
  1246. }
  1247. draw_set_color(sep_col);
  1248. if (current->tab_vertical) {
  1249. draw_filled_rect(current->_x, current->_y + tab_h, current->_w, 1);
  1250. }
  1251. else {
  1252. draw_filled_rect(current->_x + current->button_offset_y + current->_w, current->_y, 1, tab_h);
  1253. }
  1254. }
  1255. }
  1256. current->enabled = _enabled;
  1257. ui_set_hovered_tab_name(current->tab_names[current->tab_handle->position]);
  1258. current->_x = 0; // Restore positions
  1259. current->_y = orig_y;
  1260. current->_w = !current->current_window->scroll_enabled ? current->_window_w : current->_window_w - UI_SCROLL_W();
  1261. }
  1262. void ui_draw_arrow(bool selected) {
  1263. float x = current->_x + current->arrow_offset_x;
  1264. float y = current->_y + current->arrow_offset_y;
  1265. draw_set_color(theme->TEXT_COL);
  1266. if (selected) {
  1267. draw_filled_triangle(x, y,
  1268. x + UI_ARROW_SIZE(), y,
  1269. x + UI_ARROW_SIZE() / 2.0, y + UI_ARROW_SIZE());
  1270. }
  1271. else {
  1272. draw_filled_triangle(x, y,
  1273. x, y + UI_ARROW_SIZE(),
  1274. x + UI_ARROW_SIZE(), y + UI_ARROW_SIZE() / 2.0);
  1275. }
  1276. }
  1277. void ui_draw_tree(bool selected) {
  1278. float SIGN_W = 7.0 * UI_SCALE();
  1279. float x = current->_x + current->arrow_offset_x + 1;
  1280. float y = current->_y + current->arrow_offset_y + 1;
  1281. draw_set_color(theme->TEXT_COL);
  1282. if (selected) {
  1283. draw_filled_rect(x, y + SIGN_W / 2.0 - 1, SIGN_W, SIGN_W / 8.0);
  1284. }
  1285. else {
  1286. draw_filled_rect(x, y + SIGN_W / 2.0 - 1, SIGN_W, SIGN_W / 8.0);
  1287. draw_filled_rect(x + SIGN_W / 2.0 - 1, y, SIGN_W / 8.0, SIGN_W);
  1288. }
  1289. }
  1290. void ui_draw_check(bool selected, bool hover) {
  1291. float x = current->_x + current->check_offset_x;
  1292. float y = current->_y + current->check_offset_y;
  1293. draw_set_color(selected ? theme->HIGHLIGHT_COL : theme->PRESSED_COL);
  1294. ui_draw_rect(true, x, y, UI_CHECK_SIZE(), UI_CHECK_SIZE()); // Bg
  1295. draw_set_color(hover ? theme->HOVER_COL : theme->BUTTON_COL);
  1296. ui_draw_rect(false, x, y, UI_CHECK_SIZE(), UI_CHECK_SIZE()); // Bg
  1297. if (selected) { // Check
  1298. draw_set_color(hover ? theme->TEXT_COL : theme->LABEL_COL);
  1299. if (!current->enabled) {
  1300. ui_fade_color(0.25);
  1301. }
  1302. int size = UI_CHECK_SELECT_SIZE();
  1303. draw_scaled_image(&current->check_select_image, x + current->check_select_offset_x, y + current->check_select_offset_y, size, size);
  1304. }
  1305. }
  1306. void ui_draw_radio(bool selected, bool hover) {
  1307. float x = current->_x + current->radio_offset_x;
  1308. float y = current->_y + current->radio_offset_y;
  1309. draw_set_color(selected ? theme->HIGHLIGHT_COL : hover ? theme->HOVER_COL : theme->BUTTON_COL);
  1310. draw_image(&current->radio_image, x, y); // Circle bg
  1311. if (selected) { // Check
  1312. draw_set_color(theme->LABEL_COL);
  1313. if (!current->enabled) {
  1314. ui_fade_color(0.25);
  1315. }
  1316. draw_image(&current->radio_select_image, x + current->radio_select_offset_x, y + current->radio_select_offset_y); // Circle
  1317. }
  1318. }
  1319. void ui_draw_slider(float value, float from, float to, bool filled, bool hover) {
  1320. float x = current->_x + current->button_offset_y;
  1321. float y = current->_y + current->button_offset_y;
  1322. float w = current->_w - current->button_offset_y * 2.0;
  1323. draw_set_color(theme->PRESSED_COL);
  1324. ui_draw_rect(true, x, y, w, UI_BUTTON_H()); // Bg
  1325. if (hover) {
  1326. draw_set_color(theme->HOVER_COL);
  1327. ui_draw_rect(false, x, y, w, UI_BUTTON_H()); // Bg
  1328. }
  1329. draw_set_color(hover ? theme->HOVER_COL : theme->BUTTON_COL);
  1330. float offset = (value - from) / (to - from);
  1331. float bar_w = 8.0 * UI_SCALE(); // Unfilled bar
  1332. float slider_x = filled ? x : x + (w - bar_w) * offset;
  1333. slider_x = fmax(fmin(slider_x, x + (w - bar_w)), x);
  1334. float slider_w = filled ? w * offset : bar_w;
  1335. slider_w = fmax(fmin(slider_w, w), 0);
  1336. ui_draw_rect(true, slider_x, y, slider_w, UI_BUTTON_H());
  1337. }
  1338. void ui_set_scale(float factor) {
  1339. current->ops->scale_factor = factor;
  1340. current->font_size = UI_FONT_SIZE();
  1341. float font_height = draw_font_height(current->ops->font, current->font_size);
  1342. current->font_offset_y = (UI_ELEMENT_H() - font_height) / 2.0; // Precalculate offsets
  1343. current->arrow_offset_y = (UI_ELEMENT_H() - UI_ARROW_SIZE()) / 2.0;
  1344. current->arrow_offset_x = current->arrow_offset_y;
  1345. current->title_offset_x = (current->arrow_offset_x * 2.0 + UI_ARROW_SIZE()) / UI_SCALE();
  1346. current->button_offset_y = (UI_ELEMENT_H() - UI_BUTTON_H()) / 2.0;
  1347. current->check_offset_y = (UI_ELEMENT_H() - UI_CHECK_SIZE()) / 2.0;
  1348. current->check_offset_x = current->check_offset_y;
  1349. current->check_select_offset_y = (UI_CHECK_SIZE() - UI_CHECK_SELECT_SIZE()) / 2.0;
  1350. current->check_select_offset_x = current->check_select_offset_y;
  1351. current->radio_offset_y = (UI_ELEMENT_H() - UI_CHECK_SIZE()) / 2.0;
  1352. current->radio_offset_x = current->radio_offset_y;
  1353. current->radio_select_offset_y = (UI_CHECK_SIZE() - UI_CHECK_SELECT_SIZE()) / 2.0;
  1354. current->radio_select_offset_x = current->radio_select_offset_y;
  1355. current->elements_baked = false;
  1356. }
  1357. void ui_init(ui_t *ui, ui_options_t *ops) {
  1358. assert(ui_instances_count < UI_MAX_INSTANCES);
  1359. memset(ui, 0, sizeof(ui_t));
  1360. ui_instances[ui_instances_count++] = ui;
  1361. ui->ops = ops;
  1362. ui_set_current(ui);
  1363. ui_set_scale(ops->scale_factor);
  1364. current->enabled = true;
  1365. current->scroll_enabled = true;
  1366. current->highlight_on_select = true;
  1367. current->tab_switch_enabled = true;
  1368. current->input_enabled = true;
  1369. current->current_ratio = -1;
  1370. current->image_scroll_align = true;
  1371. current->window_ended = true;
  1372. current->restore_x = -1;
  1373. current->restore_y = -1;
  1374. if (ui_combo_search_handle == NULL) {
  1375. ui_combo_search_handle = ui_handle_create();
  1376. gc_root(ui_combo_search_handle);
  1377. }
  1378. if (_ui_row2 == NULL) {
  1379. _ui_row2 = f32_array_create_from_raw((float[]){1.0 / 2.0, 1.0 / 2.0}, 2);
  1380. _ui_row3 = f32_array_create_from_raw((float[]){1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0}, 3);
  1381. _ui_row4 = f32_array_create_from_raw((float[]){1.0 / 4.0, 1.0 / 4.0, 1.0 / 4.0, 1.0 / 4.0}, 4);
  1382. _ui_row5 = f32_array_create_from_raw((float[]){1.0 / 5.0, 1.0 / 5.0, 1.0 / 5.0, 1.0 / 5.0, 1.0 / 5.0}, 5);
  1383. _ui_row6 = f32_array_create_from_raw((float[]){1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0}, 6);
  1384. _ui_row7 = f32_array_create_from_raw((float[]){1.0 / 7.0, 1.0 / 7.0, 1.0 / 7.0, 1.0 / 7.0, 1.0 / 7.0, 1.0 / 7.0, 1.0 / 7.0}, 7);
  1385. gc_root(_ui_row2);
  1386. gc_root(_ui_row3);
  1387. gc_root(_ui_row4);
  1388. gc_root(_ui_row5);
  1389. gc_root(_ui_row6);
  1390. gc_root(_ui_row7);
  1391. }
  1392. }
  1393. void ui_begin(ui_t *ui) {
  1394. ui_set_current(ui);
  1395. if (!current->elements_baked) {
  1396. ui_bake_elements();
  1397. }
  1398. current->changed = false;
  1399. current->_x = 0; // Reset cursor
  1400. current->_y = 0;
  1401. current->_w = 0;
  1402. current->_h = 0;
  1403. }
  1404. // Sticky region ignores window scrolling
  1405. void ui_begin_sticky() {
  1406. current->sticky = true;
  1407. current->_y -= current->current_window->scroll_offset;
  1408. if (current->current_window->scroll_enabled) {
  1409. current->_w += UI_SCROLL_W(); // Use full width since there is no scroll bar in sticky region
  1410. }
  1411. }
  1412. void ui_end_sticky() {
  1413. current->sticky = false;
  1414. current->scissor = true;
  1415. gpu_scissor(0, current->_y, current->_window_w, current->_window_h - current->_y);
  1416. current->window_header_h += current->_y - current->window_header_h;
  1417. current->_y += current->current_window->scroll_offset;
  1418. current->is_hovered = false;
  1419. if (current->current_window->scroll_enabled) {
  1420. current->_w -= UI_SCROLL_W();
  1421. }
  1422. }
  1423. void ui_end_window() {
  1424. ui_handle_t *handle = current->current_window;
  1425. if (handle == NULL) return;
  1426. if (handle->redraws > 0 || current->is_scrolling) {
  1427. if (current->scissor) {
  1428. current->scissor = false;
  1429. gpu_disable_scissor();
  1430. }
  1431. if (current->tab_count > 0) {
  1432. ui_draw_tabs();
  1433. }
  1434. if (handle->drag_enabled) { // Draggable header
  1435. draw_set_color(theme->SEPARATOR_COL);
  1436. draw_filled_rect(0, 0, current->_window_w, UI_HEADER_DRAG_H());
  1437. }
  1438. float window_size = handle->layout == UI_LAYOUT_VERTICAL ? current->_window_h - current->window_header_h : current->_window_w - current->window_header_w; // Exclude header
  1439. float full_size = handle->layout == UI_LAYOUT_VERTICAL ? current->_y - current->window_header_h : current->_x - current->window_header_w;
  1440. full_size -= handle->scroll_offset;
  1441. if (full_size < window_size || !current->scroll_enabled) { // Disable scrollbar
  1442. handle->scroll_enabled = false;
  1443. handle->scroll_offset = 0;
  1444. }
  1445. else { // Draw window scrollbar if necessary
  1446. if (handle->layout == UI_LAYOUT_VERTICAL) {
  1447. handle->scroll_enabled = true;
  1448. }
  1449. if (current->tab_scroll < 0) { // Restore tab
  1450. handle->scroll_offset = current->tab_scroll;
  1451. current->tab_scroll = 0;
  1452. }
  1453. float wy = current->_window_y + current->window_header_h;
  1454. float amount_to_scroll = full_size - window_size;
  1455. float amount_scrolled = -handle->scroll_offset;
  1456. float ratio = amount_scrolled / amount_to_scroll;
  1457. float bar_h = window_size * fabs(window_size / full_size);
  1458. bar_h = fmax(bar_h, UI_ELEMENT_H());
  1459. float total_scrollable_area = window_size - bar_h;
  1460. float e = amount_to_scroll / total_scrollable_area;
  1461. float bar_y = total_scrollable_area * ratio + current->window_header_h;
  1462. bool bar_focus = ui_input_in_rect(current->_window_x + current->_window_w - UI_SCROLL_W(), bar_y + current->_window_y, UI_SCROLL_W(), bar_h);
  1463. if (handle->scroll_enabled && current->input_started && bar_focus) { // Start scrolling
  1464. current->scroll_handle = handle;
  1465. current->is_scrolling = true;
  1466. }
  1467. float scroll_delta = current->input_wheel_delta;
  1468. if (handle->scroll_enabled && ui_touch_scroll && current->input_down && current->input_dy != 0 && current->input_x > current->_window_x + current->window_header_w && current->input_y > current->_window_y + current->window_header_h) {
  1469. current->is_scrolling = true;
  1470. scroll_delta = -current->input_dy / 20.0;
  1471. }
  1472. if (handle == current->scroll_handle) { // Scroll
  1473. ui_scroll(current->input_dy * e);
  1474. }
  1475. else if (scroll_delta != 0 && current->combo_selected_handle == NULL && ui_input_in_rect(current->_window_x, wy, current->_window_w, window_size)) { // Wheel
  1476. ui_scroll(scroll_delta * UI_ELEMENT_H());
  1477. }
  1478. // Stay in bounds
  1479. if (handle->scroll_offset > 0) {
  1480. handle->scroll_offset = 0;
  1481. }
  1482. else if (full_size + handle->scroll_offset < window_size) {
  1483. handle->scroll_offset = window_size - full_size;
  1484. }
  1485. if (handle->layout == UI_LAYOUT_VERTICAL) {
  1486. draw_set_color(theme->BUTTON_COL); // Bar
  1487. bool scrollbar_focus = ui_input_in_rect(current->_window_x + current->_window_w - UI_SCROLL_W(), wy, UI_SCROLL_W(), window_size);
  1488. float bar_w = (scrollbar_focus || handle == current->scroll_handle) ? UI_SCROLL_W() : UI_SCROLL_MINI_W();
  1489. ui_draw_rect(true, current->_window_w - bar_w - current->scroll_align, bar_y, bar_w, bar_h);
  1490. }
  1491. }
  1492. handle->last_max_x = current->_x;
  1493. handle->last_max_y = current->_y;
  1494. if (handle->layout == UI_LAYOUT_VERTICAL) {
  1495. handle->last_max_x += current->_window_w;
  1496. }
  1497. else {
  1498. handle->last_max_y += current->_window_h;
  1499. }
  1500. handle->redraws--;
  1501. draw_end();
  1502. }
  1503. current->window_ended = true;
  1504. // Draw window texture
  1505. draw_begin(NULL, false, 0);
  1506. draw_set_color(0xffffffff);
  1507. draw_image(&handle->texture, current->_window_x, current->_window_y);
  1508. if (handle->redraws <= 0) {
  1509. handle->redraws--;
  1510. }
  1511. draw_end();
  1512. }
  1513. bool ui_window_dirty(ui_handle_t *handle, int x, int y, int w, int h) {
  1514. float wx = x + handle->drag_x;
  1515. float wy = y + handle->drag_y;
  1516. float input_changed = ui_input_in_rect(wx, wy, w, h) && ui_input_changed();
  1517. return current->always_redraw || current->is_scrolling || input_changed;
  1518. }
  1519. bool ui_window(ui_handle_t *handle, int x, int y, int w, int h, bool drag) {
  1520. if (handle->texture.width == 0 || w != handle->texture.width || h != handle->texture.height) {
  1521. ui_resize(handle, w, h);
  1522. }
  1523. if (!current->window_ended) {
  1524. ui_end_window(); // End previous window if necessary
  1525. }
  1526. current->window_ended = false;
  1527. current->current_window = handle;
  1528. current->_window_x = x + handle->drag_x;
  1529. current->_window_y = y + handle->drag_y;
  1530. current->_window_w = w;
  1531. current->_window_h = h;
  1532. current->window_header_w = 0;
  1533. current->window_header_h = 0;
  1534. if (ui_window_dirty(handle, x, y, w, h)) {
  1535. handle->redraws = 2;
  1536. }
  1537. if (ui_on_border_hover != NULL) {
  1538. if (ui_input_in_rect(current->_window_x - 4, current->_window_y, 8, current->_window_h)) {
  1539. ui_on_border_hover(handle, 0);
  1540. }
  1541. else if (ui_input_in_rect(current->_window_x + current->_window_w - 4, current->_window_y, 8, current->_window_h)) {
  1542. ui_on_border_hover(handle, 1);
  1543. }
  1544. else if (ui_input_in_rect(current->_window_x, current->_window_y - 4, current->_window_w, 8)) {
  1545. ui_on_border_hover(handle, 2);
  1546. }
  1547. else if (ui_input_in_rect(current->_window_x, current->_window_y + current->_window_h - 4, current->_window_w, 8)) {
  1548. ui_on_border_hover(handle, 3);
  1549. }
  1550. }
  1551. if (handle->redraws <= 0) {
  1552. return false;
  1553. }
  1554. if (handle->layout == UI_LAYOUT_VERTICAL) {
  1555. current->_x = 0;
  1556. current->_y = handle->scroll_offset;
  1557. }
  1558. else {
  1559. current->_x = handle->scroll_offset;
  1560. current->_y = 0;
  1561. }
  1562. if (handle->layout == UI_LAYOUT_HORIZONTAL) w = UI_ELEMENT_W();
  1563. current->_w = !handle->scroll_enabled ? w : w - UI_SCROLL_W(); // Exclude scrollbar if present
  1564. current->_h = h;
  1565. current->tooltip_text[0] = 0;
  1566. current->tooltip_img = NULL;
  1567. current->tab_count = 0;
  1568. if (theme->FILL_WINDOW_BG) {
  1569. draw_begin(&handle->texture, true, theme->WINDOW_BG_COL);
  1570. }
  1571. else {
  1572. draw_begin(&handle->texture, true, 0x00000000);
  1573. draw_set_color(theme->WINDOW_BG_COL);
  1574. draw_filled_rect(current->_x, current->_y - handle->scroll_offset, handle->last_max_x, handle->last_max_y);
  1575. }
  1576. handle->drag_enabled = drag;
  1577. if (drag) {
  1578. if (current->input_started && ui_input_in_rect(current->_window_x, current->_window_y, current->_window_w, UI_HEADER_DRAG_H())) {
  1579. current->drag_handle = handle;
  1580. }
  1581. else if (current->input_released) {
  1582. current->drag_handle = NULL;
  1583. }
  1584. if (handle == current->drag_handle) {
  1585. handle->redraws = 2;
  1586. handle->drag_x += current->input_dx;
  1587. handle->drag_y += current->input_dy;
  1588. }
  1589. current->_y += UI_HEADER_DRAG_H(); // Header offset
  1590. current->window_header_h += UI_HEADER_DRAG_H();
  1591. }
  1592. return true;
  1593. }
  1594. bool ui_button(char *text, int align, char *label/*, gpu_texture_t *icon, int sx, int sy, int sw, int sh*/) {
  1595. if (!ui_is_visible(UI_ELEMENT_H())) {
  1596. ui_end_element();
  1597. return false;
  1598. }
  1599. bool released = ui_get_released(UI_ELEMENT_H());
  1600. bool pushed = ui_get_pushed(UI_ELEMENT_H());
  1601. bool hover = ui_get_hover(UI_ELEMENT_H());
  1602. if (released) {
  1603. current->changed = true;
  1604. }
  1605. draw_set_color(pushed ? theme->PRESSED_COL :
  1606. (!theme->FILL_BUTTON_BG && hover) ? theme->HIGHLIGHT_COL :
  1607. hover ? theme->HOVER_COL :
  1608. theme->BUTTON_COL);
  1609. if (theme->FILL_BUTTON_BG || pushed || hover) {
  1610. ui_draw_rect(true, current->_x + current->button_offset_y, current->_y + current->button_offset_y,
  1611. current->_w - current->button_offset_y * 2, UI_BUTTON_H());
  1612. }
  1613. draw_set_color(theme->TEXT_COL);
  1614. ui_draw_string(text, theme->TEXT_OFFSET, 0, align, true);
  1615. if (label != NULL) {
  1616. draw_set_color(theme->LABEL_COL);
  1617. ui_draw_string(label, theme->TEXT_OFFSET, 0, align == UI_ALIGN_RIGHT ? UI_ALIGN_LEFT : UI_ALIGN_RIGHT, true);
  1618. }
  1619. /*
  1620. if (icon != NULL) {
  1621. draw_set_color(0xffffffff);
  1622. if (current->image_invert_y) {
  1623. draw_scaled_sub_image(icon, sx, sy, sw, sh, _x + current->button_offset_y, _y - 1 + sh, sw, -sh);
  1624. }
  1625. else {
  1626. draw_scaled_sub_image(icon, sx, sy, sw, sh, _x + current->button_offset_y, _y - 1, sw, sh);
  1627. }
  1628. }
  1629. */
  1630. ui_end_element();
  1631. return released;
  1632. }
  1633. void ui_split_text(char *lines, int align, int bg) {
  1634. int count = ui_line_count(lines);
  1635. for (int i = 0; i < count; ++i) {
  1636. ui_text(ui_extract_line(lines, i), align, bg);
  1637. }
  1638. }
  1639. int ui_text(char *text, int align, int bg) {
  1640. if (ui_line_count(text) > 1) {
  1641. ui_split_text(text, align, bg);
  1642. return UI_STATE_IDLE;
  1643. }
  1644. float h = fmax(UI_ELEMENT_H(), draw_font_height(current->ops->font, current->font_size));
  1645. if (!ui_is_visible(h)) {
  1646. ui_end_element_of_size(h + UI_ELEMENT_OFFSET());
  1647. return UI_STATE_IDLE;
  1648. }
  1649. bool started = ui_get_started(h);
  1650. bool down = ui_get_pushed(h);
  1651. bool released = ui_get_released(h);
  1652. bool hover = ui_get_hover(h);
  1653. if (bg != 0x0000000) {
  1654. draw_set_color(bg);
  1655. draw_filled_rect(current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, UI_BUTTON_H());
  1656. }
  1657. draw_set_color(theme->TEXT_COL);
  1658. ui_draw_string(text, theme->TEXT_OFFSET, 0, align, true);
  1659. ui_end_element_of_size(h + UI_ELEMENT_OFFSET());
  1660. return started ? UI_STATE_STARTED : released ? UI_STATE_RELEASED : down ? UI_STATE_DOWN : UI_STATE_IDLE;
  1661. }
  1662. bool ui_tab(ui_handle_t *handle, char *text, bool vertical, uint32_t color) {
  1663. if (current->tab_count == 0) { // First tab
  1664. current->tab_handle = handle;
  1665. current->tab_vertical = vertical;
  1666. current->_w -= current->tab_vertical ? UI_ELEMENT_OFFSET() + UI_ELEMENT_W() - 1 * UI_SCALE() : 0; // Shrink window area by width of vertical tabs
  1667. if (vertical) {
  1668. current->window_header_w += UI_ELEMENT_W();
  1669. }
  1670. else {
  1671. current->window_header_h += UI_BUTTON_H() + current->button_offset_y + UI_ELEMENT_OFFSET();
  1672. }
  1673. current->restore_x = current->input_x; // Mouse in tab header, disable clicks for tab content
  1674. current->restore_y = current->input_y;
  1675. if (!vertical && ui_input_in_rect(current->_window_x, current->_window_y, current->_window_w, current->window_header_h)) {
  1676. current->input_x = current->input_y = -1;
  1677. }
  1678. if (vertical) {
  1679. current->_x += current->window_header_w + 6;
  1680. current->_w -= 6;
  1681. }
  1682. else {
  1683. current->_y += current->window_header_h + 3;
  1684. }
  1685. }
  1686. assert(current->tab_count < 16);
  1687. strcpy(current->tab_names[current->tab_count], text);
  1688. current->tab_colors[current->tab_count] = color;
  1689. current->tab_enabled[current->tab_count] = current->enabled;
  1690. current->tab_count++;
  1691. return handle->position == current->tab_count - 1;
  1692. }
  1693. bool ui_panel(ui_handle_t *handle, char *text, bool is_tree, bool filled) {
  1694. if (!ui_is_visible(UI_ELEMENT_H())) {
  1695. ui_end_element();
  1696. return handle->selected;
  1697. }
  1698. if (ui_get_released(UI_ELEMENT_H())) {
  1699. handle->selected = !handle->selected;
  1700. handle->changed = current->changed = true;
  1701. }
  1702. if (is_tree) {
  1703. ui_draw_tree(handle->selected);
  1704. }
  1705. else {
  1706. ui_draw_arrow(handle->selected);
  1707. }
  1708. draw_set_color(theme->LABEL_COL); // Title
  1709. ui_draw_string(text, current->title_offset_x, 0, UI_ALIGN_LEFT, true);
  1710. ui_end_element();
  1711. return handle->selected;
  1712. }
  1713. static int image_width(void *image) {
  1714. return ((gpu_texture_t *)image)->width;
  1715. }
  1716. static int image_height(void *image) {
  1717. return ((gpu_texture_t *)image)->height;
  1718. }
  1719. int ui_sub_image(gpu_texture_t *image, uint32_t tint, int h, int sx, int sy, int sw, int sh) {
  1720. float iw = (sw > 0 ? sw : image_width(image)) * UI_SCALE();
  1721. float ih = (sh > 0 ? sh : image_height(image)) * UI_SCALE();
  1722. float w = fmin(iw, current->_w);
  1723. float x = current->_x;
  1724. float scroll = current->current_window != NULL ? current->current_window->scroll_enabled : false;
  1725. float r = current->current_ratio == -1 ? 1.0 : ui_get_ratio(current->ratios->buffer[current->current_ratio], 1);
  1726. if (current->image_scroll_align) { // Account for scrollbar size
  1727. w = fmin(iw, current->_w - current->button_offset_y * 2.0);
  1728. x += current->button_offset_y;
  1729. if (!scroll) {
  1730. w -= UI_SCROLL_W() * r;
  1731. x += UI_SCROLL_W() * r / 2.0;
  1732. }
  1733. }
  1734. else if (scroll) {
  1735. w += UI_SCROLL_W() * r;
  1736. }
  1737. // Image size
  1738. float ratio = h == -1 ?
  1739. w / iw :
  1740. h / ih;
  1741. if (h == -1) {
  1742. h = ih * ratio;
  1743. }
  1744. else {
  1745. w = iw * ratio;
  1746. }
  1747. if (!ui_is_visible(h)) {
  1748. ui_end_element_of_size(h);
  1749. return UI_STATE_IDLE;
  1750. }
  1751. bool started = ui_get_started(h);
  1752. bool down = ui_get_pushed(h);
  1753. bool released = ui_get_released(h);
  1754. bool hover = ui_get_hover(h);
  1755. // Limit input to image width
  1756. // if (current->current_ratio == -1 && (started || down || released || hover)) {
  1757. // if (current->input_x < current->_window_x + current->_x || current->input_x > current->_window_x + current->_x + w) {
  1758. // started = down = released = hover = false;
  1759. // }
  1760. // }
  1761. draw_set_color(tint);
  1762. if (!current->enabled) {
  1763. ui_fade_color(0.25);
  1764. }
  1765. if (sw > 0) { // Source rect specified
  1766. if (current->image_invert_y) {
  1767. draw_scaled_sub_image(image, sx, sy, sw, sh, x, current->_y + h, w, -h);
  1768. }
  1769. else {
  1770. draw_scaled_sub_image(image, sx, sy, sw, sh, x, current->_y, w, h);
  1771. }
  1772. }
  1773. else {
  1774. if (current->image_invert_y) {
  1775. draw_scaled_image(image, x, current->_y + h, w, -h);
  1776. }
  1777. else {
  1778. draw_scaled_image(image, x, current->_y, w, h);
  1779. }
  1780. }
  1781. ui_end_element_of_size(h);
  1782. return started ? UI_STATE_STARTED : released ? UI_STATE_RELEASED : down ? UI_STATE_DOWN : hover ? UI_STATE_HOVERED : UI_STATE_IDLE;
  1783. }
  1784. int ui_image(gpu_texture_t *image, uint32_t tint, int h) {
  1785. return ui_sub_image(image, tint, h, 0, 0, image_width(image), image_height(image));
  1786. }
  1787. char *ui_text_input(ui_handle_t *handle, char *label, int align, bool editable, bool live_update) {
  1788. if (!ui_is_visible(UI_ELEMENT_H())) {
  1789. ui_end_element();
  1790. return handle->text;
  1791. }
  1792. bool hover = ui_get_hover(UI_ELEMENT_H());
  1793. if (hover && ui_on_text_hover != NULL) {
  1794. ui_on_text_hover();
  1795. }
  1796. draw_set_color(hover ? theme->HOVER_COL : theme->BUTTON_COL); // Text bg
  1797. ui_draw_rect(false, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, UI_BUTTON_H());
  1798. bool released = ui_get_released(UI_ELEMENT_H());
  1799. if (current->submit_text_handle == handle && released) { // Keep editing selected text
  1800. current->is_typing = true;
  1801. current->text_selected_handle = current->submit_text_handle;
  1802. current->submit_text_handle = NULL;
  1803. ui_set_cursor_to_input(align);
  1804. }
  1805. bool start_edit = released || current->tab_pressed;
  1806. handle->changed = false;
  1807. if (current->text_selected_handle != handle && start_edit) {
  1808. ui_start_text_edit(handle, align);
  1809. }
  1810. if (current->text_selected_handle == handle) {
  1811. ui_update_text_edit(align, editable, live_update);
  1812. if (current->current_window != NULL) {
  1813. current->current_window->redraws = 2; // Keep redrawing window while typing
  1814. }
  1815. }
  1816. if (current->submit_text_handle == handle) {
  1817. ui_submit_text_edit();
  1818. }
  1819. if (label[0] != '\0') {
  1820. draw_set_color(theme->LABEL_COL); // Label
  1821. int label_align = align == UI_ALIGN_RIGHT ? UI_ALIGN_LEFT : UI_ALIGN_RIGHT;
  1822. ui_draw_string(label, label_align == UI_ALIGN_LEFT ? theme->TEXT_OFFSET : 0, 0, label_align, true);
  1823. }
  1824. draw_set_color(theme->TEXT_COL); // Text
  1825. if (current->text_selected_handle != handle) {
  1826. ui_draw_string(handle->text, theme->TEXT_OFFSET, 0, align, true);
  1827. }
  1828. else {
  1829. ui_draw_string(current->text_selected, theme->TEXT_OFFSET, 0, align, false);
  1830. }
  1831. ui_end_element();
  1832. return handle->text;
  1833. }
  1834. bool ui_check(ui_handle_t *handle, char *text, char *label) {
  1835. if (!ui_is_visible(UI_ELEMENT_H())) {
  1836. ui_end_element();
  1837. return handle->selected;
  1838. }
  1839. if (ui_get_released(UI_ELEMENT_H())) {
  1840. handle->selected = !handle->selected;
  1841. handle->changed = current->changed = true;
  1842. }
  1843. else handle->changed = false;
  1844. bool hover = ui_get_hover(UI_ELEMENT_H());
  1845. ui_draw_check(handle->selected, hover); // Check
  1846. draw_set_color(theme->TEXT_COL); // Text
  1847. ui_draw_string(text, current->title_offset_x, 0, UI_ALIGN_LEFT, true);
  1848. if (label[0] != '\0') {
  1849. draw_set_color(theme->LABEL_COL);
  1850. ui_draw_string(label, theme->TEXT_OFFSET, 0, UI_ALIGN_RIGHT, true);
  1851. }
  1852. ui_end_element();
  1853. return handle->selected;
  1854. }
  1855. bool ui_radio(ui_handle_t *handle, int position, char *text, char *label) {
  1856. if (!ui_is_visible(UI_ELEMENT_H())) {
  1857. ui_end_element();
  1858. return handle->position == position;
  1859. }
  1860. if (position == 0) {
  1861. handle->changed = false;
  1862. }
  1863. if (ui_get_released(UI_ELEMENT_H())) {
  1864. handle->position = position;
  1865. handle->changed = current->changed = true;
  1866. }
  1867. bool hover = ui_get_hover(UI_ELEMENT_H());
  1868. ui_draw_radio(handle->position == position, hover); // Radio
  1869. draw_set_color(theme->TEXT_COL); // Text
  1870. ui_draw_string(text, current->title_offset_x, 0, UI_ALIGN_LEFT, true);
  1871. if (label[0] != '\0') {
  1872. draw_set_color(theme->LABEL_COL);
  1873. ui_draw_string(label, theme->TEXT_OFFSET, 0, UI_ALIGN_RIGHT, true);
  1874. }
  1875. ui_end_element();
  1876. return handle->position == position;
  1877. }
  1878. int ui_combo(ui_handle_t *handle, char_ptr_array_t *texts, char *label, bool show_label, int align, bool search_bar) {
  1879. if (!ui_is_visible(UI_ELEMENT_H())) {
  1880. ui_end_element();
  1881. return handle->position;
  1882. }
  1883. if (ui_get_released(UI_ELEMENT_H())) {
  1884. if (current->combo_selected_handle == NULL) {
  1885. current->input_enabled = false;
  1886. current->combo_selected_handle = handle;
  1887. current->combo_selected_window = current->current_window;
  1888. current->combo_selected_align = align;
  1889. current->combo_selected_texts = texts;
  1890. current->combo_selected_label = (char *)label;
  1891. current->combo_selected_x = current->_x + current->_window_x;
  1892. current->combo_selected_y = current->_y + current->_window_y + UI_ELEMENT_H();
  1893. current->combo_selected_w = current->_w;
  1894. current->combo_search_bar = search_bar;
  1895. for (int i = 0; i < texts->length; ++i) { // Adapt combo list width to combo item width
  1896. int w = (int)draw_string_width(current->ops->font, current->font_size, texts->buffer[i]) + 10;
  1897. if (current->combo_selected_w < w) {
  1898. current->combo_selected_w = w;
  1899. }
  1900. }
  1901. if (current->combo_selected_w > current->_w * 2.0) {
  1902. current->combo_selected_w = current->_w * 2.0;
  1903. }
  1904. if (current->combo_selected_w > current->_w) {
  1905. current->combo_selected_w += UI_TEXT_OFFSET();
  1906. }
  1907. current->combo_to_submit = handle->position;
  1908. current->combo_initial_value = handle->position;
  1909. }
  1910. }
  1911. if (handle == current->combo_selected_handle && (current->is_escape_down || current->input_released_r)) {
  1912. handle->position = current->combo_initial_value;
  1913. handle->changed = current->changed = true;
  1914. current->submit_combo_handle = NULL;
  1915. }
  1916. else if (handle == current->submit_combo_handle) {
  1917. handle->position = current->combo_to_submit;
  1918. current->submit_combo_handle = NULL;
  1919. handle->changed = current->changed = true;
  1920. }
  1921. else {
  1922. handle->changed = false;
  1923. }
  1924. draw_set_color(theme->PRESSED_COL); // Bg
  1925. ui_draw_rect(true, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, UI_BUTTON_H());
  1926. bool hover = ui_get_hover(UI_ELEMENT_H());
  1927. draw_set_color(hover ? theme->HOVER_COL : theme->BUTTON_COL);
  1928. ui_draw_rect(false, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, UI_BUTTON_H());
  1929. int x = current->_x + current->_w - current->arrow_offset_x - 8;
  1930. int y = current->_y + current->arrow_offset_y + 3;
  1931. draw_set_color(theme->HOVER_COL);
  1932. // if (handle == current->combo_selected_handle) {
  1933. // // Flip arrow when combo is open
  1934. // draw_filled_triangle(x, y, x + UI_ARROW_SIZE(), y, x + UI_ARROW_SIZE() / 2.0, y - UI_ARROW_SIZE() / 2.0);
  1935. // }
  1936. // else {
  1937. draw_filled_triangle(x, y, x + UI_ARROW_SIZE(), y, x + UI_ARROW_SIZE() / 2.0, y + UI_ARROW_SIZE() / 2.0);
  1938. // }
  1939. if (show_label && label[0] != '\0') {
  1940. if (align == UI_ALIGN_LEFT) {
  1941. current->_x -= 15;
  1942. }
  1943. draw_set_color(theme->LABEL_COL);
  1944. ui_draw_string(label, theme->TEXT_OFFSET, 0, align == UI_ALIGN_LEFT ? UI_ALIGN_RIGHT : UI_ALIGN_LEFT, true);
  1945. if (align == UI_ALIGN_LEFT) {
  1946. current->_x += 15;
  1947. }
  1948. }
  1949. if (align == UI_ALIGN_RIGHT) {
  1950. current->_x -= 15;
  1951. }
  1952. draw_set_color(theme->TEXT_COL); // Value
  1953. if (handle->position < texts->length) {
  1954. ui_draw_string(texts->buffer[handle->position], theme->TEXT_OFFSET, 0, align, true);
  1955. }
  1956. if (align == UI_ALIGN_RIGHT) {
  1957. current->_x += 15;
  1958. }
  1959. ui_end_element();
  1960. return handle->position;
  1961. }
  1962. float ui_slider(ui_handle_t *handle, char *text, float from, float to, bool filled, float precision, bool display_value, int align, bool text_edit) {
  1963. static char temp[1024];
  1964. if (!ui_is_visible(UI_ELEMENT_H())) {
  1965. ui_end_element();
  1966. return handle->value;
  1967. }
  1968. if (ui_get_started(UI_ELEMENT_H())) {
  1969. current->scroll_handle = handle;
  1970. current->is_scrolling = true;
  1971. current->changed = handle->changed = true;
  1972. if (ui_touch_tooltip) {
  1973. current->slider_tooltip = true;
  1974. current->slider_tooltip_x = current->_x + current->_window_x;
  1975. current->slider_tooltip_y = current->_y + current->_window_y;
  1976. current->slider_tooltip_w = current->_w;
  1977. }
  1978. }
  1979. else {
  1980. handle->changed = false;
  1981. }
  1982. #if !defined(IRON_ANDROID) && !defined(IRON_IOS)
  1983. if (handle == current->scroll_handle && current->input_dx != 0) { // Scroll
  1984. #else
  1985. if (handle == current->scroll_handle) { // Scroll
  1986. #endif
  1987. float range = to - from;
  1988. float slider_x = current->_x + current->_window_x + current->button_offset_y;
  1989. float slider_w = current->_w - current->button_offset_y * 2;
  1990. float step = range / slider_w;
  1991. float value = from + (current->input_x - slider_x) * step;
  1992. handle->value = round(value * precision) / precision;
  1993. if (handle->value < from) {
  1994. handle->value = from; // Stay in bounds
  1995. }
  1996. else if (handle->value > to) {
  1997. handle->value = to;
  1998. }
  1999. handle->changed = current->changed = true;
  2000. }
  2001. bool hover = ui_get_hover(UI_ELEMENT_H());
  2002. ui_draw_slider(handle->value, from, to, filled, hover); // Slider
  2003. // Text edit
  2004. bool start_edit = (ui_get_released(UI_ELEMENT_H()) || current->tab_pressed) && text_edit;
  2005. if (start_edit) { // Mouse did not move
  2006. char tmp[256];
  2007. sprintf(tmp, "%.2f", handle->value);
  2008. handle->text = string_copy(tmp);
  2009. string_strip_trailing_zeros(handle->text);
  2010. ui_start_text_edit(handle, UI_ALIGN_LEFT);
  2011. handle->changed = current->changed = true;
  2012. }
  2013. int lalign = align == UI_ALIGN_LEFT ? UI_ALIGN_RIGHT : UI_ALIGN_LEFT;
  2014. if (current->text_selected_handle == handle) {
  2015. ui_update_text_edit(lalign, true, false);
  2016. }
  2017. if (current->submit_text_handle == handle) {
  2018. ui_submit_text_edit();
  2019. #ifdef WITH_EVAL
  2020. handle->value = js_eval(handle->text);
  2021. #else
  2022. handle->value = atof(handle->text);
  2023. #endif
  2024. handle->changed = current->changed = true;
  2025. }
  2026. draw_set_color(theme->LABEL_COL); // Text
  2027. ui_draw_string(text, theme->TEXT_OFFSET, 0, align, true);
  2028. if (display_value) {
  2029. draw_set_color(theme->TEXT_COL); // Value
  2030. if (current->text_selected_handle != handle) {
  2031. sprintf(temp, "%.2f", round(handle->value * precision) / precision);
  2032. string_strip_trailing_zeros(temp);
  2033. ui_draw_string(temp, theme->TEXT_OFFSET, 0, lalign, true);
  2034. }
  2035. else {
  2036. ui_draw_string(current->text_selected, theme->TEXT_OFFSET, 0, lalign, true);
  2037. }
  2038. }
  2039. ui_end_element();
  2040. return handle->value;
  2041. }
  2042. void ui_separator(int h, bool fill) {
  2043. if (!ui_is_visible(UI_ELEMENT_H())) {
  2044. current->_y += h * UI_SCALE();
  2045. return;
  2046. }
  2047. if (fill) {
  2048. draw_set_color(theme->SEPARATOR_COL);
  2049. draw_filled_rect(current->_x, current->_y, current->_w, h * UI_SCALE());
  2050. }
  2051. current->_y += h * UI_SCALE();
  2052. }
  2053. void ui_tooltip(char *text) {
  2054. assert(strlen(text) < 512);
  2055. strcpy(current->tooltip_text, text);
  2056. current->tooltip_y = current->_y + current->_window_y;
  2057. }
  2058. void ui_tooltip_image(gpu_texture_t *image, int max_width) {
  2059. current->tooltip_img = image;
  2060. current->tooltip_img_max_width = max_width;
  2061. current->tooltip_invert_y = current->image_invert_y;
  2062. current->tooltip_y = current->_y + current->_window_y;
  2063. }
  2064. void ui_end(bool last) {
  2065. if (!current->window_ended) {
  2066. ui_end_window();
  2067. }
  2068. ui_draw_combo(true); // Handle active combo
  2069. ui_draw_tooltip(true);
  2070. current->tab_pressed_handle = NULL;
  2071. if (last) {
  2072. ui_end_input();
  2073. }
  2074. }
  2075. void ui_set_input_position(ui_t *ui, int x, int y) {
  2076. ui->input_dx += x - ui->input_x;
  2077. ui->input_dy += y - ui->input_y;
  2078. ui->input_x = x;
  2079. ui->input_y = y;
  2080. }
  2081. // Useful for drag and drop operations
  2082. char *ui_hovered_tab_name() {
  2083. return ui_input_in_rect(current->hovered_tab_x, current->hovered_tab_y, current->hovered_tab_w, current->hovered_tab_h) ? current->hovered_tab_name : "";
  2084. }
  2085. void ui_mouse_down(ui_t *ui, int button, int x, int y) {
  2086. if (ui->pen_in_use) {
  2087. return;
  2088. }
  2089. if (button == 0) {
  2090. ui->input_started = ui->input_down = true;
  2091. }
  2092. else {
  2093. ui->input_started_r = ui->input_down_r = true;
  2094. }
  2095. ui->input_started_time = iron_time();
  2096. #if defined(IRON_ANDROID) || defined(IRON_IOS)
  2097. ui_set_input_position(ui, x, y);
  2098. #endif
  2099. ui->input_started_x = x;
  2100. ui->input_started_y = y;
  2101. }
  2102. void ui_mouse_move(ui_t *ui, int x, int y, int movement_x, int movement_y) {
  2103. #if !defined(IRON_ANDROID) && !defined(IRON_IOS)
  2104. ui_set_input_position(ui, x, y);
  2105. #endif
  2106. }
  2107. void ui_mouse_up(ui_t *ui, int button, int x, int y) {
  2108. if (ui->pen_in_use) {
  2109. return;
  2110. }
  2111. if (ui->touch_hold_activated) {
  2112. ui->touch_hold_activated = false;
  2113. return;
  2114. }
  2115. if (ui->is_scrolling) { // Prevent action when scrolling is active
  2116. ui->is_scrolling = false;
  2117. ui->scroll_handle = NULL;
  2118. ui->slider_tooltip = false;
  2119. if (x == ui->input_started_x && y == ui->input_started_y) { // Mouse not moved
  2120. if (button == 0) ui->input_released = true;
  2121. else ui->input_released_r = true;
  2122. }
  2123. }
  2124. else {
  2125. if (button == 0) {
  2126. ui->input_released = true;
  2127. }
  2128. else {
  2129. ui->input_released_r = true;
  2130. }
  2131. }
  2132. if (button == 0) {
  2133. ui->input_down = false;
  2134. }
  2135. else {
  2136. ui->input_down_r = false;
  2137. }
  2138. #if defined(IRON_ANDROID) || defined(IRON_IOS)
  2139. ui_set_input_position(ui, x, y);
  2140. #endif
  2141. ui_deselect_text(ui);
  2142. }
  2143. void ui_mouse_wheel(ui_t *ui, int delta) {
  2144. ui->input_wheel_delta = delta;
  2145. }
  2146. void ui_pen_down(ui_t *ui, int x, int y, float pressure) {
  2147. #if defined(IRON_ANDROID) || defined(IRON_IOS)
  2148. return;
  2149. #endif
  2150. ui_mouse_down(ui, 0, x, y);
  2151. }
  2152. void ui_pen_up(ui_t *ui, int x, int y, float pressure) {
  2153. #if defined(IRON_ANDROID) || defined(IRON_IOS)
  2154. return;
  2155. #endif
  2156. if (ui->input_started) {
  2157. ui->input_started = false;
  2158. ui->pen_in_use = true;
  2159. return;
  2160. }
  2161. ui_mouse_up(ui, 0, x, y);
  2162. ui->pen_in_use = true; // On pen release, additional mouse down & up events are fired at once - filter those out
  2163. }
  2164. void ui_pen_move(ui_t *ui, int x, int y, float pressure) {
  2165. #if defined(IRON_IOS)
  2166. // Listen to pen hover if no other input is active
  2167. if (pressure == 0.0) {
  2168. if (!ui->input_down && !ui->input_down_r) {
  2169. ui_set_input_position(ui, x, y);
  2170. }
  2171. return;
  2172. }
  2173. #endif
  2174. #if defined(IRON_ANDROID) || defined(IRON_IOS)
  2175. return;
  2176. #endif
  2177. ui_mouse_move(ui, x, y, 0, 0);
  2178. }
  2179. void ui_key_down(ui_t *ui, int key_code) {
  2180. ui->key_code = key_code;
  2181. ui->is_key_pressed = true;
  2182. ui->is_key_down = true;
  2183. ui_key_repeat_time = iron_time() + 0.4;
  2184. switch (key_code) {
  2185. case IRON_KEY_SHIFT: ui->is_shift_down = true; break;
  2186. case IRON_KEY_CONTROL: ui->is_ctrl_down = true; break;
  2187. #ifdef IRON_DARWIN
  2188. case IRON_KEY_META: ui->is_ctrl_down = true; break;
  2189. #endif
  2190. case IRON_KEY_ALT: ui->is_alt_down = true; break;
  2191. case IRON_KEY_BACKSPACE: ui->is_backspace_down = true; break;
  2192. case IRON_KEY_DELETE: ui->is_delete_down = true; break;
  2193. case IRON_KEY_ESCAPE: ui->is_escape_down = true; break;
  2194. case IRON_KEY_RETURN: ui->is_return_down = true; break;
  2195. case IRON_KEY_TAB: ui->is_tab_down = true; break;
  2196. case IRON_KEY_A: ui->is_a_down = true; break;
  2197. case IRON_KEY_SPACE: ui->key_char = ' '; break;
  2198. #ifdef UI_ANDROID_RMB // Detect right mouse button on Android..
  2199. case IRON_KEY_BACK: if (!ui->input_down_r) ui_mouse_down(ui, 1, ui->input_x, ui->input_y); break;
  2200. #endif
  2201. }
  2202. }
  2203. void ui_key_up(ui_t *ui, int key_code) {
  2204. ui->is_key_down = false;
  2205. switch (key_code) {
  2206. case IRON_KEY_SHIFT: ui->is_shift_down = false; break;
  2207. case IRON_KEY_CONTROL: ui->is_ctrl_down = false; break;
  2208. #ifdef IRON_DARWIN
  2209. case IRON_KEY_META: ui->is_ctrl_down = false; break;
  2210. #endif
  2211. case IRON_KEY_ALT: ui->is_alt_down = false; break;
  2212. case IRON_KEY_BACKSPACE: ui->is_backspace_down = false; break;
  2213. case IRON_KEY_DELETE: ui->is_delete_down = false; break;
  2214. case IRON_KEY_ESCAPE: ui->is_escape_down = false; break;
  2215. case IRON_KEY_RETURN: ui->is_return_down = false; break;
  2216. case IRON_KEY_TAB: ui->is_tab_down = false; break;
  2217. case IRON_KEY_A: ui->is_a_down = false; break;
  2218. #ifdef UI_ANDROID_RMB
  2219. case IRON_KEY_BACK: ui_mouse_down(ui, 1, ui->input_x, ui->input_y); break;
  2220. #endif
  2221. }
  2222. }
  2223. void ui_key_press(ui_t *ui, unsigned key_char) {
  2224. ui->key_char = key_char;
  2225. ui->is_key_pressed = true;
  2226. }
  2227. #if defined(IRON_ANDROID) || defined(IRON_IOS)
  2228. static float ui_pinch_distance = 0.0;
  2229. static float ui_pinch_total = 0.0;
  2230. static bool ui_pinch_started = false;
  2231. void ui_touch_down(ui_t *ui, int index, int x, int y) {
  2232. // Reset movement delta on touch start
  2233. if (index == 0) {
  2234. ui->input_dx = 0;
  2235. ui->input_dy = 0;
  2236. ui->input_x = x;
  2237. ui->input_y = y;
  2238. }
  2239. // Two fingers down - right mouse button
  2240. else if (index == 1) {
  2241. ui->input_down = false;
  2242. ui_mouse_down(ui, 1, ui->input_x, ui->input_y);
  2243. ui_pinch_started = true;
  2244. ui_pinch_total = 0.0;
  2245. ui_pinch_distance = 0.0;
  2246. }
  2247. // Three fingers down - middle mouse button
  2248. else if (index == 2) {
  2249. ui->input_down_r = false;
  2250. ui_mouse_down(ui, 2, ui->input_x, ui->input_y);
  2251. }
  2252. }
  2253. void ui_touch_up(ui_t *ui, int index, int x, int y) {
  2254. if (index == 1) {
  2255. ui_mouse_up(ui, 1, ui->input_x, ui->input_y);
  2256. }
  2257. }
  2258. void ui_touch_move(ui_t *ui, int index, int x, int y) {
  2259. if (index == 0) {
  2260. ui_set_input_position(ui, x, y);
  2261. }
  2262. // Pinch to zoom - mouse wheel
  2263. if (index == 1) {
  2264. float last_distance = ui_pinch_distance;
  2265. float dx = ui->input_x - x;
  2266. float dy = ui->input_y - y;
  2267. ui_pinch_distance = sqrtf(dx * dx + dy * dy);
  2268. ui_pinch_total += last_distance != 0 ? last_distance - ui_pinch_distance : 0;
  2269. if (!ui_pinch_started) {
  2270. ui->input_wheel_delta = ui_pinch_total / 50;
  2271. if (ui->input_wheel_delta != 0) {
  2272. ui_pinch_total = 0.0;
  2273. }
  2274. }
  2275. ui_pinch_started = false;
  2276. }
  2277. }
  2278. #endif
  2279. char *ui_copy() {
  2280. ui_is_copy = true;
  2281. return &ui_text_to_copy[0];
  2282. }
  2283. char *ui_cut() {
  2284. ui_is_cut = true;
  2285. return ui_copy();
  2286. }
  2287. void ui_paste(char *s) {
  2288. ui_is_paste = true;
  2289. strcpy(ui_text_to_paste, s);
  2290. }
  2291. void ui_theme_default(ui_theme_t *t) {
  2292. t->WINDOW_BG_COL = 0xff292929;
  2293. t->HOVER_COL = 0xff434343;
  2294. t->ACCENT_COL = 0xff606060;
  2295. t->BUTTON_COL = 0xff383838;
  2296. t->PRESSED_COL = 0xff222222;
  2297. t->TEXT_COL = 0xffe8e8e8;
  2298. t->LABEL_COL = 0xffc8c8c8;
  2299. t->SEPARATOR_COL = 0xff202020;
  2300. t->HIGHLIGHT_COL = 0xff205d9c;
  2301. t->FONT_SIZE = 13;
  2302. t->ELEMENT_W = 100;
  2303. t->ELEMENT_H = 24;
  2304. t->ELEMENT_OFFSET = 4;
  2305. t->ARROW_SIZE = 5;
  2306. t->BUTTON_H = 22;
  2307. t->CHECK_SIZE = 16;
  2308. t->CHECK_SELECT_SIZE = 12;
  2309. t->SCROLL_W = 9;
  2310. t->SCROLL_MINI_W = 3;
  2311. t->TEXT_OFFSET = 8;
  2312. t->TAB_W = 6;
  2313. t->FILL_WINDOW_BG = true;
  2314. t->FILL_BUTTON_BG = true;
  2315. t->LINK_STYLE = UI_LINK_STYLE_LINE;
  2316. t->FULL_TABS = false;
  2317. t->ROUND_CORNERS = true;
  2318. t->SHADOWS = true;
  2319. t->VIEWPORT_COL = 0xff080808;
  2320. }
  2321. #define MATH_PI 3.14159265358979323846
  2322. static char data_path[128] = "";
  2323. static char last_path[512];
  2324. static ui_handle_t *wheel_selected_handle = NULL;
  2325. static ui_handle_t *gradient_selected_handle = NULL;
  2326. static ui_handle_t radio_handle;
  2327. static int _ELEMENT_OFFSET = 0;
  2328. static int _BUTTON_COL = 0;
  2329. static int text_area_selection_start = -1;
  2330. bool ui_text_area_line_numbers = false;
  2331. bool ui_text_area_scroll_past_end = false;
  2332. ui_text_coloring_t *ui_text_area_coloring = NULL;
  2333. float ui_dist(float x1, float y1, float x2, float y2) {
  2334. float vx = x1 - x2;
  2335. float vy = y1 - y2;
  2336. return sqrtf(vx * vx + vy * vy);
  2337. }
  2338. float ui_fract(float f) {
  2339. return f - (int)f;
  2340. }
  2341. float ui_mix(float x, float y, float a) {
  2342. return x * (1.0 - a) + y * a;
  2343. }
  2344. float ui_clamp(float x, float min_val, float max_val) {
  2345. return fmin(fmax(x, min_val), max_val);
  2346. }
  2347. float ui_step(float edge, float x) {
  2348. return x < edge ? 0.0 : 1.0;
  2349. }
  2350. static const float kx = 1.0;
  2351. static const float ky = 2.0 / 3.0;
  2352. static const float kz = 1.0 / 3.0;
  2353. static const float kw = 3.0;
  2354. static float ar[] = {0.0, 0.0, 0.0};
  2355. void ui_hsv_to_rgb(float cr, float cg, float cb, float *out) {
  2356. float px = fabs(ui_fract(cr + kx) * 6.0 - kw);
  2357. float py = fabs(ui_fract(cr + ky) * 6.0 - kw);
  2358. float pz = fabs(ui_fract(cr + kz) * 6.0 - kw);
  2359. out[0] = cb * ui_mix(kx, ui_clamp(px - kx, 0.0, 1.0), cg);
  2360. out[1] = cb * ui_mix(kx, ui_clamp(py - kx, 0.0, 1.0), cg);
  2361. out[2] = cb * ui_mix(kx, ui_clamp(pz - kx, 0.0, 1.0), cg);
  2362. }
  2363. static const float Kx = 0.0;
  2364. static const float Ky = -1.0 / 3.0;
  2365. static const float Kz = 2.0 / 3.0;
  2366. static const float Kw = -1.0;
  2367. static const float e = 1.0e-10;
  2368. void ui_rgb_to_hsv(float cr, float cg, float cb, float *out) {
  2369. float px = ui_mix(cb, cg, ui_step(cb, cg));
  2370. float py = ui_mix(cg, cb, ui_step(cb, cg));
  2371. float pz = ui_mix(Kw, Kx, ui_step(cb, cg));
  2372. float pw = ui_mix(Kz, Ky, ui_step(cb, cg));
  2373. float qx = ui_mix(px, cr, ui_step(px, cr));
  2374. float qy = ui_mix(py, py, ui_step(px, cr));
  2375. float qz = ui_mix(pw, pz, ui_step(px, cr));
  2376. float qw = ui_mix(cr, px, ui_step(px, cr));
  2377. float d = qx - fmin(qw, qy);
  2378. out[0] = fabs(qz + (qw - qy) / (6.0 * d + e));
  2379. out[1] = d / (qx + e);
  2380. out[2] = qx;
  2381. }
  2382. float ui_float_input(ui_handle_t *handle, char *label, int align, float precision) {
  2383. sprintf(handle->text, "%f", round(handle->value * precision) / precision);
  2384. char *text = ui_text_input(handle, label, align, true, false);
  2385. handle->value = atof(text);
  2386. return handle->value;
  2387. }
  2388. void ui_init_path(ui_handle_t *handle, const char *system_id) {
  2389. strcpy(handle->text, strcmp(system_id, "Windows") == 0 ? "C:\\Users" : "/");
  2390. // %HOMEDRIVE% + %HomePath%
  2391. // ~
  2392. }
  2393. char *ui_file_browser(ui_handle_t *handle, bool folders_only) {
  2394. ui_t *current = ui_get_current();
  2395. const char *sep = "/";
  2396. char cmd[64];
  2397. strcpy(cmd, "ls ");
  2398. const char *system_id = iron_system_id();
  2399. if (strcmp(system_id, "Windows") == 0) {
  2400. strcpy(cmd, "dir /b ");
  2401. if (folders_only) {
  2402. strcat(cmd, "/ad ");
  2403. }
  2404. sep = "\\";
  2405. handle->text = string_replace_all(handle->text, "\\\\", "\\");
  2406. handle->text = string_replace_all(handle->text, "\r", "");
  2407. }
  2408. if (handle->text[0] == '\0') {
  2409. ui_init_path(handle, system_id);
  2410. }
  2411. char save[256];
  2412. strcpy(save, iron_internal_get_files_location());
  2413. strcat(save, sep);
  2414. strcat(save, data_path);
  2415. strcat(save, "dir.txt");
  2416. if (strcmp(handle->text, last_path) != 0) {
  2417. char str[512];
  2418. strcpy(str, cmd);
  2419. strcat(str, "\"");
  2420. strcat(str, handle->text);
  2421. strcat(str, "\" > \"");
  2422. strcat(str, save);
  2423. strcat(str, "\"");
  2424. }
  2425. strcpy(last_path, handle->text);
  2426. iron_file_reader_t reader;
  2427. if (!iron_file_reader_open(&reader, save, IRON_FILE_TYPE_ASSET)) {
  2428. return NULL;
  2429. }
  2430. int reader_size = (int)iron_file_reader_size(&reader);
  2431. char str[2048]; // reader_size
  2432. iron_file_reader_read(&reader, str, reader_size);
  2433. iron_file_reader_close(&reader);
  2434. // Up directory
  2435. int i1 = strstr(handle->text, "/") - handle->text;
  2436. int i2 = strstr(handle->text, "\\") - handle->text;
  2437. bool nested =
  2438. (i1 > -1 && strlen(handle->text) - 1 > i1) ||
  2439. (i2 > -1 && strlen(handle->text) - 1 > i2);
  2440. handle->changed = false;
  2441. if (nested && ui_button("..", UI_ALIGN_LEFT, "")) {
  2442. handle->changed = current->changed = true;
  2443. handle->text[strrchr(handle->text, sep[0]) - handle->text] = 0;
  2444. // Drive root
  2445. if (strlen(handle->text) == 2 && handle->text[1] == ':') {
  2446. strcat(handle->text, sep);
  2447. }
  2448. }
  2449. // Directory contents
  2450. int count = ui_line_count(str);
  2451. for (int i = 0; i < count; ++i) {
  2452. char *f = ui_extract_line(str, i);
  2453. if (f[0] == '\0' || f[0] == '.') {
  2454. continue; // Skip hidden
  2455. }
  2456. if (ui_button(f, UI_ALIGN_LEFT, "")) {
  2457. handle->changed = current->changed = true;
  2458. if (handle->text[strlen(handle->text) - 1] != sep[0]) {
  2459. strcat(handle->text, sep);
  2460. }
  2461. strcat(handle->text, f);
  2462. }
  2463. }
  2464. return handle->text;
  2465. }
  2466. int ui_inline_radio(ui_handle_t *handle, char_ptr_array_t *texts, int align) {
  2467. ui_t *current = ui_get_current();
  2468. if (!ui_is_visible(UI_ELEMENT_H())) {
  2469. ui_end_element();
  2470. return handle->position;
  2471. }
  2472. float step = current->_w / texts->length;
  2473. int hovered = -1;
  2474. if (ui_get_hover(UI_ELEMENT_H())) {
  2475. int ix = current->input_x - current->_x - current->_window_x;
  2476. for (int i = 0; i < texts->length; ++i) {
  2477. if (ix < i * step + step) {
  2478. hovered = i;
  2479. break;
  2480. }
  2481. }
  2482. }
  2483. if (ui_get_released(UI_ELEMENT_H())) {
  2484. handle->position = hovered;
  2485. handle->changed = current->changed = true;
  2486. }
  2487. else {
  2488. handle->changed = false;
  2489. }
  2490. for (int i = 0; i < texts->length; ++i) {
  2491. if (handle->position == i) {
  2492. draw_set_color(current->ops->theme->HIGHLIGHT_COL);
  2493. if (!current->enabled) {
  2494. ui_fade_color(0.25);
  2495. }
  2496. ui_draw_rect(true, current->_x + step * i, current->_y + current->button_offset_y, step, UI_BUTTON_H());
  2497. }
  2498. else if (hovered == i) {
  2499. draw_set_color(current->ops->theme->BUTTON_COL);
  2500. if (!current->enabled) {
  2501. ui_fade_color(0.25);
  2502. }
  2503. ui_draw_rect(false, current->_x + step * i, current->_y + current->button_offset_y, step, UI_BUTTON_H());
  2504. }
  2505. draw_set_color(current->ops->theme->TEXT_COL); // Text
  2506. current->_x += step * i;
  2507. float _w = current->_w;
  2508. current->_w = (int)step;
  2509. ui_draw_string(texts->buffer[i], current->ops->theme->TEXT_OFFSET, 0, align, true);
  2510. current->_x -= step * i;
  2511. current->_w = _w;
  2512. }
  2513. ui_end_element();
  2514. return handle->position;
  2515. }
  2516. uint8_t ui_color_r(uint32_t color) {
  2517. return (color & 0x00ff0000) >> 16;
  2518. }
  2519. uint8_t ui_color_g(uint32_t color) {
  2520. return (color & 0x0000ff00) >> 8;
  2521. }
  2522. uint8_t ui_color_b(uint32_t color) {
  2523. return (color & 0x000000ff);
  2524. }
  2525. uint8_t ui_color_a(uint32_t color) {
  2526. return (color) >> 24;
  2527. }
  2528. uint32_t ui_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
  2529. return (a << 24) | (r << 16) | (g << 8) | b;
  2530. }
  2531. int ui_color_wheel(ui_handle_t *handle, bool alpha, float w, float h, bool color_preview, void (*picker)(void *), void *data) {
  2532. ui_t *current = ui_get_current();
  2533. if (w < 0) {
  2534. w = current->_w;
  2535. }
  2536. float r = ui_color_r(handle->color) / 255.0f;
  2537. float g = ui_color_g(handle->color) / 255.0f;
  2538. float b = ui_color_b(handle->color) / 255.0f;
  2539. ui_rgb_to_hsv(r, g, b, ar);
  2540. float chue = ar[0];
  2541. float csat = ar[1];
  2542. float cval = ar[2];
  2543. float calpha = ui_color_a(handle->color) / 255.0f;
  2544. // Wheel
  2545. float px = current->_x;
  2546. float py = current->_y;
  2547. bool scroll = current->current_window != NULL ? current->current_window->scroll_enabled : false;
  2548. if (!scroll) {
  2549. w -= UI_SCROLL_W();
  2550. px += UI_SCROLL_W() / 2.0;
  2551. }
  2552. float _x = current->_x;
  2553. float _y = current->_y;
  2554. float _w = current->_w;
  2555. current->_w = (int)(28.0 * UI_SCALE());
  2556. if (picker != NULL && ui_button("P", UI_ALIGN_CENTER, "")) {
  2557. (*picker)(data);
  2558. current->changed = false;
  2559. handle->changed = false;
  2560. return handle->color;
  2561. }
  2562. current->_x = _x;
  2563. current->_y = _y;
  2564. current->_w = _w;
  2565. uint32_t col = ui_color(round(cval * 255.0f), round(cval * 255.0f), round(cval * 255.0f), 255);
  2566. ui_image(current->ops->color_wheel, col, -1);
  2567. // Picker
  2568. float ph = current->_y - py;
  2569. float ox = px + w / 2.0;
  2570. float oy = py + ph / 2.0;
  2571. float cw = w * 0.7;
  2572. float cwh = cw / 2.0;
  2573. float cx = ox;
  2574. float cy = oy + csat * cwh; // Sat is distance from center
  2575. float grad_tx = px + 0.897 * w;
  2576. float grad_ty = oy - cwh;
  2577. float grad_w = 0.0777 * w;
  2578. float grad_h = cw;
  2579. // Rotate around origin by hue
  2580. float theta = chue * (MATH_PI * 2.0);
  2581. float cx2 = cos(theta) * (cx - ox) - sin(theta) * (cy - oy) + ox;
  2582. float cy2 = sin(theta) * (cx - ox) + cos(theta) * (cy - oy) + oy;
  2583. cx = cx2;
  2584. cy = cy2;
  2585. current->_x = px - (scroll ? 0 : UI_SCROLL_W() / 2.0);
  2586. current->_y = py;
  2587. ui_image(current->ops->black_white_gradient, 0xffffffff, -1);
  2588. draw_set_color(0xff000000);
  2589. draw_filled_rect(cx - 3.0 * UI_SCALE(), cy - 3.0 * UI_SCALE(), 6.0 * UI_SCALE(), 6.0 * UI_SCALE());
  2590. draw_set_color(0xffffffff);
  2591. draw_filled_rect(cx - 2.0 * UI_SCALE(), cy - 2.0 * UI_SCALE(), 4.0 * UI_SCALE(), 4.0 * UI_SCALE());
  2592. draw_set_color(0xff000000);
  2593. draw_filled_rect(grad_tx + grad_w / 2.0 - 3.0 * UI_SCALE(), grad_ty + (1.0 - cval) * grad_h - 3.0 * UI_SCALE(), 6.0 * UI_SCALE(), 6.0 * UI_SCALE());
  2594. draw_set_color(0xffffffff);
  2595. draw_filled_rect(grad_tx + grad_w / 2.0 - 2.0 * UI_SCALE(), grad_ty + (1.0 - cval) * grad_h - 2.0 * UI_SCALE(), 4.0 * UI_SCALE(), 4.0 * UI_SCALE());
  2596. if (alpha) {
  2597. ui_handle_t *alpha_handle = ui_nest(handle, 1);
  2598. if (alpha_handle->init) {
  2599. alpha_handle->value = round(calpha * 100.0) / 100.0;
  2600. }
  2601. calpha = ui_slider(alpha_handle, "Alpha", 0.0, 1.0, true, 100, true, UI_ALIGN_LEFT, true);
  2602. if (alpha_handle->changed) {
  2603. handle->changed = current->changed = true;
  2604. }
  2605. }
  2606. // Mouse picking for color wheel
  2607. float gx = ox + current->_window_x;
  2608. float gy = oy + current->_window_y;
  2609. if (current->input_started && ui_input_in_rect(gx - cwh, gy - cwh, cw, cw)) {
  2610. wheel_selected_handle = handle;
  2611. }
  2612. if (current->input_released && wheel_selected_handle != NULL) {
  2613. wheel_selected_handle = NULL;
  2614. handle->changed = current->changed = true;
  2615. }
  2616. if (current->input_down && wheel_selected_handle == handle) {
  2617. csat = fmin(ui_dist(gx, gy, current->input_x, current->input_y), cwh) / cwh;
  2618. float angle = atan2(current->input_x - gx, current->input_y - gy);
  2619. if (angle < 0) {
  2620. angle = MATH_PI + (MATH_PI - fabs(angle));
  2621. }
  2622. angle = MATH_PI * 2.0 - angle;
  2623. chue = angle / (MATH_PI * 2.0);
  2624. handle->changed = current->changed = true;
  2625. }
  2626. // Mouse picking for cval
  2627. if (current->input_started && ui_input_in_rect(grad_tx + current->_window_x, grad_ty + current->_window_y, grad_w, grad_h)) {
  2628. gradient_selected_handle = handle;
  2629. }
  2630. if (current->input_released && gradient_selected_handle != NULL) {
  2631. gradient_selected_handle = NULL;
  2632. handle->changed = current->changed = true;
  2633. }
  2634. if (current->input_down && gradient_selected_handle == handle) {
  2635. cval = fmax(0.01, fmin(1.0, 1.0 - (current->input_y - grad_ty - current->_window_y) / grad_h));
  2636. handle->changed = current->changed = true;
  2637. }
  2638. // Save as rgb
  2639. ui_hsv_to_rgb(chue, csat, cval, ar);
  2640. handle->color = ui_color(round(ar[0] * 255.0), round(ar[1] * 255.0), round(ar[2] * 255.0), round(calpha * 255.0));
  2641. if (color_preview) {
  2642. ui_text("", UI_ALIGN_RIGHT, handle->color);
  2643. }
  2644. char *strings[] = {"RGB", "HSV", "Hex"};
  2645. char_ptr_array_t car;
  2646. car.buffer = strings;
  2647. car.length = 3;
  2648. int pos = ui_inline_radio(&radio_handle, &car, UI_ALIGN_LEFT);
  2649. ui_handle_t *h0 = ui_nest(ui_nest(handle, 0), 0);
  2650. ui_handle_t *h1 = ui_nest(ui_nest(handle, 0), 1);
  2651. ui_handle_t *h2 = ui_nest(ui_nest(handle, 0), 2);
  2652. if (pos == 0) {
  2653. h0->value = ui_color_r(handle->color) / 255.0f;
  2654. float r = ui_slider(h0, "R", 0, 1, true, 100, true, UI_ALIGN_LEFT, true);
  2655. h1->value = ui_color_g(handle->color) / 255.0f;
  2656. float g = ui_slider(h1, "G", 0, 1, true, 100, true, UI_ALIGN_LEFT, true);
  2657. h2->value = ui_color_b(handle->color) / 255.0f;
  2658. float b = ui_slider(h2, "B", 0, 1, true, 100, true, UI_ALIGN_LEFT, true);
  2659. handle->color = ui_color(r * 255.0, g * 255.0, b * 255.0, 255.0);
  2660. }
  2661. else if (pos == 1) {
  2662. ui_rgb_to_hsv(ui_color_r(handle->color) / 255.0f, ui_color_g(handle->color) / 255.0f, ui_color_b(handle->color) / 255.0f, ar);
  2663. h0->value = ar[0];
  2664. h1->value = ar[1];
  2665. h2->value = ar[2];
  2666. float chue = ui_slider(h0, "H", 0, 1, true, 100, true, UI_ALIGN_LEFT, true);
  2667. float csat = ui_slider(h1, "S", 0, 1, true, 100, true, UI_ALIGN_LEFT, true);
  2668. float cval = ui_slider(h2, "V", 0, 1, true, 100, true, UI_ALIGN_LEFT, true);
  2669. ui_hsv_to_rgb(chue, csat, cval, ar);
  2670. handle->color = ui_color(ar[0] * 255.0, ar[1] * 255.0, ar[2] * 255.0, 255.0);
  2671. }
  2672. else if (pos == 2) {
  2673. char tmp[16];
  2674. handle->text = tmp;
  2675. sprintf(handle->text, "%x", handle->color);
  2676. char *hex_code = ui_text_input(handle, "#", UI_ALIGN_LEFT, true, false);
  2677. if (strlen(hex_code) >= 1 && hex_code[0] == '#') { // Allow # at the beginning
  2678. hex_code = strcpy(hex_code, hex_code + 1);
  2679. }
  2680. if (strlen(hex_code) == 3) { // 3 digit CSS style values like fa0 --> ffaa00
  2681. hex_code[5] = hex_code[2];
  2682. hex_code[4] = hex_code[2];
  2683. hex_code[3] = hex_code[1];
  2684. hex_code[2] = hex_code[1];
  2685. hex_code[1] = hex_code[0];
  2686. hex_code[6] = '\0';
  2687. }
  2688. if (strlen(hex_code) == 4) { // 4 digit CSS style values
  2689. hex_code[7] = hex_code[3];
  2690. hex_code[6] = hex_code[3];
  2691. hex_code[5] = hex_code[2];
  2692. hex_code[4] = hex_code[2];
  2693. hex_code[3] = hex_code[1];
  2694. hex_code[2] = hex_code[1];
  2695. hex_code[1] = hex_code[0];
  2696. hex_code[8] = '\0';
  2697. }
  2698. if (strlen(hex_code) == 6) { // Make the alpha channel optional
  2699. hex_code[7] = hex_code[5];
  2700. hex_code[6] = hex_code[4];
  2701. hex_code[5] = hex_code[3];
  2702. hex_code[4] = hex_code[2];
  2703. hex_code[3] = hex_code[1];
  2704. hex_code[2] = hex_code[0];
  2705. hex_code[0] = 'f';
  2706. hex_code[1] = 'f';
  2707. }
  2708. #ifdef _WIN32
  2709. handle->color = _strtoi64(hex_code, NULL, 16);
  2710. #else
  2711. handle->color = strtol(hex_code, NULL, 16);
  2712. #endif
  2713. }
  2714. if (h0->changed || h1->changed || h2->changed) {
  2715. handle->changed = current->changed = true;
  2716. }
  2717. // Do not close if user clicks
  2718. if (current->input_released && ui_input_in_rect(current->_window_x + px, current->_window_y + py, w, h < 0 ? (current->_y - py) : h) && current->input_released) {
  2719. current->changed = true;
  2720. }
  2721. return handle->color;
  2722. }
  2723. static void scroll_align(ui_t *current, ui_handle_t *handle) {
  2724. // Scroll down
  2725. if ((handle->position + 1) * UI_ELEMENT_H() + current->current_window->scroll_offset > current->_h - current->window_header_h) {
  2726. current->current_window->scroll_offset -= UI_ELEMENT_H();
  2727. }
  2728. // Scroll up
  2729. else if ((handle->position + 1) * UI_ELEMENT_H() + current->current_window->scroll_offset < current->window_header_h) {
  2730. current->current_window->scroll_offset += UI_ELEMENT_H();
  2731. }
  2732. }
  2733. static char *right_align_number(char *s, int number, int length) {
  2734. sprintf(s, "%d", number);
  2735. while (strlen(s) < length) {
  2736. for (int i = strlen(s) + 1; i > 0; --i) {
  2737. s[i] = s[i - 1];
  2738. }
  2739. s[0] = ' ';
  2740. }
  2741. return s;
  2742. }
  2743. static void handle_line_select(ui_t *current, ui_handle_t *handle) {
  2744. if (current->is_shift_down) {
  2745. current->highlight_anchor = 0;
  2746. if (text_area_selection_start == -1) {
  2747. text_area_selection_start = handle->position;
  2748. }
  2749. }
  2750. else text_area_selection_start = -1;
  2751. }
  2752. static int ui_word_count(char *str) {
  2753. if (str == NULL || str[0] == '\0') {
  2754. return 0;
  2755. }
  2756. int i = 0;
  2757. int count = 1;
  2758. while (str[i] != '\0') {
  2759. if (str[i] == ' ' || str[i] == '\n') {
  2760. count++;
  2761. }
  2762. i++;
  2763. }
  2764. return count;
  2765. }
  2766. static char temp[128];
  2767. static char *ui_extract_word(char *str, int word) {
  2768. int pos = 0;
  2769. int len = strlen(str);
  2770. int word_i = 0;
  2771. for (int i = 0; i < len; ++i) {
  2772. if (str[i] == ' ' || str[i] == '\n') {
  2773. word_i++;
  2774. continue;
  2775. }
  2776. if (word_i < word) {
  2777. continue;
  2778. }
  2779. if (word_i > word) {
  2780. break;
  2781. }
  2782. temp[pos++] = str[i];
  2783. }
  2784. temp[pos] = 0;
  2785. return temp;
  2786. }
  2787. static int ui_line_pos(char *str, int line) {
  2788. int i = 0;
  2789. int current_line = 0;
  2790. while (str[i] != '\0' && current_line < line) {
  2791. if (str[i] == '\n') {
  2792. current_line++;
  2793. }
  2794. i++;
  2795. }
  2796. return i;
  2797. }
  2798. char *ui_text_area(ui_handle_t *handle, int align, bool editable, char *label, bool word_wrap) {
  2799. ui_t *current = ui_get_current();
  2800. handle->text = string_replace_all(handle->text, "\t", " ");
  2801. bool selected = current->text_selected_handle == handle; // Text being edited
  2802. char lines[4096];
  2803. strcpy(lines, handle->text);
  2804. int line_count = ui_line_count(lines);
  2805. bool show_label = (line_count == 1 && lines[0] == '\0');
  2806. bool key_pressed = selected && current->is_key_pressed;
  2807. current->highlight_on_select = false;
  2808. current->tab_switch_enabled = false;
  2809. if (word_wrap && handle->text[0] != '\0') {
  2810. bool cursor_set = false;
  2811. int cursor_pos = current->cursor_x;
  2812. for (int i = 0; i < handle->position; ++i) {
  2813. cursor_pos += strlen(ui_extract_line(lines, i)) + 1; // + '\n'
  2814. }
  2815. int word_count = ui_word_count(lines);
  2816. char line[1024];
  2817. line[0] = '\0';
  2818. char new_lines[4096];
  2819. new_lines[0] = '\0';
  2820. for (int i = 0; i < word_count; ++i) {
  2821. char *w = ui_extract_word(lines, i);
  2822. float spacew = draw_string_width(current->ops->font, current->font_size, " ");
  2823. float wordw = spacew + draw_string_width(current->ops->font, current->font_size, w);
  2824. float linew = wordw + draw_string_width(current->ops->font, current->font_size, line);
  2825. if (linew > current->_w - 10 && linew > wordw) {
  2826. if (new_lines[0] != '\0') {
  2827. strcat(new_lines, "\n");
  2828. }
  2829. strcat(new_lines, line);
  2830. line[0] = '\0';
  2831. }
  2832. if (line[0] == '\0') {
  2833. strcpy(line, w);
  2834. }
  2835. else {
  2836. strcat(line, " ");
  2837. strcat(line, w);
  2838. }
  2839. int new_line_count = new_lines[0] == '\0' ? 0 : ui_line_count(new_lines);
  2840. int lines_len = new_line_count;
  2841. for (int i = 0; i < new_line_count; ++i) {
  2842. lines_len += strlen(ui_extract_line(new_lines, i));
  2843. }
  2844. if (selected && !cursor_set && cursor_pos <= lines_len + strlen(line)) {
  2845. cursor_set = true;
  2846. handle->position = new_line_count;
  2847. current->cursor_x = current->highlight_anchor = cursor_pos - lines_len;
  2848. }
  2849. }
  2850. if (new_lines[0] != '\0') {
  2851. strcat(new_lines, "\n");
  2852. }
  2853. strcat(new_lines, line);
  2854. if (selected) {
  2855. strcpy(handle->text, ui_extract_line(new_lines, handle->position));
  2856. strcpy(current->text_selected, handle->text);
  2857. }
  2858. strcpy(lines, new_lines);
  2859. }
  2860. int cursor_start_x = current->cursor_x;
  2861. if (ui_text_area_line_numbers) {
  2862. float _y = current->_y;
  2863. int _TEXT_COL = current->ops->theme->TEXT_COL;
  2864. current->ops->theme->TEXT_COL = current->ops->theme->HOVER_COL;
  2865. int max_length = ceil(log(line_count + 0.5) / log(10)); // Express log_10 with natural log
  2866. char s[64];
  2867. for (int i = 0; i < line_count; ++i) {
  2868. ui_text(right_align_number(&s[0], i + 1, max_length), UI_ALIGN_LEFT, 0x00000000);
  2869. current->_y -= UI_ELEMENT_OFFSET();
  2870. }
  2871. current->ops->theme->TEXT_COL = _TEXT_COL;
  2872. current->_y = _y;
  2873. sprintf(s, "%d", line_count);
  2874. float numbers_w = (strlen(s) * 16 + 4) * UI_SCALE();
  2875. current->_x += numbers_w;
  2876. current->_w -= numbers_w - UI_SCROLL_W();
  2877. }
  2878. draw_set_color(current->ops->theme->SEPARATOR_COL); // Background
  2879. ui_draw_rect(true, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, line_count * UI_ELEMENT_H() - current->button_offset_y * 2);
  2880. ui_text_coloring_t *_text_coloring = current->text_coloring;
  2881. current->text_coloring = ui_text_area_coloring;
  2882. if (current->input_started) {
  2883. text_area_selection_start = -1;
  2884. }
  2885. for (int i = 0; i < line_count; ++i) { // Draw lines
  2886. char *line = ui_extract_line(lines, i);
  2887. // Text input
  2888. if ((!selected && ui_get_hover(UI_ELEMENT_H())) || (selected && i == handle->position)) {
  2889. handle->position = i; // Set active line
  2890. strcpy(handle->text, line);
  2891. current->submit_text_handle = NULL;
  2892. ui_text_input(handle, show_label ? label : "", align, editable, false);
  2893. if (key_pressed && current->key_code != IRON_KEY_RETURN && current->key_code != IRON_KEY_ESCAPE) { // Edit text
  2894. int line_pos = ui_line_pos(lines, i);
  2895. ui_remove_chars_at(lines, line_pos, strlen(line));
  2896. strcpy(line, current->text_selected);
  2897. ui_insert_chars_at(lines, line_pos, line);
  2898. }
  2899. }
  2900. // Text
  2901. else {
  2902. if (show_label) {
  2903. int TEXT_COL = current->ops->theme->TEXT_COL;
  2904. current->ops->theme->TEXT_COL = current->ops->theme->LABEL_COL;
  2905. ui_text(label, UI_ALIGN_RIGHT, 0x00000000);
  2906. current->ops->theme->TEXT_COL = TEXT_COL;
  2907. }
  2908. else {
  2909. // Multi-line selection highlight
  2910. if (text_area_selection_start > -1 &&
  2911. (i >= text_area_selection_start && i < handle->position) ||
  2912. (i <= text_area_selection_start && i > handle->position)) {
  2913. int line_height = UI_ELEMENT_H();
  2914. int cursor_height = line_height - current->button_offset_y * 3.0;
  2915. int linew = draw_string_width(current->ops->font, current->font_size, line);
  2916. draw_set_color(current->ops->theme->ACCENT_COL);
  2917. draw_filled_rect(current->_x + UI_ELEMENT_OFFSET() * 2.0, current->_y + current->button_offset_y * 1.5, linew, cursor_height);
  2918. }
  2919. ui_text(line, align, 0x00000000);
  2920. }
  2921. }
  2922. current->_y -= UI_ELEMENT_OFFSET();
  2923. }
  2924. current->_y += UI_ELEMENT_OFFSET();
  2925. current->text_coloring = _text_coloring;
  2926. if (ui_text_area_scroll_past_end) {
  2927. current->_y += current->_h - current->window_header_h - UI_ELEMENT_H() - UI_ELEMENT_OFFSET();
  2928. }
  2929. if (key_pressed) {
  2930. // Move cursor vertically
  2931. if (current->key_code == IRON_KEY_DOWN && handle->position < line_count - 1) {
  2932. handle_line_select(current, handle);
  2933. handle->position++;
  2934. scroll_align(current, handle);
  2935. }
  2936. if (current->key_code == IRON_KEY_UP && handle->position > 0) {
  2937. handle_line_select(current, handle);
  2938. handle->position--;
  2939. scroll_align(current, handle);
  2940. }
  2941. // New line
  2942. if (editable && current->key_code == IRON_KEY_RETURN && !word_wrap) {
  2943. handle->position++;
  2944. ui_insert_char_at(lines, ui_line_pos(lines, handle->position - 1) + current->cursor_x, '\n');
  2945. ui_start_text_edit(handle, UI_ALIGN_LEFT);
  2946. current->cursor_x = current->highlight_anchor = 0;
  2947. scroll_align(current, handle);
  2948. }
  2949. // Delete line
  2950. if (editable && current->key_code == IRON_KEY_BACKSPACE && cursor_start_x == 0 && handle->position > 0) {
  2951. handle->position--;
  2952. current->cursor_x = current->highlight_anchor = strlen(ui_extract_line(lines, handle->position));
  2953. ui_remove_chars_at(lines, ui_line_pos(lines, handle->position + 1) - 1, 1); // Remove '\n' of the previous line
  2954. scroll_align(current, handle);
  2955. }
  2956. strcpy(current->text_selected, ui_extract_line(lines, handle->position));
  2957. }
  2958. current->highlight_on_select = true;
  2959. current->tab_switch_enabled = true;
  2960. handle->text = string_copy(lines);
  2961. return handle->text;
  2962. }
  2963. float UI_MENUBAR_H() {
  2964. ui_t *current = ui_get_current();
  2965. return UI_BUTTON_H() * 1.1 + 2.0 + current->button_offset_y;
  2966. }
  2967. void ui_begin_menu() {
  2968. ui_t *current = ui_get_current();
  2969. _ELEMENT_OFFSET = current->ops->theme->ELEMENT_OFFSET;
  2970. _BUTTON_COL = current->ops->theme->BUTTON_COL;
  2971. current->ops->theme->ELEMENT_OFFSET = 0;
  2972. current->ops->theme->BUTTON_COL = current->ops->theme->SEPARATOR_COL;
  2973. draw_set_color(current->ops->theme->SEPARATOR_COL);
  2974. draw_filled_rect(0, 0, current->_window_w, UI_MENUBAR_H());
  2975. }
  2976. void ui_end_menu() {
  2977. ui_t *current = ui_get_current();
  2978. current->ops->theme->ELEMENT_OFFSET = _ELEMENT_OFFSET;
  2979. current->ops->theme->BUTTON_COL = _BUTTON_COL;
  2980. }
  2981. bool _ui_menu_button(char *text) {
  2982. ui_t *current = ui_get_current();
  2983. current->_w = draw_string_width(current->ops->font, current->font_size, text) + 25.0 * UI_SCALE();
  2984. return ui_button(text, UI_ALIGN_CENTER, "");
  2985. }
  2986. const char *ui_theme_keys[] = {
  2987. "WINDOW_BG_COL",
  2988. "HOVER_COL",
  2989. "ACCENT_COL",
  2990. "BUTTON_COL",
  2991. "PRESSED_COL",
  2992. "TEXT_COL",
  2993. "LABEL_COL",
  2994. "SEPARATOR_COL",
  2995. "HIGHLIGHT_COL",
  2996. "FONT_SIZE",
  2997. "ELEMENT_W",
  2998. "ELEMENT_H",
  2999. "ELEMENT_OFFSET",
  3000. "ARROW_SIZE",
  3001. "BUTTON_H",
  3002. "CHECK_SIZE",
  3003. "CHECK_SELECT_SIZE",
  3004. "SCROLL_W",
  3005. "SCROLL_MINI_W",
  3006. "TEXT_OFFSET",
  3007. "TAB_W",
  3008. "FILL_WINDOW_BG",
  3009. "FILL_BUTTON_BG",
  3010. "LINK_STYLE",
  3011. "FULL_TABS",
  3012. "ROUND_CORNERS",
  3013. "SHADOWS",
  3014. "VIEWPORT_COL"
  3015. };
  3016. int ui_theme_keys_count = sizeof(ui_theme_keys) / sizeof(ui_theme_keys[0]);