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
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}