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