1 /++
2 	Some widgets that are included in the package.
3 	
4 	
5 +/
6 module qui.widgets;
7 
8 import qui.qui;
9 import qui.misc;
10 import qui.lists;
11 
12 ///Displays some text
13 ///
14 ///And it can't handle new-line characters
15 ///
16 ///Name in theme: 'text-label';
17 class TextLabelWidget : QWidget{
18 private:
19 	RGBColor textColor, bgColor;
20 public:
21 	this(string wCaption = ""){
22 		widgetName = "text-label";
23 		widgetCaption = wCaption;
24 	}
25 
26 	override void updateColors(){
27 		if (&widgetTheme && widgetTheme.hasColors(name,["background","text"])){
28 			textColor = widgetTheme.getColor(name, "text");
29 			bgColor = widgetTheme.getColor(name, "background");
30 		}else{
31 			//use default values
32 			textColor = hexToColor("00FF00");
33 			bgColor = hexToColor("000000");
34 		}
35 	}
36 
37 	override bool update(ref Matrix display){
38 		if (needsUpdate){
39 			needsUpdate = false;
40 			//redraw text
41 			display.write(cast(char[])widgetCaption, textColor, bgColor);
42 			return true;
43 		}else{
44 			return false;
45 		}
46 	}
47 }
48 
49 /// Displays a left-to-right progress bar.
50 /// 
51 /// If caption is set, it is displayed in the middle of the widget
52 /// 
53 /// Name in theme: 'progressbar';
54 class ProgressbarWidget : QWidget{
55 private:
56 	uinteger max, done;
57 	RGBColor bgColor, barColor, textColor;
58 	void writeBarLine(ref Matrix display, uinteger filled, char[] bar){
59 		display.write(bar[0 .. filled], textColor, barColor);
60 		display.write(bar[filled .. bar.length], textColor, bgColor);
61 	}
62 public:
63 	this(uinteger totalAmount = 100, uinteger complete = 0){
64 		widgetName = "progressbar";
65 		widgetCaption = null;
66 		max = totalAmount;
67 		done = complete;
68 	}
69 
70 	override void updateColors(){
71 		needsUpdate = true;
72 		if (&widgetTheme && widgetTheme.hasColors(name, ["background", "bar", "text"])){
73 			bgColor = widgetTheme.getColor(name, "background");
74 			barColor = widgetTheme.getColor(name, "bar");
75 			textColor = widgetTheme.getColor(name, "text");
76 		}else{
77 			bgColor = hexToColor("A6A6A6");
78 			barColor = hexToColor("00FF00");
79 			textColor = hexToColor("000000");
80 		}
81 		if (forceUpdate !is null){
82 			forceUpdate();
83 		}
84 	}
85 
86 	override bool update(ref Matrix display){
87 		bool r = false;
88 		if (needsUpdate){
89 			needsUpdate = false;
90 			r = true;
91 			uinteger filled = ratioToRaw(done, max, widgetSize.width);
92 			char[] bar;
93 			bar.length = widgetSize.width;
94 			bar[0 .. bar.length] = ' ';
95 			if (widgetCaption != null){
96 				//write the caption too!
97 				uinteger middle = widgetSize.height/2;
98 				for (uinteger i = 0; i < widgetSize.height; i++){
99 					if (i == middle){
100 						bar = centerAlignText(cast(char[])caption, widgetSize.width);
101 						writeBarLine(display, filled, bar);
102 						bar[0 .. bar.length] = ' ';
103 						continue;
104 					}else{
105 						writeBarLine(display, filled, bar);
106 					}
107 				}
108 			}else{
109 				for (uinteger i = 0; i < widgetSize.height; i++){
110 					writeBarLine(display, filled, bar);
111 				}
112 			}
113 		}
114 		return r;
115 	}
116 	/// The 'total', or the max-progress. getter
117 	@property uinteger total(){
118 		return max;
119 	}
120 	/// The 'total' or the max-progress. setter
121 	@property uinteger total(uinteger newTotal){
122 		needsUpdate = true;
123 		max = newTotal;
124 		if (forceUpdate !is null){
125 			forceUpdate();
126 		}
127 		return max;
128 	}
129 	/// the amount of progress. getter
130 	@property uinteger progress(){
131 		return done;
132 	}
133 	/// the amount of progress. setter
134 	@property uinteger progress(uinteger newProgress){
135 		needsUpdate = true;
136 		done = newProgress;
137 		if (forceUpdate !is null){
138 			forceUpdate();
139 		}
140 		return done;
141 	}
142 }
143 
144 /// To get single-line input from keyboard
145 /// 
146 /// Name in theme: 'editline';
147 class EditLineWidget : QWidget{
148 private:
149 	char[] inputText;
150 	uinteger cursorX;
151 	uinteger scrollX = 0;//amount of chars that won't be displayed because of not enough space
152 	RGBColor bgColor, textColor, captionTextColor, captionBgColor;
153 public:
154 	this(string wCaption = "", string inputTxt = ""){
155 		widgetName = "editLine";
156 		inputText = cast(char[])inputTxt;
157 		widgetCaption = wCaption;
158 		//specify min/max
159 		widgetSize.minWidth = 1;
160 		widgetSize.minHeight = 1;
161 		widgetSize.maxHeight = 1;
162 	}
163 	override void updateColors(){
164 		needsUpdate = true;
165 		if (&widgetTheme && widgetTheme.hasColors(name, ["background", "caption-text", "text"])){
166 			bgColor = widgetTheme.getColor(name, "background");
167 			textColor = widgetTheme.getColor(name, "text");
168 			captionTextColor = widgetTheme.getColor(name, "captionText");
169 			captionBgColor = widgetTheme.getColor(name, "captionBackground");
170 		}else{
171 			bgColor = hexToColor("404040");
172 			textColor = hexToColor("00FF00");
173 			captionTextColor = textColor;
174 			captionBgColor = hexToColor("000000");
175 		}
176 		if (forceUpdate !is null){
177 			forceUpdate();
178 		}
179 	}
180 	override bool update(ref Matrix display){
181 		bool r = false;
182 		if (needsUpdate){
183 			needsUpdate = false;
184 			r = true;
185 			//make sure there's enough space
186 			if (display.width > widgetCaption.length){
187 				//draw the caption
188 				display.write(cast(char[])widgetCaption, captionTextColor, captionBgColor);
189 				//draw the inputText
190 				uinteger width = display.width - widgetCaption.length;
191 				if (width >= inputText.length){
192 					//draw it as it is
193 					display.write(inputText, textColor, bgColor);
194 				}else{
195 					//draw scrolled
196 					if (scrollX + width > inputText.length){
197 						display.write(inputText[scrollX .. inputText.length], textColor, bgColor);
198 					}else{
199 						display.write(inputText[scrollX .. scrollX + width], textColor, bgColor);
200 					}
201 				}
202 				//fill the left with bgColor
203 				if (widgetCaption.length + inputText.length < widgetSize.width){
204 					char[] tmp;
205 					tmp.length = widgetSize.width - (inputText.length + widgetCaption.length);
206 					tmp[0 .. tmp.length] = ' ';
207 					display.write(tmp, textColor, bgColor);
208 				}
209 				//set cursor pos, if can
210 				if (cursorPos !is null){
211 					cursorPos(widgetPosition.x + widgetCaption.length + (cursorX - scrollX), widgetPosition.y);
212 				}
213 			}else{
214 				widgetShow = false;
215 				r = false;
216 			}
217 		}
218 		return r;
219 	}
220 
221 	override void mouseEvent(MouseClick mouse){
222 		super.mouseEvent(mouse);
223 		if (mouse.mouseButton == mouse.Button.Left){
224 			needsUpdate = true;
225 			//move cursor to that pos
226 			uinteger tmp = widgetPosition.x + widgetCaption.length;
227 			if (mouse.x > tmp && mouse.x < tmp + inputText.length){
228 				cursorX = mouse.x - (widgetPosition.x + widgetCaption.length + scrollX);
229 			}
230 		}
231 	}
232 	override void keyboardEvent(KeyPress key){
233 		super.keyboardEvent(key);
234 		if (key.isChar){
235 			needsUpdate = true;
236 			//insert that key
237 			if (key.key != '\b' && key.key != '\n'){
238 				if (cursorX == inputText.length){
239 					//insert at end
240 					inputText ~= cast(char)key.key;
241 				}else{
242 					inputText = inputText.insertArray([cast(char)key.key], cursorX);
243 				}
244 				cursorX ++;
245 			}else if (key.key == '\b'){
246 				//backspace
247 				if (cursorX > 0){
248 					if (cursorX == inputText.length){
249 						inputText.length --;
250 					}else{
251 						inputText = inputText.deleteArray(cursorX-1);
252 					}
253 					cursorX --;
254 				}
255 			}
256 			//check if has to modify scrollX
257 			if (widgetSize.width < widgetCaption.length + inputText.length){
258 				scrollX = (widgetCaption.length + inputText.length) - widgetSize.width;
259 			}else if (scrollX > 0){
260 				scrollX = 0;
261 			}
262 		}else{
263 			if (key.key == key.NonCharKey.LeftArrow || key.key == key.NonCharKey.UpArrow){
264 				if (cursorX > 0){
265 					needsUpdate = true;
266 					cursorX --;
267 					if (scrollX > cursorX){
268 						scrollX = cursorX;
269 					}
270 				}
271 			}else if (key.key == key.NonCharKey.RightArrow || key.key == key.NonCharKey.DownArrow){
272 				if (cursorX < inputText.length){
273 					needsUpdate = true;
274 					cursorX ++;
275 					if (cursorX == widgetSize.width - widgetCaption.length && inputText.length > cursorX){
276 						scrollX = inputText.length - (widgetSize.width - widgetCaption.length);
277 					}
278 				}
279 			}
280 		}
281 		//set cursor pos, if can
282 		if (cursorPos !is null){
283 			cursorPos(widgetPosition.x + widgetCaption.length + (cursorX - scrollX), widgetPosition.y);
284 		}
285 	}
286 	///The text that has been input-ed.
287 	@property string text(){
288 		return cast(string)inputText;
289 	}
290 	///The text that has been input-ed.
291 	@property string text(string newText){
292 		inputText = cast(char[])newText;
293 		if (forceUpdate !is null){
294 			forceUpdate();
295 		}
296 		return cast(string)inputText;
297 	}
298 }
299 
300 /// Can be used as a simple text editor, or to just display text
301 /// 
302 /// Name in theme: 'memo';
303 class MemoWidget : QWidget{
304 private:
305 	List!string widgetLines;
306 	uinteger scrollX, scrollY;
307 	uinteger cursorX, cursorY;
308 	RGBColor bgColor, textColor;
309 	bool writeProtected = false;
310 	// used by widget itseld to recalculate scrolling
311 	void reScroll(){
312 		//calculate scrollY first
313 		if ((scrollY + widgetSize.height < cursorY || scrollY + widgetSize.height >= cursorY) && cursorY != 0){
314 			if (cursorY <= widgetSize.height/2){
315 				scrollY = 0;
316 			}else{
317 				scrollY = cursorY - (widgetSize.height/2);
318 			}
319 		}
320 		//now time for scrollX
321 		//check if is within length of line
322 		uinteger len = widgetLines.read(cursorY).length;
323 		if (cursorX > len){
324 			cursorX = len;
325 		}
326 		//now calculate scrollX, if it needs to be increased
327 		if (/*cursorX > widgetSize.width &&*/(scrollX + widgetSize.width < cursorX || scrollX + widgetSize.width >= cursorX)){
328 			if (cursorX <= widgetSize.width/2){
329 				scrollX = 0;
330 			}else{
331 				scrollX = cursorX - (widgetSize.width/2);
332 			}
333 		}
334 	}
335 	// used by widget itself to set cursor
336 	void setCursor(){
337 		//put the cursor at correct position, if possible
338 		if (cursorPos !is null){
339 			//check if cursor is at a position that's possible
340 			if (widgetLines.length >= cursorY && widgetLines.read(cursorY).length >= cursorX){
341 				cursorPos((cursorX - scrollX)+widgetPosition.x, (cursorY - scrollY)+widgetPosition.y);
342 			}
343 		}
344 	}
345 	// used by widget itself to move cursor
346 	void moveCursor(uinteger x, uinteger y){
347 		cursorX = x;
348 		cursorY = y;
349 		
350 		if (cursorY >= widgetLines.length){
351 			cursorY = widgetLines.length-1;
352 		}
353 		if (cursorX >= widgetLines.read(cursorY).length){
354 			cursorX = widgetLines.read(cursorY).length;
355 		}
356 	}
357 public:
358 	this(bool readOnly = false){
359 		widgetName = "memo";
360 		widgetLines = new List!string;
361 		scrollX, scrollY = 0;
362 		cursorX, cursorY = 0;
363 		writeProtected = readOnly;//cause if readOnly, then writeProtected = true also
364 	}
365 	~this(){
366 		delete widgetLines;
367 	}
368 
369 	override void updateColors(){
370 		needsUpdate = true;
371 		if (&widgetTheme && widgetTheme.hasColors(name, ["background", "text"])){
372 			bgColor = widgetTheme.getColor(name, "background");
373 			textColor = widgetTheme.getColor(name, "text");
374 		}else{
375 			bgColor = hexToColor("404040");
376 			textColor = hexToColor("00FF00");
377 		}
378 		if (forceUpdate !is null){
379 			forceUpdate();
380 		}
381 	}
382 
383 	override bool update(ref Matrix display){
384 		bool r = false;
385 		if (needsUpdate){
386 			needsUpdate = false;
387 			r = true;
388 			//check if there's lines to be displayed
389 			uinteger count = widgetLines.length, i, linesWritten = 0;
390 			char[] emptyLine;
391 			emptyLine.length = widgetSize.width;
392 			emptyLine[0 .. emptyLine.length] = ' ';
393 			if (count > 0){
394 				//write lines to memo
395 				char[] line;
396 				for (i = scrollY; i < count; i++){
397 					//echo current line
398 					line = cast(char[])widgetLines.read(i);
399 					//fit the line into screen, i.e check if only a part of it will be displayed
400 					if (line.length >= widgetSize.width+scrollX){
401 						//display only partial line
402 						display.write(line[scrollX .. scrollX + widgetSize.width], textColor, bgColor);
403 					}else{
404 						//either the line is small enough to fit, or 0-length
405 						if (line.length <= scrollX || line.length == 0){
406 							//just write the bgColor
407 							display.write(emptyLine, textColor, bgColor);
408 						}else{
409 							display.write(line[scrollX .. line.length], textColor, bgColor);
410 							//write the bgColor
411 							display.write(emptyLine[line.length - scrollX .. emptyLine.length], textColor, bgColor);
412 						}
413 					}
414 					linesWritten ++;
415 					//check if is at end
416 					if (i-scrollY >= widgetSize.height){
417 						break;
418 					}
419 				}
420 				//fill empty space with emptyLine
421 				if (linesWritten < widgetSize.height){
422 					count = widgetSize.height;
423 					for (i = linesWritten; i < count; i++){
424 						display.write(emptyLine,textColor, bgColor);
425 					}
426 				}
427 				//put the cursor at correct position, if possible
428 				setCursor();
429 			}
430 		}
431 		return r;
432 	}
433 
434 	override void mouseEvent(MouseClick mouse){
435 		super.mouseEvent(mouse);
436 		//calculate mouse position, relative to scroll and widgetPosition
437 		mouse.x = (mouse.x - widgetPosition.x) + scrollX;
438 		mouse.y = (mouse.y - widgetPosition.y) + scrollY;
439 		if (mouse.mouseButton == mouse.Button.Left){
440 			needsUpdate = true;
441 			moveCursor(mouse.x, mouse.y);
442 
443 		}else if (mouse.mouseButton == mouse.Button.ScrollDown){
444 			if (cursorY < widgetLines.length){
445 				needsUpdate = true;
446 				moveCursor(cursorX, cursorY + 4);
447 				reScroll();
448 			}
449 		}else if (mouse.mouseButton == mouse.Button.ScrollUp){
450 			if (cursorY > 0){
451 				needsUpdate = true;
452 				if (cursorY < 4){
453 					moveCursor(cursorX, 0);
454 				}else{
455 					moveCursor(cursorX, cursorY - 4);
456 				}
457 				reScroll();
458 			}
459 		}
460 	}
461 
462 	override void keyboardEvent(KeyPress key){
463 		super.keyboardEvent(key);
464 		if (key.isChar){
465 			if (!writeProtected){
466 				needsUpdate = true;
467 				string currentLine = widgetLines.read(cursorY);
468 				//check if backspace
469 				if (key.key == '\b'){
470 					//make sure that it's not the first line, first line cannot be removed
471 					if (cursorY > 0){
472 						//check if has to remove a '\n'
473 						if (cursorX == 0){
474 							cursorY --;
475 							//if line's not empty, append it to previous line
476 							cursorX = widgetLines.read(cursorY).length;
477 							if (currentLine != ""){
478 								//else, append this line to previous
479 								widgetLines.set(cursorY, widgetLines.read(cursorY)~currentLine);
480 							}
481 							widgetLines.remove(cursorY+1);
482 						}else{
483 							widgetLines.set(cursorY, cast(string)deleteArray(cast(char[])currentLine,cursorX-1));
484 							cursorX --;
485 						}
486 					}else if (cursorX > 0){
487 						widgetLines.set(cursorY, cast(string)deleteArray(cast(char[])currentLine,cursorX-1));
488 						cursorX --;
489 					}
490 
491 				}else if (key.key == '\n'){
492 					//insert a newline
493 					//if is at end, just add it
494 					bool atEnd = false;
495 					if (cursorY >= widgetLines.length - 1){
496 						atEnd = true;
497 					}
498 					if (cursorX == widgetLines.read(cursorY).length){
499 						if (atEnd){
500 							widgetLines.add("");
501 						}else{
502 							widgetLines.insert(cursorY + 1,"");
503 						}
504 					}else{
505 						string[2] line;
506 						line[0] = widgetLines.read(cursorY);
507 						line[1] = line[0][cursorX .. line[0].length];
508 						line[0] = line[0][0 .. cursorX];
509 						widgetLines.set(cursorY, line[0]);
510 						if (atEnd){
511 							widgetLines.add(line[1]);
512 						}else{
513 							widgetLines.insert(cursorY + 1, line[1]);
514 						}
515 					}
516 					cursorY ++;
517 					cursorX = 0;
518 				}else if (key.key == '\t'){
519 					//convert it to 4 spaces
520 					widgetLines.set(cursorY, cast(string)insertArray(cast(char[])currentLine,cast(char[])"    ",cursorX));
521 					cursorX += 4;
522 				}else{
523 					//insert that char
524 					widgetLines.set(cursorY, cast(string)insertArray(cast(char[])currentLine,[cast(char)key.key],cursorX));
525 					cursorX ++;
526 				}
527 			}
528 		}else{
529 			if (key.key == key.NonCharKey.Delete){
530 				needsUpdate = true;
531 				//check if is deleting \n
532 				if (cursorX == widgetLines.read(cursorY).length && cursorY < widgetLines.length-1){
533 					//merge next line with this one
534 					char[] line = cast(char[])widgetLines.read(cursorY)~widgetLines.read(cursorY+1);
535 					widgetLines.set(cursorY, cast(string)line);
536 					//remove next line
537 					widgetLines.remove(cursorY+1);
538 				}else if (cursorX < widgetLines.read(cursorY).length){
539 					char[] line = cast(char[])widgetLines.read(cursorY);
540 					line = line.deleteArray(cursorX);
541 					widgetLines.set(cursorY, cast(string)line);
542 				}
543 			}else if (key.key == key.NonCharKey.DownArrow){
544 				if (cursorY < widgetLines.length-1){
545 					needsUpdate = true;
546 					cursorY ++;
547 				}
548 			}else if (key.key == key.NonCharKey.UpArrow){
549 				if (cursorY > 0){
550 					needsUpdate = true;
551 					cursorY --;
552 				}
553 			}else if (key.key == key.NonCharKey.LeftArrow){
554 				if ((cursorY >= 0 && cursorX > 0) || (cursorY > 0 && cursorX == 0)){
555 					needsUpdate = true;
556 					uinteger x, y;
557 					if (cursorX == 0){
558 						cursorY --;
559 						cursorX = widgetLines.read(cursorY).length;
560 					}else{
561 						cursorX --;
562 					}
563 				}
564 			}else if (key.key == key.NonCharKey.RightArrow){
565 				needsUpdate = true;
566 				if (cursorX == widgetLines.read(cursorY).length){
567 					if (cursorY < widgetLines.length-1){
568 						cursorX = 0;
569 						cursorY ++;
570 						scrollX = 0;
571 
572 					}
573 				}else{
574 					cursorX ++;
575 				}
576 			}
577 		}
578 		reScroll();
579 	}
580 
581 	///returns a list of lines in memo
582 	///
583 	///To modify the content, just modify it in the returned list
584 	///
585 	///class `List` is defined in `qui.lists.d`
586 	@property List!string lines(){
587 		return widgetLines;
588 	}
589 	///Returns true if memo's contents cannot be modified, by user
590 	@property bool readOnly(){
591 		return writeProtected;
592 	}
593 	///sets whether to allow modifying of contents (false) or not (true)
594 	@property bool readOnly(bool newPermission){
595 		writeProtected = newPermission;
596 		if (forceUpdate !is null){
597 			forceUpdate();
598 		}
599 		return writeProtected;
600 	}
601 }
602 
603 /// Displays an un-scrollable log, that removes older lines
604 /// 
605 /// It's content cannot be modified by user.
606 /// 
607 /// Name in theme: 'log';
608 class LogWidget : QWidget{
609 private:
610 	LogList!string logs;
611 
612 	RGBColor bgColor, textColor;
613 
614 	uinteger stringLineCount(string s){
615 		uinteger width = widgetSize.width;
616 		uinteger i, widthTaken = 0, count = 1;
617 		for (i = 0; i < s.length; i++){
618 			widthTaken ++;
619 			if (s[i] == '\n' || widthTaken >= width){
620 				count ++;
621 				widthTaken = 0;
622 			}
623 			if (widthTaken >= width){
624 				count ++;
625 			}
626 		}
627 		return count;
628 	}
629 	string stripString(string s, uinteger height){
630 		char[] r;
631 		uinteger width = widgetSize.width;
632 		uinteger i, widthTaken = 0, count = 1;
633 		for (i = 0; i < s.length; i++){
634 			widthTaken ++;
635 			if (s[i] == '\n' || widthTaken >= width){
636 				count ++;
637 				widthTaken = 0;
638 			}
639 			if (count > height){
640 				r.length = i;
641 				r[0 .. i] = s[0 .. i];
642 				break;
643 			}
644 		}
645 		return cast(string)r;
646 	}
647 public:
648 	this(uinteger maxLen=100){
649 		widgetName = "log";
650 		logs = new LogList!string(maxLen);
651 	}
652 	~this(){
653 		delete logs;
654 	}
655 
656 	override public void updateColors(){
657 		needsUpdate = true;
658 		if (&widgetTheme && widgetTheme.hasColors(name, ["background", "text"])){
659 			bgColor = widgetTheme.getColor(name, "background");
660 			textColor = widgetTheme.getColor(name, "text");
661 		}else{
662 			bgColor = hexToColor("404040");
663 			textColor = hexToColor("00FF00");
664 		}
665 		if (forceUpdate !is null){
666 			forceUpdate();
667 		}
668 	}
669 
670 	override public bool update(ref Matrix display){
671 		bool r = false;
672 		if (needsUpdate){
673 			needsUpdate = false;
674 			r = true;
675 			//get list of messages
676 			string[] messages = logs.read(logs.maxCapacity);
677 			//set colors
678 			display.setColors(textColor, bgColor);
679 			//determine how many of them will be displayed
680 			uinteger count;//right now, it's used to store number of lines used
681 			uinteger i;
682 			if (messages.length>0){
683 				for (i=messages.length-1; i>=0; i--){
684 					count += stringLineCount(messages[i]);
685 					if (count > widgetSize.height){
686 						messages = messages[i+1 .. messages.length];
687 						//try to insert part of the last message
688 						uinteger thisCount = stringLineCount(messages[i]);
689 						count -= thisCount;
690 						if (count < widgetSize.height){
691 							messages ~= [stripString(messages[i], widgetSize.height - count)];
692 						}
693 						break;
694 					}
695 					if (i==0){
696 						break;
697 					}
698 				}
699 			}
700 			//write them
701 			for (i = 0; i < messages.length; i++){
702 				display.write(cast(char[])messages[i], textColor, bgColor);
703 				//add newline
704 				display.moveTo(0, display.writePosY+1);
705 			}
706 		}
707 		return r;
708 	}
709 
710 	///adds string to the log, and scrolls down to it
711 	void add(string item){
712 		//add height
713 		logs.add(item);
714 		//update
715 		needsUpdate = true;
716 		if (forceUpdate !is null){
717 			forceUpdate();
718 		}
719 	}
720 	///clears the log
721 	void clear(){
722 		logs.reset();
723 		//update
724 		needsUpdate = true;
725 		if (forceUpdate !is null){
726 			forceUpdate();
727 		}
728 	}
729 }
730 
731 /// A button
732 /// 
733 /// the caption is displayed inside the button
734 /// 
735 /// To receive input, set the `onMouseEvent` to set a custom mouse event
736 class ButtonWidget : QWidget{
737 private:
738 	RGBColor bgColor, textColor;
739 public:
740 	this(){
741 		widgetName = "button";
742 	}
743 	override public void updateColors(){
744 		if (&widgetTheme && widgetTheme.hasColors(name, ["background", "text"])){
745 			bgColor = widgetTheme.getColor(name, "background");
746 			textColor = widgetTheme.getColor(name, "text");
747 		}else{
748 			bgColor = hexToColor("00FF00");
749 			textColor = hexToColor("000000");
750 		}
751 	}
752 
753 	override public bool update(ref Matrix display) {
754 		bool r = false;
755 		if (needsUpdate){
756 			r = true;
757 			needsUpdate = false;
758 			char[] row;
759 			row.length = widgetSize.width;
760 			row[0 .. row.length] = ' ';
761 			//write the caption too!
762 			uinteger middle = widgetSize.height/2;
763 			for (uinteger i = 0; i < widgetSize.height; i++){
764 				if (i == middle && widgetCaption != ""){
765 					row = centerAlignText(cast(char[])caption, widgetSize.width);
766 					display.write(row, textColor, bgColor);
767 					row[0 .. row.length] = ' ';
768 					continue;
769 				}else{
770 					display.write(row, textColor, bgColor);
771 				}
772 			}
773 		}
774 		return r;
775 	}
776 }