Skip to content

Instantly share code, notes, and snippets.

@gingerBill
Last active April 18, 2024 18:55
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gingerBill/5bbcca224bf8d9dcd09dde38b2567d10 to your computer and use it in GitHub Desktop.
Save gingerBill/5bbcca224bf8d9dcd09dde38b2567d10 to your computer and use it in GitHub Desktop.
microui + SDL Demo in Odin
package microui_sdl
import "core:fmt"
import "core:c/libc"
import SDL "vendor:sdl2"
import mu "vendor:microui"
state := struct {
mu_ctx: mu.Context,
log_buf: [1<<16]byte,
log_buf_len: int,
log_buf_updated: bool,
bg: mu.Color,
atlas_texture: ^SDL.Texture,
}{
bg = {90, 95, 100, 255},
}
main :: proc() {
if err := SDL.Init({.VIDEO}); err != 0 {
fmt.eprintln(err)
return
}
defer SDL.Quit()
window := SDL.CreateWindow("microui-odin", SDL.WINDOWPOS_UNDEFINED, SDL.WINDOWPOS_UNDEFINED, 960, 540, {.SHOWN, .RESIZABLE})
if window == nil {
fmt.eprintln(SDL.GetError())
return
}
defer SDL.DestroyWindow(window)
backend_idx: i32 = -1
if n := SDL.GetNumRenderDrivers(); n <= 0 {
fmt.eprintln("No render drivers available")
return
} else {
for i in 0..<n {
info: SDL.RendererInfo
if err := SDL.GetRenderDriverInfo(i, &info); err == 0 {
// NOTE(bill): "direct3d" seems to not work correctly
if info.name == "opengl" {
backend_idx = i
break
}
}
}
}
renderer := SDL.CreateRenderer(window, backend_idx, {.ACCELERATED, .PRESENTVSYNC})
if renderer == nil {
fmt.eprintln("SDL.CreateRenderer:", SDL.GetError())
return
}
defer SDL.DestroyRenderer(renderer)
state.atlas_texture = SDL.CreateTexture(renderer, u32(SDL.PixelFormatEnum.RGBA32), .TARGET, mu.DEFAULT_ATLAS_WIDTH, mu.DEFAULT_ATLAS_HEIGHT)
assert(state.atlas_texture != nil)
if err := SDL.SetTextureBlendMode(state.atlas_texture, .BLEND); err != 0 {
fmt.eprintln("SDL.SetTextureBlendMode:", err)
return
}
pixels := make([][4]u8, mu.DEFAULT_ATLAS_WIDTH*mu.DEFAULT_ATLAS_HEIGHT)
for alpha, i in mu.default_atlas_alpha {
pixels[i].rgb = 0xff
pixels[i].a = alpha
}
if err := SDL.UpdateTexture(state.atlas_texture, nil, raw_data(pixels), 4*mu.DEFAULT_ATLAS_WIDTH); err != 0 {
fmt.eprintln("SDL.UpdateTexture:", err)
return
}
ctx := &state.mu_ctx
mu.init(ctx)
ctx.text_width = mu.default_atlas_text_width
ctx.text_height = mu.default_atlas_text_height
main_loop: for {
for e: SDL.Event; SDL.PollEvent(&e) != 0; /**/ {
#partial switch e.type {
case .QUIT:
break main_loop
case .MOUSEMOTION:
mu.input_mouse_move(ctx, e.motion.x, e.motion.y)
case .MOUSEWHEEL:
mu.input_scroll(ctx, e.wheel.x * 30, e.wheel.y * -30)
case .TEXTINPUT:
mu.input_text(ctx, string(cstring(&e.text.text[0])))
case .MOUSEBUTTONDOWN, .MOUSEBUTTONUP:
fn := mu.input_mouse_down if e.type == .MOUSEBUTTONDOWN else mu.input_mouse_up
switch e.button.button {
case SDL.BUTTON_LEFT: fn(ctx, e.button.x, e.button.y, .LEFT)
case SDL.BUTTON_MIDDLE: fn(ctx, e.button.x, e.button.y, .MIDDLE)
case SDL.BUTTON_RIGHT: fn(ctx, e.button.x, e.button.y, .RIGHT)
}
case .KEYDOWN, .KEYUP:
if e.type == .KEYUP && e.key.keysym.sym == .ESCAPE {
SDL.PushEvent(&SDL.Event{type = .QUIT})
}
fn := mu.input_key_down if e.type == .KEYDOWN else mu.input_key_up
#partial switch e.key.keysym.sym {
case .LSHIFT: fn(ctx, .SHIFT)
case .RSHIFT: fn(ctx, .SHIFT)
case .LCTRL: fn(ctx, .CTRL)
case .RCTRL: fn(ctx, .CTRL)
case .LALT: fn(ctx, .ALT)
case .RALT: fn(ctx, .ALT)
case .RETURN: fn(ctx, .RETURN)
case .KP_ENTER: fn(ctx, .RETURN)
case .BACKSPACE: fn(ctx, .BACKSPACE)
}
}
}
mu.begin(ctx)
all_windows(ctx)
mu.end(ctx)
render(ctx, renderer)
}
}
render :: proc(ctx: ^mu.Context, renderer: ^SDL.Renderer) {
render_texture :: proc(renderer: ^SDL.Renderer, dst: ^SDL.Rect, src: mu.Rect, color: mu.Color) {
dst.w = src.w
dst.h = src.h
SDL.SetTextureAlphaMod(state.atlas_texture, color.a)
SDL.SetTextureColorMod(state.atlas_texture, color.r, color.g, color.b)
SDL.RenderCopy(renderer, state.atlas_texture, &SDL.Rect{src.x, src.y, src.w, src.h}, dst)
}
viewport_rect := &SDL.Rect{}
SDL.GetRendererOutputSize(renderer, &viewport_rect.w, &viewport_rect.h)
SDL.RenderSetViewport(renderer, viewport_rect)
SDL.RenderSetClipRect(renderer, viewport_rect)
SDL.SetRenderDrawColor(renderer, state.bg.r, state.bg.g, state.bg.b, state.bg.a)
SDL.RenderClear(renderer)
command_backing: ^mu.Command
for variant in mu.next_command_iterator(ctx, &command_backing) {
switch cmd in variant {
case ^mu.Command_Text:
dst := SDL.Rect{cmd.pos.x, cmd.pos.y, 0, 0}
for ch in cmd.str do if ch&0xc0 != 0x80 {
r := min(int(ch), 127)
src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + r]
render_texture(renderer, &dst, src, cmd.color)
dst.x += dst.w
}
case ^mu.Command_Rect:
SDL.SetRenderDrawColor(renderer, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a)
SDL.RenderFillRect(renderer, &SDL.Rect{cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h})
case ^mu.Command_Icon:
src := mu.default_atlas[cmd.id]
x := cmd.rect.x + (cmd.rect.w - src.w)/2
y := cmd.rect.y + (cmd.rect.h - src.h)/2
render_texture(renderer, &SDL.Rect{x, y, 0, 0}, src, cmd.color)
case ^mu.Command_Clip:
SDL.RenderSetClipRect(renderer, &SDL.Rect{cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h})
case ^mu.Command_Jump:
unreachable()
}
}
SDL.RenderPresent(renderer)
}
u8_slider :: proc(ctx: ^mu.Context, val: ^u8, lo, hi: u8) -> (res: mu.Result_Set) {
mu.push_id(ctx, uintptr(val))
@static tmp: mu.Real
tmp = mu.Real(val^)
res = mu.slider(ctx, &tmp, mu.Real(lo), mu.Real(hi), 0, "%.0f", {.ALIGN_CENTER})
val^ = u8(tmp)
mu.pop_id(ctx)
return
}
write_log :: proc(str: string) {
state.log_buf_len += copy(state.log_buf[state.log_buf_len:], str)
state.log_buf_len += copy(state.log_buf[state.log_buf_len:], "\n")
state.log_buf_updated = true
}
read_log :: proc() -> string {
return string(state.log_buf[:state.log_buf_len])
}
reset_log :: proc() {
state.log_buf_updated = true
state.log_buf_len = 0
}
all_windows :: proc(ctx: ^mu.Context) {
@static opts := mu.Options{.NO_CLOSE}
if mu.window(ctx, "Demo Window", {40, 40, 300, 450}, opts) {
if .ACTIVE in mu.header(ctx, "Window Info") {
win := mu.get_current_container(ctx)
mu.layout_row(ctx, {54, -1}, 0)
mu.label(ctx, "Position:")
mu.label(ctx, fmt.tprintf("%d, %d", win.rect.x, win.rect.y))
mu.label(ctx, "Size:")
mu.label(ctx, fmt.tprintf("%d, %d", win.rect.w, win.rect.h))
}
if .ACTIVE in mu.header(ctx, "Window Options") {
mu.layout_row(ctx, {120, 120, 120}, 0)
for opt in mu.Opt {
state := opt in opts
if .CHANGE in mu.checkbox(ctx, fmt.tprintf("%v", opt), &state) {
if state {
opts += {opt}
} else {
opts -= {opt}
}
}
}
}
if .ACTIVE in mu.header(ctx, "Test Buttons", {.EXPANDED}) {
mu.layout_row(ctx, {86, -110, -1})
mu.label(ctx, "Test buttons 1:")
if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
mu.label(ctx, "Test buttons 2:")
if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
}
if .ACTIVE in mu.header(ctx, "Tree and Text", {.EXPANDED}) {
mu.layout_row(ctx, {140, -1})
mu.layout_begin_column(ctx)
if .ACTIVE in mu.treenode(ctx, "Test 1") {
if .ACTIVE in mu.treenode(ctx, "Test 1a") {
mu.label(ctx, "Hello")
mu.label(ctx, "world")
}
if .ACTIVE in mu.treenode(ctx, "Test 1b") {
if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
}
}
if .ACTIVE in mu.treenode(ctx, "Test 2") {
mu.layout_row(ctx, {53, 53})
if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
if .SUBMIT in mu.button(ctx, "Button 5") { write_log("Pressed button 5") }
if .SUBMIT in mu.button(ctx, "Button 6") { write_log("Pressed button 6") }
}
if .ACTIVE in mu.treenode(ctx, "Test 3") {
@static checks := [3]bool{true, false, true}
mu.checkbox(ctx, "Checkbox 1", &checks[0])
mu.checkbox(ctx, "Checkbox 2", &checks[1])
mu.checkbox(ctx, "Checkbox 3", &checks[2])
}
mu.layout_end_column(ctx)
mu.layout_begin_column(ctx)
mu.layout_row(ctx, {-1})
mu.text(ctx,
"Lorem ipsum dolor sit amet, consectetur adipiscing "+
"elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "+
"ipsum, eu varius magna felis a nulla.",
)
mu.layout_end_column(ctx)
}
if .ACTIVE in mu.header(ctx, "Background Colour", {.EXPANDED}) {
mu.layout_row(ctx, {-78, -1}, 68)
mu.layout_begin_column(ctx)
{
mu.layout_row(ctx, {46, -1}, 0)
mu.label(ctx, "Red:"); u8_slider(ctx, &state.bg.r, 0, 255)
mu.label(ctx, "Green:"); u8_slider(ctx, &state.bg.g, 0, 255)
mu.label(ctx, "Blue:"); u8_slider(ctx, &state.bg.b, 0, 255)
}
mu.layout_end_column(ctx)
r := mu.layout_next(ctx)
mu.draw_rect(ctx, r, state.bg)
mu.draw_box(ctx, mu.expand_rect(r, 1), ctx.style.colors[.BORDER])
mu.draw_control_text(ctx, fmt.tprintf("#%02x%02x%02x", state.bg.r, state.bg.g, state.bg.b), r, .TEXT, {.ALIGN_CENTER})
}
}
if mu.window(ctx, "Log Window", {350, 40, 300, 200}, opts) {
mu.layout_row(ctx, {-1}, -28)
mu.begin_panel(ctx, "Log")
mu.layout_row(ctx, {-1}, -1)
mu.text(ctx, read_log())
if state.log_buf_updated {
panel := mu.get_current_container(ctx)
panel.scroll.y = panel.content_size.y
state.log_buf_updated = false
}
mu.end_panel(ctx)
@static buf: [128]byte
@static buf_len: int
submitted := false
mu.layout_row(ctx, {-70, -1})
if .SUBMIT in mu.textbox(ctx, buf[:], &buf_len) {
mu.set_focus(ctx, ctx.last_id)
submitted = true
}
if .SUBMIT in mu.button(ctx, "Submit") {
submitted = true
}
if submitted {
write_log(string(buf[:buf_len]))
buf_len = 0
}
}
if mu.window(ctx, "Style Window", {350, 250, 300, 240}) {
@static colors := [mu.Color_Type]string{
.TEXT = "text",
.BORDER = "border",
.WINDOW_BG = "window bg",
.TITLE_BG = "title bg",
.TITLE_TEXT = "title text",
.PANEL_BG = "panel bg",
.BUTTON = "button",
.BUTTON_HOVER = "button hover",
.BUTTON_FOCUS = "button focus",
.BASE = "base",
.BASE_HOVER = "base hover",
.BASE_FOCUS = "base focus",
.SCROLL_BASE = "scroll base",
.SCROLL_THUMB = "scroll thumb",
}
sw := i32(f32(mu.get_current_container(ctx).body.w) * 0.14)
mu.layout_row(ctx, {80, sw, sw, sw, sw, -1})
for label, col in colors {
mu.label(ctx, label)
u8_slider(ctx, &ctx.style.colors[col].r, 0, 255)
u8_slider(ctx, &ctx.style.colors[col].g, 0, 255)
u8_slider(ctx, &ctx.style.colors[col].b, 0, 255)
u8_slider(ctx, &ctx.style.colors[col].a, 0, 255)
mu.draw_rect(ctx, mu.layout_next(ctx), ctx.style.colors[col])
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment