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 }