Skip to content

Instantly share code, notes, and snippets.

@gingerBill
Created October 2, 2021 23:17
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gingerBill/c7a91318bd7b3be96d63d428b24d19ea to your computer and use it in GitHub Desktop.
Save gingerBill/c7a91318bd7b3be96d63d428b24d19ea to your computer and use it in GitHub Desktop.
microui + raylib Demo in Odin
package microui_raylib
import "core:fmt"
import "core:unicode/utf8"
import rl "vendor:raylib"
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: rl.Texture2D,
}{
bg = {90, 95, 100, 255},
}
main :: proc() {
rl.InitWindow(960, 540, "microui-odin")
defer rl.CloseWindow()
pixels := make([][4]u8, mu.DEFAULT_ATLAS_WIDTH*mu.DEFAULT_ATLAS_HEIGHT)
for alpha, i in mu.default_atlas_alpha {
pixels[i] = {0xff, 0xff, 0xff, alpha}
}
defer delete(pixels)
image := rl.Image{
data = raw_data(pixels),
width = mu.DEFAULT_ATLAS_WIDTH,
height = mu.DEFAULT_ATLAS_HEIGHT,
mipmaps = 1,
format = .UNCOMPRESSED_R8G8B8A8,
}
state.atlas_texture = rl.LoadTextureFromImage(image)
defer rl.UnloadTexture(state.atlas_texture)
ctx := &state.mu_ctx
mu.init(ctx)
ctx.text_width = mu.default_atlas_text_width
ctx.text_height = mu.default_atlas_text_height
rl.SetTargetFPS(60)
main_loop: for !rl.WindowShouldClose() {
{ // text input
text_input: [512]byte = ---
text_input_offset := 0
for text_input_offset < len(text_input) {
ch := rl.GetCharPressed()
if ch == 0 {
break
}
b, w := utf8.encode_rune(ch)
copy(text_input[text_input_offset:], b[:w])
text_input_offset += w
}
mu.input_text(ctx, string(text_input[:text_input_offset]))
}
// mouse coordinates
mouse_pos := [2]i32{rl.GetMouseX(), rl.GetMouseY()}
mu.input_mouse_move(ctx, mouse_pos.x, mouse_pos.y)
mu.input_scroll(ctx, 0, i32(rl.GetMouseWheelMove() * -30))
// mouse buttons
@static buttons_to_key := [?]struct{
rl_button: rl.MouseButton,
mu_button: mu.Mouse,
}{
{.LEFT, .LEFT},
{.RIGHT, .RIGHT},
{.MIDDLE, .MIDDLE},
}
for button in buttons_to_key {
if rl.IsMouseButtonPressed(button.rl_button) {
mu.input_mouse_down(ctx, mouse_pos.x, mouse_pos.y, button.mu_button)
} else if rl.IsMouseButtonReleased(button.rl_button) {
mu.input_mouse_up(ctx, mouse_pos.x, mouse_pos.y, button.mu_button)
}
}
// keyboard
@static keys_to_check := [?]struct{
rl_key: rl.KeyboardKey,
mu_key: mu.Key,
}{
{.LEFT_SHIFT, .SHIFT},
{.RIGHT_SHIFT, .SHIFT},
{.LEFT_CONTROL, .CTRL},
{.RIGHT_CONTROL, .CTRL},
{.LEFT_ALT, .ALT},
{.RIGHT_ALT, .ALT},
{.ENTER, .RETURN},
{.KP_ENTER, .RETURN},
{.BACKSPACE, .BACKSPACE},
}
for key in keys_to_check {
if rl.IsKeyPressed(key.rl_key) {
mu.input_key_down(ctx, key.mu_key)
} else if rl.IsKeyReleased(key.rl_key) {
mu.input_key_up(ctx, key.mu_key)
}
}
mu.begin(ctx)
all_windows(ctx)
mu.end(ctx)
render(ctx)
}
}
render :: proc(ctx: ^mu.Context) {
render_texture :: proc(rect: mu.Rect, pos: [2]i32, color: mu.Color) {
source := rl.Rectangle{
f32(rect.x),
f32(rect.y),
f32(rect.w),
f32(rect.h),
}
position := rl.Vector2{f32(pos.x), f32(pos.y)}
rl.DrawTextureRec(state.atlas_texture, source, position, transmute(rl.Color)color)
}
rl.ClearBackground(transmute(rl.Color)state.bg)
rl.BeginDrawing()
defer rl.EndDrawing()
rl.BeginScissorMode(0, 0, rl.GetScreenWidth(), rl.GetScreenHeight())
defer rl.EndScissorMode()
command_backing: ^mu.Command
for variant in mu.next_command_iterator(ctx, &command_backing) {
switch cmd in variant {
case ^mu.Command_Text:
pos := [2]i32{cmd.pos.x, cmd.pos.y}
for ch in cmd.str do if ch&0xc0 != 0x80 {
r := min(int(ch), 127)
rect := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + r]
render_texture(rect, pos, cmd.color)
pos.x += rect.w
}
case ^mu.Command_Rect:
rl.DrawRectangle(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h, transmute(rl.Color)cmd.color)
case ^mu.Command_Icon:
rect := mu.default_atlas[cmd.id]
x := cmd.rect.x + (cmd.rect.w - rect.w)/2
y := cmd.rect.y + (cmd.rect.h - rect.h)/2
render_texture(rect, {x, y}, cmd.color)
case ^mu.Command_Clip:
rl.EndScissorMode()
rl.BeginScissorMode(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h)
case ^mu.Command_Jump:
unreachable()
}
}
}
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])
}
}
}
@tauraamui
Copy link

The scissor calls are inconsistent with what the demo app in the microui shows to do. This also does not work for me, unless I run it with these changes (just to the render proc)

render :: proc(ctx: ^mu.Context) {
    render_texture :: proc(rect: mu.Rect, pos: [2]i32, color: mu.Color) {
        source := rl.Rectangle{f32(rect.x), f32(rect.y), f32(rect.w), f32(rect.h)}
        position := rl.Vector2{f32(pos.x), f32(pos.y)}

        rl.DrawTextureRec(state.atlas_texture, source, position, transmute(rl.Color)color)
    }

    rl.ClearBackground(transmute(rl.Color)state.bg)

    rl.BeginDrawing()
    defer rl.EndDrawing()

    // REMOVED
    // rl.BeginScissorMode(0, 0, rl.GetScreenWidth(), rl.GetScreenHeight())
    // defer rl.EndScissorMode()

    command_backing: ^mu.Command
    for variant in mu.next_command_iterator(ctx, &command_backing) {
        switch cmd in variant {
        case ^mu.Command_Text:
            pos := [2]i32{cmd.pos.x, cmd.pos.y}
            for ch in cmd.str do if ch & 0xc0 != 0x80 {
                    r := min(int(ch), 127)
                    rect := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + r]
                    render_texture(rect, pos, cmd.color)
                    pos.x += rect.w
                }
        case ^mu.Command_Rect:
            rl.DrawRectangle(
                cmd.rect.x,
                cmd.rect.y,
                cmd.rect.w,
                cmd.rect.h,
                transmute(rl.Color)cmd.color,
            )
        case ^mu.Command_Icon:
            rect := mu.default_atlas[cmd.id]
            x := cmd.rect.x + (cmd.rect.w - rect.w) / 2
            y := cmd.rect.y + (cmd.rect.h - rect.h) / 2
            render_texture(rect, {x, y}, cmd.color)
        case ^mu.Command_Clip:
            rl.EndScissorMode()
            // AMENDED TO APPLY SCISSOR MODE CORRECTLY
            rl.BeginScissorMode(cmd.rect.x, rl.GetScreenHeight() - (cmd.rect.y + cmd.rect.h), cmd.rect.w, cmd.rect.h)
        case ^mu.Command_Jump:
            unreachable()
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment