Quit prompt, always show coordinates, web build, always draw rect

This commit is contained in:
Synthasmagoria 2026-09-15 14:23:59 +02:00
commit 28b5fb0a42
6 changed files with 87 additions and 44 deletions

14
TODO.MD
View file

@ -1,7 +1,13 @@
- Centered justification in UI - Centered justification in UI
- Web build
- Web read/write - 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 - When there is no matrix it should still draw the rectangle
- Keyboard input doesnt work on web - Always show coordinates
- Resizing doesn't work on web - Web build
- Escape closes dialogue if on dialogue. Otherwise stops program if on desktop, but not without an unsaved changes warning.

View file

@ -196,11 +196,16 @@ animation_draw :: proc(animation: ^Animation, global_transform: mat3, mode: Anim
transform *= mat transform *= mat
rectangles[i + 1] = rectangle_corners_transform_mat3(transform, animation.shapes.rectangle) rectangles[i + 1] = rectangle_corners_transform_mat3(transform, animation.shapes.rectangle)
} }
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 { for rectangle, i in rectangles {
color := linalg.lerp(color_origin, color_end, f32(i) / f32(animation.matrix_count)) color := linalg.lerp(color_origin, color_end, f32(i) / f32(animation.matrix_count))
rectangle_world := rectangle_corners_transform_mat3(global_transform, rectangle) rectangle_world := rectangle_corners_transform_mat3(global_transform, rectangle)
draw_animation_rectangle_corners(rectangle_world, rl.Color(color)) draw_animation_rectangle_corners(rectangle_world, rl.Color(color))
} }
}
if animation.matrix_count <= 0 { if animation.matrix_count <= 0 {
break break
} }

View file

@ -996,6 +996,7 @@ RLAPI bool IsWindowMaximized(void); // Check if wi
RLAPI bool IsWindowFocused(void); // Check if window is currently focused RLAPI bool IsWindowFocused(void); // Check if window is currently focused
RLAPI bool IsWindowResized(void); // Check if window has been resized last frame 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 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 SetWindowState(unsigned int flags); // Set window configuration state using flags
RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state 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 RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution

View file

@ -794,6 +794,11 @@ bool IsWindowState(unsigned int flag)
return FLAG_IS_SET(CORE.Window.flags, flag); return FLAG_IS_SET(CORE.Window.flags, flag);
} }
bool IsWindowCloseFlagSet()
{
return CORE.Window.shouldClose;
}
// Get current screen width // Get current screen width
int GetScreenWidth(void) int GetScreenWidth(void)
{ {

View file

@ -13,6 +13,7 @@ init :: proc() {
rl.SetConfigFlags({.VSYNC_HINT}) rl.SetConfigFlags({.VSYNC_HINT})
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION)
rl.SetTargetFPS(WINDOW_FRAMERATE) rl.SetTargetFPS(WINDOW_FRAMERATE)
rl.SetExitKey(.KEY_NULL)
program_init() program_init()
} }
@ -34,7 +35,7 @@ shutdown :: proc() {
should_run :: proc() -> bool { should_run :: proc() -> bool {
when ODIN_OS != .JS { when ODIN_OS != .JS {
if rl.WindowShouldClose() { if program.window_should_close || rl.WindowShouldClose() {
return false return false
} }
} }

View file

@ -119,6 +119,7 @@ UiButtonType :: enum {
MatrixMoveUp, MatrixMoveUp,
MatrixMoveDown, MatrixMoveDown,
CenterView, CenterView,
CloseProgram,
} }
UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{ UiButtonRecalculateFinishGroup :: bit_set[UiButtonType]{
@ -191,6 +192,7 @@ ProgramDialogType :: enum {
CreateSkewMatrix, CreateSkewMatrix,
SaveProject, SaveProject,
LoadProject, LoadProject,
CloseUnsavedProjectPrompt,
Error, Error,
} }
@ -201,6 +203,7 @@ ProgramDialogType :: enum {
.CreateSkewMatrix = "Create skew matrix", .CreateSkewMatrix = "Create skew matrix",
.SaveProject = "Save project", .SaveProject = "Save project",
.LoadProject = "Load project", .LoadProject = "Load project",
.CloseUnsavedProjectPrompt = "Unsaved project",
.Error = "Error", .Error = "Error",
} }
@ -263,11 +266,6 @@ RotationMode :: enum {
Radians, Radians,
} }
ProgramShowAnimationCoords :: enum {
NoCoords,
Coords,
}
Program :: struct { Program :: struct {
ui_context: ui.Context, ui_context: ui.Context,
ui_element_focus: ui.ElementId, ui_element_focus: ui.ElementId,
@ -287,6 +285,7 @@ Program :: struct {
mouse_draggable_radius: f32, mouse_draggable_radius: f32,
project_save_string: [DIALOG_STATE_TEXT_INPUT_FIELD_COUNT]byte, project_save_string: [DIALOG_STATE_TEXT_INPUT_FIELD_COUNT]byte,
rotation_mode: RotationMode, rotation_mode: RotationMode,
saved_project_data: ProjectData,
grid: Grid, grid: Grid,
grid_zoom_x_only: bool, grid_zoom_x_only: bool,
grid_zoom_y_only: bool, grid_zoom_y_only: bool,
@ -301,7 +300,6 @@ Program :: struct {
animation_state: ProgramAnimationState, animation_state: ProgramAnimationState,
animation_speed: f32, animation_speed: f32,
animation_mode: AnimationMode, animation_mode: AnimationMode,
animation_show_coordinates: ProgramShowAnimationCoords,
window_should_close: bool, 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 { 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), matrix_count = u32(program.matrix_count),
matrices = program.matrix_float_pool, matrices = program.matrix_float_pool,
grid = program.grid, grid = program.grid,
shapes = program.animation.shapes, shapes = program.animation.shapes,
} }
project_write(&project, path) or_return
return nil
} }
program_save_project_from_project_string_or_open_dialog :: proc(program: ^Program) { 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) 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.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 return nil
} }
@ -432,8 +438,6 @@ program_init :: proc(allocator := context.allocator) {
animation_init(&program.animation, program.matrix_float_pool) animation_init(&program.animation, program.matrix_float_pool)
if err := program_load_project(&program, default_project_load_location); err != nil { 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)) 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 { } else {
program.state = .Normal program.state = .Normal
} }
case .CloseUnsavedProjectPrompt:
program.window_should_close = true
case .Error: case .Error:
program.state = .Normal 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)&current_project_data.grid
last_saved_grid_ptr := cast([^]byte)&program.saved_project_data.grid
current_project_shapes_ptr := cast([^]byte)&current_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) clear(&program.mouse_draggables)
top_bar_container_br := rect_get_br(rect2(top_bar_container.area)) top_bar_container_br := rect_get_br(rect2(top_bar_container.area))
@ -637,10 +673,8 @@ program_update :: proc() {
ui_overlay_elements) ui_overlay_elements)
} }
if (program.animation_show_coordinates == .Coords) {
shape_coords_string_position := program_screen_size() - 4.0 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) draw_text_aligned(shape_coord_string, shape_coords_string_position, program.font_style, .Right, .Bottom, rl.WHITE)
}
} }
program_add_identity_matrix :: proc() { program_add_identity_matrix :: proc() {
@ -731,7 +765,7 @@ program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType
} else { } else {
button_type = .AddScaleMatrixConfirm button_type = .AddScaleMatrixConfirm
} }
ui_confirm_and_cancel_buttons(button_type, element_margin) ui_confirm_and_cancel_buttons("Add", button_type, element_margin)
case .CreateRotationMatrix: case .CreateRotationMatrix:
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_float_values[0], &dialog_data.input_float_value_strings[0])
toggle_group_dimensions := ui.dimensions_pixels(128.0, 32.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 := new(UiElementData_ToggleGroup)
toggle_group_data^ = {value = cast(^i32)&program.rotation_mode} toggle_group_data^ = {value = cast(^i32)&program.rotation_mode}
ui.element(.ToggleGroup, toggle_group_dimensions, toggle_group_config, toggle_group_data) 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: 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("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("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: case .SaveProject:
ui_labeled_input_text_element("Path", 4.0, dialog_data.input_text_buffers[0][:]) 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: case .LoadProject:
ui_labeled_input_text_element("Path", 4.0, dialog_data.input_text_buffers[0][:]) 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: case .Error:
button_data: ^UiElementData_Button button_data: ^UiElementData_Button
button_data = new(UiElementData_Button, context.temp_allocator) 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.same_line()
ui.element(.Slider, slider_dimensions, slider_config, slider_data) 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_config: ui.ElementConfiguration
toggle_group_dimensions := ui.Dimensions{top_bar_button_width * 3.0, ui.Size_Percentage(1.0)} toggle_group_dimensions := ui.Dimensions{top_bar_button_width * 3.0, ui.Size_Percentage(1.0)}
toggle_group_data: ^UiElementData_ToggleGroup 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} toggle_group_data^ = {value = cast(^i32)&program.animation_mode}
ui.same_line() ui.same_line()
ui.element(.ToggleGroup, toggle_group_dimensions, toggle_group_config, toggle_group_data) 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() 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 program.grid.cell_size_px = 50.0
case .DismissError: case .DismissError:
program.state = .Normal program.state = .Normal
case .CloseProgram:
program.window_should_close = true
} }
if data.type in UiButtonRecalculateFinishGroup { if data.type in UiButtonRecalculateFinishGroup {
program.animation.matrix_count = program.matrix_count 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 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: ^UiElementData_Button
button_data = new(UiElementData_Button, context.temp_allocator) button_data = new(UiElementData_Button, context.temp_allocator)
button_data^ = {type = button_type_add} 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 = new(UiElementData_Button, context.temp_allocator)
button_data^ = {type = .ExitDialog} button_data^ = {type = .ExitDialog}