1 /** diagnostic console */
2 
3 module re.ng.diag.console;
4 
5 import std.conv;
6 import std.string;
7 import std.array;
8 import std.range;
9 import std.format;
10 
11 import re.input.input;
12 import re.core;
13 import re.math;
14 import re.gfx;
15 import re.ng.diag.default_commands;
16 import re.util.interop;
17 static import raygui;
18 
19 /// overlay debug console
20 debug class Console {
21     /// the key that opens the console
22     public Keys key = Keys.KEY_GRAVE;
23     /// the character representation of the console key
24     public char key_char = '`';
25     /// whether the console is open
26     public bool open = false;
27     /// 64 chars of space
28     private enum blank = "\0                                                                ";
29 
30     /// console commands
31     public Command[string] commands;
32     private enum height = 30;
33     private char* console_text;
34     private Appender!(string[]) _history;
35     private int _history_depth = 0;
36 
37     /// represents a command for the debug console
38     struct Command {
39         string name;
40         void function(string[]) action;
41         string help;
42     }
43 
44     /// create a debug console
45     this() {
46         console_text = blank.c_str();
47 
48         // add default commands
49         add_command(Command("help", &DefaultCommands.c_help, "lists available commands"));
50         add_command(Command("entities", &DefaultCommands.c_entities, "lists scene entities"));
51         add_command(Command("dump", &DefaultCommands.c_dump, "dump a component"));
52         add_command(Command("inspect", &DefaultCommands.c_inspect,
53                 "open the inspector on a component"));
54     }
55 
56     private void add_command(Command command) {
57         commands[command.name] = command;
58     }
59 
60     public void update() {
61         // remove all instances of c from str
62         void sstrip(char* str, char c) {
63             char* pr = str;
64             char* pw = str;
65             while (*pr) {
66                 *pw = *pr++;
67                 pw += (*pw != c);
68             }
69             *pw = '\0';
70         }
71 
72         // remove console key from textbox
73         sstrip(console_text, '`');
74 
75         void load_history_entry() {
76             // load a history entry
77             auto entry = _history.data[$ - _history_depth];
78             console_text = entry.c_str();
79         }
80 
81         // arrows can scroll through history
82         if (Input.is_key_pressed(Keys.KEY_UP)) {
83             if (_history_depth < _history.data.length) {
84                 _history_depth++;
85                 load_history_entry();
86             }
87         } else if (Input.is_key_pressed(Keys.KEY_DOWN)) {
88             if (_history_depth > 0) {
89                 _history_depth--;
90                 load_history_entry();
91             } else {
92                 console_text = blank.c_str();
93             }
94         }
95     }
96 
97     public void render() {
98         alias pad = Core.debugger.screen_padding;
99         auto screen_br = Vector2(Core.debugger.ui_bounds.width, Core.debugger.ui_bounds.height);
100         // Core.log.info(format("screen_br: (%s", screen_br));
101         auto console_bg_bounds = Rectangle(pad,
102             screen_br.y - pad - height, screen_br.x - pad * 2, height);
103         // console background
104         // raylib.DrawRectangleRec(console_bg_bounds, bg_col);
105         auto bg_padding = 4;
106         auto console_bounds = Rectangle(console_bg_bounds.x + bg_padding, console_bg_bounds.y + bg_padding,
107             console_bg_bounds.width - bg_padding * 2, console_bg_bounds.height - bg_padding * 2);
108         // console text
109         if (raygui.GuiTextBox(console_bounds, console_text, 64, true)) {
110             auto console_text_str = to!string(console_text);
111             // strip extra whitespace
112             console_text_str = console_text_str.strip();
113             if (console_text_str.length > 0) {
114                 _history_depth = 0; // we just executed a command, no longer in history
115                 _history ~= console_text_str; // add to history
116                 // get raw command string
117                 auto raw_command = console_text_str.split(' ');
118                 process_command(raw_command.front, raw_command.drop(1));
119                 // pass command
120                 console_text[0] = '\0'; // clear text
121             }
122         }
123     }
124 
125     /// process a command in the console
126     public void process_command(string cmd, string[] args) {
127         if (cmd in commands) {
128             commands[cmd].action(args);
129         } else {
130             Core.log.err(format("unrecognized command: %s", cmd));
131         }
132     }
133 }