1package microui_sdl
2
3import "core:c"
4import "core:fmt"
5import "core:strings"
6import SDL "vendor:sdl2"
7import mu "vendor:microui"
8
9state := struct {
10 mu_ctx: mu.Context,
11 log_buf: [1<<16]byte,
12 log_buf_len: int,
13 log_buf_updated: bool,
14 bg: mu.Color,
15
16 atlas_texture: ^SDL.Texture,
17}{
18 bg = {90, 95, 100, 255},
19}
20
21main :: proc() {
22 if err := SDL.Init({.VIDEO}); err != 0 {
23 fmt.eprintln(err)
24 return
25 }
26 defer SDL.Quit()
27
28 window := SDL.CreateWindow("microui-odin", SDL.WINDOWPOS_UNDEFINED, SDL.WINDOWPOS_UNDEFINED, 960, 540, {.SHOWN, .RESIZABLE})
29 if window == nil {
30 fmt.eprintln(SDL.GetError())
31 return
32 }
33 defer SDL.DestroyWindow(window)
34
35 backend_idx: i32 = -1
36 if n := SDL.GetNumRenderDrivers(); n <= 0 {
37 fmt.eprintln("No render drivers available")
38 return
39 } else {
40 for i in 0..<n {
41 info: SDL.RendererInfo
42 if err := SDL.GetRenderDriverInfo(i, &info); err == 0 {
43
44 if info.name == "opengl" {
45 backend_idx = i
46 break
47 }
48 }
49 }
50 }
51
52 renderer := SDL.CreateRenderer(window, backend_idx, {.ACCELERATED, .PRESENTVSYNC})
53 if renderer == nil {
54 fmt.eprintln("SDL.CreateRenderer:", SDL.GetError())
55 return
56 }
57 defer SDL.DestroyRenderer(renderer)
58
59 state.atlas_texture = SDL.CreateTexture(renderer, .RGBA32, .TARGET, mu.DEFAULT_ATLAS_WIDTH, mu.DEFAULT_ATLAS_HEIGHT)
60 assert(state.atlas_texture != nil)
61 if err := SDL.SetTextureBlendMode(state.atlas_texture, .BLEND); err != 0 {
62 fmt.eprintln("SDL.SetTextureBlendMode:", err)
63 return
64 }
65
66 pixels := make([][4]u8, mu.DEFAULT_ATLAS_WIDTH*mu.DEFAULT_ATLAS_HEIGHT)
67 for alpha, i in mu.default_atlas_alpha {
68 pixels[i].rgb = 0xff
69 pixels[i].a = alpha
70 }
71
72 if err := SDL.UpdateTexture(state.atlas_texture, nil, raw_data(pixels), 4*mu.DEFAULT_ATLAS_WIDTH); err != 0 {
73 fmt.eprintln("SDL.UpdateTexture:", err)
74 return
75 }
76
77 ctx := &state.mu_ctx
78 mu.init(ctx,
79 set_clipboard = proc(user_data: rawptr, text: string) -> (ok: bool) {
80 cstr := strings.clone_to_cstring(text)
81 SDL.SetClipboardText(cstr)
82 delete(cstr)
83 return true
84 },
85 get_clipboard = proc(user_data: rawptr) -> (text: string, ok: bool) {
86 if SDL.HasClipboardText() {
87 text = string(SDL.GetClipboardText())
88 ok = true
89 }
90 return
91 },
92 )
93
94 ctx.text_width = mu.default_atlas_text_width
95 ctx.text_height = mu.default_atlas_text_height
96
97
98
99 Resize_Data :: struct {
100 ctx: ^mu.Context,
101 renderer: ^SDL.Renderer,
102 }
103 SDL.AddEventWatch(proc "c" (data: rawptr, event: ^SDL.Event) -> c.int {
104 if event.type == .WINDOWEVENT && event.window.event == .RESIZED {
105 resize_data := (^Resize_Data)(data)
106 render(resize_data.ctx, resize_data.renderer)
107 }
108 return 0
109 }, &Resize_Data{
110 ctx = ctx,
111 renderer = renderer,
112 })
113
114 main_loop: for {
115 free_all(context.temp_allocator)
116
117 for e: SDL.Event; SDL.PollEvent(&e); {
118 #partial switch e.type {
119 case .QUIT:
120 break main_loop
121 case .MOUSEMOTION:
122 mu.input_mouse_move(ctx, e.motion.x, e.motion.y)
123 case .MOUSEWHEEL:
124 mu.input_scroll(ctx, e.wheel.x * 30, e.wheel.y * -30)
125 case .TEXTINPUT:
126 mu.input_text(ctx, string(cstring(&e.text.text[0])))
127
128 case .MOUSEBUTTONDOWN, .MOUSEBUTTONUP:
129 fn := mu.input_mouse_down if e.type == .MOUSEBUTTONDOWN else mu.input_mouse_up
130 switch e.button.button {
131 case SDL.BUTTON_LEFT: fn(ctx, e.button.x, e.button.y, .LEFT)
132 case SDL.BUTTON_MIDDLE: fn(ctx, e.button.x, e.button.y, .MIDDLE)
133 case SDL.BUTTON_RIGHT: fn(ctx, e.button.x, e.button.y, .RIGHT)
134 }
135
136 case .KEYDOWN, .KEYUP:
137 if e.type == .KEYUP && e.key.keysym.sym == .ESCAPE {
138 SDL.PushEvent(&SDL.Event{type = .QUIT})
139 }
140
141 fn := mu.input_key_down if e.type == .KEYDOWN else mu.input_key_up
142
143 #partial switch e.key.keysym.sym {
144 case .LSHIFT: fn(ctx, .SHIFT)
145 case .RSHIFT: fn(ctx, .SHIFT)
146 case .LCTRL: fn(ctx, .CTRL)
147 case .RCTRL: fn(ctx, .CTRL)
148 case .LALT: fn(ctx, .ALT)
149 case .RALT: fn(ctx, .ALT)
150 case .RETURN: fn(ctx, .RETURN)
151 case .KP_ENTER: fn(ctx, .RETURN)
152 case .BACKSPACE: fn(ctx, .BACKSPACE)
153
154 case .LEFT: fn(ctx, .LEFT)
155 case .RIGHT: fn(ctx, .RIGHT)
156 case .HOME: fn(ctx, .HOME)
157 case .END: fn(ctx, .END)
158 case .A: fn(ctx, .A)
159 case .X: fn(ctx, .X)
160 case .C: fn(ctx, .C)
161 case .V: fn(ctx, .V)
162 }
163 }
164 }
165
166 mu.begin(ctx)
167 all_windows(ctx)
168 mu.end(ctx)
169
170 render(ctx, renderer)
171 }
172}
173
174
175render :: proc "contextless" (ctx: ^mu.Context, renderer: ^SDL.Renderer) {
176 render_texture :: proc "contextless" (renderer: ^SDL.Renderer, dst: ^SDL.Rect, src: mu.Rect, color: mu.Color) {
177 dst.w = src.w
178 dst.h = src.h
179
180 SDL.SetTextureAlphaMod(state.atlas_texture, color.a)
181 SDL.SetTextureColorMod(state.atlas_texture, color.r, color.g, color.b)
182 SDL.RenderCopy(renderer, state.atlas_texture, &SDL.Rect{src.x, src.y, src.w, src.h}, dst)
183 }
184
185 viewport_rect := &SDL.Rect{}
186 SDL.GetRendererOutputSize(renderer, &viewport_rect.w, &viewport_rect.h)
187 SDL.RenderSetViewport(renderer, viewport_rect)
188 SDL.RenderSetClipRect(renderer, viewport_rect)
189 SDL.SetRenderDrawColor(renderer, state.bg.r, state.bg.g, state.bg.b, state.bg.a)
190 SDL.RenderClear(renderer)
191
192 command_backing: ^mu.Command
193 for variant in mu.next_command_iterator(ctx, &command_backing) {
194 switch cmd in variant {
195 case ^mu.Command_Text:
196 dst := SDL.Rect{cmd.pos.x, cmd.pos.y, 0, 0}
197 for ch in cmd.str {
198 if ch&0xc0 != 0x80 {
199 r := min(int(ch), 127)
200 src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + r]
201 render_texture(renderer, &dst, src, cmd.color)
202 dst.x += dst.w
203 }
204 }
205 case ^mu.Command_Rect:
206 SDL.SetRenderDrawColor(renderer, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
207 SDL.RenderFillRect(renderer, &SDL.Rect{cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h})
208 case ^mu.Command_Icon:
209 src := mu.default_atlas[cmd.id]
210 x := cmd.rect.x + (cmd.rect.w - src.w)/2
211 y := cmd.rect.y + (cmd.rect.h - src.h)/2
212 render_texture(renderer, &SDL.Rect{x, y, 0, 0}, src, cmd.color)
213 case ^mu.Command_Clip:
214 SDL.RenderSetClipRect(renderer, &SDL.Rect{cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h})
215 case ^mu.Command_Jump:
216 unreachable()
217 }
218 }
219
220 SDL.RenderPresent(renderer)
221}
222
223
224u8_slider :: proc(ctx: ^mu.Context, val: ^u8, lo, hi: u8) -> (res: mu.Result_Set) {
225 mu.push_id(ctx, uintptr(val))
226
227 @static tmp: mu.Real
228 tmp = mu.Real(val^)
229 res = mu.slider(ctx, &tmp, mu.Real(lo), mu.Real(hi), 0, "%.0f", {.ALIGN_CENTER})
230 val^ = u8(tmp)
231 mu.pop_id(ctx)
232 return
233}
234
235write_log :: proc(str: string) {
236 state.log_buf_len += copy(state.log_buf[state.log_buf_len:], str)
237 state.log_buf_len += copy(state.log_buf[state.log_buf_len:], "\n")
238 state.log_buf_updated = true
239}
240
241read_log :: proc() -> string {
242 return string(state.log_buf[:state.log_buf_len])
243}
244reset_log :: proc() {
245 state.log_buf_updated = true
246 state.log_buf_len = 0
247}
248
249
250all_windows :: proc(ctx: ^mu.Context) {
251 @static opts := mu.Options{.NO_CLOSE}
252
253 if mu.window(ctx, "Demo Window", {40, 40, 300, 450}, opts) {
254 if .ACTIVE in mu.header(ctx, "Window Info") {
255 win := mu.get_current_container(ctx)
256 mu.layout_row(ctx, {54, -1}, 0)
257 mu.label(ctx, "Position:")
258 mu.label(ctx, fmt.tprintf("%d, %d", win.rect.x, win.rect.y))
259 mu.label(ctx, "Size:")
260 mu.label(ctx, fmt.tprintf("%d, %d", win.rect.w, win.rect.h))
261 }
262
263 if .ACTIVE in mu.header(ctx, "Window Options") {
264 mu.layout_row(ctx, {120, 120, 120}, 0)
265 for opt in mu.Opt {
266 state := opt in opts
267 if .CHANGE in mu.checkbox(ctx, fmt.tprintf("%v", opt), &state) {
268 if state {
269 opts += {opt}
270 } else {
271 opts -= {opt}
272 }
273 }
274 }
275 }
276
277 if .ACTIVE in mu.header(ctx, "Test Buttons", {.EXPANDED}) {
278 mu.layout_row(ctx, {86, -110, -1})
279 mu.label(ctx, "Test buttons 1:")
280 if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
281 if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
282 mu.label(ctx, "Test buttons 2:")
283 if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
284 if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
285 }
286
287 if .ACTIVE in mu.header(ctx, "Tree and Text", {.EXPANDED}) {
288 mu.layout_row(ctx, {140, -1})
289 mu.layout_begin_column(ctx)
290 if .ACTIVE in mu.treenode(ctx, "Test 1") {
291 if .ACTIVE in mu.treenode(ctx, "Test 1a") {
292 mu.label(ctx, "Hello")
293 mu.label(ctx, "world")
294 }
295 if .ACTIVE in mu.treenode(ctx, "Test 1b") {
296 if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
297 if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
298 }
299 }
300 if .ACTIVE in mu.treenode(ctx, "Test 2") {
301 mu.layout_row(ctx, {53, 53})
302 if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
303 if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
304 if .SUBMIT in mu.button(ctx, "Button 5") { write_log("Pressed button 5") }
305 if .SUBMIT in mu.button(ctx, "Button 6") { write_log("Pressed button 6") }
306 }
307 if .ACTIVE in mu.treenode(ctx, "Test 3") {
308 @static checks := [3]bool{true, false, true}
309 mu.checkbox(ctx, "Checkbox 1", &checks[0])
310 mu.checkbox(ctx, "Checkbox 2", &checks[1])
311 mu.checkbox(ctx, "Checkbox 3", &checks[2])
312
313 }
314 mu.layout_end_column(ctx)
315
316 mu.layout_begin_column(ctx)
317 mu.layout_row(ctx, {-1})
318 mu.text(ctx,
319 "Lorem ipsum dolor sit amet, consectetur adipiscing "+
320 "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "+
321 "ipsum, eu varius magna felis a nulla.",
322 )
323 mu.layout_end_column(ctx)
324 }
325
326 if .ACTIVE in mu.header(ctx, "Background Colour", {.EXPANDED}) {
327 mu.layout_row(ctx, {-78, -1}, 68)
328 mu.layout_begin_column(ctx)
329 {
330 mu.layout_row(ctx, {46, -1}, 0)
331 mu.label(ctx, "Red:"); u8_slider(ctx, &state.bg.r, 0, 255)
332 mu.label(ctx, "Green:"); u8_slider(ctx, &state.bg.g, 0, 255)
333 mu.label(ctx, "Blue:"); u8_slider(ctx, &state.bg.b, 0, 255)
334 }
335 mu.layout_end_column(ctx)
336
337 r := mu.layout_next(ctx)
338 mu.draw_rect(ctx, r, state.bg)
339 mu.draw_box(ctx, mu.expand_rect(r, 1), ctx.style.colors[.BORDER])
340 mu.draw_control_text(ctx, fmt.tprintf("#%02x%02x%02x", state.bg.r, state.bg.g, state.bg.b), r, .TEXT, {.ALIGN_CENTER})
341 }
342 }
343
344 if mu.window(ctx, "Log Window", {350, 40, 300, 200}, opts) {
345 mu.layout_row(ctx, {-1}, -28)
346 mu.begin_panel(ctx, "Log")
347 mu.layout_row(ctx, {-1}, -1)
348 mu.text(ctx, read_log())
349 if state.log_buf_updated {
350 panel := mu.get_current_container(ctx)
351 panel.scroll.y = panel.content_size.y
352 state.log_buf_updated = false
353 }
354 mu.end_panel(ctx)
355
356 @static buf: [128]byte
357 @static buf_len: int
358 submitted := false
359 mu.layout_row(ctx, {-70, -1})
360 if .SUBMIT in mu.textbox(ctx, buf[:], &buf_len) {
361 mu.set_focus(ctx, ctx.last_id)
362 submitted = true
363 }
364 if .SUBMIT in mu.button(ctx, "Submit") {
365 submitted = true
366 }
367 if submitted {
368 write_log(string(buf[:buf_len]))
369 buf_len = 0
370 }
371 }
372
373 if mu.window(ctx, "Style Window", {350, 250, 300, 240}) {
374 @static colors := [mu.Color_Type]string{
375 .TEXT = "text",
376 .BORDER = "border",
377 .WINDOW_BG = "window bg",
378 .TITLE_BG = "title bg",
379 .TITLE_TEXT = "title text",
380 .PANEL_BG = "panel bg",
381 .BUTTON = "button",
382 .BUTTON_HOVER = "button hover",
383 .BUTTON_FOCUS = "button focus",
384 .BASE = "base",
385 .BASE_HOVER = "base hover",
386 .BASE_FOCUS = "base focus",
387 .SCROLL_BASE = "scroll base",
388 .SCROLL_THUMB = "scroll thumb",
389 .SELECTION_BG = "selection bg",
390 }
391
392 sw := i32(f32(mu.get_current_container(ctx).body.w) * 0.14)
393 mu.layout_row(ctx, {80, sw, sw, sw, sw, -1})
394 for label, col in colors {
395 mu.label(ctx, label)
396 u8_slider(ctx, &ctx.style.colors[col].r, 0, 255)
397 u8_slider(ctx, &ctx.style.colors[col].g, 0, 255)
398 u8_slider(ctx, &ctx.style.colors[col].b, 0, 255)
399 u8_slider(ctx, &ctx.style.colors[col].a, 0, 255)
400 mu.draw_rect(ctx, mu.layout_next(ctx), ctx.style.colors[col])
401 }
402 }
403
404}