From 60622ccca5e84102cb142e577f633b3722749e32 Mon Sep 17 00:00:00 2001 From: Synthasmagoria Date: Wed, 22 Jul 2026 11:48:24 +0200 Subject: [PATCH] Grid part 1 --- program.odin | 139 +++++++++++++++++++++++++++++++++++++++++++++++---- ui.odin | 5 +- utils.odin | 32 ++++++++++++ 3 files changed, 164 insertions(+), 12 deletions(-) diff --git a/program.odin b/program.odin index 5d31fc1..cb42818 100644 --- a/program.odin +++ b/program.odin @@ -2,6 +2,8 @@ package main import rl "libraries/raylib" import "base:runtime" +import "core:math" +import "core:math/linalg" import "core:fmt" WINDOW_WIDTH :: 1280 @@ -14,11 +16,127 @@ MATRIX_FIELD_BYTES :: 8 program: Program +FontStyle :: struct { + font: rl.Font, + size, spacing: f32 +} + Program :: struct { ui_state: UiState, matrices: [dynamic; MATRIX_FIELD_COUNT]f32, matrix_fields: [dynamic; MATRIX_FIELD_COUNT][MATRIX_FIELD_BYTES]byte, matrix_type: MatrixType, + grid: Grid, +} + +Grid :: struct { + area: rect2, + inner_area: rect2, + zoom: f32, +} + +grid_matrix_to_world :: proc(area: rect2, inner_area: rect2) -> mat3 { + return ( + matrix3_translate2(rect_get_tl(area)) * + linalg.matrix3_scale(v3{**(rect_get_size(inner_area) / rect_get_size(area)), 1.0}) * + matrix3_translate2(-rect_get_tl(inner_area))) +} + +grid_get_cell_size :: proc(area: v2, lines_min: f32) -> f32 { + max_area := math.max(area.x, area.y) + check := math.pow10(math.floor(math.log10(max_area))) + for { + if max_area / check >= lines_min { + return check + } + check_halved := check / 2.0 + if max_area / check_halved >= lines_min { + return check_halved + } + check_quartered := check / 4.0 + if max_area / check_quartered >= lines_min { + return check_quartered + } + check /= 10.0 + } +} + +grid_get_inner_size_from_zoom :: proc(zoom_amount: f32) -> f32 { + return math.pow(2.0, zoom_amount) +} + +draw_grid :: proc(grid: Grid, font_style: FontStyle) { + grid_to_world_matrix := grid_matrix_to_world(grid.area, grid.inner_area) + grid_cell_size := grid_get_cell_size(rect_get_size(grid.area), 5.0) + grid_cell := v2{grid_cell_size, grid_cell_size} + grid_subgrid_cell := grid_cell / 5.0 + draw_grid_transformed(grid.area, {0.0, 0.0}, grid_cell / 5.0, grid_to_world_matrix, rl.DARKGRAY) + draw_grid_transformed_annotated(grid.area, {0.0, 0.0}, grid_cell, grid_to_world_matrix, rl.GRAY, font_style) +} + +draw_grid_transformed :: proc(area: rect2, align: v2, step: v2, transform: mat3, color: rl.Color) { + grid_start := linalg.floor((v2{area.x, area.y} + align) / step) * step + step + grid_stop := rect_get_br(area) + for x := grid_start.x; x < grid_stop.x; x += step.x { + rl.DrawLineV(matrix3_transform_v2(transform, {x, area.y}), matrix3_transform_v2(transform, {x, grid_stop.y}), color) + } + for y := grid_start.y; y < grid_stop.y; y += step.y { + rl.DrawLineV(matrix3_transform_v2(transform, {area.x, y}), matrix3_transform_v2(transform, {grid_stop.x, y}), color) + } +} + +draw_grid_transformed_annotated :: proc(area: rect2, align: v2, step: v2, transform: mat3, color: rl.Color, style: FontStyle, temp_allocator := context.temp_allocator) { + grid_outside := matrix3_transform_v2(transform, {area.x, area.y}) - 4.0 + grid_start := linalg.floor((v2{area.x, area.y} + align) / step) * step + step + grid_stop := rect_get_br(area) + for x := grid_start.x; x < grid_stop.x; x += step.x { + p1 := matrix3_transform_v2(transform, {x, area.y}) + p2 := matrix3_transform_v2(transform, {x, grid_stop.y}) + rl.DrawLineV(p1, p2, color) + draw_text_aligned(fmt.ctprintf("%.2f", x), {p1.x, grid_outside.y}, style, .Middle, .Bottom, color) + } + for y := grid_start.y; y < grid_stop.y; y += step.y { + p1 := matrix3_transform_v2(transform, {area.x, y}) + p2 := matrix3_transform_v2(transform, {grid_stop.x, y}) + rl.DrawLineV(p1, p2, color) + draw_text_aligned(fmt.ctprintf("%.2f", y), {grid_outside.x, p1.y}, style, .Right, .Center, color) + } +} + +draw_text :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color) { + rl.DrawTextEx(style.font, text, position, style.size, style.spacing, color) +} + +TextHalign :: enum { + Left, + Middle, + Right, +} + +TextValign :: enum { + Top, + Center, + Bottom, +} + +draw_text_aligned :: proc(text: cstring, position: v2, style: FontStyle, halign: TextHalign, valign: TextValign, color: rl.Color) { + size := measure_text(text, style) + position := position + switch halign { + case .Left: + case .Middle: position.x -= size.x / 2.0 + case .Right: position.x -= size.x + } + switch valign { + case .Top: + case .Center: position.y -= size.y / 2.0 + case .Bottom: position.y -= size.y + } + draw_text(text, position, style, color) +} + +measure_text :: proc(text: cstring, style: FontStyle) -> v2 { + return rl.MeasureTextEx(style.font, text, style.size, style.spacing) } MatrixType :: enum { @@ -42,9 +160,11 @@ init :: proc() { ui_style := UiStyle{ margin = 4.0, padding = 4.0, - font = rl.GetFontDefault(), - font_size = 16.0, - font_sep = 1.0 + font_style = { + font = rl.GetFontDefault(), + size = 16.0, + spacing = 1.0 + } } ui_init(&program.ui_state, ui_style, {0, 0}, context.temp_allocator) } @@ -67,10 +187,6 @@ update :: proc() { ui_element_focus_reset(&program.ui_state) } - rl.BeginDrawing() - defer rl.EndDrawing() - rl.ClearBackground(rl.BLACK) - ui_button_size_top_bar := v2{32.0, 32.0} ui_start(&program.ui_state) ui_container_begin({label = ""}) @@ -101,6 +217,12 @@ update :: proc() { ui_container_end() ui_elements := ui_end() + rl.BeginDrawing() + defer rl.EndDrawing() + rl.ClearBackground(rl.BLACK) + + + for element in ui_elements { switch element.type { case .Button: @@ -114,7 +236,6 @@ update :: proc() { input_cstr := cstring(raw_data(data.input)) if (rl.GuiTextBox(element.area, input_cstr, i32(len(data.input) - 1), edit_mode)) { ui_element_focus(&program.ui_state, element.id) - fmt.println("oentuh") } case .Container: data := element.data.(UiElementData_Container) @@ -126,7 +247,7 @@ update :: proc() { case .Text: data := element.data.(UiElementData_Text) style := program.ui_state.style - rl.DrawTextEx(style.font, data.text, rect_get_tl(element.area), style.font_size, style.font_sep, data.color) + draw_text(data.text, rect_get_tl(element.area), style.font_style, data.color) } } diff --git a/ui.odin b/ui.odin index 7c3bc5b..bb4ef13 100644 --- a/ui.odin +++ b/ui.odin @@ -46,8 +46,7 @@ UiState :: struct { UiStyle :: struct { margin, padding: f32, - font_size, font_sep: f32, - font: rl.Font, + font_style: FontStyle, } ui_init :: proc(state: ^UiState, style: UiStyle, origin: v2, allocator: mem.Allocator) { @@ -133,7 +132,7 @@ UiElementData_Text :: struct { ui_text :: proc(data: UiElementData_Text) { state := state_current - size := rl.MeasureTextEx(state.style.font, data.text, state.style.font_size, state.style.font_sep) + size := measure_text(data.text, state.style.font_style) area := _ui_advance_element(state, size) state.id_current.element_index += 1 append(&state.elements, UiElement{id = state.id_current, area = area, type = .Text, data = data}) diff --git a/utils.odin b/utils.odin index f1cee0c..5140914 100644 --- a/utils.odin +++ b/utils.odin @@ -136,3 +136,35 @@ rect_get_br :: proc { @(private="file") irect2_get_br :: proc(rect: irect2) -> iv2 { return {rect.x + rect.width, rect.y + rect.height} } + +rect_get_size :: proc { + rect2_get_size, + irect2_get_size, +} + +@(private="file") rect2_get_size :: proc(rect: rect2) -> v2 { + return {rect.width, rect.height} +} + +@(private="file") irect2_get_size :: proc(rect: irect2) -> iv2 { + return {rect.width, rect.height} +} + +/// ## LINEAR ALGEBRA ## /// +matrix3_translate2 :: proc "contextless" (translation: v2) -> mat3 { + return { + 1.0, 0.0, translation.x, + 0.0, 1.0, translation.y, + 0.0, 0.0, 1.0, + } +} + +matrix3_transform_v2 :: proc(m: mat3, v: v2) -> v2 { + return (m * v3{**v, 1.0}).xy +} + +matrix3_transform_rect2 :: proc(m: mat3, r: rect2) -> rect2 { + tl := m * v3{r.x, r.y, 1.0} + br := m * v3{r.x + r.width, r.y + r.height, 1.0} + return {**tl.xy, **(br - tl).xy} +}