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