transformation_visualizer/program.odin

401 lines
13 KiB
Odin

package main
import rl "libraries/raylib"
import ui "libraries/snths_ui"
import "core:fmt"
UiContainerType :: enum {
Normal,
Matrix,
Scroll,
}
UiElementData_Container :: struct {
type: UiContainerType,
thickness: f32,
scroll: f32,
}
UiElementData_Slider :: struct {
min, max: f32,
value: ^f32,
}
UiElementData_FloatInput :: struct {
value: ^f32,
value_string: cstring,
}
UiElementData_Dropdown :: struct {
value: ^i32,
}
UiButtonType :: enum {
Button,
Dropdown,
}
UiElementData_Button :: struct {
type: UiButtonType,
options: cstring,
}
MatrixTypes :: enum {
Identity,
Translation,
Scale,
Rotation,
}
@rodata matrix_add_hotkeys := [MatrixTypes]rl.KeyboardKey {
.Identity = rl.KeyboardKey.I,
.Translation = rl.KeyboardKey.T,
.Scale = rl.KeyboardKey.S,
.Rotation = rl.KeyboardKey.R,
}
FontStyle :: struct {
font: rl.Font,
size, spacing: f32
}
ProgramState :: enum {
Normal,
Message,
Dialog,
}
MouseState :: enum {
Hover,
DraggingGrid,
HoverMatrices,
}
ProgramDialogType :: enum {
CreateTranslationMatrix,
CreateScaleMatrix,
CreateRotationMatrix,
}
@rodata program_dialog_messages := [ProgramDialogType]cstring {
.CreateTranslationMatrix = "Create tranlation matrix",
.CreateScaleMatrix = "Create scale matrix",
.CreateRotationMatrix = "Create rotation matrix",
}
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: Program
InputFloatField :: [32]byte
MATRIX_POOL_SIZE :: 4096
Program :: struct {
ui_context: ui.Context,
ui_element_focus: ui.ElementId,
ui_overlay_context: ui.Context,
ui_overlay_element_focus: ui.ElementId,
matrix_float_pool: []f32,
matrix_value_string_pool: []InputFloatField,
matrix_create_dropdown_value: MatrixTypes,
matrix_count: int,
matrix_scroll: f32,
matrix_scroll_speed: f32,
mouse_state: MouseState,
grid: Grid,
font_style: FontStyle,
state: ProgramState,
message_type: ProgramMessageType,
dialog_type: ProgramDialogType,
}
Grid :: struct {
area: rect2,
inner_area: rect2,
zoom: f32,
}
matrix_pool_max :: proc() -> int {
return len(program.matrix_float_pool) / 9
}
matrix_pool_get_mat3 :: proc() -> ^mat3 {
assert(program.matrix_count + 1 < matrix_pool_max(), "Max matrix count exceeded")
mat := cast(^mat3)&program.matrix_float_pool[program.matrix_count * 9]
program.matrix_count += 1
return mat
}
program_show_dialog :: proc(program: ^Program, dialog_type: ProgramDialogType) {
program.state = .Dialog
program.dialog_type = dialog_type
}
program_show_mesasge :: proc(program: ^Program, message: ProgramMessageType) {
program.state = .Message
program.message_type = message
}
program_init :: proc(allocator := context.allocator) {
program.matrix_float_pool = make(type_of(program.matrix_float_pool), MATRIX_POOL_SIZE)
program.matrix_value_string_pool = make(type_of(program.matrix_value_string_pool), MATRIX_POOL_SIZE)
program.matrix_scroll_speed = 32.0
program.font_style = {
font = rl.GetFontDefault(),
size = 16.0,
spacing = 1.0
}
program.grid = {
area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT},
inner_area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT},
zoom = 1.0,
}
ui.init(&program.ui_context, 4000, ui_measure_text, &program.font_style, allocator)
program.ui_element_focus = ui.element_id_invalid()
ui.init(&program.ui_overlay_context, 200, ui_measure_text, &program.font_style, allocator)
program.ui_overlay_element_focus = ui.element_id_invalid()
}
program_update :: proc() {
if rl.IsKeyPressed(.ENTER) {
program_show_dialog(&program, .CreateScaleMatrix)
}
ui_elements, matrix_container := program_build_workspace_ui(&program.ui_context)
ui_overlay_elements: []ui.Element
switch program.state {
case .Normal:
program.mouse_state = program_handle_mouse_workspace(program.mouse_state, matrix_container)
if rl.IsKeyPressed(.TAB) {
program.ui_element_focus = ui.focus_next_element(program.ui_element_focus, ui_elements, {.InputFloat})
}
case .Message:
case .Dialog:
ui_overlay_elements = program_build_dialog_ui(&program.ui_overlay_context)
}
grid := program.grid
grid_inner_position := rect_get_tl(grid.inner_area)
draw_grid(grid.area, {32.0, 32.0}, rl.GRAY)
program_render_ui(&program.ui_context, program.font_style, ui_elements)
program_render_ui(&program.ui_overlay_context, program.font_style, ui_overlay_elements)
}
program_build_dialog_ui :: proc(ctx: ^ui.Context) -> []ui.Element {
size := v2{WINDOW_WIDTH, WINDOW_HEIGHT}
margin := size * 0.33
ui.start(ctx, V2_ZERO, {WINDOW_WIDTH, WINDOW_HEIGHT})
ui.container_begin({ui.Size_Percentage(1.0), ui.Size_Percentage(1.0)}, {margin = margin, padding = 4.0})
ui.element(.Label, {}, {label = "Create a scale matrix", color = ui.col(rl.WHITE), margin = 4.0})
ui.container_end()
return ui.end()
}
program_handle_mouse_workspace :: proc(mouse_state: MouseState, matrix_container: ^ui.Element) -> MouseState {
mouse := rl.GetMousePosition()
switch mouse_state {
case .Hover:
if rl.IsMouseButtonPressed(.LEFT) {
return .DraggingGrid
} else if rect_has_point(rect2(matrix_container.area), mouse) {
return .HoverMatrices
}
case .DraggingGrid:
program.grid.inner_area = rect_translate(program.grid.inner_area, rl.GetMouseDelta())
if !rl.IsMouseButtonDown(.LEFT) {
return .Hover
}
case .HoverMatrices:
program.matrix_scroll = max(0, program.matrix_scroll - rl.GetMouseWheelMove() * program.matrix_scroll_speed)
if !rect_has_point(rect2(matrix_container.area), mouse) {
return .Hover
}
}
return mouse_state
}
program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, matrix_container: ^ui.Element) {
ui.start(ctx, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT})
top_bar_container_config := ui.ElementConfiguration {margin = 4.0}
top_bar_element_margin: f32 = 4.0
top_bar_container := ui.container_begin({ui.Size_Percentage(1.0), ui.Size_Pixels(48.0)}, top_bar_container_config)
top_bar_inner_height := ui.get_element_inner_area(top_bar_container^).height
top_bar_button_dimensions := ui.Dimensions {ui.Size_Pixels(top_bar_inner_height), ui.Size_Pixels(top_bar_inner_height)}
top_bar_button_config := ui.ElementConfiguration {margin = top_bar_element_margin}
top_bar_dropdown_dimensions := ui.Dimensions {ui.Size_Pixels(160.0), ui.Size_Percentage(1.0)}
top_bar_dropdown_config := ui.ElementConfiguration {margin = top_bar_element_margin}
{
button_config := top_bar_button_config
button_config.label = "+"
ui.element(.Button, top_bar_button_dimensions, button_config)
button_config.label = "-"
ui.same_line()
ui.element(.Button, top_bar_button_dimensions, button_config)
ui.same_line()
matrix_dropdown_config := top_bar_dropdown_config
matrix_dropdown_config.label = enum_to_raygui_options_string_hotkey(MatrixTypes, &matrix_add_hotkeys, context.temp_allocator)
matrix_dropdown_data := new(UiElementData_Dropdown, context.temp_allocator)
matrix_dropdown_data^ = {value = cast(^i32)&program.matrix_create_dropdown_value}
ui.element(.Dropdown, top_bar_dropdown_dimensions, matrix_dropdown_config, matrix_dropdown_data)
}
ui.container_end()
side_bar_container_config := ui.ElementConfiguration {margin = 4.0}
side_bar_container_data := new(UiElementData_Container, context.temp_allocator)
side_bar_container_data^ = {type = .Scroll, scroll = program.matrix_scroll}
matrix_container = ui.container_begin({ui.Size_Percentage(0.2), ui.Size_PercentageRemainder(1.0)}, side_bar_container_config, side_bar_container_data)
for i in 0..<program.matrix_count {
container_config := ui.ElementConfiguration{margin = 8.0, label = fmt.ctprintf("Matrix %d", i)}
container_dimensions := ui.Dimensions {ui.Size_Percentage(1.0), ui.Size_Pixels(112.0)}
mat := program.matrix_float_pool[i * 9 : i * 9 + 9]
value_strings := program.matrix_value_string_pool[i * 9 : i * 9 + 9]
ui_mat3(mat, value_strings, container_dimensions, container_config)
}
ui.container_end()
elements = ui.end()
return
}
program_render_ui :: proc(ctx: ^ui.Context, font_style: FontStyle, elements: []ui.Element) {
scissor_container: ^ui.Element
render_above: ui.Element
render_above.id = ui.element_id_invalid()
for &element in elements {
offset: v2
if scissor_container != nil {
if element.id.container_index < scissor_container.id.container_index {
rl.EndScissorMode()
scissor_container = nil
} else {
offset.y = -(cast(^UiElementData_Container)scissor_container.data).scroll
}
}
area := rect_translate(rl.Rectangle(element.area), offset)
color := rl.Color(element.color)
#partial switch element.type {
case .Container:
dim := rl.Color {0, 0, 0, 192}
if element.data != nil {
data := cast(^UiElementData_Container)element.data
switch data.type {
case .Normal:
rl.DrawRectangleRounded(area, 0.1, 4.0, dim)
rl.GuiGroupBox(area, element.label)
case .Matrix:
matrix_area := ui.rect_grow(ui.rect2(area), element.margin / 2.0)
tl, tr, br, bl := ui.rect_get_corners(matrix_area)
col := rl.WHITE
hook_length: f32 = 4.0
rl.DrawLineEx(tl, bl, data.thickness, col)
rl.DrawLineEx(tl, tl + V2_RIGHT * hook_length, data.thickness, col)
rl.DrawLineEx(bl, bl + V2_RIGHT * hook_length, data.thickness, col)
rl.DrawLineEx(tr, br, data.thickness, col)
rl.DrawLineEx(tr, tr + V2_LEFT * hook_length, data.thickness, col)
rl.DrawLineEx(br, br + V2_LEFT * hook_length, data.thickness, col)
case .Scroll:
rl.DrawRectangleRounded(area, 0.1, 4.0, dim)
rl.GuiGroupBox(area, element.label)
scissor_container = &element
rl.BeginScissorMode(**rect2_to_irect2(area))
}
} else {
rl.DrawRectangleRounded(area, 0.1, 4.0, dim)
rl.GuiGroupBox(area, element.label)
}
case .Button:
rl.GuiButton(area, element.label)
case .Slider:
data := cast(^UiElementData_Slider)element.data
rl.GuiSlider(area, element.label, "", data.value, data.min, data.max)
case .InputFloat:
data := cast(^UiElementData_FloatInput)element.data
if rl.GuiValueBoxFloat(area, element.label, data.value_string, data.value, program.ui_element_focus == element.id) == 1 {
program.ui_element_focus = element.id
}
case .Dropdown:
assert(element.data != nil, "Element data cannot be nil when creating a dropdown")
data := cast(^UiElementData_Dropdown)element.data
if program.ui_element_focus == element.id {
render_above = element
} else {
if rl.GuiDropdownBox(area, element.label, data.value, program.ui_element_focus == element.id) {
if program.ui_element_focus == element.id {
program.ui_element_focus = ui.element_id_invalid()
} else {
program.ui_element_focus = element.id
}
}
}
case .Label:
rl.DrawTextEx(font_style.font, element.label, rect_get_tl(area), font_style.size, font_style.spacing, color)
case:
rl.DrawRectangleRec(area, rl.MAGENTA)
}
}
if scissor_container != nil {
rl.EndScissorMode()
scissor_container = nil
}
if render_above.id != ui.element_id_invalid() {
#partial switch render_above.type {
case .Dropdown:
data := cast(^UiElementData_Dropdown)render_above.data
if rl.GuiDropdownBox(rl.Rectangle(render_above.area), render_above.label, data.value, program.ui_element_focus == render_above.id) {
if program.ui_element_focus == render_above.id {
program.ui_element_focus = ui.element_id_invalid()
} else {
program.ui_element_focus = render_above.id
}
}
}
}
}
ui_measure_text :: proc(str: cstring, font_data: rawptr) -> ui.v2 {
style := cast(^FontStyle)font_data
return rl.MeasureTextEx(style.font, str, style.size, style.spacing)
}
ui_mat3 :: proc(mat: []f32, value_strings: []InputFloatField, dimensions: ui.Dimensions, config: ui.ElementConfiguration, allocator := context.temp_allocator) {
container_data := new(UiElementData_Container, allocator)
container_data^ = {type = .Matrix, thickness = 2.0}
ui.container_begin(dimensions, config, container_data)
matrix_input_config := ui.ElementConfiguration {margin = 4.0}
matrix_input_dimensions := ui.Dimensions {ui.Size_Percentage(1.0 / 3.0), ui.Size_Percentage(1.0 / 3.0)}
#unroll for i in 0..<3 {
#unroll for j in 0..<3 {
data := new(UiElementData_FloatInput, allocator)
index := i * 3 + j
data^ = {value = &mat[index], value_string = cstring(&value_strings[index][0])}
ui.element(.InputFloat, matrix_input_dimensions, matrix_input_config, data)
ui.same_line()
}
program.ui_context.same_line = false
}
ui.container_end()
}
draw_grid :: proc(area: rect2, line_interval: v2, color: rl.Color) {
br := rect_get_br(area)
for x := area.x; x < br.x; x += line_interval.x {
rl.DrawLineV({x, area.y}, {x, br.y}, color)
}
for y := area.y; y < br.y; y += line_interval.y {
rl.DrawLineV({area.x, y}, {br.x, y}, color)
}
}