xref: /reactos/boot/freeldr/freeldr/ui/tui.c (revision 4514e91d)
1 /*
2  *  FreeLoader
3  *  Copyright (C) 1998-2003  Brian Palmer  <brianp@sginet.com>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include <freeldr.h>
21 
22 PVOID TextVideoBuffer = NULL;
23 
24 /* GENERIC TUI UTILS *********************************************************/
25 
26 /*
27  * TuiPrintf()
28  * Prints formatted text to the screen.
29  */
30 INT
31 TuiPrintf(
32     _In_ PCSTR Format, ...)
33 {
34     INT i;
35     INT Length;
36     va_list ap;
37     CHAR Buffer[512];
38 
39     va_start(ap, Format);
40     Length = _vsnprintf(Buffer, sizeof(Buffer), Format, ap);
41     va_end(ap);
42 
43     if (Length == -1)
44         Length = (INT)sizeof(Buffer);
45 
46     for (i = 0; i < Length; i++)
47     {
48         MachConsPutChar(Buffer[i]);
49     }
50 
51     return Length;
52 }
53 
54 VOID
55 TuiTruncateStringEllipsis(
56     _Inout_z_ PSTR StringText,
57     _In_ ULONG MaxChars)
58 {
59     /* If it's too large, just add some ellipsis past the maximum */
60     if (strlen(StringText) > MaxChars)
61         strcpy(&StringText[MaxChars - 3], "...");
62 }
63 
64 /*
65  * DrawText()
66  * Displays a string on a single screen line.
67  * This function assumes coordinates are zero-based.
68  */
69 VOID
70 TuiDrawText(
71     _In_ ULONG X,
72     _In_ ULONG Y,
73     _In_ PCSTR Text,
74     _In_ UCHAR Attr)
75 {
76     TuiDrawText2(X, Y, 0 /*(ULONG)strlen(Text)*/, Text, Attr);
77 }
78 
79 /*
80  * DrawText2()
81  * Displays a string on a single screen line.
82  * This function assumes coordinates are zero-based.
83  * MaxNumChars is the maximum number of characters to display.
84  * If MaxNumChars == 0, then display the whole string.
85  */
86 VOID
87 TuiDrawText2(
88     _In_ ULONG X,
89     _In_ ULONG Y,
90     _In_opt_ ULONG MaxNumChars,
91     _In_reads_or_z_(MaxNumChars) PCSTR Text,
92     _In_ UCHAR Attr)
93 {
94     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
95     ULONG i, j;
96 
97     /* Don't display anything if we are out of the screen */
98     if ((X >= UiScreenWidth) || (Y >= UiScreenHeight))
99         return;
100 
101     /* Draw the text, not exceeding the width */
102     for (i = X, j = 0; Text[j] && i < UiScreenWidth && (MaxNumChars > 0 ? j < MaxNumChars : TRUE); i++, j++)
103     {
104         ScreenMemory[((Y*2)*UiScreenWidth)+(i*2)]   = (UCHAR)Text[j];
105         ScreenMemory[((Y*2)*UiScreenWidth)+(i*2)+1] = Attr;
106     }
107 }
108 
109 VOID
110 TuiDrawCenteredText(
111     _In_ ULONG Left,
112     _In_ ULONG Top,
113     _In_ ULONG Right,
114     _In_ ULONG Bottom,
115     _In_ PCSTR TextString,
116     _In_ UCHAR Attr)
117 {
118     SIZE_T TextLength;
119     SIZE_T Index, LastIndex;
120     ULONG  LineBreakCount;
121     ULONG  BoxWidth, BoxHeight;
122     ULONG  RealLeft, RealTop;
123     ULONG  X, Y;
124     CHAR   Temp[2];
125 
126     /* Query text length */
127     TextLength = strlen(TextString);
128 
129     /* Count the new lines and the box width */
130     LineBreakCount = 0;
131     BoxWidth = 0;
132     LastIndex = 0;
133     for (Index = 0; Index < TextLength; Index++)
134     {
135         /* Scan for new lines */
136         if (TextString[Index] == '\n')
137         {
138             /* Remember the new line */
139             LastIndex = Index;
140             LineBreakCount++;
141         }
142         else
143         {
144             /* Check for new larger box width */
145             if ((Index - LastIndex) > BoxWidth)
146             {
147                 /* Update it */
148                 BoxWidth = (ULONG)(Index - LastIndex);
149             }
150         }
151     }
152 
153     /* Base the box height on the number of lines */
154     BoxHeight = LineBreakCount + 1;
155 
156     /*
157      * Create the centered coordinates.
158      * Here, the Left/Top/Right/Bottom rectangle is a hint, around
159      * which we center the "real" text rectangle RealLeft/RealTop.
160      */
161     RealLeft = (Left + Right - BoxWidth + 1) / 2;
162     RealTop  = (Top + Bottom - BoxHeight + 1) / 2;
163 
164     /* Now go for a second scan */
165     LastIndex = 0;
166     for (Index = 0; Index < TextLength; Index++)
167     {
168         /* Look for new lines again */
169         if (TextString[Index] == '\n')
170         {
171             /* Update where the text should start */
172             RealTop++;
173             LastIndex = 0;
174         }
175         else
176         {
177             /* We've got a line of text to print, do it */
178             X = (ULONG)(RealLeft + LastIndex);
179             Y = RealTop;
180             LastIndex++;
181             Temp[0] = TextString[Index];
182             Temp[1] = 0;
183             TuiDrawText(X, Y, Temp, Attr);
184         }
185     }
186 }
187 
188 /* FULL TUI THEME ************************************************************/
189 
190 #define TAG_TUI_SCREENBUFFER 'SiuT'
191 #define TAG_TUI_PALETTE      'PiuT'
192 
193 extern UCHAR MachDefaultTextColor;
194 
195 BOOLEAN TuiInitialize(VOID)
196 {
197     MachVideoHideShowTextCursor(FALSE);
198     MachVideoSetTextCursorPosition(0, 0);
199     MachVideoClearScreen(ATTR(COLOR_GRAY, COLOR_BLACK));
200 
201     TextVideoBuffer = VideoAllocateOffScreenBuffer();
202     if (TextVideoBuffer == NULL)
203     {
204         return FALSE;
205     }
206 
207     /* Load default settings with "Full" TUI Theme */
208 
209     UiStatusBarFgColor    = COLOR_BLACK;
210     UiStatusBarBgColor    = COLOR_CYAN;
211     UiBackdropFgColor     = COLOR_WHITE;
212     UiBackdropBgColor     = COLOR_BLUE;
213     UiBackdropFillStyle   = MEDIUM_FILL;
214     UiTitleBoxFgColor     = COLOR_WHITE;
215     UiTitleBoxBgColor     = COLOR_RED;
216     UiMessageBoxFgColor   = COLOR_WHITE;
217     UiMessageBoxBgColor   = COLOR_BLUE;
218     UiMenuFgColor         = COLOR_WHITE;
219     UiMenuBgColor         = COLOR_BLUE;
220     UiTextColor           = COLOR_YELLOW;
221     UiSelectedTextColor   = COLOR_BLACK;
222     UiSelectedTextBgColor = COLOR_GRAY;
223     UiEditBoxTextColor    = COLOR_WHITE;
224     UiEditBoxBgColor      = COLOR_BLACK;
225 
226     UiShowTime          = TRUE;
227     UiMenuBox           = TRUE;
228     UiCenterMenu        = TRUE;
229     UiUseSpecialEffects = FALSE;
230 
231     // TODO: Have a boolean to show/hide title box?
232     RtlStringCbCopyA(UiTitleBoxTitleText, sizeof(UiTitleBoxTitleText),
233                      "Boot Menu");
234 
235     RtlStringCbCopyA(UiTimeText, sizeof(UiTimeText),
236                      "[Time Remaining: %d]");
237 
238     return TRUE;
239 }
240 
241 VOID TuiUnInitialize(VOID)
242 {
243     /* Do nothing if already uninitialized */
244     if (!TextVideoBuffer)
245         return;
246 
247     if (UiUseSpecialEffects)
248     {
249         TuiFadeOut();
250     }
251     else
252     {
253         MachVideoSetDisplayMode(NULL, FALSE);
254     }
255 
256     VideoFreeOffScreenBuffer();
257     TextVideoBuffer = NULL;
258 
259     MachVideoClearScreen(ATTR(COLOR_GRAY, COLOR_BLACK));
260     MachVideoSetTextCursorPosition(0, 0);
261     MachVideoHideShowTextCursor(TRUE);
262 }
263 
264 VOID TuiDrawBackdrop(VOID)
265 {
266     /* Fill in the background (excluding title box & status bar) */
267     TuiFillArea(0,
268                 TUI_TITLE_BOX_CHAR_HEIGHT,
269                 UiScreenWidth - 1,
270                 UiScreenHeight - 2,
271                 UiBackdropFillStyle,
272                 ATTR(UiBackdropFgColor, UiBackdropBgColor));
273 
274     /* Draw the title box */
275     TuiDrawBox(0,
276                0,
277                UiScreenWidth - 1,
278                TUI_TITLE_BOX_CHAR_HEIGHT - 1,
279                D_VERT,
280                D_HORZ,
281                TRUE,
282                FALSE,
283                ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
284 
285     /* Draw version text */
286     TuiDrawText(2,
287                 1,
288                 FrLdrVersionString,
289                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
290 
291     /* Draw copyright */
292     TuiDrawText(2,
293                 2,
294                 BY_AUTHOR,
295                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
296     TuiDrawText(2,
297                 3,
298                 AUTHOR_EMAIL,
299                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
300 
301     /* Draw help text */
302     TuiDrawText(UiScreenWidth - 16, 3,
303                 /*"F1 for Help"*/ "F8 for Options",
304                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
305 
306     /* Draw title text */
307     TuiDrawText((UiScreenWidth - (ULONG)strlen(UiTitleBoxTitleText)) / 2,
308                 2,
309                 UiTitleBoxTitleText,
310                 ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
311 
312     /* Update the date & time */
313     TuiUpdateDateTime();
314     VideoCopyOffScreenBufferToVRAM();
315 }
316 
317 /*
318  * FillArea()
319  * This function assumes coordinates are zero-based
320  */
321 VOID TuiFillArea(ULONG Left, ULONG Top, ULONG Right, ULONG Bottom, CHAR FillChar, UCHAR Attr /* Color Attributes */)
322 {
323     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
324     ULONG  i, j;
325 
326     /* Clip the area to the screen */
327     if ((Left >= UiScreenWidth) || (Top >= UiScreenHeight))
328     {
329         return;
330     }
331     if (Right >= UiScreenWidth)
332         Right = UiScreenWidth - 1;
333     if (Bottom >= UiScreenHeight)
334         Bottom = UiScreenHeight - 1;
335 
336     /* Loop through each line and column and fill it in */
337     for (i = Top; i <= Bottom; ++i)
338     {
339         for (j = Left; j <= Right; ++j)
340         {
341             ScreenMemory[((i*2)*UiScreenWidth)+(j*2)] = (UCHAR)FillChar;
342             ScreenMemory[((i*2)*UiScreenWidth)+(j*2)+1] = Attr;
343         }
344     }
345 }
346 
347 /*
348  * DrawShadow()
349  * This function assumes coordinates are zero-based
350  */
351 VOID TuiDrawShadow(ULONG Left, ULONG Top, ULONG Right, ULONG Bottom)
352 {
353     PUCHAR ScreenMemory = (PUCHAR)TextVideoBuffer;
354     ULONG  Idx;
355 
356     /* Shade the bottom of the area */
357     if (Bottom < (UiScreenHeight - 1))
358     {
359         if (UiScreenHeight < 34)
360             Idx = Left + 2;
361         else
362             Idx = Left + 1;
363 
364         for (; Idx <= Right; ++Idx)
365         {
366             ScreenMemory[(((Bottom+1)*2)*UiScreenWidth)+(Idx*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
367         }
368     }
369 
370     /* Shade the right of the area */
371     if (Right < (UiScreenWidth - 1))
372     {
373         for (Idx=Top+1; Idx<=Bottom; Idx++)
374         {
375             ScreenMemory[((Idx*2)*UiScreenWidth)+((Right+1)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
376         }
377     }
378     if (UiScreenHeight < 34)
379     {
380         if ((Right + 1) < (UiScreenWidth - 1))
381         {
382             for (Idx=Top+1; Idx<=Bottom; Idx++)
383             {
384                 ScreenMemory[((Idx*2)*UiScreenWidth)+((Right+2)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
385             }
386         }
387     }
388 
389     /* Shade the bottom right corner */
390     if ((Right < (UiScreenWidth - 1)) && (Bottom < (UiScreenHeight - 1)))
391     {
392         ScreenMemory[(((Bottom+1)*2)*UiScreenWidth)+((Right+1)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
393     }
394     if (UiScreenHeight < 34)
395     {
396         if (((Right + 1) < (UiScreenWidth - 1)) && (Bottom < (UiScreenHeight - 1)))
397         {
398             ScreenMemory[(((Bottom+1)*2)*UiScreenWidth)+((Right+2)*2)+1] = ATTR(COLOR_GRAY, COLOR_BLACK);
399         }
400     }
401 }
402 
403 /*
404  * DrawBox()
405  * This function assumes coordinates are zero-based
406  */
407 VOID
408 TuiDrawBoxTopLine(
409     _In_ ULONG Left,
410     _In_ ULONG Top,
411     _In_ ULONG Right,
412     _In_ UCHAR VertStyle,
413     _In_ UCHAR HorzStyle,
414     _In_ UCHAR Attr)
415 {
416     UCHAR ULCorner, URCorner;
417 
418     /* Calculate the corner values */
419     if (HorzStyle == HORZ)
420     {
421         if (VertStyle == VERT)
422         {
423             ULCorner = UL;
424             URCorner = UR;
425         }
426         else // VertStyle == D_VERT
427         {
428             ULCorner = VD_UL;
429             URCorner = VD_UR;
430         }
431     }
432     else // HorzStyle == D_HORZ
433     {
434         if (VertStyle == VERT)
435         {
436             ULCorner = HD_UL;
437             URCorner = HD_UR;
438         }
439         else // VertStyle == D_VERT
440         {
441             ULCorner = D_UL;
442             URCorner = D_UR;
443         }
444     }
445 
446     TuiFillArea(Left, Top, Left, Top, ULCorner, Attr);
447     TuiFillArea(Left+1, Top, Right-1, Top, HorzStyle, Attr);
448     TuiFillArea(Right, Top, Right, Top, URCorner, Attr);
449 }
450 
451 VOID
452 TuiDrawBoxBottomLine(
453     _In_ ULONG Left,
454     _In_ ULONG Bottom,
455     _In_ ULONG Right,
456     _In_ UCHAR VertStyle,
457     _In_ UCHAR HorzStyle,
458     _In_ UCHAR Attr)
459 {
460     UCHAR LLCorner, LRCorner;
461 
462     /* Calculate the corner values */
463     if (HorzStyle == HORZ)
464     {
465         if (VertStyle == VERT)
466         {
467             LLCorner = LL;
468             LRCorner = LR;
469         }
470         else // VertStyle == D_VERT
471         {
472             LLCorner = VD_LL;
473             LRCorner = VD_LR;
474         }
475     }
476     else // HorzStyle == D_HORZ
477     {
478         if (VertStyle == VERT)
479         {
480             LLCorner = HD_LL;
481             LRCorner = HD_LR;
482         }
483         else // VertStyle == D_VERT
484         {
485             LLCorner = D_LL;
486             LRCorner = D_LR;
487         }
488     }
489 
490     TuiFillArea(Left, Bottom, Left, Bottom, LLCorner, Attr);
491     TuiFillArea(Left+1, Bottom, Right-1, Bottom, HorzStyle, Attr);
492     TuiFillArea(Right, Bottom, Right, Bottom, LRCorner, Attr);
493 }
494 
495 VOID
496 TuiDrawBox(
497     _In_ ULONG Left,
498     _In_ ULONG Top,
499     _In_ ULONG Right,
500     _In_ ULONG Bottom,
501     _In_ UCHAR VertStyle,
502     _In_ UCHAR HorzStyle,
503     _In_ BOOLEAN Fill,
504     _In_ BOOLEAN Shadow,
505     _In_ UCHAR Attr)
506 {
507     /* Fill in the box background */
508     if (Fill)
509         TuiFillArea(Left, Top, Right, Bottom, ' ', Attr);
510 
511     /* Fill in the top horizontal line */
512     TuiDrawBoxTopLine(Left, Top, Right, VertStyle, HorzStyle, Attr);
513 
514     /* Fill in the vertical left and right lines */
515     TuiFillArea(Left, Top+1, Left, Bottom-1, VertStyle, Attr);
516     TuiFillArea(Right, Top+1, Right, Bottom-1, VertStyle, Attr);
517 
518     /* Fill in the bottom horizontal line */
519     TuiDrawBoxBottomLine(Left, Bottom, Right, VertStyle, HorzStyle, Attr);
520 
521     /* Draw the shadow */
522     if (Shadow)
523         TuiDrawShadow(Left, Top, Right, Bottom);
524 }
525 
526 VOID TuiDrawStatusText(PCSTR StatusText)
527 {
528     SIZE_T    i;
529 
530     TuiDrawText(0, UiScreenHeight-1, " ", ATTR(UiStatusBarFgColor, UiStatusBarBgColor));
531     TuiDrawText(1, UiScreenHeight-1, StatusText, ATTR(UiStatusBarFgColor, UiStatusBarBgColor));
532 
533     for (i=strlen(StatusText)+1; i<UiScreenWidth; i++)
534     {
535         TuiDrawText((ULONG)i, UiScreenHeight-1, " ", ATTR(UiStatusBarFgColor, UiStatusBarBgColor));
536     }
537 
538     VideoCopyOffScreenBufferToVRAM();
539 }
540 
541 VOID TuiUpdateDateTime(VOID)
542 {
543     TIMEINFO* TimeInfo;
544     PCSTR   DayPostfix;
545     BOOLEAN PMHour = FALSE;
546     CHAR Buffer[40];
547 
548     /* Don't draw the time if this has been disabled */
549     if (!UiShowTime) return;
550 
551     TimeInfo = ArcGetTime();
552     if (TimeInfo->Year < 1 || 9999 < TimeInfo->Year ||
553         TimeInfo->Month < 1 || 12 < TimeInfo->Month ||
554         TimeInfo->Day < 1 || 31 < TimeInfo->Day ||
555         23 < TimeInfo->Hour ||
556         59 < TimeInfo->Minute ||
557         59 < TimeInfo->Second)
558     {
559         /* This happens on QEmu sometimes. We just skip updating. */
560         return;
561     }
562 
563     /* Get the day postfix */
564     if (1 == TimeInfo->Day || 21 == TimeInfo->Day || 31 == TimeInfo->Day)
565         DayPostfix = "st";
566     else if (2 == TimeInfo->Day || 22 == TimeInfo->Day)
567         DayPostfix = "nd";
568     else if (3 == TimeInfo->Day || 23 == TimeInfo->Day)
569         DayPostfix = "rd";
570     else
571         DayPostfix = "th";
572 
573     /* Build the date string in format: "MMMM dx yyyy" */
574     RtlStringCbPrintfA(Buffer, sizeof(Buffer),
575                        "%s %d%s %d",
576                        UiMonthNames[TimeInfo->Month - 1],
577                        TimeInfo->Day,
578                        DayPostfix,
579                        TimeInfo->Year);
580 
581     /* Draw the date */
582     TuiDrawText(UiScreenWidth - (ULONG)strlen(Buffer) - 2, 1,
583                 Buffer, ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
584 
585     /* Get the hour and change from 24-hour mode to 12-hour */
586     if (TimeInfo->Hour > 12)
587     {
588         TimeInfo->Hour -= 12;
589         PMHour = TRUE;
590     }
591     if (TimeInfo->Hour == 0)
592     {
593         TimeInfo->Hour = 12;
594     }
595 
596     /* Build the time string in format: "h:mm:ss tt" */
597     RtlStringCbPrintfA(Buffer, sizeof(Buffer),
598                        "    %d:%02d:%02d %s",
599                        TimeInfo->Hour,
600                        TimeInfo->Minute,
601                        TimeInfo->Second,
602                        PMHour ? "PM" : "AM");
603 
604     /* Draw the time */
605     TuiDrawText(UiScreenWidth - (ULONG)strlen(Buffer) - 2, 2,
606                 Buffer, ATTR(UiTitleBoxFgColor, UiTitleBoxBgColor));
607 }
608 
609 VOID TuiSaveScreen(PUCHAR Buffer)
610 {
611     PUCHAR    ScreenMemory = (PUCHAR)TextVideoBuffer;
612     ULONG    i;
613 
614     for (i=0; i < (UiScreenWidth * UiScreenHeight * 2); i++)
615     {
616         Buffer[i] = ScreenMemory[i];
617     }
618 }
619 
620 VOID TuiRestoreScreen(PUCHAR Buffer)
621 {
622     PUCHAR    ScreenMemory = (PUCHAR)TextVideoBuffer;
623     ULONG    i;
624 
625     for (i=0; i < (UiScreenWidth * UiScreenHeight * 2); i++)
626     {
627         ScreenMemory[i] = Buffer[i];
628     }
629 }
630 
631 VOID TuiMessageBox(PCSTR MessageText)
632 {
633     PVOID    ScreenBuffer;
634 
635     // Save the screen contents
636     ScreenBuffer = FrLdrTempAlloc(UiScreenWidth * UiScreenHeight * 2,
637                                   TAG_TUI_SCREENBUFFER);
638     TuiSaveScreen(ScreenBuffer);
639 
640     // Display the message box
641     TuiMessageBoxCritical(MessageText);
642 
643     // Restore the screen contents
644     TuiRestoreScreen(ScreenBuffer);
645     FrLdrTempFree(ScreenBuffer, TAG_TUI_SCREENBUFFER);
646 }
647 
648 VOID TuiMessageBoxCritical(PCSTR MessageText)
649 {
650     int        width = 8;
651     unsigned int    height = 1;
652     int        curline = 0;
653     int        k;
654     size_t        i , j;
655     int        x1, x2, y1, y2;
656     char    temp[260];
657     char    key;
658 
659     // Find the height
660     for (i=0; i<strlen(MessageText); i++)
661     {
662         if (MessageText[i] == '\n')
663             height++;
664     }
665 
666     // Find the width
667     for (i=0,j=0,k=0; i<height; i++)
668     {
669         while ((MessageText[j] != '\n') && (MessageText[j] != 0))
670         {
671             j++;
672             k++;
673         }
674 
675         if (k > width)
676             width = k;
677 
678         k = 0;
679         j++;
680     }
681 
682     // Calculate box area
683     x1 = (UiScreenWidth - (width+2))/2;
684     x2 = x1 + width + 3;
685     y1 = ((UiScreenHeight - height - 2)/2) + 1;
686     y2 = y1 + height + 4;
687 
688     // Draw the box
689     TuiDrawBox(x1, y1, x2, y2, D_VERT, D_HORZ, TRUE, TRUE, ATTR(UiMessageBoxFgColor, UiMessageBoxBgColor));
690 
691     // Draw the text
692     for (i=0,j=0; i<strlen(MessageText)+1; i++)
693     {
694         if ((MessageText[i] == '\n') || (MessageText[i] == 0))
695         {
696             temp[j] = 0;
697             j = 0;
698             UiDrawText(x1+2, y1+1+curline, temp, ATTR(UiMessageBoxFgColor, UiMessageBoxBgColor));
699             curline++;
700         }
701         else
702             temp[j++] = MessageText[i];
703     }
704 
705     // Draw OK button
706     strcpy(temp, "   OK   ");
707     UiDrawText(x1+((x2-x1)/2)-3, y2-2, temp, ATTR(COLOR_BLACK, COLOR_GRAY));
708 
709     // Draw status text
710     UiDrawStatusText("Press ENTER to continue");
711 
712     VideoCopyOffScreenBufferToVRAM();
713 
714     for (;;)
715     {
716         if (MachConsKbHit())
717         {
718             key = MachConsGetCh();
719             if (key == KEY_EXTENDED)
720                 key = MachConsGetCh();
721 
722             if ((key == KEY_ENTER) || (key == KEY_SPACE) || (key == KEY_ESC))
723                 break;
724         }
725 
726         TuiUpdateDateTime();
727 
728         VideoCopyOffScreenBufferToVRAM();
729 
730         MachHwIdle();
731     }
732 }
733 
734 static VOID
735 TuiSetProgressBarText(
736     _In_ PCSTR ProgressText)
737 {
738     ULONG ProgressBarWidth;
739     CHAR ProgressString[256];
740 
741     /* Make sure the progress bar is enabled */
742     ASSERT(UiProgressBar.Show);
743 
744     /* Calculate the width of the bar proper */
745     ProgressBarWidth = UiProgressBar.Right - UiProgressBar.Left + 1;
746 
747     /* First make sure the progress bar text fits */
748     RtlStringCbCopyA(ProgressString, sizeof(ProgressString), ProgressText);
749     TuiTruncateStringEllipsis(ProgressString, ProgressBarWidth);
750 
751     /* Clear the text area */
752     TuiFillArea(UiProgressBar.Left, UiProgressBar.Top,
753                 UiProgressBar.Right, UiProgressBar.Bottom - 1,
754                 ' ', ATTR(UiTextColor, UiMenuBgColor));
755 
756     /* Draw the "Loading..." text */
757     TuiDrawCenteredText(UiProgressBar.Left, UiProgressBar.Top,
758                         UiProgressBar.Right, UiProgressBar.Bottom - 1,
759                         ProgressString, ATTR(UiTextColor, UiMenuBgColor));
760 }
761 
762 static VOID
763 TuiTickProgressBar(
764     _In_ ULONG SubPercentTimes100)
765 {
766     ULONG ProgressBarWidth;
767     ULONG FillCount;
768 
769     /* Make sure the progress bar is enabled */
770     ASSERT(UiProgressBar.Show);
771 
772     ASSERT(SubPercentTimes100 <= (100 * 100));
773 
774     /* Calculate the width of the bar proper */
775     ProgressBarWidth = UiProgressBar.Right - UiProgressBar.Left + 1;
776 
777     /* Compute fill count */
778     // FillCount = (ProgressBarWidth * Position) / Range;
779     FillCount = ProgressBarWidth * SubPercentTimes100 / (100 * 100);
780 
781     /* Fill the progress bar */
782     /* Draw the percent complete -- Use the fill character */
783     if (FillCount > 0)
784     {
785         TuiFillArea(UiProgressBar.Left, UiProgressBar.Bottom,
786                     UiProgressBar.Left + FillCount - 1, UiProgressBar.Bottom,
787                     '\xDB', ATTR(UiTextColor, UiMenuBgColor));
788     }
789     /* Fill the remaining with shadow blanks */
790     TuiFillArea(UiProgressBar.Left + FillCount, UiProgressBar.Bottom,
791                 UiProgressBar.Right, UiProgressBar.Bottom,
792                 '\xB2', ATTR(UiTextColor, UiMenuBgColor));
793 
794     TuiUpdateDateTime();
795     VideoCopyOffScreenBufferToVRAM();
796 }
797 
798 static VOID
799 TuiDrawProgressBar(
800     _In_ ULONG Left,
801     _In_ ULONG Top,
802     _In_ ULONG Right,
803     _In_ ULONG Bottom,
804     _In_ PCSTR ProgressText);
805 
806 static VOID
807 TuiDrawProgressBarCenter(
808     _In_ PCSTR ProgressText)
809 {
810     ULONG Left, Top, Right, Bottom, Width, Height;
811 
812     /* Build the coordinates and sizes */
813     Height = 2;
814     Width  = 50; // Allow for 50 "bars"
815     Left = (UiScreenWidth - Width) / 2;
816     Top  = (UiScreenHeight - Height + 4) / 2;
817     Right  = Left + Width - 1;
818     Bottom = Top + Height - 1;
819 
820     /* Inflate to include the box margins */
821     Left -= 2;
822     Right += 2;
823     Top -= 1;
824     Bottom += 1;
825 
826     /* Draw the progress bar */
827     TuiDrawProgressBar(Left, Top, Right, Bottom, ProgressText);
828 }
829 
830 static VOID
831 TuiDrawProgressBar(
832     _In_ ULONG Left,
833     _In_ ULONG Top,
834     _In_ ULONG Right,
835     _In_ ULONG Bottom,
836     _In_ PCSTR ProgressText)
837 {
838     /* Draw the box */
839     TuiDrawBox(Left, Top, Right, Bottom,
840                VERT, HORZ, TRUE, TRUE,
841                ATTR(UiMenuFgColor, UiMenuBgColor));
842 
843     /* Exclude the box margins */
844     Left += 2;
845     Right -= 2;
846     Top += 1;
847     Bottom -= 1;
848 
849     UiInitProgressBar(Left, Top, Right, Bottom, ProgressText);
850 }
851 
852 UCHAR TuiTextToColor(PCSTR ColorText)
853 {
854     static const struct
855     {
856         PCSTR ColorName;
857         UCHAR ColorValue;
858     } Colors[] =
859     {
860         {"Black"  , COLOR_BLACK  },
861         {"Blue"   , COLOR_BLUE   },
862         {"Green"  , COLOR_GREEN  },
863         {"Cyan"   , COLOR_CYAN   },
864         {"Red"    , COLOR_RED    },
865         {"Magenta", COLOR_MAGENTA},
866         {"Brown"  , COLOR_BROWN  },
867         {"Gray"   , COLOR_GRAY   },
868         {"DarkGray"    , COLOR_DARKGRAY    },
869         {"LightBlue"   , COLOR_LIGHTBLUE   },
870         {"LightGreen"  , COLOR_LIGHTGREEN  },
871         {"LightCyan"   , COLOR_LIGHTCYAN   },
872         {"LightRed"    , COLOR_LIGHTRED    },
873         {"LightMagenta", COLOR_LIGHTMAGENTA},
874         {"Yellow"      , COLOR_YELLOW      },
875         {"White"       , COLOR_WHITE       },
876     };
877     ULONG i;
878 
879     if (_stricmp(ColorText, "Default") == 0)
880         return MachDefaultTextColor;
881 
882     for (i = 0; i < RTL_NUMBER_OF(Colors); ++i)
883     {
884         if (_stricmp(ColorText, Colors[i].ColorName) == 0)
885             return Colors[i].ColorValue;
886     }
887 
888     return COLOR_BLACK;
889 }
890 
891 UCHAR TuiTextToFillStyle(PCSTR FillStyleText)
892 {
893     static const struct
894     {
895         PCSTR FillStyleName;
896         UCHAR FillStyleValue;
897     } FillStyles[] =
898     {
899         {"None"  , ' '},
900         {"Light" , LIGHT_FILL },
901         {"Medium", MEDIUM_FILL},
902         {"Dark"  , DARK_FILL  },
903     };
904     ULONG i;
905 
906     for (i = 0; i < RTL_NUMBER_OF(FillStyles); ++i)
907     {
908         if (_stricmp(FillStyleText, FillStyles[i].FillStyleName) == 0)
909             return FillStyles[i].FillStyleValue;
910     }
911 
912     return LIGHT_FILL;
913 }
914 
915 VOID TuiFadeInBackdrop(VOID)
916 {
917     PPALETTE_ENTRY TuiFadePalette = NULL;
918 
919     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed())
920     {
921         TuiFadePalette = (PPALETTE_ENTRY)FrLdrTempAlloc(sizeof(PALETTE_ENTRY) * 64,
922                                                         TAG_TUI_PALETTE);
923 
924         if (TuiFadePalette != NULL)
925         {
926             VideoSavePaletteState(TuiFadePalette, 64);
927             VideoSetAllColorsToBlack(64);
928         }
929     }
930 
931     // Draw the backdrop and title box
932     TuiDrawBackdrop();
933 
934     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed() && TuiFadePalette != NULL)
935     {
936         VideoFadeIn(TuiFadePalette, 64);
937         FrLdrTempFree(TuiFadePalette, TAG_TUI_PALETTE);
938     }
939 }
940 
941 VOID TuiFadeOut(VOID)
942 {
943     PPALETTE_ENTRY TuiFadePalette = NULL;
944 
945     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed())
946     {
947         TuiFadePalette = (PPALETTE_ENTRY)FrLdrTempAlloc(sizeof(PALETTE_ENTRY) * 64,
948                                                         TAG_TUI_PALETTE);
949 
950         if (TuiFadePalette != NULL)
951         {
952             VideoSavePaletteState(TuiFadePalette, 64);
953         }
954     }
955 
956     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed() && TuiFadePalette != NULL)
957     {
958         VideoFadeOut(64);
959     }
960 
961     MachVideoSetDisplayMode(NULL, FALSE);
962 
963     if (UiUseSpecialEffects && ! MachVideoIsPaletteFixed() && TuiFadePalette != NULL)
964     {
965         VideoRestorePaletteState(TuiFadePalette, 64);
966         FrLdrTempFree(TuiFadePalette, TAG_TUI_PALETTE);
967     }
968 
969 }
970 
971 BOOLEAN TuiEditBox(PCSTR MessageText, PCHAR EditTextBuffer, ULONG Length)
972 {
973     INT        width = 8;
974     ULONG    height = 1;
975     INT        curline = 0;
976     INT        k;
977     size_t    i , j;
978     INT        x1, x2, y1, y2;
979     CHAR    temp[260];
980     CHAR    key;
981     BOOLEAN    Extended;
982     INT        EditBoxLine;
983     ULONG    EditBoxStartX, EditBoxEndX;
984     INT        EditBoxCursorX;
985     ULONG    EditBoxTextLength, EditBoxTextPosition;
986     INT        EditBoxTextDisplayIndex;
987     BOOLEAN    ReturnCode;
988     PVOID    ScreenBuffer;
989 
990     // Save the screen contents
991     ScreenBuffer = FrLdrTempAlloc(UiScreenWidth * UiScreenHeight * 2,
992                                   TAG_TUI_SCREENBUFFER);
993     TuiSaveScreen(ScreenBuffer);
994 
995     // Find the height
996     for (i=0; i<strlen(MessageText); i++)
997     {
998         if (MessageText[i] == '\n')
999             height++;
1000     }
1001 
1002     // Find the width
1003     for (i=0,j=0,k=0; i<height; i++)
1004     {
1005         while ((MessageText[j] != '\n') && (MessageText[j] != 0))
1006         {
1007             j++;
1008             k++;
1009         }
1010 
1011         if (k > width)
1012             width = k;
1013 
1014         k = 0;
1015         j++;
1016     }
1017 
1018     // Calculate box area
1019     x1 = (UiScreenWidth - (width+2))/2;
1020     x2 = x1 + width + 3;
1021     y1 = ((UiScreenHeight - height - 2)/2) + 1;
1022     y2 = y1 + height + 4;
1023 
1024     // Draw the box
1025     TuiDrawBox(x1, y1, x2, y2, D_VERT, D_HORZ, TRUE, TRUE, ATTR(UiMessageBoxFgColor, UiMessageBoxBgColor));
1026 
1027     // Draw the text
1028     for (i=0,j=0; i<strlen(MessageText)+1; i++)
1029     {
1030         if ((MessageText[i] == '\n') || (MessageText[i] == 0))
1031         {
1032             temp[j] = 0;
1033             j = 0;
1034             UiDrawText(x1+2, y1+1+curline, temp, ATTR(UiMessageBoxFgColor, UiMessageBoxBgColor));
1035             curline++;
1036         }
1037         else
1038             temp[j++] = MessageText[i];
1039     }
1040 
1041     EditBoxTextLength = (ULONG)strlen(EditTextBuffer);
1042     EditBoxTextLength = min(EditBoxTextLength, Length - 1);
1043     EditBoxTextPosition = 0;
1044     EditBoxLine = y2 - 2;
1045     EditBoxStartX = x1 + 3;
1046     EditBoxEndX = x2 - 3;
1047 
1048     // Draw the edit box background and the text
1049     UiFillArea(EditBoxStartX, EditBoxLine, EditBoxEndX, EditBoxLine, ' ', ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1050     UiDrawText2(EditBoxStartX, EditBoxLine, EditBoxEndX - EditBoxStartX + 1, EditTextBuffer, ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1051 
1052     // Show the cursor
1053     EditBoxCursorX = EditBoxStartX;
1054     MachVideoSetTextCursorPosition(EditBoxCursorX, EditBoxLine);
1055     MachVideoHideShowTextCursor(TRUE);
1056 
1057     // Draw status text
1058     UiDrawStatusText("Press ENTER to continue, or ESC to cancel");
1059 
1060     VideoCopyOffScreenBufferToVRAM();
1061 
1062     //
1063     // Enter the text. Please keep in mind that the default input mode
1064     // of the edit boxes is in insertion mode, that is, you can insert
1065     // text without erasing the existing one.
1066     //
1067     for (;;)
1068     {
1069         if (MachConsKbHit())
1070         {
1071             Extended = FALSE;
1072             key = MachConsGetCh();
1073             if (key == KEY_EXTENDED)
1074             {
1075                 Extended = TRUE;
1076                 key = MachConsGetCh();
1077             }
1078 
1079             if (key == KEY_ENTER)
1080             {
1081                 ReturnCode = TRUE;
1082                 break;
1083             }
1084             else if (key == KEY_ESC)
1085             {
1086                 ReturnCode = FALSE;
1087                 break;
1088             }
1089             else if (key == KEY_BACKSPACE) // Remove a character
1090             {
1091                 if ( (EditBoxTextLength > 0) && (EditBoxTextPosition > 0) &&
1092                      (EditBoxTextPosition <= EditBoxTextLength) )
1093                 {
1094                     EditBoxTextPosition--;
1095                     memmove(EditTextBuffer + EditBoxTextPosition,
1096                             EditTextBuffer + EditBoxTextPosition + 1,
1097                             EditBoxTextLength - EditBoxTextPosition);
1098                     EditBoxTextLength--;
1099                     EditTextBuffer[EditBoxTextLength] = 0;
1100                 }
1101                 else
1102                 {
1103                     MachBeep();
1104                 }
1105             }
1106             else if (Extended && key == KEY_DELETE) // Remove a character
1107             {
1108                 if ( (EditBoxTextLength > 0) &&
1109                      (EditBoxTextPosition < EditBoxTextLength) )
1110                 {
1111                     memmove(EditTextBuffer + EditBoxTextPosition,
1112                             EditTextBuffer + EditBoxTextPosition + 1,
1113                             EditBoxTextLength - EditBoxTextPosition);
1114                     EditBoxTextLength--;
1115                     EditTextBuffer[EditBoxTextLength] = 0;
1116                 }
1117                 else
1118                 {
1119                     MachBeep();
1120                 }
1121             }
1122             else if (Extended && key == KEY_HOME) // Go to the start of the buffer
1123             {
1124                 EditBoxTextPosition = 0;
1125             }
1126             else if (Extended && key == KEY_END) // Go to the end of the buffer
1127             {
1128                 EditBoxTextPosition = EditBoxTextLength;
1129             }
1130             else if (Extended && key == KEY_RIGHT) // Go right
1131             {
1132                 if (EditBoxTextPosition < EditBoxTextLength)
1133                     EditBoxTextPosition++;
1134                 else
1135                     MachBeep();
1136             }
1137             else if (Extended && key == KEY_LEFT) // Go left
1138             {
1139                 if (EditBoxTextPosition > 0)
1140                     EditBoxTextPosition--;
1141                 else
1142                     MachBeep();
1143             }
1144             else if (!Extended) // Add this key to the buffer
1145             {
1146                 if ( (EditBoxTextLength   < Length - 1) &&
1147                      (EditBoxTextPosition < Length - 1) )
1148                 {
1149                     memmove(EditTextBuffer + EditBoxTextPosition + 1,
1150                             EditTextBuffer + EditBoxTextPosition,
1151                             EditBoxTextLength - EditBoxTextPosition);
1152                     EditTextBuffer[EditBoxTextPosition] = key;
1153                     EditBoxTextPosition++;
1154                     EditBoxTextLength++;
1155                     EditTextBuffer[EditBoxTextLength] = 0;
1156                 }
1157                 else
1158                 {
1159                     MachBeep();
1160                 }
1161             }
1162             else
1163             {
1164                 MachBeep();
1165             }
1166         }
1167 
1168         // Draw the edit box background
1169         UiFillArea(EditBoxStartX, EditBoxLine, EditBoxEndX, EditBoxLine, ' ', ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1170 
1171         // Fill the text in
1172         if (EditBoxTextPosition > (EditBoxEndX - EditBoxStartX))
1173         {
1174             EditBoxTextDisplayIndex = EditBoxTextPosition - (EditBoxEndX - EditBoxStartX);
1175             EditBoxCursorX = EditBoxEndX;
1176         }
1177         else
1178         {
1179             EditBoxTextDisplayIndex = 0;
1180             EditBoxCursorX = EditBoxStartX + EditBoxTextPosition;
1181         }
1182         UiDrawText2(EditBoxStartX, EditBoxLine, EditBoxEndX - EditBoxStartX + 1, &EditTextBuffer[EditBoxTextDisplayIndex], ATTR(UiEditBoxTextColor, UiEditBoxBgColor));
1183 
1184         // Move the cursor
1185         MachVideoSetTextCursorPosition(EditBoxCursorX, EditBoxLine);
1186 
1187         TuiUpdateDateTime();
1188 
1189         VideoCopyOffScreenBufferToVRAM();
1190 
1191         MachHwIdle();
1192     }
1193 
1194     // Hide the cursor again
1195     MachVideoHideShowTextCursor(FALSE);
1196 
1197     // Restore the screen contents
1198     TuiRestoreScreen(ScreenBuffer);
1199     FrLdrTempFree(ScreenBuffer, TAG_TUI_SCREENBUFFER);
1200 
1201     return ReturnCode;
1202 }
1203 
1204 const UIVTBL TuiVtbl =
1205 {
1206     TuiInitialize,
1207     TuiUnInitialize,
1208     TuiDrawBackdrop,
1209     TuiFillArea,
1210     TuiDrawShadow,
1211     TuiDrawBox,
1212     TuiDrawText,
1213     TuiDrawText2,
1214     TuiDrawCenteredText,
1215     TuiDrawStatusText,
1216     TuiUpdateDateTime,
1217     TuiMessageBox,
1218     TuiMessageBoxCritical,
1219     TuiDrawProgressBarCenter,
1220     TuiDrawProgressBar,
1221     TuiSetProgressBarText,
1222     TuiTickProgressBar,
1223     TuiEditBox,
1224     TuiTextToColor,
1225     TuiTextToFillStyle,
1226     TuiFadeInBackdrop,
1227     TuiFadeOut,
1228     TuiDisplayMenu,
1229     TuiDrawMenu,
1230 };
1231