spark

Cross Platform Minimal Installer GUI
Log | Files | Refs | Submodules | README

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:
Msrc/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 + } }