raylib/microui

Code

microui_raylib_demo.odin ¶
395 linesSource

1package microui_raylib
2
3import "core:c"
4import "core:fmt"
5import "core:strings"
6import "core:unicode/utf8"
7
8import rl "vendor:raylib"
9import mu "vendor:microui"
10
11state := struct{
12	mu_ctx: mu.Context,
13	log_buf:         [1<<16]byte,
14	log_buf_len:     int,
15	log_buf_updated: bool,
16	bg: mu.Color,
17	atlas_texture: rl.RenderTexture2D,
18
19	screen_width: c.int,
20	screen_height: c.int,
21
22	screen_texture: rl.RenderTexture2D,
23}{
24	screen_width = 960,
25	screen_height = 540,
26	bg = {90, 95, 100, 255},
27}
28
29mouse_buttons_map := [mu.Mouse]rl.MouseButton{
30	.LEFT    = .LEFT,
31	.RIGHT   = .RIGHT,
32	.MIDDLE  = .MIDDLE,
33}
34
35key_map := [mu.Key][2]rl.KeyboardKey{
36	.SHIFT     = {.LEFT_SHIFT,   .RIGHT_SHIFT},
37	.CTRL      = {.LEFT_CONTROL, .RIGHT_CONTROL},
38	.ALT       = {.LEFT_ALT,     .RIGHT_ALT},
39	.BACKSPACE = {.BACKSPACE,    .KEY_NULL},
40	.DELETE    = {.DELETE,       .KEY_NULL},
41	.RETURN    = {.ENTER,        .KP_ENTER},
42	.LEFT      = {.LEFT,         .KEY_NULL},
43	.RIGHT     = {.RIGHT,        .KEY_NULL},
44	.HOME      = {.HOME,         .KEY_NULL},
45	.END       = {.END,          .KEY_NULL},
46	.A         = {.A,            .KEY_NULL},
47	.X         = {.X,            .KEY_NULL},
48	.C         = {.C,            .KEY_NULL},
49	.V         = {.V,            .KEY_NULL},
50}
51
52
53main :: proc() {
54	rl.InitWindow(state.screen_width, state.screen_height, "microui-raylib-odin")
55	defer rl.CloseWindow()
56	rl.SetTargetFPS(60)
57
58	ctx := &state.mu_ctx
59	mu.init(ctx,
60		set_clipboard = proc(user_data: rawptr, text: string) -> (ok: bool) {
61			cstr := strings.clone_to_cstring(text)
62			rl.SetClipboardText(cstr)
63			delete(cstr)
64			return true
65		},
66		get_clipboard = proc(user_data: rawptr) -> (text: string, ok: bool) {
67			cstr := rl.GetClipboardText()
68			if cstr != nil {
69				text = string(cstr)
70				ok = true
71			}
72			return
73		},
74	)
75
76	ctx.text_width = mu.default_atlas_text_width
77	ctx.text_height = mu.default_atlas_text_height
78
79	state.atlas_texture = rl.LoadRenderTexture(c.int(mu.DEFAULT_ATLAS_WIDTH), c.int(mu.DEFAULT_ATLAS_HEIGHT))
80	defer rl.UnloadRenderTexture(state.atlas_texture)
81
82	image := rl.GenImageColor(c.int(mu.DEFAULT_ATLAS_WIDTH), c.int(mu.DEFAULT_ATLAS_HEIGHT), rl.Color{0, 0, 0, 0})
83	defer rl.UnloadImage(image)
84
85	for alpha, i in mu.default_atlas_alpha {
86		x := i % mu.DEFAULT_ATLAS_WIDTH
87		y := i / mu.DEFAULT_ATLAS_WIDTH
88		color := rl.Color{255, 255, 255, alpha}
89		rl.ImageDrawPixel(&image, c.int(x), c.int(y), color)
90	}
91
92	rl.BeginTextureMode(state.atlas_texture)
93	rl.UpdateTexture(state.atlas_texture.texture, rl.LoadImageColors(image))
94	rl.EndTextureMode()
95
96	state.screen_texture = rl.LoadRenderTexture(state.screen_width, state.screen_height)
97	defer rl.UnloadRenderTexture(state.screen_texture)
98
99	for !rl.WindowShouldClose() {
100		free_all(context.temp_allocator)
101
102		mouse_pos := rl.GetMousePosition()
103		mouse_x, mouse_y := i32(mouse_pos.x), i32(mouse_pos.y)
104		mu.input_mouse_move(ctx, mouse_x, mouse_y)
105
106		mouse_wheel_pos := rl.GetMouseWheelMoveV()
107		mu.input_scroll(ctx, i32(mouse_wheel_pos.x) * 30, i32(mouse_wheel_pos.y) * -30)
108
109		for button_rl, button_mu in mouse_buttons_map {
110			switch {
111			case rl.IsMouseButtonPressed(button_rl):
112				mu.input_mouse_down(ctx, mouse_x, mouse_y, button_mu)
113			case rl.IsMouseButtonReleased(button_rl):
114				mu.input_mouse_up  (ctx, mouse_x, mouse_y, button_mu)
115			}
116		}
117
118		for keys_rl, key_mu in key_map {
119			for key_rl in keys_rl {
120				switch {
121				case key_rl == .KEY_NULL:
122					// ignore
123				case rl.IsKeyPressed(key_rl), rl.IsKeyPressedRepeat(key_rl):
124					mu.input_key_down(ctx, key_mu)
125				case rl.IsKeyReleased(key_rl):
126					mu.input_key_up  (ctx, key_mu)
127				}
128			}
129		}
130
131		{
132			buf: [512]byte
133			n: int
134			for n < len(buf) {
135				c := rl.GetCharPressed()
136				if c == 0 {
137					break
138				}
139				b, w := utf8.encode_rune(c)
140				n += copy(buf[n:], b[:w])
141			}
142			mu.input_text(ctx, string(buf[:n]))
143		}
144
145		mu.begin(ctx)
146		all_windows(ctx)
147		mu.end(ctx)
148
149		render(ctx)
150	}
151}
152
153render :: proc "contextless" (ctx: ^mu.Context) {
154	render_texture :: proc "contextless" (renderer: rl.RenderTexture2D, dst: ^rl.Rectangle, src: mu.Rect, color: rl.Color) {
155		dst.width = f32(src.w)
156		dst.height = f32(src.h)
157
158		rl.DrawTextureRec(
159			texture  = state.atlas_texture.texture,
160			source   = {f32(src.x), f32(src.y), f32(src.w), f32(src.h)},
161			position = {dst.x, dst.y},
162			tint     = color,
163		)
164	}
165
166	to_rl_color :: proc "contextless" (in_color: mu.Color) -> (out_color: rl.Color) {
167		return {in_color.r, in_color.g, in_color.b, in_color.a}
168	}
169
170	height := rl.GetScreenHeight()
171
172	rl.BeginTextureMode(state.screen_texture)
173	rl.EndScissorMode()
174	rl.ClearBackground(to_rl_color(state.bg))
175
176	command_backing: ^mu.Command
177	for variant in mu.next_command_iterator(ctx, &command_backing) {
178		switch cmd in variant {
179		case ^mu.Command_Text:
180			dst := rl.Rectangle{f32(cmd.pos.x), f32(cmd.pos.y), 0, 0}
181			for ch in cmd.str {
182				if ch&0xc0 != 0x80 {
183					r := min(int(ch), 127)
184					src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + r]
185					render_texture(state.screen_texture, &dst, src, to_rl_color(cmd.color))
186					dst.x += dst.width
187				}
188			}
189		case ^mu.Command_Rect:
190			rl.DrawRectangle(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h, to_rl_color(cmd.color))
191		case ^mu.Command_Icon:
192			src := mu.default_atlas[cmd.id]
193			x := cmd.rect.x + (cmd.rect.w - src.w)/2
194			y := cmd.rect.y + (cmd.rect.h - src.h)/2
195			render_texture(state.screen_texture, &rl.Rectangle {f32(x), f32(y), 0, 0}, src, to_rl_color(cmd.color))
196		case ^mu.Command_Clip:
197			rl.BeginScissorMode(cmd.rect.x, height - (cmd.rect.y + cmd.rect.h), cmd.rect.w, cmd.rect.h)
198		case ^mu.Command_Jump:
199			unreachable()
200		}
201	}
202	rl.EndTextureMode()
203	rl.BeginDrawing()
204	rl.ClearBackground(rl.RAYWHITE)
205	rl.DrawTextureRec(
206		texture  = state.screen_texture.texture,
207		source   = {0, 0, f32(state.screen_width), -f32(state.screen_height)},
208		position = {0, 0},
209		tint     = rl.WHITE,
210	)
211	rl.EndDrawing()
212}
213
214u8_slider :: proc(ctx: ^mu.Context, val: ^u8, lo, hi: u8) -> (res: mu.Result_Set) {
215	mu.push_id(ctx, uintptr(val))
216
217	@static tmp: mu.Real
218	tmp = mu.Real(val^)
219	res = mu.slider(ctx, &tmp, mu.Real(lo), mu.Real(hi), 0, "%.0f", {.ALIGN_CENTER})
220	val^ = u8(tmp)
221	mu.pop_id(ctx)
222	return
223}
224
225write_log :: proc(str: string) {
226	state.log_buf_len += copy(state.log_buf[state.log_buf_len:], str)
227	state.log_buf_len += copy(state.log_buf[state.log_buf_len:], "\n")
228	state.log_buf_updated = true
229}
230
231read_log :: proc() -> string {
232	return string(state.log_buf[:state.log_buf_len])
233}
234reset_log :: proc() {
235	state.log_buf_updated = true
236	state.log_buf_len = 0
237}
238
239
240all_windows :: proc(ctx: ^mu.Context) {
241	@static opts := mu.Options{.NO_CLOSE}
242
243	if mu.window(ctx, "Demo Window", {40, 40, 300, 450}, opts) {
244		if .ACTIVE in mu.header(ctx, "Window Info") {
245			win := mu.get_current_container(ctx)
246			mu.layout_row(ctx, {54, -1}, 0)
247			mu.label(ctx, "Position:")
248			mu.label(ctx, fmt.tprintf("%d, %d", win.rect.x, win.rect.y))
249			mu.label(ctx, "Size:")
250			mu.label(ctx, fmt.tprintf("%d, %d", win.rect.w, win.rect.h))
251		}
252
253		if .ACTIVE in mu.header(ctx, "Window Options") {
254			mu.layout_row(ctx, {120, 120, 120}, 0)
255			for opt in mu.Opt {
256				state := opt in opts
257				if .CHANGE in mu.checkbox(ctx, fmt.tprintf("%v", opt), &state)  {
258					if state {
259						opts += {opt}
260					} else {
261						opts -= {opt}
262					}
263				}
264			}
265		}
266
267		if .ACTIVE in mu.header(ctx, "Test Buttons", {.EXPANDED}) {
268			mu.layout_row(ctx, {86, -110, -1})
269			mu.label(ctx, "Test buttons 1:")
270			if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
271			if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
272			mu.label(ctx, "Test buttons 2:")
273			if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
274			if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
275		}
276
277		if .ACTIVE in mu.header(ctx, "Tree and Text", {.EXPANDED}) {
278			mu.layout_row(ctx, {140, -1})
279			mu.layout_begin_column(ctx)
280			if .ACTIVE in mu.treenode(ctx, "Test 1") {
281				if .ACTIVE in mu.treenode(ctx, "Test 1a") {
282					mu.label(ctx, "Hello")
283					mu.label(ctx, "world")
284				}
285				if .ACTIVE in mu.treenode(ctx, "Test 1b") {
286					if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
287					if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
288				}
289			}
290			if .ACTIVE in mu.treenode(ctx, "Test 2") {
291				mu.layout_row(ctx, {53, 53})
292				if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
293				if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
294				if .SUBMIT in mu.button(ctx, "Button 5") { write_log("Pressed button 5") }
295				if .SUBMIT in mu.button(ctx, "Button 6") { write_log("Pressed button 6") }
296			}
297			if .ACTIVE in mu.treenode(ctx, "Test 3") {
298				@static checks := [3]bool{true, false, true}
299				mu.checkbox(ctx, "Checkbox 1", &checks[0])
300				mu.checkbox(ctx, "Checkbox 2", &checks[1])
301				mu.checkbox(ctx, "Checkbox 3", &checks[2])
302
303			}
304			mu.layout_end_column(ctx)
305
306			mu.layout_begin_column(ctx)
307			mu.layout_row(ctx, {-1})
308			mu.text(ctx,
309				"Lorem ipsum dolor sit amet, consectetur adipiscing "+
310				"elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "+
311				"ipsum, eu varius magna felis a nulla.",
312			)
313			mu.layout_end_column(ctx)
314		}
315
316		if .ACTIVE in mu.header(ctx, "Background Colour", {.EXPANDED}) {
317			mu.layout_row(ctx, {-78, -1}, 68)
318			mu.layout_begin_column(ctx)
319			{
320				mu.layout_row(ctx, {46, -1}, 0)
321				mu.label(ctx, "Red:");   u8_slider(ctx, &state.bg.r, 0, 255)
322				mu.label(ctx, "Green:"); u8_slider(ctx, &state.bg.g, 0, 255)
323				mu.label(ctx, "Blue:");  u8_slider(ctx, &state.bg.b, 0, 255)
324			}
325			mu.layout_end_column(ctx)
326
327			r := mu.layout_next(ctx)
328			mu.draw_rect(ctx, r, state.bg)
329			mu.draw_box(ctx, mu.expand_rect(r, 1), ctx.style.colors[.BORDER])
330			mu.draw_control_text(ctx, fmt.tprintf("#%02x%02x%02x", state.bg.r, state.bg.g, state.bg.b), r, .TEXT, {.ALIGN_CENTER})
331		}
332	}
333
334
335
336	if mu.window(ctx, "Log Window", {350, 40, 300, 200}, opts) {
337		mu.layout_row(ctx, {-1}, -28)
338		mu.begin_panel(ctx, "Log")
339		mu.layout_row(ctx, {-1}, -1)
340		mu.text(ctx, read_log())
341		if state.log_buf_updated {
342			panel := mu.get_current_container(ctx)
343			panel.scroll.y = panel.content_size.y
344			state.log_buf_updated = false
345		}
346		mu.end_panel(ctx)
347
348		@static buf: [128]byte
349		@static buf_len: int
350		submitted := false
351		mu.layout_row(ctx, {-70, -1})
352		if .SUBMIT in mu.textbox(ctx, buf[:], &buf_len) {
353			mu.set_focus(ctx, ctx.last_id)
354			submitted = true
355		}
356		if .SUBMIT in mu.button(ctx, "Submit") {
357			submitted = true
358		}
359		if submitted {
360			write_log(string(buf[:buf_len]))
361			buf_len = 0
362		}
363	}
364
365	if mu.window(ctx, "Style Window", {350, 250, 300, 240}) {
366		@static colors := [mu.Color_Type]string{
367			.TEXT         = "text",
368			.BORDER       = "border",
369			.WINDOW_BG    = "window bg",
370			.TITLE_BG     = "title bg",
371			.TITLE_TEXT   = "title text",
372			.PANEL_BG     = "panel bg",
373			.BUTTON       = "button",
374			.BUTTON_HOVER = "button hover",
375			.BUTTON_FOCUS = "button focus",
376			.BASE         = "base",
377			.BASE_HOVER   = "base hover",
378			.BASE_FOCUS   = "base focus",
379			.SCROLL_BASE  = "scroll base",
380			.SCROLL_THUMB = "scroll thumb",
381			.SELECTION_BG = "selection bg",
382		}
383
384		sw := i32(f32(mu.get_current_container(ctx).body.w) * 0.14)
385		mu.layout_row(ctx, {80, sw, sw, sw, sw, -1})
386		for label, col in colors {
387			mu.label(ctx, label)
388			u8_slider(ctx, &ctx.style.colors[col].r, 0, 255)
389			u8_slider(ctx, &ctx.style.colors[col].g, 0, 255)
390			u8_slider(ctx, &ctx.style.colors[col].b, 0, 255)
391			u8_slider(ctx, &ctx.style.colors[col].a, 0, 255)
392			mu.draw_rect(ctx, mu.layout_next(ctx), ctx.style.colors[col])
393		}
394	}
395}

Declarations Used 99