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 
336 	/// gets height/width of a widget using it's sizeRatio and min/max-height/width
337 	static uinteger calculateWidgetSize(QLayout.Type type)(QWidget widget, uinteger ratioTotal, uinteger totalSpace,
338 	ref bool free){
339 		Size wSize = widget.size;
340 		immutable calculatedSize = cast(uinteger)((widget.sizeRatio*totalSpace)/ratioTotal);
341 		static if (type == QLayout.Type.Horizontal){
342 			wSize.width = calculatedSize;
343 			free = wSize.minWidth == 0 && wSize.maxWidth == 0;
344 			return wSize.width;
345 		}else{ // this else just exists to shut up compiler about "statement not reachable"
346 			wSize.height = calculatedSize;
347 			free = wSize.minHeight == 0 && wSize.maxHeight == 0;
348 			return wSize.height;
349 		}
350 	}
351 	
352 	/// recalculates the size of every widget inside layout
353 	void recalculateWidgetsSize(QLayout.Type T)(){
354 		static if (T != QLayout.Type.Horizontal && T != QLayout.Type.Vertical)
355 			assert(false);
356 		FIFOStack!QWidget widgetStack = new FIFOStack!QWidget;
357 		uinteger totalRatio = 0;
358 		uinteger totalSpace = T == QLayout.Type.Horizontal ? _size.width : _size.height;
359 		foreach (widget; _widgets){
360 			if (!widget.show)
361 				continue;
362 			totalRatio += widget.sizeRatio;
363 			widget._size.height = _size.height;
364 			widget._size.width = _size.width;
365 			widgetStack.push(widget);
366 		}
367 		// do widgets with size limits
368 		uinteger limitWRatio, limitWSize; /// totalRatio, and space used of widgets with limits
369 		for (integer i = 0; i < widgetStack.count; i ++){
370 			QWidget widget = widgetStack.pop;
371 			bool free;
372 			immutable uinteger space = calculateWidgetSize!(T)(widget, totalRatio, totalSpace, free);
373 			if (free){
374 				widgetStack.push(widget);
375 				continue;
376 			}
377 			static if (T == QLayout.Type.Horizontal)
378 				widget._size.width = space;
379 			else
380 				widget._size.height = space;
381 			limitWRatio += widget.sizeRatio;
382 			limitWSize += space;
383 		}
384 		totalSpace -= limitWSize;
385 		totalRatio -= limitWRatio;
386 		while (widgetStack.count){
387 			QWidget widget = widgetStack.pop;
388 			bool free;
389 			immutable uinteger space = calculateWidgetSize!(T)(widget, totalRatio, totalSpace, free);
390 			static if (T == QLayout.Type.Horizontal)
391 				widget._size.width = space;
392 			else
393 				widget._size.height = space;
394 			totalRatio -= widget.sizeRatio;
395 			totalSpace -= space;
396 		}
397 		.destroy(widgetStack);
398 	}
399 	/// calculates and assigns widgets positions based on their sizes
400 	void recalculateWidgetsPosition(QLayout.Type T)(){
401 		static if (T != QLayout.Type.Horizontal && T != QLayout.Type.Vertical)
402 			assert(false);
403 		uinteger previousSpace = 0;
404 		foreach(widget; _widgets){
405 			if (widget.show){
406 				static if (T == QLayout.Type.Horizontal){
407 					widget._position.y = 0;
408 					widget._position.x = previousSpace;
409 					previousSpace += widget.size.width;
410 				}else{
411 					widget._position.x = 0;
412 					widget._position.y = previousSpace;
413 					previousSpace += widget.size.height;
414 				}
415 			}
416 		}
417 	}
418 protected:
419 	/// Recalculates size and position for all visible widgets
420 	/// If a widget is too large to fit in, it's visibility is marked false
421 	override void resizeEvent(){
422 		uinteger ratioTotal;
423 		foreach(w; _widgets){
424 			if (w.show){
425 				ratioTotal += w._sizeRatio;
426 			}
427 		}
428 		if (_type == QLayout.Type.Horizontal){
429 			recalculateWidgetsSize!(QLayout.Type.Horizontal);
430 			recalculateWidgetsPosition!(QLayout.Type.Horizontal);
431 		}else{
432 			recalculateWidgetsSize!(QLayout.Type.Vertical);
433 			recalculateWidgetsPosition!(QLayout.Type.Vertical);
434 		}
435 		foreach (widget; _widgets){
436 			this._display.getSlice(widget._display, widget.size.width, widget.size.height,widget._position.x,widget._position.y);
437 			widget.resizeEventCall();
438 		}
439 	}
440 	
441 	/// Redirects the mouseEvent to the appropriate widget
442 	override public void mouseEvent(MouseEvent mouse) {
443 		/// first check if it's already inside active widget, might not have to search through each widget
444 		QWidget activeWidget = null;
445 		if (_activeWidgetIndex > -1)
446 			activeWidget = _widgets[_activeWidgetIndex];
447 		if (activeWidget && mouse.x >= activeWidget._position.x && mouse.x < activeWidget._position.x +activeWidget.size.width 
448 		&& mouse.y >= activeWidget._position.y && mouse.y < activeWidget._position.y + activeWidget.size.height){
449 			activeWidget.mouseEventCall(mouse);
450 		}else{
451 			foreach (i, widget; _widgets){
452 				if (widget.show && widget.wantsInput &&
453 					mouse.x >= widget._position.x && mouse.x < widget._position.x + widget.size.width &&
454 					mouse.y >= widget._position.y && mouse.y < widget._position.y + widget.size.height){
455 					// make it active only if this layout is itself active
456 					if (this.isActive){
457 						if (activeWidget)
458 							activeWidget.activateEventCall(false);
459 						widget.activateEventCall(true);
460 						_activeWidgetIndex = i;
461 					}
462 					widget.mouseEventCall(mouse);
463 					break;
464 				}
465 			}
466 		}
467 	}
468 
469 	/// Redirects the keyboardEvent to appropriate widget
470 	override public void keyboardEvent(KeyboardEvent key){
471 		// check if key handler registered
472 		if (key.key in _keyHandlers)
473 			_keyHandlers[key.key].keyboardEventCall(key);
474 		// check if need to cycle
475 		if (key.key == _activeWidgetCycleKey || key.key == KeyboardEvent.Key.Escape){
476 			if (key.key == '\t'){ // now need to make sure active widget doesnt want to catch tab
477 				if (_activeWidgetIndex == -1)
478 					this.cycleActiveWidget();
479 				else{
480 					if (_widgets[_activeWidgetIndex].wantsTab)
481 						_widgets[_activeWidgetIndex].keyboardEventCall(key);
482 					else
483 						this.cycleActiveWidget();
484 				}
485 			}else
486 				this.cycleActiveWidget();
487 		}else if (_activeWidgetIndex > -1 && (key.key != '\t' || _widgets[_activeWidgetIndex].wantsTab))
488 			_widgets[_activeWidgetIndex].keyboardEventCall(key);
489 	}
490 
491 	/// override initialize to initliaze child widgets
492 	override void initialize(){
493 		foreach (widget; _widgets){
494 			widget._display = _display.getSlice(1,1, _position.x, _position.y); // just throw in dummy size/position, resize event will fix that
495 			widget.initializeCall();
496 		}
497 	}
498 
499 	/// override timer event to call child widgets' timers
500 	override void timerEvent(uinteger msecs){
501 		foreach (widget; _widgets)
502 			widget.timerEventCall(msecs);
503 	}
504 
505 	/// override activate event
506 	override void activateEvent(bool isActive){
507 		if (isActive){
508 			_activeWidgetIndex = -1;
509 			this.cycleActiveWidget();
510 		}else if (_activeWidgetIndex > -1){
511 			_widgets[_activeWidgetIndex].activateEventCall(isActive);
512 		}
513 	}
514 	
515 	/// called by owner widget to update
516 	override void update(){
517 		foreach(i, widget; _widgets){
518 			if (_requestingUpdate[i] && widget.show){
519 				widget._display.cursor = Position(0,0);
520 				widget.update();
521 				_requestingUpdate[i] = false;
522 			}
523 		}
524 	}
525 	/// called to cycle between actveWidgets. This is called by owner widget
526 	/// 
527 	/// Returns: true if cycled to another widget, false if _activeWidgetIndex set to -1
528 	override bool cycleActiveWidget(){
529 		// check if need to cycle within current active widget
530 		if (_activeWidgetIndex == -1 || !(_widgets[_activeWidgetIndex].cycleActiveWidget())){
531 			integer lastActiveWidgetIndex = _activeWidgetIndex;
532 			for (_activeWidgetIndex ++; _activeWidgetIndex < _widgets.length; _activeWidgetIndex ++){
533 				if (_widgets[_activeWidgetIndex].wantsInput && _widgets[_activeWidgetIndex].show)
534 					break;
535 			}
536 			if (_activeWidgetIndex >= _widgets.length)
537 				_activeWidgetIndex = -1;
538 			
539 			if (lastActiveWidgetIndex != _activeWidgetIndex){
540 				if (lastActiveWidgetIndex > -1)
541 					_widgets[lastActiveWidgetIndex].activateEventCall(false);
542 				if (_activeWidgetIndex > -1)
543 					_widgets[_activeWidgetIndex].activateEventCall(true);
544 			}
545 		}
546 		return _activeWidgetIndex != -1;
547 	}
548 
549 	/// activate the passed widget if it's in the current layout, return if it was activated or not
550 	override bool searchAndActivateWidget(QWidget target){
551 		integer lastActiveWidgetIndex = _activeWidgetIndex;
552 
553 		// search and activate recursively
554 		_activeWidgetIndex = -1;
555 		foreach (integer index, widget; _widgets) {
556 			if (widget.wantsInput && widget.show && widget.searchAndActivateWidget(target)) {
557 				_activeWidgetIndex = index;
558 				break;
559 			}
560 		}
561 
562 		// and then manipulate the current layout
563 		if (lastActiveWidgetIndex != _activeWidgetIndex && lastActiveWidgetIndex > -1)
564 			_widgets[lastActiveWidgetIndex].activateEventCall(false);
565 			// no need to call activateEvent on new activeWidget, doing `searchAndActivateWidget` in above loop should've done that
566 		return _activeWidgetIndex != -1;
567 	}
568 
569 	/// Change the key used for cycling active widgets, changes it for all added widgets as well
570 	override void setActiveWidgetCycleKey(dchar newKey){
571 		_activeWidgetCycleKey = newKey;
572 		foreach (widget; _widgets)
573 			widget.setActiveWidgetCycleKey(newKey);
574 	}
575 public:
576 	/// Layout type
577 	enum Type{
578 		Vertical,
579 		Horizontal,
580 	}
581 	/// constructor
582 	this(QLayout.Type type){
583 		_type = type;
584 	}
585 	/// Returns: whether the widget is receiving the Tab key press or not
586 	override @property bool wantsTab(){
587 		foreach (widget; _widgets){
588 			if (widget.wantsTab)
589 				return true;
590 		}
591 		return false;
592 	}
593 	/// Returns: true if the widget wants input
594 	override @property bool wantsInput(){
595 		foreach (widget; _widgets){
596 			if (widget.wantsInput)
597 				return true;
598 		}
599 		return false;
600 	}
601 	/// Returns: true if the cursor should be visible if this widget is active
602 	override @property Position cursorPosition(){
603 		// just do a hack, and check only for active widget
604 		if (_activeWidgetIndex == -1)
605 			return Position(-1, -1);
606 		Position cursorPosition = _widgets[_activeWidgetIndex].cursorPosition;
607 		if (cursorPosition == Position(-1, -1))
608 			return cursorPosition;
609 		return Position(_widgets[_activeWidgetIndex]._position.x + cursorPosition.x,
610 			_widgets[_activeWidgetIndex]._position.y + cursorPosition.y);
611 	}
612 	
613 	/// adds (appends) a widget to the widgetList, and makes space for it
614 	/// 
615 	/// If there a widget is too large, it's marked as not visible
616 	void addWidget(QWidget widget){
617 		widget._parent = this;
618 		widget._indexInParent = _widgets.length;
619 		widget.setActiveWidgetCycleKey(this._activeWidgetCycleKey);
620 		//add it to array
621 		_widgets ~= widget;
622 		// make space in _requestingUpdate
623 		_requestingUpdate ~= true;
624 	}
625 	/// adds (appends) widgets to the widgetList, and makes space for them
626 	/// 
627 	/// If there a widget is too large, it's marked as not visible
628 	void addWidget(QWidget[] widgets){
629 		foreach (i, widget; widgets){
630 			widget._parent = this;
631 			widget._indexInParent = _widgets.length+i;
632 			widget.setActiveWidgetCycleKey(this._activeWidgetCycleKey);
633 		}
634 		// add to array
635 		_widgets ~= widgets.dup;
636 		// make space in _requestingUpdate
637 		bool[] reqUpdates;
638 		reqUpdates.length = widgets.length;
639 		reqUpdates[] = true;
640 		_requestingUpdate ~= reqUpdates;
641 	}
642 }
643 
644 /// Used to write to display by widgets
645 class Display{
646 private:
647 	/// width & height
648 	uinteger _width, _height;
649 	/// x and y offsets
650 	uinteger _xOff, _yOff;
651 	/// cursor position, relative to _xOff and _yOff
652 	Position _cursor;
653 	/// the terminal
654 	TermWrapper _term;
655 	/// constructor for when this buffer is just a slice of the actual buffer
656 	this(uinteger w, uinteger h, uinteger xOff, uinteger yOff, TermWrapper terminal){
657 		_xOff = xOff;
658 		_yOff = yOff;
659 		_width = w;
660 		_height = h;
661 		_term = terminal;
662 	}
663 	/// Returns: a "slice" of this buffer, that is only limited to some rectangular area
664 	Display getSlice(uinteger w, uinteger h, uinteger x, uinteger y){
665 		return new Display(w, h, _xOff + x, _yOff + y, _term);
666 	}
667 	/// modifies an existing Display to act as a "slice"
668 	void getSlice(Display sliced, uinteger w, uinteger h, uinteger x, uinteger y){
669 		sliced._width = w;
670 		sliced._height = h;
671 		sliced._xOff = _xOff + x;
672 		sliced._yOff = _yOff + y;
673 	}
674 public:
675 	/// constructor
676 	this(uinteger w, uinteger h, TermWrapper terminal){
677 		_width = w;
678 		_height = h;
679 		_term = terminal;
680 	}
681 	~this(){
682 
683 	}
684 	/// Returns: cursor  position
685 	@property Position cursor(){
686 		return _cursor;
687 	}
688 	/// ditto
689 	@property Position cursor(Position newPos){
690 		if (newPos.x >= _width)
691 			newPos.x = _width-1;
692 		if (newPos.y >= _height)
693 			newPos.y = _height-1;
694 		if (newPos.x < 0)
695 			newPos.x = 0;
696 		if (newPos.y < 0)
697 			newPos.y = 0;
698 		return _cursor = newPos;
699 	}
700 	/// sets background and foreground color
701 	void colors(Color fg, Color bg){
702 		_term.color(fg, bg);
703 	}
704 	/// Writes a line. if string has more characters than there is space for, extra characters will be ignored.
705 	/// Tab character is converted to a single space character
706 	void write(dstring str, Color fg, Color bg){
707 		_term.color(fg, bg);
708 		this.write(str);
709 	}
710 	/// ditto
711 	void write(dstring str){
712 		foreach (c; str){
713 			if (_cursor.x >= _width){
714 				_cursor.x = 0;
715 				_cursor.y ++;
716 			}
717 			if (_cursor.x < _width && _cursor.y < _height)
718 				_term.put(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), c == '\t' || c == '\n' ? ' ' : c);
719 			else
720 				break;
721 			_cursor.x ++;
722 		}
723 	}
724 	/// fills all remaining cells with a character
725 	void fill(dchar c, Color fg, Color bg){
726 		dchar[] line;
727 		line.length = _width;
728 		line[] = c;
729 		_term.color(fg, bg);
730 		while (_cursor.y < _height){
731 			_term.write(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), cast(dstring)line[0 .. _width - _cursor.x]);
732 			_cursor.y ++;
733 			_cursor.x = 0;
734 		}
735 	}
736 	/// fills rest of current line with a character
737 	void fillLine(dchar c, Color fg, Color bg, uinteger max = 0){
738 		dchar[] line;
739 		line.length =  max < _width - _cursor.x && max > 0 ? max : _width - _cursor.x;
740 		line[] = c;
741 		_term.color(fg, bg);
742 		_term.write(cast(int)(_cursor.x + _xOff), cast(int)(_cursor.y + _yOff), cast(dstring)line);
743 		_cursor.x += line.length;
744 		if (_cursor.x >= _width -1){
745 			_cursor.y ++;
746 			_cursor.x = 0;
747 		}
748 	}
749 }
750 
751 /// A terminal (as the name says).
752 class QTerminal : QLayout{
753 private:
754 	/// To actually access the terminal
755 	TermWrapper _termWrap;
756 	/// Whether to call resize before next update
757 	bool _requestingResize;
758 	/// set to false to stop UI loop in run()
759 	bool _isRunning;
760 
761 	/// Reads InputEvent and calls appropriate functions to address those events
762 	void readEvent(Event event){
763 		if (event.type == Event.Type.HangupInterrupt){
764 			_isRunning = false;
765 		}else if (event.type == Event.Type.Keyboard){
766 			KeyboardEvent kPress = event.keyboard;
767 			this.keyboardEventCall(kPress);
768 		}else if (event.type == Event.Type.Mouse){
769 			this.mouseEventCall(event.mouse);
770 		}else if (event.type == Event.Type.Resize){
771 			//update self size
772 			_size.height = event.resize.height;
773 			_size.width = event.resize.width;
774 			//call size change on all widgets
775 			resizeEventCall();
776 		}
777 	}
778 
779 protected:
780 
781 	override void resizeEvent(){
782 		_requestingResize = false;
783 		super.resizeEvent();
784 	}
785 	
786 	override void update(){
787 		// resize if needed
788 		if (_requestingResize)
789 			this.resizeEventCall();
790 		super.update();
791 		// check if need to show/hide cursor
792 		Position cursorPos = this.cursorPosition;
793 		if (cursorPos == Position(-1, -1)){
794 			_termWrap.cursorVisible = false;
795 		}else{
796 			_termWrap.moveCursor(cast(int)cursorPos.x, cast(int)cursorPos.y);
797 			_termWrap.cursorVisible = true;
798 		}
799 		_termWrap.flush;
800 	}
801 
802 public:
803 	/// text color, and background color
804 	Color textColor, backgroundColor;
805 	/// time to wait between timer events (milliseconds)
806 	ushort timerMsecs;
807 	/// constructor
808 	this(QLayout.Type displayType = QLayout.Type.Vertical, ushort timerDuration = 500){
809 		super(displayType);
810 
811 		textColor = DEFAULT_FG;
812 		backgroundColor = DEFAULT_BG;
813 		timerMsecs = timerDuration;
814 
815 		_termWrap = new TermWrapper();
816 		_display = new Display(1,1, _termWrap);
817 		this._isActive = true; // so it can make other widgets active on mouse events
818 	}
819 	~this(){
820 		.destroy(_termWrap);
821 		.destroy(_display);
822 	}
823 
824 	/// Called to make container widgets (QLayouts) recalcualte widgets' sizes before update;
825 	override void requestResize(){
826 		_requestingResize = true;
827 	}
828 
829 	/// stops UI loop. **not instantly**, if it is in-between updates, calling event functions, or timers, it will complete those first
830 	void terminate(){
831 		_isRunning = false;
832 	}
833 	
834 	/// starts the UI loop
835 	void run(){
836 		// init termbox
837 		_size.width = _termWrap.width();
838 		_size.height = _termWrap.height();
839 		//ready
840 		initializeCall();
841 		resizeEventCall();
842 		//draw the whole thing
843 		update();
844 		_isRunning = true;
845 		// the stop watch, to count how much time has passed after each timerEvent
846 		StopWatch sw = StopWatch(AutoStart.yes);
847 		while (_isRunning){
848 			int timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
849 			Event event;
850 			while (_termWrap.getEvent(timeout, event) > 0){
851 				readEvent(event);
852 				timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
853 				update();
854 			}
855 			if (sw.peek.total!"msecs" >= timerMsecs){
856 				this.timerEventCall(sw.peek.total!"msecs");
857 				sw.reset;
858 				sw.start;
859 				update();
860 			}
861 		}
862 	}
863 
864 	/// search the passed widget recursively and activate it, returns if the activation was successful
865 	bool activateWidget(QWidget target) {
866 		return this.searchAndActivateWidget(target);
867 	}
868 
869 	/// Changes the key used to cycle between active widgets.
870 	/// 
871 	/// for `key`, use either ASCII value of keyboard key, or use KeyboardEvent.Key or KeyboardEvent.CtrlKeys
872 	override void setActiveWidgetCycleKey(dchar key){
873 		super.setActiveWidgetCycleKey(key);
874 	}
875 }