diff --git a/TODO.MD b/TODO.MD index 356bedc..b6eae12 100644 --- a/TODO.MD +++ b/TODO.MD @@ -1,6 +1,11 @@ +- Playback slider +- Icons for saving +- Set bigger font size - Save grid and animation data to file -- Rotation matrix math is mathing wrong +- Fix focus on submenu - Centered justification in UI +- Get a more legible theme + consistency +- Fix delay on control points +- Error handling (dialog with message) - Make web build -- Center line - Zoom sensitivity diff --git a/default b/default index edf5f1d..b2191f8 100755 Binary files a/default and b/default differ diff --git a/grid.odin b/grid.odin index d6655c6..d0267bf 100644 --- a/grid.odin +++ b/grid.odin @@ -6,7 +6,7 @@ import rl "libraries/raylib" Grid :: struct { cell_size_px: v2, zoom_previous, zoom: v2, - offset: v2, + world_position, world_size, offset: v2, } grid_get_zoom :: proc(zoom: v2) -> v2 { @@ -44,22 +44,20 @@ grid_world_to_grid_matrix :: proc(zoom, offset: v2) -> mat3 { linalg.matrix3_scale(v3{**zoom_amount, 1.0})) } -draw_grid :: proc(grid: Grid, area, text_draw, text_clip: rect2) { +draw_grid :: proc(grid: Grid, text_draw, text_clip: rect2) { world_to_grid_matrix := grid_world_to_grid_matrix(grid.zoom, grid.offset) grid_to_world_matrix := linalg.inverse(world_to_grid_matrix) - position := rect_get_tl(area) - size := rect_get_size(area) - grid_world_br := position + size + grid_world_br := grid.world_position + grid.world_size grid_color := rl.Color{64, 64, 64, 255} sep := grid_get_line_separation(grid.zoom, grid.cell_size_px) - tl := (v3{**position, 1.0} * world_to_grid_matrix).xy + tl := (v3{**grid.world_position, 1.0} * world_to_grid_matrix).xy tl = linalg.floor(tl / sep) * sep br := (v3{**grid_world_br, 1.0} * world_to_grid_matrix).xy for x: f32 = tl.x; x < br.x; x += sep.x { world_position_x := (v3{x, 0.0, 1.0} * grid_to_world_matrix).x - p1 := v2{world_position_x, position.y} + p1 := v2{world_position_x, grid.world_position.y} p2 := v2{world_position_x, grid_world_br.y} rl.DrawLineV(p1, p2, grid_color) if p1.x >= text_clip.x && p1.x < text_clip.x + text_clip.width { @@ -69,7 +67,7 @@ draw_grid :: proc(grid: Grid, area, text_draw, text_clip: rect2) { for y: f32 = tl.y; y < br.y; y += sep.y { world_position_y := (v3{0.0, y, 1.0} * grid_to_world_matrix).y - p1 := v2{position.x, world_position_y} + p1 := v2{grid.world_position.x, world_position_y} p2 := v2{grid_world_br.x, world_position_y} rl.DrawLineV(p1, p2, grid_color) if p1.y >= text_clip.y && p1.y < text_clip.y + text_clip.height { diff --git a/libraries/snths_ui/ui.odin b/libraries/snths_ui/ui.odin index d09343b..badf8cc 100644 --- a/libraries/snths_ui/ui.odin +++ b/libraries/snths_ui/ui.odin @@ -63,7 +63,7 @@ ElementType :: enum { Slider, InputFloat, InputText, - ToggleGroup, + Dropdown, Label, Divider, Custom0, Custom1, Custom2, Custom3, Custom4, Custom5, Custom6, Custom7, Custom8, Custom9, diff --git a/program.odin b/program.odin index 73daa94..8d6d092 100644 --- a/program.odin +++ b/program.odin @@ -57,7 +57,7 @@ UiElementData_TextInput :: struct { str: cstring, } -UiElementData_ToggleGroup :: struct { +UiElementData_Dropdown :: struct { value: ^i32, } @@ -223,11 +223,6 @@ MouseDraggable :: union { MouseDraggable_RectangleData, } -RotationMode :: enum { - Degrees, - Radians, -} - Program :: struct { ui_context: ui.Context, ui_element_focus: ui.ElementId, @@ -246,7 +241,6 @@ Program :: struct { 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, @@ -363,7 +357,8 @@ program_init :: proc(allocator := context.allocator) { program.grid = { cell_size_px = 40.0, zoom = 1.0, - offset = -program_screen_size() / 2.0, + world_position = 0.0, + world_size = program_screen_size() } program.grid_zoom_speed = 1.0 program.mouse_draggable_radius = 6.0 @@ -464,10 +459,6 @@ program_update :: proc() { } 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) } @@ -506,9 +497,9 @@ program_update :: proc() { top_bar_container_br := rect_get_br(rect2(top_bar_container.area)) matrix_container_br := rect_get_br(rect2(matrix_container.area)) + program.grid.world_size = program_screen_size() 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)) + draw_grid(program.grid, rect_grow(grid_text_clip, -4.0), rect_grow(grid_text_clip, -32.0)) { animation := &program.animation @@ -565,6 +556,11 @@ program_update :: proc() { program.font_style, ui_overlay_elements) } + + 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) + } } program_add_identity_matrix :: proc() { @@ -603,11 +599,6 @@ program_add_scalar_matrix :: proc(x, y, z: f32) { 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 - } rotmat := linalg.matrix2_rotate_f32(angle) fmt.bprint(value_strings[0][:], rotmat[0, 0]) values[0] = rotmat[0, 0] @@ -617,8 +608,6 @@ program_add_rotation_matrix :: proc(angle: f32) { values[3] = rotmat[1, 0] fmt.bprint(value_strings[4][:], rotmat[1, 1]) values[4] = rotmat[1, 1] - fmt.bprint(value_strings[8][:], 1) - values[8] = 1.0 } program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType) -> []ui.Element { @@ -655,12 +644,6 @@ program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType 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][:]) @@ -688,8 +671,7 @@ program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, 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_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} @@ -699,7 +681,6 @@ program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, 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)} @@ -791,22 +772,6 @@ program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, 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() @@ -947,7 +912,7 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u 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) + rl.DrawRectangleRec(rect2(ui.get_element_outer_area(element)), color) matrix_area := ui.rect_grow(ui.rect2(area), element.margin / 2.0) tl, tr, br, bl := ui.rect_get_corners(matrix_area) @@ -1048,7 +1013,7 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u // 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 + center := animation_get_center(program.animation) / grid_get_zoom(program.grid.zoom) - program.grid.world_size / 2.0 program.grid.offset = center case .ProjectSave: program_save_project_from_project_string_or_open_dialog(&program) @@ -1081,13 +1046,24 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u 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 + if focus_element_id == element.id { + render_above = element + } else { + if rl.GuiDropdownBox(area, element.label, data.value, focus_element_id == element.id) { + if focus_element_id == element.id { + focus_element_id = ui.element_id_invalid() + } else { + 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) } @@ -1098,6 +1074,20 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u 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 + } + } + } + } + if len(tooltip_text) > 0 { position := tooltip_position + program.ui_tooltip_offset size := measure_text(tooltip_text, program.font_style)