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 }