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