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 public int width = 400; 21 /// whether the inspector is open 22 public bool open = false; 23 private Vector2 _panel_scroll; 24 private InspectedObject[] _inspected; 25 private Entity entity; 26 private enum btn_close = ['x', '\0']; 27 28 private class InspectedObject { 29 public ReflectableObject obj; 30 public Class obj_class; 31 public string[string] fields; 32 33 this(ReflectableObject 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 _inspected = []; 53 entity = null; 54 } 55 56 public void update() { 57 // update all inspected components 58 foreach (comp; _inspected) { 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_value_text_size = 12; 74 enum field_padding = 2; 75 enum field_label_width = 120; 76 const auto field_value_width = width - 40; 77 enum header_height = field_height; // for each component 78 enum header_padding = 4; 79 enum header_line_margin = 4; 80 enum title_height = field_height; // for each entity 81 enum title_padding = 8; 82 enum props_count = 1; // transform 83 84 // calculate panel bounds 85 // this is going to calculate the space required for each component 86 int[] component_section_heights; 87 88 foreach (comp; _inspected) { 89 component_section_heights ~= (header_padding + header_padding) // header 90 + ((field_height + field_padding) // field and padding 91 * ((cast(int) comp.fields.length) + 1)); // number of fields 92 } 93 enum title_offset = title_height + title_padding; 94 enum props_offset = (props_count * (field_height + field_padding)); 95 // total height 96 auto panel_bounds_height = pad + component_section_heights.sum() + ( 97 title_offset) // title 98 + props_offset; // props 99 100 // bounds of the entire panel 101 auto panel_content_bounds = Rectangle(0, 0, width - pad, panel_bounds_height); 102 103 auto view = raygui.GuiScrollPanel(panel_bounds, panel_content_bounds, &_panel_scroll); 104 105 // start scissor 106 raylib.BeginScissorMode(cast(int) view.x, cast(int) view.y, 107 cast(int) view.width, cast(int) view.height); 108 // end scissor on scope exit 109 scope (exit) 110 raylib.EndScissorMode(); 111 112 // close button 113 enum btn_close_sz = 12; 114 if (raygui.GuiButton(Rectangle(panel_bounds.x + panel_content_bounds.width - pad, 115 panel_bounds.y + pad, btn_close_sz, btn_close_sz), cast(char*) btn_close)) { 116 close(); 117 return; // when closed, cancel this render 118 } 119 120 // the corner of the inside of the panel (pre-padded) 121 auto panel_corner = Vector2(panel_bounds.x + pad, panel_bounds.y + pad); 122 123 // entity title 124 auto entity_title = format("Entity %s", entity.name); 125 raygui.GuiLabel(Rectangle(panel_corner.x, panel_corner.y, 126 field_label_width, title_height), entity_title.c_str()); 127 // title underline 128 raylib.DrawRectangleLinesEx(Rectangle(panel_corner.x, 129 panel_corner.y + title_height, panel_bounds.width - pad * 2, 4), 1, Colors.GRAY); 130 131 // entity props 132 // 1. transform 133 auto entity_transform = to!string(entity.transform); 134 raygui.GuiLabel(Rectangle(panel_corner.x, panel_corner.y + title_offset, 135 field_label_width, title_height), "transform".c_str()); 136 raygui.GuiTextBox(Rectangle(panel_corner.x + field_label_width, panel_corner.y + title_offset, field_value_width, 137 field_height), entity_transform.c_str(), field_value_text_size, false); 138 139 // - now draw each component section 140 enum first_panel_offset = title_offset + props_offset; 141 auto panel_y_offset = first_panel_offset; // the offset from the y start of the panel (this is based on component index) 142 foreach (i, comp; _inspected) { 143 auto field_names = comp.fields.keys.sort(); 144 auto field_index = 0; 145 146 // corner for the start of this section 147 auto section_corner = Vector2(panel_corner.x, panel_corner.y + panel_y_offset); 148 // header 149 raygui.GuiLabel(Rectangle(section_corner.x, section_corner.y, 150 field_label_width, header_height), comp.obj_class.getName.c_str()); 151 // header underline 152 raylib.DrawRectangleLinesEx(Rectangle(section_corner.x + header_line_margin, 153 section_corner.y + header_height, panel_bounds.width - header_line_margin * 2, 154 1), 1, Colors.GRAY); 155 // list of fields 156 foreach (field_name; field_names) { 157 auto field_val = comp.fields[field_name]; 158 // calculate field corner 159 auto corner = Vector2(section_corner.x, 160 section_corner.y + (header_height + header_padding) + field_index * ( 161 field_padding + field_height)); 162 raygui.GuiLabel(Rectangle(corner.x, corner.y, 163 field_label_width, field_height), field_name.c_str()); 164 raygui.GuiTextBox(Rectangle(corner.x + field_label_width, corner.y, field_value_width, 165 field_height), field_val.c_str(), field_value_text_size, false); 166 field_index++; 167 } 168 panel_y_offset += component_section_heights[i]; // go to the bottom of this section 169 } 170 // raygui.GuiGrid(Rectangle(panel_bounds.x + _panel_scroll.x, panel_bounds.y + _panel_scroll.y, 171 // panel_content_bounds.width, panel_content_bounds.height), 16, 4); 172 } 173 174 /// attach the inspector to an object 175 public void inspect(Entity nt) { 176 assert(_inspected.length == 0, "only one inspector may be open at a time"); 177 open = true; 178 this.entity = nt; 179 // add components 180 _inspected ~= nt.get_all_components.map!(x => new InspectedObject(x)).array; 181 } 182 183 /// close the inspector 184 public void close() { 185 assert(open, "inspector is already closed"); 186 open = false; 187 reset(); 188 } 189 }