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