|
|
@@ -2,6 +2,10 @@ package main
|
|
|
|
|
|
import "base:runtime"
|
|
|
import "core:fmt"
|
|
|
+import "core:math"
|
|
|
+import "core:math/linalg"
|
|
|
+
|
|
|
+// Vendor libs
|
|
|
import mu "third-party:microui"
|
|
|
import sapp "third-party:sokol/app"
|
|
|
import sg "third-party:sokol/gfx"
|
|
|
@@ -9,20 +13,46 @@ import sgl "third-party:sokol/gl"
|
|
|
import sglue "third-party:sokol/glue"
|
|
|
import slog "third-party:sokol/log"
|
|
|
|
|
|
-State :: struct {
|
|
|
- pip: sg.Pipeline,
|
|
|
- bind: sg.Bindings,
|
|
|
+// --- Data ---
|
|
|
+
|
|
|
+Camera :: struct {
|
|
|
+ pos: linalg.Vector3f32,
|
|
|
+ pitch: f32,
|
|
|
+ yaw: f32,
|
|
|
+}
|
|
|
+
|
|
|
+Input_State :: struct {
|
|
|
+ keys: map[sapp.Keycode]bool,
|
|
|
+ mouse_dx: f32,
|
|
|
+ mouse_dy: f32,
|
|
|
+ locked: bool, // Курсор захвачен окном?
|
|
|
+}
|
|
|
+
|
|
|
+App_State :: struct {
|
|
|
+ // Sokol GFX resources
|
|
|
pass_action: sg.Pass_Action,
|
|
|
+
|
|
|
+ // MicroUI context
|
|
|
mu_ctx: mu.Context,
|
|
|
|
|
|
- // Ресурсы
|
|
|
+ // UI Resources
|
|
|
atlas_img: sg.Image,
|
|
|
atlas_view: sg.View,
|
|
|
atlas_smp: sg.Sampler,
|
|
|
ui_pip: sgl.Pipeline,
|
|
|
+
|
|
|
+ // 3D Resources
|
|
|
+ cube_pip: sgl.Pipeline,
|
|
|
+
|
|
|
+ // Runtime data
|
|
|
+ dpi_scale: f32,
|
|
|
+ camera: Camera,
|
|
|
+ input: Input_State,
|
|
|
}
|
|
|
|
|
|
-state: State
|
|
|
+state: App_State
|
|
|
+
|
|
|
+// --- Logic / Input ---
|
|
|
|
|
|
map_mouse_button :: proc(btn: sapp.Mousebutton) -> (mu.Mouse, bool) {
|
|
|
switch btn {
|
|
|
@@ -54,40 +84,125 @@ map_key :: proc(key: sapp.Keycode) -> (mu.Key, bool) {
|
|
|
return nil, false
|
|
|
}
|
|
|
|
|
|
-event :: proc "c" (ev: ^sapp.Event) {
|
|
|
- context = runtime.default_context()
|
|
|
+// Чистая функция обновления камеры. Принимает состояние, ввод и время.
|
|
|
+// Возвращает матрицы View и Projection.
|
|
|
+update_camera :: proc(
|
|
|
+ cam: ^Camera,
|
|
|
+ input: ^Input_State,
|
|
|
+ dt: f32,
|
|
|
+) -> (
|
|
|
+ view, proj: linalg.Matrix4f32,
|
|
|
+) {
|
|
|
+ // Константы настройки
|
|
|
+ SENSITIVITY :: 0.15 * 0.02 // mouse scale * dt factor
|
|
|
+ SPEED :: 6.0
|
|
|
+
|
|
|
+ // 1. Вращение (только если курсор захвачен)
|
|
|
+ if input.locked {
|
|
|
+ cam.yaw -= input.mouse_dx * SENSITIVITY
|
|
|
+ cam.pitch += input.mouse_dy * SENSITIVITY
|
|
|
+
|
|
|
+ // Кламп питча, чтобы не перевернуться
|
|
|
+ cam.pitch = math.clamp(cam.pitch, -1.5, 1.5)
|
|
|
+ }
|
|
|
|
|
|
- #partial switch ev.type {
|
|
|
- case .MOUSE_DOWN:
|
|
|
- if btn, ok := map_mouse_button(ev.mouse_button); ok {
|
|
|
- mu.input_mouse_down(&state.mu_ctx, i32(ev.mouse_x), i32(ev.mouse_y), btn)
|
|
|
- }
|
|
|
+ // 2. Векторы направления
|
|
|
+ // Вычисляем forward вектор из углов Эйлера
|
|
|
+ forward := linalg.Vector3f32 {
|
|
|
+ math.cos(cam.pitch) * math.sin(cam.yaw),
|
|
|
+ math.sin(cam.pitch),
|
|
|
+ math.cos(cam.pitch) * math.cos(cam.yaw),
|
|
|
+ }
|
|
|
|
|
|
- case .MOUSE_UP:
|
|
|
- if btn, ok := map_mouse_button(ev.mouse_button); ok {
|
|
|
- mu.input_mouse_up(&state.mu_ctx, i32(ev.mouse_x), i32(ev.mouse_y), btn)
|
|
|
- }
|
|
|
+ // Вычисляем right вектор (cross product с мировым UP)
|
|
|
+ world_up := linalg.Vector3f32{0, 1, 0}
|
|
|
+ right := linalg.normalize(linalg.cross(forward, world_up))
|
|
|
+
|
|
|
+ // 3. Движение
|
|
|
+ move_dir := linalg.Vector3f32{0, 0, 0}
|
|
|
+ if input.keys[.W] do move_dir -= forward
|
|
|
+ if input.keys[.S] do move_dir += forward
|
|
|
+ if input.keys[.A] do move_dir += right
|
|
|
+ if input.keys[.D] do move_dir -= right
|
|
|
+ if input.keys[.SPACE] do move_dir.y += 1.0
|
|
|
+ if input.keys[.LEFT_CONTROL] do move_dir.y -= 1.0
|
|
|
+
|
|
|
+ // Нормализуем, чтобы стрейф не был быстрее
|
|
|
+ if linalg.length2(move_dir) > 0.01 {
|
|
|
+ cam.pos += linalg.normalize(move_dir) * SPEED * dt
|
|
|
+ }
|
|
|
|
|
|
- case .MOUSE_MOVE:
|
|
|
- mu.input_mouse_move(&state.mu_ctx, i32(ev.mouse_x), i32(ev.mouse_y))
|
|
|
+ // 4. Матрицы
|
|
|
+ // LookAt
|
|
|
+ center := cam.pos - forward // -forward потому что в OpenGL камера смотрит в -Z
|
|
|
+ view = linalg.matrix4_look_at_f32(cam.pos, center, world_up)
|
|
|
|
|
|
- case .MOUSE_SCROLL:
|
|
|
- mu.input_scroll(&state.mu_ctx, 0, i32(ev.scroll_y))
|
|
|
+ // Perspective
|
|
|
+ aspect := sapp.widthf() / sapp.heightf()
|
|
|
+ proj = linalg.matrix4_perspective_f32(60.0 * (math.PI / 180.0), aspect, 0.1, 100.0)
|
|
|
|
|
|
- case .KEY_DOWN:
|
|
|
- if k, ok := map_key(ev.key_code); ok {
|
|
|
- mu.input_key_down(&state.mu_ctx, k)
|
|
|
- }
|
|
|
+ return view, proj
|
|
|
+}
|
|
|
|
|
|
+// --- Callbacks ---
|
|
|
+
|
|
|
+event :: proc "c" (ev: ^sapp.Event) {
|
|
|
+ context = runtime.default_context()
|
|
|
+
|
|
|
+ dpi := sapp.dpi_scale()
|
|
|
+
|
|
|
+ // Обновляем состояние ввода для игры
|
|
|
+ #partial switch ev.type {
|
|
|
+ case .KEY_DOWN:
|
|
|
+ state.input.keys[ev.key_code] = true
|
|
|
case .KEY_UP:
|
|
|
- if k, ok := map_key(ev.key_code); ok {
|
|
|
- mu.input_key_up(&state.mu_ctx, k)
|
|
|
+ state.input.keys[ev.key_code] = false
|
|
|
+ case .MOUSE_MOVE:
|
|
|
+ if state.input.locked {
|
|
|
+ state.input.mouse_dx = ev.mouse_dx
|
|
|
+ state.input.mouse_dy = ev.mouse_dy
|
|
|
}
|
|
|
+ case .MOUSE_DOWN:
|
|
|
+ }
|
|
|
|
|
|
- case .CHAR:
|
|
|
- if ev.char_code != 127 && ev.char_code >= 32 {
|
|
|
- text := fmt.tprintf("%r", rune(ev.char_code))
|
|
|
- mu.input_text(&state.mu_ctx, text)
|
|
|
+ // Выход из захвата по ESC
|
|
|
+ if ev.type == .KEY_DOWN && ev.key_code == .ESCAPE {
|
|
|
+ state.input.locked = !state.input.locked
|
|
|
+ sapp.lock_mouse(state.input.locked)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Логика UI (только если мышь НЕ захвачена игрой)
|
|
|
+ if !state.input.locked {
|
|
|
+ // Нормализация координат для UI
|
|
|
+ mouse_x := i32(ev.mouse_x / dpi)
|
|
|
+ mouse_y := i32(ev.mouse_y / dpi)
|
|
|
+
|
|
|
+ #partial switch ev.type {
|
|
|
+ case .MOUSE_DOWN:
|
|
|
+ if btn, ok := map_mouse_button(ev.mouse_button); ok {
|
|
|
+ mu.input_mouse_down(&state.mu_ctx, mouse_x, mouse_y, btn)
|
|
|
+ }
|
|
|
+ case .MOUSE_UP:
|
|
|
+ if btn, ok := map_mouse_button(ev.mouse_button); ok {
|
|
|
+ mu.input_mouse_up(&state.mu_ctx, mouse_x, mouse_y, btn)
|
|
|
+ }
|
|
|
+ case .MOUSE_MOVE:
|
|
|
+ mu.input_mouse_move(&state.mu_ctx, mouse_x, mouse_y)
|
|
|
+ case .MOUSE_SCROLL:
|
|
|
+ mu.input_scroll(&state.mu_ctx, 0, i32(ev.scroll_y))
|
|
|
+ case .KEY_DOWN:
|
|
|
+ if k, ok := map_key(ev.key_code); ok {
|
|
|
+ mu.input_key_down(&state.mu_ctx, k)
|
|
|
+ }
|
|
|
+ case .KEY_UP:
|
|
|
+ if k, ok := map_key(ev.key_code); ok {
|
|
|
+ mu.input_key_up(&state.mu_ctx, k)
|
|
|
+ }
|
|
|
+ case .CHAR:
|
|
|
+ if ev.char_code != 127 && ev.char_code >= 32 {
|
|
|
+ text := fmt.tprintf("%r", rune(ev.char_code))
|
|
|
+ mu.input_text(&state.mu_ctx, text)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -98,153 +213,229 @@ init :: proc "c" () {
|
|
|
sg.setup({environment = sglue.environment(), logger = {func = slog.func}})
|
|
|
sgl.setup({logger = {func = slog.func}})
|
|
|
|
|
|
+ // Init UI
|
|
|
mu.init(&state.mu_ctx)
|
|
|
state.mu_ctx.text_width = mu.default_atlas_text_width
|
|
|
state.mu_ctx.text_height = mu.default_atlas_text_height
|
|
|
+ state.dpi_scale = 1.0
|
|
|
+
|
|
|
+ // Init Camera
|
|
|
+ state.camera.pos = {0, 1.5, 5} // Чуть выше и назад
|
|
|
|
|
|
+ // --- Resources ---
|
|
|
+
|
|
|
+ // 1. UI Atlas Texture
|
|
|
width := mu.DEFAULT_ATLAS_WIDTH
|
|
|
height := mu.DEFAULT_ATLAS_HEIGHT
|
|
|
- pixels := make([]u32, width * height)
|
|
|
- defer delete(pixels)
|
|
|
-
|
|
|
+ pixels := make([]u32, width * height, context.temp_allocator)
|
|
|
for alpha, i in mu.default_atlas_alpha {
|
|
|
pixels[i] = 0x00FFFFFF | (u32(alpha) << 24)
|
|
|
}
|
|
|
+ state.atlas_img = sg.make_image(
|
|
|
+ {
|
|
|
+ width = i32(width),
|
|
|
+ height = i32(height),
|
|
|
+ pixel_format = .RGBA8,
|
|
|
+ data = {mip_levels = {0 = {ptr = raw_data(pixels), size = len(pixels) * 4}}},
|
|
|
+ },
|
|
|
+ )
|
|
|
+ state.atlas_view = sg.make_view({texture = {image = state.atlas_img}})
|
|
|
+ state.atlas_smp = sg.make_sampler({min_filter = .NEAREST, mag_filter = .NEAREST})
|
|
|
|
|
|
- img_desc: sg.Image_Desc
|
|
|
- img_desc.width = i32(width)
|
|
|
- img_desc.height = i32(height)
|
|
|
- img_desc.pixel_format = .RGBA8
|
|
|
- img_desc.data.mip_levels[0] = {
|
|
|
- ptr = raw_data(pixels),
|
|
|
- size = len(pixels) * 4,
|
|
|
- }
|
|
|
- state.atlas_img = sg.make_image(img_desc)
|
|
|
-
|
|
|
- view_desc: sg.View_Desc
|
|
|
- view_desc.texture.image = state.atlas_img
|
|
|
- state.atlas_view = sg.make_view(view_desc)
|
|
|
-
|
|
|
- smp_desc: sg.Sampler_Desc
|
|
|
- smp_desc.min_filter = .NEAREST
|
|
|
- smp_desc.mag_filter = .NEAREST
|
|
|
- state.atlas_smp = sg.make_sampler(smp_desc)
|
|
|
-
|
|
|
+ // 2. UI Pipeline
|
|
|
pip_desc: sg.Pipeline_Desc
|
|
|
pip_desc.colors[0].blend.enabled = true
|
|
|
pip_desc.colors[0].blend.src_factor_rgb = .SRC_ALPHA
|
|
|
pip_desc.colors[0].blend.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
|
|
state.ui_pip = sgl.make_pipeline(pip_desc)
|
|
|
|
|
|
- vertices := [?]f32 {
|
|
|
- 0.0,
|
|
|
- 0.5,
|
|
|
- 0.5,
|
|
|
- 1.0,
|
|
|
- 0.0,
|
|
|
- 0.0,
|
|
|
- 1.0,
|
|
|
- 0.5,
|
|
|
- -0.5,
|
|
|
- 0.5,
|
|
|
- 0.0,
|
|
|
- 1.0,
|
|
|
- 0.0,
|
|
|
- 1.0,
|
|
|
- -0.5,
|
|
|
- -0.5,
|
|
|
- 0.5,
|
|
|
- 0.0,
|
|
|
- 0.0,
|
|
|
- 1.0,
|
|
|
- 1.0,
|
|
|
- }
|
|
|
- state.bind.vertex_buffers[0] = sg.make_buffer(
|
|
|
- {data = {ptr = &vertices, size = size_of(vertices)}},
|
|
|
- )
|
|
|
- state.pip = sg.make_pipeline(
|
|
|
- {
|
|
|
- shader = sg.make_shader(triangle_shader_desc(sg.query_backend())),
|
|
|
- layout = {attrs = {0 = {format = .FLOAT3}, 1 = {format = .FLOAT4}}},
|
|
|
- },
|
|
|
- )
|
|
|
+ // 3. 3D Pipeline (Depth enabled)
|
|
|
+ cube_pip_desc: sg.Pipeline_Desc
|
|
|
+ cube_pip_desc.depth.write_enabled = true
|
|
|
+ cube_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
+ state.cube_pip = sgl.make_pipeline(cube_pip_desc)
|
|
|
+
|
|
|
state.pass_action = {
|
|
|
- colors = {0 = {load_action = .CLEAR, clear_value = {0.4, 0.4, 0.5, 1}}},
|
|
|
+ colors = {0 = {load_action = .CLEAR, clear_value = {0.1, 0.1, 0.15, 1}}},
|
|
|
}
|
|
|
}
|
|
|
|
|
|
frame :: proc "c" () {
|
|
|
context = runtime.default_context()
|
|
|
+ state.dpi_scale = sapp.dpi_scale()
|
|
|
+ dt := f32(sapp.frame_duration())
|
|
|
|
|
|
- mu.begin(&state.mu_ctx)
|
|
|
+ // 1. Update Logic
|
|
|
+ view, proj := update_camera(&state.camera, &state.input, dt)
|
|
|
+
|
|
|
+ // Сброс дельты мыши после обработки кадра
|
|
|
+ state.input.mouse_dx = 0
|
|
|
+ state.input.mouse_dy = 0
|
|
|
|
|
|
- if mu.begin_window(&state.mu_ctx, "Interactive Window", {40, 40, 300, 250}) {
|
|
|
- mu.label(&state.mu_ctx, "Try interacting!")
|
|
|
- if .SUBMIT in mu.button(&state.mu_ctx, "Click Me") {
|
|
|
- fmt.println("Clicked!")
|
|
|
+ // 2. UI Logic
|
|
|
+ mu.begin(&state.mu_ctx)
|
|
|
+ if mu.begin_window(&state.mu_ctx, "Controls", {10, 10, 200, 150}) {
|
|
|
+ mu.label(&state.mu_ctx, "WASD + Mouse to move")
|
|
|
+ mu.label(&state.mu_ctx, "Click to lock mouse")
|
|
|
+ mu.label(&state.mu_ctx, "ESC to unlock")
|
|
|
+
|
|
|
+ if .SUBMIT in mu.button(&state.mu_ctx, "Reset Camera") {
|
|
|
+ state.camera.pos = {0, 1.5, 5}
|
|
|
+ state.camera.yaw = 0
|
|
|
+ state.camera.pitch = 0
|
|
|
}
|
|
|
mu.end_window(&state.mu_ctx)
|
|
|
}
|
|
|
mu.end(&state.mu_ctx)
|
|
|
|
|
|
+ // 3. Rendering
|
|
|
+
|
|
|
+ // 3.1 Draw 3D Scene
|
|
|
sgl.defaults()
|
|
|
+ sgl.push_pipeline()
|
|
|
+ sgl.load_pipeline(state.cube_pip) // Pipeline with depth test
|
|
|
+
|
|
|
+ sgl.matrix_mode_projection()
|
|
|
+ sgl.load_matrix(cast(^f32)&proj)
|
|
|
+
|
|
|
+ sgl.matrix_mode_modelview()
|
|
|
+ sgl.load_matrix(cast(^f32)&view)
|
|
|
+
|
|
|
+ draw_cube({0, 0, 0}, 1.0, {1, 0.5, 0.2}) // Orange cube
|
|
|
+ draw_grid(20, 1.0) // Floor grid
|
|
|
+
|
|
|
+ sgl.pop_pipeline()
|
|
|
+
|
|
|
+ // 3.2 Draw UI
|
|
|
+ render_ui()
|
|
|
+
|
|
|
+ // 3.3 Commit
|
|
|
+ sg.begin_pass({action = state.pass_action, swapchain = sglue.swapchain()})
|
|
|
+ sgl.draw()
|
|
|
+ sg.end_pass()
|
|
|
+ sg.commit()
|
|
|
+}
|
|
|
+
|
|
|
+// --- Render Helpers ---
|
|
|
+
|
|
|
+draw_cube :: proc(pos: linalg.Vector3f32, size: f32, color: linalg.Vector3f32) {
|
|
|
+ sgl.push_matrix()
|
|
|
+ sgl.translate(pos.x, pos.y, pos.z)
|
|
|
+ sgl.scale(size, size, size)
|
|
|
+
|
|
|
+ sgl.begin_quads()
|
|
|
+ sgl.c3f(color.x, color.y, color.z)
|
|
|
+
|
|
|
+ // Front
|
|
|
+ sgl.v3f(-0.5, -0.5, 0.5); sgl.v3f(0.5, -0.5, 0.5)
|
|
|
+ sgl.v3f(0.5, 0.5, 0.5); sgl.v3f(-0.5, 0.5, 0.5)
|
|
|
+ // Back
|
|
|
+ sgl.v3f(0.5, -0.5, -0.5); sgl.v3f(-0.5, -0.5, -0.5)
|
|
|
+ sgl.v3f(-0.5, 0.5, -0.5); sgl.v3f(0.5, 0.5, -0.5)
|
|
|
+ // Left
|
|
|
+ sgl.v3f(-0.5, -0.5, -0.5); sgl.v3f(-0.5, -0.5, 0.5)
|
|
|
+ sgl.v3f(-0.5, 0.5, 0.5); sgl.v3f(-0.5, 0.5, -0.5)
|
|
|
+ // Right
|
|
|
+ sgl.v3f(0.5, -0.5, 0.5); sgl.v3f(0.5, -0.5, -0.5)
|
|
|
+ sgl.v3f(0.5, 0.5, -0.5); sgl.v3f(0.5, 0.5, 0.5)
|
|
|
+ // Top
|
|
|
+ sgl.c3f(color.x * 1.2, color.y * 1.2, color.z * 1.2) // Lighter top
|
|
|
+ sgl.v3f(-0.5, 0.5, 0.5); sgl.v3f(0.5, 0.5, 0.5)
|
|
|
+ sgl.v3f(0.5, 0.5, -0.5); sgl.v3f(-0.5, 0.5, -0.5)
|
|
|
+ // Bottom
|
|
|
+ sgl.c3f(color.x * 0.8, color.y * 0.8, color.z * 0.8) // Darker bottom
|
|
|
+ sgl.v3f(-0.5, -0.5, -0.5); sgl.v3f(0.5, -0.5, -0.5)
|
|
|
+ sgl.v3f(0.5, -0.5, 0.5); sgl.v3f(-0.5, -0.5, 0.5)
|
|
|
+
|
|
|
+ sgl.end()
|
|
|
+ sgl.pop_matrix()
|
|
|
+}
|
|
|
+
|
|
|
+draw_grid :: proc(slices: int, spacing: f32) {
|
|
|
+ sgl.begin_lines()
|
|
|
+ sgl.c3f(0.5, 0.5, 0.5)
|
|
|
+
|
|
|
+ half_size := f32(slices) * spacing * 0.5
|
|
|
+ for i in 0 ..= slices {
|
|
|
+ pos := -half_size + f32(i) * spacing
|
|
|
+ // X lines
|
|
|
+ sgl.v3f(-half_size, 0, pos); sgl.v3f(half_size, 0, pos)
|
|
|
+ // Z lines
|
|
|
+ sgl.v3f(pos, 0, -half_size); sgl.v3f(pos, 0, half_size)
|
|
|
+ }
|
|
|
+ sgl.end()
|
|
|
+}
|
|
|
+
|
|
|
+render_ui :: proc() {
|
|
|
sgl.push_pipeline()
|
|
|
sgl.load_pipeline(state.ui_pip)
|
|
|
sgl.enable_texture()
|
|
|
sgl.texture(state.atlas_view, state.atlas_smp)
|
|
|
|
|
|
+ // 1. Сбрасываем Проекцию
|
|
|
sgl.matrix_mode_projection()
|
|
|
sgl.push_matrix()
|
|
|
- sgl.ortho(0.0, sapp.widthf(), sapp.heightf(), 0.0, -1.0, +1.0)
|
|
|
- sgl.begin_quads()
|
|
|
+ sgl.load_identity() // <--- ВАЖНО: Сбрасываем перспективу от 3D сцены
|
|
|
+
|
|
|
+ // Настраиваем Ortho для UI
|
|
|
+ logical_w := sapp.widthf() / state.dpi_scale
|
|
|
+ logical_h := sapp.heightf() / state.dpi_scale
|
|
|
+ sgl.ortho(0.0, logical_w, logical_h, 0.0, -1.0, +1.0)
|
|
|
+
|
|
|
+ // 2. Сбрасываем ModelView (чтобы UI не вращался вместе с камерой)
|
|
|
+ sgl.matrix_mode_modelview()
|
|
|
+ sgl.push_matrix()
|
|
|
+ sgl.load_identity() // <--- ВАЖНО: Сбрасываем поворот камеры
|
|
|
|
|
|
- current_command: ^mu.Command
|
|
|
- for cmd_variant in mu.next_command_iterator(&state.mu_ctx, ¤t_command) {
|
|
|
- #partial switch cmd in cmd_variant {
|
|
|
+ sgl.begin_quads()
|
|
|
+ cmd: ^mu.Command
|
|
|
+ for variant in mu.next_command_iterator(&state.mu_ctx, &cmd) {
|
|
|
+ #partial switch c in variant {
|
|
|
case ^mu.Command_Rect:
|
|
|
- draw_rect(cmd.rect, cmd.color)
|
|
|
+ draw_rect(c.rect, c.color)
|
|
|
case ^mu.Command_Text:
|
|
|
- draw_text(cmd.str, cmd.pos, cmd.color)
|
|
|
+ draw_text(c.str, c.pos, c.color)
|
|
|
case ^mu.Command_Icon:
|
|
|
- draw_icon(cmd.id, cmd.rect, cmd.color)
|
|
|
+ draw_icon(c.id, c.rect, c.color)
|
|
|
case ^mu.Command_Clip:
|
|
|
sgl.end()
|
|
|
sgl.scissor_rect(
|
|
|
- f32(cmd.rect.x),
|
|
|
- f32(cmd.rect.y),
|
|
|
- f32(cmd.rect.w),
|
|
|
- f32(cmd.rect.h),
|
|
|
+ f32(c.rect.x) * state.dpi_scale,
|
|
|
+ f32(c.rect.y) * state.dpi_scale,
|
|
|
+ f32(c.rect.w) * state.dpi_scale,
|
|
|
+ f32(c.rect.h) * state.dpi_scale,
|
|
|
true,
|
|
|
)
|
|
|
sgl.begin_quads()
|
|
|
}
|
|
|
}
|
|
|
sgl.end()
|
|
|
+
|
|
|
+ // Восстанавливаем матрицы (pop в обратном порядке)
|
|
|
+ sgl.matrix_mode_modelview()
|
|
|
sgl.pop_matrix()
|
|
|
- sgl.pop_pipeline()
|
|
|
|
|
|
- sg.begin_pass({action = state.pass_action, swapchain = sglue.swapchain()})
|
|
|
- sg.apply_pipeline(state.pip)
|
|
|
- sg.apply_bindings(state.bind)
|
|
|
- sg.draw(0, 3, 1)
|
|
|
- sgl.draw()
|
|
|
- sg.end_pass()
|
|
|
- sg.commit()
|
|
|
+ sgl.matrix_mode_projection()
|
|
|
+ sgl.pop_matrix()
|
|
|
+
|
|
|
+ sgl.pop_pipeline()
|
|
|
}
|
|
|
|
|
|
+// --- UI Primitives ---
|
|
|
+
|
|
|
draw_rect :: proc(rect: mu.Rect, color: mu.Color) {
|
|
|
push_quad(rect, mu.default_atlas[mu.DEFAULT_ATLAS_WHITE], color)
|
|
|
}
|
|
|
|
|
|
draw_text :: proc(str: string, pos: mu.Vec2, color: mu.Color) {
|
|
|
- cur_pos := pos
|
|
|
+ p := pos
|
|
|
for char in str {
|
|
|
idx := int(char)
|
|
|
if idx > 127 do idx = 0
|
|
|
- rect := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + idx]
|
|
|
- dst := mu.Rect{cur_pos.x, cur_pos.y, rect.w, rect.h}
|
|
|
- push_quad(dst, rect, color)
|
|
|
- cur_pos.x += rect.w
|
|
|
+ src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + idx]
|
|
|
+ dst := mu.Rect{p.x, p.y, src.w, src.h}
|
|
|
+ push_quad(dst, src, color)
|
|
|
+ p.x += src.w
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -256,10 +447,14 @@ draw_icon :: proc(id: mu.Icon, rect: mu.Rect, color: mu.Color) {
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
+ w_inv := 1.0 / f32(mu.DEFAULT_ATLAS_WIDTH)
|
|
|
+ h_inv := 1.0 / f32(mu.DEFAULT_ATLAS_HEIGHT)
|
|
|
+
|
|
|
+ u0 := f32(src.x) * w_inv
|
|
|
+ v0 := f32(src.y) * h_inv
|
|
|
+ u1 := f32(src.x + src.w) * w_inv
|
|
|
+ v1 := f32(src.y + src.h) * h_inv
|
|
|
+
|
|
|
x0, y0 := f32(dst.x), f32(dst.y)
|
|
|
x1, y1 := f32(dst.x + dst.w), f32(dst.y + dst.h)
|
|
|
|
|
|
@@ -285,9 +480,10 @@ main :: proc() {
|
|
|
event_cb = event,
|
|
|
width = 1280,
|
|
|
height = 720,
|
|
|
- window_title = "Sokol + MicroUI",
|
|
|
+ window_title = "Sokol 3D + UI",
|
|
|
icon = {sokol_default = true},
|
|
|
logger = {func = slog.func},
|
|
|
+ high_dpi = true,
|
|
|
},
|
|
|
)
|
|
|
}
|