microui.odin 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707
  1. /*
  2. ** Original work: Copyright (c) 2020 rxi
  3. ** Modified work: Copyright (c) 2020 oskarnp
  4. ** Modified work: Copyright (c) 2021 gingerBill
  5. **
  6. ** Permission is hereby granted, free of charge, to any person obtaining a copy
  7. ** of this software and associated documentation files (the "Software"), to
  8. ** deal in the Software without restriction, including without limitation the
  9. ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  10. ** sell copies of the Software, and to permit persons to whom the Software is
  11. ** furnished to do so, subject to the following conditions:
  12. **
  13. ** The above copyright notice and this permission notice shall be included in
  14. ** all copies or substantial portions of the Software.
  15. **
  16. ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21. ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  22. ** IN THE SOFTWARE.
  23. */
  24. package microui
  25. import "core:fmt"
  26. import "core:math"
  27. import "core:sort"
  28. import "core:strconv"
  29. import "core:strings"
  30. import textedit "core:text/edit"
  31. COMMAND_LIST_SIZE :: #config(MICROUI_COMMAND_LIST_SIZE, 256 * 1024)
  32. ROOT_LIST_SIZE :: #config(MICROUI_ROOT_LIST_SIZE, 32)
  33. CONTAINER_STACK_SIZE :: #config(MICROUI_CONTAINER_STACK_SIZE, 32)
  34. CLIP_STACK_SIZE :: #config(MICROUI_CLIP_STACK_SIZE, 32)
  35. ID_STACK_SIZE :: #config(MICROUI_ID_STACK_SIZE, 32)
  36. LAYOUT_STACK_SIZE :: #config(MICROUI_LAYOUT_STACK_SIZE, 16)
  37. CONTAINER_POOL_SIZE :: #config(MICROUI_CONTAINER_POOL_SIZE, 48)
  38. TREENODE_POOL_SIZE :: #config(MICROUI_TREENODE_POOL_SIZE, 48)
  39. MAX_WIDTHS :: #config(MICROUI_MAX_WIDTHS, 16)
  40. SLIDER_FMT :: #config(MICROUI_SLIDER_FMT, "%.2f")
  41. MAX_FMT :: #config(MICROUI_MAX_FMT, 127)
  42. MAX_TEXT_STORE :: #config(MICROUI_MAX_TEXT_STORE, 1024)
  43. Clip :: enum u32 {
  44. NONE,
  45. PART,
  46. ALL,
  47. }
  48. Color_Type :: enum u32 {
  49. TEXT,
  50. SELECTION_BG,
  51. BORDER,
  52. WINDOW_BG,
  53. TITLE_BG,
  54. TITLE_TEXT,
  55. PANEL_BG,
  56. BUTTON,
  57. BUTTON_HOVER = BUTTON + 1,
  58. BUTTON_FOCUS = BUTTON + 2,
  59. BASE,
  60. BASE_HOVER = BASE + 1,
  61. BASE_FOCUS = BASE + 2,
  62. SCROLL_BASE,
  63. SCROLL_THUMB,
  64. }
  65. Icon :: enum u32 {
  66. NONE,
  67. CLOSE,
  68. CHECK,
  69. COLLAPSED,
  70. EXPANDED,
  71. RESIZE,
  72. }
  73. Result :: enum u32 {
  74. ACTIVE,
  75. SUBMIT,
  76. CHANGE,
  77. }
  78. Result_Set :: bit_set[Result;u32]
  79. Opt :: enum u32 {
  80. ALIGN_CENTER,
  81. ALIGN_RIGHT,
  82. NO_INTERACT,
  83. NO_FRAME,
  84. NO_RESIZE,
  85. NO_SCROLL,
  86. NO_CLOSE,
  87. NO_TITLE,
  88. HOLD_FOCUS,
  89. AUTO_SIZE,
  90. POPUP,
  91. CLOSED,
  92. EXPANDED,
  93. }
  94. Options :: distinct bit_set[Opt;u32]
  95. Mouse :: enum u32 {
  96. LEFT,
  97. RIGHT,
  98. MIDDLE,
  99. }
  100. Mouse_Set :: distinct bit_set[Mouse;u32]
  101. Key :: enum u32 {
  102. SHIFT,
  103. CTRL,
  104. ALT,
  105. BACKSPACE,
  106. DELETE,
  107. RETURN,
  108. LEFT,
  109. RIGHT,
  110. HOME,
  111. END,
  112. A,
  113. X,
  114. C,
  115. V,
  116. }
  117. Key_Set :: distinct bit_set[Key;u32]
  118. Id :: distinct u32
  119. Real :: f32
  120. Font :: distinct rawptr
  121. Vec2 :: distinct [2]i32
  122. Rect :: struct {
  123. x, y, w, h: i32,
  124. }
  125. Color :: struct {
  126. r, g, b, a: u8,
  127. }
  128. Frame_Index :: distinct i32
  129. Pool_Item :: struct {
  130. id: Id,
  131. last_update: Frame_Index,
  132. }
  133. Command_Variant :: union {
  134. ^Command_Jump,
  135. ^Command_Clip,
  136. ^Command_Rect,
  137. ^Command_Text,
  138. ^Command_Icon,
  139. }
  140. Command :: struct {
  141. variant: Command_Variant,
  142. size: i32,
  143. }
  144. Command_Jump :: struct {
  145. using command: Command,
  146. dst: rawptr,
  147. }
  148. Command_Clip :: struct {
  149. using command: Command,
  150. rect: Rect,
  151. }
  152. Command_Rect :: struct {
  153. using command: Command,
  154. rect: Rect,
  155. color: Color,
  156. }
  157. Command_Text :: struct {
  158. using command: Command,
  159. font: Font,
  160. pos: Vec2,
  161. color: Color,
  162. str: string, /* + string data (VLA) */
  163. }
  164. Command_Icon :: struct {
  165. using command: Command,
  166. rect: Rect,
  167. id: Icon,
  168. color: Color,
  169. }
  170. Layout_Type :: enum {
  171. NONE = 0,
  172. RELATIVE = 1,
  173. ABSOLUTE = 2,
  174. }
  175. Layout :: struct {
  176. body, next: Rect,
  177. position, size, max: Vec2,
  178. widths: [MAX_WIDTHS]i32,
  179. items, item_index, next_row: i32,
  180. next_type: Layout_Type,
  181. indent: i32,
  182. }
  183. Container :: struct {
  184. head, tail: ^Command,
  185. rect, body: Rect,
  186. content_size: Vec2,
  187. scroll: Vec2,
  188. zindex: i32,
  189. open: b32,
  190. }
  191. Style :: struct {
  192. font: Font,
  193. size: Vec2,
  194. padding: i32,
  195. spacing: i32,
  196. indent: i32,
  197. title_height: i32,
  198. footer_height: i32,
  199. scrollbar_size: i32,
  200. thumb_size: i32,
  201. colors: [Color_Type]Color,
  202. }
  203. Context :: struct {
  204. /* callbacks */
  205. text_width: proc(font: Font, str: string) -> i32,
  206. text_height: proc(font: Font) -> i32,
  207. draw_frame: proc(ctx: ^Context, rect: Rect, colorid: Color_Type),
  208. /* core state */
  209. _style: Style,
  210. style: ^Style,
  211. hover_id, focus_id, last_id: Id,
  212. last_rect: Rect,
  213. last_zindex: i32,
  214. updated_focus: b32,
  215. frame: Frame_Index,
  216. hover_root, next_hover_root: ^Container,
  217. scroll_target: ^Container,
  218. number_edit_buf: [MAX_FMT]u8,
  219. number_edit_len: int,
  220. number_edit_id: Id,
  221. /* stacks */
  222. command_list: Stack(u8, COMMAND_LIST_SIZE),
  223. root_list: Stack(^Container, ROOT_LIST_SIZE),
  224. container_stack: Stack(^Container, CONTAINER_STACK_SIZE),
  225. clip_stack: Stack(Rect, CLIP_STACK_SIZE),
  226. id_stack: Stack(Id, ID_STACK_SIZE),
  227. layout_stack: Stack(Layout, LAYOUT_STACK_SIZE),
  228. /* retained state pools */
  229. container_pool: [CONTAINER_POOL_SIZE]Pool_Item,
  230. containers: [CONTAINER_POOL_SIZE]Container,
  231. treenode_pool: [TREENODE_POOL_SIZE]Pool_Item,
  232. /* input state */
  233. mouse_pos, last_mouse_pos: Vec2,
  234. mouse_delta, scroll_delta: Vec2,
  235. mouse_down_bits: Mouse_Set,
  236. mouse_pressed_bits: Mouse_Set,
  237. mouse_released_bits: Mouse_Set,
  238. key_down_bits, key_pressed_bits: Key_Set,
  239. _text_store: [MAX_TEXT_STORE]u8,
  240. text_input: strings.Builder, // uses `_text_store` as backing store with nil_allocator.
  241. textbox_state: textedit.State,
  242. textbox_offset: i32,
  243. }
  244. Stack :: struct($T: typeid, $N: int) {
  245. idx: i32,
  246. items: [N]T,
  247. }
  248. push :: #force_inline proc(stk: ^$T/Stack($V, $N), val: V) {
  249. assert(stk.idx < len(stk.items))
  250. stk.items[stk.idx] = val
  251. stk.idx += 1
  252. }
  253. pop :: #force_inline proc(stk: ^$T/Stack($V, $N)) {
  254. assert(stk.idx > 0)
  255. stk.idx -= 1
  256. }
  257. unclipped_rect := Rect{0, 0, 0x1000000, 0x1000000}
  258. default_style := Style {
  259. font = nil,
  260. size = {68, 10},
  261. padding = 5,
  262. spacing = 4,
  263. indent = 24,
  264. title_height = 24,
  265. footer_height = 20,
  266. scrollbar_size = 12,
  267. thumb_size = 8,
  268. colors = {
  269. .TEXT = {230, 230, 230, 255},
  270. .SELECTION_BG = {90, 90, 90, 255},
  271. .BORDER = {25, 25, 25, 255},
  272. .WINDOW_BG = {50, 50, 50, 255},
  273. .TITLE_BG = {25, 25, 25, 255},
  274. .TITLE_TEXT = {240, 240, 240, 255},
  275. .PANEL_BG = {0, 0, 0, 0},
  276. .BUTTON = {75, 75, 75, 255},
  277. .BUTTON_HOVER = {95, 95, 95, 255},
  278. .BUTTON_FOCUS = {115, 115, 115, 255},
  279. .BASE = {30, 30, 30, 255},
  280. .BASE_HOVER = {35, 35, 35, 255},
  281. .BASE_FOCUS = {40, 40, 40, 255},
  282. .SCROLL_BASE = {43, 43, 43, 255},
  283. .SCROLL_THUMB = {30, 30, 30, 255},
  284. },
  285. }
  286. expand_rect :: proc(rect: Rect, n: i32) -> Rect {
  287. return Rect{rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2}
  288. }
  289. intersect_rects :: proc(r1, r2: Rect) -> Rect {
  290. x1 := max(r1.x, r2.x)
  291. y1 := max(r1.y, r2.y)
  292. x2 := min(r1.x + r1.w, r2.x + r2.w)
  293. y2 := min(r1.y + r1.h, r2.y + r2.h)
  294. if x2 < x1 {x2 = x1}
  295. if y2 < y1 {y2 = y1}
  296. return Rect{x1, y1, x2 - x1, y2 - y1}
  297. }
  298. rect_overlaps_vec2 :: proc(r: Rect, p: Vec2) -> bool {
  299. return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h
  300. }
  301. @(private)
  302. default_draw_frame :: proc(ctx: ^Context, rect: Rect, colorid: Color_Type) {
  303. draw_rect(ctx, rect, ctx.style.colors[colorid])
  304. if colorid == .SCROLL_BASE || colorid == .SCROLL_THUMB || colorid == .TITLE_BG {
  305. return
  306. }
  307. if ctx.style.colors[.BORDER].a != 0 { /* draw border */
  308. draw_box(ctx, expand_rect(rect, 1), ctx.style.colors[.BORDER])
  309. }
  310. }
  311. init :: proc(
  312. ctx: ^Context,
  313. set_clipboard: proc(user_data: rawptr, text: string) -> (ok: bool) = nil,
  314. get_clipboard: proc(user_data: rawptr) -> (text: string, ok: bool) = nil,
  315. clipboard_user_data: rawptr = nil,
  316. ) {
  317. ctx^ = {} // zero memory
  318. ctx.draw_frame = default_draw_frame
  319. ctx._style = default_style
  320. ctx.style = &ctx._style
  321. ctx.text_input = strings.builder_from_bytes(ctx._text_store[:])
  322. ctx.textbox_state.set_clipboard = set_clipboard
  323. ctx.textbox_state.get_clipboard = get_clipboard
  324. ctx.textbox_state.clipboard_user_data = clipboard_user_data
  325. }
  326. begin :: proc(ctx: ^Context) {
  327. assert(ctx.text_width != nil, "ctx.text_width is not set")
  328. assert(ctx.text_height != nil, "ctx.text_height is not set")
  329. ctx.command_list.idx = 0
  330. ctx.root_list.idx = 0
  331. ctx.scroll_target = nil
  332. ctx.hover_root = ctx.next_hover_root
  333. ctx.next_hover_root = nil
  334. ctx.mouse_delta.x = ctx.mouse_pos.x - ctx.last_mouse_pos.x
  335. ctx.mouse_delta.y = ctx.mouse_pos.y - ctx.last_mouse_pos.y
  336. ctx.frame += 1
  337. }
  338. end :: proc(ctx: ^Context) {
  339. /* check stacks */
  340. assert(ctx.container_stack.idx == 0)
  341. assert(ctx.clip_stack.idx == 0)
  342. assert(ctx.id_stack.idx == 0)
  343. assert(ctx.layout_stack.idx == 0)
  344. /* handle scroll input */
  345. if ctx.scroll_target != nil {
  346. ctx.scroll_target.scroll.x += ctx.scroll_delta.x
  347. ctx.scroll_target.scroll.y += ctx.scroll_delta.y
  348. }
  349. /* unset focus if focus id was not touched this frame */
  350. if !ctx.updated_focus {
  351. ctx.focus_id = 0
  352. }
  353. ctx.updated_focus = false
  354. /* bring hover root to front if mouse was pressed */
  355. if mouse_pressed(ctx) &&
  356. ctx.next_hover_root != nil &&
  357. ctx.next_hover_root.zindex < ctx.last_zindex &&
  358. ctx.next_hover_root.zindex >= 0 {
  359. bring_to_front(ctx, ctx.next_hover_root)
  360. }
  361. /* reset input state */
  362. ctx.key_pressed_bits = {} // clear
  363. strings.builder_reset(&ctx.text_input)
  364. ctx.mouse_pressed_bits = {} // clear
  365. ctx.mouse_released_bits = {} // clear
  366. ctx.scroll_delta = Vec2{0, 0}
  367. ctx.last_mouse_pos = ctx.mouse_pos
  368. /* sort root containers by zindex */
  369. n := ctx.root_list.idx
  370. sort.quick_sort_proc(ctx.root_list.items[:n], proc(a, b: ^Container) -> int {
  371. return int(a.zindex) - int(b.zindex)
  372. })
  373. /* set root container jump commands */
  374. for i: i32 = 0; i < n; i += 1 {
  375. cnt := ctx.root_list.items[i]
  376. /* if this is the first container then make the first command jump to it.
  377. ** otherwise set the previous container's tail to jump to this one */
  378. if i == 0 {
  379. cmd := (^Command_Jump)(&ctx.command_list.items[0])
  380. cmd.dst = rawptr(uintptr(cnt.head) + size_of(Command_Jump))
  381. } else {
  382. prev := ctx.root_list.items[i - 1]
  383. prev.tail.variant.(^Command_Jump).dst = rawptr(
  384. uintptr(cnt.head) + size_of(Command_Jump),
  385. )
  386. }
  387. /* make the last container's tail jump to the end of command list */
  388. if i == n - 1 {
  389. cnt.tail.variant.(^Command_Jump).dst = rawptr(
  390. &ctx.command_list.items[ctx.command_list.idx],
  391. )
  392. }
  393. }
  394. }
  395. set_focus :: proc(ctx: ^Context, id: Id) {
  396. ctx.focus_id = id
  397. ctx.updated_focus = true
  398. }
  399. get_id :: proc {
  400. get_id_string,
  401. get_id_bytes,
  402. get_id_rawptr,
  403. get_id_uintptr,
  404. }
  405. get_id_string :: #force_inline proc(ctx: ^Context, str: string) -> Id {return get_id_bytes(
  406. ctx,
  407. transmute([]byte)str,
  408. )}
  409. get_id_rawptr :: #force_inline proc(
  410. ctx: ^Context,
  411. data: rawptr,
  412. size: int,
  413. ) -> Id {return get_id_bytes(ctx, ([^]u8)(data)[:size])}
  414. get_id_uintptr :: #force_inline proc(ctx: ^Context, ptr: uintptr) -> Id {
  415. ptr := ptr
  416. return get_id_bytes(ctx, ([^]u8)(&ptr)[:size_of(ptr)])
  417. }
  418. get_id_bytes :: proc(ctx: ^Context, bytes: []byte) -> Id {
  419. /* 32bit fnv-1a hash */
  420. HASH_INITIAL :: 2166136261
  421. hash :: proc(hash: ^Id, data: []byte) {
  422. size := len(data)
  423. cptr := ([^]u8)(raw_data(data))
  424. for ; size > 0; size -= 1 {
  425. hash^ = Id(u32(hash^) ~ u32(cptr[0])) * 16777619
  426. cptr = cptr[1:]
  427. }
  428. }
  429. idx := ctx.id_stack.idx
  430. res := ctx.id_stack.items[idx - 1] if idx > 0 else HASH_INITIAL
  431. hash(&res, bytes)
  432. ctx.last_id = res
  433. return res
  434. }
  435. push_id :: proc {
  436. push_id_string,
  437. push_id_bytes,
  438. push_id_rawptr,
  439. push_id_uintptr,
  440. }
  441. push_id_string :: #force_inline proc(ctx: ^Context, str: string) {push(
  442. &ctx.id_stack,
  443. get_id(ctx, str),
  444. )}
  445. push_id_rawptr :: #force_inline proc(ctx: ^Context, data: rawptr, size: int) {push(
  446. &ctx.id_stack,
  447. get_id(ctx, data, size),
  448. )}
  449. push_id_uintptr :: #force_inline proc(ctx: ^Context, ptr: uintptr) {push(
  450. &ctx.id_stack,
  451. get_id(ctx, ptr),
  452. )}
  453. push_id_bytes :: #force_inline proc(ctx: ^Context, bytes: []byte) {push(
  454. &ctx.id_stack,
  455. get_id(ctx, bytes),
  456. )}
  457. pop_id :: proc(ctx: ^Context) {
  458. pop(&ctx.id_stack)
  459. }
  460. push_clip_rect :: proc(ctx: ^Context, rect: Rect) {
  461. last := get_clip_rect(ctx)
  462. push(&ctx.clip_stack, intersect_rects(rect, last))
  463. }
  464. pop_clip_rect :: proc(ctx: ^Context) {
  465. pop(&ctx.clip_stack)
  466. }
  467. get_clip_rect :: proc(ctx: ^Context) -> Rect {
  468. assert(ctx.clip_stack.idx > 0)
  469. return ctx.clip_stack.items[ctx.clip_stack.idx - 1]
  470. }
  471. check_clip :: proc(ctx: ^Context, r: Rect) -> Clip {
  472. cr := get_clip_rect(ctx)
  473. if r.x > cr.x + cr.w || r.x + r.w < cr.x || r.y > cr.y + cr.h || r.y + r.h < cr.y {
  474. return .ALL
  475. }
  476. if r.x >= cr.x && r.x + r.w <= cr.x + cr.w && r.y >= cr.y && r.y + r.h <= cr.y + cr.h {
  477. return .NONE
  478. }
  479. return .PART
  480. }
  481. get_layout :: proc(ctx: ^Context) -> ^Layout {
  482. return &ctx.layout_stack.items[ctx.layout_stack.idx - 1]
  483. }
  484. @(private)
  485. push_layout :: proc(ctx: ^Context, body: Rect, scroll: Vec2) {
  486. layout: Layout
  487. layout.body = Rect{body.x - scroll.x, body.y - scroll.y, body.w, body.h}
  488. layout.max = Vec2{-0x1000000, -0x1000000}
  489. push(&ctx.layout_stack, layout)
  490. layout_row(ctx, {0})
  491. }
  492. @(private)
  493. pop_container :: proc(ctx: ^Context) {
  494. cnt := get_current_container(ctx)
  495. layout := get_layout(ctx)
  496. cnt.content_size.x = layout.max.x - layout.body.x
  497. cnt.content_size.y = layout.max.y - layout.body.y
  498. /* pop container, layout and id */
  499. pop(&ctx.container_stack)
  500. pop(&ctx.layout_stack)
  501. pop_id(ctx)
  502. }
  503. get_current_container :: proc(ctx: ^Context) -> ^Container {
  504. assert(ctx.container_stack.idx > 0)
  505. return ctx.container_stack.items[ctx.container_stack.idx - 1]
  506. }
  507. @(private)
  508. internal_get_container :: proc(ctx: ^Context, id: Id, opt: Options) -> ^Container {
  509. /* try to get existing container from pool */
  510. idx, ok := pool_get(ctx, ctx.container_pool[:], id)
  511. if ok {
  512. if ctx.containers[idx].open || .CLOSED not_in opt {
  513. pool_update(ctx, &ctx.container_pool[idx])
  514. }
  515. return &ctx.containers[idx]
  516. }
  517. if .CLOSED in opt {return nil}
  518. /* container not found in pool: init new container */
  519. idx = pool_init(ctx, ctx.container_pool[:], id)
  520. cnt := &ctx.containers[idx]
  521. cnt^ = {} // clear memory
  522. cnt.open = true
  523. bring_to_front(ctx, cnt)
  524. return cnt
  525. }
  526. get_container :: proc(ctx: ^Context, name: string, opt := Options{}) -> ^Container {
  527. id := get_id(ctx, name)
  528. return internal_get_container(ctx, id, opt)
  529. }
  530. bring_to_front :: proc(ctx: ^Context, cnt: ^Container) {
  531. ctx.last_zindex += 1
  532. cnt.zindex = ctx.last_zindex
  533. }
  534. /*============================================================================
  535. ** pool
  536. **============================================================================*/
  537. pool_init :: proc(ctx: ^Context, items: []Pool_Item, id: Id) -> int {
  538. f := ctx.frame
  539. n := -1
  540. for _, i in items {
  541. if items[i].last_update < f {
  542. f = items[i].last_update
  543. n = i
  544. }
  545. }
  546. assert(n > -1)
  547. items[n].id = id
  548. pool_update(ctx, &items[n])
  549. return n
  550. }
  551. pool_get :: proc(ctx: ^Context, items: []Pool_Item, id: Id) -> (int, bool) {
  552. for _, i in items {
  553. if items[i].id == id {
  554. return i, true
  555. }
  556. }
  557. return -1, false
  558. }
  559. pool_update :: proc(ctx: ^Context, item: ^Pool_Item) {
  560. item.last_update = ctx.frame
  561. }
  562. /*============================================================================
  563. ** input handlers
  564. **============================================================================*/
  565. input_mouse_move :: proc(ctx: ^Context, x, y: i32) {
  566. ctx.mouse_pos = Vec2{x, y}
  567. }
  568. input_mouse_down :: proc(ctx: ^Context, x, y: i32, btn: Mouse) {
  569. input_mouse_move(ctx, x, y)
  570. ctx.mouse_down_bits += {btn}
  571. ctx.mouse_pressed_bits += {btn}
  572. }
  573. input_mouse_up :: proc(ctx: ^Context, x, y: i32, btn: Mouse) {
  574. input_mouse_move(ctx, x, y)
  575. ctx.mouse_down_bits -= {btn}
  576. ctx.mouse_released_bits += {btn}
  577. }
  578. input_scroll :: proc(ctx: ^Context, x, y: i32) {
  579. ctx.scroll_delta.x += x
  580. ctx.scroll_delta.y += y
  581. }
  582. input_key_down :: proc(ctx: ^Context, key: Key) {
  583. ctx.key_pressed_bits += {key}
  584. ctx.key_down_bits += {key}
  585. }
  586. input_key_up :: proc(ctx: ^Context, key: Key) {
  587. ctx.key_down_bits -= {key}
  588. }
  589. input_text :: proc(ctx: ^Context, text: string) {
  590. strings.write_string(&ctx.text_input, text)
  591. }
  592. /*============================================================================
  593. ** commandlist
  594. **============================================================================*/
  595. push_command :: proc(ctx: ^Context, $Type: typeid, extra_size := 0) -> ^Type {
  596. size := i32(size_of(Type) + extra_size)
  597. cmd := transmute(^Type)&ctx.command_list.items[ctx.command_list.idx]
  598. assert(ctx.command_list.idx + size < COMMAND_LIST_SIZE)
  599. ctx.command_list.idx += size
  600. cmd.variant = cmd
  601. cmd.size = size
  602. return cmd
  603. }
  604. next_command :: proc "contextless" (ctx: ^Context, pcmd: ^^Command) -> bool {
  605. cmd := pcmd^
  606. defer pcmd^ = cmd
  607. if cmd != nil {
  608. cmd = (^Command)(uintptr(cmd) + uintptr(cmd.size))
  609. } else {
  610. cmd = (^Command)(&ctx.command_list.items[0])
  611. }
  612. invalid_command :: #force_inline proc "contextless" (ctx: ^Context) -> ^Command {
  613. return (^Command)(&ctx.command_list.items[ctx.command_list.idx])
  614. }
  615. for cmd != invalid_command(ctx) {
  616. if jmp, ok := cmd.variant.(^Command_Jump); ok {
  617. cmd = (^Command)(jmp.dst)
  618. continue
  619. }
  620. return true
  621. }
  622. return false
  623. }
  624. next_command_iterator :: proc "contextless" (
  625. ctx: ^Context,
  626. pcm: ^^Command,
  627. ) -> (
  628. Command_Variant,
  629. bool,
  630. ) {
  631. if next_command(ctx, pcm) {
  632. return pcm^.variant, true
  633. }
  634. return nil, false
  635. }
  636. @(private)
  637. push_jump :: proc(ctx: ^Context, dst: ^Command) -> ^Command {
  638. cmd := push_command(ctx, Command_Jump)
  639. cmd.dst = dst
  640. return cmd
  641. }
  642. set_clip :: proc(ctx: ^Context, rect: Rect) {
  643. cmd := push_command(ctx, Command_Clip)
  644. cmd.rect = rect
  645. }
  646. draw_rect :: proc(ctx: ^Context, rect: Rect, color: Color) {
  647. rect := rect
  648. rect = intersect_rects(rect, get_clip_rect(ctx))
  649. if rect.w > 0 && rect.h > 0 {
  650. cmd := push_command(ctx, Command_Rect)
  651. cmd.rect = rect
  652. cmd.color = color
  653. }
  654. }
  655. draw_box :: proc(ctx: ^Context, rect: Rect, color: Color) {
  656. draw_rect(ctx, Rect{rect.x + 1, rect.y, rect.w - 2, 1}, color)
  657. draw_rect(ctx, Rect{rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1}, color)
  658. draw_rect(ctx, Rect{rect.x, rect.y, 1, rect.h}, color)
  659. draw_rect(ctx, Rect{rect.x + rect.w - 1, rect.y, 1, rect.h}, color)
  660. }
  661. draw_text :: proc(ctx: ^Context, font: Font, str: string, pos: Vec2, color: Color) {
  662. rect := Rect{pos.x, pos.y, ctx.text_width(font, str), ctx.text_height(font)}
  663. clipped := check_clip(ctx, rect)
  664. switch clipped {
  665. case .NONE: // okay
  666. case .ALL:
  667. return
  668. case .PART:
  669. set_clip(ctx, get_clip_rect(ctx))
  670. }
  671. /* add command */
  672. text_cmd := push_command(ctx, Command_Text, len(str))
  673. text_cmd.pos = pos
  674. text_cmd.color = color
  675. text_cmd.font = font
  676. /* copy string */
  677. dst_str := ([^]byte)(text_cmd)[size_of(Command_Text):][:len(str)]
  678. copy(dst_str, str)
  679. text_cmd.str = string(dst_str)
  680. /* reset clipping if it was set */
  681. if clipped != .NONE {
  682. set_clip(ctx, unclipped_rect)
  683. }
  684. }
  685. draw_icon :: proc(ctx: ^Context, id: Icon, rect: Rect, color: Color) {
  686. /* do clip command if the rect isn't fully contained within the cliprect */
  687. clipped := check_clip(ctx, rect)
  688. switch clipped {
  689. case .NONE: // okay
  690. case .ALL:
  691. return
  692. case .PART:
  693. set_clip(ctx, get_clip_rect(ctx))
  694. }
  695. /* do icon command */
  696. cmd := push_command(ctx, Command_Icon)
  697. cmd.id = id
  698. cmd.rect = rect
  699. cmd.color = color
  700. /* reset clipping if it was set */
  701. if clipped != .NONE {
  702. set_clip(ctx, unclipped_rect)
  703. }
  704. }
  705. /*============================================================================
  706. ** layout
  707. **============================================================================*/
  708. layout_begin_column :: proc(ctx: ^Context) {
  709. push_layout(ctx, layout_next(ctx), Vec2{0, 0})
  710. }
  711. layout_end_column :: proc(ctx: ^Context) {
  712. b := get_layout(ctx)
  713. pop(&ctx.layout_stack)
  714. /* inherit position/next_row/max from child layout if they are greater */
  715. a := get_layout(ctx)
  716. a.position.x = max(a.position.x, b.position.x + b.body.x - a.body.x)
  717. a.next_row = max(a.next_row, b.next_row + b.body.y - a.body.y)
  718. a.max.x = max(a.max.x, b.max.x)
  719. a.max.y = max(a.max.y, b.max.y)
  720. }
  721. @(deferred_in = layout_end_column)
  722. layout_column :: proc(ctx: ^Context) -> bool {
  723. layout_begin_column(ctx)
  724. return true
  725. }
  726. layout_row :: proc(ctx: ^Context, widths: []i32, height: i32 = 0) {
  727. layout := get_layout(ctx)
  728. items := len(widths)
  729. if len(widths) > 0 {
  730. items = copy(layout.widths[:], widths[:])
  731. }
  732. layout.items = i32(items)
  733. layout.position = Vec2{layout.indent, layout.next_row}
  734. layout.size.y = height
  735. layout.item_index = 0
  736. }
  737. layout_row_items :: proc(ctx: ^Context, items: i32, height: i32 = 0) {
  738. layout := get_layout(ctx)
  739. layout.items = items
  740. layout.position = Vec2{layout.indent, layout.next_row}
  741. layout.size.y = height
  742. layout.item_index = 0
  743. }
  744. layout_width :: proc(ctx: ^Context, width: i32) {
  745. get_layout(ctx).size.x = width
  746. }
  747. layout_height :: proc(ctx: ^Context, height: i32) {
  748. get_layout(ctx).size.y = height
  749. }
  750. layout_set_next :: proc(ctx: ^Context, r: Rect, relative: bool) {
  751. layout := get_layout(ctx)
  752. layout.next = r
  753. layout.next_type = .RELATIVE if relative else .ABSOLUTE
  754. }
  755. layout_next :: proc(ctx: ^Context) -> (res: Rect) {
  756. layout := get_layout(ctx)
  757. style := ctx.style
  758. defer ctx.last_rect = res
  759. if layout.next_type != .NONE {
  760. /* handle rect set by `layout_set_next` */
  761. type := layout.next_type
  762. layout.next_type = .NONE
  763. res = layout.next
  764. if type == .ABSOLUTE {
  765. return
  766. }
  767. } else {
  768. /* handle next row */
  769. if layout.item_index == layout.items {
  770. layout_row_items(ctx, layout.items, layout.size.y)
  771. }
  772. /* position */
  773. res.x = layout.position.x
  774. res.y = layout.position.y
  775. /* size */
  776. res.w = layout.items > 0 ? layout.widths[layout.item_index] : layout.size.x
  777. res.h = layout.size.y
  778. if res.w == 0 {res.w = style.size.x + style.padding * 2}
  779. if res.h == 0 {res.h = style.size.y + style.padding * 2}
  780. if res.w < 0 {res.w += layout.body.w - res.x + 1}
  781. if res.h < 0 {res.h += layout.body.h - res.y + 1}
  782. layout.item_index += 1
  783. }
  784. /* update position */
  785. layout.position.x += res.w + style.spacing
  786. layout.next_row = max(layout.next_row, res.y + res.h + style.spacing)
  787. /* apply body offset */
  788. res.x += layout.body.x
  789. res.y += layout.body.y
  790. /* update max position */
  791. layout.max.x = max(layout.max.x, res.x + res.w)
  792. layout.max.y = max(layout.max.y, res.y + res.h)
  793. return
  794. }
  795. /*============================================================================
  796. ** controls
  797. **============================================================================*/
  798. @(private)
  799. in_hover_root :: proc(ctx: ^Context) -> bool {
  800. for i := ctx.container_stack.idx - 1; i >= 0; i -= 1 {
  801. if ctx.container_stack.items[i] == ctx.hover_root {
  802. return true
  803. }
  804. /* only root containers have their `head` field set; stop searching if we've
  805. ** reached the current root container */
  806. if ctx.container_stack.items[i].head != nil {
  807. break
  808. }
  809. }
  810. return false
  811. }
  812. draw_control_frame :: proc(
  813. ctx: ^Context,
  814. id: Id,
  815. rect: Rect,
  816. colorid: Color_Type,
  817. opt := Options{},
  818. ) {
  819. if .NO_FRAME in opt {
  820. return
  821. }
  822. assert(colorid == .BUTTON || colorid == .BASE)
  823. colorid := colorid
  824. colorid = Color_Type(
  825. int(colorid) + int((ctx.focus_id == id) ? 2 : (ctx.hover_id == id) ? 1 : 0),
  826. )
  827. ctx.draw_frame(ctx, rect, colorid)
  828. }
  829. draw_control_text :: proc(
  830. ctx: ^Context,
  831. str: string,
  832. rect: Rect,
  833. colorid: Color_Type,
  834. opt := Options{},
  835. ) {
  836. pos: Vec2
  837. font := ctx.style.font
  838. tw := ctx.text_width(font, str)
  839. push_clip_rect(ctx, rect)
  840. pos.y = rect.y + (rect.h - ctx.text_height(font)) / 2
  841. if .ALIGN_CENTER in opt {
  842. pos.x = rect.x + (rect.w - tw) / 2
  843. } else if .ALIGN_RIGHT in opt {
  844. pos.x = rect.x + rect.w - tw - ctx.style.padding
  845. } else {
  846. pos.x = rect.x + ctx.style.padding
  847. }
  848. draw_text(ctx, font, str, pos, ctx.style.colors[colorid])
  849. pop_clip_rect(ctx)
  850. }
  851. mouse_over :: proc(ctx: ^Context, rect: Rect) -> bool {
  852. return(
  853. rect_overlaps_vec2(rect, ctx.mouse_pos) &&
  854. rect_overlaps_vec2(get_clip_rect(ctx), ctx.mouse_pos) &&
  855. in_hover_root(ctx) \
  856. )
  857. }
  858. update_control :: proc(ctx: ^Context, id: Id, rect: Rect, opt := Options{}) {
  859. mouseover := mouse_over(ctx, rect)
  860. if ctx.focus_id == id {
  861. ctx.updated_focus = true
  862. }
  863. if .NO_INTERACT in opt {
  864. return
  865. }
  866. if mouseover && !mouse_down(ctx) {
  867. ctx.hover_id = id
  868. }
  869. if ctx.focus_id == id {
  870. if mouse_pressed(ctx) && !mouseover {
  871. set_focus(ctx, 0)
  872. }
  873. if !mouse_down(ctx) && .HOLD_FOCUS not_in opt {
  874. set_focus(ctx, 0)
  875. }
  876. }
  877. if ctx.hover_id == id {
  878. if mouse_pressed(ctx) {
  879. set_focus(ctx, id)
  880. } else if !mouseover {
  881. ctx.hover_id = 0
  882. }
  883. }
  884. }
  885. text :: proc(ctx: ^Context, text: string) {
  886. text := text
  887. font := ctx.style.font
  888. color := ctx.style.colors[.TEXT]
  889. layout_begin_column(ctx)
  890. layout_row(ctx, {-1}, ctx.text_height(font))
  891. for len(text) > 0 {
  892. w: i32
  893. start: int
  894. end: int = len(text)
  895. r := layout_next(ctx)
  896. for ch, i in text {
  897. if ch == ' ' || ch == '\n' {
  898. word := text[start:i]
  899. w += ctx.text_width(font, word)
  900. if w > r.w && start != 0 {
  901. end = start
  902. break
  903. }
  904. w += ctx.text_width(font, text[i:i + 1])
  905. if ch == '\n' {
  906. end = i + 1
  907. break
  908. }
  909. start = i + 1
  910. }
  911. }
  912. draw_text(ctx, font, text[:end], Vec2{r.x, r.y}, color)
  913. text = text[end:]
  914. }
  915. layout_end_column(ctx)
  916. }
  917. label :: proc(ctx: ^Context, text: string) {
  918. draw_control_text(ctx, text, layout_next(ctx), .TEXT)
  919. }
  920. button :: proc(
  921. ctx: ^Context,
  922. label: string,
  923. icon: Icon = .NONE,
  924. opt: Options = {.ALIGN_CENTER},
  925. ) -> (
  926. res: Result_Set,
  927. ) {
  928. id := len(label) > 0 ? get_id(ctx, label) : get_id(ctx, uintptr(icon))
  929. r := layout_next(ctx)
  930. update_control(ctx, id, r, opt)
  931. /* handle click */
  932. if ctx.mouse_pressed_bits == {.LEFT} && ctx.focus_id == id {
  933. res += {.SUBMIT}
  934. }
  935. /* draw */
  936. draw_control_frame(ctx, id, r, .BUTTON, opt)
  937. if len(label) > 0 {
  938. draw_control_text(ctx, label, r, .TEXT, opt)
  939. }
  940. if icon != .NONE {
  941. draw_icon(ctx, icon, r, ctx.style.colors[.TEXT])
  942. }
  943. return
  944. }
  945. checkbox :: proc(ctx: ^Context, label: string, state: ^bool) -> (res: Result_Set) {
  946. id := get_id(ctx, uintptr(state))
  947. r := layout_next(ctx)
  948. box := Rect{r.x, r.y, r.h, r.h}
  949. update_control(ctx, id, r, {})
  950. /* handle click */
  951. if .LEFT in ctx.mouse_released_bits && ctx.hover_id == id {
  952. res += {.CHANGE}
  953. state^ = !state^
  954. }
  955. /* draw */
  956. draw_control_frame(ctx, id, box, .BASE, {})
  957. if state^ {
  958. draw_icon(ctx, .CHECK, box, ctx.style.colors[.TEXT])
  959. }
  960. r = Rect{r.x + box.w, r.y, r.w - box.w, r.h}
  961. draw_control_text(ctx, label, r, .TEXT)
  962. return
  963. }
  964. textbox_raw :: proc(
  965. ctx: ^Context,
  966. textbuf: []u8,
  967. textlen: ^int,
  968. id: Id,
  969. r: Rect,
  970. opt := Options{},
  971. ) -> (
  972. res: Result_Set,
  973. ) {
  974. update_control(ctx, id, r, opt | {.HOLD_FOCUS})
  975. font := ctx.style.font
  976. if ctx.focus_id == id {
  977. /* create a builder backed by the user's buffer */
  978. builder := strings.builder_from_bytes(textbuf)
  979. non_zero_resize(&builder.buf, textlen^)
  980. ctx.textbox_state.builder = &builder
  981. if ctx.textbox_state.id != u64(id) {
  982. ctx.textbox_state.id = u64(id)
  983. ctx.textbox_state.selection = {}
  984. }
  985. /* check selection bounds */
  986. if ctx.textbox_state.selection[0] > textlen^ || ctx.textbox_state.selection[1] > textlen^ {
  987. ctx.textbox_state.selection = {}
  988. }
  989. /* handle text input */
  990. // if strings.builder_len(ctx.text_input) > 0 {
  991. // if textedit.input_text(&ctx.textbox_state, strings.to_string(ctx.text_input)) > 0 {
  992. // textlen^ = strings.builder_len(builder)
  993. // res += {.CHANGE}
  994. // }
  995. // }
  996. /* handle ctrl+a */
  997. if .A in ctx.key_pressed_bits &&
  998. .CTRL in ctx.key_down_bits &&
  999. .ALT not_in ctx.key_down_bits {
  1000. ctx.textbox_state.selection = {textlen^, 0}
  1001. }
  1002. /* handle ctrl+x */
  1003. if .X in ctx.key_pressed_bits &&
  1004. .CTRL in ctx.key_down_bits &&
  1005. .ALT not_in ctx.key_down_bits {
  1006. if textedit.cut(&ctx.textbox_state) {
  1007. textlen^ = strings.builder_len(builder)
  1008. res += {.CHANGE}
  1009. }
  1010. }
  1011. /* handle ctrl+c */
  1012. if .C in ctx.key_pressed_bits &&
  1013. .CTRL in ctx.key_down_bits &&
  1014. .ALT not_in ctx.key_down_bits {
  1015. textedit.copy(&ctx.textbox_state)
  1016. }
  1017. /* handle ctrl+v */
  1018. if .V in ctx.key_pressed_bits &&
  1019. .CTRL in ctx.key_down_bits &&
  1020. .ALT not_in ctx.key_down_bits {
  1021. if textedit.paste(&ctx.textbox_state) {
  1022. textlen^ = strings.builder_len(builder)
  1023. res += {.CHANGE}
  1024. }
  1025. }
  1026. /* handle left/right */
  1027. if .LEFT in ctx.key_pressed_bits {
  1028. move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
  1029. if .SHIFT in ctx.key_down_bits {
  1030. textedit.select_to(&ctx.textbox_state, move)
  1031. } else {
  1032. textedit.move_to(&ctx.textbox_state, move)
  1033. }
  1034. }
  1035. if .RIGHT in ctx.key_pressed_bits {
  1036. move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
  1037. if .SHIFT in ctx.key_down_bits {
  1038. textedit.select_to(&ctx.textbox_state, move)
  1039. } else {
  1040. textedit.move_to(&ctx.textbox_state, move)
  1041. }
  1042. }
  1043. /* handle home/end */
  1044. if .HOME in ctx.key_pressed_bits {
  1045. if .SHIFT in ctx.key_down_bits {
  1046. textedit.select_to(&ctx.textbox_state, .Start)
  1047. } else {
  1048. textedit.move_to(&ctx.textbox_state, .Start)
  1049. }
  1050. }
  1051. if .END in ctx.key_pressed_bits {
  1052. if .SHIFT in ctx.key_down_bits {
  1053. textedit.select_to(&ctx.textbox_state, .End)
  1054. } else {
  1055. textedit.move_to(&ctx.textbox_state, .End)
  1056. }
  1057. }
  1058. /* handle backspace/delete */
  1059. if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 {
  1060. move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
  1061. textedit.delete_to(&ctx.textbox_state, move)
  1062. textlen^ = strings.builder_len(builder)
  1063. res += {.CHANGE}
  1064. }
  1065. if .DELETE in ctx.key_pressed_bits && textlen^ > 0 {
  1066. move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
  1067. textedit.delete_to(&ctx.textbox_state, move)
  1068. textlen^ = strings.builder_len(builder)
  1069. res += {.CHANGE}
  1070. }
  1071. /* handle return */
  1072. if .RETURN in ctx.key_pressed_bits {
  1073. set_focus(ctx, 0)
  1074. res += {.SUBMIT}
  1075. }
  1076. /* handle click/drag */
  1077. if .LEFT in ctx.mouse_down_bits {
  1078. idx := textlen^
  1079. for i in 0 ..< textlen^ {
  1080. /* skip continuation bytes */
  1081. if textbuf[i] >= 0x80 && textbuf[i] < 0xc0 {
  1082. continue
  1083. }
  1084. if ctx.mouse_pos.x <
  1085. r.x + ctx.textbox_offset + ctx.text_width(font, string(textbuf[:i])) {
  1086. idx = i
  1087. break
  1088. }
  1089. }
  1090. ctx.textbox_state.selection[0] = idx
  1091. if .LEFT in ctx.mouse_pressed_bits && .SHIFT not_in ctx.key_down_bits {
  1092. ctx.textbox_state.selection[1] = idx
  1093. }
  1094. }
  1095. }
  1096. textstr := string(textbuf[:textlen^])
  1097. /* draw */
  1098. draw_control_frame(ctx, id, r, .BASE, opt)
  1099. if ctx.focus_id == id {
  1100. text_color := ctx.style.colors[.TEXT]
  1101. sel_color := ctx.style.colors[.SELECTION_BG]
  1102. textw := ctx.text_width(font, textstr)
  1103. texth := ctx.text_height(font)
  1104. headx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[0]])
  1105. tailx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[1]])
  1106. ofmin := max(ctx.style.padding - headx, r.w - textw - ctx.style.padding)
  1107. ofmax := min(r.w - headx - ctx.style.padding, ctx.style.padding)
  1108. ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax)
  1109. textx := r.x + ctx.textbox_offset
  1110. texty := r.y + (r.h - texth) / 2
  1111. push_clip_rect(ctx, r)
  1112. draw_rect(
  1113. ctx,
  1114. Rect{textx + min(headx, tailx), texty, abs(headx - tailx), texth},
  1115. sel_color,
  1116. )
  1117. draw_text(ctx, font, textstr, Vec2{textx, texty}, text_color)
  1118. draw_rect(ctx, Rect{textx + headx, texty, 1, texth}, text_color)
  1119. pop_clip_rect(ctx)
  1120. } else {
  1121. draw_control_text(ctx, textstr, r, .TEXT, opt)
  1122. }
  1123. return
  1124. }
  1125. @(private)
  1126. parse_real :: #force_inline proc(s: string) -> (Real, bool) {
  1127. f, ok := strconv.parse_f64(s)
  1128. return Real(f), ok
  1129. }
  1130. number_textbox :: proc(ctx: ^Context, value: ^Real, r: Rect, id: Id, fmt_string: string) -> bool {
  1131. if ctx.mouse_pressed_bits == {.LEFT} && .SHIFT in ctx.key_down_bits && ctx.hover_id == id {
  1132. ctx.number_edit_id = id
  1133. nstr := fmt.bprintf(ctx.number_edit_buf[:], fmt_string, value^)
  1134. ctx.number_edit_len = len(nstr)
  1135. }
  1136. if ctx.number_edit_id == id {
  1137. res := textbox_raw(ctx, ctx.number_edit_buf[:], &ctx.number_edit_len, id, r, {})
  1138. if .SUBMIT in res || ctx.focus_id != id {
  1139. value^, _ = parse_real(string(ctx.number_edit_buf[:ctx.number_edit_len]))
  1140. ctx.number_edit_id = 0
  1141. } else {
  1142. return true
  1143. }
  1144. }
  1145. return false
  1146. }
  1147. textbox :: proc(ctx: ^Context, buf: []u8, textlen: ^int, opt := Options{}) -> Result_Set {
  1148. id := get_id(ctx, uintptr(&buf[0]))
  1149. r := layout_next(ctx)
  1150. return textbox_raw(ctx, buf, textlen, id, r, opt)
  1151. }
  1152. slider :: proc(
  1153. ctx: ^Context,
  1154. value: ^Real,
  1155. low, high: Real,
  1156. step: Real = 0.0,
  1157. fmt_string: string = SLIDER_FMT,
  1158. opt: Options = {.ALIGN_CENTER},
  1159. ) -> (
  1160. res: Result_Set,
  1161. ) {
  1162. last := value^
  1163. v := last
  1164. id := get_id(ctx, uintptr(value))
  1165. base := layout_next(ctx)
  1166. /* handle text input mode */
  1167. if number_textbox(ctx, &v, base, id, fmt_string) {
  1168. return
  1169. }
  1170. /* handle normal mode */
  1171. update_control(ctx, id, base, opt)
  1172. /* handle input */
  1173. if ctx.focus_id == id && ctx.mouse_down_bits == {.LEFT} {
  1174. v = low + Real(ctx.mouse_pos.x - base.x) * (high - low) / Real(base.w)
  1175. if step != 0.0 {
  1176. v = math.floor((v + step / 2) / step) * step
  1177. }
  1178. }
  1179. /* clamp and store value, update res */
  1180. v = clamp(v, low, high);value^ = v
  1181. if last != v {
  1182. res += {.CHANGE}
  1183. }
  1184. /* draw base */
  1185. draw_control_frame(ctx, id, base, .BASE, opt)
  1186. /* draw thumb */
  1187. w := ctx.style.thumb_size
  1188. x := i32((v - low) * Real(base.w - w) / (high - low))
  1189. thumb := Rect{base.x + x, base.y, w, base.h}
  1190. draw_control_frame(ctx, id, thumb, .BUTTON, opt)
  1191. /* draw text */
  1192. text_buf: [4096]byte
  1193. draw_control_text(ctx, fmt.bprintf(text_buf[:], fmt_string, v), base, .TEXT, opt)
  1194. return
  1195. }
  1196. number :: proc(
  1197. ctx: ^Context,
  1198. value: ^Real,
  1199. step: Real,
  1200. fmt_string: string = SLIDER_FMT,
  1201. opt: Options = {.ALIGN_CENTER},
  1202. ) -> (
  1203. res: Result_Set,
  1204. ) {
  1205. id := get_id(ctx, uintptr(value))
  1206. base := layout_next(ctx)
  1207. last := value^
  1208. /* handle text input mode */
  1209. if number_textbox(ctx, value, base, id, fmt_string) {
  1210. return
  1211. }
  1212. /* handle normal mode */
  1213. update_control(ctx, id, base, opt)
  1214. /* handle input */
  1215. if ctx.focus_id == id && ctx.mouse_down_bits == {.LEFT} {
  1216. value^ += Real(ctx.mouse_delta.x) * step
  1217. }
  1218. /* set flag if value changed */
  1219. if value^ != last {
  1220. res += {.CHANGE}
  1221. }
  1222. /* draw base */
  1223. draw_control_frame(ctx, id, base, .BASE, opt)
  1224. /* draw text */
  1225. text_buf: [4096]byte
  1226. draw_control_text(ctx, fmt.bprintf(text_buf[:], fmt_string, value^), base, .TEXT, opt)
  1227. return
  1228. }
  1229. @(private)
  1230. _header :: proc(ctx: ^Context, label: string, is_treenode: bool, opt := Options{}) -> Result_Set {
  1231. id := get_id(ctx, label)
  1232. idx, active := pool_get(ctx, ctx.treenode_pool[:], id)
  1233. expanded := .EXPANDED in opt ? !active : active
  1234. layout_row(ctx, {-1})
  1235. r := layout_next(ctx)
  1236. update_control(ctx, id, r, {})
  1237. /* handle click */
  1238. if ctx.mouse_pressed_bits == {.LEFT} && ctx.focus_id == id {
  1239. active = !active
  1240. }
  1241. /* update pool ref */
  1242. if idx >= 0 {
  1243. if active {
  1244. pool_update(ctx, &ctx.treenode_pool[idx])
  1245. } else {
  1246. ctx.treenode_pool[idx] = {}
  1247. }
  1248. } else if active {
  1249. pool_init(ctx, ctx.treenode_pool[:], id)
  1250. }
  1251. /* draw */
  1252. if is_treenode {
  1253. if ctx.hover_id == id {
  1254. ctx.draw_frame(ctx, r, .BUTTON_HOVER)
  1255. }
  1256. } else {
  1257. draw_control_frame(ctx, id, r, .BUTTON)
  1258. }
  1259. draw_icon(
  1260. ctx,
  1261. expanded ? .EXPANDED : .COLLAPSED,
  1262. Rect{r.x, r.y, r.h, r.h},
  1263. ctx.style.colors[.TEXT],
  1264. )
  1265. r.x += r.h - ctx.style.padding
  1266. r.w -= r.h - ctx.style.padding
  1267. draw_control_text(ctx, label, r, .TEXT)
  1268. return expanded ? {.ACTIVE} : {}
  1269. }
  1270. header :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
  1271. return _header(ctx, label, false, opt)
  1272. }
  1273. begin_treenode :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
  1274. res := _header(ctx, label, true, opt)
  1275. if .ACTIVE in res {
  1276. get_layout(ctx).indent += ctx.style.indent
  1277. push(&ctx.id_stack, ctx.last_id)
  1278. }
  1279. return res
  1280. }
  1281. end_treenode :: proc(ctx: ^Context) {
  1282. get_layout(ctx).indent -= ctx.style.indent
  1283. pop_id(ctx)
  1284. }
  1285. scoped_end_treenode :: proc(ctx: ^Context, _: string, _: Options, result_set: Result_Set) {
  1286. if result_set != nil {
  1287. end_treenode(ctx)
  1288. }
  1289. }
  1290. /* This is scoped and is intended to be use in the condition of a if-statement */
  1291. @(deferred_in_out = scoped_end_treenode)
  1292. treenode :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
  1293. return begin_treenode(ctx, label, opt)
  1294. }
  1295. @(private)
  1296. scrollbar :: proc(ctx: ^Context, cnt: ^Container, _b: ^Rect, cs: Vec2, id_string: string, i: int) {
  1297. b := (^struct {
  1298. pos, size: [2]i32,
  1299. })(_b)
  1300. #assert(size_of(b^) == size_of(_b^))
  1301. /* only add scrollbar if content size is larger than body */
  1302. maxscroll := cs[i] - b.size[i]
  1303. contentsize := b.size[i]
  1304. if maxscroll > 0 && contentsize > 0 {
  1305. id := get_id(ctx, id_string)
  1306. /* get sizing / positioning */
  1307. base := b^
  1308. base.pos[1 - i] = b.pos[1 - i] + b.size[1 - i]
  1309. base.size[1 - i] = ctx.style.scrollbar_size
  1310. /* handle input */
  1311. update_control(ctx, id, transmute(Rect)base)
  1312. if ctx.focus_id == id && .LEFT in ctx.mouse_down_bits {
  1313. cnt.scroll[i] += ctx.mouse_delta[i] * cs[i] / base.size[i]
  1314. }
  1315. /* clamp scroll to limits */
  1316. cnt.scroll[i] = clamp(cnt.scroll[i], 0, maxscroll)
  1317. /* draw base and thumb */
  1318. ctx.draw_frame(ctx, transmute(Rect)base, .SCROLL_BASE)
  1319. thumb := base
  1320. thumb.size[i] = max(ctx.style.thumb_size, base.size[i] * b.size[i] / cs[i])
  1321. thumb.pos[i] += cnt.scroll[i] * (base.size[i] - thumb.size[i]) / maxscroll
  1322. ctx.draw_frame(ctx, transmute(Rect)thumb, .SCROLL_THUMB)
  1323. /* set this as the scroll_target (will get scrolled on mousewheel) */
  1324. /* if the mouse is over it */
  1325. if mouse_over(ctx, transmute(Rect)b^) {
  1326. ctx.scroll_target = cnt
  1327. }
  1328. } else {
  1329. cnt.scroll[i] = 0
  1330. }
  1331. }
  1332. @(private)
  1333. scrollbars :: proc(ctx: ^Context, cnt: ^Container, body: ^Rect) {
  1334. sz := ctx.style.scrollbar_size
  1335. cs := cnt.content_size
  1336. cs.x += ctx.style.padding * 2
  1337. cs.y += ctx.style.padding * 2
  1338. push_clip_rect(ctx, body^)
  1339. /* resize body to make room for scrollbars */
  1340. if cs.y > cnt.body.h {body.w -= sz}
  1341. if cs.x > cnt.body.w {body.h -= sz}
  1342. /* to create a horizontal or vertical scrollbar almost-identical code is
  1343. ** used; only the references to `x|y` `w|h` need to be switched */
  1344. scrollbar(ctx, cnt, body, cs, "!scrollbarv", 1) // 1 = y,h
  1345. scrollbar(ctx, cnt, body, cs, "!scrollbarh", 0) // 0 = x,w
  1346. pop_clip_rect(ctx)
  1347. }
  1348. @(private)
  1349. push_container_body :: proc(ctx: ^Context, cnt: ^Container, body: Rect, opt := Options{}) {
  1350. body := body
  1351. if .NO_SCROLL not_in opt {
  1352. scrollbars(ctx, cnt, &body)
  1353. }
  1354. push_layout(ctx, expand_rect(body, -ctx.style.padding), cnt.scroll)
  1355. cnt.body = body
  1356. }
  1357. @(private)
  1358. begin_root_container :: proc(ctx: ^Context, cnt: ^Container) {
  1359. push(&ctx.container_stack, cnt)
  1360. /* push container to roots list and push head command */
  1361. push(&ctx.root_list, cnt)
  1362. cnt.head = push_jump(ctx, nil)
  1363. /* set as hover root if the mouse is overlapping this container and it has a
  1364. ** higher zindex than the current hover root */
  1365. if rect_overlaps_vec2(cnt.rect, ctx.mouse_pos) &&
  1366. (ctx.next_hover_root == nil || cnt.zindex > ctx.next_hover_root.zindex) {
  1367. ctx.next_hover_root = cnt
  1368. }
  1369. /* clipping is reset here in case a root-container is made within
  1370. ** another root-containers's begin/end block; this prevents the inner
  1371. ** root-container being clipped to the outer */
  1372. push(&ctx.clip_stack, unclipped_rect)
  1373. }
  1374. @(private)
  1375. end_root_container :: proc(ctx: ^Context) {
  1376. /* push tail 'goto' jump command and set head 'skip' command. the final steps
  1377. ** on initing these are done in end() */
  1378. cnt := get_current_container(ctx)
  1379. cnt.tail = push_jump(ctx, nil)
  1380. cnt.head.variant.(^Command_Jump).dst = &ctx.command_list.items[ctx.command_list.idx]
  1381. /* pop base clip rect and container */
  1382. pop_clip_rect(ctx)
  1383. pop_container(ctx)
  1384. }
  1385. begin_window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{}) -> bool {
  1386. assert(title != "", "missing window title")
  1387. id := get_id(ctx, title)
  1388. cnt := internal_get_container(ctx, id, opt)
  1389. if cnt == nil || !cnt.open {
  1390. return false
  1391. }
  1392. push(&ctx.id_stack, id)
  1393. rect := rect
  1394. if cnt.rect.w == 0 {
  1395. cnt.rect = rect
  1396. }
  1397. begin_root_container(ctx, cnt)
  1398. rect = cnt.rect
  1399. body := cnt.rect
  1400. /* draw frame */
  1401. if .NO_FRAME not_in opt {
  1402. ctx.draw_frame(ctx, rect, .WINDOW_BG)
  1403. }
  1404. /* do title bar */
  1405. if .NO_TITLE not_in opt {
  1406. tr := rect
  1407. tr.h = ctx.style.title_height
  1408. ctx.draw_frame(ctx, tr, .TITLE_BG)
  1409. /* do title text */
  1410. if .NO_TITLE not_in opt {
  1411. tid := get_id(ctx, "!title")
  1412. update_control(ctx, tid, tr, opt)
  1413. draw_control_text(ctx, title, tr, .TITLE_TEXT, opt)
  1414. if tid == ctx.focus_id && ctx.mouse_down_bits == {.LEFT} {
  1415. cnt.rect.x += ctx.mouse_delta.x
  1416. cnt.rect.y += ctx.mouse_delta.y
  1417. }
  1418. body.y += tr.h
  1419. body.h -= tr.h
  1420. }
  1421. /* do `close` button */
  1422. if .NO_CLOSE not_in opt {
  1423. cid := get_id(ctx, "!close")
  1424. r := Rect{tr.x + tr.w - tr.h, tr.y, tr.h, tr.h}
  1425. tr.w -= r.w
  1426. draw_icon(ctx, .CLOSE, r, ctx.style.colors[.TITLE_TEXT])
  1427. update_control(ctx, cid, r, opt)
  1428. if .LEFT in ctx.mouse_released_bits && cid == ctx.hover_id {
  1429. cnt.open = false
  1430. }
  1431. }
  1432. }
  1433. /* do `resize` handle */
  1434. if .NO_RESIZE not_in opt {
  1435. @(static)
  1436. tmp_start: Vec2
  1437. @(static)
  1438. tmp_size: Vec2
  1439. sz := ctx.style.footer_height
  1440. rid := get_id(ctx, "!resize")
  1441. r := Rect{rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz}
  1442. draw_icon(ctx, .RESIZE, r, ctx.style.colors[.TEXT])
  1443. update_control(ctx, rid, r, opt)
  1444. if rid == ctx.focus_id && .LEFT in ctx.mouse_pressed_bits {
  1445. tmp_start = ctx.mouse_pos
  1446. tmp_size = {cnt.rect.w, cnt.rect.h}
  1447. }
  1448. if rid == ctx.focus_id && .LEFT in ctx.mouse_down_bits {
  1449. cnt.rect.w = max(96, tmp_size.x + ctx.mouse_pos.x - tmp_start.x)
  1450. cnt.rect.h = max(64, tmp_size.y + ctx.mouse_pos.y - tmp_start.y)
  1451. }
  1452. body.h -= sz
  1453. }
  1454. push_container_body(ctx, cnt, body, opt)
  1455. /* resize to content size */
  1456. if .AUTO_SIZE in opt {
  1457. r := get_layout(ctx).body
  1458. cnt.rect.w = cnt.content_size.x + (cnt.rect.w - r.w)
  1459. cnt.rect.h = cnt.content_size.y + (cnt.rect.h - r.h)
  1460. }
  1461. /* close if this is a popup window and elsewhere was clicked */
  1462. if .POPUP in opt && mouse_pressed(ctx) && ctx.hover_root != cnt {
  1463. cnt.open = false
  1464. }
  1465. push_clip_rect(ctx, cnt.body)
  1466. return true
  1467. }
  1468. end_window :: proc(ctx: ^Context) {
  1469. pop_clip_rect(ctx)
  1470. end_root_container(ctx)
  1471. }
  1472. /* This is scoped and is intended to be use in the condition of a if-statement */
  1473. @(deferred_in_out = scoped_end_window)
  1474. window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{}) -> bool {
  1475. return begin_window(ctx, title, rect, opt)
  1476. }
  1477. scoped_end_window :: proc(ctx: ^Context, _: string, _: Rect, _: Options, ok: bool) {
  1478. if ok {
  1479. end_window(ctx)
  1480. }
  1481. }
  1482. open_popup :: proc(ctx: ^Context, name: string) {
  1483. cnt := get_container(ctx, name)
  1484. /* set as hover root so popup isn't closed in begin_window() */
  1485. ctx.hover_root = cnt
  1486. ctx.next_hover_root = cnt
  1487. /* position at mouse cursor, open and bring-to-front */
  1488. cnt.rect = Rect{ctx.mouse_pos.x, ctx.mouse_pos.y, 1, 1}
  1489. cnt.open = true
  1490. bring_to_front(ctx, cnt)
  1491. }
  1492. begin_popup :: proc(ctx: ^Context, name: string) -> bool {
  1493. opt := Options{.POPUP, .AUTO_SIZE, .NO_RESIZE, .NO_SCROLL, .NO_TITLE, .CLOSED}
  1494. return begin_window(ctx, name, Rect{}, opt)
  1495. }
  1496. end_popup :: proc(ctx: ^Context) {
  1497. end_window(ctx)
  1498. }
  1499. /* This is scoped and is intended to be use in the condition of a if-statement */
  1500. @(deferred_in_out = scoped_end_popup)
  1501. popup :: proc(ctx: ^Context, name: string) -> bool {
  1502. return begin_popup(ctx, name)
  1503. }
  1504. scoped_end_popup :: proc(ctx: ^Context, _: string, ok: bool) {
  1505. if ok {
  1506. end_popup(ctx)
  1507. }
  1508. }
  1509. begin_panel :: proc(ctx: ^Context, name: string, opt := Options{}) {
  1510. assert(name != "", "missing panel name")
  1511. push_id(ctx, name)
  1512. cnt := internal_get_container(ctx, ctx.last_id, opt)
  1513. cnt.rect = layout_next(ctx)
  1514. if .NO_FRAME not_in opt {
  1515. ctx.draw_frame(ctx, cnt.rect, .PANEL_BG)
  1516. }
  1517. push(&ctx.container_stack, cnt)
  1518. push_container_body(ctx, cnt, cnt.rect, opt)
  1519. push_clip_rect(ctx, cnt.body)
  1520. }
  1521. end_panel :: proc(ctx: ^Context) {
  1522. pop_clip_rect(ctx)
  1523. pop_container(ctx)
  1524. }
  1525. @(private)
  1526. mouse_released :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_released_bits != nil}
  1527. @(private)
  1528. mouse_pressed :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_pressed_bits != nil}
  1529. @(private)
  1530. mouse_down :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_down_bits != nil}