odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

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