microui.odin (21247B)
1 /* 2 This example showcases Blend2d as the rendering backend for microui, using SDL 3 as the cross platform graphics API. 4 5 SDL is used to create the OS window and provide a rendering context. 6 Blend2d draws directly into the pixel buffer of an SDL GPU texture. 7 microui is used to implement the UI layout. 8 9 SDL events -> microui 10 microui -> render commands -> Blend2d 11 Blend2d -> GPU texture 12 13 You can also render microui using SDL drawing primitives, but the point is 14 to use Blend2d to do rendering on the CPU. 15 16 Bugs: 17 - log text input not working 18 - clips are too aggressive on paths (text + icons) 19 */ 20 package main 21 22 import "core:c" 23 24 import "base:runtime" 25 import "core:fmt" 26 import "core:math" 27 import "core:mem/virtual" 28 import "core:strings" 29 30 import bl "../binding" 31 import mu "vendor:microui" 32 import sdl "vendor:sdl3" 33 34 // FontHandle is provided to microui, allowing access to the context within text 35 // measuring callbacks. 36 FontHandle :: struct { 37 ctx: ^Context, 38 font: ^bl.FontCore, 39 } 40 41 // Context is an amalgam of state required by SDL, microui, blend2d and the ui logic. 42 Context :: struct { 43 runtime_context: runtime.Context, 44 mu_ctx: ^mu.Context, 45 bl_ctx: ^bl.ContextCore, 46 face: ^bl.FontFaceCore, 47 font: ^bl.FontCore, 48 window: ^sdl.Window, 49 renderer: ^sdl.Renderer, 50 texture: ^sdl.Texture, 51 bg: mu.Color, 52 cookie: bl.ContextCookie, 53 log_buf: [1 << 16]byte, 54 log_buf_len: int, 55 log_buf_updated: bool, 56 frame_count: i64, 57 render_width: i32, 58 render_height: i32, 59 logical_width: i32, 60 logical_height: i32, 61 scale: i32, 62 debug_text: bool, 63 } 64 65 // context_init initializes SDL, blend2d and microui. 66 context_init :: proc(ctx: ^Context) { 67 ctx.runtime_context = context 68 69 ctx.bl_ctx = new(bl.ContextCore) 70 71 if bl.context_init(ctx.bl_ctx) != 0 { 72 panic("failed to create blend2d context") 73 } 74 75 ctx.mu_ctx = new(mu.Context) 76 mu.init(ctx.mu_ctx) 77 78 assert(sdl.Init({.VIDEO, .EVENTS})) 79 80 display_mode := sdl.GetCurrentDisplayMode(sdl.GetPrimaryDisplay()) 81 ctx.scale = i32(display_mode.pixel_density) 82 83 window := sdl.CreateWindow( 84 "microui - blend2d - sdl3", 85 WINDOW_WIDTH, 86 WINDOW_HEIGHT, 87 {.RESIZABLE, .HIGH_PIXEL_DENSITY}, 88 ) 89 90 ctx.window = window 91 92 renderer := sdl.CreateRenderer(window, "") 93 94 ctx.renderer = renderer 95 96 sdl.GetRenderOutputSize(renderer, &ctx.render_width, &ctx.render_height) 97 98 texture := sdl.CreateTexture( 99 renderer, 100 .RGBA32, 101 .STREAMING, 102 ctx.render_width, 103 ctx.render_height, 104 ) 105 ctx.texture = texture 106 107 sdl.SetTextureBlendMode( 108 texture, 109 sdl.ComposeCustomBlendMode( 110 .ONE, 111 .ONE_MINUS_SRC_ALPHA, 112 .ADD, 113 .ONE, 114 .ONE_MINUS_SRC_ALPHA, 115 .ADD, 116 ), 117 ) 118 119 sdl.SetRenderVSync(renderer, 1) 120 121 window_scale := sdl.GetWindowDisplayScale(ctx.window) 122 window_pixel_density := sdl.GetWindowPixelDensity(ctx.window) 123 sdl.GetRenderOutputSize(ctx.renderer, &ctx.render_width, &ctx.render_height) 124 125 /* 126 init font 127 */ 128 129 { 130 ctx.face = new(bl.FontFaceCore) 131 bl.font_face_init(ctx.face) 132 133 if bl.font_face_create_from_file( 134 ctx.face, 135 "./example/resource/Roboto-Medium.ttf", 136 .NO_FLAGS, 137 ) != 138 0 { 139 panic("failed to load font") 140 } 141 142 ctx.font = new(bl.FontCore) 143 assert(bl.font_init(ctx.font) == 0) 144 145 assert(bl.font_create_from_face(ctx.font, ctx.face, f32(14 * ctx.scale)) == 0) 146 147 // Setup the font handle. Provides access to the context in the text measuring 148 // callbacks. 149 fh := new(FontHandle) 150 fh.ctx = ctx 151 fh.font = ctx.font 152 ctx.mu_ctx.style.font = mu.Font(fh) 153 154 ctx.mu_ctx.text_height = proc(font: mu.Font) -> i32 { 155 if font == nil { 156 return 0 157 } 158 159 fh := cast(^FontHandle)(font) 160 161 fm: bl.FontMetrics 162 bl.font_get_metrics(fh.font, &fm) 163 164 height := i32(fm.ascent + fm.descent) 165 166 // The font measurements need to be unscaled because microui 167 // works within a logical coordinate space. 168 return height / fh.ctx.scale 169 } 170 171 ctx.mu_ctx.text_width = proc(font: mu.Font, text: string) -> i32 { 172 if font == nil { 173 return 0 174 } 175 176 fh := cast(^FontHandle)(font) 177 178 @(static) gb: bl.GlyphBufferCore 179 bl.glyph_buffer_init(&gb) 180 defer bl.glyph_buffer_reset(&gb) 181 182 bl.glyph_buffer_set_text(&gb, raw_data(text), len(text), .UTF8) 183 184 tm: bl.TextMetrics 185 bl.font_get_text_metrics(fh.font, &gb, &tm) 186 187 // The font measurements need to be unscaled because microui 188 // works within a logical coordinate space. 189 return i32(tm.advance.x) / fh.ctx.scale 190 } 191 } 192 } 193 194 WINDOW_WIDTH :: 800 195 WINDOW_HEIGHT :: 800 196 197 main :: proc() { 198 ctx: Context 199 context_init(&ctx) 200 201 arena: virtual.Arena 202 assert(virtual.arena_init_growing(&arena) == nil) 203 context.allocator = virtual.arena_allocator(&arena) 204 205 sdl.GetWindowSize(ctx.window, &ctx.logical_width, &ctx.logical_height) 206 207 mainloop: for { 208 defer virtual.arena_free_all(&arena) 209 defer ctx.frame_count += 1 210 211 event: sdl.Event 212 213 for sdl.PollEvent(&event) { 214 #partial switch event.type { 215 case .QUIT: 216 break mainloop 217 case .MOUSE_MOTION: 218 mu.input_mouse_move( 219 ctx.mu_ctx, 220 i32(math.round(event.motion.x)), 221 i32(math.round(event.motion.y)), 222 ) 223 case .MOUSE_BUTTON_DOWN: 224 if btn := sdl_button_to_mu_button(event.button.button); btn != nil { 225 mu.input_mouse_down( 226 ctx.mu_ctx, 227 i32(math.round(event.button.x)), 228 i32(math.round(event.button.y)), 229 btn.?, 230 ) 231 } 232 case .MOUSE_BUTTON_UP: 233 if btn := sdl_button_to_mu_button(event.button.button); btn != nil { 234 mu.input_mouse_up( 235 ctx.mu_ctx, 236 i32(math.round(event.button.x)), 237 i32(math.round(event.button.y)), 238 btn.?, 239 ) 240 } 241 case .MOUSE_WHEEL: 242 mu.input_scroll( 243 ctx.mu_ctx, 244 i32(event.wheel.integer_x * -30), 245 i32(event.wheel.integer_y * -30), 246 ) 247 case .KEY_DOWN: 248 if key := sdl_key_to_mu_key(event.key.key); key != nil { 249 mu.input_key_down(ctx.mu_ctx, key.?) 250 } 251 case .KEY_UP: 252 if key := sdl_key_to_mu_key(event.key.key); key != nil { 253 mu.input_key_up(ctx.mu_ctx, key.?) 254 } 255 case .TEXT_INPUT: 256 mu.input_text(ctx.mu_ctx, strings.clone_from_cstring(event.text.text)) 257 } 258 } 259 260 render(&ctx) 261 } 262 } 263 264 sdl_button_to_mu_button :: proc(sdl_btn: u8) -> Maybe(mu.Mouse) { 265 switch sdl_btn { 266 case sdl.BUTTON_LEFT: 267 return .LEFT 268 case sdl.BUTTON_RIGHT: 269 return .RIGHT 270 case sdl.BUTTON_MIDDLE: 271 return .MIDDLE 272 case: 273 return nil 274 } 275 } 276 277 sdl_key_to_mu_key :: proc(sdl_key: sdl.Keycode) -> (ret: Maybe(mu.Key)) { 278 switch sdl_key { 279 case sdl.K_LSHIFT, sdl.K_RSHIFT: 280 return .SHIFT 281 case sdl.K_LCTRL, sdl.K_RCTRL: 282 return .CTRL 283 case sdl.K_LALT, sdl.K_RALT: 284 return .ALT 285 case sdl.K_BACKSPACE: 286 return .BACKSPACE 287 case sdl.K_DELETE: 288 return .DELETE 289 case sdl.K_RETURN: 290 return .RETURN 291 case sdl.K_LEFT: 292 return .LEFT 293 case sdl.K_RIGHT: 294 return .RIGHT 295 case sdl.K_HOME: 296 return .HOME 297 case sdl.K_END: 298 return .END 299 case sdl.K_A: 300 return .A 301 case sdl.K_X: 302 return .X 303 case sdl.K_C: 304 return .C 305 case sdl.K_V: 306 return .V 307 case: 308 return nil 309 } 310 } 311 312 render :: proc(ctx: ^Context) { 313 pixels: rawptr 314 pitch: c.int 315 316 if !sdl.LockTexture(ctx.texture, nil, &pixels, &pitch) { 317 return 318 } 319 320 img: bl.ImageCore 321 322 image_init_res := bl.image_init_as_from_data( 323 &img, 324 i32(ctx.render_width), 325 i32(ctx.render_height), 326 .PRGB32, 327 pixels, 328 int(pitch), 329 .RW, 330 nil, 331 nil, 332 ) 333 334 if image_init_res != 0 { 335 return 336 } 337 338 defer bl.image_reset(&img) 339 340 { 341 bl.context_begin(ctx.bl_ctx, &img, nil) 342 defer bl.context_end(ctx.bl_ctx) 343 344 mu.begin(ctx.mu_ctx) 345 all_windows(ctx) 346 mu.end(ctx.mu_ctx) 347 348 bl.context_clear_all(ctx.bl_ctx) 349 bl.context_fill_all_rgba32(ctx.bl_ctx, transmute(u32)(ctx.bg)) 350 351 // cmd_backing is the iteration context, since mu.next_command is implemented 352 // via pointer math. 353 cmd_backing: ^mu.Command 354 for var in mu.next_command_iterator(ctx.mu_ctx, &cmd_backing) { 355 switch cmd in var { 356 case ^mu.Command_Text: 357 _render_text(ctx, cmd) 358 case ^mu.Command_Icon: 359 _render_icon(ctx, cmd) 360 case ^mu.Command_Rect: 361 _render_rect(ctx, cmd) 362 case ^mu.Command_Clip: 363 _set_clip(ctx, cmd) 364 case ^mu.Command_Jump: 365 panic("jm_ jump command") 366 } 367 } 368 369 } 370 371 src := sdl.FRect { 372 w = f32(ctx.render_width), 373 h = f32(ctx.render_height), 374 } 375 376 sdl.UnlockTexture(ctx.texture) 377 sdl.SetRenderDrawColor(ctx.renderer, 0, 0, 0, 255) 378 sdl.RenderClear(ctx.renderer) 379 sdl.RenderTexture(ctx.renderer, ctx.texture, &src, nil) 380 sdl.RenderPresent(ctx.renderer) 381 382 texture_width: f32 383 texture_height: f32 384 sdl.GetTextureSize(ctx.texture, &texture_width, &texture_height) 385 386 sdl.SetWindowTitle( 387 ctx.window, 388 fmt.ctprintf( 389 "render %vx%v texture %vx%v logical %vx%v scale %v mouse (%v,%v)", 390 ctx.render_width, 391 ctx.render_height, 392 ctx.logical_width, 393 ctx.logical_height, 394 texture_width, 395 texture_height, 396 ctx.scale, 397 ctx.mu_ctx.mouse_pos.x, 398 ctx.mu_ctx.mouse_pos.y, 399 ), 400 ) 401 } 402 403 _render_text :: proc(ctx: ^Context, cmd: ^mu.Command_Text) { 404 font := cast(^FontHandle)(cmd.font) 405 text_data := cast(cstring)(raw_data(cmd.str)) 406 text_len := uint(len(cmd.str)) 407 408 fm: bl.FontMetrics 409 bl.font_get_metrics(font.font, &fm) 410 411 @(static) glyph_buffer: bl.GlyphBufferCore 412 bl.glyph_buffer_init(&glyph_buffer) 413 defer bl.glyph_buffer_reset(&glyph_buffer) 414 415 bl.glyph_buffer_set_text(&glyph_buffer, rawptr(text_data), uint(text_len), .UTF8) 416 417 tm: bl.TextMetrics 418 bl.font_get_text_metrics(font.font, &glyph_buffer, &tm) 419 420 // draw the text 421 { 422 origin := bl.PointI { 423 x = (cmd.pos.x * ctx.scale) + i32(tm.bounding_box.x0), 424 y = (cmd.pos.y * ctx.scale) + i32(fm.ascent), // Adjust from top to baseline 425 } 426 bl.context_fill_glyph_run_i_rgba32( 427 ctx.bl_ctx, 428 &origin, 429 ctx.font, 430 bl.glyph_buffer_get_glyph_run(&glyph_buffer), 431 transmute(u32)(cmd.color), 432 ) 433 } 434 435 if ctx.debug_text { 436 // draw bounding box 437 { 438 rect := bl.RectI { 439 x = (cmd.pos.x * ctx.scale) + i32(tm.bounding_box.x0), 440 y = (cmd.pos.y * ctx.scale), 441 w = i32((tm.advance.x)), 442 h = i32(fm.descent + fm.ascent), 443 } 444 bl.context_stroke_rect_i_rgba32(ctx.bl_ctx, &rect, transmute(u32)(cmd.color)) 445 446 } 447 // draw baseline 448 { 449 rect := bl.RectI { 450 x = (cmd.pos.x * ctx.scale) + i32(tm.bounding_box.x0), 451 y = (cmd.pos.y * ctx.scale) + i32(fm.ascent), 452 w = i32((tm.bounding_box.x1 - tm.bounding_box.x0)), 453 h = 1, 454 } 455 bl.context_stroke_rect_i_rgba32(ctx.bl_ctx, &rect, transmute(u32)(cmd.color)) 456 } 457 // draw origin 458 { 459 rect := bl.RectI { 460 x = (cmd.pos.x * ctx.scale) + i32(tm.bounding_box.x0) - 6, 461 y = (cmd.pos.y * ctx.scale) - 6, 462 w = 12, 463 h = 12, 464 } 465 bl.context_stroke_rect_i_rgba32(ctx.bl_ctx, &rect, transmute(u32)(cmd.color)) 466 } 467 } 468 } 469 470 _render_rect :: proc(ctx: ^Context, cmd: ^mu.Command_Rect) { 471 rect := bl.RectI { 472 x = cmd.rect.x * ctx.scale, 473 y = cmd.rect.y * ctx.scale, 474 w = cmd.rect.w * ctx.scale, 475 h = cmd.rect.h * ctx.scale, 476 } 477 bl.context_fill_rect_i_rgba32(ctx.bl_ctx, &rect, transmute(u32)(cmd.color)) 478 } 479 480 // _render_icon uses vector paths to draw the icons to showcase blend's path api. 481 _render_icon :: proc(ctx: ^Context, cmd: ^mu.Command_Icon) { 482 color := transmute(u32)(cmd.color) 483 484 rect := bl.Rect { 485 x = f64(cmd.rect.x * ctx.scale), 486 y = f64(cmd.rect.y * ctx.scale), 487 w = f64(cmd.rect.w * ctx.scale), 488 h = f64(cmd.rect.h * ctx.scale), 489 } 490 491 switch cmd.id { 492 case .NONE: 493 return 494 case .RESIZE: 495 size := bl.Size { 496 w = 18, 497 h = 18, 498 } 499 _draw_resize(ctx, rect, size, color) 500 case .CLOSE: 501 size := bl.Size { 502 w = 18, 503 h = 18, 504 } 505 _draw_close(ctx, rect, size, color) 506 case .CHECK: 507 size := bl.Size { 508 w = 18, 509 h = 18, 510 } 511 _draw_check(ctx, rect, size, color) 512 case .EXPANDED: 513 size := bl.Size { 514 w = 12, 515 h = 8, 516 } 517 _draw_down_angle(ctx, rect, size, color) 518 case .COLLAPSED: 519 size := bl.Size { 520 w = 8, 521 h = 12, 522 } 523 _draw_right_angle(ctx, rect, size, color) 524 } 525 } 526 527 _draw_resize :: proc(ctx: ^Context, rect: bl.Rect, size: bl.Size, rgba: u32) { 528 _draw_icon(ctx, rect, size, rgba, proc(p: ^bl.PathCore, size: bl.Size) { 529 bl.path_move_to(p, size.w, size.h / 2) 530 bl.path_line_to(p, size.w, size.h) 531 bl.path_move_to(p, size.w / 2, size.h) 532 bl.path_line_to(p, size.w, size.h) 533 }) 534 } 535 536 _draw_close :: proc(ctx: ^Context, rect: bl.Rect, size: bl.Size, rgba: u32) { 537 _draw_icon(ctx, rect, size, rgba, proc(p: ^bl.PathCore, size: bl.Size) { 538 bl.path_move_to(p, 0, 0) 539 bl.path_line_to(p, size.w, size.h) 540 bl.path_move_to(p, size.w, 0) 541 bl.path_line_to(p, 0, size.h) 542 }) 543 } 544 545 _draw_check :: proc(ctx: ^Context, rect: bl.Rect, size: bl.Size, rgba: u32) { 546 _draw_icon(ctx, rect, size, rgba, proc(p: ^bl.PathCore, size: bl.Size) { 547 bl.path_move_to(p, 0, (size.h / 2)) 548 bl.path_line_to(p, (size.w / 3), size.h) 549 bl.path_line_to(p, size.w, 0) 550 }) 551 } 552 553 _draw_right_angle :: proc(ctx: ^Context, rect: bl.Rect, size: bl.Size, rgba: u32) { 554 _draw_icon(ctx, rect, size, rgba, proc(p: ^bl.PathCore, size: bl.Size) { 555 bl.path_move_to(p, 0, 0) 556 bl.path_line_to(p, size.w, (size.h / 2)) 557 bl.path_line_to(p, 0, size.h) 558 }) 559 } 560 561 _draw_down_angle :: proc(ctx: ^Context, rect: bl.Rect, size: bl.Size, rgba: u32) { 562 _draw_icon(ctx, rect, size, rgba, proc(p: ^bl.PathCore, size: bl.Size) { 563 bl.path_move_to(p, 0, 0) 564 bl.path_line_to(p, (size.w / 2), size.h) 565 bl.path_line_to(p, size.w, 0) 566 }) 567 } 568 569 570 // _draw_icon centers the path on the rect and draws it via the path_proc. 571 // path_proc is expected to fill the PathCore with lines. 572 _draw_icon :: proc( 573 ctx: ^Context, 574 rect: bl.Rect, 575 size: bl.Size, 576 rgba: u32, 577 path_proc: proc(_: ^bl.PathCore, _: bl.Size), 578 ) { 579 @(static) p: bl.PathCore 580 581 bl.path_init(&p) 582 defer bl.path_reset(&p) 583 584 path_proc(&p, size) 585 586 origin := bl.Point { 587 x = rect.x + (rect.w - size.w) / 2, 588 y = rect.y + (rect.w - size.h) / 2, 589 } 590 591 bl.context_set_stroke_options(ctx.bl_ctx, &bl.StrokeOptionsCore{width = 2}) 592 defer bl.context_set_stroke_options(ctx.bl_ctx, &bl.StrokeOptionsCore{width = 1}) 593 594 bl.context_stroke_path_d_rgba32(ctx.bl_ctx, &origin, &p, rgba) 595 } 596 597 // FIXME: there's some weird clipping behaviour: the moment text is even partially occluded, 598 // the entire text run disappears. Also occurs for icons. 599 _set_clip :: proc(ctx: ^Context, cmd: ^mu.Command_Clip) { 600 // TODO: verify this magic number. 601 // It's the number that microui spits out, I think it represents a clip clear 602 // by logically setting the clip to a super large dimension. 603 if cmd.rect.h == 16777216 { 604 bl.context_restore(ctx.bl_ctx, &ctx.cookie) 605 } else { 606 bl.context_save(ctx.bl_ctx, &ctx.cookie) 607 bl.context_clip_to_rect_i( 608 ctx.bl_ctx, 609 &bl.RectI { 610 x = cmd.rect.x * ctx.scale, 611 y = (ctx.render_height - (cmd.rect.y * ctx.scale + cmd.rect.h * ctx.scale)), 612 w = cmd.rect.w * ctx.scale, 613 h = cmd.rect.h * ctx.scale, 614 }, 615 ) 616 } 617 } 618 619 /* 620 UI Logic 621 */ 622 623 u8_slider :: proc(ctx: ^mu.Context, val: ^u8, lo, hi: u8) -> (res: mu.Result_Set) { 624 mu.push_id(ctx, uintptr(val)) 625 626 @(static) tmp: mu.Real 627 tmp = mu.Real(val^) 628 res = mu.slider(ctx, &tmp, mu.Real(lo), mu.Real(hi), 0, "%.0f", {.ALIGN_CENTER}) 629 val^ = u8(tmp) 630 mu.pop_id(ctx) 631 return 632 } 633 634 write_log :: proc(ctx: ^Context, str: string) { 635 ctx.log_buf_len += copy(ctx.log_buf[ctx.log_buf_len:], str) 636 ctx.log_buf_len += copy(ctx.log_buf[ctx.log_buf_len:], "\n") 637 ctx.log_buf_updated = true 638 } 639 640 read_log :: proc(ctx: ^Context) -> string { 641 return string(ctx.log_buf[:ctx.log_buf_len]) 642 } 643 644 reset_log :: proc(ctx: ^Context) { 645 ctx.log_buf_updated = true 646 ctx.log_buf_len = 0 647 } 648 649 all_windows :: proc(ctx: ^Context) { 650 @(static) opts := mu.Options{.NO_CLOSE} 651 652 if mu.window(ctx.mu_ctx, "Demo Window", {40, 40, 300, 500}, opts) { 653 if .ACTIVE in mu.header(ctx.mu_ctx, "Window Info") { 654 win := mu.get_current_container(ctx.mu_ctx) 655 mu.layout_row(ctx.mu_ctx, {54, -1}, 0) 656 mu.label(ctx.mu_ctx, "Position:") 657 mu.label(ctx.mu_ctx, fmt.tprintf("%d, %d", win.rect.x, win.rect.y)) 658 mu.label(ctx.mu_ctx, "Size:") 659 mu.label(ctx.mu_ctx, fmt.tprintf("%d, %d", win.rect.w, win.rect.h)) 660 } 661 662 if .ACTIVE in mu.header(ctx.mu_ctx, "Window Options") { 663 mu.layout_row(ctx.mu_ctx, {120, 120, 120}, 0) 664 for opt in mu.Opt { 665 state := opt in opts 666 if .CHANGE in mu.checkbox(ctx.mu_ctx, fmt.tprintf("%v", opt), &state) { 667 if state { 668 opts += {opt} 669 } else { 670 opts -= {opt} 671 } 672 } 673 } 674 } 675 676 if .ACTIVE in mu.header(ctx.mu_ctx, "Test Buttons", {.EXPANDED}) { 677 mu.layout_row(ctx.mu_ctx, {86, -110, -1}) 678 mu.label(ctx.mu_ctx, "Test buttons 1:") 679 if .SUBMIT in mu.button(ctx.mu_ctx, "Button 1") {write_log(ctx, "Pressed button 1")} 680 if .SUBMIT in mu.button(ctx.mu_ctx, "Button 2") {write_log(ctx, "Pressed button 2")} 681 mu.label(ctx.mu_ctx, "Test buttons 2:") 682 if .SUBMIT in mu.button(ctx.mu_ctx, "Button 3") {write_log(ctx, "Pressed button 3")} 683 if .SUBMIT in mu.button(ctx.mu_ctx, "Button 4") {write_log(ctx, "Pressed button 4")} 684 } 685 686 if .ACTIVE in mu.header(ctx.mu_ctx, "Tree and Text", {.EXPANDED}) { 687 mu.layout_row(ctx.mu_ctx, {140, -1}) 688 mu.layout_begin_column(ctx.mu_ctx) 689 if .ACTIVE in mu.treenode(ctx.mu_ctx, "Test 1") { 690 if .ACTIVE in mu.treenode(ctx.mu_ctx, "Test 1a") { 691 mu.label(ctx.mu_ctx, "Hello") 692 mu.label(ctx.mu_ctx, "world") 693 } 694 if .ACTIVE in mu.treenode(ctx.mu_ctx, "Test 1b") { 695 if .SUBMIT in 696 mu.button(ctx.mu_ctx, "Button 1") {write_log(ctx, "Pressed button 1")} 697 if .SUBMIT in 698 mu.button(ctx.mu_ctx, "Button 2") {write_log(ctx, "Pressed button 2")} 699 } 700 } 701 if .ACTIVE in mu.treenode(ctx.mu_ctx, "Test 2") { 702 mu.layout_row(ctx.mu_ctx, {53, 53}) 703 if .SUBMIT in 704 mu.button(ctx.mu_ctx, "Button 3") {write_log(ctx, "Pressed button 3")} 705 if .SUBMIT in 706 mu.button(ctx.mu_ctx, "Button 4") {write_log(ctx, "Pressed button 4")} 707 if .SUBMIT in 708 mu.button(ctx.mu_ctx, "Button 5") {write_log(ctx, "Pressed button 5")} 709 if .SUBMIT in 710 mu.button(ctx.mu_ctx, "Button 6") {write_log(ctx, "Pressed button 6")} 711 } 712 if .ACTIVE in mu.treenode(ctx.mu_ctx, "Test 3") { 713 @(static) checks := [3]bool{true, false, true} 714 mu.checkbox(ctx.mu_ctx, "Checkbox 1", &checks[0]) 715 mu.checkbox(ctx.mu_ctx, "Checkbox 2", &checks[1]) 716 mu.checkbox(ctx.mu_ctx, "Checkbox 3", &checks[2]) 717 718 } 719 mu.layout_end_column(ctx.mu_ctx) 720 721 mu.layout_begin_column(ctx.mu_ctx) 722 mu.layout_row(ctx.mu_ctx, {-1}) 723 mu.text( 724 ctx.mu_ctx, 725 "Lorem ipsum dolor sit amet, consectetur adipiscing " + 726 "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus " + 727 "ipsum, eu varius magna felis a nulla.", 728 ) 729 mu.layout_end_column(ctx.mu_ctx) 730 } 731 732 if .ACTIVE in mu.header(ctx.mu_ctx, "Background Colour", {.EXPANDED}) { 733 mu.layout_row(ctx.mu_ctx, {-78, -1}, 68) 734 mu.layout_begin_column(ctx.mu_ctx) 735 { 736 mu.layout_row(ctx.mu_ctx, {46, -1}, 0) 737 mu.label(ctx.mu_ctx, "Red:"); u8_slider(ctx.mu_ctx, &ctx.bg.r, 0, 255) 738 mu.label(ctx.mu_ctx, "Green:"); u8_slider(ctx.mu_ctx, &ctx.bg.g, 0, 255) 739 mu.label(ctx.mu_ctx, "Blue:"); u8_slider(ctx.mu_ctx, &ctx.bg.b, 0, 255) 740 mu.label(ctx.mu_ctx, "Alpha:"); u8_slider(ctx.mu_ctx, &ctx.bg.a, 0, 255) 741 } 742 mu.layout_end_column(ctx.mu_ctx) 743 744 r := mu.layout_next(ctx.mu_ctx) 745 mu.draw_rect(ctx.mu_ctx, r, ctx.bg) 746 mu.draw_box(ctx.mu_ctx, mu.expand_rect(r, 1), ctx.mu_ctx.style.colors[.BORDER]) 747 mu.draw_control_text( 748 ctx.mu_ctx, 749 fmt.tprintf("#%02x%02x%02x", ctx.bg.r, ctx.bg.g, ctx.bg.b), 750 r, 751 .TEXT, 752 {.ALIGN_CENTER}, 753 ) 754 } 755 } 756 757 if mu.window(ctx.mu_ctx, "Log Window", {350, 40, 300, 200}, opts) { 758 mu.layout_row(ctx.mu_ctx, {-1}, -28) 759 mu.begin_panel(ctx.mu_ctx, "Log") 760 mu.layout_row(ctx.mu_ctx, {-1}, -1) 761 mu.text(ctx.mu_ctx, read_log(ctx)) 762 if ctx.log_buf_updated { 763 panel := mu.get_current_container(ctx.mu_ctx) 764 panel.scroll.y = panel.content_size.y 765 ctx.log_buf_updated = false 766 } 767 mu.end_panel(ctx.mu_ctx) 768 769 @(static) buf: [128]byte 770 @(static) buf_len: int 771 submitted := false 772 mu.layout_row(ctx.mu_ctx, {-70, -1}) 773 if .SUBMIT in mu.textbox(ctx.mu_ctx, buf[:], &buf_len) { 774 mu.set_focus(ctx.mu_ctx, ctx.mu_ctx.last_id) 775 submitted = true 776 } 777 if .SUBMIT in mu.button(ctx.mu_ctx, "Submit") { 778 submitted = true 779 } 780 if submitted { 781 write_log(ctx, string(buf[:buf_len])) 782 buf_len = 0 783 } 784 } 785 786 if mu.window(ctx.mu_ctx, "Style Window", {350, 250, 300, 240}) { 787 @(static) colors := [mu.Color_Type]string { 788 .TEXT = "text", 789 .BORDER = "border", 790 .WINDOW_BG = "window bg", 791 .TITLE_BG = "title bg", 792 .TITLE_TEXT = "title text", 793 .PANEL_BG = "panel bg", 794 .BUTTON = "button", 795 .BUTTON_HOVER = "button hover", 796 .BUTTON_FOCUS = "button focus", 797 .BASE = "base", 798 .BASE_HOVER = "base hover", 799 .BASE_FOCUS = "base focus", 800 .SCROLL_BASE = "scroll base", 801 .SCROLL_THUMB = "scroll thumb", 802 .SELECTION_BG = "selection bg", 803 } 804 805 sw := i32(f32(mu.get_current_container(ctx.mu_ctx).body.w) * 0.14) 806 mu.layout_row(ctx.mu_ctx, {80, sw, sw, sw, sw, -1}) 807 for label, col in colors { 808 mu.label(ctx.mu_ctx, label) 809 u8_slider(ctx.mu_ctx, &ctx.mu_ctx.style.colors[col].r, 0, 255) 810 u8_slider(ctx.mu_ctx, &ctx.mu_ctx.style.colors[col].g, 0, 255) 811 u8_slider(ctx.mu_ctx, &ctx.mu_ctx.style.colors[col].b, 0, 255) 812 u8_slider(ctx.mu_ctx, &ctx.mu_ctx.style.colors[col].a, 0, 255) 813 mu.draw_rect(ctx.mu_ctx, mu.layout_next(ctx.mu_ctx), ctx.mu_ctx.style.colors[col]) 814 } 815 } 816 } 817