1225 lines
45 KiB
Odin
1225 lines
45 KiB
Odin
package main
|
|
|
|
/*
|
|
Rules of matrices:
|
|
1) Order matters when multiplying with vectors
|
|
- m * v = Column vector x matrix
|
|
- v * m = Row vector x matrix
|
|
|
|
2) Odin matrices are column-major for math efficiency
|
|
fmt.println(matrix[3, 3]f32 {
|
|
a, -b, 0,
|
|
b, a, 0,
|
|
0, 0, 1,
|
|
})
|
|
>> [0.8660254, 0.5, 0, -0.5, 0.8660254, 0, 0, 0, 1]
|
|
|
|
Additionally this means that the first index of a matrix is the column.
|
|
mat[<column>, <row>].
|
|
If you had an array of 9 floats ([9]f32) and you accessed the first one
|
|
It'd be the same as accessing the first colmun of a matrix.
|
|
array[1] = mat[1, 0]
|
|
|
|
3) Matrix multiplication order matters
|
|
// scale first then translate
|
|
transformation_a := translation * scale
|
|
// translate first then scale
|
|
transformation_b := scale * translation
|
|
*/
|
|
|
|
import rl "libraries/raylib"
|
|
import ui "libraries/snths_ui"
|
|
import "core:mem"
|
|
import "core:fmt"
|
|
import "core:math"
|
|
import "core:math/linalg"
|
|
import "core:strings"
|
|
|
|
RAYGUI_ICON_NEW :: "#009#"
|
|
RAYGUI_ICON_SAVE :: "#002#"
|
|
RAYGUI_ICON_SAVE_AS :: "#001#"
|
|
RAYGUI_ICON_LOAD :: "#006#"
|
|
RAYGUI_ICON_IDENTITY_MATRIX :: "#000#"
|
|
RAYGUI_ICON_TRANSLATION_MATRIX :: "#068#"
|
|
RAYGUI_ICON_SCALE_MATRIX :: "#070#"
|
|
RAYGUI_ICON_ROTATION_MATRIX :: "#078#"
|
|
RAYGUI_ICON_PLAY :: "#131#"
|
|
RAYGUI_ICON_PAUSE :: "#132#"
|
|
RAYGUI_ICON_RESET :: "#211#"
|
|
RAYGUI_ICON_TRASH :: "#143#"
|
|
RAYGUI_ICON_DOWN :: "#120#"
|
|
RAYGUI_ICON_UP :: "#121#"
|
|
|
|
TRANSFORMATION_RECTANGLE_COLOR_ORIGIN :: rl.RED
|
|
TRANSFORMATION_RECTANGLE_COLOR_MIDDLE :: rl.MAGENTA
|
|
TRANSFORMATION_RECTANGLE_COLOR_NEXT :: rl.GRAY
|
|
TRANSFORMATION_RECTANGLE_COLOR_LAST :: rl.BLUE
|
|
|
|
@rodata default_project_load_location := "default"
|
|
@rodata font_data := #load("assets/fonts/cmuntb.ttf")
|
|
|
|
UiContainerType :: enum {
|
|
Normal,
|
|
Matrix,
|
|
Scroll,
|
|
}
|
|
|
|
UiElementData_Container :: struct {
|
|
type: UiContainerType,
|
|
thickness: f32,
|
|
scroll: f32,
|
|
matrix_index: int,
|
|
}
|
|
|
|
UiElementData_Slider :: struct {
|
|
min, max: f32,
|
|
value: ^f32,
|
|
}
|
|
|
|
UiElementData_FloatInput :: struct {
|
|
value: ^f32,
|
|
value_string: cstring,
|
|
tag: bit_set[enum{UpdateMatrix}],
|
|
}
|
|
|
|
UiElementData_TextInput :: struct {
|
|
str: cstring,
|
|
}
|
|
|
|
UiElementData_ToggleGroup :: struct {
|
|
value: ^i32,
|
|
}
|
|
|
|
UiButtonType :: enum {
|
|
Nothing,
|
|
ProjectSave,
|
|
ProjectSaveAs,
|
|
ProjectLoad,
|
|
ProjectNew,
|
|
ProjectSaveConfirm,
|
|
ProjectLoadConfirm,
|
|
DismissError,
|
|
AddIdentity,
|
|
AddTranslation,
|
|
AddScale,
|
|
AddRotation,
|
|
AddTranslationMatrixConfirm,
|
|
AddScaleMatrixConfirm,
|
|
AddRotationMatrixConfirm,
|
|
ExitDialog,
|
|
StartTransformation,
|
|
PauseTransformation,
|
|
ResumeTransformation,
|
|
ResetTransformation,
|
|
MatrixDelete,
|
|
MatrixMoveUp,
|
|
MatrixMoveDown,
|
|
CenterView,
|
|
}
|
|
|
|
UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{
|
|
.AddIdentity,
|
|
.AddTranslationMatrixConfirm,
|
|
.AddScaleMatrixConfirm,
|
|
.AddRotationMatrixConfirm,
|
|
.MatrixDelete,
|
|
.MatrixMoveUp,
|
|
.MatrixMoveDown,
|
|
}
|
|
|
|
UiElementData_Button :: struct {
|
|
type: UiButtonType,
|
|
target_matrix: int,
|
|
options: cstring,
|
|
}
|
|
|
|
UI_ALLOWED_FOCUS :: bit_set[ui.ElementType]{.InputFloat}
|
|
|
|
HotkeyTypes :: enum {
|
|
AddIdentity,
|
|
AddTranslation,
|
|
AddScale,
|
|
AddRotation,
|
|
ZoomXOnly,
|
|
ZoomYOnly,
|
|
}
|
|
|
|
@rodata hotkey_without_modifier := [HotkeyTypes]rl.KeyboardKey {
|
|
.AddIdentity = .I,
|
|
.AddTranslation = .T,
|
|
.AddScale = .S,
|
|
.AddRotation = .R,
|
|
.ZoomXOnly = .X,
|
|
.ZoomYOnly = .Y,
|
|
}
|
|
|
|
HotkeyWithModifier :: enum {
|
|
ProjectSave,
|
|
ProjectLoad,
|
|
}
|
|
|
|
@rodata hotkey_with_modifier := [HotkeyWithModifier]rl.KeyboardKey {
|
|
.ProjectSave = .S,
|
|
.ProjectLoad = .O,
|
|
}
|
|
|
|
FontStyle :: struct {
|
|
font: rl.Font,
|
|
size, spacing: f32
|
|
}
|
|
|
|
ProgramState :: enum {
|
|
Normal,
|
|
Dialog,
|
|
}
|
|
|
|
MouseState :: enum {
|
|
Hover,
|
|
DraggingGrid,
|
|
DraggingDraggable,
|
|
HoverMatrices,
|
|
}
|
|
|
|
ProgramDialogType :: enum {
|
|
CreateTranslationMatrix,
|
|
CreateScaleMatrix,
|
|
CreateRotationMatrix,
|
|
SaveProject,
|
|
LoadProject,
|
|
Error,
|
|
}
|
|
|
|
@rodata program_dialog_messages := [ProgramDialogType]cstring {
|
|
.CreateTranslationMatrix = "Create tranlation matrix",
|
|
.CreateScaleMatrix = "Create scale matrix",
|
|
.CreateRotationMatrix = "Create rotation matrix",
|
|
.SaveProject = "Save project",
|
|
.LoadProject = "Load project",
|
|
.Error = "Error",
|
|
}
|
|
|
|
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
|
|
|
|
DIALOG_STATE_INPUT_FIELD_COUNT :: 16
|
|
DIALOG_STATE_TEXT_INPUT_FIELD_SIZE :: 256
|
|
DIALOG_STATE_TEXT_INPUT_FIELD_COUNT :: 8
|
|
ProgramData_DialogState :: struct {
|
|
input_float_value_strings: [DIALOG_STATE_INPUT_FIELD_COUNT]InputFloatField,
|
|
input_float_values: [DIALOG_STATE_INPUT_FIELD_COUNT]f32,
|
|
input_text_buffers: [DIALOG_STATE_TEXT_INPUT_FIELD_SIZE][DIALOG_STATE_TEXT_INPUT_FIELD_COUNT]byte,
|
|
}
|
|
|
|
ProgramAnimationState :: enum {
|
|
Ready,
|
|
Playing,
|
|
Paused,
|
|
}
|
|
|
|
MouseDraggableType :: enum {
|
|
Point,
|
|
RectangleCorner,
|
|
Rectangle,
|
|
}
|
|
|
|
MouseDraggable_Point :: struct {
|
|
ref: ^v2,
|
|
}
|
|
|
|
MouseDraggable_RectangleCornerData :: struct {
|
|
corner: RectangleCornersCorner,
|
|
corners_ref: ^RectangleCorners,
|
|
corner_ref: ^v2,
|
|
}
|
|
|
|
MouseDraggable_RectangleData :: struct {
|
|
corners: ^RectangleCorners,
|
|
}
|
|
|
|
MouseDraggable :: union {
|
|
MouseDraggable_Point,
|
|
MouseDraggable_RectangleCornerData,
|
|
MouseDraggable_RectangleData,
|
|
}
|
|
|
|
RotationMode :: enum {
|
|
Degrees,
|
|
Radians,
|
|
}
|
|
|
|
Program :: struct {
|
|
ui_context: ui.Context,
|
|
ui_element_focus: ui.ElementId,
|
|
ui_overlay_context: ui.Context,
|
|
ui_overlay_element_focus: ui.ElementId,
|
|
ui_overlay_queue_focus_first: bool,
|
|
ui_tooltip_offset: v2,
|
|
matrix_float_pool: []f32,
|
|
matrix_value_string_pool: []InputFloatField,
|
|
matrix_create_dropdown_value: HotkeyTypes,
|
|
matrix_count: int,
|
|
matrix_scroll: f32,
|
|
matrix_scroll_speed: f32,
|
|
mouse_state: MouseState,
|
|
mouse_draggables: [dynamic; 64]MouseDraggable,
|
|
mouse_current_draggable: MouseDraggable,
|
|
mouse_draggable_radius: f32,
|
|
project_save_string: [DIALOG_STATE_TEXT_INPUT_FIELD_COUNT]byte,
|
|
rotation_mode: RotationMode,
|
|
grid: Grid,
|
|
grid_zoom_x_only: bool,
|
|
grid_zoom_y_only: bool,
|
|
grid_zoom_speed: f32,
|
|
font_style: FontStyle,
|
|
state: ProgramState,
|
|
message_type: ProgramMessageType,
|
|
dialog_type: ProgramDialogType,
|
|
dialog_data: ProgramData_DialogState,
|
|
dialog_message: cstring,
|
|
animation: Animation,
|
|
animation_state: ProgramAnimationState,
|
|
animation_speed: f32,
|
|
}
|
|
|
|
float_slice_get_mat3 :: proc(floats: []f32, index: int) -> ^mat3 {
|
|
return cast(^mat3)(&floats[index * 9])
|
|
}
|
|
|
|
matrix_pool_max :: proc() -> int {
|
|
return len(program.matrix_float_pool) / 9
|
|
}
|
|
|
|
matrix_pool_get_mat3 :: proc() -> (value_strings: []InputFloatField, values: []f32) {
|
|
assert(program.matrix_count + 1 < matrix_pool_max(), "Max matrix count exceeded")
|
|
value_strings = program.matrix_value_string_pool[program.matrix_count * 9 : program.matrix_count * 9 + 9]
|
|
values = program.matrix_float_pool[program.matrix_count * 9 : program.matrix_count * 9 + 9]
|
|
for i in 0..<len(value_strings) {
|
|
mem.set(raw_data(&value_strings[i]), 0, len(InputFloatField))
|
|
value_strings[i][0] = '0'
|
|
values[i] = 0.0
|
|
}
|
|
program.matrix_count += 1
|
|
return value_strings, values
|
|
}
|
|
|
|
matrix_move_to :: proc(floats: []f32, inputs: []InputFloatField, from, to: int) {
|
|
if from == to do return
|
|
floats := program.matrix_float_pool
|
|
inputs := program.matrix_value_string_pool
|
|
floats_values_size := size_of(f32) * 9
|
|
inputs_buffer_size := size_of(InputFloatField) * 9
|
|
floats_moving_matrix := &floats[from * 9]
|
|
inputs_moving_matrix := &inputs[from * 9][0]
|
|
floats_moved_to_matrix := &floats[to * 9]
|
|
inputs_moved_to_matrix := &inputs[to * 9][0]
|
|
floats_temp := [9]f32{}
|
|
inputs_temp := [9]InputFloatField{}
|
|
mem.copy(&floats_temp[0], floats_moving_matrix, floats_values_size)
|
|
mem.copy(&inputs_temp[0][0], inputs_moving_matrix, inputs_buffer_size)
|
|
mem.copy(floats_moving_matrix, floats_moved_to_matrix, floats_values_size)
|
|
mem.copy(inputs_moving_matrix, inputs_moved_to_matrix, inputs_buffer_size)
|
|
mem.copy(floats_moved_to_matrix, &floats_temp[0], floats_values_size)
|
|
mem.copy(inputs_moved_to_matrix, &inputs_temp[0][0], inputs_buffer_size)
|
|
}
|
|
|
|
program_save_project :: proc(program: ^Program, path: string) -> ProjectWriteError {
|
|
project := ProjectData {
|
|
matrix_count = u32(program.matrix_count),
|
|
matrices = program.matrix_float_pool,
|
|
}
|
|
project_write(&project, path) or_return
|
|
return nil
|
|
}
|
|
|
|
program_save_project_from_project_string_or_open_dialog :: proc(program: ^Program) {
|
|
path := strings.string_from_null_terminated_ptr(&program.project_save_string[0], DIALOG_STATE_TEXT_INPUT_FIELD_SIZE)
|
|
if len(path) > 0 {
|
|
if err := program_save_project(program, path); err != nil {
|
|
msg := fmt.caprint("Failed to save project to '", path, "'\n" ,typeid_of(type_of(err)), ".", err, sep = "")
|
|
program_show_dialog(program, .Error, msg)
|
|
} else {
|
|
program.state = .Normal
|
|
}
|
|
} else {
|
|
program_show_dialog(program, .SaveProject)
|
|
}
|
|
}
|
|
|
|
program_load_project :: proc(program: ^Program, path: string) -> ProjectReadError {
|
|
project: ProjectData
|
|
project.matrices = program.matrix_float_pool
|
|
project_read(&project, path) or_return
|
|
program.matrix_count = int(project.matrix_count)
|
|
for val, i in program.matrix_float_pool[:program.matrix_count * 9] {
|
|
fmt.bprint(program.matrix_value_string_pool[i][:], val)
|
|
}
|
|
program.animation.matrix_count = program.matrix_count
|
|
return nil
|
|
}
|
|
|
|
program_show_dialog :: proc(program: ^Program, dialog_type: ProgramDialogType, message: cstring = "") {
|
|
program.dialog_data = {}
|
|
program.ui_overlay_queue_focus_first = true
|
|
program.state = .Dialog
|
|
program.dialog_type = dialog_type
|
|
program.dialog_message = message
|
|
mem.set(&program.dialog_data, 0, size_of(program.dialog_data))
|
|
for i in 0..<len(program.dialog_data.input_float_values) {
|
|
program.dialog_data.input_float_values[i] = 0.0
|
|
program.dialog_data.input_float_value_strings[i][0] = '0'
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
font := rl.LoadFontFromMemory(".ttf", raw_data(font_data), i32(len(font_data)), 24.0, nil, 0)
|
|
program.font_style = {
|
|
font = font,
|
|
size = 24.0,
|
|
spacing = 2.0
|
|
}
|
|
rl.GuiSetFont(font)
|
|
rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SIZE), i32(program.font_style.size))
|
|
rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SPACING), i32(program.font_style.spacing))
|
|
|
|
program.grid = {
|
|
cell_size_px = 40.0,
|
|
zoom = 1.0,
|
|
offset = -program_screen_size() / 2.0,
|
|
}
|
|
program.grid_zoom_speed = 1.0
|
|
program.mouse_draggable_radius = 6.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.ui_tooltip_offset = 20.0
|
|
|
|
animation_init(&program.animation, program.matrix_float_pool)
|
|
|
|
if err := program_load_project(&program, default_project_load_location); err != nil {
|
|
program_show_dialog(&program, .Error, fmt.caprint(typeid_of(type_of(err)), ".", err))
|
|
} else {
|
|
mem.copy(&program.project_save_string[0], raw_data(default_project_load_location), len(default_project_load_location))
|
|
}
|
|
}
|
|
|
|
program_get_master_matrix :: proc() -> mat3 {
|
|
matrices := (cast([^]mat3)raw_data(program.matrix_float_pool))[:program.matrix_count]
|
|
master_matrix := linalg.identity_matrix(mat3)
|
|
#reverse for mat in matrices {
|
|
master_matrix *= mat
|
|
}
|
|
return master_matrix
|
|
}
|
|
|
|
program_set_animation_state :: proc(state: ProgramAnimationState) {
|
|
if program.animation_state == state do return
|
|
previous_state := program.animation_state
|
|
program.animation_state = state
|
|
switch program.animation_state {
|
|
case .Ready:
|
|
program.animation.matrix_index = 0.0
|
|
case .Playing:
|
|
|
|
case .Paused:
|
|
}
|
|
}
|
|
|
|
program_update :: proc() {
|
|
if rl.IsKeyPressed(.F4) {
|
|
rl.ToggleFullscreen()
|
|
}
|
|
|
|
ui_elements, top_bar_container, 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(program.mouse_state, top_bar_container, matrix_container)
|
|
if rl.IsKeyPressed(.TAB) {
|
|
program.ui_element_focus = ui.focus_next_element(program.ui_element_focus, ui_elements, UI_ALLOWED_FOCUS)
|
|
}
|
|
if rl.IsKeyDown(.LEFT_CONTROL) {
|
|
for hotkey, index in hotkey_with_modifier {
|
|
if rl.IsKeyPressed(hotkey) {
|
|
switch index {
|
|
case .ProjectSave:
|
|
program_save_project_from_project_string_or_open_dialog(&program)
|
|
case .ProjectLoad:
|
|
program_show_dialog(&program, .LoadProject)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for hotkey, index in hotkey_without_modifier {
|
|
if rl.IsKeyPressed(hotkey) {
|
|
#partial switch index {
|
|
case .AddIdentity:
|
|
program_add_identity_matrix()
|
|
case .AddTranslation:
|
|
program_show_dialog(&program, .CreateTranslationMatrix)
|
|
case .AddScale:
|
|
program_show_dialog(&program, .CreateScaleMatrix)
|
|
case .AddRotation:
|
|
program_show_dialog(&program, .CreateRotationMatrix)
|
|
case .ZoomXOnly:
|
|
if program.grid_zoom_x_only {
|
|
program.grid_zoom_x_only = false
|
|
program.grid_zoom_y_only = false
|
|
} else {
|
|
program.grid_zoom_x_only = true
|
|
program.grid_zoom_y_only = false
|
|
}
|
|
case .ZoomYOnly:
|
|
if program.grid_zoom_y_only {
|
|
program.grid_zoom_x_only = false
|
|
program.grid_zoom_y_only = false
|
|
} else {
|
|
program.grid_zoom_x_only = false
|
|
program.grid_zoom_y_only = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case .Dialog:
|
|
ui_overlay_elements = program_build_dialog_ui(&program.ui_overlay_context, program.dialog_type)
|
|
if program.ui_overlay_queue_focus_first {
|
|
program.ui_overlay_queue_focus_first = false
|
|
program.ui_overlay_element_focus = ui.focus_next_element({container_index = 0, element_index = 0}, ui_overlay_elements, UI_ALLOWED_FOCUS)
|
|
}
|
|
if rl.IsKeyPressed(.TAB) {
|
|
program.ui_overlay_element_focus = ui.focus_next_element(program.ui_overlay_element_focus, ui_overlay_elements, UI_ALLOWED_FOCUS)
|
|
}
|
|
if rl.IsKeyDown(.ENTER) {
|
|
vals := program.dialog_data.input_float_values
|
|
switch program.dialog_type {
|
|
case .CreateTranslationMatrix: program_add_translation_matrix(vals[0], vals[1], vals[2])
|
|
case .CreateScaleMatrix: program_add_scalar_matrix(vals[0], vals[1], vals[2])
|
|
case .CreateRotationMatrix: program_add_rotation_matrix(vals[0])
|
|
case .SaveProject:
|
|
path := strings.string_from_null_terminated_ptr(&program.dialog_data.input_text_buffers[0][0], DIALOG_STATE_TEXT_INPUT_FIELD_SIZE)
|
|
if err := program_save_project(&program, path); err != nil {
|
|
msg := fmt.caprint("Tried saving but got error: '", typeid_of(type_of(err)), ".", err, "'", sep = "")
|
|
program_show_dialog(&program, .Error, msg)
|
|
} else {
|
|
program.project_save_string = program.dialog_data.input_text_buffers[0]
|
|
program.state = .Normal
|
|
}
|
|
case .LoadProject:
|
|
path := strings.string_from_null_terminated_ptr(&program.dialog_data.input_text_buffers[0][0], DIALOG_STATE_TEXT_INPUT_FIELD_SIZE)
|
|
if err := program_load_project(&program, path); err != nil {
|
|
msg := fmt.caprint("Tried loading but got error: '", typeid_of(type_of(err)), ".", err, "'", sep = "")
|
|
program_show_dialog(&program, .Error, msg)
|
|
} else {
|
|
program.state = .Normal
|
|
}
|
|
case .Error:
|
|
program.state = .Normal
|
|
}
|
|
program.animation.matrix_count = program.matrix_count
|
|
program.state = .Normal
|
|
}
|
|
}
|
|
|
|
clear(&program.mouse_draggables)
|
|
|
|
top_bar_container_br := rect_get_br(rect2(top_bar_container.area))
|
|
matrix_container_br := rect_get_br(rect2(matrix_container.area))
|
|
grid_text_clip := rect_from_area(v2{matrix_container_br.x, top_bar_container_br.y}, program_screen_size())
|
|
grid_area := rect2{0.0, 0.0, **program_screen_size()}
|
|
draw_grid(program.grid, grid_area, rect_grow(grid_text_clip, -4.0), rect_grow(grid_text_clip, -32.0), {255, 255, 255, 64})
|
|
|
|
{
|
|
animation := &program.animation
|
|
|
|
if program.animation_state == .Playing {
|
|
animation.matrix_index += 0.01
|
|
}
|
|
|
|
animation_draw(animation^, linalg.inverse(grid_world_to_grid_matrix(program.grid.zoom, program.grid.offset)))
|
|
|
|
switch animation.type {
|
|
case .Rectangle:
|
|
append(&program.mouse_draggables, MouseDraggable_RectangleCornerData{
|
|
corner = .Tl,
|
|
corners_ref = &animation.shapes.rectangle,
|
|
corner_ref = &animation.shapes.rectangle.tl})
|
|
append(&program.mouse_draggables, MouseDraggable_RectangleCornerData{
|
|
corner = .Tr,
|
|
corners_ref = &animation.shapes.rectangle,
|
|
corner_ref = &animation.shapes.rectangle.tr})
|
|
append(&program.mouse_draggables, MouseDraggable_RectangleCornerData{
|
|
corner = .Bl,
|
|
corners_ref = &animation.shapes.rectangle,
|
|
corner_ref = &animation.shapes.rectangle.bl})
|
|
append(&program.mouse_draggables, MouseDraggable_RectangleCornerData{
|
|
corner = .Br,
|
|
corners_ref = &animation.shapes.rectangle,
|
|
corner_ref = &animation.shapes.rectangle.br})
|
|
append(&program.mouse_draggables, MouseDraggable_RectangleData{&animation.shapes.rectangle})
|
|
}
|
|
}
|
|
|
|
grid_to_world_matrix := linalg.inverse(grid_world_to_grid_matrix(program.grid.zoom, program.grid.offset))
|
|
for draggable in program.mouse_draggables {
|
|
switch draggable in draggable {
|
|
case MouseDraggable_Point:
|
|
position_world := matrix3_transform_xy(grid_to_world_matrix, draggable.ref^)
|
|
rl.DrawCircleV(position_world, program.mouse_draggable_radius, ANIMATION_COLOR_ORIGIN)
|
|
case MouseDraggable_RectangleCornerData:
|
|
position_world := matrix3_transform_xy(grid_to_world_matrix, draggable.corner_ref^)
|
|
rl.DrawCircleV(position_world, program.mouse_draggable_radius, ANIMATION_COLOR_ORIGIN)
|
|
case MouseDraggable_RectangleData:
|
|
}
|
|
}
|
|
|
|
ui_element_focus := program.state == .Normal ? program.ui_element_focus : ui.element_id_invalid()
|
|
program.ui_element_focus = program_render_ui_and_handle_focus(&program.ui_context, ui_element_focus, program.font_style, ui_elements)
|
|
|
|
if len(ui_overlay_elements) > 0 {
|
|
rl.DrawRectangle(0, 0, rl.GetScreenWidth(), rl.GetScreenHeight(), {0, 0, 0, 192})
|
|
program.ui_overlay_element_focus = program_render_ui_and_handle_focus(
|
|
&program.ui_overlay_context,
|
|
program.ui_overlay_element_focus,
|
|
program.font_style,
|
|
ui_overlay_elements)
|
|
}
|
|
}
|
|
|
|
program_add_identity_matrix :: proc() {
|
|
value_strings, values := matrix_pool_get_mat3()
|
|
write_matrix_to_values_and_value_strings(values, value_strings, mat3(1))
|
|
}
|
|
|
|
program_add_translation_matrix :: proc(x, y, z: f32) {
|
|
value_strings, values := matrix_pool_get_mat3()
|
|
write_matrix_to_values_and_value_strings(values, value_strings, mat3 {
|
|
1, 0, x,
|
|
0, 1, y,
|
|
0, 0, z,
|
|
})
|
|
}
|
|
|
|
program_add_scalar_matrix :: proc(x, y, z: f32) {
|
|
value_strings, values := matrix_pool_get_mat3()
|
|
write_matrix_to_values_and_value_strings(values, value_strings, linalg.matrix3_scale(v3{x, y, z}))
|
|
}
|
|
|
|
program_add_rotation_matrix :: proc(angle: f32) {
|
|
value_strings, values := matrix_pool_get_mat3()
|
|
radians: f32
|
|
switch (program.rotation_mode) {
|
|
case .Degrees: radians = linalg.RAD_PER_DEG * angle
|
|
case .Radians: radians = angle
|
|
}
|
|
write_matrix_to_values_and_value_strings(values, value_strings, matrix3_rotate2(radians))
|
|
}
|
|
|
|
write_matrix_to_values_and_value_strings :: proc(values: []f32, value_strings: []InputFloatField, mat: mat3) {
|
|
for i in 0..<9 {
|
|
col := i % 3
|
|
row := i / 3
|
|
values[i] = mat[col, row]
|
|
write_float_to_buffer(value_strings[i][:], mat[col, row])
|
|
}
|
|
}
|
|
|
|
write_float_to_buffer :: proc(buffer: []byte, value: f32) {
|
|
fraction := fract(value)
|
|
if fraction == 0.0 {
|
|
fmt.bprintf(buffer, "%.0f", value)
|
|
} else if math.floor(fraction * 10.0) == 1.0 {
|
|
fmt.bprintf(buffer, "%.1f", value)
|
|
} else if math.floor(fraction * 100.0) == 1.0 {
|
|
fmt.bprintf(buffer, "%.2f", value)
|
|
} else {
|
|
fmt.bprintf(buffer, "%.3f", value)
|
|
}
|
|
}
|
|
|
|
program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType) -> []ui.Element {
|
|
size := program_screen_size()
|
|
container_margin := size * 0.33
|
|
element_margin: f32 = 4.0
|
|
dialog_data := &program.dialog_data
|
|
|
|
ui.start(ctx, V2_ZERO, program_screen_size())
|
|
ui.container_begin(ui.dimensions_auto(), {margin = container_margin, padding = 8.0})
|
|
header_label_config := ui.ElementConfiguration{label = program_dialog_messages[program.dialog_type], margin = element_margin}
|
|
if dialog_type == .Error {
|
|
header_label_config.color = ui.col(rl.RED)
|
|
} else {
|
|
header_label_config.color = ui.col(rl.WHITE)
|
|
}
|
|
ui.element(.Label, {}, header_label_config)
|
|
|
|
if (len(program.dialog_message) > 0) {
|
|
ui.element(.Label, {}, {label = program.dialog_message, color = ui.col(rl.WHITE), margin = element_margin})
|
|
}
|
|
|
|
switch dialog_type {
|
|
case .CreateTranslationMatrix, .CreateScaleMatrix:
|
|
ui_labeled_input_float_element("X", 4.0, &dialog_data.input_float_values[0], &dialog_data.input_float_value_strings[0])
|
|
ui_labeled_input_float_element("Y", 4.0, &dialog_data.input_float_values[1], &dialog_data.input_float_value_strings[1])
|
|
ui_labeled_input_float_element("Z", 4.0, &dialog_data.input_float_values[2], &dialog_data.input_float_value_strings[2])
|
|
button_type: UiButtonType
|
|
if dialog_type == .CreateTranslationMatrix {
|
|
button_type = .AddTranslationMatrixConfirm
|
|
} else {
|
|
button_type = .AddScaleMatrixConfirm
|
|
}
|
|
ui_confirm_and_cancel_buttons(button_type, element_margin)
|
|
case .CreateRotationMatrix:
|
|
ui_labeled_input_float_element("Angle", 4.0, &dialog_data.input_float_values[0], &dialog_data.input_float_value_strings[0])
|
|
toggle_group_dimensions := ui.dimensions_pixels(128.0, 32.0)
|
|
toggle_group_options := enum_to_raygui_options_string(RotationMode, context.temp_allocator)
|
|
toggle_group_config := ui.ElementConfiguration {label = toggle_group_options, margin = element_margin}
|
|
toggle_group_data := new(UiElementData_ToggleGroup)
|
|
toggle_group_data^ = {value = cast(^i32)&program.rotation_mode}
|
|
ui.element(.ToggleGroup, toggle_group_dimensions, toggle_group_config, toggle_group_data)
|
|
ui_confirm_and_cancel_buttons(.AddRotationMatrixConfirm, element_margin)
|
|
case .SaveProject:
|
|
ui_labeled_input_text_element("Path", 4.0, dialog_data.input_text_buffers[0][:])
|
|
ui_confirm_and_cancel_buttons(.ProjectSaveConfirm, element_margin)
|
|
case .LoadProject:
|
|
ui_labeled_input_text_element("Path", 4.0, dialog_data.input_text_buffers[0][:])
|
|
ui_confirm_and_cancel_buttons(.ProjectLoadConfirm, element_margin)
|
|
case .Error:
|
|
button_data: ^UiElementData_Button
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_data^ = {type = .DismissError}
|
|
ui.element(.Button, ui.dimensions_pixels(96.0, 32.0), {label = "Ok", margin = element_margin}, button_data)
|
|
}
|
|
ui.container_end()
|
|
return ui.end()
|
|
}
|
|
|
|
program_screen_size :: proc() -> v2 {
|
|
return {f32(rl.GetScreenWidth()), f32(rl.GetScreenHeight())}
|
|
}
|
|
|
|
program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, top_bar_container, matrix_container: ^ui.Element) {
|
|
ui.start(ctx, {0, 0}, program_screen_size())
|
|
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_width := ui.Size_Pixels(top_bar_inner_height)
|
|
top_bar_button_dimensions := ui.Dimensions {top_bar_button_width, ui.Size_Percentage(1.0)}
|
|
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_margin := top_bar_element_margin
|
|
button_config: ui.ElementConfiguration
|
|
button_tooltip: cstring
|
|
button_dimensions := top_bar_button_dimensions
|
|
button_data: ^UiElementData_Button
|
|
|
|
divider_dimensions := ui.Dimensions{ui.Size_Pixels(1.0), ui.Size_Percentage(1.0)}
|
|
divider_config := ui.ElementConfiguration{padding = 4.0, color = ui.col(rl.WHITE)}
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_tooltip = fmt.ctprint("Save project\n(CTRL+", hotkey_with_modifier[.ProjectSave], ")", sep = "")
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_SAVE, tooltip = button_tooltip}
|
|
button_data^ = {type = .ProjectSave}
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_SAVE_AS, tooltip = "Save project as..."}
|
|
button_data^ = {type = .ProjectSaveAs}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_tooltip = fmt.ctprint("Load project\n(CTRL+", hotkey_with_modifier[.ProjectLoad], ")", sep = "")
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_LOAD, tooltip = button_tooltip}
|
|
button_data^ = {type = .ProjectLoad}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_NEW, tooltip = "New project"}
|
|
button_data^ = {type = .ProjectNew}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
ui.same_line()
|
|
ui.element(.Divider, divider_dimensions, divider_config)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_tooltip = fmt.ctprint("Add identity\nmatrix (", hotkey_without_modifier[.AddIdentity], ")", sep = "")
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_IDENTITY_MATRIX, tooltip = button_tooltip}
|
|
button_data^ = {type = .AddIdentity}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_tooltip = fmt.ctprint("Add translation\nmatrix (", hotkey_without_modifier[.AddTranslation], ")", sep = "")
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_TRANSLATION_MATRIX, tooltip = button_tooltip}
|
|
button_data^ = {type = .AddTranslation}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_tooltip = fmt.ctprint("Add scale\nmatrix (", hotkey_without_modifier[.AddScale], ")", sep = "")
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_SCALE_MATRIX, tooltip = button_tooltip}
|
|
button_data^ = {type = .AddScale}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_tooltip = fmt.ctprint("Add rotation\nmatrix (", hotkey_without_modifier[.AddIdentity], ")", sep = "")
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_ROTATION_MATRIX, tooltip = button_tooltip}
|
|
button_data^ = {type = .AddRotation}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
// TODO (synthas): Use raygui border theme color for consistency
|
|
// TODO (synthas): Padding and margin doesn't work here for some reason
|
|
ui.same_line()
|
|
ui.element(.Divider, divider_dimensions, divider_config)
|
|
|
|
play_button_label: cstring
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
switch program.animation_state {
|
|
case .Ready:
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_PLAY, tooltip = "Play\nanimation"}
|
|
button_data^ = {type = .StartTransformation}
|
|
case .Paused:
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_PLAY, tooltip = "Resume\nanimation"}
|
|
button_data^ = {type = .ResumeTransformation}
|
|
case .Playing:
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_PAUSE, tooltip = "Pause\nanimation"}
|
|
button_data^ = {type = .PauseTransformation}
|
|
}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_config = {margin = button_margin, label = RAYGUI_ICON_RESET, tooltip = "Reset\nanimation"}
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_data^ = {type = .ResetTransformation}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
button_config = {margin = button_margin, label = "1", tooltip = "Center\nanimation"}
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_data^ = {type = .CenterView}
|
|
ui.same_line()
|
|
ui.element(.Button, button_dimensions, button_config, button_data)
|
|
|
|
ui.same_line()
|
|
ui.element(.Divider, divider_dimensions, divider_config)
|
|
|
|
slider_config: ui.ElementConfiguration
|
|
slider_data: ^UiElementData_Slider
|
|
slider_dimensions := ui.Dimensions {top_bar_button_width * 5.0, ui.Size_Percentage(1.0)}
|
|
slider_config = {margin = top_bar_element_margin * 2}
|
|
slider_data = new(UiElementData_Slider, context.temp_allocator)
|
|
slider_data^ = {
|
|
value = &program.animation.matrix_index,
|
|
max = f32(program.animation.matrix_count),
|
|
min = 0.0
|
|
}
|
|
ui.same_line()
|
|
ui.element(.Slider, slider_dimensions, slider_config, slider_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_Pixels(320.0), 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(192.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(i, mat, value_strings, container_dimensions, container_config)
|
|
}
|
|
ui.container_end()
|
|
elements = ui.end()
|
|
return
|
|
}
|
|
|
|
program_handle_mouse :: proc(mouse_state: MouseState, top_bar_container, matrix_container: ^ui.Element) -> MouseState {
|
|
mouse := rl.GetMousePosition()
|
|
mouse_wheel := rl.GetMouseWheelMoveV()
|
|
world_to_grid_matrix := grid_world_to_grid_matrix(program.grid.zoom, program.grid.offset)
|
|
grid_to_world_matrix := linalg.inverse(world_to_grid_matrix)
|
|
|
|
mouse_state := mouse_state
|
|
|
|
state_label: switch mouse_state {
|
|
case .Hover:
|
|
if rect_has_point(rect2(matrix_container.area), mouse) {
|
|
mouse_state = .HoverMatrices
|
|
break state_label
|
|
}
|
|
if rect_has_point(rect2(top_bar_container.area), mouse) {
|
|
break state_label
|
|
}
|
|
if rl.IsMouseButtonPressed(.LEFT) {
|
|
for draggable in program.mouse_draggables {
|
|
switch draggable in draggable {
|
|
case MouseDraggable_Point:
|
|
circle_position := matrix3_transform_xy(grid_to_world_matrix, draggable.ref^)
|
|
if linalg.distance(mouse, circle_position) <= program.mouse_draggable_radius {
|
|
program.mouse_current_draggable = draggable
|
|
mouse_state = .DraggingDraggable
|
|
break state_label
|
|
}
|
|
case MouseDraggable_RectangleCornerData:
|
|
circle_position := matrix3_transform_xy(grid_to_world_matrix, draggable.corner_ref^)
|
|
if linalg.distance(mouse, circle_position) <= program.mouse_draggable_radius {
|
|
program.mouse_current_draggable = draggable
|
|
mouse_state = .DraggingDraggable
|
|
break state_label
|
|
}
|
|
case MouseDraggable_RectangleData:
|
|
corners_world := rectangle_corners_transform_mat3(grid_to_world_matrix, draggable.corners^)
|
|
rect := rect_from(corners_world.tl, corners_world.tr, corners_world.br, corners_world.bl)
|
|
if rect_has_point(rect, mouse) {
|
|
program.mouse_current_draggable = draggable
|
|
mouse_state = .DraggingDraggable
|
|
break state_label
|
|
}
|
|
}
|
|
}
|
|
mouse_state = .DraggingGrid
|
|
break state_label
|
|
}
|
|
program.grid.zoom_previous = program.grid.zoom
|
|
grid_zoom_dimension_lock := v2{1.0 - f32(int(program.grid_zoom_y_only)), 1.0 - f32(int(program.grid_zoom_x_only))}
|
|
program.grid.zoom -= grid_zoom_dimension_lock * mouse_wheel.y * 0.2 * program.grid_zoom_speed
|
|
case .DraggingDraggable:
|
|
if !rl.IsMouseButtonDown(.LEFT) {
|
|
mouse_state = .Hover
|
|
break state_label
|
|
}
|
|
draggable := program.mouse_current_draggable
|
|
switch draggable in draggable {
|
|
case MouseDraggable_Point:
|
|
draggable.ref^ = matrix3_transform_xy(world_to_grid_matrix, mouse)
|
|
case MouseDraggable_RectangleCornerData:
|
|
position := matrix3_transform_xy(world_to_grid_matrix, mouse)
|
|
corners := rectangle_corners_move_corner(draggable.corners_ref^, position, draggable.corner)
|
|
draggable.corners_ref^ = corners
|
|
case MouseDraggable_RectangleData:
|
|
mouse_delta_grid := rl.GetMouseDelta() * grid_get_zoom(program.grid.zoom)
|
|
draggable.corners^ = rectangle_corners_add(draggable.corners^, mouse_delta_grid)
|
|
}
|
|
case .DraggingGrid:
|
|
program.grid.offset -= rl.GetMouseDelta() * grid_get_zoom(program.grid.zoom)
|
|
if !rl.IsMouseButtonDown(.LEFT) {
|
|
mouse_state = .Hover
|
|
break state_label
|
|
}
|
|
case .HoverMatrices:
|
|
program.matrix_scroll = max(0, program.matrix_scroll - mouse_wheel.y * program.matrix_scroll_speed)
|
|
if !rect_has_point(rect2(matrix_container.area), mouse) {
|
|
mouse_state = .Hover
|
|
break state_label
|
|
}
|
|
}
|
|
|
|
return mouse_state
|
|
}
|
|
|
|
program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: ui.ElementId, font_style: FontStyle, elements: []ui.Element) -> (focus: ui.ElementId) {
|
|
tooltip_text: cstring
|
|
tooltip_position: v2
|
|
|
|
scissor_container: ^ui.Element
|
|
render_above: ui.Element
|
|
render_above.id = ui.element_id_invalid()
|
|
focus_element_id := focus_element_id
|
|
|
|
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:
|
|
element_matrix_index := f32(data.matrix_index + 1)
|
|
animation_matrix_index := animation_get_eased_progress(program.animation)
|
|
matrix_animation_distance := element_matrix_index - animation_matrix_index
|
|
alpha := max(0.0, 1.0 - abs(matrix_animation_distance))
|
|
color := rl.Color{128, 255, 128, u8(alpha * 64.0)}
|
|
rl.DrawRectangleRec(area, color)
|
|
|
|
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:
|
|
if len(element.tooltip) > 0 && rect_has_point(area, rl.GetMousePosition()) {
|
|
tooltip_text = element.tooltip
|
|
// TODO (synthas): implement keeping the rectangle within screen borders
|
|
tooltip_position = rl.GetMousePosition()
|
|
}
|
|
if rl.GuiButton(area, element.label) {
|
|
if element.data == nil do break
|
|
data := cast(^UiElementData_Button)element.data
|
|
switch data.type {
|
|
case .Nothing:
|
|
case .AddIdentity: program_add_identity_matrix()
|
|
case .AddTranslation: program_show_dialog(&program, .CreateTranslationMatrix)
|
|
case .AddScale: program_show_dialog(&program, .CreateScaleMatrix)
|
|
case .AddRotation: program_show_dialog(&program, .CreateRotationMatrix)
|
|
case .AddTranslationMatrixConfirm:
|
|
inputs := program.dialog_data.input_float_values
|
|
program_add_translation_matrix(inputs[0], inputs[1], inputs[2])
|
|
program.state = .Normal
|
|
case .AddScaleMatrixConfirm:
|
|
inputs := program.dialog_data.input_float_values
|
|
program_add_scalar_matrix(inputs[0], inputs[1], inputs[2])
|
|
program.state = .Normal
|
|
case .AddRotationMatrixConfirm:
|
|
program_add_rotation_matrix(program.dialog_data.input_float_values[0])
|
|
program.state = .Normal
|
|
case .ExitDialog:
|
|
program.state = .Normal
|
|
case .StartTransformation:
|
|
if program.matrix_count > 0 {
|
|
program.animation_state = .Playing
|
|
program.animation.matrix_index = 0.0
|
|
}
|
|
case .PauseTransformation:
|
|
program.animation_state = .Paused
|
|
case .ResumeTransformation:
|
|
if animation_is_finished(program.animation) {
|
|
program.animation.matrix_index = 0.0
|
|
}
|
|
program.animation_state = .Playing
|
|
case .ResetTransformation:
|
|
program.animation.matrix_index = 0.0
|
|
case .ProjectSaveConfirm:
|
|
path := strings.string_from_null_terminated_ptr(&program.dialog_data.input_text_buffers[0][0], DIALOG_STATE_TEXT_INPUT_FIELD_SIZE)
|
|
if err := program_save_project(&program, path); err != nil {
|
|
program_show_dialog(&program, .Error, fmt.caprint(typeid_of(type_of(err)), ".", err))
|
|
} else {
|
|
program.state = .Normal
|
|
}
|
|
case .ProjectLoadConfirm:
|
|
path := strings.string_from_null_terminated_ptr(&program.dialog_data.input_text_buffers[0][0], DIALOG_STATE_TEXT_INPUT_FIELD_SIZE)
|
|
if err := program_load_project(&program, path); err != nil {
|
|
msg := fmt.caprint("Tried loading but got error: '", typeid_of(type_of(err)), ".", err, "'", sep = "")
|
|
program_show_dialog(&program, .Error, msg)
|
|
} else {
|
|
program.state = .Normal
|
|
}
|
|
case .MatrixDelete:
|
|
if data.target_matrix + 1 < program.matrix_count {
|
|
floats := program.matrix_float_pool
|
|
inputs := program.matrix_value_string_pool
|
|
amount := program.matrix_count - data.target_matrix
|
|
matrix_values_size := size_of(f32) * 9
|
|
matrix_inputs_size := size_of(InputFloatField) * 9
|
|
mem.copy(&floats[data.target_matrix * 9], &floats[data.target_matrix * 9 + 9], matrix_values_size * amount)
|
|
mem.copy(&inputs[data.target_matrix * 9][0], &inputs[data.target_matrix * 9 + 9][0], matrix_inputs_size * amount)
|
|
}
|
|
program.matrix_count = max(program.matrix_count - 1, 0)
|
|
case .MatrixMoveUp:
|
|
if data.target_matrix <= 0 do break;
|
|
matrix_move_to(program.matrix_float_pool, program.matrix_value_string_pool, data.target_matrix, data.target_matrix - 1)
|
|
case .MatrixMoveDown:
|
|
if data.target_matrix + 1 >= program.matrix_count do break;
|
|
matrix_move_to(program.matrix_float_pool, program.matrix_value_string_pool, data.target_matrix, data.target_matrix + 1)
|
|
case .CenterView:
|
|
// TODO (synthas): Could be improved to center the whole animation
|
|
// TODO (synthas): Reset zoom
|
|
world_to_grid_matrix := grid_world_to_grid_matrix(program.grid.zoom, program.grid.offset)
|
|
grid_to_world_matrix := linalg.inverse(world_to_grid_matrix)
|
|
center := animation_get_center(program.animation) / grid_get_zoom(program.grid.zoom) - program_screen_size() / 2.0
|
|
program.grid.offset = center
|
|
case .ProjectSave:
|
|
program_save_project_from_project_string_or_open_dialog(&program)
|
|
case .ProjectSaveAs:
|
|
program_show_dialog(&program, .SaveProject)
|
|
case .ProjectLoad:
|
|
program_show_dialog(&program, .LoadProject)
|
|
case .ProjectNew:
|
|
case .DismissError:
|
|
program.state = .Normal
|
|
}
|
|
if data.type in UiButtonRecalculateFinishGroup {
|
|
program.animation.matrix_count = program.matrix_count
|
|
}
|
|
}
|
|
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
|
|
value_previous := data.value^
|
|
if rl.GuiValueBoxFloat(area, element.label, data.value_string, data.value, focus_element_id == element.id) == 1 {
|
|
focus_element_id = element.id
|
|
}
|
|
if value_previous != data.value^ && .UpdateMatrix in data.tag {
|
|
program.animation.matrix_count = program.matrix_count
|
|
}
|
|
case .InputText:
|
|
data := cast(^UiElementData_TextInput)element.data
|
|
if rl.GuiTextBox(area, data.str, i32(program.font_style.size), focus_element_id == element.id) {
|
|
focus_element_id = element.id
|
|
}
|
|
case .Label:
|
|
rl.DrawTextEx(font_style.font, element.label, rect_get_tl(area), font_style.size, font_style.spacing, color)
|
|
case .Divider:
|
|
rl.DrawRectangleRec(rect2(ui.get_element_outer_area(element)), color)
|
|
case .ToggleGroup:
|
|
data := cast(^UiElementData_ToggleGroup)element.data
|
|
rl.GuiToggleGroup(area, element.label, data.value)
|
|
case:
|
|
rl.DrawRectangleRec(area, rl.MAGENTA)
|
|
}
|
|
}
|
|
|
|
if scissor_container != nil {
|
|
rl.EndScissorMode()
|
|
scissor_container = nil
|
|
}
|
|
|
|
if len(tooltip_text) > 0 {
|
|
position := tooltip_position + program.ui_tooltip_offset
|
|
size := measure_text(tooltip_text, program.font_style)
|
|
bounds := rect_grow(rect_from(position, size), 4.0)
|
|
rl.DrawRectangleRec(bounds, rl.BLACK)
|
|
rl.DrawRectangleLinesEx(bounds, 1.0, rl.WHITE)
|
|
draw_multiline_text_centered(tooltip_text, position, program.font_style, rl.WHITE, context.temp_allocator)
|
|
}
|
|
|
|
return focus_element_id
|
|
}
|
|
|
|
ui_confirm_and_cancel_buttons :: proc(button_type_add: UiButtonType, margin: f32) {
|
|
button_data: ^UiElementData_Button
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_data^ = {type = button_type_add}
|
|
ui.element(.Button, ui.dimensions_pixels(96.0, 32.0), {label = "Add", margin = margin}, button_data)
|
|
|
|
button_data = new(UiElementData_Button, context.temp_allocator)
|
|
button_data^ = {type = .ExitDialog}
|
|
ui.same_line()
|
|
ui.element(.Button, ui.dimensions_pixels(96.0, 32.0), {label = "Cancel", margin = margin}, button_data)
|
|
}
|
|
|
|
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(idx: int, mat: []f32, value_strings: []InputFloatField, dimensions: ui.Dimensions, config: ui.ElementConfiguration, allocator := context.temp_allocator) {
|
|
ui.container_begin(dimensions, config)
|
|
defer ui.container_end()
|
|
|
|
button_config := ui.ElementConfiguration {margin = 4.0}
|
|
button_data: ^UiElementData_Button
|
|
|
|
button_config.label = RAYGUI_ICON_TRASH
|
|
button_data = new(UiElementData_Button, allocator)
|
|
button_data^ = {target_matrix = idx, type = .MatrixDelete}
|
|
ui.element(.Button, ui.dimensions_pixels(32.0), button_config, button_data)
|
|
|
|
button_config.label = RAYGUI_ICON_UP
|
|
button_data = new(UiElementData_Button, allocator)
|
|
button_data^ = {target_matrix = idx, type = .MatrixMoveUp}
|
|
ui.same_line()
|
|
ui.element(.Button, ui.dimensions_pixels(32.0), button_config, button_data)
|
|
|
|
button_config.label = RAYGUI_ICON_DOWN
|
|
button_data = new(UiElementData_Button, allocator)
|
|
button_data^ = {target_matrix = idx, type = .MatrixMoveDown}
|
|
ui.same_line()
|
|
ui.element(.Button, ui.dimensions_pixels(32.0), button_config, button_data)
|
|
|
|
matrix_container_dimensions := ui.Dimensions {ui.Size_Percentage(1.0), ui.Size_PercentageRemainder(1.0)}
|
|
matrix_container_config := ui.ElementConfiguration {margin = 4.0}
|
|
matrix_container_data := new(UiElementData_Container, allocator)
|
|
matrix_container_data^ = {type = .Matrix, thickness = 2.0, matrix_index = idx}
|
|
ui.container_begin(matrix_container_dimensions, matrix_container_config, matrix_container_data)
|
|
matrix_input_dimensions := ui.Dimensions {ui.Size_Percentage(1.0 / 3.0), ui.Size_Percentage(1.0 / 3.0)}
|
|
matrix_input_config := ui.ElementConfiguration {margin = 4.0}
|
|
// column first matrices
|
|
#unroll for j in 0..<3 {
|
|
#unroll for i in 0..<3 {
|
|
data := new(UiElementData_FloatInput, allocator)
|
|
index := i * 3 + j
|
|
data^ = {value = &mat[index], value_string = cstring(&value_strings[index][0]), tag = {.UpdateMatrix}}
|
|
ui.element(.InputFloat, matrix_input_dimensions, matrix_input_config, data)
|
|
ui.same_line()
|
|
}
|
|
program.ui_context.same_line = false
|
|
}
|
|
ui.container_end()
|
|
}
|
|
|
|
ui_labeled_input_float_element :: proc(label: cstring, margin: f32, value: ^f32, value_string: ^InputFloatField, allocator := context.temp_allocator) -> ^ui.Element {
|
|
label := fmt.ctprintf("%s: ", label)
|
|
label_element := ui.element(.Label, {}, {label = label, color = ui.col(rl.WHITE), margin = margin})
|
|
dimensions := ui.Dimensions{ui.Size_Pixels(64.0), ui.dimensions_pixels(ui.rect_get_size(ui.get_element_outer_area(label_element^))).height}
|
|
ui.same_line()
|
|
input_data := new(UiElementData_FloatInput, context.temp_allocator)
|
|
input_data^ = {value = value, value_string = cstring(&value_string[0])}
|
|
return ui.element(.InputFloat, dimensions, {margin = margin}, input_data)
|
|
}
|
|
|
|
ui_labeled_input_text_element :: proc(label: cstring, margin: f32, str: []byte, allocator := context.temp_allocator) -> ^ui.Element {
|
|
label := fmt.ctprintf("%s: ", label)
|
|
label_element := ui.element(.Label, {}, {label = label, color = ui.col(rl.WHITE), margin = margin})
|
|
dimensions := ui.Dimensions{ui.Size_Pixels(64.0), ui.dimensions_pixels(ui.rect_get_size(ui.get_element_outer_area(label_element^))).height}
|
|
ui.same_line()
|
|
input_data := new(UiElementData_TextInput, context.temp_allocator)
|
|
input_data^ = {str = cstring(&str[0])}
|
|
return ui.element(.InputText, dimensions, {margin = margin}, input_data)
|
|
}
|