diff --git a/libraries/snths_ui/ui.odin b/libraries/snths_ui/ui.odin index 04a67d1..576c045 100644 --- a/libraries/snths_ui/ui.odin +++ b/libraries/snths_ui/ui.odin @@ -60,9 +60,19 @@ ElementType :: enum { Button, Slider, InputFloat, + InputText, Dropdown, 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} @@ -83,10 +93,25 @@ Dimensions :: struct { width, height: Size, } -dimensions_pixels :: proc(v: [2]f32) -> Dimensions {return {Size_Pixels(v.x), Size_Pixels(v.y)}} -dimensions_percentage :: proc(v: [2]f32) -> Dimensions {return {Size_Percentage(v.x), Size_Percentage(v.y)}} -dimensions_percentage_remainder :: proc(v: [2]f32) -> Dimensions {return {Size_PercentageRemainder(v.x), Size_PercentageRemainder(v.y)}} -dimensions_auto :: proc() -> Dimensions {return {SIZE_AUTO, SIZE_AUTO}} +dimensions_pixels :: proc { + dimensions_pixels_size, + dimensions_pixels_v, + 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} @@ -161,16 +186,16 @@ container_end :: proc() { br = {max(br.x, element_br.x), max(br.y, element_br.y)} } 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 { - container.area.height = br.y - container.area.y + container.padding.y + container.area.height = br.y - container.area.y } } ctx.same_line = false } -assert_auto_dimension_with_pixels :: proc(dimensions: Dimensions) { +assert_auto_with_pixels :: proc(dimensions: Dimensions) { container := _container_current() if _, is_auto := container.dimensions.width.(Size_Auto); is_auto { _, 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 { 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.area = _resolve_element_area(element, is_text = (element.type == .Label)) return _append_element(element) diff --git a/program.odin b/program.odin index 14784ca..7df8a4e 100644 --- a/program.odin +++ b/program.odin @@ -39,6 +39,10 @@ UiElementData_FloatInput :: struct { tag: bit_set[enum{UpdateMatrix}], } +UiElementData_TextInput :: struct { + str: cstring, +} + UiElementData_Dropdown :: struct { value: ^i32, } @@ -56,6 +60,7 @@ UiButtonType :: enum { PauseTransformation, ResumeTransformation, ResetTransformation, + SaveProjectConfirm, } UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{ @@ -79,11 +84,21 @@ MatrixTypes :: enum { Rotation, } -@rodata matrix_add_hotkeys := [MatrixTypes]rl.KeyboardKey { - .Identity = rl.KeyboardKey.I, - .Translation = rl.KeyboardKey.T, - .Scale = rl.KeyboardKey.S, - .Rotation = rl.KeyboardKey.R, +@rodata hotkey_without_modifier := [MatrixTypes]rl.KeyboardKey { + .Identity = .I, + .Translation = .T, + .Scale = .S, + .Rotation = .R, +} + +HotkeyWithModifier :: enum { + Save, + Open, +} + +@rodata hotkey_with_modifier := [HotkeyWithModifier]rl.KeyboardKey { + .Save = .S, + .Open = .O, } FontStyle :: struct { @@ -107,12 +122,16 @@ ProgramDialogType :: enum { CreateTranslationMatrix, CreateScaleMatrix, CreateRotationMatrix, + SaveProject, + LoadProject, } @rodata program_dialog_messages := [ProgramDialogType]cstring { .CreateTranslationMatrix = "Create tranlation matrix", .CreateScaleMatrix = "Create scale matrix", .CreateRotationMatrix = "Create rotation matrix", + .SaveProject = "Save project", + .LoadProject = "Load project", } ProgramMessageType :: enum { @@ -129,9 +148,12 @@ 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_value_strings: [DIALOG_STATE_INPUT_FIELD_COUNT]InputFloatField, - input_values: [DIALOG_STATE_INPUT_FIELD_COUNT]f32, + 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, } Program :: struct { @@ -202,11 +224,6 @@ program_show_dialog :: proc(program: ^Program, dialog_type: ProgramDialogType) { 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) @@ -279,21 +296,33 @@ program_update :: proc() { if rl.IsKeyPressed(.TAB) { 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.IsKeyPressed(hotkey) { - switch index { - 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 + if rl.IsKeyDown(.LEFT_CONTROL) { + for hotkey, index in hotkey_with_modifier { + if rl.IsKeyPressed(hotkey) { + switch index { + case .Save: + program_show_dialog(&program, .SaveProject) + case .Open: + } + } + } + } else { + for hotkey, index in hotkey_without_modifier { + if rl.IsKeyPressed(hotkey) { + switch index { + 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) } if rl.IsKeyDown(.ENTER) { - vals := program.dialog_data.input_values + 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: + case .LoadProject: } animation_update_last(&program.animation, program_get_master_matrix()) 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) } +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 { size := v2{WINDOW_WIDTH, WINDOW_HEIGHT} container_margin := size * 0.33 element_margin: f32 = 4.0 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.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 { 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_values[0], &dialog_data.input_value_strings[0]) - 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_values[2], &dialog_data.input_value_strings[2]) - + 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_add_matrix_footer_buttons(button_type, element_margin) 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_values[0], &dialog_data.input_value_strings[0]) + ui_labeled_input_float_element("Angle", 4.0, &dialog_data.input_float_values[0], &dialog_data.input_float_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() 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) { ui.start(ctx, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT}) 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 .AddRotation: program_show_dialog(&program, .CreateRotationMatrix) 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.state = .Normal 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.state = .Normal 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 case .ExitDialog: 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 case .ResetTransformation: 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 { 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 { 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: assert(element.data != nil, "Element data cannot be nil when creating a dropdown") data := cast(^UiElementData_Dropdown)element.data diff --git a/project.odin b/project.odin new file mode 100644 index 0000000..9a4824d --- /dev/null +++ b/project.odin @@ -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 +}