From bd47c52111f6844f0199581cc55640d704107649 Mon Sep 17 00:00:00 2001 From: Synthasmagoria Date: Sun, 23 Aug 2026 04:16:17 +0200 Subject: [PATCH] Basic grid zooming --- program.odin | 126 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 33 deletions(-) diff --git a/program.odin b/program.odin index aa823dd..c0d27ad 100644 --- a/program.odin +++ b/program.odin @@ -93,18 +93,22 @@ UiElementData_Button :: struct { UI_ALLOWED_FOCUS :: bit_set[ui.ElementType]{.InputFloat} -MatrixTypes :: enum { - Identity, - Translation, - Scale, - Rotation, +HotkeyTypes :: enum { + AddIdentity, + AddTranslation, + AddScale, + AddRotation, + ZoomXOnly, + ZoomYOnly, } -@rodata hotkey_without_modifier := [MatrixTypes]rl.KeyboardKey { - .Identity = .I, - .Translation = .T, - .Scale = .S, - .Rotation = .R, +@rodata hotkey_without_modifier := [HotkeyTypes]rl.KeyboardKey { + .AddIdentity = .I, + .AddTranslation = .T, + .AddScale = .S, + .AddRotation = .R, + .ZoomXOnly = .X, + .ZoomYOnly = .Y, } HotkeyWithModifier :: enum { @@ -180,12 +184,14 @@ Program :: struct { ui_overlay_queue_focus_first: bool, matrix_float_pool: []f32, matrix_value_string_pool: []InputFloatField, - matrix_create_dropdown_value: MatrixTypes, + matrix_create_dropdown_value: HotkeyTypes, matrix_count: int, matrix_scroll: f32, matrix_scroll_speed: f32, mouse_state: MouseState, grid: Grid, + grid_zoom_x_only: bool, + grid_zoom_y_only: bool, font_style: FontStyle, state: ProgramState, message_type: ProgramMessageType, @@ -213,9 +219,9 @@ AnimationState :: enum { } Grid :: struct { - area: rect2, - inner_area: rect2, - zoom: f32, + cell_size: v2, + zoom_previous, zoom: v2, + world_position, world_size, offset: v2, } float_slice_get_mat3 :: proc(floats: []f32, index: int) -> ^mat3 { @@ -275,9 +281,10 @@ program_init :: proc(allocator := context.allocator) { spacing = 1.0 } program.grid = { - area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, - inner_area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, + cell_size = 32.0, zoom = 1.0, + world_position = 0.0, + world_size = {WINDOW_WIDTH, WINDOW_HEIGHT} } ui.init(&program.ui_context, 4000, ui_measure_text, &program.font_style, allocator) @@ -326,12 +333,12 @@ program_set_transformation_state :: proc(state: AnimationState) { } program_update :: proc() { - ui_elements, matrix_container := program_build_workspace_ui(&program.ui_context) + ui_elements, top_bar_container, matrix_container := program_build_workspace_ui(&program.ui_context) ui_overlay_elements: []ui.Element switch program.state { case .Normal: - program.mouse_state = program_handle_mouse_workspace(program.mouse_state, matrix_container) + program.mouse_state = program_handle_mouse_workspace(program.mouse_state, top_bar_container, matrix_container) if rl.IsKeyPressed(.TAB) { program.ui_element_focus = ui.focus_next_element(program.ui_element_focus, ui_elements, UI_ALLOWED_FOCUS) } @@ -348,19 +355,35 @@ program_update :: proc() { } else { for hotkey, index in hotkey_without_modifier { if rl.IsKeyPressed(hotkey) { - switch index { - case .Identity: + #partial switch index { + case .AddIdentity: program_add_identity_matrix() program.ui_overlay_queue_focus_first = true - case .Translation: + case .AddTranslation: program_show_dialog(&program, .CreateTranslationMatrix) program.ui_overlay_queue_focus_first = true - case .Scale: + case .AddScale: program_show_dialog(&program, .CreateScaleMatrix) program.ui_overlay_queue_focus_first = true - case .Rotation: + case .AddRotation: program_show_dialog(&program, .CreateRotationMatrix) program.ui_overlay_queue_focus_first = true + case .ZoomXOnly: + if program.grid_zoom_x_only { + program.grid_zoom_x_only = false + program.grid_zoom_y_only = false + } else { + program.grid_zoom_x_only = true + program.grid_zoom_y_only = false + } + case .ZoomYOnly: + if program.grid_zoom_y_only { + program.grid_zoom_x_only = false + program.grid_zoom_y_only = false + } else { + program.grid_zoom_x_only = false + program.grid_zoom_y_only = true + } } } } @@ -393,8 +416,32 @@ program_update :: proc() { } grid := program.grid - grid_inner_position := rect_get_tl(grid.inner_area) - draw_grid(grid.area, {32.0, 32.0}, rl.GRAY) + zoom_amount := grid_get_zoom_amount(grid.zoom) + world_to_grid_matrix := ( + mat3 { + 1, 0, 0, + 0, 1, 0, + grid.offset.x, grid.offset.y, 1, + } * + linalg.matrix3_scale(v3{**zoom_amount, 1.0})) + grid_to_world_matrix := linalg.inverse(world_to_grid_matrix) + grid_world_br := grid.world_position + grid.world_size + + grid_tl := (v3{**grid.world_position, 1.0} * world_to_grid_matrix).xy + grid_tl = linalg.floor(grid_tl / grid.cell_size) * grid.cell_size + grid_br := (v3{**grid_world_br, 1.0} * world_to_grid_matrix).xy + for x: f32 = grid_tl.x; x < grid_br.x; x += grid.cell_size.x { + world_position := (v3{x, 0.0, 1.0} * grid_to_world_matrix).x + p1 := v2{world_position, grid.world_position.y} + p2 := v2{world_position, grid_world_br.y} + rl.DrawLineV(p1, p2, {255, 255, 255, 128}) + } + for y: f32 = grid_tl.y; y < grid_br.y; y += grid.cell_size.y { + world_position := (v3{0.0, y, 1.0} * grid_to_world_matrix).y + p1 := v2{grid.world_position.x, world_position} + p2 := v2{grid_world_br.x, world_position} + rl.DrawLineV(p1, p2, {255, 255, 255, 128}) + } if program.animation_state == .Playing { program.animation_matrix_progress += program.animation_speed * delta_time_fraction() @@ -517,11 +564,12 @@ program_build_dialog_ui :: proc(ctx: ^ui.Context, dialog_type: ProgramDialogType } -program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, matrix_container: ^ui.Element) { +program_build_workspace_ui :: proc(ctx: ^ui.Context) -> + (elements: []ui.Element, top_bar_container, matrix_container: ^ui.Element) { ui.start(ctx, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT}) top_bar_container_config := ui.ElementConfiguration {margin = 4.0} 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_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_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} @@ -595,22 +643,34 @@ program_build_workspace_ui :: proc(ctx: ^ui.Context) -> (elements: []ui.Element, return } -program_handle_mouse_workspace :: proc(mouse_state: MouseState, matrix_container: ^ui.Element) -> MouseState { +grid_get_zoom_amount :: proc(zoom: v2) -> v2 { + return linalg.pow(v2{2.0, 2.0}, zoom) - 1.0 +} + +program_handle_mouse_workspace :: proc(mouse_state: MouseState, top_bar_container, matrix_container: ^ui.Element) -> MouseState { mouse := rl.GetMousePosition() + mouse_wheel := rl.GetMouseWheelMoveV() switch mouse_state { case .Hover: - if rl.IsMouseButtonPressed(.LEFT) { - return .DraggingGrid - } else if rect_has_point(rect2(matrix_container.area), mouse) { + if rect_has_point(rect2(matrix_container.area), mouse) { return .HoverMatrices } + if rect_has_point(rect2(top_bar_container.area), mouse) { + return mouse_state + } + if rl.IsMouseButtonPressed(.LEFT) { + return .DraggingGrid + } + program.grid.zoom_previous = program.grid.zoom + grid_zoom_dimension_lock := v2{1.0 - f32(int(program.grid_zoom_y_only)), 1.0 - f32(int(program.grid_zoom_x_only))} + program.grid.zoom += grid_zoom_dimension_lock * mouse_wheel.y * 0.01 case .DraggingGrid: - program.grid.inner_area = rect_translate(program.grid.inner_area, rl.GetMouseDelta()) + program.grid.offset -= rl.GetMouseDelta() if !rl.IsMouseButtonDown(.LEFT) { return .Hover } case .HoverMatrices: - program.matrix_scroll = max(0, program.matrix_scroll - rl.GetMouseWheelMove() * program.matrix_scroll_speed) + program.matrix_scroll = max(0, program.matrix_scroll - mouse_wheel.y * program.matrix_scroll_speed) if !rect_has_point(rect2(matrix_container.area), mouse) { return .Hover }