1 /** 2 Grimoire 3 Copyright (c) 2017 Enalye 4 5 This software is provided 'as-is', without any express or implied warranty. 6 In no event will the authors be held liable for any damages arising 7 from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute 11 it freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; 14 you must not claim that you wrote the original software. 15 If you use this software in a product, an acknowledgment 16 in the product documentation would be appreciated but 17 is not required. 18 19 2. Altered source versions must be plainly marked as such, 20 and must not be misrepresented as being the original software. 21 22 3. This notice may not be removed or altered from any source distribution. 23 */ 24 25 module ui.console.console; 26 27 import common.all; 28 import core.all; 29 import render.all; 30 31 import ui.list.all; 32 import ui.widget; 33 import ui.layout; 34 import ui.inputfield; 35 import ui.overlay; 36 import ui.text; 37 38 private { 39 Console _console; 40 ConsoleHandler _consoleHandler; 41 42 alias FunctionCallback = void function(string[]); 43 } 44 45 void setupConsole(string invokeKey) { 46 _consoleHandler = new ConsoleHandler(invokeKey); 47 _console = new Console; 48 addWidget(_consoleHandler); 49 } 50 51 void addConsoleCmd(string cmd, WidgetCallback callback) { 52 if(!_console) 53 return; 54 _console.addCmd(cmd, callback); 55 } 56 57 void addConsoleCmd(string cmd, FunctionCallback callback) { 58 if(!_console) 59 return; 60 _console.addCmd(cmd, callback); 61 } 62 63 void removeConsoleCmd(string cmd) { 64 if(!_console) 65 return; 66 _console.removeCmd(cmd); 67 } 68 69 void logConsole(string log) { 70 if(!_console) 71 return; 72 _console.addMessage(log); 73 } 74 75 private class ConsoleHandler: Widget { 76 private { 77 bool _isToggled = false; 78 string _invokeKey; 79 Timer _timer; 80 } 81 82 this(string invokeKey) { 83 _invokeKey = invokeKey; 84 } 85 86 override void onEvent(Event event) {} 87 88 override void update(float deltaTime) { 89 if(_invokeKey.length && getKeyDown(_invokeKey)) 90 toggleConsole(); 91 92 if(_timer.isRunning) { 93 _timer.update(deltaTime); 94 _console.position = lerp( 95 Vec2f(_console.size.x /2f, -_console.size.y / 2f), 96 _console.size / 2f, 97 easeInOutSine(_timer.time)); 98 99 if(!_timer.isRunning && !_isToggled) 100 stopOverlay(); 101 } 102 } 103 104 override void draw() {} 105 106 void toggleConsole() { 107 _isToggled = !_isToggled; 108 if(_isToggled) { 109 _console.setup(); 110 if(!_timer.isRunning) { 111 setOverlay(_console); 112 _timer.start(.25f); 113 } 114 else 115 _timer.isReversed = false; 116 } 117 else { 118 if(!_timer.isRunning) 119 _timer.startReverse(.25f); 120 else 121 _timer.isReversed = true; 122 } 123 } 124 } 125 126 private class Console: AnchoredLayout { 127 private { 128 LogList _log; 129 InputField _inputField; 130 WidgetCallback[string] _widgetCallbacks; 131 FunctionCallback[string] _functionCallbacks; 132 Sprite _background; 133 } 134 135 this() { 136 _size = Vec2f(screenWidth, screenHeight / 2f); 137 138 float inputFieldRatio = 25f / _size.y; 139 float inputFieldHeight = _size.y * inputFieldRatio; 140 141 _inputField = new InputField(Vec2f(_size.x, inputFieldHeight)); 142 _log = new LogList(_size - Vec2f(0f, inputFieldHeight)); 143 144 _background = fetch!Sprite("gui_texel"); 145 _background.size = _size; 146 147 addChild(_log, Vec2f(.5f, .5f - inputFieldRatio / 2f), Vec2f(1f, 1f - inputFieldRatio)); 148 addChild(_inputField, Vec2f(.5f, 1f - inputFieldRatio / 2f), Vec2f(1f, inputFieldRatio)); 149 } 150 151 override void onEvent(Event event) { 152 super.onEvent(event); 153 154 switch(event.type) with(EventType) { 155 case KeyEnter: 156 if(_inputField.hasFocus) { 157 string text = _inputField.text; 158 if(text.length) { 159 parse(text); 160 _inputField.clear(); 161 } 162 } 163 break; 164 default: 165 break; 166 } 167 } 168 169 override void draw() { 170 _background.texture.setColorMod(Color.black * .25f); 171 _background.draw(_position); 172 super.draw(); 173 } 174 175 void setup() { 176 _inputField.hasFocus = true; 177 } 178 179 void addCmd(string cmd, WidgetCallback callback) { 180 _widgetCallbacks[cmd] = callback; 181 } 182 183 void addCmd(string cmd, FunctionCallback callback) { 184 _functionCallbacks[cmd] = callback; 185 } 186 187 void removeCmd(string cmd) { 188 _widgetCallbacks.remove(cmd); 189 _functionCallbacks.remove(cmd); 190 } 191 192 void addMessage(string message) { 193 //The bold tag is temporary since the font in LogList is not properly rendered. 194 _log.addChild(new Text("{b}" ~ message)); 195 } 196 197 protected void parse(string text) { 198 import std.array: split; 199 auto parameters = text.split; 200 if(!parameters.length) 201 return; 202 auto widgetCallback = parameters[0] in _widgetCallbacks; 203 if(widgetCallback) { 204 Event event; 205 event.type = EventType.Callback; 206 event.id = (*widgetCallback).id; 207 event.sarray = parameters[1..$]; 208 (*widgetCallback).widget.onEvent(event); 209 return; 210 } 211 auto functionCallback = parameters[0] in _functionCallbacks; 212 if(functionCallback) { 213 (*functionCallback)(parameters[1..$]); 214 return; 215 } 216 addMessage("Invalid command \'" ~ parameters[0] ~ "\'."); 217 } 218 }