commit 437d5ea1ee4445d1743d3d3b6fc3583100a393b9
parent 1cb4bfd47e92b64e1b2b8380c91aa0ab4d4fe2bd
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Tue, 3 Dec 2024 09:55:17 +0800
main: example gui with sokol
Diffstat:
| M | src/main.odin | | | 334 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 334 insertions(+), 0 deletions(-)
diff --git a/src/main.odin b/src/main.odin
@@ -1,6 +1,340 @@
package main
+import "base:runtime"
+import "core:fmt"
+
+import sapp "sokol/sokol/app"
+import sg "sokol/sokol/gfx"
+import sgl "sokol/sokol/gl"
+import sglue "sokol/sokol/glue"
+
+import mu "vendor:microui"
+
+state := struct {
+ pip: sgl.Pipeline,
+ mu_ctx: mu.Context,
+ log_buf: [1 << 16]byte,
+ log_buf_len: int,
+ log_buf_updated: bool,
+ bg: mu.Color,
+ atlas_img: sg.Image,
+ atlas_smp: sg.Sampler,
+} {
+ bg = {90, 95, 100, 255},
+}
+
+r_init :: proc() {
+ 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
+ }
+
+ state.atlas_img = sg.make_image(
+ sg.Image_Desc {
+ width = mu.DEFAULT_ATLAS_WIDTH,
+ height = mu.DEFAULT_ATLAS_HEIGHT,
+ data = {
+ subimage = {
+ 0 = {
+ 0 = {
+ ptr = raw_data(pixels),
+ size = mu.DEFAULT_ATLAS_WIDTH * mu.DEFAULT_ATLAS_HEIGHT * 4,
+ },
+ },
+ },
+ },
+ },
+ )
+
+ state.atlas_smp = sg.make_sampler(
+ sg.Sampler_Desc{min_filter = .NEAREST, mag_filter = .NEAREST},
+ )
+
+
+ state.pip = sgl.make_pipeline(
+ sg.Pipeline_Desc {
+ colors = {
+ 0 = {
+ blend = {
+ enabled = true,
+ src_factor_rgb = .SRC_ALPHA,
+ dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
+ },
+ },
+ },
+ },
+ )
+
+ free(raw_data(pixels))
+}
+
+init :: proc "c" () {
+ context = runtime.default_context()
+
+ sg.setup(sg.Desc{environment = sglue.environment()})
+ sgl.setup({})
+
+ r_init()
+
+ ctx := &state.mu_ctx
+ mu.init(ctx)
+
+ ctx.text_width = mu.default_atlas_text_width
+ ctx.text_height = mu.default_atlas_text_height
+}
+
+r_begin :: proc(disp_width, disp_height: i32) {
+ sgl.defaults()
+ sgl.push_pipeline()
+ sgl.load_pipeline(state.pip)
+ sgl.enable_texture()
+ sgl.texture(state.atlas_img, state.atlas_smp)
+ sgl.matrix_mode_projection()
+ sgl.push_matrix()
+ sgl.ortho(0.0, f32(disp_width), f32(disp_height), 0.0, -1.0, +1.0)
+ sgl.begin_quads()
+}
+
+r_push_quad :: proc(dst: mu.Rect, src: mu.Rect, color: mu.Color) {
+ u0 := f32(src.x) / f32(mu.DEFAULT_ATLAS_WIDTH)
+ v0 := f32(src.y) / f32(mu.DEFAULT_ATLAS_HEIGHT)
+ u1 := f32(src.x + src.w) / f32(mu.DEFAULT_ATLAS_WIDTH)
+ v1 := f32(src.y + src.h) / f32(mu.DEFAULT_ATLAS_HEIGHT)
+
+ x0 := f32(dst.x)
+ y0 := f32(dst.y)
+ x1 := f32(dst.x + dst.w)
+ y1 := f32(dst.y + dst.h)
+
+ sgl.c4b(color.r, color.g, color.b, color.a)
+ sgl.v2f_t2f(x0, y0, u0, v0)
+ sgl.v2f_t2f(x1, y0, u1, v0)
+ sgl.v2f_t2f(x1, y1, u1, v1)
+ sgl.v2f_t2f(x0, y1, u0, v1)
+}
+
+r_draw_text :: proc(text: string, pos: mu.Vec2, color: mu.Color) {
+ dst := mu.Rect{pos.x, pos.y, 0, 0}
+ for ch in text {
+ src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + int(ch)]
+ dst.w = src.w
+ dst.h = src.h
+ r_push_quad(dst, src, color)
+ dst.x += dst.w
+ }
+}
+
+r_draw_rect :: proc(rect: mu.Rect, color: mu.Color) {
+ r_push_quad(rect, mu.default_atlas[mu.DEFAULT_ATLAS_WHITE], color)
+}
+
+r_draw_icon :: proc(id: mu.Icon, rect: mu.Rect, color: mu.Color) {
+ src := mu.default_atlas[id]
+ x := rect.x + (rect.w - src.w) / 2
+ y := rect.y + (rect.h - src.h) / 2
+ r_push_quad(mu.Rect{x, y, src.w, src.h}, src, color)
+}
+
+r_set_clip_rect :: proc(rect: mu.Rect) {
+ sgl.end()
+ sgl.scissor_rect(rect.x, rect.y, rect.w, rect.h, true)
+ sgl.begin_quads()
+}
+
+r_end :: proc() {
+ sgl.end()
+ sgl.pop_matrix()
+ sgl.pop_pipeline()
+}
+
+r_draw :: proc() {
+ sgl.draw()
+}
+
+frame :: proc "c" () {
+ context = runtime.default_context()
+
+ mu_ctx := &state.mu_ctx
+ mu.begin(mu_ctx)
+ all_windows(mu_ctx)
+ mu.end(mu_ctx)
+
+ r_begin(sapp.width(), sapp.height())
+ command_backing: ^mu.Command
+ for variant in mu.next_command_iterator(mu_ctx, &command_backing) {
+ switch cmd in variant {
+ case ^mu.Command_Text:
+ r_draw_text(cmd.str, cmd.pos, cmd.color)
+ case ^mu.Command_Rect:
+ r_draw_rect(cmd.rect, cmd.color)
+ case ^mu.Command_Icon:
+ r_draw_icon(cmd.id, cmd.rect, cmd.color)
+ case ^mu.Command_Clip:
+ r_set_clip_rect(cmd.rect)
+ case ^mu.Command_Jump:
+ unreachable()
+ }
+ }
+ r_end()
+
+ sg.begin_pass(
+ sg.Pass {
+ action = {
+ colors = {
+ 0 = {
+ load_action = .CLEAR,
+ clear_value = {
+ f32(state.bg.r) / 255.0,
+ f32(state.bg.g) / 255.0,
+ f32(state.bg.b) / 255.0,
+ 1.0,
+ },
+ },
+ },
+ },
+ swapchain = sglue.swapchain(),
+ },
+ )
+
+ r_draw()
+ sg.end_pass()
+ sg.commit()
+}
+
+key_map := map[sapp.Keycode]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,
+}
+event :: proc "c" (ev: ^sapp.Event) {
+
+ context = runtime.default_context()
+ mu_ctx := &state.mu_ctx
+ #partial switch ev.type {
+ case .MOUSE_DOWN:
+ mu.input_mouse_down(mu_ctx, i32(ev.mouse_x), i32(ev.mouse_y), mu.Mouse(ev.mouse_button))
+ case .MOUSE_UP:
+ mu.input_mouse_up(mu_ctx, i32(ev.mouse_x), i32(ev.mouse_y), mu.Mouse(ev.mouse_button))
+ case .MOUSE_MOVE:
+ mu.input_mouse_move(mu_ctx, i32(ev.mouse_x), i32(ev.mouse_y))
+ case .MOUSE_SCROLL:
+ mu.input_scroll(mu_ctx, 0, i32(ev.scroll_y))
+ case .KEY_DOWN:
+ if ev.key_code in key_map {
+ mu.input_key_down(mu_ctx, key_map[ev.key_code])
+ }
+ case .KEY_UP:
+ if ev.key_code in key_map {
+ mu.input_key_up(mu_ctx, key_map[ev.key_code])
+ }
+ case .CHAR:
+ mu.input_text(mu_ctx, fmt.tprint(rune(ev.char_code)))
+ }
+}
+
+cleanup :: proc "c" () {
+ sgl.shutdown()
+ sg.shutdown()
+}
+
main :: proc() {
+ sapp.run(
+ {
+ init_cb = init,
+ frame_cb = frame,
+ cleanup_cb = cleanup,
+ event_cb = event,
+ width = 300,
+ height = 300,
+ sample_count = 4,
+ window_title = "sokol microui",
+ icon = {sokol_default = true},
+ },
+ )
+}
+
+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) {
+ window_width := sapp.width()
+ window_height := sapp.height()
+
+ if mu.window(
+ ctx,
+ "Spark",
+ {x = 0, y = 0, w = window_width, h = window_height},
+ opt = {.NO_TITLE, .NO_INTERACT, .NO_FRAME, .NO_CLOSE, .NO_RESIZE, .NO_SCROLL},
+ ) {
+ // To achieve a single window layout we need to syncrhonize the microui window rectangle
+ // with the dimensions of the sokol window.
+ cnt := mu.get_current_container(ctx)
+ cnt.rect.w = window_width
+ cnt.rect.h = window_height
+
+ mu.layout_row(ctx, {-1})
+
+ @(static) prog: mu.Real = 0.0
+ prog = min(1.0, prog + 0.001)
+ progress(ctx, &prog)
+ }
+}
+
+
+progress :: proc(
+ ctx: ^mu.Context,
+ value: ^mu.Real,
+ opt: mu.Options = {.ALIGN_CENTER},
+) -> (
+ res: mu.Result_Set,
+) {
+ id := mu.get_id(ctx, uintptr(value))
+ base := mu.layout_next(ctx)
+ mu.draw_control_frame(ctx, id, base, .BASE, opt)
+
+ prog := mu.Rect{base.x, base.y, base.w - i32((1.0 - value^) * f32(base.w)), base.h}
+ mu.draw_rect(ctx, prog, {50, 50, 100, 255})
+
+ return
+}
+draw_rrect :: proc(ctx: ^mu.Context, rect: mu.Rect, radius: i32, color: mu.Color) {
+ rect := rect
+ rect = mu.intersect_rects(rect, mu.get_clip_rect(ctx))
+ if rect.w > 0 && rect.h > 0 {
+ cmd := mu.push_command(ctx, mu.Command_Rect)
+ cmd.rect = rect
+ cmd.color = color
+ }
}