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.ds;
10 import std.conv : to;
11 import qui.termwrap;
12 
13 import qui.utils;
14 
15 /// the default foreground color
16 enum Color DEFAULT_FG = Color.white;
17 /// the default background color
18 enum Color DEFAULT_BG = Color.black;
19 /// background color of overflowing layouts
20 enum Color DEFAULT_OVERFLOW_BG = Color.red;
21 /// default active widget cycling key (tab)
22 enum dchar WIDGET_CYCLE_KEY = '\t';
23 
24 /// Available colors are in this enum
25 public alias Color = qui.termwrap.Color;
26 /// Availabe Keys (keyboard) for input
27 public alias Key = qui.termwrap.Event.Keyboard.Key;
28 /// Mouse Event
29 public alias MouseEvent = Event.Mouse;
30 /// Keyboard Event
31 public alias KeyboardEvent = Event.Keyboard;
32 
33 /// mouseEvent function. Return true if the event should be dropped
34 alias MouseEventFuction = bool delegate(QWidget, MouseEvent);
35 ///keyboardEvent function. Return true if the event should be dropped
36 alias KeyboardEventFunction = bool delegate(QWidget, KeyboardEvent, bool);
37 /// resizeEvent function. Return true if the event should be dropped
38 alias ResizeEventFunction = bool delegate(QWidget);
39 /// scrollEvent function. Return true if the event should be dropped
40 alias scrollEventFunction = bool delegate(QWidget);
41 /// activateEvent function. Return true if the event should be dropped
42 alias ActivateEventFunction = bool delegate(QWidget, bool);
43 /// TimerEvent function. Return true if the event should be dropped
44 alias TimerEventFunction = bool delegate(QWidget, uint);
45 /// Init function. Return true if the event should be dropped
46 alias InitFunction = bool delegate(QWidget);
47 /// UpdateEvent function. Return true if the event should be dropped
48 alias UpdateEventFunction = bool delegate(QWidget);
49 
50 /// mask of events subscribed
51 enum EventMask : uint{
52 	/// mouse clicks/presses. This value matches `MouseEvent.State.Click`
53 	MousePress = 1,
54 	/// mouse releases. This value matches `MouseEvent.State.Release`
55 	MouseRelease = 1 << 1,
56 	/// mouse move/hover. This value matches `MouseEvent.State.Hover`
57 	MouseHover = 1 << 2,
58 	/// key presses. This value matches `KeyboardEvent.State.Pressed`
59 	KeyboardPress = 1 << 3,
60 	/// key releases. This value matches `KeyboardEvent.State.Released`
61 	KeyboardRelease = 1 << 4,
62 	/// widget scrolling.
63 	Scroll = 1 << 5,
64 	/// widget resize.
65 	Resize = 1 << 6,
66 	/// widget activated/deactivated.
67 	Activate = 1 << 7,
68 	/// timer
69 	Timer = 1 << 8,
70 	/// initialize
71 	Initialize = 1 << 9,
72 	/// draw itself.
73 	Update = 1 << 10,
74 	/// All mouse events
75 	MouseAll = MousePress | MouseRelease | MouseHover,
76 	/// all keyboard events, this does **NOT** include KeyboardWidgetCycleKey
77 	KeyboardAll = KeyboardPress | KeyboardRelease,
78 	/// All keyboard and mouse events
79 	InputAll = MouseAll | KeyboardAll,
80 	//All = uint.max, /// all events (all bits=1). ** DO NOT USE THIS **
81 }
82 
83 /// A cell on terminal
84 struct Cell{
85 	/// character
86 	dchar c;
87 	/// foreground and background colors
88 	Color fg, bg;
89 	/// if properties of this are same as other
90 	bool propSame(Cell B){
91 		return this.fg == B.fg && this.bg == B.fg;
92 	}
93 }
94 
95 /// Display buffer
96 struct Viewport{
97 private:
98 	Cell[] _buffer;
99 	uint _seekX, _seekY;
100 	uint _width, _height;
101 	uint _actualWidth;
102 	// these are subtracted, only when seek is set, not after
103 	uint _offsetX, _offsetY;
104 
105 	/// reset
106 	void _reset(){
107 		_buffer = [];
108 		_seekX = 0;
109 		_seekY = 0;
110 		_width = 0;
111 		_height = 0;
112 		_actualWidth = 0;
113 		_offsetX = 0;
114 		_offsetY = 0;
115 	}
116 
117 	/// seek position in _buffer calculated from _seekX & _seekY
118 	@property int _seek(){
119 		return (_seekX - _offsetX) + ((_seekY - _offsetY)*_actualWidth);
120 	}
121 	/// set another DisplayBuffer so that it is a rectangular slice from this
122 	///
123 	/// Returns: true if done, false if not
124 	void _getSlice(Viewport* sub, int x, int y, uint width, uint height){
125 		sub._reset();
126 		sub._actualWidth = _actualWidth;
127 		if (width + height == 0)
128 			return;
129 		x -= _offsetX;
130 		y -= _offsetY;
131 		if (x > cast(int)_width || y > cast(int)_height ||
132 		x + width <= 0 ||y + height <= 0)
133 			return;
134 		if (x < 0){
135 			sub._offsetX = -x;
136 			x = 0;
137 		}
138 		if (y < 0){
139 			sub._offsetY = -y;
140 			y = 0;
141 		}
142 		if (width + x > _width)
143 			width = _width - x;
144 		if (height + y > _height)
145 			height = _height - y;
146 		immutable uint buffStart  = x + (y*_actualWidth),
147 			buffEnd = buffStart + ((height-1)*_actualWidth) + width;
148 		if (buffEnd > _buffer.length || buffStart >= buffEnd)
149 			return;
150 		sub._width = width;
151 		sub._height = height;
152 		sub._buffer = _buffer[buffStart .. buffEnd];
153 	}
154 public:
155 	/// if x and y are at a position where writing can happen
156 	bool isWritable(uint x, uint y){
157 		return x >= _offsetX && y >= _offsetY &&
158 			x < _width + _offsetX && y < _height + _offsetY;
159 	}
160 	/// move to a position. if x > width, moved to x=0 of next row
161 	void moveTo(uint x, uint y){
162 		_seekX = x;
163 		_seekY = y;
164 		if (_seekY >= _width){
165 			_seekX = 0;
166 			_seekY ++;
167 		}
168 	}
169 	/// Writes a character at current position and move ahead
170 	/// 
171 	/// Returns: false if outside writing area
172 	bool write(dchar c, Color fg, Color bg){
173 		if (_seekX < _offsetX || _seekY < _offsetY){
174 			_seekX ++;
175 			if (_seekY >= _width){
176 				_seekX = 0;
177 				_seekY ++;
178 			}
179 			return false;
180 		}
181 		if (_seekX >= _width && _seekY >= _height)
182 			return false;
183 		if (_buffer.length > _seek)
184 			_buffer[_seek] = Cell(c, fg, bg);
185 		_seekX ++;
186 		if (_seekX >= _width){
187 			_seekX = 0;
188 			_seekY ++;
189 		}
190 		return true;
191 	}
192 	/// Writes a string.
193 	/// 
194 	/// Returns: number of characters written
195 	uint write(dstring s, Color fg, Color bg){
196 		uint r;
197 		foreach (c; s){
198 			if (write(c, fg, bg))
199 				r ++;
200 			else
201 				return r;
202 		}
203 		return r;
204 	}
205 	/// Fills line, starting from current coordinates,
206 	/// with maximum `max` number of chars, if `max>0`
207 	/// 
208 	/// Returns: number of columns (characters) written
209 	uint fillLine(dchar c, Color fg, Color bg, uint max=0){
210 		uint r = 0;
211 		const uint currentY = _seekY;
212 		bool status = true;
213 		while (status && (max == 0 || r < max) && _seekY == currentY){
214 			status = write(c, fg, bg);
215 			r ++;
216 		}
217 		return r;
218 	}
219 }
220 
221 /// Base class for all widgets, including layouts and QTerminal
222 ///
223 /// Use this as parent-class for new widgets
224 abstract class QWidget{
225 private:
226 	/// stores the position of this widget, relative to it's parent widget's position
227 	uint _posX, _posY;
228 	/// width of widget
229 	uint _width;
230 	/// height of widget
231 	uint _height;
232 	/// scroll
233 	uint _scrollX, _scrollY;
234 	/// viewport
235 	Viewport _view;
236 	/// stores if this widget is the active widget
237 	bool _isActive = false;
238 	/// whether this widget is requesting update
239 	bool _requestingUpdate = true;
240 	/// Whether to call resize before next update
241 	bool _requestingResize = false;
242 	/// the parent widget
243 	QWidget _parent = null;
244 	/// if it can make parent change scrolling
245 	bool _canReqScroll = false;
246 	/// if this widget is itself a scrollable container
247 	bool _isScrollableContainer = false;
248 	/// Events this widget is subscribed to, see `EventMask`
249 	uint _eventSub = 0;
250 	/// whether this widget should be drawn or not.
251 	bool _show = true;
252 	/// specifies ratio of height or width (depending on parent layout type)
253 	uint _sizeRatio = 1;
254 
255 	/// custom onInit event
256 	InitFunction _customInitEvent;
257 	/// custom mouse event
258 	MouseEventFuction _customMouseEvent;
259 	/// custom keyboard event
260 	KeyboardEventFunction _customKeyboardEvent;
261 	/// custom resize event
262 	ResizeEventFunction _customResizeEvent;
263 	/// custom rescroll event
264 	scrollEventFunction _customScrollEvent;
265 	/// custom onActivate event,
266 	ActivateEventFunction _customActivateEvent;
267 	/// custom onTimer event
268 	TimerEventFunction _customTimerEvent;
269 	/// custom upedateEvent
270 	UpdateEventFunction _customUpdateEvent;
271 
272 	/// Called when it needs to request an update.  
273 	void _requestUpdate(){
274 		if (_requestingUpdate || !(_eventSub & EventMask.Update))
275 			return;
276 		_requestingUpdate = true;
277 		if (_parent)
278 			_parent.requestUpdate();
279 	}
280 	/// Called to request this widget to resize at next update
281 	void _requestResize(){
282 		if (_requestingResize)
283 			return;
284 		_requestingResize = true;
285 		if (_parent)
286 			_parent.requestResize();
287 	}
288 	/// Called to request cursor to be positioned at x,y
289 	/// Will do nothing if not active widget
290 	void _requestCursorPos(int x, int y){
291 		if (_isActive && _parent)
292 			_parent.requestCursorPos(x < 0 ? x : _posX + x - _view._offsetX,
293 				y < 0 ? y : _posY + y - _view._offsetY);
294 	}
295 	/// Called to request _scrollX to be adjusted
296 	/// Returns: true if _scrollX was modified
297 	bool _requestScrollX(uint x){
298 		if (_canReqScroll && _parent &&
299 		_view._width < _width && x < _width - _view._width)
300 			return _parent.requestScrollX(x + _posX);
301 		return false;
302 	}
303 	/// Called to request _scrollY to be adjusted
304 	/// Returns: true if _scrollY was modified
305 	bool _requestScrollY(uint y){
306 		if (_canReqScroll && _parent &&
307 		_view._height < _height && y < _height - _view._height)
308 			return _parent.requestScrollY(y + _posY);
309 		return false;
310 	}
311 
312 	/// called by parent for initialize event
313 	bool _initializeCall(){
314 		if (!(_eventSub & EventMask.Initialize))
315 			return false;
316 		if (_customInitEvent && _customInitEvent(this))
317 			return true;
318 		return this.initialize();
319 	}
320 	/// called by parent for mouseEvent
321 	bool _mouseEventCall(MouseEvent mouse){
322 		if (!(_eventSub & mouse.state)) 
323 			return false;
324 		// mouse input comes relative to visible area
325 		mouse.x = (mouse.x - cast(int)this._posX) + _view._offsetX;
326 		mouse.y = (mouse.y - cast(int)this._posY) + _view._offsetY;
327 		if (_customMouseEvent && _customMouseEvent(this, mouse))
328 			return true;
329 		return this.mouseEvent(mouse);
330 	}
331 	/// called by parent for keyboardEvent
332 	bool _keyboardEventCall(KeyboardEvent key, bool cycle){
333 		if (!(_eventSub & key.state))
334 			return false;
335 		if (_customKeyboardEvent && _customKeyboardEvent(this, key, cycle))
336 			return true;
337 		return this.keyboardEvent(key, cycle);
338 	}
339 	/// called by parent for resizeEvent
340 	bool _resizeEventCall(){
341 		_requestingResize = false;
342 		_requestUpdate();
343 		if (!(_eventSub & EventMask.Resize))
344 			return false;
345 		if (_customResizeEvent && _customResizeEvent(this))
346 			return true;
347 		return this.resizeEvent();
348 	}
349 	/// called by parent for scrollEvent
350 	bool _scrollEventCall(){
351 		_requestUpdate();
352 		if (!(_eventSub & EventMask.Scroll))
353 			return false;
354 		if (_customScrollEvent && _customScrollEvent(this))
355 			return true;
356 		return this.scrollEvent();
357 	}
358 	/// called by parent for activateEvent
359 	bool _activateEventCall(bool isActive){
360 		if (!(_eventSub & EventMask.Activate))
361 			return false;
362 		if (_isActive == isActive)
363 			return false;
364 		_isActive = isActive;
365 		if (_customActivateEvent && _customActivateEvent(this, isActive))
366 			return true;
367 		return this.activateEvent(isActive);
368 	}
369 	/// called by parent for timerEvent
370 	bool _timerEventCall(uint msecs){
371 		if (!(_eventSub & EventMask.Timer))
372 			return false;
373 		if (_customTimerEvent && _customTimerEvent(this, msecs))
374 			return true;
375 		return timerEvent(msecs);
376 	}
377 	/// called by parent for updateEvent
378 	bool _updateEventCall(){
379 		if (!_requestingUpdate || !(_eventSub & EventMask.Update))
380 			return false;
381 		_requestingUpdate = false;
382 		_view.moveTo(_view._offsetX,_view._offsetY);
383 		if (_customUpdateEvent && _customUpdateEvent(this))
384 			return true;
385 		return this.updateEvent();
386 	}
387 protected:
388 	/// minimum width
389 	uint _minWidth;
390 	/// maximum width
391 	uint _maxWidth;
392 	/// minimum height
393 	uint _minHeight;
394 	/// maximum height
395 	uint _maxHeight;
396 
397 	/// viewport coordinates. (drawable area for widget)
398 	final @property uint viewportX(){
399 		return _view._offsetX;
400 	}
401 	/// ditto
402 	final @property uint viewportY(){
403 		return _view._offsetY;
404 	}
405 	/// viewport size. (drawable area for widget)
406 	final @property uint viewportWidth(){
407 		return _view._width;
408 	}
409 	/// ditto
410 	final @property uint viewportHeight(){
411 		return _view._height;
412 	}
413 
414 	/// If a coordinate is within writing area, and writing area actually exists
415 	final bool isWritable(uint x, uint y){
416 		return _view.isWritable(x,y);
417 	}
418 
419 	/// move seek for next write to terminal.  
420 	/// can only write in between:
421 	/// `(_viewX .. _viewX + _viewWidth, _viewY .. _viewX + _viewHeight)`
422 	final void moveTo(uint newX, uint newY){
423 		_view.moveTo(newX, newY);
424 	}
425 	/// writes a character on terminal
426 	/// 
427 	/// Returns: true if done, false if outside writing area
428 	final bool write(dchar c, Color fg, Color bg){
429 		return _view.write(c, fg, bg);
430 	}
431 	/// writes a (d)string to terminal. if it does not fit in one line, it is wrapped
432 	/// 
433 	/// Returns: number of characters written
434 	final uint write(dstring s, Color fg, Color bg){
435 		return _view.write(s, fg, bg);
436 	}
437 	/// fill current line with a character. `max` is ignored if `max==0`
438 	/// 
439 	/// Returns: number of cells written
440 	final uint fillLine(dchar c, Color fg, Color bg, uint max = 0){
441 		return _view.fillLine(c, fg, bg, max);
442 	}
443 
444 	/// activate the passed widget if this is actually the correct widget
445 	/// 
446 	/// Returns: if it was activated or not
447 	bool searchAndActivateWidget(QWidget target) {
448 		if (this == target) {
449 			this._isActive = true;
450 			if (this._eventSub & EventMask.Activate)
451 				this._activateEventCall(true);
452 			return true;
453 		}
454 		return false;
455 	}
456 
457 	/// called by itself, to update events subscribed to
458 	final void eventSubscribe(uint newSub){
459 		_eventSub = newSub;
460 		if (_parent)
461 			_parent.eventSubscribe();
462 	}
463 	/// called by children when they want to re-subscribe to events
464 	void eventSubscribe(){}
465 
466 	/// to set cursor position on terminal. will only work if this is active widget.  
467 	/// set x or y or both to negative to hide cursor
468 	void requestCursorPos(int x, int y){
469 		_requestCursorPos(x, y);
470 	}
471 
472 	/// called to request to scrollX
473 	bool requestScrollX(uint x){
474 		return _requestScrollX(x);
475 	}
476 	/// called to request to scrollY
477 	bool requestScrollY(uint y){
478 		return _requestScrollY(y);
479 	}
480 
481 	/// Called after UI has been run
482 	bool initialize(){
483 		return false;
484 	}
485 	/// Called when mouse is clicked with cursor on this widget.
486 	bool mouseEvent(MouseEvent mouse){
487 		return false;
488 	}
489 	/// Called when key is pressed and this widget is active.
490 	bool keyboardEvent(KeyboardEvent key, bool cycle){
491 		return false;
492 	}
493 	/// Called when widget size is changed,
494 	/// or widget should recalculate it's child widgets' sizes;  
495 	bool resizeEvent(){
496 		return false;
497 	}
498 	/// Called when the widget is rescrolled, but size not changed.  
499 	bool scrollEvent(){
500 		return false;
501 	}
502 	/// called right after this widget is activated, or de-activated
503 	bool activateEvent(bool isActive){
504 		return false;
505 	}
506 	/// called often. `msecs` is the msecs since last timerEvent, not accurate
507 	bool timerEvent(uint msecs){
508 		return false;
509 	}
510 	/// Called to update this widget
511 	bool updateEvent(){
512 		return false;
513 	}
514 public:
515 	/// To request parent to trigger an update event
516 	void requestUpdate(){
517 		_requestUpdate();
518 	}
519 	/// To request parent to trigger a resize event
520 	void requestResize(){
521 		_requestResize();
522 	}
523 	/// use to change the custom initialize event
524 	final @property InitFunction onInitEvent(InitFunction func){
525 		return _customInitEvent = func;
526 	}
527 	/// use to change the custom mouse event
528 	final @property MouseEventFuction onMouseEvent(MouseEventFuction func){
529 		return _customMouseEvent = func;
530 	}
531 	/// use to change the custom keyboard event
532 	final @property KeyboardEventFunction onKeyboardEvent(KeyboardEventFunction func){
533 		return _customKeyboardEvent = func;
534 	}
535 	/// use to change the custom resize event
536 	final @property ResizeEventFunction onResizeEvent(ResizeEventFunction func){
537 		return _customResizeEvent = func;
538 	}
539 	/// use to change the custom activate event
540 	final @property ActivateEventFunction onActivateEvent(ActivateEventFunction func){
541 		return _customActivateEvent = func;
542 	}
543 	/// use to change the custom timer event
544 	final @property TimerEventFunction onTimerEvent(TimerEventFunction func){
545 		return _customTimerEvent = func;
546 	}
547 	/// Returns: true if this widget is the current active widget
548 	final @property bool isActive(){
549 		return _isActive;
550 	}
551 	/// Returns: EventMask of subscribed events
552 	final @property uint eventSub(){
553 		return _eventSub;
554 	}
555 	/// specifies ratio of height or width (depending on parent layout type)
556 	final @property uint sizeRatio(){
557 		return _sizeRatio;
558 	}
559 	/// ditto
560 	final @property uint sizeRatio(uint newRatio){
561 		_sizeRatio = newRatio;
562 		_requestResize();
563 		return _sizeRatio;
564 	}
565 	/// visibility of the widget.
566 	final @property bool show(){
567 		return _show;
568 	}
569 	/// ditto
570 	final @property bool show(bool visibility){
571 		_show = visibility;
572 		_requestResize();
573 		return _show;
574 	}
575 	/// horizontal scroll.
576 	final @property uint scrollX(){
577 		return _scrollX;
578 	}
579 	/// ditto
580 	final @property uint scrollX(uint newVal){
581 		_requestScrollX(newVal);
582 		return _scrollX;
583 	}
584 	/// vertical scroll.
585 	final @property uint scrollY(){
586 		return _scrollY;
587 	}
588 	/// ditto
589 	final @property uint scrollY(uint newVal){
590 		_requestScrollY(newVal);
591 		return _scrollY;
592 	}
593 	/// width of widget
594 	final @property uint width(){
595 		return _width;
596 	}
597 	/// fixed width setter.
598 	/// This will actually do `minWidth=maxWidth=value` and call requestResize
599 	@property uint width(uint value){
600 		_minWidth = value;
601 		_maxWidth = value;
602 		_requestResize();
603 		return value;
604 	}
605 	/// height of widget
606 	final @property uint height(){
607 		return _height;
608 	}
609 	/// fixed height setter.
610 	/// This will actually do `minHeight=maxHeight=value` and call requestResize
611 	@property uint height(uint value){
612 		_minHeight = value;
613 		_maxHeight = value;
614 		_requestResize();
615 		return value;
616 	}
617 	/// minimum width
618 	@property uint minWidth(){
619 		return _minWidth;
620 	}
621 	/// ditto
622 	@property uint minWidth(uint value){
623 		_requestResize();
624 		return _minWidth = value;
625 	}
626 	/// minimum height
627 	@property uint minHeight(){
628 		return _minHeight;
629 	}
630 	/// ditto
631 	@property uint minHeight(uint value){
632 		_requestResize();
633 		return _minHeight = value;
634 	}
635 	/// maximum width
636 	@property uint maxWidth(){
637 		return _maxWidth;
638 	}
639 	/// ditto
640 	@property uint maxWidth(uint value){
641 		_requestResize();
642 		return _maxWidth = value;
643 	}
644 	/// maximum height
645 	@property uint maxHeight(){
646 		return _maxHeight;
647 	}
648 	/// ditto
649 	@property uint maxHeight(uint value){
650 		_requestResize();
651 		return _maxHeight = value;
652 	}
653 }
654 
655 ///Used to place widgets in an order (i.e vertical or horizontal)
656 class QLayout : QWidget{
657 private:
658 	/// array of all the widgets that have been added to this layout
659 	QWidget[] _widgets;
660 	/// layout type, horizontal or vertical
661 	QLayout.Type _type;
662 	/// stores index of active widget. -1 if none.
663 	int _activeWidgetIndex = -1;
664 	/// if it is overflowing
665 	bool _isOverflowing = false;
666 	/// Color to fill with in unoccupied space
667 	Color _fillColor;
668 	/// Color to fill with when overflowing
669 	Color _overflowColor = DEFAULT_OVERFLOW_BG;
670 
671 	/// gets height/width of a widget using it's sizeRatio and min/max-height/width
672 	uint _calculateWidgetSize(QWidget widget, uint ratioTotal, uint totalSpace,
673 		ref bool free){
674 		immutable uint calculatedSize =
675 			cast(uint)(widget._sizeRatio * totalSpace / ratioTotal);
676 		if (_type == QLayout.Type.Horizontal){
677 			free = widget.minWidth == 0 && widget.maxWidth == 0;
678 			return getLimitedSize(calculatedSize, widget.minWidth, widget.maxWidth);
679 		}
680 		free = widget.minHeight == 0 && widget.maxHeight == 0;
681 		return getLimitedSize(calculatedSize, widget.minHeight, widget.maxHeight);
682 	}
683 	
684 	/// recalculates the size of every widget inside layout
685 	void _recalculateWidgetsSize(){
686 		FIFOStack!QWidget widgetStack = new FIFOStack!QWidget;
687 		uint totalRatio = 0;
688 		uint totalSpace = _type == QLayout.Type.Horizontal ? _width : _height;
689 		bool free; // @suppress(dscanner.suspicious.unmodified) shut up vscode
690 		foreach (widget; _widgets){
691 			if (!widget._show)
692 				continue;
693 			totalRatio += widget._sizeRatio;
694 			widget._height = getLimitedSize(_height, widget.minHeight, widget.maxHeight);
695 			widget._width = getLimitedSize(_width, widget.minWidth, widget.maxWidth);
696 			widgetStack.push(widget);
697 		}
698 		// do widgets with size limits
699 		/// totalRatio, and space used of widgets with limits
700 		uint limitWRatio, limitWSize;
701 		for (int i = 0; i < widgetStack.count; i ++){
702 			QWidget widget = widgetStack.pop;
703 			immutable uint space =
704 				_calculateWidgetSize(widget, totalRatio, totalSpace, free);
705 			if (free){
706 				widgetStack.push(widget);
707 				continue;
708 			}
709 			if (_type == QLayout.Type.Horizontal)
710 				widget._width = space;
711 			else
712 				widget._height = space;
713 			limitWRatio += widget._sizeRatio;
714 			limitWSize += space;
715 		}
716 		totalSpace -= limitWSize;
717 		totalRatio -= limitWRatio;
718 		while (widgetStack.count){
719 			QWidget widget = widgetStack.pop;
720 			immutable uint space =
721 				_calculateWidgetSize(widget, totalRatio, totalSpace, free);
722 			if (_type == QLayout.Type.Horizontal)
723 				widget._width = space;
724 			else
725 				widget._height = space;
726 			totalRatio -= widget._sizeRatio;
727 			totalSpace -= space;
728 		}
729 		.destroy(widgetStack);
730 	}
731 
732 	/// find the next widget to activate
733 	/// Returns: index, or -1 if no one wanna be active
734 	int _nextActiveWidget(){
735 		for (int i = _activeWidgetIndex + 1; i < _widgets.length; i ++){
736 			if ((_widgets[i]._eventSub & EventMask.KeyboardAll) && _widgets[i]._show)
737 				return i;
738 		}
739 		return -1;
740 	}
741 protected:
742 	override void eventSubscribe(){
743 		_eventSub = 0;
744 		foreach (widget; _widgets)
745 			_eventSub |= widget._eventSub;
746 		
747 		// if children can become active, then need activate too
748 		if (_eventSub & EventMask.KeyboardAll)
749 			_eventSub |= EventMask.Activate;
750 		
751 		_eventSub |= EventMask.Scroll;
752 
753 		if (_parent)
754 			_parent.eventSubscribe();
755 	}
756 
757 	/// Recalculates size and position for all visible widgets
758 	override bool resizeEvent(){
759 		_recalculateWidgetsSize(); // resize everything
760 		// if parent is scrollable container, and there are no size limits,
761 		// then grow as needed
762 		if (minHeight + maxHeight + minWidth + maxWidth == 0 && 
763 		_parent && _parent._isScrollableContainer){
764 			_width = 0;
765 			_height = 0;
766 			if (_type == Type.Horizontal){
767 				foreach (widget; _widgets){
768 					if (_height < widget._height)
769 						_height = widget._height;
770 					_width += widget._width;
771 				}
772 			}else{
773 				foreach (widget; _widgets){
774 					if (_width < widget._width)
775 						_width = widget._width;
776 					_height += widget._height;
777 				}
778 			}
779 		}
780 		// now reposition everything
781 		/// space taken by widgets before
782 		uint previousSpace = 0;
783 		uint w, h;
784 		foreach(widget; _widgets){
785 			if (!widget._show)
786 				continue;
787 			if (_type == QLayout.Type.Horizontal){
788 				widget._posY = 0;
789 				widget._posX = previousSpace;
790 				previousSpace += widget._width;
791 				w += widget._width;
792 				if (widget._height > h)
793 					h = widget._height;
794 			}else{
795 				widget._posX = 0;
796 				widget._posY = previousSpace;
797 				previousSpace += widget._height;
798 				h += widget._height;
799 				if (widget._width > w)
800 					w = widget._width;
801 			}
802 		}
803 		_isOverflowing = w > _width || h > _height;
804 		if (_isOverflowing){
805 			foreach (widget; _widgets)
806 				widget._view._reset();
807 		}else{
808 			foreach (i, widget; _widgets){
809 				_view._getSlice(&(widget._view), widget._posX, widget._posY,
810 					widget._width, widget._height);
811 				widget._resizeEventCall();
812 			}
813 		}
814 		return true;
815 	}
816 
817 	/// Resize event
818 	override bool scrollEvent(){
819 		if (_isOverflowing){
820 			foreach (widget; _widgets)
821 				widget._view._reset();
822 			return false;
823 		}
824 		foreach (i, widget; _widgets){
825 			_view._getSlice(&(widget._view), widget._posX, widget._posY,
826 				widget._width, widget._height);
827 			widget._scrollEventCall();
828 		}
829 		return true;
830 	}
831 	
832 	/// Redirects the mouseEvent to the appropriate widget
833 	override public bool mouseEvent(MouseEvent mouse){
834 		if (_isOverflowing)
835 			return false;
836 		int index;
837 		if (_type == Type.Horizontal){
838 			foreach (i, w; _widgets){
839 				if (w._show && w._posX <= mouse.x && w._posX + w._width > mouse.x){
840 					index = cast(int)i;
841 					break;
842 				}
843 			}
844 		}else{
845 			foreach (i, w; _widgets){
846 				if (w._show && w._posY <= mouse.y && w._posY + w._height > mouse.y){
847 					index = cast(int)i;
848 					break;
849 				}
850 			}
851 		}
852 		if (index > -1){
853 			if (mouse.state != MouseEvent.State.Hover && index != _activeWidgetIndex &&
854 			_widgets[index]._eventSub & EventMask.KeyboardAll){
855 				if (_activeWidgetIndex > -1)
856 					_widgets[_activeWidgetIndex]._activateEventCall(false);
857 				_widgets[index]._activateEventCall(true);
858 				_activeWidgetIndex = index;
859 			}
860 			return _widgets[index]._mouseEventCall(mouse);
861 		}
862 		return false;
863 	}
864 
865 	/// Redirects the keyboardEvent to appropriate widget
866 	override public bool keyboardEvent(KeyboardEvent key, bool cycle){
867 		if (_isOverflowing)
868 			return false;
869 		if (_activeWidgetIndex > -1 &&
870 		_widgets[_activeWidgetIndex]._keyboardEventCall(key, cycle))
871 			return true;
872 		
873 		if (!cycle)
874 			return false;
875 		immutable int next = _nextActiveWidget();
876 
877 		if (_activeWidgetIndex > -1 && next != _activeWidgetIndex)
878 			_widgets[_activeWidgetIndex]._activateEventCall(false);
879 
880 		if (next == -1)
881 			return false;
882 		_activeWidgetIndex = next;
883 		_widgets[_activeWidgetIndex]._activateEventCall(true);
884 		return true;
885 	}
886 
887 	/// override initialize to initliaze child widgets
888 	override bool initialize(){
889 		foreach (widget; _widgets){
890 			widget._view._reset();
891 			widget._initializeCall();
892 		}
893 		return true;
894 	}
895 
896 	/// override timer event to call child widgets' timers
897 	override bool timerEvent(uint msecs){
898 		foreach (widget; _widgets)
899 			widget._timerEventCall(msecs);
900 		return true;
901 	}
902 
903 	/// override activate event
904 	override bool activateEvent(bool isActive){
905 		if (isActive){
906 			_activeWidgetIndex = -1;
907 			_activeWidgetIndex = _nextActiveWidget();
908 		}
909 		if (_activeWidgetIndex > -1)
910 			_widgets[_activeWidgetIndex]._activateEventCall(isActive);
911 		return true;
912 	}
913 	
914 	/// called by parent widget to update
915 	override bool updateEvent(){
916 		if (_isOverflowing){
917 			foreach (y; viewportY .. viewportY + viewportHeight){
918 				moveTo(viewportX, y);
919 				fillLine(' ', DEFAULT_FG, _overflowColor);
920 			}
921 			return false;
922 		}
923 		if (_type == Type.Horizontal){
924 			foreach(i, widget; _widgets){
925 				if (widget._show && widget._requestingUpdate)
926 					widget._updateEventCall();
927 				foreach (y; widget._height .. viewportY + viewportHeight){
928 					moveTo(widget._posX, y);
929 					fillLine(' ', DEFAULT_FG, _fillColor, widget._width);
930 				}
931 			}
932 		}else{
933 			foreach(i, widget; _widgets){
934 				if (widget._show && widget._requestingUpdate)
935 					widget._updateEventCall();
936 				if (widget._width == _width)
937 					continue;
938 				foreach (y; widget._posY .. widget._posY + widget._height){
939 					moveTo(widget._posX + widget._width, y);
940 					fillLine(' ', DEFAULT_FG, _fillColor);
941 				}
942 			}
943 		}
944 		return true;
945 	}
946 
947 	/// activate the passed widget if it's in the current layout
948 	/// Returns: true if it was activated or not
949 	override bool searchAndActivateWidget(QWidget target){
950 		immutable int lastActiveWidgetIndex = _activeWidgetIndex;
951 
952 		// search and activate recursively
953 		_activeWidgetIndex = -1;
954 		foreach (index, widget; _widgets) {
955 			if ((widget._eventSub & EventMask.KeyboardAll) && widget._show &&
956 			widget.searchAndActivateWidget(target)){
957 				_activeWidgetIndex = cast(int)index;
958 				break;
959 			}
960 		}
961 
962 		// and then manipulate the current layout
963 		if (lastActiveWidgetIndex != _activeWidgetIndex && lastActiveWidgetIndex > -1)
964 			_widgets[lastActiveWidgetIndex]._activateEventCall(false);
965 		return _activeWidgetIndex != -1;
966 	}
967 
968 public:
969 	/// Layout type
970 	enum Type{
971 		Vertical,
972 		Horizontal,
973 	}
974 	/// constructor
975 	this(QLayout.Type type){
976 		_type = type;
977 		this._fillColor = DEFAULT_BG;
978 	}
979 	/// destructor, kills children
980 	~this(){
981 		foreach (child; _widgets)
982 			.destroy(child);
983 	}
984 	/// Color for unoccupied space
985 	@property Color fillColor(){
986 		return _fillColor;
987 	}
988 	/// ditto
989 	@property Color fillColor(Color newColor){
990 		return _fillColor = newColor;
991 	}
992 	/// Color to fill with when out of space
993 	@property Color overflowColor(){
994 		return _overflowColor;
995 	}
996 	/// ditto
997 	@property Color overflowColor(Color newColor){
998 		return _overflowColor = newColor;
999 	}
1000 	/// adds a widget, and makes space for it
1001 	void addWidget(QWidget widget, bool allowScrollControl = false){
1002 		widget._parent = this;
1003 		widget._canReqScroll = allowScrollControl && _canReqScroll;
1004 		_widgets ~= widget;
1005 		eventSubscribe();
1006 	}
1007 	/// ditto
1008 	void addWidget(QWidget[] widgets){
1009 		foreach (i, widget; widgets){
1010 			widget._parent = this;
1011 		}
1012 		_widgets ~= widgets;
1013 		eventSubscribe();
1014 	}
1015 }
1016 
1017 /// Container for widgets to make them scrollable, with optional scrollbars
1018 class ScrollContainer : QWidget{
1019 private:
1020 	/// offset in _widget before adding scrolling to it
1021 	uint _offX, _offY;
1022 protected:
1023 	/// the widget to be scrolled
1024 	QWidget _widget;
1025 	/// if vertical and horizontal scrollbars are to be shown
1026 	bool _scrollbarV, _scrollbarH;
1027 	/// if page down/up button should scroll
1028 	bool _pgDnUp = false;
1029 	/// if mouse wheel should scroll
1030 	bool _mouseWheel = false;
1031 	/// height and width after subtracting space for scrollbars
1032 	uint _drawAreaHeight, _drawAreaWidth;
1033 	
1034 	override void eventSubscribe(){
1035 		_eventSub = EventMask.Resize | EventMask.Scroll;
1036 		if (_widget)
1037 			_eventSub |= _widget._eventSub;
1038 		if (_pgDnUp)
1039 			_eventSub |= EventMask.KeyboardPress;
1040 		if (_mouseWheel)
1041 			_eventSub |= EventMask.MouseAll;
1042 		if (_scrollbarH || _scrollbarV)
1043 			_eventSub |= EventMask.Update;
1044 		if (_parent)
1045 			_parent.eventSubscribe();
1046 	}
1047 
1048 	/// re-assings display buffer based on _subScrollX/Y, and calls scrollEvent on child
1049 	final void rescroll(bool callScrollEvent = true){
1050 		if (!_widget)
1051 			return;
1052 		_widget._view._reset();
1053 		if (_width == 0 || _height == 0)
1054 			return;
1055 		uint w = _drawAreaWidth, h = _drawAreaHeight;
1056 		if (w > _widget._width)
1057 			w = _widget._width;
1058 		if (h > _widget._height)
1059 			h = _widget._height;
1060 		_view._getSlice(&(_widget._view), 0, 0, w, h);
1061 		_widget._view._offsetX = _offX + _widget._scrollX;
1062 		_widget._view._offsetY = _offY + _widget._scrollY;
1063 		if (callScrollEvent)
1064 			_widget._scrollEventCall();
1065 	}
1066 
1067 	override bool requestScrollX(uint x){
1068 		if (!_widget)
1069 			return false;
1070 		if (_widget._width <= _drawAreaWidth)
1071 			x = 0;
1072 		else if (x > _widget._width - _drawAreaWidth)
1073 			x = _widget._width - _drawAreaWidth;
1074 		
1075 		if (_widget._scrollX == x)
1076 			return false;
1077 		_widget._scrollX = x;
1078 		rescroll();
1079 		return true;
1080 	}
1081 	override bool requestScrollY(uint y){
1082 		if (!_widget)
1083 			return false;
1084 		if (_widget._height <= _drawAreaHeight)
1085 			y = 0;
1086 		else if (y > _widget._height - _drawAreaHeight)
1087 			y = _widget._height - _drawAreaHeight;
1088 		
1089 		if (_widget._scrollY == y)
1090 			return false;
1091 		_widget._scrollY = y;
1092 		rescroll();
1093 		return true;
1094 	}
1095 
1096 	override bool resizeEvent(){
1097 		_offX = _view._offsetX;
1098 		_offY = _view._offsetY;
1099 		_drawAreaHeight = _height - (1*(_height>0 && _scrollbarV));
1100 		_drawAreaWidth = _width - (1*(_width>0 && _scrollbarV));
1101 		if (!_widget)
1102 			return false;
1103 		if (_scrollbarH || _scrollbarV)
1104 			requestUpdate();
1105 		// try to size widget to fit
1106 		if (_height > 0 && _width > 0){
1107 			_widget._width = getLimitedSize(_drawAreaWidth, _widget.minWidth,
1108 				_widget.maxWidth);
1109 			_widget._height = getLimitedSize(_drawAreaHeight, _widget.minHeight,
1110 				_widget.maxHeight);
1111 		}
1112 		rescroll(false);
1113 		_widget._resizeEventCall();
1114 
1115 		return true;
1116 	}
1117 
1118 	override bool scrollEvent(){
1119 		_offX = _view._offsetX;
1120 		_offY = _view._offsetY;
1121 		if (_scrollbarH || _scrollbarV)
1122 			requestUpdate();
1123 		rescroll();
1124 
1125 		return true;
1126 	}
1127 
1128 	override bool keyboardEvent(KeyboardEvent key, bool cycle){
1129 		if (!_widget)
1130 			return false;
1131 		if (_widget.isActive && _widget._keyboardEventCall(key, cycle))
1132 			return true;
1133 		
1134 		if (!cycle && _pgDnUp && _drawAreaHeight < _widget._height &&
1135 		key.state == KeyboardEvent.State.Pressed){
1136 			if (key.key == Key.PageUp){
1137 				return requestScrollY(_drawAreaHeight > _widget._scrollY ? 0 :
1138 					_widget._scrollY - _drawAreaHeight);
1139 			}
1140 			if (key.key == Key.PageDown){
1141 				return requestScrollY(
1142 					_drawAreaHeight + _widget._scrollY > _widget._height ? 
1143 					_widget._height - _drawAreaHeight :
1144 					_widget._scrollY + _drawAreaHeight);
1145 			}
1146 		}
1147 		_widget._activateEventCall(false);
1148 		return false;
1149 	}
1150 
1151 	override bool mouseEvent(MouseEvent mouse){
1152 		if (!_widget)
1153 			return false;
1154 		if (_widget._mouseEventCall(mouse)){
1155 			_widget._activateEventCall(true);
1156 			return true;
1157 		}
1158 		_widget._activateEventCall(false);
1159 		if (_mouseWheel && _drawAreaHeight < _widget._height){
1160 			if (mouse.button == mouse.Button.ScrollUp){
1161 				if (_widget._scrollY)
1162 					return requestScrollY(_widget._scrollY - 1);
1163 			}
1164 			if (mouse.button == mouse.Button.ScrollDown){
1165 				if (_widget._scrollY + _drawAreaHeight < _widget._height)
1166 					return requestScrollY(_widget._scrollY + 1);
1167 			}
1168 		}
1169 		return false;
1170 	}
1171 
1172 	override bool updateEvent(){
1173 		if (!_widget)
1174 			return false;
1175 		if (_widget.show)
1176 			_widget._updateEventCall();
1177 		if (_width > _widget._view._width + (1*(_scrollbarV))){
1178 			foreach (y; 0 .. _widget._view._height){
1179 				moveTo(_widget._view._width, y);
1180 				fillLine(' ', DEFAULT_FG, DEFAULT_BG);
1181 			}
1182 		}
1183 		if (_height > _widget._view._height + (1*(_scrollbarH))){
1184 			foreach (y; _widget._view._height .. _height - (1*(_scrollbarH))){
1185 				moveTo(0, y);
1186 				fillLine(' ', DEFAULT_BG, DEFAULT_FG);
1187 			}
1188 		}
1189 		drawScrollbars();
1190 
1191 		return true;
1192 	}
1193 
1194 	/// draws scrollbars, very basic stuff
1195 	void drawScrollbars(){
1196 		if (!_widget || _width == 0 || _height == 0)
1197 			return;
1198 		const dchar verticalLine = '│', horizontalLine = '─';
1199 		if (_scrollbarH && _scrollbarV){
1200 			moveTo(_width - 1, _height - 1);
1201 			write('┘', DEFAULT_FG, DEFAULT_BG); // bottom right corner
1202 		}
1203 		if (_scrollbarH){
1204 			moveTo(0, _drawAreaHeight);
1205 			fillLine(horizontalLine, DEFAULT_FG, DEFAULT_BG, _drawAreaWidth);
1206 			const int maxScroll = _widget._width - _drawAreaWidth;
1207 			if (maxScroll > 0){
1208 				const uint barPos = (_widget._scrollX * _drawAreaWidth) / maxScroll;
1209 				moveTo(barPos, _drawAreaHeight);
1210 				write(' ', DEFAULT_BG, DEFAULT_FG);
1211 			}
1212 		}
1213 		if (_scrollbarV){
1214 			foreach (y; 0 .. _drawAreaHeight){
1215 				moveTo(_drawAreaWidth, y);
1216 				write(verticalLine, DEFAULT_FG, DEFAULT_BG);
1217 			}
1218 			const int maxScroll = _widget._height - _drawAreaHeight;
1219 			if (maxScroll > 0){
1220 				const uint barPos = (_widget._scrollY * _drawAreaHeight) / maxScroll;
1221 				moveTo(_drawAreaWidth, barPos);
1222 				write(' ', DEFAULT_BG, DEFAULT_FG);
1223 			}
1224 		}
1225 	}
1226 public:
1227 	/// constructor
1228 	this(){
1229 		this._isScrollableContainer = true;
1230 		this._scrollbarV = true;
1231 		this._scrollbarH = true;
1232 	}
1233 	~this(){
1234 		if (_widget)
1235 			.destroy(_widget); // kill the child!
1236 	}
1237 
1238 	/// Sets the child widget.
1239 	/// 
1240 	/// Returns: false if alreadty has a child
1241 	bool setWidget(QWidget child){
1242 		if (_widget)
1243 			return false;
1244 		_widget = child;
1245 		_widget._parent = this;
1246 		_widget._canReqScroll = true;
1247 		_widget._posX = 0;
1248 		_widget._posY = 0;
1249 		eventSubscribe();
1250 		return true;
1251 	}
1252 
1253 	override void requestResize(){
1254 		// just do a the resize within itself, no need to ask parent
1255 		_resizeEventCall();
1256 	}
1257 
1258 	/// Whether to scroll on page up/down keys
1259 	@property bool scrollOnPageUpDown(){
1260 		return _pgDnUp;
1261 	}
1262 	/// ditto
1263 	@property bool scrollOnPageUpDown(bool newVal){
1264 		_pgDnUp = newVal;
1265 		eventSubscribe();
1266 		return _pgDnUp;
1267 	}
1268 
1269 	/// Whether to scroll on mouse scroll wheel
1270 	@property bool scrollOnMouseWheel(){
1271 		return _mouseWheel;
1272 	}
1273 	/// ditto
1274 	@property bool scrollOnMouseWheel(bool newVal){
1275 		_mouseWheel = newVal;
1276 		eventSubscribe();
1277 		return _mouseWheel;
1278 	}
1279 
1280 	/// Whether to show vertical scrollbar. Modifying this will request update 
1281 	@property bool scrollbarV(){
1282 		return _scrollbarV;
1283 	}
1284 	/// ditto
1285 	@property bool scrollbarV(bool newVal){
1286 		if (newVal != _scrollbarV){
1287 			_scrollbarV = newVal;
1288 			rescroll();
1289 		}
1290 		return _scrollbarV;
1291 	}
1292 	/// Whether to show horizontal scrollbar. Modifying this will request update
1293 	@property bool scrollbarH(){
1294 		return _scrollbarH;
1295 	}
1296 	/// ditto
1297 	@property bool scrollbarH(bool newVal){
1298 		if (newVal != _scrollbarH){
1299 			_scrollbarH = newVal;
1300 			rescroll();
1301 		}
1302 		return _scrollbarH;
1303 	}
1304 }
1305 
1306 /// A terminal (as the name says).
1307 class QTerminal : QLayout{
1308 private:
1309 	/// To actually access the terminal
1310 	TermWrapper _termWrap;
1311 	/// set to false to stop UI loop in run()
1312 	bool _isRunning;
1313 	/// the key used for cycling active widget
1314 	dchar _activeWidgetCycleKey = WIDGET_CYCLE_KEY;
1315 	/// whether to stop UI loop on Interrupt
1316 	bool _stopOnInterrupt = true;
1317 	/// cursor position
1318 	int _cursorX = -1, _cursorY = -1;
1319 
1320 	/// Reads InputEvent and calls appropriate functions to address those events
1321 	void _readEvent(Event event){
1322 		if (event.type == Event.Type.HangupInterrupt){
1323 			if (_stopOnInterrupt)
1324 				_isRunning = false;
1325 			else{ // otherwise read it as a Ctrl+C
1326 				KeyboardEvent keyEvent;
1327 				keyEvent.key = KeyboardEvent.CtrlKeys.CtrlC;
1328 				this._keyboardEventCall(keyEvent, false);
1329 			}
1330 		}else if (event.type == Event.Type.Keyboard){
1331 			KeyboardEvent kPress = event.keyboard;
1332 			this._keyboardEventCall(kPress, false);
1333 		}else if (event.type == Event.Type.Mouse){
1334 			this._mouseEventCall(event.mouse);
1335 		}else if (event.type == Event.Type.Resize){
1336 			this._resizeEventCall();
1337 		}
1338 	}
1339 
1340 	/// writes _view to _termWrap
1341 	void _flushBuffer(){
1342 		if (_view._buffer.length == 0)
1343 			return;
1344 		Cell prev = _view._buffer[0];
1345 		_termWrap.color(prev.fg, prev.bg);
1346 		uint x, y;
1347 		foreach (cell; _view._buffer){
1348 			if (!prev.propSame(cell))
1349 				_termWrap.color(cell.fg, cell.bg);
1350 			prev = cell;
1351 			_termWrap.put(x, y, cell.c);
1352 			x ++;
1353 			if (x == _width){
1354 				x = 0;
1355 				y ++;
1356 			}
1357 		}
1358 	}
1359 
1360 protected:
1361 
1362 	override void eventSubscribe(){
1363 		// ignore what children want, this needs all events
1364 		// so custom event handlers can be set up 
1365 		_eventSub = uint.max;
1366 	}
1367 
1368 	override void requestCursorPos(int x, int y){
1369 		_cursorX = x;
1370 		_cursorY = y;
1371 	}
1372 
1373 	override bool resizeEvent(){
1374 		_height = _termWrap.height;
1375 		_width = _termWrap.width;
1376 		_view._buffer.length = _width * _height;
1377 		_view._width = _width;
1378 		_view._actualWidth = _width;
1379 		_view._height = _height;
1380 		super.resizeEvent();
1381 		return true;
1382 	}
1383 
1384 	override bool keyboardEvent(KeyboardEvent key, bool cycle){
1385 		cycle = key.state == KeyboardEvent.State.Pressed &&
1386 			key.key == _activeWidgetCycleKey;
1387 		return super.keyboardEvent(key, cycle);
1388 	}
1389 	
1390 	override bool updateEvent(){
1391 		// resize if needed
1392 		if (_requestingResize)
1393 			this._resizeEventCall();
1394 		_cursorX = -1;
1395 		_cursorY = -1;
1396 		// no, this is not a mistake, dont change this to updateEventCall again!
1397 		super.updateEvent();
1398 		// flush _view._buffer to _termWrap
1399 		_flushBuffer();
1400 		// check if need to show/hide cursor
1401 		if (_cursorX < 0 || _cursorY < 0)
1402 			_termWrap.cursorVisible = false;
1403 		else{
1404 			_termWrap.moveCursor(_cursorX, _cursorY);
1405 			_termWrap.cursorVisible = true;
1406 		}
1407 		_termWrap.flush();
1408 		return true;
1409 	}
1410 
1411 public:
1412 	/// time to wait between timer events (milliseconds)
1413 	ushort timerMsecs;
1414 	/// constructor
1415 	this(QLayout.Type displayType = QLayout.Type.Vertical, ushort timerDuration = 500){
1416 		super(displayType);
1417 		timerMsecs = timerDuration;
1418 
1419 		_termWrap = new TermWrapper();
1420 		// so it can make other widgets active on mouse events
1421 		this._isActive = true;
1422 	}
1423 	~this(){
1424 		.destroy(_termWrap);
1425 	}
1426 
1427 	/// stops UI loop. **not instant**
1428 	/// if it is in-between event functions, it will complete those first
1429 	void terminate(){
1430 		_isRunning = false;
1431 	}
1432 
1433 	/// whether to stop UI loop on HangupInterrupt (Ctrl+C)
1434 	@property bool terminateOnHangup(){
1435 		return _stopOnInterrupt;
1436 	}
1437 	/// ditto
1438 	@property bool terminateOnHangup(bool newVal){
1439 		return _stopOnInterrupt = newVal;
1440 	}
1441 	
1442 	/// starts the UI loop
1443 	void run(){
1444 		_initializeCall();
1445 		_resizeEventCall();
1446 		_updateEventCall();
1447 		_isRunning = true;
1448 		StopWatch sw = StopWatch(AutoStart.yes);
1449 		while (_isRunning){
1450 			int timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
1451 			Event event;
1452 			while (_termWrap.getEvent(timeout, event) > 0){
1453 				_readEvent(event);
1454 				timeout = cast(int)(timerMsecs - sw.peek.total!"msecs");
1455 				_updateEventCall();
1456 			}
1457 			if (sw.peek.total!"msecs" >= timerMsecs){
1458 				_timerEventCall(cast(uint)sw.peek.total!"msecs");
1459 				sw.reset;
1460 				sw.start;
1461 				_updateEventCall();
1462 			}
1463 		}
1464 	}
1465 
1466 	/// search the passed widget recursively and activate it
1467 	/// 
1468 	/// Returns: true if the widget was made active, false if not found and not done
1469 	bool activateWidget(QWidget target) {
1470 		return this.searchAndActivateWidget(target);
1471 	}
1472 
1473 	/// Changes the key used to cycle between active widgets.
1474 	void setActiveWidgetCycleKey(dchar key){
1475 		_activeWidgetCycleKey = key;
1476 	}
1477 }