diff --git a/TODO.MD b/TODO.MD index 1a68354..56e62e9 100644 --- a/TODO.MD +++ b/TODO.MD @@ -1,7 +1,13 @@ - Centered justification in UI -- Web build - Web read/write -- Remove default project load error message on startup +- Ship with default setup +- Fix -vet errors +- Snap to grid option +- Set grid +- Set animation value + +DONE: - When there is no matrix it should still draw the rectangle -- Keyboard input doesnt work on web -- Resizing doesn't work on web +- Always show coordinates +- Web build +- Escape closes dialogue if on dialogue. Otherwise stops program if on desktop, but not without an unsaved changes warning. diff --git a/src/animation.odin b/src/animation.odin index 45e4cb0..f3a462a 100644 --- a/src/animation.odin +++ b/src/animation.odin @@ -196,10 +196,15 @@ animation_draw :: proc(animation: ^Animation, global_transform: mat3, mode: Anim transform *= mat rectangles[i + 1] = rectangle_corners_transform_mat3(transform, animation.shapes.rectangle) } - for rectangle, i in rectangles { - color := linalg.lerp(color_origin, color_end, f32(i) / f32(animation.matrix_count)) - rectangle_world := rectangle_corners_transform_mat3(global_transform, rectangle) - draw_animation_rectangle_corners(rectangle_world, rl.Color(color)) + if animation.matrix_count == 0 { + rectangle_world := rectangle_corners_transform_mat3(global_transform, rectangles[0]) + draw_animation_rectangle_corners(rectangle_world, ANIMATION_COLOR_ORIGIN) + } else { + for rectangle, i in rectangles { + color := linalg.lerp(color_origin, color_end, f32(i) / f32(animation.matrix_count)) + rectangle_world := rectangle_corners_transform_mat3(global_transform, rectangle) + draw_animation_rectangle_corners(rectangle_world, rl.Color(color)) + } } if animation.matrix_count <= 0 { break diff --git a/src/libraries/raylib/raylib/src/raylib.h b/src/libraries/raylib/raylib/src/raylib.h index d2a5242..0dc2091 100644 --- a/src/libraries/raylib/raylib/src/raylib.h +++ b/src/libraries/raylib/raylib/src/raylib.h @@ -996,6 +996,7 @@ RLAPI bool IsWindowMaximized(void); // Check if wi RLAPI bool IsWindowFocused(void); // Check if window is currently focused RLAPI bool IsWindowResized(void); // Check if window has been resized last frame RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI bool IsWindowCloseFlagSet(); // Check if the close flag has been set RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution diff --git a/src/libraries/raylib/raylib/src/rcore.c b/src/libraries/raylib/raylib/src/rcore.c index b1b360d..9394f3f 100644 --- a/src/libraries/raylib/raylib/src/rcore.c +++ b/src/libraries/raylib/raylib/src/rcore.c @@ -794,6 +794,11 @@ bool IsWindowState(unsigned int flag) return FLAG_IS_SET(CORE.Window.flags, flag); } +bool IsWindowCloseFlagSet() +{ + return CORE.Window.shouldClose; +} + // Get current screen width int GetScreenWidth(void) { diff --git a/src/main.odin b/src/main.odin index 36d71f8..eee01c2 100644 --- a/src/main.odin +++ b/src/main.odin @@ -13,6 +13,7 @@ init :: proc() { rl.SetConfigFlags({.VSYNC_HINT}) rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) rl.SetTargetFPS(WINDOW_FRAMERATE) + rl.SetExitKey(.KEY_NULL) program_init() } @@ -34,7 +35,7 @@ shutdown :: proc() { should_run :: proc() -> bool { when ODIN_OS != .JS { - if rl.WindowShouldClose() { + if program.window_should_close || rl.WindowShouldClose() { return false } } diff --git a/src/program.odin b/src/program.odin index 7dedbf3..fddc874 100644 --- a/src/program.odin +++ b/src/program.odin @@ -119,6 +119,7 @@ UiButtonType :: enum { MatrixMoveUp, MatrixMoveDown, CenterView, + CloseProgram, } UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{ @@ -191,6 +192,7 @@ ProgramDialogType :: enum { CreateSkewMatrix, SaveProject, LoadProject, + CloseUnsavedProjectPrompt, Error, } @@ -201,6 +203,7 @@ ProgramDialogType :: enum { .CreateSkewMatrix = "Create skew matrix", .SaveProject = "Save project", .LoadProject = "Load project", + .CloseUnsavedProjectPrompt = "Unsaved project", .Error = "Error", } @@ -263,11 +266,6 @@ RotationMode :: enum { Radians, } -ProgramShowAnimationCoords :: enum { - NoCoords, - Coords, -} - Program :: struct { ui_context: ui.Context, ui_element_focus: ui.ElementId, @@ -287,6 +285,7 @@ Program :: struct { mouse_draggable_radius: f32, project_save_string: [DIALOG_STATE_TEXT_INPUT_FIELD_COUNT]byte, rotation_mode: RotationMode, + saved_project_data: ProjectData, grid: Grid, grid_zoom_x_only: bool, grid_zoom_y_only: bool, @@ -301,7 +300,6 @@ Program :: struct { animation_state: ProgramAnimationState, animation_speed: f32, animation_mode: AnimationMode, - animation_show_coordinates: ProgramShowAnimationCoords, window_should_close: bool, } @@ -347,14 +345,20 @@ matrix_move_to :: proc(floats: []f32, inputs: []InputFloatField, from, to: int) } program_save_project :: proc(program: ^Program, path: string) -> ProjectWriteError { - project := ProjectData { + project := program_create_project_data(program^) + project_write(&project, path) or_return + program.saved_project_data = project + program.saved_project_data.matrices = slice.clone(program.saved_project_data.matrices) + return nil +} + +program_create_project_data :: proc(program: Program) -> ProjectData { + return { matrix_count = u32(program.matrix_count), matrices = program.matrix_float_pool, grid = program.grid, shapes = program.animation.shapes, } - project_write(&project, path) or_return - return nil } program_save_project_from_project_string_or_open_dialog :: proc(program: ^Program) { @@ -385,6 +389,8 @@ program_load_project :: proc(program: ^Program, path: string) -> ProjectReadErro write_matrix_to_values_and_value_strings(program.matrix_float_pool[start:end], program.matrix_value_string_pool[start:end], mat) } program.animation.matrix_count = program.matrix_count + program.saved_project_data = project + program.saved_project_data.matrices = slice.clone(program.saved_project_data.matrices) return nil } @@ -432,8 +438,6 @@ program_init :: proc(allocator := context.allocator) { 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)) } } @@ -550,6 +554,8 @@ program_update :: proc() { } else { program.state = .Normal } + case .CloseUnsavedProjectPrompt: + program.window_should_close = true case .Error: program.state = .Normal } @@ -558,6 +564,36 @@ program_update :: proc() { } } + if rl.IsKeyPressed(.ESCAPE) { + switch program.state { + case .Normal: + when ODIN_OS != .JS { + current_project_data := program_create_project_data(program) + + current_matrices_ptr := cast([^]byte)raw_data(current_project_data.matrices) + current_matrices := current_matrices_ptr[:current_project_data.matrix_count * 9 * 4] + last_saved_matrices_ptr := cast([^]byte)raw_data(program.saved_project_data.matrices) + last_saved_matrices := last_saved_matrices_ptr[:program.saved_project_data.matrix_count * 9 * 4] + + current_project_grid_ptr := cast([^]byte)¤t_project_data.grid + last_saved_grid_ptr := cast([^]byte)&program.saved_project_data.grid + + current_project_shapes_ptr := cast([^]byte)¤t_project_data.shapes + last_saved_shapes_ptr := cast([^]byte)&program.saved_project_data.shapes + + if mem.compare(current_matrices, last_saved_matrices) == 0 && + mem.compare(current_project_grid_ptr[:size_of(Grid)], last_saved_grid_ptr[:size_of(Grid)]) == 0 && + mem.compare(current_project_shapes_ptr[:size_of(AnimationShapes)], last_saved_shapes_ptr[:size_of(AnimationShapes)]) == 0 { + program.window_should_close = true + } + + program_show_dialog(&program, .CloseUnsavedProjectPrompt, "Are you sure you want to quit?"); + } + case .Dialog: + program.state = .Normal + } + } + clear(&program.mouse_draggables) top_bar_container_br := rect_get_br(rect2(top_bar_container.area)) @@ -637,10 +673,8 @@ program_update :: proc() { ui_overlay_elements) } - if (program.animation_show_coordinates == .Coords) { - shape_coords_string_position := program_screen_size() - 4.0 - draw_text_aligned(shape_coord_string, shape_coords_string_position, program.font_style, .Right, .Bottom, rl.WHITE) - } + shape_coords_string_position := program_screen_size() - 4.0 + draw_text_aligned(shape_coord_string, shape_coords_string_position, program.font_style, .Right, .Bottom, rl.WHITE) } program_add_identity_matrix :: proc() { @@ -731,7 +765,7 @@ program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType } else { button_type = .AddScaleMatrixConfirm } - ui_confirm_and_cancel_buttons(button_type, element_margin) + ui_confirm_and_cancel_buttons("Add", 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) @@ -740,17 +774,19 @@ program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType 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) + ui_confirm_and_cancel_buttons("Add", .AddRotationMatrixConfirm, element_margin) case .CreateSkewMatrix: 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_confirm_and_cancel_buttons(.AddSkewMatrixConfirm, element_margin) + ui_confirm_and_cancel_buttons("Add", .AddSkewMatrixConfirm, 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) + ui_confirm_and_cancel_buttons("Add", .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) + ui_confirm_and_cancel_buttons("Add", .ProjectLoadConfirm, element_margin) + case .CloseUnsavedProjectPrompt: + ui_confirm_and_cancel_buttons("Confirm", .CloseProgram, element_margin) case .Error: button_data: ^UiElementData_Button button_data = new(UiElementData_Button, context.temp_allocator) @@ -898,10 +934,6 @@ program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, ui.same_line() ui.element(.Slider, slider_dimensions, slider_config, slider_data) - // label_config := ui.ElementConfiguration{margin = button_margin, label = "Animation mode: ", color = ui.col(rl.WHITE)} - // ui.same_line() - // ui.element(.Label, ui.dimensions_auto(), label_config) - toggle_group_config: ui.ElementConfiguration toggle_group_dimensions := ui.Dimensions{top_bar_button_width * 3.0, ui.Size_Percentage(1.0)} toggle_group_data: ^UiElementData_ToggleGroup @@ -914,15 +946,6 @@ program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, toggle_group_data^ = {value = cast(^i32)&program.animation_mode} ui.same_line() ui.element(.ToggleGroup, toggle_group_dimensions, toggle_group_config, toggle_group_data) - - toggle_group_config = { - margin = button_margin, - label = enum_to_raygui_options_string(ProgramShowAnimationCoords, context.temp_allocator), - } - toggle_group_data = new(UiElementData_ToggleGroup, context.temp_allocator) - toggle_group_data^ = {value = cast(^i32)&program.animation_show_coordinates} - ui.same_line() - ui.element(.ToggleGroup, toggle_group_dimensions, toggle_group_config, toggle_group_data) } ui.container_end() @@ -1180,6 +1203,8 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u program.grid.cell_size_px = 50.0 case .DismissError: program.state = .Normal + case .CloseProgram: + program.window_should_close = true } if data.type in UiButtonRecalculateFinishGroup { program.animation.matrix_count = program.matrix_count @@ -1241,11 +1266,11 @@ program_render_ui_and_handle_focus :: proc(ctx: ^ui.Context, focus_element_id: u return focus_element_id } -ui_confirm_and_cancel_buttons :: proc(button_type_add: UiButtonType, margin: f32) { +ui_confirm_and_cancel_buttons :: proc(confirm_label: cstring, 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) + ui.element(.Button, ui.dimensions_pixels(96.0, 32.0), {label = confirm_label, margin = margin}, button_data) button_data = new(UiElementData_Button, context.temp_allocator) button_data^ = {type = .ExitDialog}