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 	/// the key used for cycling active widget, defaults to tab
121 	dchar _activeWidgetCycleKey = '\t';
122 	/// called by owner for initialize event
123 	void initializeCall(){
124 		if (!_customInitEvent || !_customInitEvent(this))
125 			this.initialize();
126 	}
127 	/// called by owner for mouseEvent
128 	void mouseEventCall(MouseEvent mouse){
129 		mouse.x = mouse.x - cast(int)this._position.x;
130 		mouse.y = mouse.y - cast(int)this._position.y;
131 		if (!_customMouseEvent || !_customMouseEvent(this, mouse))
132 			this.mouseEvent(mouse);
133 	}
134 	/// called by owner for keyboardEvent
135 	void keyboardEventCall(KeyboardEvent key){
136 		if (!_customKeyboardEvent || !_customKeyboardEvent(this, key))
137 			this.keyboardEvent(key);
138 	}
139 	/// called by owner for resizeEvent
140 	void resizeEventCall(){
141 		if (!_customResizeEvent || !_customResizeEvent(this))
142 			this.resizeEvent();
143 	}
144 	/// called by owner for activateEvent
145 	void activateEventCall(bool isActive){
146 		_isActive = isActive;
147 		if (!_customActivateEvent || !_customActivateEvent(this, isActive))
148 			this.activateEvent(isActive);
149 	}
150 	/// called by owner for mouseEvent
151 	void timerEventCall(uinteger msecs){
152 		if (!_customTimerEvent || !_customTimerEvent(this, msecs))
153 			this.timerEvent(msecs);
154 	}
155 	/// Called by children of this widget to request updates
156 	void requestUpdate(uinteger index){
157 		if (index < _requestingUpdate.length){
158 			_requestingUpdate[index] = true;
159 			requestUpdate();
160 		}
161 	}
162 	/// Called by children of this widget to register key handlers
163 	bool registerKeyHandler(QWidget widget, dchar key){
164 		if (key in _keyHandlers || _parent is null)
165 			return false;
166 		_keyHandlers[key] = widget;
167 		this.registerKeyHandler(key);
168 		return true;
169 	}
170 protected:
171 	///size of this widget
172 	Size _size;
173 	///whether this widget should be drawn or not
174 	bool _show = true;
175 	/// specifies that how much height (in horizontal layout) or width (in vertical) is given to this widget.
176 	/// The ratio of all widgets is added up and height/width for each widget is then calculated using this
177 	uinteger _sizeRatio = 1;
178 	/// specifies whether this widget should receive the Tab key press, default is false, and should only be changed to true
179 	/// if only required, for example, in text editors
180 	bool _wantsTab = false;
181 	/// whether the widget wants input
182 	bool _wantsInput = false;
183 	/// used to write to terminal
184 	Display _display = null;
185 
186 	/// For cycling between widgets. Returns false, always.
187 	bool cycleActiveWidget(){
188 		return false;
189 	}
190 	/// activate the passed widget if this is actually the correct widget, return if it was activated or not
191 	bool searchAndActivateWidget(QWidget target) {
192 		if (this == target) {
193 			this.activateEventCall(true);
194 			return true;
195 		}
196 		return false;
197 	}
198 
199 	/// changes the key used for cycling active widgets
200 	void setActiveWidgetCycleKey(dchar newKey){
201 		this._activeWidgetCycleKey = newKey;
202 	}
203 
204 	/// custom onInit event, if not null, it should be called before doing anything else in init();
205 	InitFunction _customInitEvent;
206 	/// custom mouse event, if not null, it should be called before doing anything else in mouseEvent.
207 	MouseEventFuction _customMouseEvent;
208 	/// custom keyboard event, if not null, it should be called before doing anything else in keyboardEvent.
209 	KeyboardEventFunction _customKeyboardEvent;
210 	/// custom resize event, if not null, it should be called before doing anything else in the resizeEvent
211 	ResizeEventFunction _customResizeEvent;
212 	/// custom onActivate event, if not null, it should be called before doing anything else in activateEvent
213 	ActivateEventFunction _customActivateEvent;
214 	/// custom onTimer event, if not null, it should be called before doing anything else in timerEvent
215 	TimerEventFunction _customTimerEvent;
216 
217 	/// Called by parent to update this widget
218 	void update(){}
219 
220 	/// Called after `_display` has been set and this widget is ready to be used
221 	void initialize(){}
222 	/// Called when mouse is clicked with cursor on this widget.
223 	void mouseEvent(MouseEvent mouse){}
224 	/// Called when key is pressed and this widget is active.
225 	void keyboardEvent(KeyboardEvent key){}
226 	/// Called when widget size is changed, or widget should recalculate it's child widgets' sizes;
227 	void resizeEvent(){}
228 	/// called right after this widget is activated, or de-activated, i.e: is made _activeWidget, or un-made _activeWidget
229 	void activateEvent(bool isActive){}
230 	/// called often. `msecs` is the msecs since last timerEvent, not accurate
231 	void timerEvent(uinteger msecs){}
232 public:
233 	/// Called by itself when it needs to request an update
234 	void requestUpdate(){
235 		if (_parent && _indexInParent > -1 && _indexInParent < _parent._requestingUpdate.length && 
236 		_parent._requestingUpdate[_indexInParent] == false)
237 			_parent.requestUpdate(_indexInParent);
238 	}
239 	/// Called to request this widget to resize at next update
240 	void requestResize(){
241 		if (_parent)
242 			_parent.requestResize();
243 	}
244 	/// Called by itself (not necessarily) to register itself as a key handler
245 	bool registerKeyHandler(dchar key){
246 		return _parent && _indexInParent > -1 && _parent.registerKeyHandler(this, key);
247 	}
248 	/// use to change the custom initialize event
249 	@property InitFunction onInitEvent(InitFunction func){
250 		return _customInitEvent = func;
251 	}
252 	/// use to change the custom mouse event
253 	@property MouseEventFuction onMouseEvent(MouseEventFuction func){
254 		return _customMouseEvent = func;
255 	}
256 	/// use to change the custom keyboard event
257 	@property KeyboardEventFunction onKeyboardEvent(KeyboardEventFunction func){
258 		return _customKeyboardEvent = func;
259 	}
260 	/// use to change the custom resize event
261 	@property ResizeEventFunction onResizeEvent(ResizeEventFunction func){
262 		return _customResizeEvent = func;
263 	}
264 	/// use to change the custom activate event
265 	@property ActivateEventFunction onActivateEvent(ActivateEventFunction func){
266 		return _customActivateEvent = func;
267 	}
268 	/// use to change the custom timer event
269 	@property TimerEventFunction onTimerEvent(TimerEventFunction func){
270 		return _customTimerEvent = func;
271 	}
272 	/// Returns: this widget's parent
273 	@property QWidget parent(){
274 		return _parent;
275 	}
276 	/// Returns: true if this widget is the current active widget
277 	@property bool isActive(){
278 		return _isActive;
279 	}
280 	/// Returns: whether the widget is receiving the Tab key press or not
281 	@property bool wantsTab(){
282 		return _wantsTab;
283 	}
284 	/// ditto
285 	@property bool wantsTab(bool newStatus){
286 		return _wantsTab = newStatus;
287 	}
288 	/// Returns: true if the widget wants input
289 	@property bool wantsInput(){
290 		return _wantsInput;
291 	}
292 	/// Returns: position of cursor to be shown on terminal. (-1,-1) if dont show
293 	@property Position cursorPosition(){
294 		return Position(-1,-1);
295 	}
296 	/// size of width (height/width, depending of Layout.Type it is in) of this widget, in ratio to other widgets in that layout
297 	@property uinteger sizeRatio(){
298 		return _sizeRatio;
299 	}
300 	/// ditto
301 	@property uinteger sizeRatio(uinteger newRatio){
302 		requestResize;
303 		return _sizeRatio = newRatio;
304 	}
305 	/// visibility of the widget. getter
306 	@property bool show(){
307 		return _show;
308 	}
309 	/// visibility of the widget. setter
310 	@property bool show(bool visibility){
311 		requestResize;
312 		return _show = visibility;
313 	}
314 	/// size of the widget. getter
315 	/// **NOTE: call this.requestResize() if you change the size!**
316 	@property ref Size size(){
317 		return _size;
318 	}
319 	/// size of the widget. setter
320 	@property ref Size size(Size newSize){
321 		requestResize;
322 		return _size = newSize;
323 	}
324 }
325 
326 ///Used to place widgets in an order (i.e vertical or horizontal)
327 class QLayout : QWidget{
328 private:
329 	/// array of all the widgets that have been added to this layout
330 	QWidget[] _widgets;
331 	/// stores the layout type, horizontal or vertical
332 	QLayout.Type _type;
333 	/// stores index of active widget. -1 if none. This is useful only for Layouts. For widgets, this stays 0
334 	integer _activeWidgetIndex = -1;
335 	/// Color to fill with in unoccupied space
336 	Color _fillColor;
337 
338 	/// gets height/width of a widget using it's sizeRatio and min/max-height/width
339 	static uinteger calculateWidgetSize(QLayout.Type type)(QWidget widget, uinteger ratioTotal, uinteger totalSpace,
340 	ref bool free){
341 		Size wSize = widget.size;
342 		immutable calculatedSize = cast(uinteger)((widget.sizeRatio*totalSpace)/ratioTotal);
343 		static if (type == QLayout.Type.Horizontal){
344 			wSize.width = calculatedSize;
345 			free = wSize.minWidth == 0 && wSize.maxWidth == 0;
346 			return wSize.width;
347 		}else{ // this else just exists to shut up compiler about "statement not reachable"
348 			wSize.height = calculatedSize;
349 			free = wSize.minHeight == 0 && wSize.maxHeight == 0;
350 			return wSize.height;
351 		}
352 	}
353 	
354 	/// recalculates the size of every widget inside layout
355 	void recalculateWidgetsSize(QLayout.Type T)(){
356 		static if (T != QLayout.Type.Horizontal && T != QLayout.Type.Vertical)
357 			assert(false);
358 		FIFOStack!QWidget widgetStack = new FIFOStack!QWidget;
359 		uinteger totalRatio = 0;
360 		uinteger totalSpace = T == QLayout.Type.Horizontal ? _size.width : _size.height;
361 		foreach (widget; _widgets){
362 			if (!widget.show)
363 				continue;
364 			totalRatio += widget.sizeRatio;
365 			widget._size.height = _size.height;
366 			widget._size.width = _size.width;
367 			widgetStack.push(widget);
368 		}
369 		// do widgets with size limits
370 		uinteger limitWRatio, limitWSize; /// totalRatio, and space used of widgets with limits
371 		for (integer i = 0; i < widgetStack.count; i ++){
372 			QWidget widget = widgetStack.pop;
373 			bool free;
374 			immutable uinteger space = calculateWidgetSize!(T)(widget, totalRatio, totalSpace, free);
375 			if (free){
376 				widgetStack.push(widget);
377 				continue;
378 			}
379 			static if (T == QLayout.Type.Horizontal)
380 				widget._size.width = space;
381 			else
382 				widget._size.height = space;
383 			limitWRatio += widget.sizeRatio;
384 			limitWSize += space;
385 		}
386 		totalSpace -= limitWSize;
387 		totalRatio -= limitWRatio;
388 		while (widgetStack.count){
389 			QWidget widget = widgetStack.pop;
390 			bool free;
391 			immutable uinteger space = calculateWidgetSize!(T)(widget, totalRatio, totalSpace, free);
392 			static if (T == QLayout.Type.Horizontal)
393 				widget._size.width = space;
394 			else
395 				widget._size.height = space;
396 			totalRatio -= widget.sizeRatio;
397 			totalSpace -= space;
398 		}
399 		.destroy(widgetStack);
400 	}
401 	/// calculates and assigns widgets positions based on their sizes
402 	void recalculateWidgetsPosition(QLayout.Type T)(){
403 		static if (T != QLayout.Type.Horizontal && T != QLayout.Type.Vertical)
404 			assert(false);
405 		uinteger previousSpace = 0;
406 		foreach(widget; _widgets){
407 			if (widget.show){
408 				static if (T == QLayout.Type.Horizontal){
409 					widget._position.y = 0;
410 					widget._position.x = previousSpace;
411 					previousSpace += widget.size.width;
412 				}else{
413 					widget._position.x = 0;
414 					widget._position.y = previousSpace;
415 					previousSpace += widget.size.height;
416 				}
417 			}
418 		}
419 	}
420 protected:
421 	/// Recalculates size and position for all visible widgets
422 	/// If a widget is too large to fit in, it's visibility is marked false
423 	override void resizeEvent(){
424 		uinteger ratioTotal;
425 		foreach(w; _widgets){
426 			if (w.show){
427 				ratioTotal += w._sizeRatio;
428 			}
429 		}
430 		if (_type == QLayout.Type.Horizontal){
431 			recalculateWidgetsSize!(QLayout.Type.Horizontal);
432 			recalculateWidgetsPosition!(QLayout.Type.Horizontal);
433 		}else{
434 			recalculateWidgetsSize!(QLayout.Type.Vertical);
435 			recalculateWidgetsPosition!(QLayout.Type.Vertical);
436 		}
437 		foreach (i, widget; _widgets){
438 			this._display.getSlice(widget._display, widget.size.width, widget.size.height,widget._position.x,widget._position.y);
439 			widget.resizeEventCall();
440 		}
441 	}
442 	
443 	/// Redirects the mouseEvent to the appropriate widget
444 	override public void mouseEvent(MouseEvent mouse) {
445 		/// first check if it's already inside active widget, might not have to search through each widget
446 		QWidget activeWidget = null;
447 		if (_activeWidgetIndex > -1)
448 			activeWidget = _widgets[_activeWidgetIndex];
449 		if (activeWidget && mouse.x >= activeWidget._position.x && mouse.x < activeWidget._position.x +activeWidget.size.width 
450 		&& mouse.y >= activeWidget._position.y && mouse.y < activeWidget._position.y + activeWidget.size.height){
451 			activeWidget.mouseEventCall(mouse);
452 		}else{
453 			foreach (i, widget; _widgets){
454 				if (widget.show && widget.wantsInput &&
455 					mouse.x >= widget._position.x && mouse.x < widget._position.x + widget.size.width &&
456 					mouse.y >= widget._position.y && mouse.y < widget._position.y + widget.size.height){
457 					// make it active only if this layout is itself active
458 					if (this.isActive){
459 						if (activeWidget)
460 							activeWidget.activateEventCall(false);
461 						widget.activateEventCall(true);
462 						_activeWidgetIndex = i;
463 					}
464 					widget.mouseEventCall(mouse);
465 					break;
466 				}
467 			}
468 		}
469 	}
470 
471 	/// Redirects the keyboardEvent to appropriate widget
472 	override public void keyboardEvent(KeyboardEvent key){
473 		// check if key handler registered
474 		if (key.key in _keyHandlers)
475 			_keyHandlers[key.key].keyboardEventCall(key);
476 		// check if need to cycle
477 		if (key.key == _activeWidgetCycleKey || key.key == KeyboardEvent.Key.Escape){
478 			if (key.key == '\t'){ // now need to make sure active widget doesnt want to catch tab
479 				if (_activeWidgetIndex == -1)
480 					this.cycleActiveWidget();
481 				else{
482 					if (_widgets[_activeWidgetIndex].wantsTab)
483 						_widgets[_activeWidgetIndex].keyboardEventCall(key);
484 					else
485 						this.cycleActiveWidget();
486 				}
487 			}else
488 				this.cycleActiveWidget();
489 		}else if (_activeWidgetIndex > -1 && (key.key != '\t' || _widgets[_activeWidgetIndex].wantsTab))
490 			_widgets[_activeWidgetIndex].keyboardEventCall(key);
491 	}
492 
493 	/// override initialize to initliaze child widgets
494 	override void initialize(){
495 		foreach (widget; _widgets){
496 			widget._display = _display.getSlice(1,1, _position.x, _position.y); // just throw in dummy size/position, resize event will fix that
497 			widget.initializeCall();
498 		}
499 	}
500 
501 	/// override timer event to call child widgets' timers
502 	override void timerEvent(uinteger msecs){
503 		foreach (widget; _widgets)
504 			widget.timerEventCall(msecs);
505 	}
506 
507 	/// override activate event
508 	override void activateEvent(bool isActive){
509 		if (isActive){
510 			_activeWidgetIndex = -1;
511 			this.cycleActiveWidget();
512 		}else if (_activeWidgetIndex > -1){
513 			_widgets[_activeWidgetIndex].activateEventCall(isActive);
514 		}
515 	}
516 	
517 	/// called by owner widget to update
518 	override void update(){
519 		uinteger space = 0;
520 		foreach(i, widget; _widgets){
521 			if (widget.show){
522 				if (_requestingUpdate[i]){
523 					widget._display.cursor = Position(0,0);
524 					widget.update();
525 					_requestingUpdate[i] = false;
526 				}
527 				if (_type == Type.Horizontal){
528 					space += widget.size.width;
529 					if (widget.size.height < this._size.height){
530 						foreach (y; widget.size.height ..  this._size.height){
531 							_display.cursor = Position(widget._position.x, y);
532 							_display.fillLine(' ', _fillColor, _fillColor, widget.size.width);
533 						}
534 					}
535 				}else{
536 					space += widget.size.height;
537 					if (widget.size.width < this._size.width){
538 						immutable lineWidth = _size.width - widget.size.width;
539 						foreach (y; 0 .. widget.size.height){
540 							_display.cursor = Position(widget.size.width, widget._position.y + y);
541 							_display.fillLine(' ', _fillColor, _fillColor, lineWidth);
542 						}
543 					}
544 				}
545 			}
546 		}
547 		if (_type == Type.Horizontal && space < this._size.width){
548 			immutable uinteger lineWidth = this._size.width - space;
549 			foreach (y; 0 .. this._size.height){
550 				_display.cursor = Position(space, y);
551 				_display.fillLine(' ', _fillColor, _fillColor, lineWidth);
552 			}
553 		}else if (_type == Type.Vertical && space < this._size.height){
554 			foreach (y; space .. this._size.height){
555 				_display.cursor = Position(0, y);
556 				_display.fillLine(' ', _fillColor, _fillColor, this._size.width);
557 			}
558 		}
559 	}
560 	/// called to cycle between actveWidgets. This is called by owner widget
561 	/// 
562 	/// Returns: true if cycled to another widget, false if _activeWidgetIndex set to -1
563 	override bool cycleActiveWidget(){
564 		// check if need to cycle within current active widget
565 		if (_activeWidgetIndex == -1 || !(_widgets[_activeWidgetIndex].cycleActiveWidget())){
566 			integer lastActiveWidgetIndex = _activeWidgetIndex;
567 			for (_activeWidgetIndex ++; _activeWidgetIndex < _widgets.length; _activeWidgetIndex ++){
568 				if (_widgets[_activeWidgetIndex].wantsInput && _widgets[_activeWidgetIndex].show)
569 					break;
570 			}
571 			if (_activeWidgetIndex >= _widgets.length)
572 				_activeWidgetIndex = -1;
573 			
574 			if (lastActiveWidgetIndex != _activeWidgetIndex){
575 				if (lastActiveWidgetIndex > -1)
576 					_widgets[lastActiveWidgetIndex].activateEventCall(false);
577 				if (_activeWidgetIndex > -1)
578 					_widgets[_activeWidgetIndex].activateEventCall(true);
579 			}
580 		}
581 		return _activeWidgetIndex != -1;
582 	}
583 
584 	/// activate the passed widget if it's in the current layout, return if it was activated or not
585 	override bool searchAndActivateWidget(QWidget target){
586 		integer lastActiveWidgetIndex = _activeWidgetIndex;
587 
588 		// search and activate recursively
589 		_activeWidgetIndex = -1;
590 		foreach (integer index, widget; _widgets) {
591 			if (widget.wantsInput && widget.show && widget.searchAndActivateWidget(target)) {
592 				_activeWidgetIndex = index;
593 				break;
594 			}
595 		}
596 
597 		// and then manipulate the current layout
598 		if (lastActiveWidgetIndex != _activeWidgetIndex && lastActiveWidgetIndex > -1)
599 			_widgets[lastActiveWidgetIndex].activateEventCall(false);
600 			// no need to call activateEvent on new activeWidget, doing `searchAndActivateWidget` in above loop should've done that
601 		return _activeWidgetIndex != -1;
602 	}
603 
604 	/// Change the key used for cycling active widgets, changes it for all added widgets as well
605 	override void setActiveWidgetCycleKey(dchar newKey){
606 		_activeWidgetCycleKey = newKey;
607 		foreach (widget; _widgets)
608 			widget.setActiveWidgetCycleKey(newKey);
609 	}
610 public:
611 	/// Layout type
612 	enum Type{
613 		Vertical,
614 		Horizontal,
615 	}
616 	/// constructor
617 	this(QLayout.Type type){
618 		_type = type;
619 		this._fillColor = DEFAULT_BG;
620 	}
621 	/// Color for unoccupied space
622 	@property Color fillColor(){
623 		return _fillColor;
624 	}
625 	/// ditto
626 	@property Color fillColor(Color newColor){
627 		return _fillColor = newColor;
628 	}
629 	/// Returns: whether the widget is receiving the Tab key press or not
630 	override @property bool wantsTab(){
631 		foreach (widget; _widgets){
632 			if (widget.wantsTab)
633 				return true;
634 		}
635 		return false;
636 	}
637 	/// Returns: true if the widget wants input
638 	override @property bool wantsInput(){
639 		foreach (widget; _widgets){
640 			if (widget.wantsInput)
641 				return true;
642 		}
643 		return false;
644 	}
645 	/// Returns: true if the cursor should be visible if this widget is active
646 	override @property Position cursorPosition(){
647 		// just do a hack, and check only for active widget
648 		if (_activeWidgetIndex == -1)
649 			return Position(-1, -1);
650 		Position cursorPosition = _widgets[_activeWidgetIndex].cursorPosition;
651 		if (cursorPosition == Position(-1, -1))
652 			return cursorPosition;
653 		return Position(_widgets[_activeWidgetIndex]._position.x + cursorPosition.x,
654 			_widgets[_activeWidgetIndex]._position.y + cursorPosition.y);
655 	}
656 	
657 	/// adds (appends) a widget to the widgetList, and makes space for it
658 	/// 
659 	/// If there a widget is too large, it's marked as not visible
660 	void addWidget(QWidget widget){
661 		widget._parent = this;
662 		widget._indexInParent = _widgets.length;
663 		widget.setActiveWidgetCycleKey(this._activeWidgetCycleKey);
664 		//add it to array
665 		_widgets ~= widget;
666 		// make space in _requestingUpdate
667 		_requestingUpdate ~= true;
668 	}
669 	/// adds (appends) widgets to the widgetList, and makes space for them
670 	/// 
671 	/// If there a widget is too large, it's marked as not visible
672 	void addWidget(QWidget[] widgets){
673 		foreach (i, widget; widgets){
674 			widget._parent = this;
675 			widget._indexInParent = _widgets.length+i;
676 			widget.setActiveWidgetCycleKey(this._activeWidgetCycleKey);
677 		}
678 		// add to array
679 		_widgets ~= widgets.dup;
680 		// make space in _requestingUpdate
681 		_requestingUpdate.length += widgets.length;
682 		_requestingUpdate[$ - widgets.length .. $] = true;
683 	}
684 }
685 
686 /// Used to write to display by widgets
687 class Display{
688 private:
689 	/// width & height
690 	uinteger _width, _height;
691 	/// x and y offsets
692 	uinteger _xOff, _yOff;
693 	/// cursor position, relative to _xOff and _yOff
694 	Position _cursor;
695 	/// the terminal
696 	TermWrapper _term;
697 	/// constructor for when this buffer is just a slice of the actual buffer
698 	this(uinteger w, uinteger h, uinteger xOff, uinteger yOff, TermWrapper terminal){
699 		_xOff = xOff;
700 		_yOff = yOff;
701 		_width = w;
702 		_height = h;
703 		_term = terminal;
704 	}
705 	/// Returns: a "slice" of this buffer, that is only limited to some rectangular area
706 	/// 
707 	/// no bound checking is done
708 	Display getSlice(uinteger w, uinteger h, uinteger x, uinteger y){
709 		return new Display(w, h, _xOff + x, _yOff + y, _term);
710 	}
711 	/// modifies an existing Display to act as a "slice"
712 	/// 
713 	/// no bound checking is done
714 	void getSlice(Display sliced, uinteger w, uinteger h, uinteger x, uinteger y){
715 		sliced._width = w;
716 		sliced._height = h;
717 		sliced._xOff = _xOff + x;
718 		sliced._yOff = _yOff + y;
719 	}
720 public:
721 	/// constructor
722 	this(uinteger w, uinteger h, TermWrapper terminal){
723 		_width = w;
724 		_height = h;
725 		_term = terminal;
726 	}
727 	~this(){
728 
729 	}
730 	/// Returns: cursor  position
731 	@property Position cursor(){
732 		return _cursor;
733 	}
734 	/// ditto
735 	@property Position cursor(Position newPos){
736 		if (newPos.x >= _width)
737 			newPos.x = _width-1;
738 		if (newPos.y >= _height)
739 			newPos.y = _height-1;
740 		if (newPos.x < 0)
741 			newPos.x = 0;
742 		if (newPos.y < 0)
743 			newPos.y = 0;
744 		return _cursor = newPos;
745 	}
746 	/// sets background and foreground color
747 	void colors(Color fg, Color bg){
748 		_term.color(fg, bg);
749 	}
750 	/// Writes a line. if string has more characters than there is space for, extra characters will be ignored.
751 	/// Each character is written in a 1 cell.
752 	void write(dstring str, Color fg, Color bg){
753 		_term.color(fg, bg);
754 		this.write(str);
755 	}
756 	/// ditto
757 	void write(dstring str){
758 		foreach (c; str){
759 			if (_cursor.x >= _width){
760 				_cursor.x = 0;
761 				_cursor.y ++;
762 			}
763 			if (_cursor.x < _width && _cursor.y < _height)
764 				_term.put(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), c == '\t' || c == '\n' ? ' ' : c);
765 			else
766 				break;
767 			_cursor.x ++;
768 		}
769 	}
770 	/// fills all remaining cells with a character
771 	void fill(dchar c, Color fg, Color bg){
772 		dchar[] line;
773 		line.length = _width;
774 		line[] = c;
775 		_term.color(fg, bg);
776 		while (_cursor.y < _height){
777 			_term.write(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), cast(dstring)line[0 .. _width - _cursor.x]);
778 			_cursor.y ++;
779 			_cursor.x = 0;
780 		}
781 	}
782 	/// fills rest of current line with a character
783 	void fillLine(dchar c, Color fg, Color bg, uinteger max = 0){
784 		dchar[] line;
785 		line.length =  max < _width - _cursor.x && max > 0 ? max : _width - _cursor.x;
786 		line[] = c;
787 		_term.color(fg, bg);
788 		_term.write(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), cast(dstring)line);
789 		_cursor.x += line.length;
790 		if (_cursor.x >= _width -1){
791 			_cursor.y ++;
792 			_cursor.x = 0;
793 		}
794 	}
795 }
796 
797 /// A terminal (as the name says).
798 class QTerminal : QLayout{
799 private:
800 	/// To actually access the terminal
801 	TermWrapper _termWrap;
802 	/// Whether to call resize before next update
803 	bool _requestingResize;
804 	/// set to false to stop UI loop in run()
805 	bool _isRunning;
806 
807 	/// Reads InputEvent and calls appropriate functions to address those events
808 	void readEvent(Event event){
809 		if (event.type == Event.Type.HangupInterrupt){
810 			_isRunning = false;
811 		}else if (event.type == Event.Type.Keyboard){
812 			KeyboardEvent kPress = event.keyboard;
813 			this.keyboardEventCall(kPress);
814 		}else if (event.type == Event.Type.Mouse){
815 			this.mouseEventCall(event.mouse);
816 		}else if (event.type == Event.Type.Resize){
817 			//update self size
818 			_size.height = event.resize.height;
819 			_size.width = event.resize.width;
820 			_display._height = _size.height;
821 			_display._width = _size.width;
822 			//call size change on all widgets
823 			resizeEventCall();
824 		}
825 	}
826 
827 protected:
828 
829 	override void resizeEvent(){
830 		_requestingResize = false;
831 		super.resizeEvent();
832 	}
833 	
834 	override void update(){
835 		// resize if needed
836 		if (_requestingResize)
837 			this.resizeEventCall();
838 		super.update();
839 		// check if need to show/hide cursor
840 		Position cursorPos = this.cursorPosition;
841 		if (cursorPos == Position(-1, -1)){
842 			_termWrap.cursorVisible = false;
843 		}else{
844 			_termWrap.moveCursor(cast(int)cursorPos.x, cast(int)cursorPos.y);
845 			_termWrap.cursorVisible = true;
846 		}
847 		_termWrap.flush;
848 	}
849 
850 public:
851 	/// time to wait between timer events (milliseconds)
852 	ushort timerMsecs;
853 	/// constructor
854 	this(QLayout.Type displayType = QLayout.Type.Vertical, ushort timerDuration = 500){
855 		super(displayType);
856 		timerMsecs = timerDuration;
857 
858 		_termWrap = new TermWrapper();
859 		_display = new Display(1,1, _termWrap);
860 		this._isActive = true; // so it can make other widgets active on mouse events
861 	}
862 	~this(){
863 		.destroy(_termWrap);
864 		.destroy(_display);
865 	}
866 
867 	/// Called to make container widgets (QLayouts) recalcualte widgets' sizes before update;
868 	override void requestResize(){
869 		_requestingResize = true;
870 	}
871 
872 	/// stops UI loop. **not instant**, if it is in-between updates, event functions, or timers, it will complete those first
873 	void terminate(){
874 		_isRunning = false;
875 	}
876 	
877 	/// starts the UI loop
878 	void run(){
879 		// set size
880 		_size.width = _termWrap.width();
881 		_size.height = _termWrap.height();
882 		_display._width = _size.width;
883 		_display._height = _size.height;
884 		//ready
885 		initializeCall();
886 		resizeEventCall();
887 		//draw the whole thing
888 		update();
889 		_isRunning = true;
890 		// the stop watch, to count how much time has passed after each timerEvent
891 		StopWatch sw = StopWatch(AutoStart.yes);
892 		while (_isRunning){
893 			int timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
894 			Event event;
895 			while (_termWrap.getEvent(timeout, event) > 0){
896 				readEvent(event);
897 				timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
898 				update();
899 			}
900 			if (sw.peek.total!"msecs" >= timerMsecs){
901 				this.timerEventCall(sw.peek.total!"msecs");
902 				sw.reset;
903 				sw.start;
904 				update();
905 			}
906 		}
907 	}
908 
909 	/// search the passed widget recursively and activate it, returns if the activation was successful
910 	bool activateWidget(QWidget target) {
911 		return this.searchAndActivateWidget(target);
912 	}
913 
914 	/// Changes the key used to cycle between active widgets.
915 	/// 
916 	/// for `key`, use either ASCII value of keyboard key, or use KeyboardEvent.Key or KeyboardEvent.CtrlKeys
917 	override void setActiveWidgetCycleKey(dchar key){
918 		super.setActiveWidgetCycleKey(key);
919 	}
920 }