1 module re.ng.diag.inspector; 2 3 import re.core; 4 import re.ecs; 5 import re.math; 6 import re.gfx; 7 import std.conv; 8 import std.array; 9 import std.algorithm; 10 import std.typecons; 11 import std.string; 12 import re.util.interop; 13 import witchcraft; 14 static import raylib; 15 static import raygui; 16 17 /// real-time object inspector 18 debug class Inspector { 19 /// panel width 20 enum width = 400; 21 /// whether the inspector is open 22 public bool open = false; 23 private Vector2 _panel_scroll; 24 private InspectedComponent[] _components; 25 private Entity entity; 26 private enum btn_close = ['x', '\0']; 27 28 private class InspectedComponent { 29 public Component obj; 30 public Class obj_class; 31 public string[string] fields; 32 33 this(Component obj) { 34 this.obj = obj; 35 this.obj_class = obj.getMetaType; 36 } 37 38 private void update_fields() { 39 foreach (field; obj_class.getFields) { 40 string field_name = field.getName; 41 string field_value = to!string(field.get(obj)); 42 this.fields[field_name] = field_value; 43 } 44 } 45 } 46 47 this() { 48 reset(); 49 } 50 51 private void reset() { 52 _components = []; 53 entity = null; 54 } 55 56 public void update() { 57 // update all inspected components 58 foreach (comp; _components) { 59 comp.update_fields(); 60 } 61 } 62 63 public void render() { 64 alias pad = Core.debugger.screen_padding; 65 66 // this is the (clipped) scrollable panel bounds 67 auto panel_bounds = Rectangle(pad, pad, width, Core.window.height - pad * 2); 68 // draw indicator of panel bounds 69 // raylib.DrawRectangleRec(panel_bounds, Colors.GRAY); 70 71 // - layout vars 72 enum field_height = 16; // for each field 73 enum field_padding = 2; 74 enum field_label_width = 120; 75 enum field_value_width = 240; 76 enum header_height = field_height; // for each component 77 enum header_padding = 4; 78 enum header_line_margin = 4; 79 enum title_height = field_height; // for each entity 80 enum title_padding = 8; 81 82 // calculate panel bounds 83 // this is going to calculate the space required for each component 84 int[] component_section_heights; 85 86 foreach (comp; _components) { 87 component_section_heights ~= (header_padding + header_padding) // header 88 + ((field_height + field_padding) // field and padding 89 * ((cast(int) comp.fields.length) + 1)); // number of fields 90 } 91 // total height 92 auto panel_bounds_height = pad + component_section_heights.sum() + ( 93 title_height + title_padding); 94 95 // bounds of the entire panel 96 auto panel_content_bounds = Rectangle(0, 0, width - pad, panel_bounds_height); 97 98 auto view = raygui.GuiScrollPanel(panel_bounds, panel_content_bounds, &_panel_scroll); 99 100 // start scissor 101 raylib.BeginScissorMode(cast(int) view.x, cast(int) view.y, 102 cast(int) view.width, cast(int) view.height); 103 // end scissor on scope exit 104 scope (exit) 105 raylib.EndScissorMode(); 106 107 // close button 108 enum btn_close_sz = 12; 109 if (raygui.GuiButton(Rectangle(panel_bounds.x + panel_content_bounds.width - pad, 110 panel_bounds.y + pad, btn_close_sz, btn_close_sz), cast(char*) btn_close)) { 111 close(); 112 return; // when closed, cancel this render 113 } 114 115 // the corner of the inside of the panel (pre-padded) 116 auto panel_corner = Vector2(panel_bounds.x + pad, panel_bounds.y + pad); 117 118 // entity title 119 auto entity_title = format("Entity %s", entity.name); 120 raygui.GuiLabel(Rectangle(panel_corner.x, panel_corner.y, 121 field_label_width, title_height), entity_title.c_str()); 122 // title underline 123 raylib.DrawRectangleLinesEx(Rectangle(panel_corner.x, 124 panel_corner.y + title_height, panel_bounds.width - pad * 2, 4), 1, Colors.GRAY); 125 126 // - now draw each component section 127 auto panel_y_offset = (title_height + title_padding); // the offset from the y start of the panel (this is based on component index) 128 foreach (i, comp; _components) { 129 auto field_names = comp.fields.keys.sort(); 130 auto field_index = 0; 131 132 // corner for the start of this section 133 auto section_corner = Vector2(panel_corner.x, panel_corner.y + panel_y_offset); 134 // header 135 raygui.GuiLabel(Rectangle(section_corner.x, section_corner.y, 136 field_label_width, header_height), comp.obj_class.getName.c_str()); 137 // header underline 138 raylib.DrawRectangleLinesEx(Rectangle(section_corner.x + header_line_margin, 139 section_corner.y + header_height, panel_bounds.width - header_line_margin * 2, 140 1), 1, Colors.GRAY); 141 // list of fields 142 foreach (field_name; field_names) { 143 auto field_val = comp.fields[field_name]; 144 // calculate field corner 145 auto corner = Vector2(section_corner.x, 146 section_corner.y + (header_height + header_padding) + field_index * ( 147 field_padding + field_height)); 148 raygui.GuiLabel(Rectangle(corner.x, corner.y, 149 field_label_width, field_height), field_name.c_str()); 150 raygui.GuiTextBox(Rectangle(corner.x + field_label_width, corner.y, 151 field_value_width, field_height), field_val.c_str(), 152 field_value_width, false); 153 field_index++; 154 } 155 panel_y_offset += component_section_heights[i]; // go to the bottom of this section 156 } 157 // raygui.GuiGrid(Rectangle(panel_bounds.x + _panel_scroll.x, panel_bounds.y + _panel_scroll.y, 158 // panel_content_bounds.width, panel_content_bounds.height), 16, 4); 159 } 160 161 /// attach the inspector to an object 162 public void inspect(Entity nt) { 163 assert(_components.length == 0, "only one inspector may be open at a time"); 164 open = true; 165 this.entity = nt; 166 // add components 167 _components ~= nt.get_all_components.map!(x => new InspectedComponent(x)).array; 168 } 169 170 /// close the inspector 171 public void close() { 172 assert(open, "inspector is already closed"); 173 open = false; 174 reset(); 175 } 176 }