Started modeling an interface. Early MicroUI backend.

This commit is contained in:
2026-02-10 11:38:37 +01:00
parent 07c01b4b38
commit ac81bf5aaf
2 changed files with 397 additions and 1 deletions

View File

@@ -1,3 +1,249 @@
package imgui
// Hello, world!
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)
}

150
microui/microui.odin Normal file
View File

@@ -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^ = {}
}