2 * PROJECT: ReactOS Boot Loader
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: boot/freeldr/freeldr/ui/directui.c
5 * PURPOSE: FreeLDR UI Routines
6 * PROGRAMMERS: ReactOS Portable Systems Group
10 /* INCLUDES *******************************************************************/
14 /* GLOBALS ********************************************************************/
16 /* FUNCTIONS ******************************************************************/
20 UCHAR UiMenuFgColor
= COLOR_GRAY
;
21 UCHAR UiMenuBgColor
= COLOR_BLACK
;
22 UCHAR UiTextColor
= COLOR_GRAY
;
23 UCHAR UiSelectedTextColor
= COLOR_BLACK
;
24 UCHAR UiSelectedTextBgColor
= COLOR_GRAY
;
25 CHAR UiTimeText
[260] = "Seconds until highlighted choice will be started automatically: ";
28 TuiPrintf(const char *Format
,
37 Length
= _vsnprintf(Buffer
, sizeof(Buffer
), Format
, ap
);
40 if (Length
== -1) Length
= sizeof(Buffer
);
42 for (i
= 0; i
< Length
; i
++)
44 MachConsPutChar(Buffer
[i
]);
51 UiInitialize(IN BOOLEAN ShowGui
)
56 if (!ShowGui
) return TRUE
;
58 /* Set mode and query size */
59 MachVideoSetDisplayMode(NULL
, TRUE
);
60 MachVideoGetDisplaySize(&UiScreenWidth
, &UiScreenHeight
, &Depth
);
65 UiUnInitialize(IN PCSTR BootText
)
74 /* Clear the screen */
75 MachVideoClearScreen(ATTR(COLOR_WHITE
, COLOR_BLACK
));
79 UiDrawText(IN ULONG X
,
86 /* Draw the text character by character, but don't exceed the width */
87 for (i
= X
, j
= 0; Text
[j
] && i
< UiScreenWidth
; i
++, j
++)
89 /* Write the character */
90 MachVideoPutChar(Text
[j
], Attr
, i
, Y
);
95 UiDrawText2(IN ULONG X
,
103 /* Draw the text character by character, but don't exceed the width */
104 for (i
= X
, j
= 0; Text
[j
] && i
< UiScreenWidth
&& (MaxNumChars
> 0 ? j
< MaxNumChars
: TRUE
); i
++, j
++)
106 /* Write the character */
107 MachVideoPutChar(Text
[j
], Attr
, i
, Y
);
112 UiDrawCenteredText(IN ULONG Left
,
119 ULONG TextLength
, BoxWidth
, BoxHeight
, LineBreakCount
, Index
, LastIndex
;
120 ULONG RealLeft
, RealTop
, X
, Y
;
123 /* Query text length */
124 TextLength
= strlen(TextString
);
126 /* Count the new lines and the box width */
130 for (Index
=0; Index
< TextLength
; Index
++)
132 /* Scan for new lines */
133 if (TextString
[Index
] == '\n')
135 /* Remember the new line */
141 /* Check for new larger box width */
142 if ((Index
- LastIndex
) > BoxWidth
)
145 BoxWidth
= (Index
- LastIndex
);
150 /* Base the box height on the number of lines */
151 BoxHeight
= LineBreakCount
+ 1;
153 /* Create the centered coordinates */
154 RealLeft
= (((Right
- Left
) - BoxWidth
) / 2) + Left
;
155 RealTop
= (((Bottom
- Top
) - BoxHeight
) / 2) + Top
;
157 /* Now go for a second scan */
159 for (Index
=0; Index
< TextLength
; Index
++)
161 /* Look for new lines again */
162 if (TextString
[Index
] == '\n')
164 /* Update where the text should start */
170 /* We've got a line of text to print, do it */
171 X
= RealLeft
+ LastIndex
;
174 Temp
[0] = TextString
[Index
];
176 UiDrawText(X
, Y
, Temp
, Attr
);
182 UiDrawStatusText(IN PCSTR StatusText
)
188 UiInfoBox(IN PCSTR MessageText
)
190 TuiPrintf(MessageText
);
194 UiMessageBox(IN PCSTR MessageText
)
196 TuiPrintf(MessageText
);
200 UiMessageBoxCritical(IN PCSTR MessageText
)
202 TuiPrintf(MessageText
);
206 UiDrawProgressBarCenter(IN ULONG Position
,
208 IN PCHAR ProgressText
)
210 ULONG Left
, Top
, Right
, Bottom
, Width
, Height
;
212 /* Build the coordinates and sizes */
214 Width
= UiScreenWidth
;
216 Right
= (Left
+ Width
) - 1;
217 Top
= UiScreenHeight
- Height
- 4;
218 Bottom
= Top
+ Height
+ 1;
220 /* Draw the progress bar */
221 UiDrawProgressBar(Left
, Top
, Right
, Bottom
, Position
, Range
, ProgressText
);
225 UiDrawProgressBar(IN ULONG Left
,
231 IN PCHAR ProgressText
)
233 ULONG i
, ProgressBarWidth
;
235 /* Calculate the width of the bar proper */
236 ProgressBarWidth
= (Right
- Left
) - 3;
238 /* First make sure the progress bar text fits */
239 UiTruncateStringEllipsis(ProgressText
, ProgressBarWidth
- 4);
240 if (Position
> Range
) Position
= Range
;
242 /* Draw the "Loading..." text */
243 UiDrawCenteredText(Left
+ 2, Top
+ 1, Right
- 2, Top
+ 1, ProgressText
, ATTR(7, 0));
245 /* Draw the percent complete */
246 for (i
= 0; i
< (Position
* ProgressBarWidth
) / Range
; i
++)
248 /* Use the fill character */
249 UiDrawText(Left
+ 2 + i
, Top
+ 2, "\xDB", ATTR(UiTextColor
, UiMenuBgColor
));
254 UiShowMessageBoxesInSection(
255 IN ULONG_PTR SectionId
)
261 UiShowMessageBoxesInArgv(
269 UiTruncateStringEllipsis(IN PCHAR StringText
,
272 /* If it's too large, just add some ellipsis past the maximum */
273 if (strlen(StringText
) > MaxChars
)
274 strcpy(&StringText
[MaxChars
- 3], "...");
278 UiDrawMenuBox(IN PUI_MENU_INFO MenuInfo
)
280 CHAR MenuLineText
[80], TempString
[80];
283 /* If there is a timeout draw the time remaining */
284 if (MenuInfo
->MenuTimeRemaining
>= 0)
286 /* Copy the integral time text string, and remove the last 2 chars */
287 strcpy(TempString
, UiTimeText
);
288 i
= strlen(TempString
);
289 TempString
[i
- 2] = 0;
291 /* Display the first part of the string and the remaining time */
292 strcpy(MenuLineText
, TempString
);
293 _itoa(MenuInfo
->MenuTimeRemaining
, TempString
, 10);
294 strcat(MenuLineText
, TempString
);
296 /* Add the last 2 chars */
297 strcat(MenuLineText
, &UiTimeText
[i
- 2]);
299 /* Display under the menu directly */
301 MenuInfo
->Bottom
+ 4,
303 ATTR(UiMenuFgColor
, UiMenuBgColor
));
307 /* Erase the timeout string with spaces, and 0-terminate for sure */
308 for (i
=0; i
<sizeof(MenuLineText
)-1; i
++)
310 MenuLineText
[i
] = ' ';
312 MenuLineText
[sizeof(MenuLineText
)-1] = 0;
314 /* Draw this "empty" string to erase */
316 MenuInfo
->Bottom
+ 4,
318 ATTR(UiMenuFgColor
, UiMenuBgColor
));
322 for (i
= 0; i
< MenuInfo
->MenuItemCount
; i
++)
324 /* Check if it's a separator */
325 if (MenuInfo
->MenuItemList
[i
] == NULL
)
327 /* Draw the separator line */
328 UiDrawText(MenuInfo
->Left
,
329 MenuInfo
->Top
+ i
+ 1,
331 ATTR(UiMenuFgColor
, UiMenuBgColor
));
332 UiDrawText(MenuInfo
->Right
,
333 MenuInfo
->Top
+ i
+ 1,
335 ATTR(UiMenuFgColor
, UiMenuBgColor
));
341 UiDrawMenuItem(IN PUI_MENU_INFO MenuInfo
,
342 IN ULONG MenuItemNumber
)
344 CHAR MenuLineText
[80];
345 UCHAR Attribute
= ATTR(UiTextColor
, UiMenuBgColor
);
347 /* Simply left-align it */
348 MenuLineText
[0] = '\0';
349 strcat(MenuLineText
, " ");
351 /* Now append the text string */
352 if (MenuInfo
->MenuItemList
[MenuItemNumber
])
353 strcat(MenuLineText
, MenuInfo
->MenuItemList
[MenuItemNumber
]);
355 /* If it is a separator */
356 if (MenuInfo
->MenuItemList
[MenuItemNumber
] == NULL
)
358 /* Make it a separator line and use menu colors */
359 memset(MenuLineText
, 0, 80);
360 memset(MenuLineText
, 0xC4, (MenuInfo
->Right
- MenuInfo
->Left
- 1));
361 Attribute
= ATTR(UiMenuFgColor
, UiMenuBgColor
);
363 else if (MenuItemNumber
== MenuInfo
->SelectedMenuItem
)
365 /* If this is the selected item, use the selected colors */
366 Attribute
= ATTR(UiSelectedTextColor
, UiSelectedTextBgColor
);
370 UiDrawText(MenuInfo
->Left
+ 1,
371 MenuInfo
->Top
+ 1 + MenuItemNumber
,
377 UiDrawMenu(IN PUI_MENU_INFO MenuInfo
)
381 /* No GUI status bar text, just minimal text. Show the menu header. */
382 if (MenuInfo
->MenuHeader
)
386 MenuInfo
->MenuHeader
,
387 ATTR(UiMenuFgColor
, UiMenuBgColor
));
390 /* Now tell the user how to choose */
392 MenuInfo
->Bottom
+ 1,
393 "Use \x18 and \x19 to move the highlight to your choice.",
394 ATTR(UiMenuFgColor
, UiMenuBgColor
));
396 MenuInfo
->Bottom
+ 2,
397 "Press ENTER to choose.",
398 ATTR(UiMenuFgColor
, UiMenuBgColor
));
400 /* And show the menu footer */
401 if (MenuInfo
->MenuFooter
)
405 MenuInfo
->MenuFooter
,
406 ATTR(UiMenuFgColor
, UiMenuBgColor
));
409 /* Draw the menu box */
410 UiDrawMenuBox(MenuInfo
);
412 /* Draw each line of the menu */
413 for (i
= 0; i
< MenuInfo
->MenuItemCount
; i
++)
415 UiDrawMenuItem(MenuInfo
, i
);
418 /* Display the boot options if needed */
419 if (MenuInfo
->ShowBootOptions
)
421 DisplayBootTimeOptions();
426 UiProcessMenuKeyboardEvent(IN PUI_MENU_INFO MenuInfo
,
427 IN UiMenuKeyPressFilterCallback KeyPressFilter
)
430 ULONG Selected
, Count
;
432 /* Check for a keypress */
433 if (!MachConsKbHit())
434 return 0; // None, bail out
436 /* Check if the timeout is not already complete */
437 if (MenuInfo
->MenuTimeRemaining
!= -1)
439 /* Cancel it and remove it */
440 MenuInfo
->MenuTimeRemaining
= -1;
441 UiDrawMenuBox(MenuInfo
);
444 /* Get the key (get the extended key if needed) */
445 KeyEvent
= MachConsGetCh();
446 if (KeyEvent
== KEY_EXTENDED
)
447 KeyEvent
= MachConsGetCh();
450 * Call the supplied key filter callback function to see
451 * if it is going to handle this keypress.
453 if (KeyPressFilter
&&
454 KeyPressFilter(KeyEvent
, MenuInfo
->SelectedMenuItem
, MenuInfo
->Context
))
456 /* It processed the key character, so redraw and exit */
457 UiDrawMenu(MenuInfo
);
461 /* Process the key */
462 if ((KeyEvent
== KEY_UP
) || (KeyEvent
== KEY_DOWN
) ||
463 (KeyEvent
== KEY_HOME
) || (KeyEvent
== KEY_END
))
465 /* Get the current selected item and count */
466 Selected
= MenuInfo
->SelectedMenuItem
;
467 Count
= MenuInfo
->MenuItemCount
- 1;
469 /* Check the key and change the selected menu item */
470 if ((KeyEvent
== KEY_UP
) && (Selected
> 0))
472 /* Deselect previous item and go up */
473 MenuInfo
->SelectedMenuItem
--;
474 UiDrawMenuItem(MenuInfo
, Selected
);
477 // Skip past any separators
478 if ((Selected
> 0) &&
479 (MenuInfo
->MenuItemList
[Selected
] == NULL
))
481 MenuInfo
->SelectedMenuItem
--;
484 else if ( ((KeyEvent
== KEY_UP
) && (Selected
== 0)) ||
485 (KeyEvent
== KEY_END
) )
488 MenuInfo
->SelectedMenuItem
= Count
;
489 UiDrawMenuItem(MenuInfo
, Selected
);
491 else if ((KeyEvent
== KEY_DOWN
) && (Selected
< Count
))
493 /* Deselect previous item and go down */
494 MenuInfo
->SelectedMenuItem
++;
495 UiDrawMenuItem(MenuInfo
, Selected
);
498 // Skip past any separators
499 if ((Selected
< Count
) &&
500 (MenuInfo
->MenuItemList
[Selected
] == NULL
))
502 MenuInfo
->SelectedMenuItem
++;
505 else if ( ((KeyEvent
== KEY_DOWN
) && (Selected
== Count
)) ||
506 (KeyEvent
== KEY_HOME
) )
508 /* Go to the beginning */
509 MenuInfo
->SelectedMenuItem
= 0;
510 UiDrawMenuItem(MenuInfo
, Selected
);
513 /* Select new item and update video buffer */
514 UiDrawMenuItem(MenuInfo
, MenuInfo
->SelectedMenuItem
);
517 /* Return the pressed key */
522 UiCalcMenuBoxSize(IN PUI_MENU_INFO MenuInfo
)
524 ULONG i
, Width
= 0, Height
, Length
;
526 /* Height is the menu item count plus 2 (top border & bottom border) */
527 Height
= MenuInfo
->MenuItemCount
+ 2;
528 Height
-= 1; // Height is zero-based
530 /* Loop every item */
531 for (i
= 0; i
< MenuInfo
->MenuItemCount
; i
++)
533 /* Get the string length and make it become the new width if necessary */
534 if (MenuInfo
->MenuItemList
[i
])
536 Length
= (ULONG
)strlen(MenuInfo
->MenuItemList
[i
]);
537 if (Length
> Width
) Width
= Length
;
541 /* Allow room for left & right borders, plus 8 spaces on each side */
544 /* Put the menu in the default left-corner position */
548 /* The other margins are the same */
549 MenuInfo
->Right
= (MenuInfo
->Left
) + Width
;
550 MenuInfo
->Bottom
= (MenuInfo
->Top
) + Height
;
556 IN PCSTR MenuFooter OPTIONAL
,
557 IN BOOLEAN ShowBootOptions
,
558 IN PCSTR MenuItemList
[],
559 IN ULONG MenuItemCount
,
560 IN ULONG DefaultMenuItem
,
562 OUT PULONG SelectedMenuItem
,
563 IN BOOLEAN CanEscape
,
564 IN UiMenuKeyPressFilterCallback KeyPressFilter OPTIONAL
,
565 IN PVOID Context OPTIONAL
)
567 UI_MENU_INFO MenuInformation
;
568 ULONG LastClockSecond
;
569 ULONG CurrentClockSecond
;
573 * Before taking any default action if there is no timeout,
574 * check whether the supplied key filter callback function
575 * may handle a specific user keypress. If it does, the
576 * timeout is cancelled.
578 if (!MenuTimeOut
&& KeyPressFilter
&& MachConsKbHit())
580 /* Get the key (get the extended key if needed) */
581 KeyPress
= MachConsGetCh();
582 if (KeyPress
== KEY_EXTENDED
)
583 KeyPress
= MachConsGetCh();
586 * Call the supplied key filter callback function to see
587 * if it is going to handle this keypress.
589 if (KeyPressFilter(KeyPress
, DefaultMenuItem
, Context
))
591 /* It processed the key character, cancel the timeout */
596 /* Check if there's no timeout */
599 /* Return the default selected item */
600 if (SelectedMenuItem
) *SelectedMenuItem
= DefaultMenuItem
;
604 /* Setup the MENU_INFO structure */
605 MenuInformation
.MenuHeader
= MenuHeader
;
606 MenuInformation
.MenuFooter
= MenuFooter
;
607 MenuInformation
.ShowBootOptions
= ShowBootOptions
;
608 MenuInformation
.MenuItemList
= MenuItemList
;
609 MenuInformation
.MenuItemCount
= MenuItemCount
;
610 MenuInformation
.MenuTimeRemaining
= MenuTimeOut
;
611 MenuInformation
.SelectedMenuItem
= DefaultMenuItem
;
612 MenuInformation
.Context
= Context
;
614 /* Calculate the size of the menu box */
615 UiCalcMenuBoxSize(&MenuInformation
);
618 UiDrawMenu(&MenuInformation
);
620 /* Get the current second of time */
621 LastClockSecond
= ArcGetTime()->Second
;
626 /* Process key presses */
627 KeyPress
= UiProcessMenuKeyboardEvent(&MenuInformation
, KeyPressFilter
);
629 /* Check for ENTER or ESC */
630 if (KeyPress
== KEY_ENTER
) break;
631 if (CanEscape
&& KeyPress
== KEY_ESC
) return FALSE
;
633 /* Check if there is a countdown */
634 if (MenuInformation
.MenuTimeRemaining
> 0)
636 /* Get the updated time, seconds only */
637 CurrentClockSecond
= ArcGetTime()->Second
;
639 /* Check if more then a second has now elapsed */
640 if (CurrentClockSecond
!= LastClockSecond
)
642 /* Update the time information */
643 LastClockSecond
= CurrentClockSecond
;
644 MenuInformation
.MenuTimeRemaining
--;
646 /* Update the menu */
647 UiDrawMenuBox(&MenuInformation
);
650 else if (MenuInformation
.MenuTimeRemaining
== 0)
652 /* A time out occurred, exit this loop and return default OS */
657 /* Return the selected item */
658 if (SelectedMenuItem
) *SelectedMenuItem
= MenuInformation
.SelectedMenuItem
;