From af82f9c2d40183fd7f64a77c6fa2229529c6fc24 Mon Sep 17 00:00:00 2001 From: Synthasmagoria Date: Wed, 29 Jul 2026 17:07:23 +0200 Subject: [PATCH] Prepping to add ui --- .gitmodules | 3 + libraries/snths_ui | 1 + main.odin | 42 ++++++ program.odin | 369 ++++++--------------------------------------- ui.odin | 272 --------------------------------- utils.odin | 38 +++++ 6 files changed, 128 insertions(+), 597 deletions(-) create mode 160000 libraries/snths_ui create mode 100644 main.odin delete mode 100644 ui.odin diff --git a/.gitmodules b/.gitmodules index 420af13..53d27e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libraries/back"] path = libraries/back url = https://github.com/laytan/back.git +[submodule "libraries/snths_ui"] + path = libraries/snths_ui + url = https://offgrd.xyz/git/Synthasmagoria/snths_ui.git diff --git a/libraries/snths_ui b/libraries/snths_ui new file mode 160000 index 0000000..6c8479f --- /dev/null +++ b/libraries/snths_ui @@ -0,0 +1 @@ +Subproject commit 6c8479f922438d73fbecfffbd6a70abb36068192 diff --git a/main.odin b/main.odin new file mode 100644 index 0000000..600bfd4 --- /dev/null +++ b/main.odin @@ -0,0 +1,42 @@ +package main + +import rl "libraries/raylib" +import "base:runtime" + +WINDOW_WIDTH :: 1280 +WINDOW_HEIGHT :: 720 +WINDOW_FRAMERATE :: 30 +WINDOW_CAPTION :: "Transformation Visualizer" + +init :: proc() { + rl.SetTraceLogLevel(.WARNING) + rl.SetConfigFlags({.VSYNC_HINT}) + rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) + rl.SetTargetFPS(WINDOW_FRAMERATE) + program_init() +} + +update :: proc() { + rl.BeginDrawing() + defer rl.EndDrawing() + rl.ClearBackground(rl.BLACK) + program_update() + free_all(context.temp_allocator) +} + +parent_window_size_changed :: proc(w, h: int) { + rl.SetWindowSize(i32(w), i32(h)) +} + +shutdown :: proc() { + rl.CloseWindow() +} + +should_run :: proc() -> bool { + when ODIN_OS != .JS { + if rl.WindowShouldClose() { + return false + } + } + return true +} diff --git a/program.odin b/program.odin index 81a52cf..d591988 100644 --- a/program.odin +++ b/program.odin @@ -1,53 +1,12 @@ package main -import rl "libraries/raylib" import fixed_pool "libraries/pool" -import "base:runtime" -import "core:fmt" +import rl "libraries/raylib" +import ui "libraries/snths_ui" -WINDOW_WIDTH :: 1280 -WINDOW_HEIGHT :: 720 -WINDOW_FRAMERATE :: 30 -WINDOW_CAPTION :: "Transformation Visualizer" - -MATRIX_FIELD_COUNT :: 1024 -MATRIX_FIELD_BYTES :: 8 - -program: Program - -FontStyle :: struct { - font: rl.Font, - size, spacing: f32 -} - -ProgramState :: enum { - Normal, - Message, -} - -ProgramMessageType :: enum { - ChangeMatrixTypeClearData, -} - -@rodata program_messages := [ProgramMessageType]cstring { - .ChangeMatrixTypeClearData = "Changing the matrix type will delete all current matrices.\nAre you sure you want to do this?", -} - -Program :: struct { - ui_state: UiContext, - matrix_field_pool: FieldPool(f32), - matrix_mode_perceived: MatrixType, - matrix_mode_actual: MatrixType, - grid: Grid, - font_style: FontStyle, - state: ProgramState, - message_type: ProgramMessageType, -} - -Grid :: struct { - area: rect2, - inner_area: rect2, - zoom: f32, +Field :: struct($T: typeid) { + value: f32, + input: FieldInput, } FIELD_BYTE_COUNT :: 8 @@ -76,142 +35,28 @@ field_pool_free_range :: proc(pool: ^FieldPool($T), from: int, count: int) { fixed_pool.free_range(&pool.inputs, from, count) } -Field :: struct($T: typeid) { - value: f32, - input: FieldInput, -} - field_pool_get_allocated_elems :: proc(pool: ^FieldPool($T)) -> #soa[]Field(T) { values := fixed_pool.get_allocated_elems(pool.values) fields := fixed_pool.get_allocated_elems(pool.inputs) return soa_zip(value = values, input = fields) } -draw_text :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color) { - rl.DrawTextEx(style.font, text, position, style.size, style.spacing, color) +FontStyle :: struct { + font: rl.Font, + size, spacing: f32 } -TextHalign :: enum { - Left, - Middle, - Right, +ProgramState :: enum { + Normal, + Message, } -TextValign :: enum { - Top, - Center, - Bottom, +ProgramMessageType :: enum { + ChangeMatrixTypeClearData, } -draw_text_aligned :: proc(text: cstring, position: v2, style: FontStyle, halign: TextHalign, valign: TextValign, color: rl.Color) { - size := measure_text(text, style) - position := position - switch halign { - case .Left: - case .Middle: position.x -= size.x / 2.0 - case .Right: position.x -= size.x - } - switch valign { - case .Top: - case .Center: position.y -= size.y / 2.0 - case .Bottom: position.y -= size.y - } - draw_text(text, position, style, color) -} - -UiButton_AddMat2CallbackData :: struct { - program: ^Program, - mat: mat2, -} - -program_add_mat2 :: proc(data: rawptr) { - data := cast(^UiButton_AddMat2CallbackData)data - field_pool_add_mat2(&data.program.matrix_field_pool, data.mat) -} - -field_pool_add_mat2 :: proc(pool: ^FieldPool(f32), m: mat2) { - values, _ := field_pool_get(pool, 4) - m := m - floats := cast([^]f32)&m[0, 0] - #unroll for i in 0..<4 {values[i] = floats[i]} -} - -UiButton_AddMat3CallbackData :: struct { - program: ^Program, - mat: mat3, -} - -program_add_mat3 :: proc(data: rawptr) { - data := cast(^UiButton_AddMat3CallbackData)data - field_pool_add_mat3(&data.program.matrix_field_pool, data.mat) -} - -field_pool_add_mat3 :: proc(pool: ^FieldPool(f32), m: mat3) { - values, _ := field_pool_get(pool, 9) - m := m - floats := cast([^]f32)&m[0, 0] - #unroll for i in 0..<9 {values[i] = floats[i]} -} - -UiButton_AddMat4CallbackData :: struct { - program: ^Program, - mat: mat4, -} - -program_add_mat4 :: proc(data: rawptr) { - data := cast(^UiButton_AddMat4CallbackData)data - field_pool_add_mat4(&data.program.matrix_field_pool, data.mat) -} - -field_pool_add_mat4 :: proc(pool: ^FieldPool(f32), m: mat4) { - values, _ := field_pool_get(pool, 16) - m := m - floats := cast([^]f32)&m[0, 0] - #unroll for i in 0..<16 {values[i] = floats[i]} -} - -measure_text :: proc(text: cstring, style: FontStyle) -> v2 { - return rl.MeasureTextEx(style.font, text, style.size, style.spacing) -} - -MatrixType :: enum { - Mat2, - Mat3, - Mat4, -} - -@rodata matrix_type_table := [MatrixType]typeid { - .Mat2 = mat2, - .Mat3 = mat3, - .Mat4 = mat4, -} - -init :: proc() { - rl.SetTraceLogLevel(.WARNING) - rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT}) - rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) - rl.SetTargetFPS(WINDOW_FRAMERATE) - - field_pool_init(&program.matrix_field_pool, 4096, context.allocator) - - program.font_style = { - font = rl.GetFontDefault(), - size = 16.0, - spacing = 1.0 - } - - program.grid = { - area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, - inner_area = {-1, -1, 2, 2}, - zoom = 1.0, - } - - ui_style := UiStyle{ - margin = 4.0, - padding = 4.0, - font_style = program.font_style, - } - ui_init(&program.ui_state, ui_style, {0, 0}, context.temp_allocator) +@rodata program_messages := [ProgramMessageType]cstring { + .ChangeMatrixTypeClearData = "Changing the matrix type will delete all current matrices.\nAre you sure you want to do this?", } program_set_message_state :: proc(program: ^Program, message: ProgramMessageType) { @@ -219,171 +64,45 @@ program_set_message_state :: proc(program: ^Program, message: ProgramMessageType program.message_type = message } -program_matrix_mode_dropdown_callback :: proc(previous, current: i32, data: rawptr) { - program := cast(^Program)data - program_set_message_state(program, .ChangeMatrixTypeClearData) +program: Program + +Program :: struct { + ui_context: ui.Context, + matrix_field_pool: FieldPool(f32), + grid: Grid, + font_style: FontStyle, + state: ProgramState, + message_type: ProgramMessageType, } -program_add_matrix_button_data :: proc(label: cstring, matrix_type: MatrixType, allocator := context.temp_allocator) -> UiElementData_Button { - callback: UiElementCallback_Button - callback_data: rawptr - switch program.matrix_mode_actual { - case .Mat2: - callback = program_add_mat2 - data := new(UiButton_AddMat2CallbackData, context.temp_allocator) - data^ = {program = &program, mat = mat2{0, 1, 2, 3}} - callback_data = cast(rawptr)data - case .Mat3: - callback = program_add_mat3 - data := new(UiButton_AddMat3CallbackData, context.temp_allocator) - data^ = {program = &program, mat = mat3{0, 1, 2, 3, 4, 5, 6, 7, 8}} - callback_data = cast(rawptr)data - case .Mat4: - callback = program_add_mat4 - data := new(UiButton_AddMat4CallbackData, context.temp_allocator) - data^ = {program = &program, mat = mat4{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}} - callback_data = cast(rawptr)data +Grid :: struct { + area: rect2, + inner_area: rect2, + zoom: f32, +} + +program_init :: proc(allocator := context.allocator) { + field_pool_init(&program.matrix_field_pool, 4096, allocator) + program.font_style = { + font = rl.GetFontDefault(), + size = 16.0, + spacing = 1.0 } - return {label = label, callback = callback, callback_data = callback_data} -} - -program_build_ui :: proc() -> []UiElement { - - - ui_button_size_top_bar := v2{32.0, 32.0} - ui_dropdown_size_top_bar := v2{96.0, 32.0} - ui_start(&program.ui_state) - ui_container_begin({label = ""}) - matrix_add_data := program_add_matrix_button_data("#10#", program.matrix_mode_actual, context.temp_allocator) - ui_button(ui_button_size_top_bar, matrix_add_data) - ui_same_line() - ui_button(ui_button_size_top_bar, {label = "#11#"}) - ui_same_line() - ui_button(ui_button_size_top_bar, {label = "#12#"}) - ui_same_line() - matrix_options := enum_to_raygui_options_string(MatrixType, context.temp_allocator) - matrix_mode_ref := cast(^i32)&program.matrix_mode_perceived - ui_dropdown(ui_dropdown_size_top_bar, { - label = matrix_options, - active = matrix_mode_ref, - callback = program_matrix_mode_dropdown_callback, - callback_data = cast(rawptr)&program}) - ui_container_end() - ui_container_begin({label = "Matrices"}) - field_pool := &program.matrix_field_pool - if fixed_pool.usage(field_pool.values) == 0 { - ui_text({text = "Add matrices", color = rl.WHITE}) - } else { - inputs := fixed_pool.get_allocated_elements(program.matrix_field_pool.inputs) - matrix_count := len(inputs) / 4 - field_size := v2{48.0, 32.0} - for i in 0.. bool { - when ODIN_OS != .JS { - if rl.WindowShouldClose() { - return false - } - } - return true } diff --git a/ui.odin b/ui.odin deleted file mode 100644 index 449f997..0000000 --- a/ui.odin +++ /dev/null @@ -1,272 +0,0 @@ -package main - -import "core:math/linalg" -import "core:fmt" -import "core:mem" -import rl "libraries/raylib" - -@(private="file") state_current: ^UiContext - -UiElementType :: enum { - Input, - Button, - Container, - Text, - DropDown, -} - -UiElementData :: union { - UiElementData_Button, - UiElementData_Input, - UiElementData_Container, - UiElementData_Text, - UiElementData_Dropdown, -} - -UiElement :: struct { - area: rect2, - type: UiElementType, - id: UiElementId, - data: UiElementData, -} - -UiElementId :: bit_field u32 { - group_index: u16 | 16, - element_index: u16 | 16, -} - -UiContext :: struct { - origin, position_left, position: v2, - same_line: bool, - style: UiStyle, - element_focus_id: UiElementId, - id_current: UiElementId, - elements: [dynamic]UiElement, - container: ^UiElement, - group_element_count: int, -} - -UiStyle :: struct { - margin, padding: f32, - font_style: FontStyle, -} - -ui_init :: proc(state: ^UiContext, style: UiStyle, origin: v2, allocator: mem.Allocator) { - state^ = { - element_focus_id = ui_id_invalid(), - origin = origin, - position = origin, - style = style, - elements = make(type_of(state.elements), 0, 512, allocator) - } -} - -ui_start :: proc(state: ^UiContext) { - state_current = state - clear(&state.elements) - state.id_current = {group_index = 0, element_index = 0} - state.position_left = state.origin - state.position = state.position_left -} - -ui_end :: proc() -> []UiElement { - elements := state_current.elements[:] - state_current = nil - return elements -} - -UiElementCallback_Button :: proc(data: rawptr) - -UiElementData_Button :: struct { - label: cstring, - callback: UiElementCallback_Button, - callback_data: rawptr, -} - -ui_button :: proc(size: v2, data: UiElementData_Button) { - state := state_current - area := _ui_advance_element(state, size) - state.id_current.element_index += 1 - append(&state.elements, UiElement{id = state.id_current, area = area, type = .Button, data = data}) -} - -UiElementData_Container :: struct { - label: cstring, -} - -ui_container_begin :: proc(data: UiElementData_Container) { - state := state_current - assert(state.container == nil) - state.id_current = {group_index = state.id_current.group_index + 1, element_index = 0} - element := UiElement{id = state.id_current, area = _ui_advance_container(state), type = .Container, data = data} - append(&state.elements, element) - state.position_left += state.style.margin + state.style.padding - state.container = &state.elements[len(state.elements) - 1] - state.group_element_count = 0 -} - -ui_container_end :: proc() { - state := state_current - assert(state.container != nil) - state.position_left.x = state.origin.x - state.position_left.y = state.container.area.y + state.container.area.height - state.position = state.position_left - state.container = nil - state.group_element_count = 0 -} - -UiElementData_Input :: struct { - label: cstring, - input: []byte, - min, max: f32, -} - -ui_input :: proc(size: v2, data: UiElementData_Input) { - state := state_current - area := _ui_advance_element(state, size) - state.id_current.element_index += 1 - append(&state.elements, UiElement{id = state.id_current, area = area, type = .Input, data = data}) -} - -UiElementData_Text :: struct { - text: cstring, - color: rl.Color, -} - -ui_text :: proc(data: UiElementData_Text) { - state := state_current - size := measure_text(data.text, state.style.font_style) - area := _ui_advance_element(state, size) - state.id_current.element_index += 1 - append(&state.elements, UiElement{id = state.id_current, area = area, type = .Text, data = data}) -} - -ui_mat2 :: proc(input_field_size: v2, inputs: [][]byte, min, max: f32) { - ui_input(input_field_size, {label = "", input = inputs[0], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[1], min = min, max = max}) - - ui_input(input_field_size, {label = "", input = inputs[2], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[3], min = min, max = max}) -} - -ui_mat3 :: proc(input_field_size: v2, inputs: [][]byte, min, max: f32) { - ui_input(input_field_size, {label = "", input = inputs[0], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[1], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[2], min = min, max = max}) - - ui_input(input_field_size, {label = "", input = inputs[3], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[4], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[5], min = min, max = max}) - - ui_input(input_field_size, {label = "", input = inputs[6], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[7], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[8], min = min, max = max}) -} - -ui_mat4 :: proc(input_field_size: v2, inputs: [][]byte, min, max: f32) { - ui_input(input_field_size, {label = "", input = inputs[0], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[1], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[2], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[3], min = min, max = max}) - - ui_input(input_field_size, {label = "", input = inputs[4], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[5], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[6], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[7], min = min, max = max}) - - ui_input(input_field_size, {label = "", input = inputs[8], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[9], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[10], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[11], min = min, max = max}) - - ui_input(input_field_size, {label = "", input = inputs[12], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[13], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[14], min = min, max = max}) - ui_same_line() - ui_input(input_field_size, {label = "", input = inputs[15], min = min, max = max}) -} - -UiElementCallback_Dropdown :: proc(previous, current: i32, data: rawptr) - -UiElementData_Dropdown :: struct { - label: cstring, - active: ^i32, - callback: UiElementCallback_Dropdown, - callback_data: rawptr, -} - -ui_dropdown :: proc(size: v2, data: UiElementData_Dropdown) { - state := state_current - area := _ui_advance_element(state, size) - state.id_current.element_index += 1 - append(&state.elements, UiElement{id = state.id_current, area = area, type = .DropDown, data = data}) -} - -ui_same_line :: proc() { - state_current.same_line = true -} - -ui_element_is_editing :: proc(state: UiContext, id: UiElementId) -> bool { - return state.element_focus_id == id -} - -ui_element_focus :: proc(state: ^UiContext, id: UiElementId) { - state.element_focus_id = id -} - -ui_element_focus_reset :: proc(state: ^UiContext) { - state.element_focus_id = ui_id_invalid() -} - -ui_id_invalid :: proc() -> UiElementId { - return {group_index = max(u16), element_index = max(u16)} -} - -_ui_advance_element :: proc(state: ^UiContext, size: v2) -> rect2 { - if state.group_element_count > 0 { - if state.same_line { - previous_element := state.elements[len(state.elements) - 1] - state.position.x += previous_element.area.width + state.style.padding - state.same_line = false - } else { - state.position_left.y += size.y + state.style.padding - state.position = state.position_left - } - } else { - state.position += state.style.padding - } - area := rect2{**state.position, **size} - if state.container != nil { - element_bottom_left := rect_get_br(area) - container_bottom_left := rect_get_br(state.container.area) - new_br := linalg.max(element_bottom_left + state.style.padding, container_bottom_left) - state.container.area.width = new_br.x - state.container.area.x - state.container.area.height = new_br.y - state.container.area.y - } - state.group_element_count += 1 - return area -} - -_ui_advance_container :: proc(state: ^UiContext) -> rect2 { - state.position += state.style.margin - return {**state.position, 0.0, 0.0} -} diff --git a/utils.odin b/utils.odin index c474bd6..e7111cd 100644 --- a/utils.odin +++ b/utils.odin @@ -17,6 +17,44 @@ write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (succ return _write_entire_file(name, data, truncate) } +/// ### RAYLIB SHORTHANDS ### /// +measure_text :: proc(text: cstring, style: FontStyle) -> v2 { + return rl.MeasureTextEx(style.font, text, style.size, style.spacing) +} + +draw_text :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color) { + rl.DrawTextEx(style.font, text, position, style.size, style.spacing, color) +} + +TextHalign :: enum { + Left, + Middle, + Right, +} + +TextValign :: enum { + Top, + Center, + Bottom, +} + +draw_text_aligned :: proc(text: cstring, position: v2, style: FontStyle, halign: TextHalign, valign: TextValign, color: rl.Color) { + size := measure_text(text, style) + position := position + switch halign { + case .Left: + case .Middle: position.x -= size.x / 2.0 + case .Right: position.x -= size.x + } + switch valign { + case .Top: + case .Center: position.y -= size.y / 2.0 + case .Bottom: position.y -= size.y + } + draw_text(text, position, style, color) +} + +/// ### TYPES ### /// v1 :: [1]f32 v2 :: [2]f32 v3 :: [3]f32