1 /++
2 	This module contains most of the functions you'll need.
3 	All the 'base' classes, like QWidget are defined in this.
4 +/
5 module qui.qui;
6 
7 import std.datetime.stopwatch;
8 import utils.misc;
9 import utils.lists;
10 import std.conv : to;
11 import qui.termwrap;
12 
13 import qui.utils;
14 
15 /// the default foreground color
16 const Color DEFAULT_FG = Color.white;
17 /// the default background color
18 const Color DEFAULT_BG = Color.black;
19 
20 /// Available colors are in this enum
21 public alias Color = qui.termwrap.Color;
22 /// Availabe Keys (keyboard) for input
23 public alias Key = qui.termwrap.Event.Keyboard.Key;
24 /// Mouse Event
25 public alias MouseEvent = Event.Mouse;
26 /// Keyboard Event
27 public alias KeyboardEvent = Event.Keyboard;
28 
29 /// Used to store position for widgets
30 struct Position{
31 	/// x and y position
32 	integer x = 0, y = 0;
33 	/// Returns: a string representation of Position
34 	string tostring(){
35 		return "{x:"~to!string(x)~",y:"~to!string(y)~"}";
36 	}
37 }
38 
39 /// To store size for widgets
40 /// 
41 /// zero in min/max means no limit
42 struct Size{
43 	private{
44 		uinteger _w = 0, _h = 0;
45 		/// width
46 		@property uinteger width(uinteger newWidth){
47 			if (newWidth == 0)
48 				return _w = 0;
49 			if (minWidth > 0 && newWidth < minWidth){
50 				return _w = minWidth;
51 			}else if (maxWidth > 0 && newWidth > maxWidth){
52 				return _w = maxWidth;
53 			}
54 			return _w = newWidth;
55 		}
56 		/// height
57 		@property uinteger height(uinteger newHeight){
58 			if (newHeight == 0)
59 				return _h = 0;
60 			if (minHeight > 0 && newHeight < minHeight){
61 				return _h = minHeight;
62 			}else if (maxHeight > 0 && newHeight > maxHeight){
63 				return _h = maxHeight;
64 			}
65 			return _h = newHeight;
66 		}
67 	}
68 	/// width
69 	@property uinteger width(){
70 		return _w;
71 	}
72 	/// height
73 	@property uinteger height(){
74 		return _h;
75 	}
76 	
77 	/// minimun width & height. These are "applied" automatically when setting value using `width` or `height`
78 	uinteger minWidth = 0, minHeight = 0;
79 	/// maximum width & height. These are "applied" automatically when setting value using `width` or `height`
80 	uinteger maxWidth = 0, maxHeight = 0;
81 	/// Returns: a string representation of KeyPress, in JSON
82 	string tostring(){
83 		return "{width:"~to!string(_w)~",height:"~to!string(_h)~
84 			",minWidth:"~to!string(minWidth)~",maxWidth:"~to!string(maxWidth)~
85 				",minHeight:"~to!string(minHeight)~",maxHeight:"~to!string(maxHeight)~"}";
86 	}
87 }
88 
89 /// mouseEvent function. Return true if the event should be dropped
90 alias MouseEventFuction = bool delegate(QWidget, MouseEvent);
91 ///keyboardEvent function. Return true if the event should be dropped
92 alias KeyboardEventFunction = bool delegate(QWidget, KeyboardEvent);
93 /// resizeEvent function. Return true if the event should be dropped
94 alias ResizeEventFunction = bool delegate(QWidget);
95 /// activateEvent function. Return true if the event should be dropped
96 alias ActivateEventFunction = bool delegate(QWidget, bool);
97 /// TimerEvent function. Return true if the event should be dropped
98 alias TimerEventFunction = bool delegate(QWidget, uinteger);
99 /// Init function. Return true if the event should be dropped
100 alias InitFunction = bool delegate(QWidget);
101 
102 
103 /// Base class for all widgets, including layouts and QTerminal
104 ///
105 /// Use this as parent-class for new widgets
106 abstract class QWidget{
107 private:
108 	/// stores the position of this widget, relative to it's parent widget's position
109 	Position _position;
110 	/// stores if this widget is the active widget
111 	bool _isActive = false;
112 	/// stores what child widgets want updates
113 	bool[] _requestingUpdate;
114 	/// what key handlers are registered for what keys (key/index)
115 	QWidget[dchar]  _keyHandlers;
116 	/// the parent widget
117 	QWidget _parent = null;
118 	/// the index it is stored at in _parent. -1 if no parent asigned yet
119 	integer _indexInParent = -1;
120 	/// called by owner for initialize event
121 	void initializeCall(){
122 		if (!_customInitEvent || !_customInitEvent(this))
123 			this.initialize();
124 	}
125 	/// called by owner for mouseEvent
126 	void mouseEventCall(MouseEvent mouse){
127 		mouse.x = mouse.x - cast(int)this._position.x;
128 		mouse.y = mouse.y - cast(int)this._position.y;
129 		if (!_customMouseEvent || !_customMouseEvent(this, mouse))
130 			this.mouseEvent(mouse);
131 	}
132 	/// called by owner for keyboardEvent
133 	void keyboardEventCall(KeyboardEvent key){
134 		if (!_customKeyboardEvent || !_customKeyboardEvent(this, key))
135 			this.keyboardEvent(key);
136 	}
137 	/// called by owner for resizeEvent
138 	void resizeEventCall(){
139 		if (!_customResizeEvent || !_customResizeEvent(this))
140 			this.resizeEvent();
141 	}
142 	/// called by owner for activateEvent
143 	void activateEventCall(bool isActive){
144 		_isActive = isActive;
145 		if (!_customActivateEvent || !_customActivateEvent(this, isActive))
146 			this.activateEvent(isActive);
147 	}
148 	/// called by owner for mouseEvent
149 	void timerEventCall(uinteger msecs){
150 		if (!_customTimerEvent || !_customTimerEvent(this, msecs))
151 			this.timerEvent(msecs);
152 	}
153 	/// Called by children of this widget to request updates
154 	void requestUpdate(uinteger index){
155 		if (index < _requestingUpdate.length){
156 			_requestingUpdate[index] = true;
157 			requestUpdate();
158 		}
159 	}
160 	/// Called by children of this widget to register key handlers
161 	bool registerKeyHandler(QWidget widget, dchar key){
162 		if (key in _keyHandlers || _parent is null)
163 			return false;
164 		_keyHandlers[key] = widget;
165 		this.registerKeyHandler(key);
166 		return true;
167 	}
168 protected:
169 	///size of this widget
170 	Size _size;
171 	///whether this widget should be drawn or not
172 	bool _show = true;
173 	/// specifies that how much height (in horizontal layout) or width (in vertical) is given to this widget.
174 	/// The ratio of all widgets is added up and height/width for each widget is then calculated using this
175 	uinteger _sizeRatio = 1;
176 	/// specifies whether this widget should receive the Tab key press, default is false, and should only be changed to true
177 	/// if only required, for example, in text editors
178 	bool _wantsTab = false;
179 	/// whether the widget wants input
180 	bool _wantsInput = false;
181 	/// used to write to terminal
182 	Display _display = null;
183 
184 	/// For cycling between widgets. Returns false, always.
185 	bool cycleActiveWidget(){
186 		return false;
187 	}
188 
189 	/// custom onInit event, if not null, it should be called before doing anything else in init();
190 	InitFunction _customInitEvent;
191 	/// custom mouse event, if not null, it should be called before doing anything else in mouseEvent.
192 	MouseEventFuction _customMouseEvent;
193 	/// custom keyboard event, if not null, it should be called before doing anything else in keyboardEvent.
194 	KeyboardEventFunction _customKeyboardEvent;
195 	/// custom resize event, if not null, it should be called before doing anything else in the resizeEvent
196 	ResizeEventFunction _customResizeEvent;
197 	/// custom onActivate event, if not null, it should be called before doing anything else in activateEvent
198 	ActivateEventFunction _customActivateEvent;
199 	/// custom onTimer event, if not null, it should be called before doing anything else in timerEvent
200 	TimerEventFunction _customTimerEvent;
201 
202 	/// Called by parent to update this widget
203 	void update(){}
204 
205 	/// Called after `_display` has been set and this widget is ready to be used
206 	void initialize(){}
207 	/// Called when mouse is clicked with cursor on this widget.
208 	void mouseEvent(MouseEvent mouse){}
209 	/// Called when key is pressed and this widget is active.
210 	void keyboardEvent(KeyboardEvent key){}
211 	/// Called when widget size is changed, or widget should recalculate it's child widgets' sizes;
212 	void resizeEvent(){}
213 	/// called right after this widget is activated, or de-activated, i.e: is made _activeWidget, or un-made _activeWidget
214 	void activateEvent(bool isActive){}
215 	/// called often. `msecs` is the msecs since last timerEvent, not accurate
216 	void timerEvent(uinteger msecs){}
217 public:
218 	/// Called by itself when it needs to request an update
219 	void requestUpdate(){
220 		if (_parent && _indexInParent > -1 && _indexInParent < _parent._requestingUpdate.length && 
221 		_parent._requestingUpdate[_indexInParent] == false)
222 			_parent.requestUpdate(_indexInParent);
223 	}
224 	/// Called to request this widget to resize at next update
225 	void requestResize(){
226 		if (_parent)
227 			_parent.requestResize();
228 	}
229 	/// Called by itself (not necessarily) to register itself as a key handler
230 	bool registerKeyHandler(dchar key){
231 		return _parent && _indexInParent > -1 && _parent.registerKeyHandler(this, key);
232 	}
233 	/// use to change the custom initialize event
234 	@property InitFunction onInitEvent(InitFunction func){
235 		return _customInitEvent = func;
236 	}
237 	/// use to change the custom mouse event
238 	@property MouseEventFuction onMouseEvent(MouseEventFuction func){
239 		return _customMouseEvent = func;
240 	}
241 	/// use to change the custom keyboard event
242 	@property KeyboardEventFunction onKeyboardEvent(KeyboardEventFunction func){
243 		return _customKeyboardEvent = func;
244 	}
245 	/// use to change the custom resize event
246 	@property ResizeEventFunction onResizeEvent(ResizeEventFunction func){
247 		return _customResizeEvent = func;
248 	}
249 	/// use to change the custom activate event
250 	@property ActivateEventFunction onActivateEvent(ActivateEventFunction func){
251 		return _customActivateEvent = func;
252 	}
253 	/// use to change the custom timer event
254 	@property TimerEventFunction onTimerEvent(TimerEventFunction func){
255 		return _customTimerEvent = func;
256 	}
257 	/// Returns: this widget's parent
258 	@property QWidget parent(){
259 		return _parent;
260 	}
261 	/// Returns: true if this widget is the current active widget
262 	@property bool isActive(){
263 		return _isActive;
264 	}
265 	/// Returns: whether the widget is receiving the Tab key press or not
266 	@property bool wantsTab(){
267 		return _wantsTab;
268 	}
269 	/// ditto
270 	@property bool wantsTab(bool newStatus){
271 		return _wantsTab = newStatus;
272 	}
273 	/// Returns: true if the widget wants input
274 	@property bool wantsInput(){
275 		return _wantsInput;
276 	}
277 	/// Returns: position of cursor to be shown on terminal. (-1,-1) if dont show
278 	@property Position cursorPosition(){
279 		return Position(-1,-1);
280 	}
281 	/// size of width (height/width, depending of Layout.Type it is in) of this widget, in ratio to other widgets in that layout
282 	@property uinteger sizeRatio(){
283 		return _sizeRatio;
284 	}
285 	/// ditto
286 	@property uinteger sizeRatio(uinteger newRatio){
287 		requestResize;
288 		return _sizeRatio = newRatio;
289 	}
290 	/// visibility of the widget. getter
291 	@property bool show(){
292 		return _show;
293 	}
294 	/// visibility of the widget. setter
295 	@property bool show(bool visibility){
296 		requestResize;
297 		return _show = visibility;
298 	}
299 	/// size of the widget. getter
300 	/// **NOTE: call this.requestResize() if you change the size!**
301 	@property ref Size size(){
302 		return _size;
303 	}
304 	/// size of the widget. setter
305 	@property ref Size size(Size newSize){
306 		requestResize;
307 		return _size = newSize;
308 	}
309 }
310 
311 ///Used to place widgets in an order (i.e vertical or horizontal)
312 class QLayout : QWidget{
313 private:
314 	/// array of all the widgets that have been added to this layout
315 	QWidget[] _widgets;
316 	/// stores the layout type, horizontal or vertical
317 	QLayout.Type _type;
318 	/// stores index of active widget. -1 if none. This is useful only for Layouts. For widgets, this stays 0
319 	integer _activeWidgetIndex = -1;
320 
321 	/// gets height/width of a widget using it's sizeRatio and min/max-height/width
322 	static uinteger calculateWidgetSize(QLayout.Type type)(QWidget widget, uinteger ratioTotal, uinteger totalSpace,
323 	ref bool free){
324 		Size wSize = widget.size;
325 		immutable calculatedSize = cast(uinteger)((widget.sizeRatio*totalSpace)/ratioTotal);
326 		static if (type == QLayout.Type.Horizontal){
327 			wSize.width = calculatedSize;
328 			free = wSize.minWidth == 0 && wSize.maxWidth == 0;
329 			return wSize.width;
330 		}else{ // this else just exists to shut up compiler about "statement not reachable"
331 			wSize.height = calculatedSize;
332 			free = wSize.minHeight == 0 && wSize.maxHeight == 0;
333 			return wSize.height;
334 		}
335 	}
336 	
337 	/// recalculates the size of every widget inside layout
338 	void recalculateWidgetsSize(QLayout.Type T)(){
339 		static if (T != QLayout.Type.Horizontal && T != QLayout.Type.Vertical)
340 			assert(false);
341 		FIFOStack!QWidget widgetStack = new FIFOStack!QWidget;
342 		uinteger totalRatio = 0;
343 		uinteger totalSpace = T == QLayout.Type.Horizontal ? _size.width : _size.height;
344 		foreach (widget; _widgets){
345 			if (!widget.show)
346 				continue;
347 			totalRatio += widget.sizeRatio;
348 			widget._size.height = _size.height;
349 			widget._size.width = _size.width;
350 			widgetStack.push(widget);
351 		}
352 		// do widgets with size limits
353 		uinteger limitWRatio, limitWSize; /// totalRatio, and space used of widgets with limits
354 		for (integer i = 0; i < widgetStack.count; i ++){
355 			QWidget widget = widgetStack.pop;
356 			bool free;
357 			immutable uinteger space = calculateWidgetSize!(T)(widget, totalRatio, totalSpace, free);
358 			if (free){
359 				widgetStack.push(widget);
360 				continue;
361 			}
362 			static if (T == QLayout.Type.Horizontal)
363 				widget._size.width = space;
364 			else
365 				widget._size.height = space;
366 			limitWRatio += widget.sizeRatio;
367 			limitWSize += space;
368 		}
369 		totalSpace -= limitWSize;
370 		totalRatio -= limitWRatio;
371 		while (widgetStack.count){
372 			QWidget widget = widgetStack.pop;
373 			bool free;
374 			immutable uinteger space = calculateWidgetSize!(T)(widget, totalRatio, totalSpace, free);
375 			static if (T == QLayout.Type.Horizontal)
376 				widget._size.width = space;
377 			else
378 				widget._size.height = space;
379 			totalRatio -= widget.sizeRatio;
380 			totalSpace -= space;
381 		}
382 		.destroy(widgetStack);
383 	}
384 	/// calculates and assigns widgets positions based on their sizes
385 	void recalculateWidgetsPosition(QLayout.Type T)(){
386 		static if (T != QLayout.Type.Horizontal && T != QLayout.Type.Vertical)
387 			assert(false);
388 		uinteger previousSpace = 0;
389 		foreach(widget; _widgets){
390 			if (widget.show){
391 				static if (T == QLayout.Type.Horizontal){
392 					widget._position.y = 0;
393 					widget._position.x = previousSpace;
394 					previousSpace += widget.size.width;
395 				}else{
396 					widget._position.x = 0;
397 					widget._position.y = previousSpace;
398 					previousSpace += widget.size.height;
399 				}
400 			}
401 		}
402 	}
403 protected:
404 	/// Recalculates size and position for all visible widgets
405 	/// If a widget is too large to fit in, it's visibility is marked false
406 	override void resizeEvent(){
407 		uinteger ratioTotal;
408 		foreach(w; _widgets){
409 			if (w.show){
410 				ratioTotal += w._sizeRatio;
411 			}
412 		}
413 		if (_type == QLayout.Type.Horizontal){
414 			recalculateWidgetsSize!(QLayout.Type.Horizontal);
415 			recalculateWidgetsPosition!(QLayout.Type.Horizontal);
416 		}else{
417 			recalculateWidgetsSize!(QLayout.Type.Vertical);
418 			recalculateWidgetsPosition!(QLayout.Type.Vertical);
419 		}
420 		foreach (widget; _widgets){
421 			this._display.getSlice(widget._display, widget.size.width, widget.size.height,widget._position.x,widget._position.y);
422 			widget.resizeEventCall();
423 		}
424 	}
425 	
426 	/// Redirects the mouseEvent to the appropriate widget
427 	override public void mouseEvent(MouseEvent mouse) {
428 		/// first check if it's already inside active widget, might not have to search through each widget
429 		QWidget activeWidget = null;
430 		if (_activeWidgetIndex > -1)
431 			activeWidget = _widgets[_activeWidgetIndex];
432 		if (activeWidget && mouse.x >= activeWidget._position.x && mouse.x < activeWidget._position.x +activeWidget.size.width 
433 		&& mouse.y >= activeWidget._position.y && mouse.y < activeWidget._position.y + activeWidget.size.height){
434 			activeWidget.mouseEventCall(mouse);
435 		}else{
436 			foreach (i, widget; _widgets){
437 				if (widget.show && widget.wantsInput &&
438 					mouse.x >= widget._position.x && mouse.x < widget._position.x + widget.size.width &&
439 					mouse.y >= widget._position.y && mouse.y < widget._position.y + widget.size.height){
440 					// make it active only if this layout is itself active
441 					if (this.isActive){
442 						if (activeWidget)
443 							activeWidget.activateEventCall(false);
444 						widget.activateEventCall(true);
445 						_activeWidgetIndex = i;
446 					}
447 					widget.mouseEventCall(mouse);
448 					break;
449 				}
450 			}
451 		}
452 	}
453 
454 	/// Redirects the keyboardEvent to appropriate widget
455 	override public void keyboardEvent(KeyboardEvent key){
456 		// check if key handler registered
457 		if (key.key in _keyHandlers)
458 			_keyHandlers[key.key].keyboardEventCall(key);
459 		// check if need to cycle
460 		if ((key.key == '\t' && (_activeWidgetIndex == -1 || !_widgets[_activeWidgetIndex].wantsTab)) || 
461 		key.key == KeyboardEvent.Key.Escape){
462 			this.cycleActiveWidget();
463 		}else if (_activeWidgetIndex > -1){
464 			_widgets[_activeWidgetIndex].keyboardEventCall(key);
465 		}
466 	}
467 
468 	/// override initialize to initliaze child widgets
469 	override void initialize(){
470 		foreach (widget; _widgets){
471 			widget._display = _display.getSlice(1,1, _position.x, _position.y); // just throw in dummy size/position, resize event will fix that
472 			widget.initializeCall();
473 		}
474 	}
475 
476 	/// override timer event to call child widgets' timers
477 	override void timerEvent(uinteger msecs){
478 		foreach (widget; _widgets)
479 			widget.timerEventCall(msecs);
480 	}
481 
482 	/// override activate event
483 	override void activateEvent(bool isActive){
484 		if (isActive){
485 			_activeWidgetIndex = -1;
486 			this.cycleActiveWidget();
487 		}else if (_activeWidgetIndex > -1){
488 			_widgets[_activeWidgetIndex].activateEventCall(isActive);
489 		}
490 	}
491 	
492 	/// called by owner widget to update
493 	override void update(){
494 		foreach(i, widget; _widgets){
495 			if (_requestingUpdate[i] && widget.show){
496 				widget._display.cursor = Position(0,0);
497 				widget.update();
498 				_requestingUpdate[i] = false;
499 			}
500 		}
501 	}
502 	/// called to cycle between actveWidgets. This is called by owner widget
503 	/// 
504 	/// Returns: true if cycled to another widget, false if _activeWidgetIndex set to -1
505 	override bool cycleActiveWidget(){
506 		// check if need to cycle within current active widget
507 		if (_activeWidgetIndex == -1 || !(_widgets[_activeWidgetIndex].cycleActiveWidget())){
508 			integer lastActiveWidgetIndex = _activeWidgetIndex;
509 			for (_activeWidgetIndex ++; _activeWidgetIndex < _widgets.length; _activeWidgetIndex ++){
510 				if (_widgets[_activeWidgetIndex].wantsInput && _widgets[_activeWidgetIndex].show)
511 					break;
512 			}
513 			if (_activeWidgetIndex >= _widgets.length)
514 				_activeWidgetIndex = -1;
515 			
516 			if (lastActiveWidgetIndex != _activeWidgetIndex){
517 				if (lastActiveWidgetIndex > -1)
518 					_widgets[lastActiveWidgetIndex].activateEventCall(false);
519 				if (_activeWidgetIndex > -1)
520 					_widgets[_activeWidgetIndex].activateEventCall(true);
521 			}
522 		}
523 		return _activeWidgetIndex != -1;
524 	}
525 public:
526 	/// Layout type
527 	enum Type{
528 		Vertical,
529 		Horizontal,
530 	}
531 	/// constructor
532 	this(QLayout.Type type){
533 		_type = type;
534 	}
535 	/// Returns: whether the widget is receiving the Tab key press or not
536 	override @property bool wantsTab(){
537 		foreach (widget; _widgets){
538 			if (widget.wantsTab)
539 				return true;
540 		}
541 		return false;
542 	}
543 	/// Returns: true if the widget wants input
544 	override @property bool wantsInput(){
545 		foreach (widget; _widgets){
546 			if (widget.wantsInput)
547 				return true;
548 		}
549 		return false;
550 	}
551 	/// Returns: true if the cursor should be visible if this widget is active
552 	override @property Position cursorPosition(){
553 		// just do a hack, and check only for active widget
554 		if (_activeWidgetIndex == -1)
555 			return Position(-1, -1);
556 		Position cursorPosition = _widgets[_activeWidgetIndex].cursorPosition;
557 		if (cursorPosition == Position(-1, -1))
558 			return cursorPosition;
559 		return Position(_widgets[_activeWidgetIndex]._position.x + cursorPosition.x,
560 			_widgets[_activeWidgetIndex]._position.y + cursorPosition.y);
561 	}
562 	
563 	/// adds (appends) a widget to the widgetList, and makes space for it
564 	/// 
565 	/// If there a widget is too large, it's marked as not visible
566 	void addWidget(QWidget widget){
567 		widget._parent = this;
568 		widget._indexInParent = _widgets.length;
569 		//add it to array
570 		_widgets ~= widget;
571 		// make space in _requestingUpdate
572 		_requestingUpdate ~= true;
573 	}
574 	/// adds (appends) widgets to the widgetList, and makes space for them
575 	/// 
576 	/// If there a widget is too large, it's marked as not visible
577 	void addWidget(QWidget[] widgets){
578 		foreach (i, widget; widgets){
579 			widget._parent = this;
580 			widget._indexInParent = _widgets.length+i;
581 		}
582 		// add to array
583 		_widgets ~= widgets.dup;
584 		// make space in _requestingUpdate
585 		bool[] reqUpdates;
586 		reqUpdates.length = widgets.length;
587 		reqUpdates[] = true;
588 		_requestingUpdate ~= reqUpdates;
589 	}
590 }
591 
592 /// Used to write to display by widgets
593 class Display{
594 private:
595 	/// width & height
596 	uinteger _width, _height;
597 	/// x and y offsets
598 	uinteger _xOff, _yOff;
599 	/// cursor position, relative to _xOff and _yOff
600 	Position _cursor;
601 	/// the terminal
602 	TermWrapper _term;
603 	/// constructor for when this buffer is just a slice of the actual buffer
604 	this(uinteger w, uinteger h, uinteger xOff, uinteger yOff, TermWrapper terminal){
605 		_xOff = xOff;
606 		_yOff = yOff;
607 		_width = w;
608 		_height = h;
609 		_term = terminal;
610 	}
611 	/// Returns: a "slice" of this buffer, that is only limited to some rectangular area
612 	Display getSlice(uinteger w, uinteger h, uinteger x, uinteger y){
613 		return new Display(w, h, _xOff + x, _yOff + y, _term);
614 	}
615 	/// modifies an existing Display to act as a "slice"
616 	void getSlice(Display sliced, uinteger w, uinteger h, uinteger x, uinteger y){
617 		sliced._width = w;
618 		sliced._height = h;
619 		sliced._xOff = _xOff + x;
620 		sliced._yOff = _yOff + y;
621 	}
622 public:
623 	/// constructor
624 	this(uinteger w, uinteger h, TermWrapper terminal){
625 		_width = w;
626 		_height = h;
627 		_term = terminal;
628 	}
629 	~this(){
630 
631 	}
632 	/// Returns: cursor  position
633 	@property Position cursor(){
634 		return _cursor;
635 	}
636 	/// ditto
637 	@property Position cursor(Position newPos){
638 		if (newPos.x >= _width)
639 			newPos.x = _width-1;
640 		if (newPos.y >= _height)
641 			newPos.y = _height-1;
642 		if (newPos.x < 0)
643 			newPos.x = 0;
644 		if (newPos.y < 0)
645 			newPos.y = 0;
646 		return _cursor = newPos;
647 	}
648 	/// sets background and foreground color
649 	void colors(Color fg, Color bg){
650 		_term.color(fg, bg);
651 	}
652 	/// Writes a line. if string has more characters than there is space for, extra characters will be ignored.
653 	/// Tab character is converted to a single space character
654 	void write(dstring str, Color fg, Color bg){
655 		_term.color(fg, bg);
656 		this.write(str);
657 	}
658 	/// ditto
659 	void write(dstring str){
660 		foreach (c; str){
661 			if (_cursor.x >= _width){
662 				_cursor.x = 0;
663 				_cursor.y ++;
664 			}
665 			if (_cursor.x < _width && _cursor.y < _height)
666 				_term.put(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), c == '\t' || c == '\n' ? ' ' : c);
667 			else
668 				break;
669 			_cursor.x ++;
670 		}
671 	}
672 	/// fills all remaining cells with a character
673 	void fill(dchar c, Color fg, Color bg){
674 		dchar[] line;
675 		line.length = _width;
676 		line[] = c;
677 		_term.color(fg, bg);
678 		while (_cursor.y < _height){
679 			_term.write(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), cast(dstring)line[0 .. _width - _cursor.x]);
680 			_cursor.y ++;
681 			_cursor.x = 0;
682 		}
683 	}
684 	/// fills rest of current line with a character
685 	void fillLine(dchar c, Color fg, Color bg, uinteger max = 0){
686 		dchar[] line;
687 		line.length =  max < _width - _cursor.x && max > 0 ? max : _width - _cursor.x;
688 		line[] = c;
689 		_term.color(fg, bg);
690 		_term.write(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), cast(dstring)line);
691 		_cursor.x += line.length;
692 		if (_cursor.x >= _width -1){
693 			_cursor.y ++;
694 			_cursor.x = 0;
695 		}
696 	}
697 }
698 
699 /// A terminal (as the name says).
700 class QTerminal : QLayout{
701 private:
702 	/// To actually access the terminal
703 	TermWrapper _termWrap;
704 	/// Whether to call resize before next update
705 	bool _requestingResize;
706 	/// set to false to stop UI loop in run()
707 	bool _isRunning;
708 
709 	/// Reads InputEvent and calls appropriate functions to address those events
710 	void readEvent(Event event){
711 		if (event.type == Event.Type.HangupInterrupt){
712 			_isRunning = false;
713 		}else if (event.type == Event.Type.Keyboard){
714 			KeyboardEvent kPress = event.keyboard;
715 			this.keyboardEventCall(kPress);
716 		}else if (event.type == Event.Type.Mouse){
717 			this.mouseEventCall(event.mouse);
718 		}else if (event.type == Event.Type.Resize){
719 			//update self size
720 			_size.height = event.resize.height;
721 			_size.width = event.resize.width;
722 			//call size change on all widgets
723 			resizeEventCall();
724 		}
725 	}
726 
727 protected:
728 
729 	override void resizeEvent(){
730 		_requestingResize = false;
731 		super.resizeEvent();
732 	}
733 	
734 	override void update(){
735 		// resize if needed
736 		if (_requestingResize)
737 			this.resizeEventCall();
738 		super.update();
739 		// check if need to show/hide cursor
740 		Position cursorPos = this.cursorPosition;
741 		if (cursorPos == Position(-1, -1)){
742 			_termWrap.cursorVisible = false;
743 		}else{
744 			_termWrap.moveCursor(cast(int)cursorPos.x, cast(int)cursorPos.y);
745 			_termWrap.cursorVisible = true;
746 		}
747 		_termWrap.flush;
748 	}
749 
750 public:
751 	/// text color, and background color
752 	Color textColor, backgroundColor;
753 	/// time to wait between timer events (milliseconds)
754 	ushort timerMsecs;
755 	/// constructor
756 	this(QLayout.Type displayType = QLayout.Type.Vertical, ushort timerDuration = 500){
757 		super(displayType);
758 
759 		textColor = DEFAULT_FG;
760 		backgroundColor = DEFAULT_BG;
761 		timerMsecs = timerDuration;
762 
763 		_termWrap = new TermWrapper();
764 		_display = new Display(1,1, _termWrap);
765 		this._isActive = true; // so it can make other widgets active on mouse events
766 	}
767 	~this(){
768 		.destroy(_termWrap);
769 		.destroy(_display);
770 	}
771 
772 	/// Called to make container widgets (QLayouts) recalcualte widgets' sizes before update;
773 	override void requestResize(){
774 		_requestingResize = true;
775 	}
776 
777 	/// stops UI loop. **not instantly**, if it is in-between updates, calling event functions, or timers, it will complete those first
778 	void terminate(){
779 		_isRunning = false;
780 	}
781 	
782 	/// starts the UI loop
783 	void run(){
784 		// init termbox
785 		_size.width = _termWrap.width();
786 		_size.height = _termWrap.height();
787 		//ready
788 		initializeCall();
789 		resizeEventCall();
790 		//draw the whole thing
791 		update();
792 		_isRunning = true;
793 		// the stop watch, to count how much time has passed after each timerEvent
794 		StopWatch sw = StopWatch(AutoStart.yes);
795 		while (_isRunning){
796 			int timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
797 			Event event;
798 			while (_termWrap.getEvent(timeout, event) > 0){
799 				readEvent(event);
800 				timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
801 				update();
802 			}
803 			if (sw.peek.total!"msecs" >= timerMsecs){
804 				this.timerEventCall(sw.peek.total!"msecs");
805 				sw.reset;
806 				sw.start;
807 				update();
808 			}
809 		}
810 	}
811 }