Saving projects

This commit is contained in:
Synthasmagoria 2026-08-18 00:57:52 +02:00
commit 64735fe60e
3 changed files with 182 additions and 63 deletions

View file

@ -60,9 +60,19 @@ ElementType :: enum {
Button, Button,
Slider, Slider,
InputFloat, InputFloat,
InputText,
Dropdown, Dropdown,
Label, Label,
Custom0, Custom1, Custom2, Custom3, Custom4, Custom5, Custom6, Custom7, Custom8, Custom9, Custom0,
Custom1,
Custom2,
Custom3,
Custom4,
Custom5,
Custom6,
Custom7,
Custom8,
Custom9,
} }
@rodata element_type_input := bit_set[ElementType] {.InputFloat} @rodata element_type_input := bit_set[ElementType] {.InputFloat}
@ -83,10 +93,25 @@ Dimensions :: struct {
width, height: Size, width, height: Size,
} }
dimensions_pixels :: proc(v: [2]f32) -> Dimensions {return {Size_Pixels(v.x), Size_Pixels(v.y)}} dimensions_pixels :: proc {
dimensions_percentage :: proc(v: [2]f32) -> Dimensions {return {Size_Percentage(v.x), Size_Percentage(v.y)}} dimensions_pixels_size,
dimensions_percentage_remainder :: proc(v: [2]f32) -> Dimensions {return {Size_PercentageRemainder(v.x), Size_PercentageRemainder(v.y)}} dimensions_pixels_v,
dimensions_auto :: proc() -> Dimensions {return {SIZE_AUTO, SIZE_AUTO}} dimensions_pixels_width_height,
}
dimensions_pixels_width_height :: proc(w, h: Size_Pixels) -> Dimensions {return {w, h}}
dimensions_pixels_v :: proc(v: v2) -> Dimensions {return {Size_Pixels(v.x), Size_Pixels(v.y)}}
dimensions_pixels_size :: proc(s: Size_Pixels) -> Dimensions {return {s, s}}
dimensions_percentage :: proc {
dimensions_percentage_size,
dimensions_percentage_v,
dimensions_percentage_width_height,
}
dimensions_percentage_width_height :: proc(w, h: Size_Percentage) -> Dimensions {return {w, h}}
dimensions_percentage_v :: proc(v: v2) -> Dimensions {return {Size_Percentage(v.x), Size_Percentage(v.y)}}
dimensions_percentage_size :: proc(s: Size_Percentage) -> Dimensions {return {s, s}}
dimensions_auto :: proc() -> Dimensions {return {Size_Auto(true), Size_Auto(true)}}
Axis :: enum {X, Y} Axis :: enum {X, Y}
@ -161,16 +186,16 @@ container_end :: proc() {
br = {max(br.x, element_br.x), max(br.y, element_br.y)} br = {max(br.x, element_br.x), max(br.y, element_br.y)}
} }
if width_is_auto { if width_is_auto {
container.area.width = br.x - container.area.x + container.padding.x container.area.width = br.x - container.area.x
} }
if height_is_auto { if height_is_auto {
container.area.height = br.y - container.area.y + container.padding.y container.area.height = br.y - container.area.y
} }
} }
ctx.same_line = false ctx.same_line = false
} }
assert_auto_dimension_with_pixels :: proc(dimensions: Dimensions) { assert_auto_with_pixels :: proc(dimensions: Dimensions) {
container := _container_current() container := _container_current()
if _, is_auto := container.dimensions.width.(Size_Auto); is_auto { if _, is_auto := container.dimensions.width.(Size_Auto); is_auto {
_, is_pixels := dimensions.width.(Size_Pixels) _, is_pixels := dimensions.width.(Size_Pixels)
@ -184,7 +209,7 @@ assert_auto_dimension_with_pixels :: proc(dimensions: Dimensions) {
element :: proc(element_type: ElementType, dimensions: Dimensions, config: ElementConfiguration, data: rawptr = nil) -> ^Element { element :: proc(element_type: ElementType, dimensions: Dimensions, config: ElementConfiguration, data: rawptr = nil) -> ^Element {
assert(element_type != .Container) assert(element_type != .Container)
assert_auto_dimension_with_pixels(dimensions) assert_auto_with_pixels(dimensions)
element := Element{config = config, type = element_type, dimensions = dimensions, data = data} element := Element{config = config, type = element_type, dimensions = dimensions, data = data}
element.area = _resolve_element_area(element, is_text = (element.type == .Label)) element.area = _resolve_element_area(element, is_text = (element.type == .Label))
return _append_element(element) return _append_element(element)

View file

@ -39,6 +39,10 @@ UiElementData_FloatInput :: struct {
tag: bit_set[enum{UpdateMatrix}], tag: bit_set[enum{UpdateMatrix}],
} }
UiElementData_TextInput :: struct {
str: cstring,
}
UiElementData_Dropdown :: struct { UiElementData_Dropdown :: struct {
value: ^i32, value: ^i32,
} }
@ -56,6 +60,7 @@ UiButtonType :: enum {
PauseTransformation, PauseTransformation,
ResumeTransformation, ResumeTransformation,
ResetTransformation, ResetTransformation,
SaveProjectConfirm,
} }
UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{ UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{
@ -79,11 +84,21 @@ MatrixTypes :: enum {
Rotation, Rotation,
} }
@rodata matrix_add_hotkeys := [MatrixTypes]rl.KeyboardKey { @rodata hotkey_without_modifier := [MatrixTypes]rl.KeyboardKey {
.Identity = rl.KeyboardKey.I, .Identity = .I,
.Translation = rl.KeyboardKey.T, .Translation = .T,
.Scale = rl.KeyboardKey.S, .Scale = .S,
.Rotation = rl.KeyboardKey.R, .Rotation = .R,
}
HotkeyWithModifier :: enum {
Save,
Open,
}
@rodata hotkey_with_modifier := [HotkeyWithModifier]rl.KeyboardKey {
.Save = .S,
.Open = .O,
} }
FontStyle :: struct { FontStyle :: struct {
@ -107,12 +122,16 @@ ProgramDialogType :: enum {
CreateTranslationMatrix, CreateTranslationMatrix,
CreateScaleMatrix, CreateScaleMatrix,
CreateRotationMatrix, CreateRotationMatrix,
SaveProject,
LoadProject,
} }
@rodata program_dialog_messages := [ProgramDialogType]cstring { @rodata program_dialog_messages := [ProgramDialogType]cstring {
.CreateTranslationMatrix = "Create tranlation matrix", .CreateTranslationMatrix = "Create tranlation matrix",
.CreateScaleMatrix = "Create scale matrix", .CreateScaleMatrix = "Create scale matrix",
.CreateRotationMatrix = "Create rotation matrix", .CreateRotationMatrix = "Create rotation matrix",
.SaveProject = "Save project",
.LoadProject = "Load project",
} }
ProgramMessageType :: enum { ProgramMessageType :: enum {
@ -129,9 +148,12 @@ InputFloatField :: [32]byte
MATRIX_POOL_SIZE :: 4096 MATRIX_POOL_SIZE :: 4096
DIALOG_STATE_INPUT_FIELD_COUNT :: 16 DIALOG_STATE_INPUT_FIELD_COUNT :: 16
DIALOG_STATE_TEXT_INPUT_FIELD_SIZE :: 256
DIALOG_STATE_TEXT_INPUT_FIELD_COUNT :: 8
ProgramData_DialogState :: struct { ProgramData_DialogState :: struct {
input_value_strings: [DIALOG_STATE_INPUT_FIELD_COUNT]InputFloatField, input_float_value_strings: [DIALOG_STATE_INPUT_FIELD_COUNT]InputFloatField,
input_values: [DIALOG_STATE_INPUT_FIELD_COUNT]f32, 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,
} }
Program :: struct { Program :: struct {
@ -202,11 +224,6 @@ program_show_dialog :: proc(program: ^Program, dialog_type: ProgramDialogType) {
program.dialog_type = dialog_type 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_init :: proc(allocator := context.allocator) {
program.matrix_float_pool = make(type_of(program.matrix_float_pool), MATRIX_POOL_SIZE) 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_value_string_pool = make(type_of(program.matrix_value_string_pool), MATRIX_POOL_SIZE)
@ -279,21 +296,33 @@ program_update :: proc() {
if rl.IsKeyPressed(.TAB) { if rl.IsKeyPressed(.TAB) {
program.ui_element_focus = ui.focus_next_element(program.ui_element_focus, ui_elements, UI_ALLOWED_FOCUS) program.ui_element_focus = ui.focus_next_element(program.ui_element_focus, ui_elements, UI_ALLOWED_FOCUS)
} }
for hotkey, index in matrix_add_hotkeys { if rl.IsKeyDown(.LEFT_CONTROL) {
if rl.IsKeyPressed(hotkey) { for hotkey, index in hotkey_with_modifier {
switch index { if rl.IsKeyPressed(hotkey) {
case .Identity: switch index {
program_add_identity_matrix() case .Save:
program.ui_overlay_queue_focus_first = true program_show_dialog(&program, .SaveProject)
case .Translation: case .Open:
program_show_dialog(&program, .CreateTranslationMatrix) }
program.ui_overlay_queue_focus_first = true }
case .Scale: }
program_show_dialog(&program, .CreateScaleMatrix) } else {
program.ui_overlay_queue_focus_first = true for hotkey, index in hotkey_without_modifier {
case .Rotation: if rl.IsKeyPressed(hotkey) {
program_show_dialog(&program, .CreateRotationMatrix) switch index {
program.ui_overlay_queue_focus_first = true case .Identity:
program_add_identity_matrix()
program.ui_overlay_queue_focus_first = true
case .Translation:
program_show_dialog(&program, .CreateTranslationMatrix)
program.ui_overlay_queue_focus_first = true
case .Scale:
program_show_dialog(&program, .CreateScaleMatrix)
program.ui_overlay_queue_focus_first = true
case .Rotation:
program_show_dialog(&program, .CreateRotationMatrix)
program.ui_overlay_queue_focus_first = true
}
} }
} }
} }
@ -304,11 +333,13 @@ program_update :: proc() {
program.ui_overlay_element_focus = ui.focus_next_element(program.ui_overlay_element_focus, ui_overlay_elements, UI_ALLOWED_FOCUS) program.ui_overlay_element_focus = ui.focus_next_element(program.ui_overlay_element_focus, ui_overlay_elements, UI_ALLOWED_FOCUS)
} }
if rl.IsKeyDown(.ENTER) { if rl.IsKeyDown(.ENTER) {
vals := program.dialog_data.input_values vals := program.dialog_data.input_float_values
switch program.dialog_type { switch program.dialog_type {
case .CreateTranslationMatrix: program_add_translation_matrix(vals[0], vals[1], vals[2]) 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 .CreateScaleMatrix: program_add_scalar_matrix(vals[0], vals[1], vals[2])
case .CreateRotationMatrix: program_add_rotation_matrix(vals[0]) case .CreateRotationMatrix: program_add_rotation_matrix(vals[0])
case .SaveProject:
case .LoadProject:
} }
animation_update_last(&program.animation, program_get_master_matrix()) animation_update_last(&program.animation, program_get_master_matrix())
program.state = .Normal program.state = .Normal
@ -416,45 +447,61 @@ ui_labeled_input_float_element :: proc(label: cstring, margin: f32, value: ^f32,
return ui.element(.InputFloat, dimensions, {margin = margin}, input_data) 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)
}
program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType) -> []ui.Element { program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType) -> []ui.Element {
size := v2{WINDOW_WIDTH, WINDOW_HEIGHT} size := v2{WINDOW_WIDTH, WINDOW_HEIGHT}
container_margin := size * 0.33 container_margin := size * 0.33
element_margin: f32 = 4.0 element_margin: f32 = 4.0
dialog_data := &program.dialog_data dialog_data := &program.dialog_data
dialog_type_to_button_type := [ProgramDialogType]UiButtonType {
.CreateTranslationMatrix = .AddTranslationMatrixConfirm,
.CreateScaleMatrix = .AddScaleMatrixConfirm,
.CreateRotationMatrix = .AddRotationMatrixConfirm,
}
button_type := dialog_type_to_button_type[program.dialog_type]
ui.start(ctx, V2_ZERO, {WINDOW_WIDTH, WINDOW_HEIGHT}) ui.start(ctx, V2_ZERO, {WINDOW_WIDTH, WINDOW_HEIGHT})
ui.container_begin(ui.dimensions_auto(), {margin = container_margin, padding = 8.0}) ui.container_begin(ui.dimensions_auto(), {margin = container_margin, padding = 8.0})
ui.element(.Label, {}, {label = program_dialog_messages[program.dialog_type], color = ui.col(rl.WHITE), margin = element_margin})
switch dialog_type { switch dialog_type {
case .CreateTranslationMatrix, .CreateScaleMatrix: case .CreateTranslationMatrix, .CreateScaleMatrix:
ui.element(.Label, {}, {label = program_dialog_messages[program.dialog_type], color = ui.col(rl.WHITE), margin = element_margin}) 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("X", 4.0, &dialog_data.input_values[0], &dialog_data.input_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("Y", 4.0, &dialog_data.input_values[1], &dialog_data.input_value_strings[1]) ui_labeled_input_float_element("Z", 4.0, &dialog_data.input_float_values[2], &dialog_data.input_float_value_strings[2])
ui_labeled_input_float_element("Z", 4.0, &dialog_data.input_values[2], &dialog_data.input_value_strings[2]) button_type: UiButtonType
if dialog_type == .CreateTranslationMatrix {
button_type = .AddTranslationMatrixConfirm
} else {
button_type = .AddScaleMatrixConfirm
}
ui_add_matrix_footer_buttons(button_type, element_margin)
case .CreateRotationMatrix: case .CreateRotationMatrix:
ui.element(.Label, {}, {label = program_dialog_messages[program.dialog_type], color = ui.col(rl.WHITE), margin = element_margin}) ui_labeled_input_float_element("Angle", 4.0, &dialog_data.input_float_values[0], &dialog_data.input_float_value_strings[0])
ui_labeled_input_float_element("Angle", 4.0, &dialog_data.input_values[0], &dialog_data.input_value_strings[0]) ui_add_matrix_footer_buttons(.AddRotationMatrixConfirm, element_margin)
case .SaveProject:
ui_labeled_input_text_element("Path", 4.0, dialog_data.input_text_buffers[0][:])
ui_add_matrix_footer_buttons(.SaveProjectConfirm, element_margin)
case .LoadProject:
} }
button_data: ^UiElementData_Button
button_data = new(UiElementData_Button, context.temp_allocator)
button_data^ = {type = button_type}
ui.element(.Button, ui.dimensions_pixels({96.0, 32.0}), {label = "Add", margin = element_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 = element_margin}, button_data)
ui.container_end() ui.container_end()
return ui.end() return ui.end()
} }
ui_add_matrix_footer_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)
}
program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, matrix_container: ^ui.Element) { program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, matrix_container: ^ui.Element) {
ui.start(ctx, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT}) ui.start(ctx, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT})
top_bar_container_config := ui.ElementConfiguration {margin = 4.0} top_bar_container_config := ui.ElementConfiguration {margin = 4.0}
@ -617,15 +664,15 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u
case .AddScale: program_show_dialog(&program, .CreateScaleMatrix) case .AddScale: program_show_dialog(&program, .CreateScaleMatrix)
case .AddRotation: program_show_dialog(&program, .CreateRotationMatrix) case .AddRotation: program_show_dialog(&program, .CreateRotationMatrix)
case .AddTranslationMatrixConfirm: case .AddTranslationMatrixConfirm:
inputs := program.dialog_data.input_values inputs := program.dialog_data.input_float_values
program_add_translation_matrix(inputs[0], inputs[1], inputs[2]) program_add_translation_matrix(inputs[0], inputs[1], inputs[2])
program.state = .Normal program.state = .Normal
case .AddScaleMatrixConfirm: case .AddScaleMatrixConfirm:
inputs := program.dialog_data.input_values inputs := program.dialog_data.input_float_values
program_add_scalar_matrix(inputs[0], inputs[1], inputs[2]) program_add_scalar_matrix(inputs[0], inputs[1], inputs[2])
program.state = .Normal program.state = .Normal
case .AddRotationMatrixConfirm: case .AddRotationMatrixConfirm:
program_add_rotation_matrix(program.dialog_data.input_values[0]) program_add_rotation_matrix(program.dialog_data.input_float_values[0])
program.state = .Normal program.state = .Normal
case .ExitDialog: case .ExitDialog:
program.state = .Normal program.state = .Normal
@ -641,6 +688,16 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u
program.animation_state = .Playing program.animation_state = .Playing
case .ResetTransformation: case .ResetTransformation:
animation_reset(&program.animation) animation_reset(&program.animation)
case .SaveProjectConfirm:
project := ProjectData {
matrix_count = u32(program.matrix_count),
matrices = program.matrix_float_pool,
}
if error := project_write(&project, string(program.dialog_data.input_text_buffers[0][:])); error != nil {
// TODO (synthas): Generic error message
} else {
program.state = .Normal
}
} }
if data.type in UiButtonRecalculateFinishGroup { if data.type in UiButtonRecalculateFinishGroup {
animation_update_last(&program.animation, program_get_master_matrix()) animation_update_last(&program.animation, program_get_master_matrix())
@ -658,6 +715,11 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u
if value_previous != data.value^ && .UpdateMatrix in data.tag { if value_previous != data.value^ && .UpdateMatrix in data.tag {
animation_update_last(&program.animation, program_get_master_matrix()) animation_update_last(&program.animation, program_get_master_matrix())
} }
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 .Dropdown: case .Dropdown:
assert(element.data != nil, "Element data cannot be nil when creating a dropdown") assert(element.data != nil, "Element data cannot be nil when creating a dropdown")
data := cast(^UiElementData_Dropdown)element.data data := cast(^UiElementData_Dropdown)element.data

32
project.odin Normal file
View file

@ -0,0 +1,32 @@
package main
import "core:os"
import "core:io"
PROJECT_DATA_HEADER :: "WOWOWOW THIS IS HEADER WOWOWOW"
PROJECT_DATA_VERSION :: 1
ProjectData :: struct {
version: u32,
matrix_count: u32,
matrices: []f32,
}
WriteProjectError :: union {
os.Error,
}
WriteProjectErrorType :: enum {
IncompatibleVersion,
}
project_write :: proc(project: ^ProjectData, path: string) -> os.Error {
f := os.open(path, {.Create, .Trunc, .Write}) or_return
w := io.to_writer(os.to_stream(f))
version: u32 = PROJECT_DATA_VERSION
io.write_ptr(w, &version, size_of(version))
io.write_ptr(w, &project.matrix_count, size_of(project.matrix_count))
io.write_ptr(w, raw_data(project.matrices), int(project.matrix_count) * 9 * size_of(f32))
os.close(f)
return nil
}