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
- 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.

View file

@ -196,11 +196,16 @@ animation_draw :: proc(animation: ^Animation, global_transform: mat3, mode: Anim
transform *= mat
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 {
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
}

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 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

View file

@ -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)
{

View file

@ -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
}
}

View file

@ -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)&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)
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)
}
}
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}