Author:ptrace Comitter:ptrace Date:2026-05-08 00:57:13 UTC

fixed cell view; improved UI/UX for cell view; minor cleanup at vtree and cells; introducing htree view

diff --git a/src/cells.jai b/src/cells.jai index ea3bd6e..3cba949 100644 --- a/src/cells.jai +++ b/src/cells.jai @@ -3,12 +3,15 @@ cells_init :: (tree_copy: []int) {     tree = tree_copy;     row_count, col_count = dimensions(tree.count); cells_init :: (tree_copy: []int, stat: *Statistics) {     row_count, col_count = dimensions(tree_copy.count);     create_cells(tree_copy, stat);     // Note: if we do hot reloading, we have to manage memory here!     this_allocation_is_not_a_leak(cells.data); } cells_fit_zoom :: ($update_camera: bool = true) -> Camera2D { cells_fit_zoom :: () -> Camera2D {     width  := col_count * CELL_WIDTH_HEIGHT;     height := row_count * CELL_WIDTH_HEIGHT; @@ -23,85 +26,184 @@ cells_fit_zoom :: ($update_camera: bool = true) -> Camera2D {     cam.offset = { SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0 };     cam.target = center;     #if update_camera then camera = cam;     camera = cam;     return cam; } cells_draw_2d :: (stats: *Statistics) {     code :: #code {         label: string;         color: Color; cells_draw_2d :: () {     assert(cells.count > 0, "Cell array empty");         if it_index == 0 {             label = "R";             color = COLOR_NODE_ROOT;             stats.nodes += 1;     first_cell := cells[0].rect;     last_cell  := cells[cells.count-1].rect;     last_cell_in_first_row := cells[col_count].rect;     whole_cell: Rectangle; {         using whole_cell;         x = first_cell.x;         y = first_cell.y;         width  = (last_cell_in_first_row.x + last_cell_in_first_row.width) - first_cell.x;         height = (last_cell.y + last_cell.width) - first_cell.y;     }     is_mouse_inside_any_cell := CheckCollisionPointRec(mouse_world, whole_cell);     for cells {         using it;         new_color := color;         inactive_color   := color - { 0, 0, 0, 130 };         inactive_border  := COLOR_CELLS_BORDER - { 0, 0, 0, 100 };         border_thickness := CELL_BORDER_THICK_NORMAL;         is_mouseover := CheckCollisionPointRec(mouse_world, rect);         if is_mouseover {             new_color += { 30, 30, 30, 0 };             border_thickness = CELL_BORDER_THICK_ACTIVE;         }         else {             if it == {             case NODE_INTERNAL;                 label = "N";                 color = COLOR_NODE_INTERNAL;                 stats.nodes += 1;             case NODE_EMPTY;                 color = COLOR_NODE_EMPTY;                 stats.empty += 1;             case;                 label = tprint("%", it);                 color = COLOR_NODE_VALUE;                 stats.children += 1;         if is_mouse_inside_any_cell {             if is_mouseover {                 DrawRectangleRec(rect, new_color);                 DrawRectangleLinesEx(rect, border_thickness, COLOR_CELLS_BORDER_HIGHLIGHT);             } else {                 DrawRectangleRec(rect, inactive_color);                 DrawRectangleLinesEx(rect, border_thickness, inactive_border);             }         } else {             DrawRectangleRec(rect, new_color);             DrawRectangleLinesEx(rect, border_thickness, COLOR_CELLS_BORDER);         }     }     {         parent :: #code {             new_color := cell.color + COLOR_HIGHLIGHT0;             DrawRectangleRec(cell.rect, new_color);         }         if CheckCollisionPointRec(mouse_world, rect) {             color += Color.{ 30, 30, 30, 0 };         child_left :: #code {             new_color := cell.color + COLOR_HIGHLIGHT0;             DrawRectangleRec(cell.rect, new_color);         }         DrawRectangleRec(rect, color);         DrawRectangleLinesEx(rect, 1.0, COLOR_CELLS_BORDER);         child_right :: #code {             new_color := cell.color + COLOR_HIGHLIGHT0;             DrawRectangleRec(cell.rect, new_color);         }         draw_hover_cells(parent, child_left, child_right);     }     {         parent :: #code {             border_color := cell.color + COLOR_HIGHLIGHT2;             DrawRectangleLinesEx(                 cell.rect,                 CELL_BORDER_THICK_ACTIVE,                 border_color             );             do_text(                 "P",                 { cell.rect.x, cell.rect.y },                 CELL_INFO_FONT_SIZE,                 .SOUTH_WEST,                 border_color             );         }         child_left :: #code {             border_color := cell.color + COLOR_HIGHLIGHT2;             DrawRectangleLinesEx(                 cell.rect,                 CELL_BORDER_THICK_ACTIVE,                 border_color             );             do_text(                 "L",                 { cell.rect.x, cell.rect.y },                 CELL_INFO_FONT_SIZE,                 .SOUTH_WEST,                 border_color             );         }         child_right :: #code {             border_color := cell.color + COLOR_HIGHLIGHT2;             DrawRectangleLinesEx(                 cell.rect,                 CELL_BORDER_THICK_ACTIVE,                 border_color             );             do_text(                 "R",                 { cell.rect.x, cell.rect.y },                 CELL_INFO_FONT_SIZE,                 .SOUTH_WEST,                 border_color             );         }         draw_hover_cells(parent, child_left, child_right);     }     for cells {         using it;         do_text(label, { rect.x, rect.y }, 30, .CENTER, WHITE);         do_text(             tprint("%", it_index),             tprint("%", it.index),             { rect.x, rect.y },             10,             .NORTH_WEST,             WHITE         );     }     traverse_tree_array(code, stats); } cells_draw_screen :: (stats: *Statistics) {     gui_statistics(stats);     code :: #code {         if CheckCollisionPointRec(             mouse_world, rect         ) {             text := tooltip_text(it_index, it);     if !flag_has(.TOOLTIP) return;     for cells {         if !it.label continue;         if CheckCollisionPointRec(mouse_world, it.rect) {             text := tooltip_text(it_index, it.value);             tooltip(mouse_screen, text);         }     }     traverse_tree_array(code); } #scope_file tree: []int; cells: [..]Cell; row_count, col_count: int; CELL_INFO_FONT_SIZE :: 20; CELL_BORDER_THICK_NORMAL :: 1.0; CELL_BORDER_THICK_ACTIVE :: 2.0; CELL_WIDTH_HEIGHT :: 100.0; CELL_ASPECT_RATIO :float: #run 16.0 / 9.0; CELL_GAP :: 5.0; CELL_GAP :: 4.0; Cell :: struct {     index: int;     value: int;     rect: Rectangle;     label: string;     color: Color; } /** Overkill, but maybe we need it */ Anchor :: enum { @@ -124,31 +226,98 @@ dimensions :: (length: int) -> (row: int, col: int) {     return cast(int, row), cast(int, col); } traverse_tree_array :: ($code1: Code, stats: *Statistics = null) { cell_pos_from_index :: (index: int) -> Rectangle {     col := index % col_count;     row := index / col_count;     return .{         x      = col * (CELL_WIDTH_HEIGHT + CELL_GAP),         y      = row * (CELL_WIDTH_HEIGHT + CELL_GAP),         width  = CELL_WIDTH_HEIGHT,         height = CELL_WIDTH_HEIGHT,     }; } create_cells :: (tree: []int, stat: *Statistics) {     CELL_WITH_GAP :: #run CELL_WIDTH_HEIGHT + CELL_GAP;     rect: Rectangle;     rect.x = 0.0;     rect.y = 0.0;     rect.width  = CELL_WIDTH_HEIGHT;     rect.height = CELL_WIDTH_HEIGHT;     rect.x      = -CELL_WITH_GAP;     idx_col := col_count;     idx_col := col_count + 1;     for tree {         if idx_col {         cell: Cell;             #insert,scope() code1;             rect.x += CELL_WIDTH_HEIGHT + CELL_GAP;         if idx_col {             rect.x  += CELL_WITH_GAP;             idx_col -= 1;         } else {             rect.y += CELL_WIDTH_HEIGHT + CELL_GAP;             rect.x  = 0.0;             idx_col = col_count;             rect.x   = 0.0;             rect.y  += CELL_WITH_GAP;             idx_col  = col_count;         }         if it_index == 0 {             cell.label = "R";             cell.color = COLOR_NODE_ROOT;             stat.nodes += 1;         } else {             if it == {             case NODE_INTERNAL;                 cell.label = "N";                 cell.color = COLOR_NODE_INTERNAL;                 stat.nodes += 1;             case NODE_EMPTY;                 cell.color = COLOR_NODE_EMPTY;                 stat.empty += 1;             case;                 cell.label = tprint("%", it);                 cell.color = COLOR_NODE_VALUE;                 stat.children += 1;             }         }         cell.rect = rect;         cell.index = it_index;         cell.value = it;         array_add(*cells, cell);     } } draw_hover_cells :: (     $parent: Code,     $child_left: Code,     $child_right: Code, ) {     for cells {         using it;         if CheckCollisionPointRec(mouse_world, rect) && label {             parent_idx := (it_index - 1) / 2;             left_idx   := 2 * it_index + 1;             right_idx  := 2 * it_index + 2;             n := tree.count;             if it_index > 0 && cells[parent_idx].label {                 cell := cells[parent_idx];                 #insert,scope() parent;             }             if left_idx < n  && cells[left_idx].label {                 cell := cells[left_idx];                 #insert,scope() child_left;             }             if right_idx < n && cells[right_idx].label {                 cell := cells[right_idx];                 #insert,scope() child_right;             }         }     } } /** Overkill, but maybe we need it */ do_text :: (text: string, pos: Vector2, size: s32, anchor: Anchor, color: Color) {     label_c := to_c_string(text);     tw := MeasureTextEx(GetFontDefault(), label_c, xx size, 1); diff --git a/src/htree.jai b/src/htree.jai new file mode 100644 index 0000000..9cb7195 --- /dev/null +++ b/src/htree.jai @@ -0,0 +1,27 @@ // Continue: hyper hyper! htree_init :: (tree_copy: []int, stat: *Statistics) { } htree_fit_zoom :: () { } htree_draw_2d :: () { } htree_draw_screen :: (stat: *Statistics) { } #scope_file diff --git a/src/input.jai b/src/input.jai index 9c5b5d5..340178e 100644 --- a/src/input.jai +++ b/src/input.jai @@ -6,6 +6,10 @@ Mode :: enum #specified {     CELLS       :: 2; } Gui_Feature :: enum_flags {     TOOLTIP; } input :: () { @@ -87,10 +91,10 @@ input :: () {     }     else if IsKeyPressed(.F2) {         if mode == .HYPER_TREE {             // tbd             // htree_fit_zoom();         } else {             mode = .HYPER_TREE;             // state_switch(mode);             state_switch(mode);         }     }     else if IsKeyPressed(.F3) { @@ -101,6 +105,15 @@ input :: () {             state_switch(mode);         }     }     /* ---------------------- *      * Context Based Features *      * ---------------------- */     if mode == {     case .CELLS;         if IsKeyPressed(.KEY_T) then flag_toggle(.TOOLTIP);     } } input_draw_selection :: () { @@ -110,20 +123,25 @@ input_draw_selection :: () {     width = mouse_world.x - x;     height = mouse_world.y - y;     DrawRectangleLinesEx(selection, 2.0, GREEN);     selection_normalized = normalize_selection(selection);     DrawRectangleLinesEx(selection_normalized, 2.0, GREEN); } // flag_has :: (flag: Mode) -> bool { //     return cast(bool, mode & flag); // } flag_has :: (flag: Gui_Feature) -> bool {     return cast(bool, gui_feature & flag); } // flag_add :: (flag: Mode) { //     mode |= flag; // } flag_add :: (flag: Gui_Feature) {     gui_feature |= flag; } // flag_remove :: (flag: Mode) { //     mode &= ~flag; // } flag_remove :: (flag: Gui_Feature) {     gui_feature &= ~flag; } flag_toggle :: (flag: Gui_Feature) {     gui_feature ^= flag; } #scope_file @@ -133,11 +151,19 @@ drag_start: Vector2; dragging: bool; selection: Rectangle; selection_normalized: Rectangle; selecting: bool; normalize_selection :: (using selection: Rectangle) -> Rectangle {     nx := ifx width  < 0 then x + width else x;     ny := ifx height < 0 then y + height else y;     return { nx, ny, abs(width), abs(height) }; } zoom_to_selection :: () {     using selection;     using selection_normalized;     center := Vector2.{ x + width / 2.0, y + height / 2.0 };     zoom_x := cast(float, GetScreenWidth()  / width); diff --git a/src/main.jai b/src/main.jai index 21348c7..a901f5f 100644 --- a/src/main.jai +++ b/src/main.jai @@ -11,20 +11,19 @@ #load "tooltip.jai"; #load "state.jai"; #load "vtree.jai"; #load "htree.jai"; #load "cells.jai"; /** Ideas / Todo     - Check the CELLS array, it looks scuffed!     - Second option for visualizing the tree -> https://en.wikipedia.org/wiki/Hyperbolic_tree     - Different font     - Hot reload a binary dump     - Update the binary dump     - Half the size of the nodes with every depth     - Smooth zoom (maybe)     - Different font     - Generic approach for other types of info data in the tooltip     - Second option for visualizing the tree -> https://en.wikipedia.org/wiki/Hyperbolic_tree     - Smooth zoom (maybe) */ @@ -45,6 +44,7 @@ tree: [..]int; bvs: [..]Aabb; mode: Mode; gui_feature: Gui_Feature; camera: Camera2D; frame_time: float; @@ -68,8 +68,12 @@ COLOR_NODE_INTERNAL :: Color.{ 30, 60, 120, 255 }; COLOR_NODE_EMPTY    :: #run COLOR_BACKGROUND + { 10, 10, 10, 0 }; COLOR_NODE_VALUE    :: Color.{ 30, 110, 70, 255 }; COLOR_CELLS_BORDER  :: #run COLOR_BACKGROUND + { 70, 70, 75, 0 }; COLOR_BACKGROUND    :: Color.{ 42, 42, 60, 255 }; COLOR_HIGHLIGHT0    :: Color.{ 30, 30, 30, 0 }; COLOR_HIGHLIGHT1    :: Color.{ 70, 70, 70, 0 }; COLOR_HIGHLIGHT2    :: Color.{ 110, 110, 110, 0 }; COLOR_CELLS_BORDER  :: #run COLOR_BACKGROUND + COLOR_HIGHLIGHT1; COLOR_CELLS_BORDER_HIGHLIGHT :: #run COLOR_CELLS_BORDER + COLOR_HIGHLIGHT1; DRAW_BV :: true; @@ -104,8 +108,6 @@ Statistics :: struct { #if true main :: () {     #if MEMORY_DEBUGGER_ENABLED defer report_memory_leaks();     log("Hello, Sailor!");     SetTraceLogLevel(.LOG_NONE);     SetConfigFlags(.FLAG_MSAA_4X_HINT); @@ -119,7 +121,6 @@ Statistics :: struct {     log_vars(bvs.count-1);     array_add(*tree, NODE_INTERNAL, 0, 1);     log_vars(tree);     for i: 2..bvs.count-1 {         // Find sibling @@ -136,11 +137,17 @@ Statistics :: struct {     tree_copy := array_copy(tree);     vtree_init(tree_copy);     cells_init(tree_copy);     state_vtree: State;     state_htree: State;     state_cells: State;     stats_vtree: Statistics;     stats_htree: Statistics;     stats_cells: Statistics;     vtree_init(tree_copy, *stats_vtree);     htree_init(tree_copy, *stats_htree);     cells_init(tree_copy, *stats_cells);     {         using state_cells; @@ -149,21 +156,24 @@ Statistics :: struct {     }     {         using state_htree;         id = .HYPER_TREE;         // camera = htree_fit_zoom();     }     {         using state_vtree;         id = .TRAD_TREE;         camera = vtree_fit_zoom();     }     state_init(mode, state_vtree, state_cells);     state_init(mode, state_vtree, state_htree, state_cells);     gui_init();     push_allocator(temp);     while !WindowShouldClose() {         stat_vtree: Statistics;         stat_cells: Statistics;         frame_time   = GetFrameTime();         mouse_screen = GetMousePosition();         mouse_world  = GetScreenToWorld2D(mouse_screen, camera); @@ -191,9 +201,9 @@ Statistics :: struct {             defer EndMode2D();             if mode == {             case .TRAD_TREE;  vtree_draw_2d(*stat_vtree);             case .HYPER_TREE; // Todo;             case .CELLS;      cells_draw_2d(*stat_cells);             case .TRAD_TREE;  vtree_draw_2d();             case .HYPER_TREE; htree_draw_2d();             case .CELLS;      cells_draw_2d();             }             input_draw_selection(); @@ -202,9 +212,9 @@ Statistics :: struct {         gui_draw_screen();         if mode == {         case .TRAD_TREE;  vtree_draw_screen(*stat_vtree);         case .HYPER_TREE; // Todo;         case .CELLS;      cells_draw_screen(*stat_cells);         case .TRAD_TREE;  vtree_draw_screen(*stats_vtree);         case .HYPER_TREE; htree_draw_screen(*stats_htree);         case .CELLS;      cells_draw_screen(*stats_cells);         }         DrawFPS(10, 10); @@ -384,10 +394,19 @@ aabb_get_sa :: (a: Aabb, b: Aabb) -> float { operator + :: (a: Color, b: Color) -> Color {     c: Color = ---;     c.r = a.r + b.r;     c.g = a.g + b.g;     c.b = a.b + b.b;     c.a = a.a + b.a;     c.r = clamp(a.r + b.r, 0, U8_MAX);     c.g = clamp(a.g + b.g, 0, U8_MAX);     c.b = clamp(a.b + b.b, 0, U8_MAX);     c.a = clamp(a.a + b.a, 0, U8_MAX);     return c; } operator - :: (a: Color, b: Color) -> Color {     c: Color = ---;     c.r = clamp(a.r - b.r, 0, U8_MAX);     c.g = clamp(a.g - b.g, 0, U8_MAX);     c.b = clamp(a.b - b.b, 0, U8_MAX);     c.a = clamp(a.a - b.a, 0, U8_MAX);     return c; } diff --git a/src/vtree.jai b/src/vtree.jai index 4ca972f..2cb5d1f 100644 --- a/src/vtree.jai +++ b/src/vtree.jai @@ -1,13 +1,15 @@ NODES_MAX :: 256; vtree_init :: (tree_copy: []int) { vtree_init :: (tree_copy: []int, stat: *Statistics) {     tree = tree_copy;     assign_positions(0, -(SCREEN_WIDTH / 2), SCREEN_WIDTH - (SCREEN_WIDTH / 2), 0);     create_stats(stat); } vtree_fit_zoom :: ($update_camera: bool = true) -> Camera2D { vtree_fit_zoom :: () -> Camera2D {     min, max: Vector2;     min.x = nodes[0].x - NODE_RADIUS;     max.x = nodes[0].x + NODE_RADIUS; @@ -32,22 +34,14 @@ vtree_fit_zoom :: ($update_camera: bool = true) -> Camera2D {     cam.offset = { SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0 };     cam.target = { (min.x + max.x) / 2.0, (min.y + max.y) / 2.0 };     #if update_camera then camera = cam;     camera = cam;     return cam; } vtree_draw_2d :: (stat: *Statistics) { vtree_draw_2d :: () {     for i: 0..node_count-1 {         n := nodes[i];         if n.value == {         case NODE_EMPTY;    stat.empty += 1;         case NODE_INTERNAL; stat.nodes += 1;         case;             stat.children += 1;         }         idx_left  := 2 * n.index + 1;         idx_right := 2 * n.index + 2; @@ -59,6 +53,7 @@ vtree_draw_2d :: (stat: *Statistics) {                 { n.x, n.y }, { lc.x, lc.y }, 2.0, { 100, 180, 255, 160 }             );         }         if found_r {             DrawLineEx(                 { n.x, n.y }, { rc.x, rc.y }, 2.0, { 255, 140, 100, 160 } @@ -75,29 +70,47 @@ vtree_draw_2d :: (stat: *Statistics) {         tn_x := cast(s32, n.x);         tn_y := cast(s32, n.y);         is_root     := n.index == 0;         is_internal := n.value == NODE_INTERNAL;         glow := ifx is_internal             then Color.{ 80, 160, 255, 60 }             else Color.{ 80, 255, 160, 60 };         DrawCircle(tn_x, tn_y, node_r + 6.0, glow);         fill: Color;         glow: Color;         border: Color;         if is_root {             fill = COLOR_NODE_ROOT;         } else {             if is_internal             then fill = COLOR_NODE_INTERNAL;             else fill = COLOR_NODE_VALUE;         }         glow = fill + { 40, 40, 40, 0 };         glow -= { 0, 0, 0, 190 };         fill := ifx is_internal             then COLOR_NODE_INTERNAL             else COLOR_NODE_VALUE;         border = fill + { 80, 80, 80, 0 };         DrawCircle(tn_x, tn_y, node_r + 6.0, glow);         if CheckCollisionPointCircle(mouse_world, { n.x, n.y }, node_r) {             fill += Color.{ 30, 30, 30, 0 };             fill += { 30, 30, 30, 0 };         }         DrawCircle(tn_x, tn_y, xx node_r, fill);         border := ifx is_internal             then Color.{ 100, 180, 255, 255 }             else Color.{ 80, 255, 160, 255 };         DrawCircleLines(tn_x, tn_y, xx node_r, border);         label := ifx is_internal then "N" else tprint("%", n.value);         label: string;         if is_root {             label = "R";         }         else if is_internal {             label = "N";         }         else {             label = tprint("%", n.value);         }         label_c := to_c_string(label);         tw := MeasureTextEx(GetFontDefault(), label_c, xx font1_sz, 1); @@ -130,6 +143,7 @@ vtree_draw_screen :: (stat: *Statistics) {         node_scale  := pow(DEPTH_SCALE_NODE, cast(float)n.depth);         node_r := NODE_RADIUS * node_scale;         n_screen := GetWorldToScreen2D({ n.x, n.y }, camera);         if CheckCollisionPointCircle(mouse_screen, n_screen, node_r * camera.zoom) {             text := tooltip_text(n.index, n.value);             tooltip(mouse_screen, text); @@ -148,7 +162,6 @@ nodes: [NODES_MAX]VTree_Node; node_count: int; NODES_MAX   :: 128; NODE_RADIUS :: 52; FONT1_SIZE  :: 69; FONT2_SIZE  :: 30; @@ -203,6 +216,9 @@ assign_positions :: (idx: int, left: float, right: float, depth: int) {         node_count += 1;     }     // TODO: Error handling     assert(node_count < NODES_MAX, "Too many nodes");     mid := (left + right) / 2.0;     assign_positions(2 * idx + 1, left, mid, depth + 1);     assign_positions(2 * idx + 2, mid, right, depth + 1); @@ -215,3 +231,14 @@ find_node :: (idx: int) -> (found: bool, node: VTree_Node) {     return false, {}; } create_stats :: (stat: *Statistics) {     for i: 0..node_count-1 {         if nodes[i].value == {         case NODE_INTERNAL; stat.nodes += 1;         case NODE_EMPTY;    stat.empty += 1;         case;             stat.children += 1;         }     } }