1 /++
2 	This module just tries to simplify arsd.terminal.d by removing some features that aren't needed (yet, at least)
3 +/
4 module qui.termwrap;
5 
6 import arsd.terminal;
7 import std.datetime.stopwatch;
8 import std.conv : to;
9 
10 public alias Color = arsd.terminal.Color;
11 
12 /// Input events
13 public struct Event{
14 	/// Keyboard Event
15 	struct Keyboard{
16 		private this(KeyboardEvent event){
17 			key = event.which;
18 		}
19 		//// what key was pressed
20 		dchar key;
21 		/// Non character keys (can match against `this.key`)
22 		/// 
23 		/// copied from arsd.terminal
24 		enum Key : dchar{
25 			Escape = 0x1b + 0xF0000,
26 			F1 = 0x70 + 0xF0000,
27 			F2 = 0x71 + 0xF0000,
28 			F3 = 0x72 + 0xF0000,
29 			F4 = 0x73 + 0xF0000,
30 			F5 = 0x74 + 0xF0000,
31 			F6 = 0x75 + 0xF0000,
32 			F7 = 0x76 + 0xF0000,
33 			F8 = 0x77 + 0xF0000,
34 			F9 = 0x78 + 0xF0000,
35 			F10 = 0x79 + 0xF0000,
36 			F11 = 0x7A + 0xF0000,
37 			F12 = 0x7B + 0xF0000,
38 			LeftArrow = 0x25 + 0xF0000,
39 			RightArrow = 0x27 + 0xF0000,
40 			UpArrow = 0x26 + 0xF0000,
41 			DownArrow = 0x28 + 0xF0000,
42 			Insert = 0x2d + 0xF0000,
43 			Delete = 0x2e + 0xF0000,
44 			Home = 0x24 + 0xF0000,
45 			End = 0x23 + 0xF0000,
46 			PageUp = 0x21 + 0xF0000,
47 			PageDown = 0x22 + 0xF0000,
48 		}
49 		/// Ctrl+Letter keys
50 		enum CtrlKeys : dchar{
51 			CtrlA = 1,
52 			CtrlB = 2,
53 			CtrlC = 3,
54 			CtrlD = 4,
55 			CtrlE = 5,
56 			CtrlF = 6,
57 			CtrlG = 7,
58 			//CtrlJ = 10,
59 			CtrlK = 11,
60 			CtrlL = 12,
61 			CtrlM = 13,
62 			CtrlN = 14,
63 			CtrlO = 15,
64 			CtrlP = 16,
65 			CtrlQ = 17,
66 			CtrlR = 18,
67 			CtrlS = 19,
68 			CtrlT = 20,
69 			CtrlU = 21,
70 			CtrlV = 22,
71 			CtrlW = 23,
72 			CtrlX = 24,
73 			CtrlY = 25,
74 			CtrlZ = 26,
75 		}
76 		/// Returns: true if the key pressed is a character
77 		/// backspace, space, and tab are characters!
78 		@property bool isChar(){
79 			return !(key >= Key.min && key <= Key.max) && !isCtrlKey();
80 		}
81 		/// Returns: true if key is a Ctrl+Letter key
82 		@property bool isCtrlKey(){
83 			return key >= CtrlKeys.min && key <= CtrlKeys.max && key!=8 && key!=9 && key!=10;
84 		}
85 		/// Returns: a string representation of the key pressed
86 		@property string tostring(){
87 			if (isChar())
88 				return to!string(key);
89 			if (isCtrlKey())
90 				return to!string(cast(CtrlKeys)key);
91 			return to!string(cast(Key)key);
92 		}
93 	}
94 	/// Mouse Event
95 	struct Mouse{
96 		/// Buttons
97 		enum Button{
98 			Left, /// Left mouse btn clicked
99 			Right, /// Right mouse btn clicked
100 			Middle, /// Middle mouse btn clicked
101 			ScrollUp, /// Scroll up clicked
102 			ScrollDown, /// Scroll Down clicked
103 			None, /// no button pressed, mouse was hovered. Not supported on all terminals (works on xterm, not on konsole)
104 		}
105 		/// x and y position of cursor
106 		int x, y;
107 		/// what button was clicked
108 		Button button;
109 		/// constructor
110 		this (Button btn, int xPos, int yPos){
111 			x = xPos;
112 			y = yPos;
113 			btn = button;
114 		}
115 		/// constructor, from arsd.terminal.MouseEvent
116 		private this(MouseEvent mouseE){
117 			if (mouseE.buttons == MouseEvent.Button.Left)
118 				this.button = this.Button.Left;
119 			else if (mouseE.buttons == MouseEvent.Button.Right)
120 				this.button = this.Button.Right;
121 			else if (mouseE.buttons == MouseEvent.Button.Middle)
122 				this.button = this.Button.Middle;
123 			else if (mouseE.buttons == MouseEvent.Button.ScrollUp)
124 				this.button = this.Button.ScrollUp;
125 			else if (mouseE.buttons == MouseEvent.Button.ScrollDown)
126 				this.button = this.Button.ScrollDown;
127 			else if (mouseE.buttons == MouseEvent.Button.None)
128 				this.button = this.Button.None;
129 			this.x = mouseE.x;
130 			this.y = mouseE.y;
131 		}
132 	}
133 	/// Resize event
134 	struct Resize{
135 		/// the new width and height after resize
136 		int width, height;
137 	}
138 	/// types of events
139 	enum Type{
140 		Keyboard, /// Keyboard event
141 		Mouse, /// Mouse event
142 		Resize, /// Resize event
143 		HangupInterrupt, /// terminal closed or interrupt (Ctrl+C)
144 	}
145 	/// stores the type of event
146 	private Type _type;
147 	/// union to store events
148 	union{
149 		Keyboard _key; /// stores keyboard event
150 		Mouse _mouse; /// stores mouse event
151 		Resize _resize; /// stores resize event
152 	}
153 	/// Returns: type of event
154 	@property Type type(){
155 		return _type;
156 	}
157 	/// Returns: keyboard event. Make sure you check this.type so the wrong event isn't read
158 	@property Keyboard keyboard(){
159 		return _key;
160 	}
161 	/// Returns: mouse event. Make sure you check this.type so the wrong event isn't read
162 	@property Mouse mouse(){
163 		return _mouse;
164 	}
165 	/// Returns: resize event. Make sure you check this.type so the wrong event isn't read
166 	@property Resize resize(){
167 		return _resize;
168 	}
169 
170 	private this(Keyboard key){
171 		this._type = Type.Keyboard;
172 		this._key = key;
173 	}
174 
175 	private this(Mouse mouse){
176 		this._type = Type.Mouse;
177 		this._mouse = mouse;
178 	}
179 
180 	private this(Resize rsize){
181 		this._type = Type.Resize;
182 		this._resize = rsize;
183 	}
184 	/// constructor, by default, its a hangupInterrupt
185 	this(Type eType){
186 		this._type = eType;
187 	}
188 }
189 
190 
191 
192 /// Wrapper to arsd.terminal to make it bit easier to manage
193 public class TermWrapper{
194 private:
195 	Terminal _term;
196 	RealTimeConsoleInput _input;
197 public:
198 	/// constructor
199 	this(){
200 		_term = Terminal(ConsoleOutputType.cellular);
201 		_input = RealTimeConsoleInput(&_term,ConsoleInputFlags.allInputEvents | ConsoleInputFlags.raw);
202 	}
203 	~this(){
204 		_term.clear;
205 		_term.reset;
206 	}
207 	/// Returns: width of termial
208 	@property int width(){
209 		return _term.width;
210 	}
211 	/// Returns: height of terminal
212 	@property int height(){
213 		return _term.height;
214 	}
215 	/// fills all cells with a character
216 	void fill(dchar ch){
217 		const int _w = width, _h = height;
218 		dchar[] line;
219 		line.length = _w;
220 		line[] = ch;
221 		// write line _h times
222 		for (uint i = 0; i < _h; i ++){
223 			_term.moveTo(0,i);
224 			_term.write(line);
225 		}
226 	}
227 	/// fills a rectangle with a character
228 	void fill(dchar ch, int x1, int x2, int y1, int y2){
229 		dchar[] line;
230 		line.length = (x2 - x1) + 1;
231 		line[] = ch;
232 		foreach(i; y1 .. y2 +1){
233 			_term.moveTo(x1, i);
234 			_term.write(line);
235 		}
236 	}
237 	/// flush to terminal
238 	void flush(){
239 		_term.flush();
240 	}
241 	/// writes a character `ch` at a position `(x, y)`
242 	void put(int x, int y, dchar ch){
243 		_term.moveTo(x, y);
244 		_term.write(ch);
245 	}
246 	/// writes a character `ch` at a position `(x, y)` with `fg` as foreground and `bg` as background color
247 	void put(int x, int y, dchar ch, Color fg, Color bg){
248 		_term.color(fg, bg);
249 		_term.moveTo(x, y);
250 		_term.write(ch);
251 	}
252 	/// sets colors
253 	void color(Color fg, Color bg){
254 		_term.color(fg, bg);
255 	}
256 	/// writes a string at position `(x, y)`
257 	void write(int x, int y, dstring str){
258 		_term.moveTo(x, y);
259 		_term.write(str);
260 	}
261 	/// writes a string at position `(x, y)` with `fg` as foreground and `bg` as background color
262 	void write(int x, int y, dstring str, Color fg, Color bg){
263 		_term.color(fg, bg);
264 		_term.moveTo(x, y);
265 		_term.write(str);
266 	}
267 	/// moves cursor to position
268 	void moveCursor(int x, int y){
269 		_term.moveTo(x, y);
270 	}
271 	/// Set true to show cursor, false to hide cursor
272 	@property bool cursorVisible(bool visibility){
273 		if (visibility)
274 			_term.showCursor;
275 		else
276 			_term.hideCursor;
277 		return visibility;
278 	}
279 	/// waits `msecTimeout` msecs for event to occur. Returns as soon as it occurs (or if one had occurred before calling it)
280 	/// 
281 	/// Returns: true if event occured
282 	bool getEvent(int msecTimeout, ref Event event){
283 		StopWatch sw;
284 		sw.start;
285 		while (msecTimeout - sw.peek.total!"msecs" > 0){
286 			if (_input.timedCheckForInput(cast(int)(msecTimeout - sw.peek.total!"msecs"))){
287 				InputEvent e = _input.nextEvent;
288 				if (e.type == InputEvent.Type.HangupEvent || e.type == InputEvent.Type.UserInterruptionEvent){
289 					event = Event(Event.Type.HangupInterrupt);
290 					return true;
291 				}
292 				if (e.type == InputEvent.Type.KeyboardEvent && 
293 				e.get!(InputEvent.Type.KeyboardEvent).pressed){
294 					event = Event(Event.Keyboard(e.get!(InputEvent.Type.KeyboardEvent)));
295 					return true;
296 				}
297 				if (e.type == InputEvent.Type.MouseEvent){
298 					MouseEvent mouseE = e.get!(InputEvent.Type.MouseEvent);
299 					event = Event(Event.Mouse(mouseE));
300 					return true;
301 				}else if (e.type == InputEvent.Type.SizeChangedEvent){
302 					SizeChangedEvent resize = e.get!(InputEvent.Type.SizeChangedEvent);
303 					event = Event(Event.Resize(resize.newWidth, resize.newHeight));
304 					return true;
305 				}
306 			}
307 		}
308 		sw.stop;
309 		return false;
310 	}
311 }