diff --git a/imgui.odin b/imgui.odin index 7fe93cf..1475983 100644 --- a/imgui.odin +++ b/imgui.odin @@ -1,3 +1,249 @@ package imgui -// Hello, world! \ No newline at end of file +import "core:container/queue" +import "core:log" + +import "microui" + +MAX_DRAW_COMMANDS :: #config(IMGUI_MAX_DRAW_COMMANDS, 512 * 1024) + +// TODO: SS - Consider not having 'microui' as a seperate package and instead have it (and other backends) in the 'imgui' package directly. + +IMGUI_BACKEND_MICROUI :: #config(IMGUI_BACKEND_MICROUI, false) + +when IMGUI_BACKEND_MICROUI { + Context_Backend :: microui.Context +} + +Context :: struct { + backend: ^Context_Backend, + + draw_commands: queue.Queue(Draw_Command), + input: Input, +} + +Input :: struct { + mouse_state: Mouse_State, +} + +Mouse_Button_State :: enum { + Up, + Pressed, + Down, + Released, +} + +Mouse_State :: struct { + pos_x, pos_y: u16, + left, middle, right: Mouse_Button_State, +} + +Draw_Command :: struct { + color: [4]u8, + type: Draw_Command_Type, +} + +Draw_Command_Type :: union { + Draw_Command_Rect, +} + +Draw_Command_Rect :: struct { + x, y, width, height: u16, +} + +Rect :: struct { + x, y, width, height: u16, +} + +init :: proc(ctx: ^Context) -> bool { + assert(ctx != nil) + assert(ctx.backend == nil) + + backend, err := new(Context_Backend) + if err != .None { + log.errorf("Failed to allocate space for the backend. Error: %v", err) + return false + } + ctx.backend = backend + + ok := false + + when IMGUI_BACKEND_MICROUI { + ok = microui.init(ctx.backend) + } + + if !ok { + free(backend) + return false + } + + queue_err := queue.init(&ctx.draw_commands, MAX_DRAW_COMMANDS) + if queue_err != .None { + free(backend) + log.errorf("Failed to allocate space for the queue of imgui draw-commands. Error: %v", queue_err) + return false + } + + return true +} + +set_input :: proc(ctx: ^Context, input: Input) { + ctx.input = input +} + +begin :: proc(ctx: ^Context) { + assert(ctx != nil) + assert(ctx.backend != nil) + + when IMGUI_BACKEND_MICROUI { + input: microui.Input + input.mouse_x = ctx.input.mouse_state.pos_x + input.mouse_y = ctx.input.mouse_state.pos_y + + if ctx.input.mouse_state.left == .Pressed { + input.mouse_buttons_pressed += { .LEFT } + } + else if ctx.input.mouse_state.left == .Released { + input.mouse_buttons_released += { .LEFT } + } + + if ctx.input.mouse_state.middle == .Pressed { + input.mouse_buttons_pressed += { .MIDDLE } + } + else if ctx.input.mouse_state.middle == .Released { + input.mouse_buttons_released += { .MIDDLE } + } + + if ctx.input.mouse_state.right == .Pressed { + input.mouse_buttons_pressed += { .RIGHT } + } + else if ctx.input.mouse_state.right == .Released { + input.mouse_buttons_released += { .RIGHT } + } + + microui.begin(ctx.backend, input) + } +} + +end :: proc(ctx: ^Context) { + assert(ctx != nil) + assert(ctx.backend != nil) + + when IMGUI_BACKEND_MICROUI { + microui.end(ctx.backend) + } +} + +begin_window :: proc(ctx: ^Context, title: string, rect: Rect) -> bool { + assert(ctx != nil) + + when IMGUI_BACKEND_MICROUI { + return microui.begin_window(ctx.backend, title, microui.Rect { + x = i32(rect.x), + y = i32(rect.y), + w = i32(rect.width), + h = i32(rect.height), + }) + } + + return false +} + +end_window :: proc(ctx: ^Context) { + assert(ctx != nil) + + when IMGUI_BACKEND_MICROUI { + microui.end_window(ctx.backend) + } +} + +button :: proc(ctx: ^Context, title: string) -> bool { + when IMGUI_BACKEND_MICROUI { + return microui.button(ctx.backend, title) + } + + return false +} + +collect :: proc(ctx: ^Context) { // TODO: SS - Come up with a better name. Adds commands from backend to queue in ctx. + assert(ctx != nil) + + // TODO: SS - Reduce code-repetition if possible. MicroUI asks to be "popped"/iterated like this, apparently. + + when IMGUI_BACKEND_MICROUI { + last_cmd: ^microui.Command = nil + for { + dc: Draw_Command + dc.type = nil + + cmd := microui.pop_draw_command(ctx.backend, last_cmd) + if cmd == nil { + break + } + + last_cmd = cmd + + #partial switch &v in &cmd.variant { + // case ^microui.Command_Jump: {} // Internal. + // case ^microui.Command_Clip: { + // } + // case ^microui.Command_Icon: { + // } + case ^microui.Command_Rect: { + assert(v.rect.x >= 0) + assert(v.rect.y >= 0) + assert(v.rect.w >= 0) + assert(v.rect.h >= 0) + + dc.color = { + v.color.r, + v.color.g, + v.color.b, + v.color.a, + } + dc.type = Draw_Command_Rect { + x = u16(v.rect.x), + y = u16(v.rect.y), + width = u16(v.rect.w), + height = u16(v.rect.h), + } + } + // case ^microui.Command_Text: { + // } + } + + if dc.type != nil { + queue.push_back(&ctx.draw_commands, dc) + } + } + } +} + +pop_draw_command :: proc(ctx: ^Context, out_draw_command: ^Draw_Command) -> bool { + assert(ctx != nil) + assert(out_draw_command != nil) + + out_draw_command^ = {} + + elem, ok := queue.pop_front_safe(&ctx.draw_commands) + if !ok { + return false + } + + out_draw_command^ = elem + return true +} + +shutdown :: proc(ctx: ^Context) { + assert(ctx != nil) + assert(ctx.backend != nil) + + when IMGUI_BACKEND_MICROUI { + microui.shutdown(ctx.backend) + } + + free(ctx.backend) + ctx.backend = nil + + queue.destroy(&ctx.draw_commands) +} \ No newline at end of file diff --git a/microui/microui.odin b/microui/microui.odin new file mode 100644 index 0000000..b23c0df --- /dev/null +++ b/microui/microui.odin @@ -0,0 +1,150 @@ +package microui_wrapper + +import "core:log" + +import mu "vendor:microui" + +Context :: mu.Context + +@(private) set_clipboard :: proc(userdata: rawptr, text: string) -> bool { + return false +} +@(private) get_clipboard :: proc(userdata: rawptr) -> (string, bool) { + return {}, false +} + +@(private) text_width :: proc(font: mu.Font, text: string) -> i32 { + // log.warnf("TODO: SS - Implement %v.", #procedure) + return 0 // TEMP: SS +} +@(private) text_height :: proc(font: mu.Font) -> i32 { + // log.warnf("TODO: SS - Implement %v.", #procedure) + return 0 // TEMP: SS +} + +Command :: mu.Command +Command_Jump :: mu.Command_Jump // Internal. Should not be used externally. +Command_Clip :: mu.Command_Clip +Command_Icon :: mu.Command_Icon +Command_Rect :: mu.Command_Rect +Command_Text :: mu.Command_Text +Rect :: mu.Rect + +Input :: struct { + mouse_x, mouse_y: u16, + mouse_buttons_pressed: mu.Mouse_Set, + mouse_buttons_released: mu.Mouse_Set, +} + +init :: proc(ctx: ^Context) -> bool { + assert(ctx != nil) + + clipboard_user_data: rawptr = nil // TEMP: SS + mu.init(ctx, set_clipboard, get_clipboard, clipboard_user_data) + ctx.text_width = text_width + ctx.text_height = text_height + + return true +} + +begin :: proc(ctx: ^Context, input: Input) { + assert(ctx != nil) + + { // Apply input. + mx := i32(input.mouse_x) + my := i32(input.mouse_y) + mu.input_mouse_move(ctx, mx, my) + + for b in input.mouse_buttons_pressed { + mu.input_mouse_down(ctx, mx, my, b) + } + for b in input.mouse_buttons_released { + mu.input_mouse_up(ctx, mx, my, b) + } + } + + // Now, begin. + mu.begin(ctx) + + { // TEMP: SS - Just trying it out. + // if mu.begin_window( + // ctx, + // "Test", + // mu.Rect { + // x = 0, y = 20, + // w = 256, h = 256, + // }, + // opt = {} + // ) { + // mu.label(ctx, "First:") + // if mu.button(ctx, "Button1") == {.SUBMIT} { + // log.infof("Button1 pressed") + // } + + // mu.label(ctx, "Second:"); + // if mu.button(ctx, "Button2") == {.SUBMIT} { + // mu.open_popup(ctx, "My Popup"); + // } + + // if (mu.begin_popup(ctx, "My Popup")) { + // mu.label(ctx, "Hello world!") + + // if mu.button(ctx, "Button3") == {.SUBMIT} { + // log.infof("Button3 pressed") + // } + + // mu.end_popup(ctx) + // } + + + // mu.end_window(ctx) + // } + } +} + +end :: proc(ctx: ^Context) { + assert(ctx != nil) + mu.end(ctx) +} + + +begin_window :: proc(ctx: ^Context, title: string, rect: mu.Rect) -> bool { + assert(ctx != nil) + + opt := mu.Options { + // .NO_CLOSE, // TEMP: SS - Some weird bug is happening. Windows are closing randomly, but this helps.. + } + + return mu.begin_window( + ctx, + title, + rect, + opt, + ) +} + +end_window :: proc(ctx: ^Context) { + assert(ctx != nil) + + mu.end_window(ctx) +} + +button :: proc(ctx: ^Context, title: string) -> bool { + return mu.button(ctx, title) == {.SUBMIT} +} + +pop_draw_command :: proc(ctx: ^Context, last_cmd: ^mu.Command) -> ^mu.Command { + cmd: ^mu.Command = last_cmd + if !mu.next_command(ctx, &cmd) { + return nil + } + + assert(cmd != nil) + return cmd +} + +shutdown :: proc(ctx: ^Context) { + assert(ctx != nil) + + ctx^ = {} +} \ No newline at end of file